Back

Using Edge Detection to design my next tattoo

Note: If you just want to see the code, you can find it at mehularora8/pixel8.


I got my first tattoo in 2022 through a company called Ephemeral Tattoo. It was scheduled to fade after 1 year, but lasted a significant bit longer than that. When it finally started to fade, I decided to redesign the same concept using pixel art as the medium. Here's how I used Edge Detection to create pixel art.

Prerequisites

Convolution: https://en.wikipedia.org/wiki/Convolution

What is an Edge?

When trying to detecting edges, we're primarily dealing with pixel values. An "edge" can be defined as a sharp change in intensity in the image, or in other words, a high gradient in pixel values. The Canny Edge detector uses this trick to find edges.

Step 1: Input and Smoothing

Following some standard image reading using scikit-io, we use a 5x5 Gaussian Kernel to convolve the input image. This helps with noise reduction, i.e. reduces abrupt changes in pixel values. Noise can create false positives, which we want to avoid.

Step 2: Gradient Magnitude and Angle

We calculate the x (or horizontal) derivative of the image by convolving the image with a filter in the x direction and the y (or vertical) derivative of the image by convolving the image with a filter in the y direction. We get the total gradient magnitude by combining the x and y derivatives as

√(x² + y²)

We then calculate the Gradient Angle, or theta, i.e. the direction perpendicular to the edge at every pixel. This can be accomplished by calculating the arctan of the y over x gradients.

Step 3: Non Max Suppression

Simply put, non-max suppression picks the pixel with the strongest gradient in a given direction from setting the other gradients in the area to 0 for every pixel in the image. This helps with the precision of calculating edges.

To perform this step, we check each pixel's nearest neighbors in the direction of the gradient, and keep the one with the highest value.

Step 4: Thresholding and Linking

The Canny Edge Detector relies on the concept of "strong" edges, defined as pixel values that lie above a high threshold, and "weak" edges, defined as pixel values that lie between the high and a low threshold.

We use predetermined high and low thresholds, and set other values to 0. This process leaves us with two sets — a strong_edges set, and a weak_edges set. From now on, we can safely assume these exist as part of an edge.

Now, we don't want two different sets of edges. We want a single set of edges in the image. On a related note, we might also notice that these edges aren't necessary to be contiguous — and we'd be correct. The last step of edge detection process is linking strong and weak edges, which can be done by performing a Breadth First Search (or Depth First, depending on your preferences) over the our set of strong_edges. For each of these, we check if their neighbors belong in the weak_edges set, and if so, we consider them to be part of the strong edge.

Step 5: Pixelation

After thresholding, we only have pixel values that correspond to an edge. Here, we sample down by 2x. This step is optional, but I found that this helps with enhance pixelation effect.

And that's it! We have now implemented all the functions necessary to read an image, extract its edges and pixelate them to create pixel art from it.

Links

You can find the code here: mehularora8/pixel8 and the twitter thread here.