车牌识别系统
读代码就像看电影~~细品哈
just do it ! ! !
介绍(非常简单哈)
输入:图片
处理:用scikit-image 和Opencv-python识别图片中的车牌号
输出:将车牌号保存到json文件中
功能(utils.py~其实就是一堆函数)
sort_comes(corns,img) 实现透视变换
perspective() 从指定的图像中裁剪车牌
div_func() 对裁剪的车牌实现颜色转换
divide_tab() 用数字和字母在线段上划分裁剪区域,返回包含字母和数字的列表
recognize() 识别裁剪区域的每个字母和数字,在识别每个字母时,会参考图像字符的概率,找到可能性最大的字母并将它附加到列表中
help_perform() 对指定图像实现颜色转换,检测每一个部分是否有四个角,然后计算角之间的距离,并处以这些值以确定是否为一个车牌号
perform_processing() 调用上面的功能函数实现读取图像裁剪识别等功能
主程序(调用而已)
main.py 功能是调用文件util.py中的功能函数,实现车牌识别功能
- 首先使用函数 add_argument()
添加两个python命令参数mages_dir result_file
然后根据用户设置的 images_dir逐一读取images_dir的目录中的图片 - 实现车牌识别(重点)
- 将结果保存到 resulits_file中
函数调用顺序(就是图片中的车牌子如何被识别成数字的流程)
perform_processing 被调用,开始车牌识别流程。
help_perform 被多次调用,尝试找到车牌区域。
如果help_perform成功,sort_corners被调用,对车牌进行排序。
perspective 根据排序结果裁剪车牌区域。
divide_tab 调用div_func,分割车牌中的字符。
recognize 识别每个字符,构建车牌号码。
开始读代码(细品哈)
perform_processing() 调用上面的功能函数实现读取图像裁剪识别等功能
# 主要功能->读取图像、处理等。
def perform_processing(image: np.ndarray, ref) -> str:
th = 135
win = 3
wrapped_tab = None
for i in range(5):
wrapped_tab = help_perform(image, th=th, wind=win)
th -= 5
win += 6
if wrapped_tab is not None:
break
# 如果没有找到任何内容,请返回问号
if wrapped_tab is None:
return '???????'
try:
single = divide_tab(wrapped_tab, th=115)
resu = recognize(single, ref)
if len(resu) == 7:
return resu
elif len(resu) > 7:
x = len(resu) - 7
return resu[x:]
elif len(resu) == 0:
return '???????'
elif 7 > len(resu) > 0:
x = 7 - len(resu)
for i in range(x):
resu = '?' + resu[0:]
return resu
except NameError:
return '???????'
return '???????'
help_perform() 对指定图像实现颜色转换,检测每一个部分是否有四个角,然后计算角之间的距离,并处以这些值以确定是否为一个车牌号
def help_perform(image, th=135, wind=3):
wrapped_tab = None
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
w = image.shape[1]
h = image.shape[0]
blurred = cv2.GaussianBlur(img_gray, (wind, wind), 3)
_, thresh = cv2.threshold(blurred, th, 255, cv2.THRESH_BINARY_INV)
cont, _ = cv2.findContours(thresh, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
hull_list = []
for i in range(len(cont)):
h = cv2.convexHull(cont[i])
hull_list.append(h)
contours = sorted(hull_list, key=cv2.contourArea, reverse=True)[:8]
for c in contours:
clos = cv2.arcLength(c, True)
apr = cv2.approxPolyDP(c, 0.05 * clos, True)
cnt = 0
nums = []
points_arr = []
# 检查是否有4个角
if len(apr) == 4:
for i in range(4):
j = i + 1
if j == 4:
j = 0
k = i - 1
if k == -1:
k = 3
# 计算角点之间的距离,然后除以这些值,以确定这是否是一个车牌号
w_len_1 = sqrt((apr[i][0][0] - apr[j][0][0]) ** 2 + (apr[i][0][1] - apr[j][0][1]) ** 2)
w_len_2 = sqrt((apr[i][0][0] - apr[k][0][0]) ** 2 + (apr[i][0][1] - apr[k][0][1]) ** 2)
if w_len_1 > w_len_2:
if w_len_1 / w_len_2 < 3 or w_len_1 / w_len_2 > 7.3 or w_len_1 >= w:
continue
else:
if w_len_2 / w_len_1 < 3 or w_len_2 / w_len_1 > 7.3 or w_len_2 >= w:
continue
# 下一个判断条件是确定这是否是车牌号
if w_len_1 >= w / 3 or w_len_2 >= w / 3:
cnt += 1
points_arr.append(apr[i][0])
if cnt == 2:
nums.append(apr)
if len(nums) == 1:
wrapped_tab = sort_cornes(points_arr, image)
return wrapped_tab
sort_comes(corns,img) 实现透视变换
# 透视变换,实现排序[ul, ur, bl, br]
def sort_cornes(corns, img):
sorted_arr = []
image_corns = []
upper_right = [img.shape[1], 0]
upper_left = [0, 0]
bottom_right = [img.shape[1], img.shape[0]]
bottom_left = [0, img.shape[0]]
image_corns.append(upper_left)
image_corns.append(upper_right)
image_corns.append(bottom_left)
image_corns.append(bottom_right)
order = []
ord = 0
#计算车牌角和整个图像角之间的距离,找出[ul,ur,bl,br] 中的字符
for ind, val in enumerate(image_corns):
lowest_dist = sqrt(img.shape[1]**2 + img.shape[0]**2)
for i, v in enumerate(corns):
dist = sqrt((val[0] - v[0])**2 + (val[1] - v[1])**2)
if dist < lowest_dist:
lowest_dist = dist
ord = i
order.append(ord)
for o in order:
sorted_arr.append(corns[o])
# 一旦我们有了一个排序列表,我们就可以从图像中裁剪出车牌号
return perspective(sorted_arr, img)
perspective() 从指定的图像中裁剪车牌
def perspective(arr, img, width=1000, height=250):
p1 = np.float32([arr[0], arr[1], arr[2], arr[3]])
p2 = np.float32([[0, 0], [width, 0], [0, height], [width, height]])
get_tf = cv2.getPerspectiveTransform(p1, p2)
persp = cv2.warpPerspective(img, get_tf, (width, height))
return persp
div_func() 对裁剪的车牌实现颜色转换
# divide_tab的帮助函数
def div_func(image, th=115):
img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(img_gray, (5, 5), 0)
_, thresh = cv2.threshold(blurred, th, 255, cv2.THRESH_BINARY_INV)
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)
boxes = []
for c in contours:
(x, y, w, h) = cv2.boundingRect(c)
#检查高度和宽度是否合理
if h > 150 and (w > 15 and w < 200):
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 0), 1)
boxes.append([[x, y], [x + w, y], [x, y + h], [x + w, y + h]])
return boxes
divide_tab() 用数字和字母在线段上划分裁剪区域,返回包含字母和数字的列表
def divide_tab(image, th=115):
temp_img = image.copy()
bboxes = div_func(image, th)
if len(bboxes) < 7:
th += 10
if th >= 30:
bboxes = div_func(temp_img, th)
bboxes.sort()
num_let_arr = []
for b in bboxes:
# 裁剪的单个值
num_let = perspective(b, image, width=120, height=220)
num_let_arr.append(num_let)
# 返回包含字母和数字的列表
return num_let_arr
recognize() 识别裁剪区域的每个字母和数字,在识别每个字母时,会参考图像字符的概率,找到可能性最大的字母并将它附加到列表中
# 识别每个字母和数字
def recognize(letters, im_paths):
reference = []
names = []
# 读取参考图像并将其添加到数组(同时添加名称)
for image_path in im_paths:
image = cv2.imread(str(image_path), 0)
reference.append(image)
names.append(image_path.name[:1])
text_arr = []
#对于每个字母,计算参考图像的概率(所有字母和数字)
for let in letters:
probabilites = []
letters_arr = []
img_gray = cv2.cvtColor(let, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(img_gray, (3, 3), 0)
ret, thresh = cv2.threshold(blurred, 135, 255, cv2.THRESH_BINARY)
for ind, im in enumerate(reference):
im = cv2.resize(im, (thresh.shape[1], thresh.shape[0]))
result, _ = compare_ssim(thresh, im, full=True)
letter = names[ind]
probabilites.append(result)
letters_arr.append(letter)
# 找到概率最大的字母并将其附加到列表中
max_val = max(probabilites)
for ind, val in enumerate(probabilites):
if max_val == val:
if letters_arr[ind] != 'w' and letters_arr[ind] != 'c' and letters_arr[ind] != 'r':
text_arr.append(letters_arr[ind])
return ''.join(text_arr)
main.py
import argparse
import json
from pathlib import Path
import cv2
import os
from processing.utils import perform_processing
def main():
parser = argparse.ArgumentParser()
parser.add_argument('images_dir', type=str)
parser.add_argument('results_file', type=str)
args = parser.parse_args()
im_dir = Path('./numbers_letters/')
im_paths = sorted([im_path for im_path in im_dir.iterdir() if im_path.name.endswith('.jpeg')])
images_dir = Path(args.images_dir)
results_file = Path(args.results_file)
images_paths = sorted([image_path for image_path in images_dir.iterdir() if image_path.name.endswith('.jpg')])
results = {}
for image_path in images_paths:
image = cv2.imread(str(image_path))
if image is None:
print(f'Error loading image {image_path}')
continue
results[image_path.name] = perform_processing(image, im_paths)
print(os.path.abspath(images_dir))
print(os.path.abspath(results_file))
with results_file.open('w') as output_file:
json.dump(results, output_file, indent=4)
if __name__ == '__main__':
main()
To be continued…
关注我,后续带来识别过程背后的数学原理,一起看透识别的本质 !!!