How I Created a Sketch-and-Cartoon-Making App Using Flask

5/5 - (3 votes)

Building a project with Python is one of the ways to get exposed to the programming language as well as its third-party libraries that reduce the amount of code we have to write.

For example, in our previous project tutorial on building a sketch-making app, we learned how to create a web application using the Flask framework and how to use OpenCV to process images and turn them into pencil drawings.

The amount of code we write was greatly reduced using Python libraries. Along the line, we learned useful programming concepts.

In this tutorial, we will add another feature that will give the user an option to choose whether to turn the uploaded image into a pencil drawing or a cartoon version. Since we will continue from the previous tutorial, you are required to attempt the project first and have everything including the Flask application set up to attempt this project.

The sketch-making app removes the color of the original image. But when we use the cartoon feature, the color will be preserved.

Libraries that turn an image into a cartoon

Three Python libraries can be used to turn a picture into a cartoon. Apart from using OpenCV, we also have Scikit-image and Pillow. Let’s first explore these libraries and see which one will work well for our application.

Using OpenCV

Open the Jupyter Notebook and create a new notebook with Python 3 (ipykernel).

Then import these libraries:

import cv2
import matplotlib.pyplot as plt

Load the image using the imread() method. I’m using my picture for this demonstration. You should follow along with your picture.

img = cv2.imread('image.jpg')

By default, OpenCV orders images using the BGR color. So, we have to turn it to RGB to display images using Matplotlib.

img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

The result is a NumPy array describing the format of each element. Remember, computers render images as numbers. Next, we convert to grayscale. A grayscale image is rendered from white to black.

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

The next thing we will do now is to apply Median blur to reduce noise and delete scratches on the image. To apply Median blur, we use the medianBlur() function but under the hood, it simply replaces the central element of the image with the median of all the pixels in the kernel area.

gray = cv2.medianBlur(gray, 5)

Next is to perform adaptive thresholding on the image. If you check the documentation, it means turning a grayscale image into a binary image.

edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 5)

For a detailed explanation of adaptive threshold, check this article.

Finally, converting to a cartoon version. To accomplish this, we will perform two operations. One, bilateral smoothing and the other, bitwise operator.

color = cv2.bilateralFilter(img, 9, 250, 250)

This smooths an image and reduces the noise much better than the other filters like Median blur.

cartoon = cv2.bitwise_and(color, color, mask=edges)

Here, we combine two images bitwise, and perform masking on them, the masking being the adaptive thresholding. This returns a cartoon version of the image.

Let’s visualize what we have done using the Matplotlib library.

plt.figure(figsize=(2,2))
plt.imshow(img)
plt.axis('off')
plt.title('Original Image')
plt.show()
plt.figure(figsize=(2,2))
plt.imshow(cartoon)
plt.axis('off')
plt.title('Cartoon Image')
plt.show()

We can see how they are. Not bad for a cartoon!

Using Scikit-image

Just as we did while using OpenCV, we will also read the image and convert it to a grayscale color.

from skimage import io, color
from skimage.segmentation import felzenszwalb

img = io.imread('image.jpg')
gray = color.rgb2gray(img)

Next, we perform Felzenszwalb image segmentation. Check this article to know more about the algorithm.

segment = felzenszwalb(img, scale=100, sigma=0.5, min_size=50)

Finally, we convert it to RGB image color to make it look like a cartoon.

cartoon = color.label2rgb(segment, img, kind='avg')

That’s another design from Scikit-image.

Using Pillow

We can also use Pillow to create a cartoon image.

from PIL import Image, ImageOps

img = Image.open('Jonaben.jpg')
cartoon = ImageOps.posterize(img, 2)

We have seen how to turn an image into a cartoon version using various Python libraries.

Adding a cartoon feature to a Flask application

I prefer the first method that uses OpenCV. We will use it to create the cartoon feature. Assuming you have gone through the previous project where we created a sketch-making app, add the following to a new file, cartoonify.py inside the app package.

