feat: add not working code
This commit is contained in:
@@ -7,6 +7,37 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass
|
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]:
|
def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLike]:
|
||||||
"""Extract the car plate part from given image.
|
"""Extract the car plate part from given image.
|
||||||
@@ -14,26 +45,107 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi
|
|||||||
:param img: The image containing car plate in BGR format.
|
:param img: The image containing car plate in BGR format.
|
||||||
:return: The image of binary car plate in U8 format if succeed, otherwise None.
|
: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)
|
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
# Step 2: Apply Gaussian blur to reduce noise
|
# Histogram balance to increase contrast
|
||||||
blurred = cv.GaussianBlur(gray, (5, 5), 0)
|
hist_gray = cv.equalizeHist(gray)
|
||||||
|
|
||||||
# Step 3: Edge detection using Canny
|
# Apply Gaussian blur to reduce noise
|
||||||
|
blurred = cv.GaussianBlur(hist_gray, (5, 5), 0)
|
||||||
|
|
||||||
|
# Edge detection using Canny
|
||||||
edges = cv.Canny(blurred, 50, 150)
|
edges = cv.Canny(blurred, 50, 150)
|
||||||
|
|
||||||
# Step 4: Morphological operations to connect edges
|
# cv.imshow('contours', edges)
|
||||||
kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))
|
# k = cv.waitKey(0)
|
||||||
dilated = cv.dilate(edges, kernel, iterations=2)
|
# return None
|
||||||
closed = cv.morphologyEx(dilated, cv.MORPH_CLOSE, kernel)
|
|
||||||
|
|
||||||
# Step 5: Find contours
|
# 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
|
||||||
|
|
||||||
|
# Find contours
|
||||||
contours, _ = cv.findContours(closed, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
|
contours, _ = cv.findContours(closed, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
if not contours:
|
if not contours:
|
||||||
logging.error("No contours found")
|
logging.error("No contours found")
|
||||||
return None
|
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
|
# Step 6: Find the most likely license plate contour
|
||||||
# License plates are typically rectangular with specific aspect ratios
|
# License plates are typically rectangular with specific aspect ratios
|
||||||
@@ -134,7 +246,6 @@ def extract_car_plate(img: cv.typing.MatLike) -> typing.Optional[cv.typing.MatLi
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Cli:
|
class Cli:
|
||||||
input_file: Path
|
input_file: Path
|
||||||
@@ -177,7 +288,7 @@ class Cli:
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Setup logging format
|
# 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
|
# Get user request
|
||||||
cli = Cli.from_cmdline()
|
cli = Cli.from_cmdline()
|
||||||
|
|||||||
Reference in New Issue
Block a user