1212from skimage .transform import rescale , resize
1313from skimage .util import img_as_ubyte
1414from skimage .exposure import rescale_intensity
15+ from skimage .segmentation import clear_border
1516
1617from preprocessing import keep_n_biggest_objects
1718
2021 pipe = load (f )
2122
2223
23- def extract_digit_from_digit_square (image : np .ndarray ) -> np .ndarray :
24+ def extract_digit_from_digit_roi (image : np .ndarray ) -> np .ndarray :
2425 image = img_as_ubyte (image )
2526
2627 resized = rescale (image , scale = 0.5 )
2728
28- binary = resized > threshold_otsu (resized )
29+ try :
30+ binary = resized > threshold_otsu (resized )
31+ except ValueError :
32+ return img_as_ubyte (np .zeros ((50 , 50 )))
33+
2934 labelled = label (binary )
3035
3136 light_square = keep_n_biggest_objects (labelled , n = 1 )
@@ -54,20 +59,26 @@ def extract_digit_from_digit_square(image: np.ndarray) -> np.ndarray:
5459 return digit_crop
5560
5661
57- def detect_digit_squares (image : np .ndarray , n_squares = 12 ) -> pd .DataFrame :
62+ def detect_digit_roi (image : np .ndarray , max_objects = 12 ) -> pd .DataFrame :
63+ # RGB => Grayscale
5864 gray = rgb2gray (image )
59- binary = gray < threshold_otsu (gray )
65+
66+ # Otsu threshold
67+ binary = gray > threshold_otsu (gray )
68+
69+ # Labelling + keep biggest object
6070 labelled = label (binary )
61- grid_frame = keep_n_biggest_objects (labelled , n = 1 )
62- filled_grid_frame = ndi .binary_fill_holes (grid_frame )
63- digit_squares = np .logical_and (filled_grid_frame , grid_frame == 0 )
64- digit_squares_labelled = label (digit_squares )
65- digit_squares_cleaned = keep_n_biggest_objects (digit_squares_labelled , n = n_squares )
71+
72+ # Remove object touching the image border (incl. the white background, probably)
73+ labelled = clear_border (labelled )
74+
75+ # Keep the biggest `max_objects` objects only
76+ digit_crops = keep_n_biggest_objects (labelled , n = max_objects )
6677
6778 # Return a dataframe with intensity image and centroids
6879 df = pd .DataFrame (
6980 regionprops_table (
70- digit_squares_cleaned ,
81+ digit_crops ,
7182 intensity_image = gray ,
7283 properties = ["intensity_image" , "bbox" ],
7384 )
@@ -77,40 +88,41 @@ def detect_digit_squares(image: np.ndarray, n_squares=12) -> pd.DataFrame:
7788
7889
7990def classify_digit_square (digit_square_img : np .ndarray ) -> str :
80- digit = extract_digit_from_digit_square (digit_square_img )
91+ digit = extract_digit_from_digit_roi (digit_square_img )
8192
8293 # Normalize intensity values
8394 digit_normed = rescale_intensity (digit , out_range = (0 , 1 ))
8495
96+ # Flatten pixels
8597 pixel_features = np .reshape (digit_normed [None ], (1 , - 1 ))
98+
99+ # Predict
86100 pred_label = pipe .predict (pixel_features )[0 ]
87101
88102 return pred_label
89103
90104
91- def draw_boxes (img , df : pd .DataFrame ):
105+ def draw_boxes (img , df : pd .DataFrame , font_size = 1.5 , thickness = 2 ):
92106 boxes = df [['bbox-0' , 'bbox-1' , 'bbox-2' , 'bbox-3' ]].values
93107 labels = df ["pred" ].values
94108
95109 output = img .copy ()
96110 for i , box in enumerate (boxes ):
97111 y1 , x1 , y2 , x2 = map (int , box )
98112
99- cv2 .rectangle (output , (x1 , y1 ), (x2 , y2 ), color = (0 , 255 , 0 ), thickness = 2 )
113+ cv2 .rectangle (output , (x1 , y1 ), (x2 , y2 ), color = (0 , 255 , 0 ), thickness = thickness )
100114
101115 text = str (labels [i ])
102116
103117 (text_w , text_h ), baseline = cv2 .getTextSize (
104118 text = text ,
105119 fontFace = cv2 .FONT_HERSHEY_SIMPLEX ,
106- fontScale = 0.5 ,
107- thickness = 1 ,
120+ fontScale = font_size ,
121+ thickness = thickness ,
108122 )
109123
110- # Put label above box if there is room, otherwise inside/top edge
111- text_y = y1 - 8 if y1 - 8 > text_h else y1 + text_h + 8
124+ text_y = y1 + text_h + 14
112125
113- # Background rectangle for text
114126 cv2 .rectangle (
115127 output ,
116128 (x1 , text_y - text_h - baseline ),
@@ -124,9 +136,9 @@ def draw_boxes(img, df: pd.DataFrame):
124136 text = text ,
125137 org = (x1 , text_y ),
126138 fontFace = cv2 .FONT_HERSHEY_SIMPLEX ,
127- fontScale = 0.5 ,
139+ fontScale = font_size ,
128140 color = (0 , 0 , 0 ),
129- thickness = 1 ,
141+ thickness = thickness ,
130142 lineType = cv2 .LINE_AA ,
131143 )
132144
@@ -139,13 +151,15 @@ def main(camera_idx: int):
139151 if not cap .isOpened ():
140152 raise RuntimeError ("Could not open camera" )
141153
154+ cv2 .namedWindow ("Camera" , cv2 .WINDOW_NORMAL )
155+
142156 while True :
143157 ret , frame = cap .read ()
144158 if not ret :
145159 print ("Failed to grab frame" )
146160 break
147161
148- df = detect_digit_squares (frame )
162+ df = detect_digit_roi (frame )
149163
150164 df ["pred" ] = df ["intensity_image" ].apply (classify_digit_square )
151165
0 commit comments