SECRET OF CSS

Image Processing in Python — Draw Aesthetic Portraits Using Only Nails and a Thread | by Ilias Nahmed | Aug, 2022


Unleash the artist that lives within you.

Marilyn Monroe by Georgia Fowler. Right photo by Author.

Artists have become more and more creative, so much so that they might have us thinking : “ how did they even know they could do that”. One way of drawing portraits that have become pretty famous on Instagram reels and Tiktok is doing it with a long thread, and a set of nails. The artist hammers the nails all over the circumference of a circular canvas, then starts linking the nails with the thread. While doing so, the image of the person drawn starts coming into being.

Having no talent whatsoever in drawing, I thought that, given the order in which the nails should be linked, even I could draw aesthetic portraits. All I had to do was to write a code that would provide me with this order of nails.

The main idea is quite a naive one. We take a grayscale picture and hammer imaginary nails into the circumference of the inscribed circle. Then, starting from a random nail, we calculate the obscurity of the line between this nail and all the other nails. The most obscure line will then be drawn in our canvas. We repeat this step hundreds of times, then we will acquire our final image.

What is an image to the computer’s eyes?

To implement this idea, we must imagine that the image is a matrix in which the elements are the pixels. Each pixel is coded in a 3-elements array : its RGB code. It is a code that denotes the intensity of the 3 primary colors : red, green, and blue.

In fact, since every color is just a combination of these three colors, we can define images by this type of arrays. We should note that the intensity of each color is an int value between 0 and 255, with 0 being the lowest intensity, and 255 the highest.

1*R4oM9p1aRqd5PzXBraYm3w
Photo on FreshersNow

The line linking every two nails can be seen as a vector of pixels. Evaluating its obscurity is the same as calculating the norm of the vector. The usual norms we might think of are the Euclidian norm, the L1-norm, and the infinite norm.

However, the infinite norm is not a precise one, as it will make us draw every line where there is a black pixel, even if it is the only non-white pixel on the line.

The obscurity of the upper line is inferior to the bottom line’s if we use the infinite norm, however, the upper line is overall darker than the other, hence it is the one that needs to be drawn.
The obscurity of the upper line is inferior to the bottom line’s if we use the infinite norm, however, the upper line is overall darker than the other, hence it is the one that needs to be drawn.

On the other hand, the Euclidian norm would be quite costly, as we are looking to repeat this process a few hundreds of times. That is why we will go with the L1-norm: we will be adding the value of the intensities of each pixel in the imaginary line, then in order to normalize, we will be dividing by the number of the pixels in the line (because there will be lines longer, yet not as obscure as other ones).

The line with the lowest “norm” is the most obscure one.

1*7E0Usu1tBkMj3PBL52Jmqg
Formula of obscurity, L being the line vector.

In each step, after selecting a line from the image, we will be turning the color of that very same line into white, so that we will not be drawing the same line again and again.

We will be using 3 main libraries: Numpy, Sci-kit Image (skimage), and Turtle.

Numpy, Skimage & Turtle

Skimage will allow us to process the image. It reads the picture (a matrix of RGB codes). It can also provide us with a vector of the coordinates of all pixels that are linking two pixels, which will be really useful for us in order to calculate the obscurity of the lines.

Numpy will allow us to do some maths operations, such as calculating the norm of the vector (obscurity of the lines) and determining the coordinates of the nails using angles and “cos” and “sine” functions.

Finally, Turtle will allow us to draw the final image. What is interesting with Turtle is that it shows the process of the drawing, so we do not have to wait for the code to finish in order to see the result.

We will first import the three previous libraries. Then, we will read the image we will be drawing and crop it into a square. Next, we will hammer our imaginary nails. We will be using 300 nails.

We open the canvas we will be drawing on with Turtle. However, this library uses coordinates as a cartesian grid : the 0 is in the center, the x-axis goes from left to right, and the y axis from bottom to top. But since the image we are working on is a matrix in a way, then the 0 is in the top left corner and the y axis is towards the bottom. We will need to translate our screen then.

In order to get the order of nails we will be linking, we will be concatenating the nails’ numbers in order into a string.

Now let’s get to the main part: the for loop.

We will start from the first nail in our list, which we will call next_nail. The first for loop allows us to choose the number of iterations we will be doing. It is basically the number of links we will be drawing. The nested for loop allows us to search through all the other nails which one is linked to our next_nail with the most obscure line. Hence, we will be comparing the obscurity parameter. rr1,cc1 is the vector of pixels we got with the draw.line function of the skimage library.

Afterward, we calculate the obscurity: it is the A/B parameter. Since we are looking for the most obscure line, we need the smallest A/B value, hence, in the j-loop, we update each time parameter next_nail with the best nail we can find, and the best_line with the line between our nail and next_nail. Finally, we draw in our Turtle canvas the best_line.

Note that we are using the “graph_nails” coordinates for the reason we specified upwards.

The execution of the code shows the full process of the drawing.

Here is a time-lapse of Lily Collins’ portrait

The making of Lily Collins’ portrait

The final result looked like this.

1*P3i29BXA wav8bJsU8n8Bg
Image by Author

The nails’ numbers that need to be linked are the following :

1*kQj NrPVaUBVTF gSOiZNw

It shows that it took about 7 minutes to draw this portrait.

The main limit of this code is the type of pictures it could be tried on. They need to have specific contrast details, otherwise, it wouldn’t look good afterward.

Another limit is that the photo needs to be as “square” as possible. If the ratio of the length to the width of the rectangle is too far away from 1, the square cropping will take out too much details, and provide a poor result.

The results depended on the quality of the photo. It could be very clear with some, and really unclear with others.

Lily Collins portrait
Left photo by Georgia Fowler. Right photo by Author.
1*ouNhZ8QRkSkharwtwLhyiQ
Left photo by Atakan-Erkut Uzun. Right photo by Author.
1*BCXrTYpleiBZYFOWHICVTg
An example of a bad result. Left photo by Thomas Fulfer. Right photo by Author.

This mini-project is a good introduction for beginners to image processing. There are still many improvements that can be made, like customizing a filter so that most pictures become compatible with the code. We can also take into account monochromatic photographs, which only need a few colored threads in order to provide a good result. One last thing that might be a good improvement of this project is making the machine learn how to identify the face details (eyes, nose, ears …): this way, linking the nails might become more precise.

Finally, I would like to thank you for reading this article until the end. I hope this was interesting. If you have any questions, feel free to leave a response.



News Credit

%d bloggers like this: