diff --git a/mv-and-ip/car_plate.py b/mv-and-ip/car_plate.py index c477de2..475ef90 100644 --- a/mv-and-ip/car_plate.py +++ b/mv-and-ip/car_plate.py @@ -47,8 +47,12 @@ def _uniform_car_plate(img: cv.typing.MatLike) -> cv.typing.MatLike: @dataclass class CarPlateHsvBoundary: + """HSV boundary for car plate color detection.""" + lower_bound: cv.typing.MatLike + """Lower bound of HSV range for car plate color detection.""" upper_bound: cv.typing.MatLike + """Upper bound of HSV range for car plate color detection.""" need_revert: bool """是否取反黑白颜色,因为蓝牌和黄牌的操作正好是反的""" @@ -65,7 +69,10 @@ CAR_PLATE_HSV_BOUNDARIES: tuple[CarPlateHsvBoundary, ...] = ( @dataclass class CarPlateMask: + """Car plate mask result.""" + mask: cv.typing.MatLike + """The masked image in U8 format.""" need_revert: bool """是否对颜色取反,与CarPlateHsvBoundary中的同名字段含义一致""" @@ -73,7 +80,13 @@ class CarPlateMask: def _batchly_mask_car_plate( hsv: cv.typing.MatLike, ) -> typing.Iterator[CarPlateMask]: - """ """ + """ + Iterate over each car plate HSV boundary and apply mask to the given HSV image. + + :param hsv: The HSV image to apply mask. + :return: An iterator of CarPlateMask. + """ + for boundary in CAR_PLATE_HSV_BOUNDARIES: # 以给定HSV范围检测符合该颜色的位置 mask = cv.inRange(hsv, boundary.lower_bound, boundary.upper_bound) @@ -90,6 +103,8 @@ def _batchly_mask_car_plate( @dataclass class CarPlateRegion: + """Car plate region result.""" + x: int y: int w: int @@ -99,34 +114,46 @@ class CarPlateRegion: MIN_AREA: float = 3000 +"""Minimum area for car plate region.""" MIN_ASPECT_RATIO: float = 1.5 +"""Minimum aspect ratio for car plate region.""" MAX_ASPECT_RATIO: float = 6.0 +"""Maximum aspect ratio for car plate region.""" BEST_ASPECT_RATIO: float = 3.5 +"""Best aspect ratio for car plate region.""" def _analyse_car_plate_connection( - mask: CarPlateMask, + masks: typing.Iterator[CarPlateMask], ) -> typing.Optional[CarPlateRegion]: - # 连通域分析,筛选最符合车牌长宽比的区域 - num_labels, labels, stats, _ = cv.connectedComponentsWithStats( - mask.mask, connectivity=8 - ) + """ + Analyse car plate connection in given masks. + + :param masks: An iterator of CarPlateMask to analyse. + :return: The car plate region if succeed, otherwise None. + """ best: typing.Optional[CarPlateRegion] = None best_score = 0 - for i in range(1, num_labels): - x, y, w, h, area = stats[i] - # 检查面积 - if area < MIN_AREA: - continue - # 标准车牌宽高比约 3:1 ~ 5:1 - ratio = w / (h + 1e-5) - if ratio >= MIN_ASPECT_RATIO and ratio <= MAX_ASPECT_RATIO: - score = area * (1 - abs(ratio - BEST_ASPECT_RATIO) / BEST_ASPECT_RATIO) - if score > best_score: - best_score = score - best = CarPlateRegion(x, y, w, h, mask.need_revert) + for mask in masks: + # 连通域分析,筛选最符合车牌长宽比的区域 + num_labels, labels, stats, _ = cv.connectedComponentsWithStats( + mask.mask, connectivity=8 + ) + + for i in range(1, num_labels): + x, y, w, h, area = stats[i] + # 检查面积 + if area < MIN_AREA: + continue + # 标准车牌宽高比约 3:1 ~ 5:1 + ratio = w / (h + 1e-5) + if ratio >= MIN_ASPECT_RATIO and ratio <= MAX_ASPECT_RATIO: + score = area * (1 - abs(ratio - BEST_ASPECT_RATIO) / BEST_ASPECT_RATIO) + if score > best_score: + best_score = score + best = CarPlateRegion(x, y, w, h, mask.need_revert) return best @@ -138,20 +165,15 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi :param img: The image containing car plate in BGR format. :return: The image of binary car plate in U8 format if succeed, otherwise None. """ + # 统一图片大小 img = _uniform_car_plate(img) # 转换到HSV空间 hsv = cv.cvtColor(img, cv.COLOR_BGR2HSV) # 利用车牌颜色在 HSV 空间定位车牌 - candidate: typing.Optional[CarPlateRegion] = None - for mask in _batchly_mask_car_plate(hsv): - # 连通域分析,筛选最符合车牌长宽比的区域作为车牌 - candidate = _analyse_car_plate_connection(mask) - # 找到任意一个就退出 - if candidate is not None: - break - + masks = _batchly_mask_car_plate(hsv) + candidate = _analyse_car_plate_connection(masks) if candidate is None: logging.error("Can not find any car plate.") return None