Image Styling & Cartoonify Flask web app using OpenCV in Python

From last few years, we have seen lots of image styling apps are available in android and app store market. But in this article, we will build our own flask based web app which not only do image styling but also cartoonify the image.

So for styling the images we will be using filters defined in OpenCV. So first filter we will be using is detail enhancing filter. This filter enhances the finer details of the image and the image looks more sharper in comparison to original image.

1. Detail Enhancing Filter

The code for using detail enhancing filter using opencv is given below:

import cv2
img = cv2.imread('image1.jpg')
dst = cv2.detailEnhance(img, sigma_s=10, sigma_r=0.15)

Parameters description is given below:

Parameters

srcInput 8-bit 3-channel image.
dstOutput image with the same size and type as src.
sigma_sRange between 0 to 200.
sigma_rRange between 0 to 1.
Table describing the parameters of detail enhancing filter in opencv

Results of detail enhancing filter

As we can see the result of detail enhancing filter i.e., the image on the right side is much sharper and enhanced in comparison to original image.

Comparison of original vs detail enhancing filter output image

Next filter we will be using in the app is pencil sketch effect filter that is also available in opencv.

2. Pencil Sketch Filter

This filter produces an output which converts the image into pencil sketch and in this filter we get two types of images one is grey scale pencil sketch while the other one is colored pencil sketch.

The code for pencil sketch is shared below.

img = cv2.imread('image1.jpg')
imout_gray, imout = cv2.pencilSketch(img, sigma_s=60, sigma_r=0.07, shade_factor=0.05)

The parameters in pencil sketch filter is same as detail enhancing filter except one additional parameter i.e., shade factor. Its value ranges between 0 to 1. The higher the value the brighter the image. This parameter is used for scaling the image intensity.

Results of Pencil Sketch filter

Image with Pencil Sketch filter using opencv

Next we will be converting the image into cartoon.

3. Cartoon Effect

For converting the image to cartoon, we have to perform multiple transformations. First we will transform the image to grayscale.

# Convert the input image to gray scale 
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

In the next step, we will retrieve the edges and then highlight them using adaptive threshold filter. In this we will pass greyscale image obtained from step 1. The threshold value represents the difference of mean of the neighborhood pixel values and the constant C. Thresh_binary is the type of threshold applied, and the remaining parameters represents the size of the block.

# Perform adaptive threshold for retrieving edges
edges  = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 8)

In the next step we will perform color quantization. Color quantization is the process of reducing the number of colors in the original image. But to perform color quantization the input data need to be in a single column. So for that we will be reshaping the image to a single column. Next we will use k-means clustering and for that we will using cv2.kmeans() function. Here parameter k represents number of clusters, whereas criteria represents iteration termination criteria. This function returns three output but we will be focusing only on center of the clusters only.

# Defining input data for clustering
    data = np.float32(img).reshape((-1, 3))

# Defining criteria
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

# Applying cv2.kmeans function
_, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
center = np.uint8(center)

 # Reshape the output data to the size of input image
result = center[label.flatten()]
result = result.reshape(img.shape)

Next we will reshape the output image into original image size and further we will smoothen the resulting image using median blur technique.

In the last step we will combine this blurred image with edges in the form of masks using bitwise and operation in opencv.

# Smooth the result
blurred = cv2.medianBlur(result, 3)

# Combine the result and edges to get final cartoon effect
cartoon = cv2.bitwise_and(blurred, blurred, mask=edges)

Results of Cartoon Effect

Comparison of original image vs cartoonized image output

4. Water Color

In this effect, we will use open cv stylize method to give water color effect to the input image.

# Convert the input image to gray scale 
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
# stylization of image
img_style = cv2.stylization(img, sigma_s=150,sigma_r=0.25)

Before using stylization method, we have to convert the image to RGB format as the default color palette after reading the image is BGR.

Results of Water Color Effect

Comparison of original image vs water color effect

