11import logging
22from typing import Iterable , Sequence
33
4+ import cv2
45import numpy as np
56from numpy .typing import NDArray
67
@@ -26,9 +27,9 @@ def get_download_options(cls) -> DownloadOptions:
2627
2728 def __call__ (self , input : Iterable [NDArray [np .uint8 ]]) -> list [ClassificationResult ]:
2829 logger .debug ("Started preprocessing" )
29- input = self .preprocess (input )
30+ images = self .preprocess (input )
3031
31- input_dict = dict (zip (self .input_names , [input ]))
32+ input_dict = dict (zip (self .input_names , [images ]))
3233
3334 logger .debug ("Done preprocessing" )
3435 logger .debug ("Started running the model" )
@@ -44,6 +45,65 @@ def __call__(self, input: Iterable[NDArray[np.uint8]]) -> list[ClassificationRes
4445
4546 return result
4647
48+ def preprocess (self , input : Iterable [NDArray [np .uint8 ]]) -> list [NDArray [np .float32 ]]:
49+ """PP-LCNet image preprocessing pipeline.
50+
51+ Args:
52+ input: iterable of HxWxC uint8 images (C=3, assumed RGB).
53+
54+ Output:
55+ list of CxHxW float32 tensors (BGR order), normalized with PP-LCNet mean/std.
56+ """
57+ resize_short = 256 # shorter edge after resize
58+ crop_size = 224 # center crop size
59+ mean = np .asarray ([0.406 , 0.456 , 0.485 ], dtype = np .float32 ) # RGB mean
60+ std = np .asarray ([0.225 , 0.224 , 0.229 ], dtype = np .float32 ) # RGB std
61+ rescale_factor = 1.0 / 255.0 # uint8 -> [0,1]
62+
63+ out : list [NDArray [np .float32 ]] = []
64+
65+ for img in input :
66+ # Validate and coerce to expected dtype/layout (HWC, uint8, 3 channels)
67+ if img .ndim != 3 or img .shape [2 ] != 3 :
68+ raise ValueError (f"Expected HxWx3 image, got shape={ img .shape } " )
69+ if img .dtype != np .uint8 :
70+ raise ValueError (f"Expected uint8 image, got dtype={ img .dtype } " )
71+
72+ h , w = img .shape [:2 ]
73+
74+ # Resize while preserving aspect ratio using the shorter edge as reference
75+ scale = resize_short / float (min (h , w ))
76+ new_h = int (round (h * scale ))
77+ new_w = int (round (w * scale ))
78+
79+ # Perform the resize (OpenCV expects size as (width, height))
80+ resized = cv2 .resize (img , (new_w , new_h ), interpolation = cv2 .INTER_LINEAR )
81+
82+ # Center-crop to crop_size x crop_size (assumes resized dims are >= crop_size)
83+ if new_h < crop_size or new_w < crop_size :
84+ raise ValueError (
85+ f"Resized image too small for center crop: resized={ new_h } x{ new_w } , crop={ crop_size } "
86+ )
87+ top = (new_h - crop_size ) // 2
88+ left = (new_w - crop_size ) // 2
89+ cropped = resized [top : top + crop_size , left : left + crop_size , :]
90+
91+ # Convert to float32 and rescale to [0,1]
92+ x = cropped .astype (np .float32 ) * rescale_factor
93+
94+ # Normalize per channel in RGB space: (x - mean) / std
95+ x = (x - mean ) / std
96+
97+ # Convert RGB -> BGR
98+ x = x [..., ::- 1 ]
99+
100+ # Convert HWC -> CHW
101+ x = np .transpose (x , (2 , 0 , 1 )).astype (np .float32 , copy = False )
102+
103+ out .append (x )
104+
105+ return out
106+
47107 @classmethod
48108 def postprocess (cls , pred : Sequence [Sequence [float ]]) -> list [ClassificationResult ]:
49109 return [ClassificationResult (cls .classes [np .argmax (p )], max (p )) for p in pred ]
0 commit comments