|
|
|
|
|
""" |
|
|
Image to Sketch Converter |
|
|
A Python program that converts images to pencil sketch style drawings. |
|
|
""" |
|
|
|
|
|
import cv2 |
|
|
import numpy as np |
|
|
from PIL import Image |
|
|
import argparse |
|
|
import os |
|
|
import sys |
|
|
|
|
|
|
|
|
class ImageToSketch: |
|
|
"""Class to handle image to sketch conversion operations.""" |
|
|
|
|
|
def __init__(self): |
|
|
|
|
|
self.supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp'] |
|
|
|
|
|
def validate_image_file(self, filepath): |
|
|
"""Validate if the file exists and has a supported format.""" |
|
|
if not os.path.exists(filepath): |
|
|
raise FileNotFoundError(f"Image file not found: {filepath}") |
|
|
|
|
|
file_ext = os.path.splitext(filepath)[1].lower() |
|
|
if file_ext not in self.supported_formats: |
|
|
raise ValueError(f"Unsupported image format: {file_ext}. Supported formats: {', '.join(self.supported_formats)}") |
|
|
|
|
|
return True |
|
|
|
|
|
def load_image(self, filepath): |
|
|
"""Load image from file path.""" |
|
|
self.validate_image_file(filepath) |
|
|
|
|
|
|
|
|
image = cv2.imread(filepath) |
|
|
if image is None: |
|
|
raise ValueError(f"Could not load image: {filepath}") |
|
|
|
|
|
return image |
|
|
|
|
|
def convert_to_grayscale(self, image): |
|
|
"""Convert color image to grayscale.""" |
|
|
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
|
|
|
|
|
def invert_image(self, image): |
|
|
"""Invert the grayscale image (create negative).""" |
|
|
return 255 - image |
|
|
|
|
|
def apply_gaussian_blur(self, image, blur_value=21): |
|
|
"""Apply Gaussian blur to the image.""" |
|
|
|
|
|
if blur_value < 1: |
|
|
raise ValueError(f"Blur value must be positive (>= 1), got: {blur_value}") |
|
|
if blur_value % 2 == 0: |
|
|
blur_value += 1 |
|
|
return cv2.GaussianBlur(image, (blur_value, blur_value), 0) |
|
|
|
|
|
def create_sketch_blend(self, grayscale_img, blurred_img): |
|
|
"""Create sketch effect by blending grayscale and blurred images.""" |
|
|
|
|
|
sketch = cv2.divide(grayscale_img, 255 - blurred_img, scale=256) |
|
|
return sketch |
|
|
|
|
|
def enhance_sketch(self, sketch): |
|
|
"""Enhance the sketch with additional processing.""" |
|
|
|
|
|
enhanced = cv2.adaptiveThreshold( |
|
|
sketch, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9 |
|
|
) |
|
|
return enhanced |
|
|
|
|
|
def convert_to_sketch(self, input_path, output_path=None, blur_value=21, enhance=False): |
|
|
""" |
|
|
Main method to convert image to sketch. |
|
|
|
|
|
Args: |
|
|
input_path (str): Path to input image |
|
|
output_path (str): Path to save sketch (optional) |
|
|
blur_value (int): Gaussian blur intensity (default: 21) |
|
|
enhance (bool): Apply enhancement for better line definition |
|
|
|
|
|
Returns: |
|
|
numpy.ndarray: Sketch image array |
|
|
""" |
|
|
print(f"Loading image: {input_path}") |
|
|
|
|
|
|
|
|
original_image = self.load_image(input_path) |
|
|
|
|
|
|
|
|
print("Converting to grayscale...") |
|
|
grayscale = self.convert_to_grayscale(original_image) |
|
|
|
|
|
|
|
|
print("Creating inverted image...") |
|
|
inverted = self.invert_image(grayscale) |
|
|
|
|
|
|
|
|
print(f"Applying Gaussian blur (blur value: {blur_value})...") |
|
|
blurred = self.apply_gaussian_blur(inverted, blur_value) |
|
|
|
|
|
|
|
|
print("Creating sketch blend...") |
|
|
sketch = self.create_sketch_blend(grayscale, blurred) |
|
|
|
|
|
|
|
|
if enhance: |
|
|
print("Enhancing sketch...") |
|
|
sketch = self.enhance_sketch(sketch) |
|
|
|
|
|
|
|
|
if output_path: |
|
|
self.save_sketch(sketch, output_path) |
|
|
print(f"Sketch saved to: {output_path}") |
|
|
|
|
|
return sketch |
|
|
|
|
|
def save_sketch(self, sketch, output_path): |
|
|
"""Save sketch image to file.""" |
|
|
|
|
|
output_dir = os.path.dirname(output_path) |
|
|
if output_dir and not os.path.exists(output_dir): |
|
|
os.makedirs(output_dir) |
|
|
|
|
|
|
|
|
success = cv2.imwrite(output_path, sketch) |
|
|
if not success: |
|
|
raise ValueError(f"Failed to save sketch to: {output_path}") |
|
|
|
|
|
def display_comparison(self, original_path, sketch): |
|
|
"""Display original and sketch side by side using matplotlib.""" |
|
|
try: |
|
|
import matplotlib.pyplot as plt |
|
|
|
|
|
|
|
|
original = self.load_image(original_path) |
|
|
original_rgb = cv2.cvtColor(original, cv2.COLOR_BGR2RGB) |
|
|
|
|
|
|
|
|
fig, axes = plt.subplots(1, 2, figsize=(12, 6)) |
|
|
|
|
|
axes[0].imshow(original_rgb) |
|
|
axes[0].set_title('Original Image') |
|
|
axes[0].axis('off') |
|
|
|
|
|
axes[1].imshow(sketch, cmap='gray') |
|
|
axes[1].set_title('Sketch') |
|
|
axes[1].axis('off') |
|
|
|
|
|
plt.tight_layout() |
|
|
plt.show() |
|
|
|
|
|
except ImportError: |
|
|
print("Matplotlib not available for display. Sketch conversion completed successfully.") |
|
|
|
|
|
|
|
|
def main(): |
|
|
"""Main function to handle command line interface.""" |
|
|
parser = argparse.ArgumentParser( |
|
|
description="Convert images to pencil sketch style drawings", |
|
|
formatter_class=argparse.RawDescriptionHelpFormatter, |
|
|
epilog=""" |
|
|
Examples: |
|
|
python image_to_sketch.py input.jpg |
|
|
python image_to_sketch.py input.jpg -o sketch.png |
|
|
python image_to_sketch.py input.jpg -o sketch.png --blur 25 --enhance |
|
|
python image_to_sketch.py input.jpg --display |
|
|
""" |
|
|
) |
|
|
|
|
|
parser.add_argument('input', help='Input image file path') |
|
|
parser.add_argument('-o', '--output', help='Output sketch file path') |
|
|
parser.add_argument('--blur', type=int, default=21, |
|
|
help='Gaussian blur value (default: 21, must be odd)') |
|
|
parser.add_argument('--enhance', action='store_true', |
|
|
help='Apply enhancement for better line definition') |
|
|
parser.add_argument('--display', action='store_true', |
|
|
help='Display original and sketch comparison') |
|
|
|
|
|
args = parser.parse_args() |
|
|
|
|
|
|
|
|
if args.blur < 1: |
|
|
print(f"Error: Blur value must be positive (>= 1), got: {args.blur}", file=sys.stderr) |
|
|
sys.exit(1) |
|
|
if args.blur % 2 == 0: |
|
|
args.blur += 1 |
|
|
print(f"Blur value adjusted to {args.blur} (must be odd)") |
|
|
|
|
|
|
|
|
if not args.output: |
|
|
input_name, input_ext = os.path.splitext(args.input) |
|
|
args.output = f"{input_name}_sketch{input_ext}" |
|
|
|
|
|
try: |
|
|
|
|
|
converter = ImageToSketch() |
|
|
|
|
|
|
|
|
sketch = converter.convert_to_sketch( |
|
|
args.input, |
|
|
args.output, |
|
|
blur_value=args.blur, |
|
|
enhance=args.enhance |
|
|
) |
|
|
|
|
|
print(f"\n✓ Successfully converted '{args.input}' to sketch!") |
|
|
print(f"✓ Sketch saved as: '{args.output}'") |
|
|
|
|
|
|
|
|
if args.display: |
|
|
converter.display_comparison(args.input, sketch) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"Error: {e}", file=sys.stderr) |
|
|
sys.exit(1) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |