Skip to content

Commit ff124ae

Browse files
committed
improve & deploy
1 parent 7439864 commit ff124ae

3 files changed

Lines changed: 75 additions & 115 deletions

File tree

Binary file not shown.

ml-backend/app/models/sketch_enhancer_v2.py

Lines changed: 68 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -198,185 +198,145 @@ def _enhance_with_opencv(
198198
Advanced OpenCV enhancement pipeline
199199
200200
Pipeline stages:
201-
1. Multi-scale edge detection
202-
2. Morphological line connection
203-
3. Style-specific processing
204-
4. Post-processing (gamma, sharpening)
201+
1. Adaptive solid stroke extraction (avoids Canny double-edges)
202+
2. Style-specific processing
203+
3. Post-processing (gamma, sharpening)
205204
"""
206205

207206
print("Running OpenCV enhancement pipeline...")
208207

209-
# Stage 1: Multi-scale edge detection
210-
edges = self._detect_edges_multiscale(img)
208+
# Stage 1: Extract clean solid strokes
209+
strokes = self._extract_solid_strokes(img)
211210

212-
# Stage 2: Connect broken strokes
213-
edges = self._connect_strokes(edges)
214-
215-
# Stage 3: Apply style-specific processing
211+
# Stage 2: Apply style-specific processing
216212
if style == "professional":
217-
result = self._apply_professional_style(img, edges)
213+
result = self._apply_professional_style(img, strokes)
218214
elif style == "artistic":
219-
result = self._apply_artistic_style(img, edges)
215+
result = self._apply_artistic_style(img, strokes)
220216
elif style == "clean":
221-
result = self._apply_clean_style(img, edges)
217+
result = self._apply_clean_style(img, strokes)
222218
elif style == "minimal":
223-
result = self._apply_minimal_style(img, edges)
219+
result = self._apply_minimal_style(img, strokes)
224220
else:
225-
result = self._apply_professional_style(img, edges)
221+
result = self._apply_professional_style(img, strokes)
226222

227-
# Stage 4: Post-processing
223+
# Stage 3: Post-processing
228224
result = self._post_process(result, style)
229225

230226
print("[OK] OpenCV enhancement complete")
231227

232228
return result
233229

234-
def _detect_edges_multiscale(self, img: np.ndarray) -> np.ndarray:
230+
def _extract_solid_strokes(self, img: np.ndarray) -> np.ndarray:
235231
"""
236-
Multi-scale edge detection - combines three Canny passes
237-
Captures both strong and subtle edges
232+
Extract solid strokes from drawing using adaptive thresholding
233+
This completely avoids the double-edge outlines caused by Canny.
238234
"""
239235
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
240236

241-
# Three scales with different thresholds
242-
edges_fine = cv2.Canny(gray, 30, 100) # Captures subtle details
243-
edges_mid = cv2.Canny(gray, 50, 150) # Main strokes
244-
edges_strong = cv2.Canny(gray, 70, 200) # Strong features only
245-
246-
# Weighted combination (prioritize main strokes)
247-
edges = np.maximum(
248-
edges_strong,
249-
np.maximum(edges_mid * 0.7, edges_fine * 0.4)
250-
).astype(np.uint8)
251-
252-
return edges
253-
254-
def _connect_strokes(self, edges: np.ndarray) -> np.ndarray:
255-
"""
256-
Connect broken strokes using morphological operations
257-
"""
258-
# Dilation to connect nearby edges
259-
kernel_connect = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
260-
connected = cv2.dilate(edges, kernel_connect, iterations=1)
237+
# Bilateral filter removes paper texture noise while preserving sharp stroke boundaries
238+
smoothed = cv2.bilateralFilter(gray, 9, 75, 75)
239+
240+
# Adaptive threshold extracts local dark drawing strokes on light background
241+
thresh = cv2.adaptiveThreshold(
242+
smoothed,
243+
255,
244+
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
245+
cv2.THRESH_BINARY_INV,
246+
15, # local window size
247+
8 # constant offset
248+
)
261249

262-
# Thinning to restore line width
263-
kernel_thin = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (2, 2))
264-
connected = cv2.erode(connected, kernel_thin, iterations=1)
250+
# Clean small isolated noise specks
251+
kernel_clean = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
252+
cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel_clean)
265253

266-
# Remove small isolated noise
267-
kernel_denoise = np.ones((2, 2), np.uint8)
268-
connected = cv2.morphologyEx(connected, cv2.MORPH_OPEN, kernel_denoise)
254+
# Connect nearby stroke gaps
255+
kernel_smooth = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
256+
smoothed_strokes = cv2.morphologyEx(cleaned, cv2.MORPH_CLOSE, kernel_smooth)
269257

270-
return connected
258+
return smoothed_strokes
271259

272260
def _apply_professional_style(
273261
self,
274262
img: np.ndarray,
275-
edges: np.ndarray
263+
strokes: np.ndarray
276264
) -> np.ndarray:
277265
"""
278266
Professional technical drawing style
279267
- Clean white background
280-
- Pure black lines
281-
- Slight anti-aliasing
268+
- Solid, clean, anti-aliased black strokes
282269
"""
283-
# Create white background
284270
result = np.ones_like(img) * 255
285271

286-
# Apply edges in pure black
287-
result[edges > 0] = [0, 0, 0]
288-
289-
# Slight Gaussian blur for anti-aliasing
290-
result = cv2.GaussianBlur(result, (3, 3), 0.5)
272+
# Create premium anti-aliased borders
273+
mask = cv2.GaussianBlur(strokes, (3, 3), 0.5)
291274

275+
# Apply anti-aliased black strokes
276+
for c in range(3):
277+
result[:, :, c] = 255 - mask
278+
292279
return result
293280

294281
def _apply_artistic_style(
295282
self,
296283
img: np.ndarray,
297-
edges: np.ndarray
284+
strokes: np.ndarray
298285
) -> np.ndarray:
299286
"""
300287
Artistic pencil sketch style
301-
- Dodge blend technique
302-
- Textured appearance
303-
- Maintains some grayscale variation
288+
- Retains beautiful textured pencil graphite details
289+
- Completely purifies the paper background to clean white
304290
"""
305291
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
306292

307-
# Invert grayscale
308-
inv = 255 - gray
309-
310-
# Blur inverted image
311-
blur = cv2.GaussianBlur(inv, (21, 21), 0)
312-
313-
# Dodge blend (divide)
293+
# Bleach background to white using a dodge blend
294+
inv_gray = 255 - gray
295+
blur = cv2.GaussianBlur(inv_gray, (21, 21), 0)
314296
sketch = cv2.divide(gray, 255 - blur, scale=256)
315297

316-
# Overlay edges for definition
317-
sketch[edges > 127] = 0
318-
319-
# Convert back to RGB
320-
result = cv2.cvtColor(sketch, cv2.COLOR_GRAY2RGB)
298+
# Enhance strokes specifically with the solid mask for clean outline contrast
299+
enhanced_sketch = cv2.multiply(sketch, 255 - (strokes // 3), scale=1.0/255)
321300

301+
result = cv2.cvtColor(enhanced_sketch.astype(np.uint8), cv2.COLOR_GRAY2RGB)
322302
return result
323303

324304
def _apply_clean_style(
325305
self,
326306
img: np.ndarray,
327-
edges: np.ndarray
307+
strokes: np.ndarray
328308
) -> np.ndarray:
329309
"""
330310
Clean minimal style
331-
- High contrast binary
332-
- No grayscale variation
333-
- Sharp clean lines
311+
- Sharp, high-contrast, pure solid black lines
334312
"""
335-
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
336-
337-
# Adaptive threshold for uneven lighting
338-
binary = cv2.adaptiveThreshold(
339-
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
340-
cv2.THRESH_BINARY, 11, 2
341-
)
342-
343-
# Combine with edges
344-
binary[edges > 0] = 0
345-
346-
# Noise removal
347-
kernel = np.ones((2, 2), np.uint8)
348-
cleaned = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
349-
350-
# Convert to RGB
351-
result = cv2.cvtColor(cleaned, cv2.COLOR_GRAY2RGB)
352-
313+
result = np.ones_like(img) * 255
314+
result[strokes > 0] = [0, 0, 0]
353315
return result
354316

355317
def _apply_minimal_style(
356318
self,
357319
img: np.ndarray,
358-
edges: np.ndarray
320+
strokes: np.ndarray
359321
) -> np.ndarray:
360322
"""
361-
Ultra-minimal line drawing
362-
- Only strongest edges
363-
- Thin lines
364-
- Maximum simplicity
323+
Ultra-minimal thin line drawing
324+
- Elegantly thinned strokes
325+
- Fine, high-quality contours
365326
"""
366-
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
367-
368-
# Only keep strongest edges
369-
strong_edges = cv2.Canny(gray, 100, 200)
327+
# Erode the stroke mask to uniform thin centerlines
328+
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
329+
thinned = cv2.erode(strokes, kernel, iterations=1)
370330

371-
# Thin lines
372-
kernel = np.ones((2, 2), np.uint8)
373-
thinned = cv2.erode(strong_edges, kernel, iterations=1)
331+
# Anti-alias the fine strokes
332+
mask = cv2.GaussianBlur(thinned, (3, 3), 0.5)
374333

375-
# White background
376334
result = np.ones_like(img) * 255
377-
result[thinned > 0] = [0, 0, 0]
378-
335+
for c in range(3):
336+
result[:, :, c] = 255 - mask
337+
379338
return result
339+
380340

381341
def _post_process(
382342
self,

ml-backend/requirements.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ uvicorn[standard]
33
python-multipart
44
python-dotenv
55
sympy
6-
opencv-python-headless==4.9.0.80
7-
scikit-image==0.22.0
8-
scipy==1.11.4
9-
diffusers==0.25.0
10-
transformers==4.37.0
11-
accelerate==0.26.1
6+
opencv-python-headless>=4.9.0.80
7+
scikit-image>=0.22.0
8+
scipy>=1.11.4
9+
diffusers>=0.25.0
10+
transformers>=4.37.0
11+
accelerate>=0.26.1
1212
Pillow
13-
numpy==1.26.4
13+
numpy>=1.26.4
1414
requests
1515
google-generativeai
1616
easyocr

0 commit comments

Comments
 (0)