import cv2


def cartoon(img):
    image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    gray = cv2.medianBlur(image, 5)
    edges = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 5)
    color = cv2.bilateralFilter(img, 9, 250, 250)
    cartoon = cv2.bitwise_and(color, color, mask=edges)
    return cartoon

let’s us adjust the main.py file a little bit to accommodate this additional feature. The file should now look like this:

import cv2
import os
from werkzeug.utils import secure_filename
from flask import request, render_template
from app import app
from app.file import allowed_file
from app.sketch import make_sketch
from app.cartoonify import cartoon


UPLOAD_FOLDER = 'app/static/uploads'
IMG_FOLDER = 'app/static/images'


@app.route('/')
def home():
    return render_template('home.html')


@app.route('/sketch', methods=['GET', 'POST'])
def sketch():
    file = request.files['file']
    op = request.form['choice']
    if file and allowed_file(file.filename):
        filename =secure_filename(file.filename)
        file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
        img = cv2.imread(UPLOAD_FOLDER+'/'+filename)
        if op == 'sketch':
            sketch_img = make_sketch(img)
            img_name = filename.split('.')[0]+'_sketch.jpg'
            save = cv2.imwrite(IMG_FOLDER+'/'+img_name, sketch_img)
            return render_template('home.html', org_img_name=filename, img_name=img_name)
        else:
            cartoon_img = cartoon(img)
            img_name = filename.split('.')[0]+'_cartoon.jpg'
            save = cv2.imwrite(IMG_FOLDER+'/'+img_name, cartoon_img)
            return render_template('home.html', org_img_name=filename, img_name=img_name)
     return render_template('home.html')

In the template file, we will use the select tag together with the option tag to display the options (sketch or cartoon). If the user selects sketch, the image will be converted to a pencil drawing. Otherwise, a cartoon image. Make sure you create a new images folder inside the static folder.

This is how the markup file will be:

<!DOCTYPE html>
<html lang="en">

<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="static/css/styles.css">
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous"><title>Sketchy</title>
</head>

<body>

    <div class='regform mt-3'>
        <h1>Flask Sketch-Making App</h1>
    </div>

    <form action='/sketch' class='main-form' method="POST" enctype="multipart/form-data">

        <div class='text-center'>
            <label for="file">Upload a file:</label>
            <input type="file" id="file" name='file' style="margin-top:10px;margin-bottom:10px;">
            <label for="choice">Select an option:</label>
            <select name="choice" id="choice">
              <option value="sketch">sketch</option>
              <option value="cartoon">cartoon</option>
            </select>
            <button type='submit' class='btn btn-outline-success'> Make Sketch
            </button>
        </div>

    </form>

    {% if img_name %}
    <div class="row" style="margin-top:10px;margin-bottom:10px;">
        <div class="col text-center">
            <h2>Original Image</h2><img src="static/uploads/{{ org_img_name }}"style="display: block;margin-left: auto;margin-right: auto;">
        </div>
        <div class="col text-center">
            <h2>Sketch Image</h2><img src="static/images/{{ img_name }}"style="display: block;margin-left: auto;margin-right: auto;">
             <a href="static/images/{{ img_name }}" download="images">Download</a>
        </div>
    </div>
    {% endif %}

</body>
</html>

We add a download link that will download the images when clicked. Notice that no file extension is given to the download link. The browser will automatically detect the extension.

Conclusion

We created a sketch-making app in the previous tutorial and added a cartoon feature to it. Our users now have the option to choose how they want their uploaded images to be processed.

We accomplished all these using OpenCV and the Flask framework. No doubt, you have learned a thing or two from this tutorial. I adjusted the full code on GitHub to prevent the sketch-making app from being redundant seeing that we have made many adjustments to it. Alright, that’s it for this tutorial. Have a nice day.

πŸ”— Recommended: How I Created a Sketch-Making App Using Flask