1+ """
2+ Inference script for digit recognition on a webcam or USB camera.
3+ """
4+
15import sys
26from pickle import load
37
48import cv2
59import numpy as np
610import pandas as pd
7- import scipy .ndimage as ndi
811from skimage .color import rgb2gray
912from skimage .filters import threshold_otsu
1013from skimage .measure import regionprops_table
1114from skimage .morphology import label
12- from skimage .transform import rescale , resize
15+ from skimage .transform import resize
1316from skimage .util import img_as_ubyte
1417from skimage .exposure import rescale_intensity
1518from skimage .segmentation import clear_border
1619
1720from preprocessing import keep_n_biggest_objects
1821
22+
1923# Reload the sklearn `pipeline`
2024with open ("pipeline.pkl" , "rb" ) as f :
2125 pipe = load (f )
2226
2327
24- def extract_digit_from_digit_roi (image : np .ndarray ) -> np .ndarray :
25- """Assuming that a digit is drawn inside the ROI, we extract a crop around it and resize it to (50, 50)."""
26- image = img_as_ubyte (image ) # Convert to 8-bit
27-
28- # Downscale by a factor 2
29- resized = rescale (image , scale = 0.5 )
30-
31- # Otsu threshold
32- try :
33- binary = resized > threshold_otsu (resized )
34- except ValueError :
35- return img_as_ubyte (np .zeros ((50 , 50 )))
36-
37- # Labelling
38- labelled = label (binary )
39-
40- # The white background is assumed to be the biggest object
41- light_square = keep_n_biggest_objects (labelled , n = 1 )
42-
43- # We assume that the digit is the biggest "hole" in that bright background
44- # => If the digit is connected to the border it won't be detected
45- filled_light_square = ndi .binary_fill_holes (light_square )
46- binary_in_light_square = np .logical_and (filled_light_square , light_square == 0 )
47- labelled_in_light_square = label (binary_in_light_square )
48-
49- n_objects = labelled_in_light_square .max ()
50- if n_objects == 0 :
51- # Case where the ROI is empty
52- digit_crop = resized
53- else :
54- # Digit = biggest object
55- digit = keep_n_biggest_objects (labelled_in_light_square , n = 1 )
56- # Extract bounding box
57- df = pd .DataFrame (
58- regionprops_table (digit , intensity_image = resized , properties = ["bbox" ])
59- )
60- digit_row = df .iloc [0 ]
61- # Crop around the digit
62- digit_crop = resized [digit_row ["bbox-0" ] : digit_row ["bbox-2" ], digit_row ["bbox-1" ] : digit_row ["bbox-3" ]]
63-
64- # Resize to a fixed size
65- digit_crop = resize (digit_crop , output_shape = (50 , 50 ))
66-
67- # Convert to 8-bit
68- digit_crop = img_as_ubyte (digit_crop )
69-
70- return digit_crop
71-
72-
73- def detect_digit_roi (image : np .ndarray , max_objects = 12 ) -> pd .DataFrame :
28+ def detect_digit_roi (image : np .ndarray , max_objects = 12 , min_area_px = 2000 ) -> pd .DataFrame :
7429 # RGB => Grayscale
7530 gray = rgb2gray (image )
7631
7732 # Otsu threshold
78- binary = gray > threshold_otsu (gray )
33+ binary = gray < threshold_otsu (gray )
7934
8035 # Labelling + keep biggest object
8136 labelled = label (binary )
@@ -91,16 +46,30 @@ def detect_digit_roi(image: np.ndarray, max_objects=12) -> pd.DataFrame:
9146 regionprops_table (
9247 digit_crops ,
9348 intensity_image = gray ,
94- properties = ["intensity_image" , "bbox" ],
95- )
49+ properties = ["area" , "bbox" ])
9650 )
9751
52+ # Size filter (ignore small objects)
53+ df = df [df ["area" ] > min_area_px ]
54+
55+ digit_crops = []
56+ for _ , row in df .iterrows ():
57+ # Crop around the digit
58+ digit_crop = gray [int (row ["bbox-0" ]) : int (row ["bbox-2" ]), int (row ["bbox-1" ]) : int (row ["bbox-3" ])]
59+
60+ # Resize to a fixed size
61+ digit_crop = resize (digit_crop , output_shape = (50 , 50 ))
62+
63+ # Convert to 8-bit
64+ digit_crop = img_as_ubyte (digit_crop )
65+ digit_crops .append (digit_crop )
66+
67+ df ["roi" ] = digit_crops
68+
9869 return df
9970
10071
101- def classify_digit_square (digit_square_img : np .ndarray ) -> str :
102- digit = extract_digit_from_digit_roi (digit_square_img )
103-
72+ def classify_digit_roi (digit : np .ndarray ) -> str :
10473 # Normalize intensity values
10574 digit_normed = rescale_intensity (digit , out_range = (0 , 1 ))
10675
@@ -172,7 +141,7 @@ def main(camera_idx: int):
172141
173142 df = detect_digit_roi (frame )
174143
175- df ["pred" ] = df ["intensity_image " ].apply (classify_digit_square )
144+ df ["pred" ] = df ["roi " ].apply (classify_digit_roi )
176145
177146 vis_frame = draw_boxes (frame , df )
178147
@@ -187,6 +156,7 @@ def main(camera_idx: int):
187156
188157
189158if __name__ == "__main__" :
159+ # Run with the correct camera index, for example: `python inference.py 4`
190160 _ , camera_idx = sys .argv
191161
192162 camera_idx = int (camera_idx )
0 commit comments