As we have seen all the functionalities which we will be employing in our app, now we will move towards building a flask based web app which allows user to upload any image and on clicking stylize button it can convert original image into different effects which we have demonstrated earlier in this article.

Flask based web app

So, before building a web app we first have to create a virtual environment so that it should not effect your current python environment.

conda create -n cartoonenv python=3.8

So, by executing above command a virtual environment will be created with the name cartoonenv and python version 3.8

After creating environment we have to navigate to web app folder named CARTOONIZER and then we have to activate the environment.

After activating the environment we have to install all the required libraries using the command “pip install -r requirements.txt“.

The content of the requirements.txt file is shown below.

Next we will see the folder structure of our flask web app.

Folder structure of Cartoonizer flask app

So, in this we have two basic folders that flask app requires i.e., static and templates folder. The static folder contains basic CSS files, images for giving style while designing the user interface of the web app whereas templates folder contains all the HTML pages of the web app.

Apart from that we have tow other folders i.e., cartoon_images where the output of image stylization will be stored whereas uploads folder contains those images which will be uploaded using uploader for applying image stylization.

Now we will look into the code of HTML pages under templates folder. So the first page is base.html. So in this we can observe that we have used bootstrap css for giving basic style and navbars for creating basic menus for the web app.

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Cartoonizer Web App</title>
    <link href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/popper.js/1.12.9/umd/popper.min.js"></script>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdn.bootcss.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>

    
    <style>

        .float-container {
            border: 3px solid #fff;
            padding: 20px;
        }
        
        .float-child {
            width: 50%;
            float: left;
            padding: 20px;
            border: 2px solid red;
        }  

    .a {
            text-align: center;
          }
    </style>
</head>

<body>
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <a class="navbar-brand" href="#">Cartoonizer Web App</a>
      
        <div class="collapse navbar-collapse" id="navbarTogglerDemo03">
          <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
            <li class="nav-item active">
              <a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
            </li>
     
           
          </ul>
          <form class="form-inline my-2 my-lg-0">
            <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
            <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
          </form>
        </div>
      </nav>
    <div class="container">
        <div id="content" style="margin-top:2em">{% block content %}{% endblock %}</div>
    </div>
</body>

</html>

Next we have used block content inside the container div so that we can populate the prediction within this div by extending the base.html in index.html and predict.html pages for giving uniform design without explicitly coding design part in these pages independently.

The home page of our web app is index.html where we will actually upload the image for stylization and also choose which stylization we want for our uploaded image. The code for index.html is given below.

{% extends "base.html" %} {% block content %}

<h2></h2>

<div>
    <form id="upload-file"  action="/predict" method="POST" enctype="multipart/form-data">
      <center> {% for message in get_flashed_messages() %}
        <div class="alert alert-danger alert-dismissible fade show" role="alert">
          {{ message }}
          <button type="button" class="close" data-dismiss="alert" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>

        {% endfor %}
      </center>

      <div class="card">
        <div class="card-header">
         <h4> Upload Image </h4>
        </div>
        <div class="card-body">
          <h5 class="card-title">Browse Image</h5>
          <p class="card-text"><input type="file" id="file" name="file" ></p>
          <p>
            <label class="my-1 mr-2" for="inlineFormCustomSelectPref">Choose Style</label>
            <select class="custom-select my-1 mr-sm-2" id="inlineFormCustomSelectPref">
              <option selected>Choose...</option>
              <option value="Style6">Image Enhancement</option>
              <option value="Style1">Cartoon</option>
              <option value="Style2">Water color</option>
              <option value="Style5">Pastel color</option>
              <option value="Style3">Pencil Sketch</option>
              <option value="Style4">Pencil Color Sketch</option>
            </select>
          
          </p>
          
          <input type="submit" class="btn btn-primary " name="submit" value="Stylize">
        </div>
      </div>
 
    </form>

    <div class="loader" style="display:none;"></div>

    <h3 id="result">
        <span> </span>
    </h3>

