How I Created a Sketch-Making App Using Flask

4.5/5 - (2 votes)

Have you ever wanted to turn your photo into a pencil drawing but find it difficult to do so? Well, not everyone is endowed with such talent. Unless you want to learn to draw, you can use a sketch-making app to perform the task for you.

If you are an artist with lots of pictures to sketch, you can reduce your workload by using a sketch-making app to automate such tasks for you. In this tutorial, we will learn how to create a sketch-making app using the OpenCV library and have it displayed on a Flask web application.

OpenCV is an open-source library used for computer vision and Machine Learning. It is also used for image processing. Thus, it is the main library that we will use to sketch images. In upcoming tutorials, I will demonstrate how to design this app using OpenCV on a Streamlit web application.

Modules Required

  1. OpenCV – for image processing
  2. Flask – for creating a web application
  3. Os – to join two pathnames pointing to a file
  4. Secrets – to create secret keys
  5. Werkzeug – we will use one of its functions to secure a filename that can safely be stored on a regular file.

Getting Started

Create and change into a new folder for this project. Activate a virtual environment and install the above modules except for os and secrets which are Python in-built modules. Create another folder, app, in your current directory.

The Package Files

Inside the app folder, create an __init__.py file. By doing so Python will treat the app directory as a Python package.

The __init__.py file

The __init__.py file will contain the following code:

from flask import Flask
from secrets import token_hex


UPLOAD_FOLDER = 'app/static/uploads'
SECRET_KEY = token_hex(16)


app = Flask(__name__)

app.config['SEND_FILE_MAXIMUM_AGE_DEFAULT'] = 0
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['SECRET_KEY'] = SECRET_KEY


from app import main

We define some constant variables and create the Flask application object with the __name__ variable passed to the Flask class. This stores the name of the Python module being executed and can be used to load associated resources. The first variable defines the path where uploaded image files will be kept.

We use the token_hex() function from the secrets module to generate a random text string in hexadecimal. This will be our secret key for security. It generates a different key whenever the app is run. In a production environment, you will have to set a defined key that no one but you should know.

By setting the SEND_FILE_MAXIMUM_AGE_DEFAULT to zero in the app’s configuration settings, we disable the cache. This prevents the app from displaying old images on the web page.

We import the main module at the bottom and not at the top to prevent Flask from throwing errors resulting from circular imports. This is because the main module will also need the app variable to be imported.

So, once we import the app folder (now a package), the __init__.py will execute and every file inside the package will be treated as a module.

The Main File (Module)

Create a main.py file (module) inside the app folder (package) and write the following:

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



UPLOAD_FOLDER = 'app/static/uploads'
SKETCH_FOLDER = 'app/static/sketches'


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


@app.route('/sketch', methods=['GET', 'POST'])
def sketch():
    file = request.files['file']
    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)
        sketch_img = make_sketch(img)
        sketch_img_name = filename.split('.')[0]+'_sketch.jpg'
        save = cv2.imwrite(SKETCH_FOLDER+'/'+sketch_img_name, sketch_img)
        return render_template('home.html', org_img_name=filename, 
                               sketch_img_name=sketch_img_name)
    return render_template('home.html')

We use the @app.route decorator to register the functions that will be invoked (or run) upon request from a web browser. The first function simply calls the render_template() function to display the home.html web page.

And here is the markup file inside the templates folder. Make sure you create the folder inside the app package.

<!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'>
            <input type="file" id="file" name='file' style="margin-top:10px;margin-bottom:10px;">
            <button type='submit' class='btn btn-outline-success'> Make Sketch
            </button>
        </div>

    </form>

    {% if sketch_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/sketches/{{ sketch_img_name }}'
                style="display: block;margin-left: auto;margin-right: auto;">
        </div>
    </div>
    {% endif %}

</body>
</html>

A form will be displayed on the web page. The input type is set to file indicating that a file is to be uploaded. Notice that the file has the method set to POST indicating a POST request.

So, when a user uploads a file and hits the submit button, the file will be sent to the server for processing which in turn maps the route to /sketch as seen in the action attribute. Then the associated view function will execute.

The sketch() function uses request.files['file'] to fetch the files. The name (file) passed to it must be the same as the one set to the name attribute in the form.

But before further processing, the function must first check whether the file is received and that it is according to the required format. We define the required format in the file module which has its function already imported. Here is everything in the file.py module.

ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg'])

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.')[1] in ALLOWED_EXTENSIONS

Only image files with the above extensions are allowed. The allowed_file() function returns a Boolean. If every condition in the if statement in the view function is True, we get a suitable name, or rather, a secure filename by using the secure_filename() function.

What this function from werkzeug module does is, among other things, to make sure that no space is allowed for a given filename. Then, we save the file in the path already configured.

Now is the time for OpenCV, the library that forms the backbone of this project to be called. First of all, we use the imread() method to load the image that is stored in the uploads folder. Then, we call the make_sketch() function already imported to process and return a sketched image. Here is the function in the sketch.py module:

import cv2

def make_sketch(img):
    grayed = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    inverted = cv2.bitwise_not(grayed)
    blurred = cv2.GaussianBlur(inverted, (19,19), sigmaX=0, sigmaY=0)
    result = cv2.divide(grayed, 255 - blurred, scale=256)
    return result

The names assigned to the variables show the process the function passes through to turn the image to a pencil drawing. It first turns the image to a grayscale, a black-and-white color. Then, the white background is turned to black. Next, it blurs the image color. Finally, it restores the white background and turns the image into a pencil drawing.

All these are accomplished using various functions from the OpenCV library. Feel free to return each step and comment others to better understand the process.  If you want to learn more about these functions, check the documentation.

A few things you should know are:

  1. Computers take everything including images as numbers. So, the images are converted to a NumPy array for processing.
  2. In OpenCV, color images are ordered in BGR, not RGB. Any conversion must be from the default BGR as shown in the grayscale conversion. If for example, you want to display an image using the Matplotlib library, you have to convert to RGB using the COLOR_BGR2RGB() function.
  3. An image consists of pixels.  For a 640 * 480 image, the number of pixels is the multiplication of the numbers which is 307,200 pixels. Each pixel in a grayscale image represents a shade of gray, and in OpenCV, they are 256 in number ranging from 0-255.
  4. A blur effect is applied to smoothen an image.

To get started learning image processing with OpenCV, check out this beginner guide.

The sketched image is saved in a different folder and rendered on the home.html web page. The variables passed to the render_template function will be used by Jinja to display on the web page.

Finally, create another file, run.py in your project folder. The folder should have only two files, the app folder and the run.py file. The full code is on my GitHub page.

Inside the file, write this:

from app import app

if __name__ == '__main__':
    app.run()

Then type flask run in the terminal to open the local server. Make sure you are in the project folder.

πŸ’‘ Recommended: How I Created a REST API Using Django REST Framework

Conclusion

No doubt, it has been an interesting project. You have learned among other things:

  1. how to create a Flask application
  2. how to organize your files which gives you a good base structure for creating larger applications.
  3. How to convert images to pencil drawings using OpenCV.

The sketched image may not be exactly the way you want it to be due to the nature of the original image you uploaded. Moreover, the original color is removed. In that case, you may consider turning the image into a cartoon. I will give attention to that in upcoming project tutorials. Stay Tuned!

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