diff --git a/mv-and-ip/car_plate.py b/mv-and-ip/car_plate.py index 00e81e3..d941da4 100644 --- a/mv-and-ip/car_plate.py +++ b/mv-and-ip/car_plate.py @@ -118,6 +118,72 @@ def _analyse_car_plate_connection( return best +@dataclass +class PerspectiveData: + top_left: tuple[int, int] + top_right: tuple[int, int] + bottom_left: tuple[int, int] + bottom_right: tuple[int, int] + + new_width: int + new_height: int + + +def _extract_perspective_data( + gray: cv.typing.MatLike, +) -> typing.Optional[PerspectiveData]: + """ """ + # Histogram balance to increase contrast + hist_gray = cv.equalizeHist(gray) + + # Apply Gaussian blur to reduce noise + blurred = cv.GaussianBlur(hist_gray, (5, 5), 0) + + # Edge detection using Canny + edges = cv.Canny(blurred, 50, 150) + + # Find contours + contours, _ = cv.findContours(edges, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) + if not contours: + return None + # Find the largest one because all image is car plate + max_area_contour = max(contours, key=lambda contour: cv.contourArea(contour)) + + # Approximate the contour + peri = cv.arcLength(max_area_contour, True) + approx = cv.approxPolyDP(max_area_contour, 0.02 * peri, True) + if len(approx) != 4: + return None + + # Perspective transformation to get front view + # Order points: top-left, top-right, bottom-right, bottom-left + pts = approx.reshape(4, 2) + rect = np.zeros((4, 2), dtype="float32") + + # Sum and difference of coordinates to find corners + s = pts.sum(axis=1) + top_left = pts[np.argmin(s)] # Top-left has smallest sum + bottom_right = pts[np.argmax(s)] # Bottom-right has largest sum + + diff = np.diff(pts, axis=1) + top_right = pts[np.argmin(diff)] # Top-right has smallest difference + bottom_left = pts[np.argmax(diff)] # Bottom-left has largest difference + + # Calculate width and height of new image + width_a = np.linalg.norm(rect[0] - rect[1]) + width_b = np.linalg.norm(rect[2] - rect[3]) + max_width = max(int(width_a), int(width_b)) + + height_a = np.linalg.norm(rect[0] - rect[3]) + height_b = np.linalg.norm(rect[1] - rect[2]) + max_height = max(int(height_a), int(height_b)) + + # Return value + return PerspectiveData( + top_left, top_right, bottom_left, bottom_right, max_width, max_height + ) + + def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLike]: """ Extract the car plate part from given image. @@ -136,10 +202,11 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi # 连通域分析,筛选最符合车牌长宽比的区域作为车牌 candidate = _analyse_car_plate_connection(mask) # 找到任意一个就退出 - if candidate is not None: break + if candidate is not None: + break if candidate is None: - logging.error('Can not find any car plate.') + logging.error("Can not find any car plate.") return None # 稍微扩边获取最终车牌区域 @@ -149,13 +216,13 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi y1 = max(candidate.y - pad, 0) x2 = min(candidate.x + candidate.w + pad, w_img) y2 = min(candidate.y + candidate.h + pad, h_img) - logging.info(f'车牌区域: x={x1}, y={y1}, w={x2 - x1}, h={y2 - y1}') + logging.info(f"车牌区域: x={x1}, y={y1}, w={x2 - x1}, h={y2 - y1}") # # 在原图上标记(仅供调试) # debug = img.copy() # cv.rectangle(debug, (x1, y1), (x2, y2), (0, 255, 0), 3) # cv.imwrite('./debug_detected.jpg', debug) - + # 二值化:文字/边缘 → 黑色,背景 → 白色 gray = cv.cvtColor(img[y1:y2, x1:x2], cv.COLOR_BGR2GRAY) @@ -171,7 +238,29 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi kernel_denoise = cv.getStructuringElement(cv.MORPH_RECT, (2, 2)) binary = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel_denoise) - #return binary + # 尝试获取视角矫正数据 + perspective_data = _extract_perspective_data(gray) + if perspective_data is None: + logging.warning(f'Can not fetch perspective data. The output image has no perspective correction.') + return binary + + # 执行视角矫正 + perspective_src = np.array([ + list(perspective_data.top_left), + list(perspective_data.top_right), + list(perspective_data.bottom_right), + list(perspective_data.bottom_left) + ], dtype="float32") + perspective_dst = np.array([ + [0, 0], + [perspective_data.new_width - 1, 0], + [perspective_data.new_width - 1, perspective_data.new_height - 1], + [0, perspective_data.new_height - 1] + ], dtype="float32") + M = cv.getPerspectiveTransform(perspective_src, perspective_dst) + warped = cv.warpPerspective(binary, M, (perspective_data.new_width, perspective_data.new_height)) + + return warped # cv.imwrite('./plate_binary.png', binary) # print("二值化结果已保存: plate_binary.png")