</div>

{% endblock %}

The output of the web page is also shown in below image.

Home page of the web app

Next we will see the predict.html page code which is the output page of this web app.

{% extends "base.html" %} {% block content %}

<div class="float-container">

    <div class="float-child">
        <h2 class="a">Uploaded Image</h2>
      <div class="green"><img src="{{ url_for('upload_img', filename=file_name) }}" class="img-fluid"></div>
    </div>
    
    <div class="float-child">
        <h2 class="a">Cartoonized Image</h2>
      <div class="blue"><img src="{{ url_for('cartoon_img', filename=cartoon_file) }}" class="img-fluid"></div>
    </div>
    
  </div>
 
{% endblock %}

Next most important file is app.py where all the python code is residing.

The python code for the web app is shared below.

# coding=utf-8
import sys
import os, shutil
import glob
import re
import numpy as np
import cv2

# Flask utils
from flask import Flask,flash, request, render_template,send_from_directory
from werkzeug.utils import secure_filename


# Define a flask app
app = Flask(__name__, static_url_path='')
app.secret_key = os.urandom(24)

app.config['CARTOON_FOLDER'] = 'cartoon_images'
app.config['UPLOAD_FOLDER'] = 'uploads'


@app.route('/uploads/<filename>')
def upload_img(filename):
    
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

@app.route('/cartoon_images/<filename>')
def cartoon_img(filename):
    
    return send_from_directory(app.config['CARTOON_FOLDER'], filename)


def cartoonize_1(img, k):

    # Convert the input image to gray scale 
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Peform adaptive threshold
    edges  = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 8)

    # cv2.imshow('edges', edges)

    # Defining input data for clustering
    data = np.float32(img).reshape((-1, 3))

  

    # Defining criteria
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0)

    # Applying cv2.kmeans function
    _, label, center = cv2.kmeans(data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)
    center = np.uint8(center)
    # print(center)

    # Reshape the output data to the size of input image
    result = center[label.flatten()]
    result = result.reshape(img.shape)
    #cv2.imshow("result", result)

    # Smooth the result
    blurred = cv2.medianBlur(result, 3)

    # Combine the result and edges to get final cartoon effect
    cartoon = cv2.bitwise_and(blurred, blurred, mask=edges)

    return cartoon

def cartoonize_2(img):

    # Convert the input image to gray scale 
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    # stylization of image
    img_style = cv2.stylization(img, sigma_s=150,sigma_r=0.25)
    
    return img_style

def cartoonize_3(img):

    # Convert the input image to gray scale 
    
    
    # pencil sketch  of image
    
    imout_gray, imout = cv2.pencilSketch(img, sigma_s=60, sigma_r=0.07, shade_factor=0.05)
    
    return imout_gray

def cartoonize_4(img):

    # Convert the input image to gray scale 
    
    
    # pencil sketch  of image
    
    imout_gray, imout = cv2.pencilSketch(img, sigma_s=60, sigma_r=0.07, shade_factor=0.05)
    
    return imout

def cartoonize_5(img, k):

    # Convert the input image to gray scale 
    img1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img1g=cv2.cvtColor(img1,cv2.COLOR_RGB2GRAY)
    img1b=cv2.medianBlur(img1g,3)
    #Clustering - (K-MEANS)
    imgf=np.float32(img1).reshape(-1,3)
    criteria=(cv2.TERM_CRITERIA_EPS+cv2.TERM_CRITERIA_MAX_ITER,20,1.0)
    compactness,label,center=cv2.kmeans(imgf,k,None,criteria,10,cv2.KMEANS_RANDOM_CENTERS)
    center=np.uint8(center)
    final_img=center[label.flatten()]
    final_img=final_img.reshape(img1.shape)
    edges=cv2.adaptiveThreshold(img1b,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,3,3)
    final=cv2.bitwise_and(final_img,final_img,mask=edges)

    return final

