@@ -14,18 +14,20 @@ def __init__(self, model_path: str):
1414 self .dim = (0 , 0 )
1515 self .videoCapture = cv2 .VideoCapture ()
1616 self .isGrayscale = False
17+ self .resizeMode = ''
1718
18- def init (self ):
19- model_info = super (ImageImpulseRunner , self ).init ()
20- width = model_info ['model_parameters' ]['image_input_width' ];
21- height = model_info ['model_parameters' ]['image_input_height' ];
19+ def init (self , debug = False ):
20+ model_info = super (ImageImpulseRunner , self ).init (debug )
21+ width = model_info ['model_parameters' ]['image_input_width' ]
22+ height = model_info ['model_parameters' ]['image_input_height' ]
2223
2324 if width == 0 or height == 0 :
2425 raise Exception ('Model file "' + self ._model_path + '" is not suitable for image recognition' )
2526
2627 self .dim = (width , height )
2728 self .labels = model_info ['model_parameters' ]['labels' ]
28- self .isGrayscale = model_info ['model_parameters' ]['image_channel_count' ] == 1
29+ self .isGrayscale = model_info ['model_parameters' ]['image_channel_count' ] == 1
30+ self .resizeMode = model_info ['model_parameters' ].get ('image_resize_mode' , 'not-reported' )
2931 return model_info
3032
3133 def __enter__ (self ):
@@ -69,7 +71,7 @@ def classifier(self, videoDeviceId = 0):
6971 res = self .classify (features )
7072 yield res , cropped
7173
72- # This expects images in RGB format (not BGR)
74+ # This expects images in RGB format (not BGR), DEPRECATED, use get_features_from_image_auto_studio_setings
7375 def get_features_from_image (self , img , crop_direction_x = 'center' , crop_direction_y = 'center' ):
7476 features = []
7577
@@ -129,3 +131,107 @@ def get_features_from_image(self, img, crop_direction_x='center', crop_direction
129131 features .append ((r << 16 ) + (g << 8 ) + b )
130132
131133 return features , cropped
134+
135+ def get_features_from_image_auto_studio_setings (self , img ):
136+ if self .resizeMode == '' :
137+ raise Exception (
138+ 'Runner has not initialized, please call init() first' )
139+ if self .resizeMode == 'not-reported' :
140+ raise Exception (
141+ 'Model file "' + self ._model_path + '" does not report the image resize mode\n '
142+ 'Please update the model file via edge-impulse-linux-runner --download' )
143+ return get_features_from_image_with_studio_mode (img , self .resizeMode , self .dim [0 ], self .dim [1 ], self .isGrayscale )
144+
145+
146+ def resize_with_letterbox (image , target_width , target_height ):
147+ """Resize an image while maintaining aspect ratio using letterboxing.
148+
149+ Args:
150+ image: The input image as a NumPy array.
151+ target_size: A tuple (width, height) specifying the desired output size.
152+
153+ Returns:
154+ The resized image as a NumPy array and the letterbox dimensions.
155+ """
156+
157+ height , width = image .shape [:2 ]
158+
159+ # Calculate scale factors to preserve aspect ratio
160+ scale_x = target_width / width
161+ scale_y = target_height / height
162+ scale = min (scale_x , scale_y )
163+
164+ # Calculate new dimensions and padding
165+ new_width = int (width * scale )
166+ new_height = int (height * scale )
167+ top_pad = (target_height - new_height ) // 2
168+ bottom_pad = target_height - new_height - top_pad
169+ left_pad = (target_width - new_width ) // 2
170+ right_pad = target_width - new_width - left_pad
171+
172+ # Resize image and add padding
173+ resized_image = cv2 .resize (image , (new_width , new_height ))
174+ padded_image = cv2 .copyMakeBorder (resized_image , top_pad , bottom_pad , left_pad , right_pad , cv2 .BORDER_CONSTANT , value = 0 )
175+
176+ return padded_image
177+
178+
179+ def get_features_from_image_with_studio_mode (img , mode , output_width , output_height , is_grayscale ):
180+ """
181+ Extract features from an image using different resizing modes suitable for Edge Impulse Studio.
182+
183+ Args:
184+ img (numpy.ndarray): The input image as a NumPy array.
185+ mode (str): The resizing mode to use. Options are 'fit-shortest', 'fit-longest', and 'squash'.
186+ output_width (int): The desired output width of the image.
187+ output_height (int): The desired output height of the image.
188+ is_grayscale (bool): Whether the output image should be converted to grayscale.
189+
190+ Returns:
191+ tuple: A tuple containing:
192+ - features (list): A list of pixel values in the format (R << 16) + (G << 8) + B for color images,
193+ or (P << 16) + (P << 8) + P for grayscale images.
194+ - resized_img (numpy.ndarray): The resized image as a NumPy array.
195+ """
196+ features = []
197+
198+ in_frame_cols = img .shape [1 ]
199+ in_frame_rows = img .shape [0 ]
200+
201+ if mode == 'fit-shortest' :
202+ aspect_ratio = output_width / output_height
203+ if in_frame_cols / in_frame_rows > aspect_ratio :
204+ # Image is wider than target aspect ratio
205+ new_width = int (in_frame_rows * aspect_ratio )
206+ offset = (in_frame_cols - new_width ) // 2
207+ cropped_img = img [:, offset :offset + new_width ]
208+ else :
209+ # Image is taller than target aspect ratio
210+ new_height = int (in_frame_cols / aspect_ratio )
211+ offset = (in_frame_rows - new_height ) // 2
212+ cropped_img = img [offset :offset + new_height , :]
213+
214+ resized_img = cv2 .resize (cropped_img , (output_width , output_height ), interpolation = cv2 .INTER_AREA )
215+ elif mode == 'fit-longest' :
216+ resized_img = resize_with_letterbox (img , output_width , output_height )
217+ elif mode == 'squash' :
218+ resized_img = cv2 .resize (img , (output_width , output_height ), interpolation = cv2 .INTER_AREA )
219+ else :
220+ raise ValueError (f"Unsupported mode: { mode } " )
221+
222+ if is_grayscale :
223+ resized_img = cv2 .cvtColor (resized_img , cv2 .COLOR_BGR2GRAY )
224+ pixels = np .array (resized_img ).flatten ().tolist ()
225+
226+ for p in pixels :
227+ features .append ((p << 16 ) + (p << 8 ) + p )
228+ else :
229+ pixels = np .array (resized_img ).flatten ().tolist ()
230+
231+ for ix in range (0 , len (pixels ), 3 ):
232+ r = pixels [ix + 0 ]
233+ g = pixels [ix + 1 ]
234+ b = pixels [ix + 2 ]
235+ features .append ((r << 16 ) + (g << 8 ) + b )
236+
237+ return features , resized_img
0 commit comments