From e4089132814e5446cd0012882382c45f68eccb04 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Tue, 7 Apr 2026 23:40:07 +0800 Subject: [PATCH] feat: add not working code --- mv-and-ip/car_plate.py | 135 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 12 deletions(-) diff --git a/mv-and-ip/car_plate.py b/mv-and-ip/car_plate.py index bd16250..0104c2c 100644 --- a/mv-and-ip/car_plate.py +++ b/mv-and-ip/car_plate.py @@ -7,6 +7,37 @@ import logging from pathlib import Path from dataclasses import dataclass +def _uniform_car_plate(img: cv.typing.MatLike) -> cv.typing.MatLike: + """ + Uniform the image size to 512x512 while maintaining aspect ratio, padding with black. + + :param img: The image in BGR format to be uniformed. + :return: The uniformed image in BGR format. + """ + UNI_HW: int = 512 + + # Calculate the new width and height for given image + h, w = img.shape[:2] + scale = min(UNI_HW / w, UNI_HW / h) + new_w = int(w * scale) + new_h = int(h * scale) + + # Resize the image + resized_img = cv.resize(img, (new_w, new_h)) + + # Create a black canvas of size UNI_HW x UNI_HW + padded_img = np.zeros((UNI_HW, UNI_HW, 3), dtype=np.uint8) + + # Calculate position to paste the resized image (centered) + y_offset = (UNI_HW - new_h) // 2 + x_offset = (UNI_HW - new_w) // 2 + + # Paste the resized image onto the canvas + padded_img[y_offset:y_offset+new_h, x_offset:x_offset+new_w] = resized_img + + # Return the padded image + return padded_img + def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLike]: """Extract the car plate part from given image. @@ -14,27 +45,108 @@ 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. """ - # Step 1: Convert to grayscale + # Reference: https://www.cnblogs.com/linuxAndMcu/p/19144795 + + # Resize the image to make following step works about finding proper contours. + img = _uniform_car_plate(img) + + # Convert to grayscale image gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY) + + # Histogram balance to increase contrast + hist_gray = cv.equalizeHist(gray) - # Step 2: Apply Gaussian blur to reduce noise - blurred = cv.GaussianBlur(gray, (5, 5), 0) + # Apply Gaussian blur to reduce noise + blurred = cv.GaussianBlur(hist_gray, (5, 5), 0) - # Step 3: Edge detection using Canny + # Edge detection using Canny edges = cv.Canny(blurred, 50, 150) + + # cv.imshow('contours', edges) + # k = cv.waitKey(0) + # return None - # Step 4: Morphological operations to connect edges - kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5)) - dilated = cv.dilate(edges, kernel, iterations=2) - closed = cv.morphologyEx(dilated, cv.MORPH_CLOSE, kernel) + # Morphological operations to connect broken edges + kernel_close = cv.getStructuringElement(cv.MORPH_RECT, (5, 5)) + closed = cv.morphologyEx(edges, cv.MORPH_CLOSE, kernel_close) + kernel_open = cv.getStructuringElement(cv.MORPH_RECT, (3, 3)) + opened = cv.morphologyEx(closed, cv.MORPH_OPEN, kernel_open) + kernel_dilate = cv.getStructuringElement(cv.MORPH_RECT, (3, 3)) + dilated = cv.dilate(edges, kernel_dilate, iterations=2) + + cv.imshow('contours', opened) + k = cv.waitKey(0) + return None - # Step 5: Find contours + # Find contours contours, _ = cv.findContours(closed, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE) - if not contours: logging.error("No contours found") return None + # List all contours + logging.debug(f'Total {len(contours)} contours.') + for i, contour in enumerate(contours): + logging.debug(f'Contour[{i}] has {contour.shape[0]} points.') + cv.drawContours(img, contours, -1, (0, 0, 255), 3) + cv.imshow('contours', img) + k = cv.waitKey(0) + return None + + # Filter contours + candidates: list[cv.typing.MatLike] = [] + MIN_AREA: float = 2000 + MAX_AREA: float = 100000 + MIN_ASPECT_RATIO: float = 2.5 + MAX_ASPECT_RATIO: float = 6.0 + + for i, contour in enumerate(contours): + # Calculate the area + area = cv.contourArea(contour) + if area < MIN_AREA or area > MAX_AREA: + logging.debug(f'Contour[{i}] failed at area limit. The area of this contour is {area}.') + continue + + # Get bounding rectangle + bouding_rect = cv.boundingRect(contour) + (x, y, w, h) = bouding_rect + # Calaulate aspect ratio + aspect_ratio = w / h + if aspect_ratio < MIN_ASPECT_RATIO or aspect_ratio > MAX_ASPECT_RATIO: + logging.debug(f'Contour[{i}] failed at aspect ratio limit. The aspect ratio of this contour is {aspect_ratio}.') + continue + + # Get the convex hull of contour + hull = cv.convexHull(contour) + + # Compute the occupation of contour area in convex hull area + hull_area = cv.contourArea(hull) + solidity = area / hull_area + + # Filter more regular contour + if solidity > 0.6: + # Extra check for the rectangle fill rate + fill_ratio = area / (w * h) + if fill_ratio > 0.3: + logging.debug(f'Contour[{i}] is perfect.') + candidates.append(contour) + continue + else: + logging.debug(f'Contour[{i}] failed at rectangle fill ratio limit. The fill ratio of this contour is {fill_ratio}') + else: + logging.debug(f'Contour[{i}] failed at solidity limit. The solidity of this contour is {solidity}.') + + if len(candidates) == 0: + logging.error("No candidate contour") + return None + + cv.drawContours(img, contours, -1, (0, 0, 255), 3) + cv.imshow('contours', img) + k = cv.waitKey(0) + return None + + + # Step 6: Find the most likely license plate contour # License plates are typically rectangular with specific aspect ratios max_area = 0 @@ -134,7 +246,6 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi return result - @dataclass class Cli: input_file: Path @@ -177,7 +288,7 @@ class Cli: def main(): # Setup logging format - logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.INFO) + logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.DEBUG) # Get user request cli = Cli.from_cmdline()