def cartoonize_6(img):

    # Convert the input image to gray scale 
    
    
    # pencil sketch  of image
    
    dst = cv2.detailEnhance(img, sigma_s=10, sigma_r=0.15)
    
    return dst


@app.route('/', methods=['GET'])
def index():
    # Main page
    return render_template('index.html')


@app.route('/predict', methods=['GET', 'POST'])
def predict():
    if request.method == 'POST':
        # Get the file from post request
        
        f = request.files['file']
        style = request.form.get('style')
        print(style)
        # Save the file to ./uploads
        basepath = os.path.dirname(__file__)
        file_path = os.path.join(
            basepath, 'uploads', secure_filename(f.filename))
        
        f.save(file_path)
        file_name=os.path.basename(file_path)
        
        # reading the uploaded image
        
        img = cv2.imread(file_path)
        if style =="Style1":
            cart_fname =file_name + "_style1_cartoon.jpg"
            cartoonized = cartoonize_1(img, 8)
            cartoon_path = os.path.join(
                basepath, 'cartoon_images', secure_filename(cart_fname))
            fname=os.path.basename(cartoon_path)
            print(fname)
            cv2.imwrite(cartoon_path,cartoonized)
            return render_template('predict.html',file_name=file_name, cartoon_file=fname)
        elif style =="Style2":
            cart_fname =file_name + "_style2_cartoon.jpg"
            cartoonized = cartoonize_2(img)
            cartoon_path = os.path.join(
                basepath, 'cartoon_images', secure_filename(cart_fname))
            fname=os.path.basename(cartoon_path)
            print(fname)
            cv2.imwrite(cartoon_path,cartoonized)
            return render_template('predict.html',file_name=file_name, cartoon_file=fname)
        elif style=="Style3":
            cart_fname =file_name + "_style3_cartoon.jpg"
            cartoonized = cartoonize_3(img)
            cartoon_path = os.path.join(
                basepath, 'cartoon_images', secure_filename(cart_fname))
            fname=os.path.basename(cartoon_path)
            print(fname)
            cv2.imwrite(cartoon_path,cartoonized)
            return render_template('predict.html',file_name=file_name, cartoon_file=fname)
        elif style=="Style4":
            cart_fname =file_name + "_style4_cartoon.jpg"
            cartoonized = cartoonize_4(img)
            cartoon_path = os.path.join(
                basepath, 'cartoon_images', secure_filename(cart_fname))
            fname=os.path.basename(cartoon_path)
            print(fname)
            cv2.imwrite(cartoon_path,cartoonized)
            return render_template('predict.html',file_name=file_name, cartoon_file=fname)
        elif style=="Style5":
            cart_fname =file_name + "_style5_cartoon.jpg"
            cartoonized = cartoonize_5(img,5)
            cartoon_path = os.path.join(
                basepath, 'cartoon_images', secure_filename(cart_fname))
            fname=os.path.basename(cartoon_path)
            print(fname)
            cv2.imwrite(cartoon_path,cartoonized)
            return render_template('predict.html',file_name=file_name, cartoon_file=fname)
        elif style=="Style6":
            cart_fname =file_name + "_style6_cartoon.jpg"
            cartoonized = cartoonize_6(img)
            cartoon_path = os.path.join(
                basepath, 'cartoon_images', secure_filename(cart_fname))
            fname=os.path.basename(cartoon_path)
            print(fname)
            cv2.imwrite(cartoon_path,cartoonized)
            return render_template('predict.html',file_name=file_name, cartoon_file=fname)
        else:
             flash('Please select style')
             return render_template('index.html')
            
       
              
        
    return ""

if __name__ == '__main__':
        app.run(debug=True, host="localhost", port=8080)

So, now we will run this web app by executing command python app.py in the terminal. After executing the command the web app will be available on localhost:8080

The full code demonstrated in this video is available in this repo.

The below video shows the functionality of the web app once it is available on localhost.

Leave a Comment