feat: add not working code
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user