Upload 8 files
Browse files- Dockerfile +34 -0
- README.md +57 -0
- app.py +162 -0
- image_to_sketch.py +223 -0
- pyproject.toml +12 -0
- replit.md +64 -0
- requirements.txt +5 -0
- uv.lock +0 -0
Dockerfile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Create user for security
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
USER user
|
| 6 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 7 |
+
|
| 8 |
+
# Set working directory
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
|
| 11 |
+
# Install system dependencies
|
| 12 |
+
USER root
|
| 13 |
+
RUN apt-get update && apt-get install -y \
|
| 14 |
+
libglib2.0-0 \
|
| 15 |
+
libgomp1 \
|
| 16 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 17 |
+
|
| 18 |
+
USER user
|
| 19 |
+
|
| 20 |
+
# Copy requirements and install Python dependencies
|
| 21 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 22 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
| 23 |
+
|
| 24 |
+
# Copy application files
|
| 25 |
+
COPY --chown=user . /app
|
| 26 |
+
|
| 27 |
+
# Create necessary directories
|
| 28 |
+
RUN mkdir -p uploads results
|
| 29 |
+
|
| 30 |
+
# Expose port (will be set by Hugging Face)
|
| 31 |
+
EXPOSE 7860
|
| 32 |
+
|
| 33 |
+
# Start application with gunicorn (bind to PORT env var with 7860 fallback)
|
| 34 |
+
CMD ["bash", "-c", "exec gunicorn --workers=2 --threads=4 --timeout=60 --bind=0.0.0.0:${PORT:-7860} app:app"]
|
README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: SketchAI - Image to Sketch Converter
|
| 3 |
+
emoji: 🎨
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
license: mit
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# SketchAI - Image to Sketch Converter
|
| 13 |
+
|
| 14 |
+
Transform your photos into beautiful pencil sketch drawings with AI! SketchAI uses advanced computer vision techniques to convert regular photographs into artistic sketch representations.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
✨ **Beautiful Web Interface** - Stunning dark theme with animated gradient borders
|
| 19 |
+
🖼️ **Drag & Drop Upload** - Easy image uploading with support for multiple formats
|
| 20 |
+
⚙️ **Customizable Settings** - Adjust blur intensity and enable enhanced line definition
|
| 21 |
+
👁️ **Real-time Preview** - See your original and sketch side-by-side
|
| 22 |
+
💾 **Instant Download** - Get your sketch with one click
|
| 23 |
+
📱 **Mobile Friendly** - Works perfectly on all devices
|
| 24 |
+
|
| 25 |
+
## How It Works
|
| 26 |
+
|
| 27 |
+
1. **Upload** your image by dragging and dropping or browsing files
|
| 28 |
+
2. **Customize** the sketch settings (blur intensity, enhanced lines)
|
| 29 |
+
3. **Process** - Watch as AI transforms your photo into a sketch
|
| 30 |
+
4. **Download** your beautiful pencil sketch drawing
|
| 31 |
+
|
| 32 |
+
## Supported Formats
|
| 33 |
+
|
| 34 |
+
- JPG/JPEG
|
| 35 |
+
- PNG
|
| 36 |
+
- BMP
|
| 37 |
+
- TIFF
|
| 38 |
+
- WebP
|
| 39 |
+
|
| 40 |
+
## Technical Details
|
| 41 |
+
|
| 42 |
+
The application uses a multi-step image processing pipeline:
|
| 43 |
+
- **Grayscale Conversion** - Converts color images to grayscale
|
| 44 |
+
- **Image Inversion** - Creates negative for sketch effect
|
| 45 |
+
- **Gaussian Blur** - Applies customizable blur for artistic effect
|
| 46 |
+
- **Color Dodge Blending** - Combines images for realistic pencil sketch appearance
|
| 47 |
+
- **Optional Enhancement** - Adaptive thresholding for sharper lines
|
| 48 |
+
|
| 49 |
+
Built with Flask, OpenCV, and modern web technologies.
|
| 50 |
+
|
| 51 |
+
## Usage
|
| 52 |
+
|
| 53 |
+
Simply visit the app, upload any photo, adjust settings to your liking, and download your personalized sketch artwork!
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
*Created with ❤️ using OpenCV and Flask*
|
app.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import uuid
|
| 3 |
+
from flask import Flask, render_template, request, jsonify, send_from_directory, url_for
|
| 4 |
+
from werkzeug.utils import secure_filename
|
| 5 |
+
import cv2
|
| 6 |
+
import numpy as np
|
| 7 |
+
from PIL import Image
|
| 8 |
+
from image_to_sketch import ImageToSketch
|
| 9 |
+
|
| 10 |
+
app = Flask(__name__)
|
| 11 |
+
|
| 12 |
+
# Configuration
|
| 13 |
+
app.config['MAX_CONTENT_LENGTH'] = 10 * 1024 * 1024 # 10MB max file size
|
| 14 |
+
app.config['UPLOAD_FOLDER'] = 'uploads'
|
| 15 |
+
app.config['RESULT_FOLDER'] = 'results'
|
| 16 |
+
|
| 17 |
+
# Allowed file extensions
|
| 18 |
+
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'tiff', 'webp'}
|
| 19 |
+
|
| 20 |
+
def allowed_file(filename):
|
| 21 |
+
"""Check if file extension is allowed."""
|
| 22 |
+
return '.' in filename and \
|
| 23 |
+
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
| 24 |
+
|
| 25 |
+
def ensure_directories():
|
| 26 |
+
"""Ensure upload and result directories exist."""
|
| 27 |
+
for directory in [app.config['UPLOAD_FOLDER'], app.config['RESULT_FOLDER']]:
|
| 28 |
+
if not os.path.exists(directory):
|
| 29 |
+
os.makedirs(directory)
|
| 30 |
+
|
| 31 |
+
@app.route('/')
|
| 32 |
+
def index():
|
| 33 |
+
"""Serve the main page."""
|
| 34 |
+
return render_template('index.html')
|
| 35 |
+
|
| 36 |
+
@app.route('/convert', methods=['POST'])
|
| 37 |
+
def convert_image():
|
| 38 |
+
"""Handle image conversion to sketch."""
|
| 39 |
+
try:
|
| 40 |
+
# Check if image was uploaded
|
| 41 |
+
if 'image' not in request.files:
|
| 42 |
+
return jsonify({'error': 'No image file provided'}), 400
|
| 43 |
+
|
| 44 |
+
file = request.files['image']
|
| 45 |
+
if file.filename == '':
|
| 46 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 47 |
+
|
| 48 |
+
if not allowed_file(file.filename):
|
| 49 |
+
return jsonify({'error': 'Invalid file type. Please upload an image file.'}), 400
|
| 50 |
+
|
| 51 |
+
# Get processing options
|
| 52 |
+
blur_value = int(request.form.get('blur', 21))
|
| 53 |
+
enhance = request.form.get('enhance', 'false').lower() == 'true'
|
| 54 |
+
|
| 55 |
+
# Validate blur value
|
| 56 |
+
if blur_value < 1:
|
| 57 |
+
return jsonify({'error': 'Blur value must be positive'}), 400
|
| 58 |
+
|
| 59 |
+
# Make blur value odd
|
| 60 |
+
if blur_value % 2 == 0:
|
| 61 |
+
blur_value += 1
|
| 62 |
+
|
| 63 |
+
# Generate unique filename
|
| 64 |
+
file_id = str(uuid.uuid4())
|
| 65 |
+
if file.filename and '.' in file.filename:
|
| 66 |
+
original_ext = file.filename.rsplit('.', 1)[1].lower()
|
| 67 |
+
else:
|
| 68 |
+
original_ext = 'jpg' # Default extension
|
| 69 |
+
input_filename = f"{file_id}_original.{original_ext}"
|
| 70 |
+
output_filename = f"{file_id}_sketch.jpg"
|
| 71 |
+
|
| 72 |
+
# Save uploaded file
|
| 73 |
+
input_path = os.path.join(app.config['UPLOAD_FOLDER'], input_filename)
|
| 74 |
+
output_path = os.path.join(app.config['RESULT_FOLDER'], output_filename)
|
| 75 |
+
|
| 76 |
+
file.save(input_path)
|
| 77 |
+
|
| 78 |
+
# Convert image to sketch
|
| 79 |
+
converter = ImageToSketch()
|
| 80 |
+
sketch = converter.convert_to_sketch(
|
| 81 |
+
input_path,
|
| 82 |
+
output_path,
|
| 83 |
+
blur_value=blur_value,
|
| 84 |
+
enhance=enhance
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
# Generate URL for the result
|
| 88 |
+
sketch_url = url_for('get_result', filename=output_filename)
|
| 89 |
+
|
| 90 |
+
# Clean up input file after successful processing
|
| 91 |
+
try:
|
| 92 |
+
os.remove(input_path)
|
| 93 |
+
except Exception:
|
| 94 |
+
pass # Don't fail if cleanup fails
|
| 95 |
+
|
| 96 |
+
return jsonify({
|
| 97 |
+
'success': True,
|
| 98 |
+
'sketch_url': sketch_url,
|
| 99 |
+
'blur_value': blur_value,
|
| 100 |
+
'enhanced': enhance
|
| 101 |
+
})
|
| 102 |
+
|
| 103 |
+
except cv2.error as e:
|
| 104 |
+
print(f"OpenCV error processing image: {str(e)}")
|
| 105 |
+
return jsonify({'error': 'Invalid or corrupted image file'}), 400
|
| 106 |
+
except Exception as e:
|
| 107 |
+
print(f"Error processing image: {str(e)}")
|
| 108 |
+
return jsonify({'error': 'Failed to process image. Please try again.'}), 500
|
| 109 |
+
|
| 110 |
+
@app.route('/results/<filename>')
|
| 111 |
+
def get_result(filename):
|
| 112 |
+
"""Serve result images."""
|
| 113 |
+
return send_from_directory(app.config['RESULT_FOLDER'], filename)
|
| 114 |
+
|
| 115 |
+
# Removed /uploads route for security - originals should not be publicly accessible
|
| 116 |
+
|
| 117 |
+
@app.errorhandler(413)
|
| 118 |
+
def too_large(e):
|
| 119 |
+
"""Handle file too large error."""
|
| 120 |
+
return jsonify({'error': 'File size exceeds 10MB limit'}), 413
|
| 121 |
+
|
| 122 |
+
@app.errorhandler(500)
|
| 123 |
+
def internal_error(e):
|
| 124 |
+
"""Handle internal server errors."""
|
| 125 |
+
return jsonify({'error': 'Internal server error'}), 500
|
| 126 |
+
|
| 127 |
+
def cleanup_old_files():
|
| 128 |
+
"""Clean up old uploaded and result files."""
|
| 129 |
+
import time
|
| 130 |
+
current_time = time.time()
|
| 131 |
+
max_age = 24 * 60 * 60 # 24 hours
|
| 132 |
+
|
| 133 |
+
for folder in [app.config['UPLOAD_FOLDER'], app.config['RESULT_FOLDER']]:
|
| 134 |
+
if os.path.exists(folder):
|
| 135 |
+
for filename in os.listdir(folder):
|
| 136 |
+
file_path = os.path.join(folder, filename)
|
| 137 |
+
if os.path.isfile(file_path):
|
| 138 |
+
file_age = current_time - os.path.getctime(file_path)
|
| 139 |
+
if file_age > max_age:
|
| 140 |
+
try:
|
| 141 |
+
os.remove(file_path)
|
| 142 |
+
print(f"Cleaned up old file: {file_path}")
|
| 143 |
+
except Exception as e:
|
| 144 |
+
print(f"Error cleaning up {file_path}: {e}")
|
| 145 |
+
|
| 146 |
+
if __name__ == '__main__':
|
| 147 |
+
ensure_directories()
|
| 148 |
+
|
| 149 |
+
# Clean up old files on startup
|
| 150 |
+
try:
|
| 151 |
+
cleanup_old_files()
|
| 152 |
+
except Exception as e:
|
| 153 |
+
print(f"Error during cleanup: {e}")
|
| 154 |
+
|
| 155 |
+
# Auto-detect port: prefer PORT env var, else 7860 for HF, else 5000 for local
|
| 156 |
+
port = int(os.environ.get('PORT') or (7860 if 'SPACE_ID' in os.environ else 5000))
|
| 157 |
+
|
| 158 |
+
# Start the Flask development server
|
| 159 |
+
print("Starting SketchAI Web Application...")
|
| 160 |
+
print(f"Open your browser and navigate to: http://localhost:{port}")
|
| 161 |
+
|
| 162 |
+
app.run(host='0.0.0.0', port=port, debug=False)
|
image_to_sketch.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Image to Sketch Converter
|
| 4 |
+
A Python program that converts images to pencil sketch style drawings.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import cv2
|
| 8 |
+
import numpy as np
|
| 9 |
+
from PIL import Image
|
| 10 |
+
import argparse
|
| 11 |
+
import os
|
| 12 |
+
import sys
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ImageToSketch:
|
| 16 |
+
"""Class to handle image to sketch conversion operations."""
|
| 17 |
+
|
| 18 |
+
def __init__(self):
|
| 19 |
+
# Note: OpenCV supports many formats, so we mainly rely on cv2.imread success
|
| 20 |
+
self.supported_formats = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp']
|
| 21 |
+
|
| 22 |
+
def validate_image_file(self, filepath):
|
| 23 |
+
"""Validate if the file exists and has a supported format."""
|
| 24 |
+
if not os.path.exists(filepath):
|
| 25 |
+
raise FileNotFoundError(f"Image file not found: {filepath}")
|
| 26 |
+
|
| 27 |
+
file_ext = os.path.splitext(filepath)[1].lower()
|
| 28 |
+
if file_ext not in self.supported_formats:
|
| 29 |
+
raise ValueError(f"Unsupported image format: {file_ext}. Supported formats: {', '.join(self.supported_formats)}")
|
| 30 |
+
|
| 31 |
+
return True
|
| 32 |
+
|
| 33 |
+
def load_image(self, filepath):
|
| 34 |
+
"""Load image from file path."""
|
| 35 |
+
self.validate_image_file(filepath)
|
| 36 |
+
|
| 37 |
+
# Load image using OpenCV
|
| 38 |
+
image = cv2.imread(filepath)
|
| 39 |
+
if image is None:
|
| 40 |
+
raise ValueError(f"Could not load image: {filepath}")
|
| 41 |
+
|
| 42 |
+
return image
|
| 43 |
+
|
| 44 |
+
def convert_to_grayscale(self, image):
|
| 45 |
+
"""Convert color image to grayscale."""
|
| 46 |
+
return cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 47 |
+
|
| 48 |
+
def invert_image(self, image):
|
| 49 |
+
"""Invert the grayscale image (create negative)."""
|
| 50 |
+
return 255 - image
|
| 51 |
+
|
| 52 |
+
def apply_gaussian_blur(self, image, blur_value=21):
|
| 53 |
+
"""Apply Gaussian blur to the image."""
|
| 54 |
+
# Validate blur value
|
| 55 |
+
if blur_value < 1:
|
| 56 |
+
raise ValueError(f"Blur value must be positive (>= 1), got: {blur_value}")
|
| 57 |
+
if blur_value % 2 == 0:
|
| 58 |
+
blur_value += 1
|
| 59 |
+
return cv2.GaussianBlur(image, (blur_value, blur_value), 0)
|
| 60 |
+
|
| 61 |
+
def create_sketch_blend(self, grayscale_img, blurred_img):
|
| 62 |
+
"""Create sketch effect by blending grayscale and blurred images."""
|
| 63 |
+
# Use color dodge blend mode to create sketch effect
|
| 64 |
+
sketch = cv2.divide(grayscale_img, 255 - blurred_img, scale=256)
|
| 65 |
+
return sketch
|
| 66 |
+
|
| 67 |
+
def enhance_sketch(self, sketch):
|
| 68 |
+
"""Enhance the sketch with additional processing."""
|
| 69 |
+
# Apply adaptive threshold for better line definition
|
| 70 |
+
enhanced = cv2.adaptiveThreshold(
|
| 71 |
+
sketch, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 9
|
| 72 |
+
)
|
| 73 |
+
return enhanced
|
| 74 |
+
|
| 75 |
+
def convert_to_sketch(self, input_path, output_path=None, blur_value=21, enhance=False):
|
| 76 |
+
"""
|
| 77 |
+
Main method to convert image to sketch.
|
| 78 |
+
|
| 79 |
+
Args:
|
| 80 |
+
input_path (str): Path to input image
|
| 81 |
+
output_path (str): Path to save sketch (optional)
|
| 82 |
+
blur_value (int): Gaussian blur intensity (default: 21)
|
| 83 |
+
enhance (bool): Apply enhancement for better line definition
|
| 84 |
+
|
| 85 |
+
Returns:
|
| 86 |
+
numpy.ndarray: Sketch image array
|
| 87 |
+
"""
|
| 88 |
+
print(f"Loading image: {input_path}")
|
| 89 |
+
|
| 90 |
+
# Load the original image
|
| 91 |
+
original_image = self.load_image(input_path)
|
| 92 |
+
|
| 93 |
+
# Convert to grayscale
|
| 94 |
+
print("Converting to grayscale...")
|
| 95 |
+
grayscale = self.convert_to_grayscale(original_image)
|
| 96 |
+
|
| 97 |
+
# Invert the grayscale image
|
| 98 |
+
print("Creating inverted image...")
|
| 99 |
+
inverted = self.invert_image(grayscale)
|
| 100 |
+
|
| 101 |
+
# Apply Gaussian blur to inverted image
|
| 102 |
+
print(f"Applying Gaussian blur (blur value: {blur_value})...")
|
| 103 |
+
blurred = self.apply_gaussian_blur(inverted, blur_value)
|
| 104 |
+
|
| 105 |
+
# Create sketch by blending
|
| 106 |
+
print("Creating sketch blend...")
|
| 107 |
+
sketch = self.create_sketch_blend(grayscale, blurred)
|
| 108 |
+
|
| 109 |
+
# Apply enhancement if requested
|
| 110 |
+
if enhance:
|
| 111 |
+
print("Enhancing sketch...")
|
| 112 |
+
sketch = self.enhance_sketch(sketch)
|
| 113 |
+
|
| 114 |
+
# Save the sketch if output path is provided
|
| 115 |
+
if output_path:
|
| 116 |
+
self.save_sketch(sketch, output_path)
|
| 117 |
+
print(f"Sketch saved to: {output_path}")
|
| 118 |
+
|
| 119 |
+
return sketch
|
| 120 |
+
|
| 121 |
+
def save_sketch(self, sketch, output_path):
|
| 122 |
+
"""Save sketch image to file."""
|
| 123 |
+
# Ensure output directory exists
|
| 124 |
+
output_dir = os.path.dirname(output_path)
|
| 125 |
+
if output_dir and not os.path.exists(output_dir):
|
| 126 |
+
os.makedirs(output_dir)
|
| 127 |
+
|
| 128 |
+
# Save using OpenCV
|
| 129 |
+
success = cv2.imwrite(output_path, sketch)
|
| 130 |
+
if not success:
|
| 131 |
+
raise ValueError(f"Failed to save sketch to: {output_path}")
|
| 132 |
+
|
| 133 |
+
def display_comparison(self, original_path, sketch):
|
| 134 |
+
"""Display original and sketch side by side using matplotlib."""
|
| 135 |
+
try:
|
| 136 |
+
import matplotlib.pyplot as plt
|
| 137 |
+
|
| 138 |
+
# Load original for comparison
|
| 139 |
+
original = self.load_image(original_path)
|
| 140 |
+
original_rgb = cv2.cvtColor(original, cv2.COLOR_BGR2RGB)
|
| 141 |
+
|
| 142 |
+
# Create side-by-side comparison
|
| 143 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
|
| 144 |
+
|
| 145 |
+
axes[0].imshow(original_rgb)
|
| 146 |
+
axes[0].set_title('Original Image')
|
| 147 |
+
axes[0].axis('off')
|
| 148 |
+
|
| 149 |
+
axes[1].imshow(sketch, cmap='gray')
|
| 150 |
+
axes[1].set_title('Sketch')
|
| 151 |
+
axes[1].axis('off')
|
| 152 |
+
|
| 153 |
+
plt.tight_layout()
|
| 154 |
+
plt.show()
|
| 155 |
+
|
| 156 |
+
except ImportError:
|
| 157 |
+
print("Matplotlib not available for display. Sketch conversion completed successfully.")
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def main():
|
| 161 |
+
"""Main function to handle command line interface."""
|
| 162 |
+
parser = argparse.ArgumentParser(
|
| 163 |
+
description="Convert images to pencil sketch style drawings",
|
| 164 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 165 |
+
epilog="""
|
| 166 |
+
Examples:
|
| 167 |
+
python image_to_sketch.py input.jpg
|
| 168 |
+
python image_to_sketch.py input.jpg -o sketch.png
|
| 169 |
+
python image_to_sketch.py input.jpg -o sketch.png --blur 25 --enhance
|
| 170 |
+
python image_to_sketch.py input.jpg --display
|
| 171 |
+
"""
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
parser.add_argument('input', help='Input image file path')
|
| 175 |
+
parser.add_argument('-o', '--output', help='Output sketch file path')
|
| 176 |
+
parser.add_argument('--blur', type=int, default=21,
|
| 177 |
+
help='Gaussian blur value (default: 21, must be odd)')
|
| 178 |
+
parser.add_argument('--enhance', action='store_true',
|
| 179 |
+
help='Apply enhancement for better line definition')
|
| 180 |
+
parser.add_argument('--display', action='store_true',
|
| 181 |
+
help='Display original and sketch comparison')
|
| 182 |
+
|
| 183 |
+
args = parser.parse_args()
|
| 184 |
+
|
| 185 |
+
# Validate blur value (must be positive and odd)
|
| 186 |
+
if args.blur < 1:
|
| 187 |
+
print(f"Error: Blur value must be positive (>= 1), got: {args.blur}", file=sys.stderr)
|
| 188 |
+
sys.exit(1)
|
| 189 |
+
if args.blur % 2 == 0:
|
| 190 |
+
args.blur += 1
|
| 191 |
+
print(f"Blur value adjusted to {args.blur} (must be odd)")
|
| 192 |
+
|
| 193 |
+
# Generate output filename if not provided
|
| 194 |
+
if not args.output:
|
| 195 |
+
input_name, input_ext = os.path.splitext(args.input)
|
| 196 |
+
args.output = f"{input_name}_sketch{input_ext}"
|
| 197 |
+
|
| 198 |
+
try:
|
| 199 |
+
# Create converter instance
|
| 200 |
+
converter = ImageToSketch()
|
| 201 |
+
|
| 202 |
+
# Convert image to sketch
|
| 203 |
+
sketch = converter.convert_to_sketch(
|
| 204 |
+
args.input,
|
| 205 |
+
args.output,
|
| 206 |
+
blur_value=args.blur,
|
| 207 |
+
enhance=args.enhance
|
| 208 |
+
)
|
| 209 |
+
|
| 210 |
+
print(f"\n✓ Successfully converted '{args.input}' to sketch!")
|
| 211 |
+
print(f"✓ Sketch saved as: '{args.output}'")
|
| 212 |
+
|
| 213 |
+
# Display comparison if requested
|
| 214 |
+
if args.display:
|
| 215 |
+
converter.display_comparison(args.input, sketch)
|
| 216 |
+
|
| 217 |
+
except Exception as e:
|
| 218 |
+
print(f"Error: {e}", file=sys.stderr)
|
| 219 |
+
sys.exit(1)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
if __name__ == "__main__":
|
| 223 |
+
main()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "repl-nix-workspace"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
requires-python = ">=3.11"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"flask>=3.1.2",
|
| 8 |
+
"matplotlib>=3.10.6",
|
| 9 |
+
"numpy>=2.3.3",
|
| 10 |
+
"opencv-python>=4.11.0.86",
|
| 11 |
+
"pillow>=11.3.0",
|
| 12 |
+
]
|
replit.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Image to Sketch Converter
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
This is a Python-based image processing application that converts regular photographs and images into pencil sketch style drawings. The application uses computer vision techniques to transform color images into artistic sketch representations, providing a simple command-line interface for image conversion operations.
|
| 6 |
+
|
| 7 |
+
## Recent Changes
|
| 8 |
+
|
| 9 |
+
### September 29, 2025
|
| 10 |
+
- Created complete image-to-sketch conversion program (`image_to_sketch.py`)
|
| 11 |
+
- Implemented core algorithms: grayscale conversion, image inversion, Gaussian blur, and color-dodge blending
|
| 12 |
+
- Added comprehensive command-line interface with help system
|
| 13 |
+
- Included optional sketch enhancement with adaptive thresholding
|
| 14 |
+
- **NEW: Built beautiful web interface with Flask backend**
|
| 15 |
+
- Adapted stunning dark theme design for sketch conversion
|
| 16 |
+
- Drag & drop file upload with animated gradient borders
|
| 17 |
+
- Real-time image processing with visual feedback
|
| 18 |
+
- Side-by-side original vs sketch comparison
|
| 19 |
+
- Adjustable settings (blur intensity, enhanced lines)
|
| 20 |
+
- Secure file handling and automatic cleanup
|
| 21 |
+
- Successfully tested complete web application and verified output quality
|
| 22 |
+
- Configured workflow for easy web server deployment
|
| 23 |
+
|
| 24 |
+
## User Preferences
|
| 25 |
+
|
| 26 |
+
Preferred communication style: Simple, everyday language.
|
| 27 |
+
|
| 28 |
+
## System Architecture
|
| 29 |
+
|
| 30 |
+
### Core Processing Architecture
|
| 31 |
+
- **Object-oriented design**: Built around the `ImageToSketch` class that encapsulates all conversion logic and image processing operations
|
| 32 |
+
- **Pipeline-based processing**: Uses a multi-step conversion process including grayscale conversion and image inversion as the foundation for sketch creation
|
| 33 |
+
- **File validation system**: Implements robust input validation to ensure image files exist and are in supported formats before processing
|
| 34 |
+
|
| 35 |
+
### Image Processing Framework
|
| 36 |
+
- **OpenCV integration**: Primary image processing library for loading, manipulating, and converting images with support for multiple image formats
|
| 37 |
+
- **PIL/Pillow support**: Secondary image handling capability for additional format compatibility and image operations
|
| 38 |
+
- **NumPy arrays**: Underlying data structure for efficient image pixel manipulation and mathematical operations
|
| 39 |
+
|
| 40 |
+
### Input/Output Handling
|
| 41 |
+
- **Multi-format support**: Handles common image formats including JPG, PNG, BMP, TIFF, and WebP files
|
| 42 |
+
- **Command-line interface**: Uses argparse for parsing command-line arguments and user input processing
|
| 43 |
+
- **Error handling**: Comprehensive validation for file existence, format compatibility, and image loading failures
|
| 44 |
+
|
| 45 |
+
### Processing Pipeline
|
| 46 |
+
- **Grayscale conversion**: First step converts color images to grayscale using OpenCV's color space transformation
|
| 47 |
+
- **Image inversion**: Creates negative images by inverting pixel values (255 - pixel_value) as part of the sketch effect
|
| 48 |
+
- **Extensible design**: Architecture allows for easy addition of additional processing steps in the conversion pipeline
|
| 49 |
+
|
| 50 |
+
## External Dependencies
|
| 51 |
+
|
| 52 |
+
### Core Libraries
|
| 53 |
+
- **OpenCV (cv2)**: Primary computer vision library for image loading, processing, and format handling
|
| 54 |
+
- **NumPy**: Numerical computing library for efficient array operations and pixel manipulation
|
| 55 |
+
- **PIL/Pillow**: Python Imaging Library for additional image processing capabilities and format support
|
| 56 |
+
|
| 57 |
+
### System Dependencies
|
| 58 |
+
- **Python 3**: Requires Python 3.x runtime environment
|
| 59 |
+
- **argparse**: Built-in Python module for command-line argument parsing
|
| 60 |
+
- **os and sys**: Standard library modules for file system operations and system interaction
|
| 61 |
+
|
| 62 |
+
### File System Requirements
|
| 63 |
+
- **Input validation**: Depends on local file system for image file validation and loading
|
| 64 |
+
- **Format detection**: Uses file extensions and OpenCV's built-in format detection for image compatibility
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask==3.1.2
|
| 2 |
+
opencv-python-headless==4.11.0.86
|
| 3 |
+
numpy==2.3.3
|
| 4 |
+
pillow==11.3.0
|
| 5 |
+
gunicorn==23.0.0
|
uv.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|