Converting an Anime Image to a Sketch

Using Edge-Detection — implemented in Python

gerdoo
5 min readMar 27, 2022

Edges are sudden changes in pixel intensities within an image. So, to detect the edges, we can go looking for pixels with sudden changes in the neighboring pixels.

I want to convert a sketch of an anime character so that my nephew can color it. As you will see, I am going to use Python to accomplish this conversion. Here is the anime image onto which we want to do our magic:

Uchiha Madara image about to get image-processed!
FIG. 1 — An image of Uchiha Madara from the Naruto anime. [Courtesy of Shōnen Jump].

The anime series is called Naruto — I think it is one of the most popular Anime series in the world. But the photo is from Uchiha Madara — one my favorite characters.
The edge detection algorithm I will be using involves (1) conversion to grayscale (2) noise reduction, (3) gradient calculation, and (4) thresholding. Well, let’s do this:

(1) Conversion to Grayscale

First, import packages that we are going to use:

import os
import cv2
import matplotlib.pyplot as plt

Next, let’s find the image on our computer memory and read the image into our program:

path = r’C:\User\Documents\gerdoo’
os.chdir(path)
file = path + ‘image.jpg’
image = cv2.imread(file, cv2.IMREAD_UNCHANGED)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

The last line in the above code converts the image into a grayscale version of the image. Note, however, that the cv2 function is BGR2GRAY and not RBG2GRAY. That is because cv2 by default stores the image channels in blue-green-red (BGR) order (not the more common red-blue-green (RBG) order). Next, let’s plot them side by side:

fig, ax = plt.subplots (1,2, figsize = (10,5))
ax[0].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
ax[0].axis(“off”)
ax[1].imshow(cv2.cvtColor(gray, cv2.COLOR_BGR2RGB))
ax[1].axis(“off”)
plt.show()

The result should look like this:

Uchiha Madara in grayscale illusion!
FIG. 2 — Conversion of the color image to a grayscale image

(2) Noise Reduction

This step is done to reduce noise in the image for better results. We can smooth the image by applying a Gaussian Kernel to the image. Gaussian kernel can have any size (you can think of it as a convolutional filter). Smaller kernels are less visible and larger kernels can be more effective. Let’s do it:

blur = cv2.GaussianBlur(gray,(3,3),cv2.BORDER_DEFAULT)

Single line of code, isn’t that amazing. Although python packages like OpenCV and SciPy have made this easy for us to implement, this is really not that simple of a process. Read more from Refs 3–5.

Uchiha Madara getting Gaussian blur
FIG. 3 — Conversion of the grayscale image to a blur image

We can’t see much of a difference in this image, because it is fairly simple image.

(3) Gradient Calculation

I think gradient calculation is the core of most edge detection methods. Gradient of an image can be defined as a directional change in image intensity. I come from a physics background and understand gradient as an operator that vectorizes a scalar distribution in direction of steepest/fastest increase. Mathematically, you can think of it this way:

Computing image gradients

Enough math already. Sobel and Scharr are two well-known gradient calculation methods and can be conveniently implemented using OpenCV. I am using Sobel here (use Scharr when you want more information content in the sketch).

grad_X = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
grad_Y = cv2.Sobel(gray, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)

Note that ‘grad_X’ compute the gradient across the x direction and ‘grad_Y’ compute the gradient across the y direction. But they are both float numbers and we should convert them to 8-bit integers (between 0 and 255). Like this:

grad_X = cv2.convertScaleAbs(grad_X)
grad_Y = cv2.convertScaleAbs(grad_Y)

Here, we are interested in a combined X and Y gradient. We can combine them like this:

combined = cv2.addWeighted(grad_X, 0.5, grad_Y, 0.5, 0)

And I did play around with the kernel sizes and decided that I like the blur kernel to be (3, 3). You can see some my experimentation in the figure below:

Combined gradient image with different blur kernel sizes
FIG. 4 — Combined gradient image with different blur kernel sizes

(4) Thresholding

We can already see ‘strong edges’ of the original image. Those are edges that essentially define boundaries of physical object and do not contain unnecessary texture information (unnecessary for edge detection algorithms). We can use thresholding to emphasize strong edges and drop weak edges. Like this:

ret,threshold_binary = cv2.threshold(gradient,100,255,cv2.THRESH_BINARY_INV)

Thresholding is one word and I have done it (with power of open source Python and its amazing community) in one line, but it is a huge topic in itself. Here, I used binary thresholding and loving how the end-result looks:

FIG. 5 — The complete edge detection method. The bottom-right panel is the end result and exactly what I had in mind before starting the project.

Almsot forgot to mention; you can generate the above subplot using Matplotlib:

fig, ax = plt.subplots (2,2, figsize = (10,6))
ax[0, 0].imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
ax[0, 0].axis(“off”)
ax[0, 0].title.set_text(“image”)
ax[0, 1].imshow(cv2.cvtColor(gray, cv2.COLOR_BGR2RGB))
ax[0, 1].axis(“off”)
ax[0, 1].title.set_text(“gray”)
ax[1, 0].imshow(cv2.cvtColor(gradient, cv2.COLOR_BGR2RGB))
ax[1, 0].axis(“off”)
ax[1, 0].title.set_text(“gradient”)
ax[1, 1].imshow(cv2.cvtColor(threshold_binary, cv2.COLOR_BGR2RGB))
ax[1, 1].axis(“off”)
ax[1, 1].title.set_text(“threshold_binary”)

This was not a difficult thresholding exercise. More complex images will demand more sophisticated algorithms to handle complexity of information content in those scenarios. I am hoping to write about them next.

Thanks for reading! If you liked it, please let me know by clapping, sharing, leaving a comment, following me, or sending me a message.

References:

[1] Sobel Derivatives, OpenCV, https://docs.opencv.org/2.4/doc/tutorials/imgproc/imgtrans/sobel_derivatives/sobel_derivatives.html

[2] Schar, rOptimale Operatoren in der Digitalen Bildverarbeitung, http://archiv.ub.uni-heidelberg.de/volltextserver/962/

[3] Sofiane Sahir, Canny Edge Detection Step by Step in Python — Computer Vision, https://towardsdatascience.com/canny-edge-detection-step-by-step-in-python-computer-vision-b49c3a2d8123

[4] Abhisek Jana, Applying Gaussian Smoothing to an Image using Python from scratch, http://www.adeveloperdiary.com/data-science/computer-vision/applying-gaussian-smoothing-to-an-image-using-python-from-scratch/

[5] Thresholding, Open CV documentation, https://docs.opencv.org/3.4/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1

--

--

gerdoo

I like easy-to-follow explanations and short-cut solutions. Here, I will write about those.