Make Drawing from a Photo

Photo to Drawing Code

This article proposes conversion of a photo image into a line drawing by using edge detect, smooth and enhancement process.

The script configures an edge detection algorithm, which is a multi-step process that detects a wide range of edges in images.

To make the outline more drawing-like, a smoothing filter is applied with a Gaussian blur to the edge-detected image.

Additionally, the PIL library’s ImageFilter module is used to enhance the drawing effect.

Edge Detection

The line edges = cv2.Canny(gray_image, threshold1=30, threshold2=150) applies the Canny edge detection algorithm to the grayscale image (gray_image). Hereโ€™s a detailed explanation of how this function works and what each parameter does:

Canny Edge Detection Algorithm

The Canny edge detection algorithm is a multi-step process that detects a wide range of edges in images. It is known for its effectiveness and efficiency.

The steps involved in the Canny edge detection algorithm are:

  1. Noise Reduction:
    • The algorithm first applies a Gaussian filter to the image to smooth it and reduce noise. This step is crucial because noise can lead to false edge detection.
    • In OpenCV’s cv2.Canny function, this step is handled internally.
  2. Gradient Calculation:
    • The algorithm calculates the intensity gradient of the image using Sobel operators. It computes the gradient in the x and y directions (Gx and Gy) and then calculates the gradient magnitude and direction.
    • The gradient magnitude represents the strength of the edge, and the gradient direction indicates the orientation of the edge.
  3. Non-Maximum Suppression:
    • To thin the edges, the algorithm performs non-maximum suppression. It keeps only the local maxima in the gradient direction and sets all other pixels to zero. This step ensures that the edges are thin and well-defined.
  4. Double Threshold:
    • The algorithm applies two thresholds to identify strong and weak edges.
    • Strong Edges: Pixels with gradient magnitudes above the high threshold (threshold2).
    • Weak Edges: Pixels with gradient magnitudes between the low threshold (threshold1) and the high threshold.
    • Non-Edges: Pixels with gradient magnitudes below the low threshold are discarded.
  5. Edge Tracking by Hysteresis:
    • The algorithm tracks edges by connecting weak edges to strong edges if they are connected directly or through other weak edges. This step helps in discarding weak edges that are not connected to any strong edge, thereby reducing the likelihood of false edges.

Function Parameters

  • gray_image: The input image in grayscale. The Canny edge detection algorithm works on single-channel images, so the input image is typically converted to grayscale before applying this function.
  • threshold1 (30): The lower threshold for the hysteresis procedure. Pixels with gradient magnitudes below this value are considered non-edges and are discarded.
  • threshold2 (150): The upper threshold for the hysteresis procedure. Pixels with gradient magnitudes above this value are considered strong edges and are retained.

Explanation of the Code Line

edges = cv2.Canny(gray_image, threshold1=30, threshold2=150)
  • gray_image: The grayscale image on which edge detection is performed.
  • threshold1=30: The lower bound for edge detection. Pixels with gradient values below 30 are ignored.
  • threshold2=150: The upper bound for edge detection. Pixels with gradient values above 150 are considered strong edges.

What the Function Does

  • The function cv2.Canny processes the input gray_image through the Canny edge detection algorithm.
  • It produces an output image edges, where the edges are marked with white pixels (255) and non-edges are marked with black pixels (0).

Practical Use Case

Using the Canny edge detection in image processing is common for applications like:

  • Detecting edges in images for computer vision tasks.
  • Preprocessing images to find object boundaries.
  • Assisting in feature extraction for image recognition and classification.

Example Code

Hereโ€™s a simple example to demonstrate the use of cv2.Canny:

import cv2
import matplotlib.pyplot as plt

# Load the image
image = cv2.imread('path/to/image.jpg')

# Convert the image to grayscale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# Apply Canny edge detection
edges = cv2.Canny(gray_image, threshold1=30, threshold2=150)

# Display the original image and the edge-detected image
plt.figure(figsize=(10, 5))

plt.subplot(1, 2, 1)
plt.title('Original Image')
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.axis('off')

plt.subplot(1, 2, 2)
plt.title('Edges')
plt.imshow(edges, cmap='gray')
plt.axis('off')

plt.show()

This example reads an image, converts it to grayscale, applies the Canny edge detection algorithm, and displays the original and edge-detected images side by side using Matplotlib.

