Devashishraghav commited on
Commit
080728c
·
verified ·
1 Parent(s): 5f25ffa

Upload processor/bg_removal.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. processor/bg_removal.py +125 -0
processor/bg_removal.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from rembg import remove
4
+
5
+
6
+ def remove_background_and_crop(image_bytes: bytes) -> np.ndarray:
7
+ """
8
+ Production-grade card isolation:
9
+ 1. Use rembg to remove background (produces alpha mask)
10
+ 2. Analyze contours in the alpha mask
11
+ 3. Keep ONLY the most card-like (rectangular) contour
12
+ 4. Discard all other objects (coins, clips, fingers, etc.)
13
+ 5. Return a tightly cropped BGRA image with clean transparent background
14
+
15
+ Works for both vertical and horizontal card orientations.
16
+ """
17
+ # Step 1: Run rembg with alpha matting for clean edges
18
+ bg_removed_bytes = remove(
19
+ image_bytes,
20
+ alpha_matting=True,
21
+ alpha_matting_foreground_threshold=240,
22
+ alpha_matting_background_threshold=10,
23
+ alpha_matting_erode_size=10
24
+ )
25
+
26
+ # Decode result (BGRA)
27
+ nparr = np.frombuffer(bg_removed_bytes, np.uint8)
28
+ img = cv2.imdecode(nparr, cv2.IMREAD_UNCHANGED)
29
+
30
+ if img is None:
31
+ raise ValueError("Could not decode image")
32
+
33
+ if len(img.shape) != 3 or img.shape[2] != 4:
34
+ # No alpha channel — return as is
35
+ return img
36
+
37
+ # Step 2: Extract alpha and find contours
38
+ alpha = img[:, :, 3]
39
+ _, thresh = cv2.threshold(alpha, 127, 255, cv2.THRESH_BINARY)
40
+
41
+ # Morphological close to fill small holes
42
+ kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
43
+ thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)
44
+
45
+ contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
46
+
47
+ if not contours:
48
+ return img
49
+
50
+ # Step 3: Score each contour for "card-likeness"
51
+ # A card is: (a) the largest object, (b) very rectangular
52
+ img_area = img.shape[0] * img.shape[1]
53
+ best_contour = None
54
+ best_score = -1
55
+
56
+ for contour in contours:
57
+ area = cv2.contourArea(contour)
58
+
59
+ # Skip tiny contours (noise)
60
+ if area < img_area * 0.05:
61
+ continue
62
+
63
+ # Fit a minimum area rectangle
64
+ rect = cv2.minAreaRect(contour)
65
+ box = cv2.boxPoints(rect)
66
+ rect_area = cv2.contourArea(box)
67
+
68
+ if rect_area == 0:
69
+ continue
70
+
71
+ # Rectangularity score: how well the contour fills its bounding rectangle
72
+ # A perfect rectangle scores 1.0; a circle scores ~0.78
73
+ rectangularity = area / rect_area
74
+
75
+ # Check aspect ratio — standard credit card is 85.6mm x 53.98mm ≈ 1.586
76
+ # Allow range from 1.3 to 1.8 (and its inverse for vertical cards)
77
+ w_rect, h_rect = rect[1]
78
+ if min(w_rect, h_rect) == 0:
79
+ continue
80
+ aspect = max(w_rect, h_rect) / min(w_rect, h_rect)
81
+
82
+ # Card-like aspect ratio bonus
83
+ if 1.2 <= aspect <= 1.9:
84
+ aspect_score = 1.0
85
+ else:
86
+ aspect_score = 0.3 # Penalize non-card shapes
87
+
88
+ # Combined score: weighted by area, rectangularity, and aspect ratio
89
+ score = (area / img_area) * rectangularity * aspect_score
90
+
91
+ if score > best_score:
92
+ best_score = score
93
+ best_contour = contour
94
+
95
+ if best_contour is None:
96
+ # Fallback: use the largest contour
97
+ best_contour = max(contours, key=cv2.contourArea)
98
+
99
+ # Step 4: Create a clean mask from ONLY the best contour
100
+ clean_mask = np.zeros(img.shape[:2], dtype=np.uint8)
101
+ cv2.drawContours(clean_mask, [best_contour], -1, 255, -1)
102
+
103
+ # Step 5: Apply the clean mask to the alpha channel
104
+ # This removes all non-card objects
105
+ new_alpha = cv2.bitwise_and(alpha, clean_mask)
106
+ img[:, :, 3] = new_alpha
107
+
108
+ # Step 6: Tight crop around the card only
109
+ _, crop_thresh = cv2.threshold(new_alpha, 10, 255, cv2.THRESH_BINARY)
110
+ crop_contours, _ = cv2.findContours(crop_thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
111
+
112
+ if crop_contours:
113
+ largest = max(crop_contours, key=cv2.contourArea)
114
+ x, y, w, h = cv2.boundingRect(largest)
115
+
116
+ # Minimal padding (just 2px to avoid border clipping)
117
+ pad = 2
118
+ x1 = max(0, x - pad)
119
+ y1 = max(0, y - pad)
120
+ x2 = min(img.shape[1], x + w + pad)
121
+ y2 = min(img.shape[0], y + h + pad)
122
+
123
+ return img[y1:y2, x1:x2]
124
+
125
+ return img