From eeee61108a9618931b5e39fdfa8fa9945b12d6c2 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Mon, 16 Mar 2026 14:42:51 +0100 Subject: [PATCH] fix: remove morph close that merged balloons into giant blob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 5x5 MORPH_CLOSE was connecting scattered color pixels into one page-spanning contour that swallowed individual balloons. Fix: - Remove MORPH_CLOSE, keep only MORPH_OPEN for speckle removal - Lower sat threshold 50→40 to catch more colored elements - Filter contours spanning >50% of width OR height (was AND) - Filter contours >10% of image area Co-Authored-By: Claude Opus 4.6 --- klausur-service/backend/cv_graphic_detect.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/klausur-service/backend/cv_graphic_detect.py b/klausur-service/backend/cv_graphic_detect.py index 0997a01..911ee5e 100644 --- a/klausur-service/backend/cv_graphic_detect.py +++ b/klausur-service/backend/cv_graphic_detect.py @@ -137,15 +137,14 @@ def detect_graphic_elements( # PASS 1 — COLOR CHANNEL (no word exclusion needed) # ===================================================================== # Saturated pixels = colored ink. Black text has sat ≈ 0 → invisible. - sat_mask = (hsv[:, :, 1] > 50).astype(np.uint8) * 255 + sat_mask = (hsv[:, :, 1] > 40).astype(np.uint8) * 255 # Exclude very bright backgrounds (white/near-white with color cast) - val_mask = (hsv[:, :, 2] < 235).astype(np.uint8) * 255 + val_mask = (hsv[:, :, 2] < 240).astype(np.uint8) * 255 color_mask = cv2.bitwise_and(sat_mask, val_mask) - # Morphological cleanup: close small gaps, remove speckle - kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) - color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, kernel) - kernel_open = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) + # Only remove tiny speckle — NO closing, which would merge nearby + # colored elements into one giant blob spanning half the page. + kernel_open = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2)) color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_OPEN, kernel_open) contours_color, _ = cv2.findContours( @@ -162,8 +161,8 @@ def detect_graphic_elements( if bw < 8 or bh < 8: continue - # Skip page-spanning contours (background color cast) - if bw > w * 0.8 and bh > h * 0.8: + # Skip page-spanning contours (background color cast / merged blobs) + if bw > w * 0.5 or bh > h * 0.5 or area > img_area * 0.10: continue perimeter = cv2.arcLength(cnt, True)