Batch Image Processing

This script works to process all images in a folder, apply the desired image processing steps, and save each result with a unique identifier (UID):

Script Overview

The script consists of two main functions:

  1. process_image(image_path, output_folder):
    • This function processes a single image.
    • It reads the image, applies Canny edge detection, inverts the colors, smooths the edges with a Gaussian blur, enhances the edges to make them more drawing-like, and saves the processed image with a UID-based name.
  2. process_folder(input_folder, output_folder):
    • This function processes all images in the specified input folder.
    • It iterates over each image file in the input folder, calls process_image to process the image, and saves the result in the output folder.

Detailed Steps

1. Import Necessary Libraries

import os
import cv2
import uuid
from PIL import Image, ImageOps, ImageFilter
  • os: Used for handling file and directory operations.
  • cv2: OpenCV library for image processing.
  • uuid: Used to generate unique identifiers.
  • PIL (Pillow): Python Imaging Library for image operations.

2. Define process_image Function

def process_image(image_path, output_folder):
    # Read the image
    image = cv2.imread(image_path)

    # Verify if the image is loaded successfully
    if image is None:
        print(f"Error: Failed to load the image at path '{image_path}'.")
        return

    # Convert the image to grayscale
    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Apply Canny edge detection
    edges = cv2.Canny(gray_image, threshold1=50, threshold2=150)

    # Convert edges to a PIL image
    edges_pil = Image.fromarray(edges)

    # Invert the colors
    invert = ImageOps.invert(edges_pil)

    # Apply a Gaussian blur to smooth the edges
    blurred = invert.filter(ImageFilter.GaussianBlur(radius=1))

    # Enhance the edges to make them more drawing-like
    enhanced = blurred.filter(ImageFilter.EDGE_ENHANCE)

    # Generate a unique identifier (UID) for the output filename
    uid = uuid.uuid4()
    output_path = os.path.join(output_folder, f'{uid}.png')

    # Save the smoothed and enhanced edge-detected image
    enhanced.save(output_path)
    print(f"Saved: {output_path}")

Step-by-Step Explanation:

  • Read the Image: Uses OpenCV to read the image file from the specified path.
  • Check if Image is Loaded: Ensures the image is successfully loaded; if not, prints an error message.
  • Convert to Grayscale: Converts the color image to grayscale, which is necessary for edge detection.
  • Edge Detection: Applies the Canny edge detection algorithm to find the edges in the image.
  • Convert to PIL Image: Converts the resulting edges (a NumPy array) to a PIL Image object for further processing.
  • Invert Colors: Inverts the colors of the edge-detected image.
  • Apply Gaussian Blur: Applies a Gaussian blur to smooth the edges, giving a softer look.
  • Enhance Edges: Enhances the edges to make them more pronounced, creating a drawing-like effect.
  • Generate UID: Creates a unique identifier for the output filename.
  • Save Image: Saves the processed image to the output folder with the UID-based name.

3. Define process_folder Function

def process_folder(input_folder, output_folder):
    # Ensure the output folder exists
    os.makedirs(output_folder, exist_ok=True)

    # Process each image in the input folder
    for filename in os.listdir(input_folder):
        if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
            image_path = os.path.join(input_folder, filename)
            print(f"Processing: {image_path}")
            process_image(image_path, output_folder)

Step-by-Step Explanation:

  • Ensure Output Folder Exists: Creates the output folder if it doesn’t already exist.
  • Iterate Over Files: Loops through each file in the input folder.
    • Check File Extension: Processes only files with .png, .jpg, or .jpeg extensions (case-insensitive).
    • Process Image: Calls process_image for each valid image file, passing the file path and output folder.

4. Parameters and Script Execution

# Parameters
input_folder = '\input'  # Folder containing the grid images
output_folder = '\output'  # Folder to save the individual icons

# Run the batch processing
process_folder(input_folder, output_folder)
  • Set Input and Output Folders: Specifies the paths for the input and output folders.
  • Run the Batch Processing: Calls process_folder to process all images in the input folder and save the results in the output folder.

Summary

  • The script processes all images in the specified input folder.
  • Each image undergoes edge detection, color inversion, smoothing, and edge enhancement.
  • The processed images are saved in the output folder with unique UID-based filenames.
  • The script ensures that only valid image files are processed and handles errors if images cannot be loaded.