汽车车牌识别(License Plate Recognition)是一个日常生活中的普遍应用,特别是在智能交通系统中,汽车牌照识别发挥了巨大的作用。汽车牌照的自动识别技术是把处理图像的方法与计算机的软件技术相连接在一起,以准确识别出车牌牌照的字符为目的,将识别出的数据传送至交通实时管理系统,以最终实现交通监管的功能。在车牌自动识别系统中,从汽车图像的获取到车牌字符处理是一个复杂的过程,主要分为四个阶段:图像获取、车牌定位、字符分割以及字符识别。目前关于车牌识别的算法有很多,本文基于opencv构建了车牌识别的整个流程,供大家学习参考。
OpenCV的全称是:Open Source Computer Vision Library。OpenCV是一个基于开源发行的跨平台计算机视觉库,可以运行在Linux、Windows和Mac OS操作系统上。它轻量级而且高效——由一系列 C 函数和少量 C++ 类构成,同时提供了Python、Ruby、MATLAB等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
车牌辨认的整个过程,可以拆解为以下三个步骤:
我国的汽车牌照一般由七个字符和一个点组成,车牌字符的高度和宽度是固定的,分别为90mm和45mm,七个字符之间的距离也是固定的12mm,点分割符的直径是10mm,字符间的差异可能会引起字符间的距离变化。
在民用车牌中,字符的排列位置遵循以下规律:
从图像处理角度看,汽车牌照有以下几个特征:
为了简化处理,本次学习中只考虑蓝底白字的车牌。
import cv2
img = cv2.imread('../data/bmw01.jpg')
# 调整图片大小
img = cv2.resize(img, (1024, 800))
# 灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 显示效果
cv2.imshow('gray', gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
显示结果如下:
# 双边滤波
blf = cv2.bilateralFilter(gray, 13, 15, 15)
show_image('bilateralFilter', blf)
显示结果如下:
# 边缘检测
edged = cv2.Canny(blf, 30, 200)
show_image('canny', edged)
显示结果如下:
cv2.findContours说明:
image, contours, hierarchy = cv.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
contours, hierarchy = cv.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
OpenCV中HSV空间颜色对照表
提取图像区域的颜色
def reg_area_color(image):
"""找到原图像最多的颜色,当该颜色为红色或蓝色时返回该颜色的名称"""
kernel = np.ones((35, 35), np.uint8)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 以上为图像处理
Open = cv2.morphologyEx(hsv, cv2.MORPH_OPEN, kernel)
# 对Open图像的H通道进行直方图统计
hist = cv2.calcHist([Open], [0], None, [180], [0, 180])
# 找到直方图hist中列方向最大的点hist_max
hist_max = np.where(hist == np.max(hist))
# hist_max[0]为hist_max的行方向的值,即H的值,H在0~10为红色
if 0 < hist_max[0] < 10:
res_color = 'red'
elif 100 < hist_max[0] < 124: # H在100~124为蓝色
res_color = 'blue'
else:
# H不在前两者之间跳出函数
res_color = 'unknow'
return res_color
寻找车牌轮廓:
# 寻找轮廓(图像矩阵,输出模式,近似方法)
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 根据区域大小排序取前十
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]
screenCnt = None
# 遍历轮廓,找到车牌轮廓
for c in contours:
if cv2.contourArea(c) > 1024 * 768 * 0.05:
continue
# 计算轮廓周长(轮廓,是否闭合)
peri = cv2.arcLength(c, True)
# 折线化(轮廓,阈值(越小越接近曲线),是否闭合)返回折线顶点坐标
approx = cv2.approxPolyDP(c, 0.018 * peri, True)
# 获取四个顶点(即四边形, 左下/右下/右上/左上
if len(approx) == 4:
# [参数]左上角纵坐标:左下角纵坐标,左上角横坐标:右上角横坐标
crop_image = img[approx[3][0][1]:approx[0][0][1], approx[3][0][0]:approx[2][0][0]]
show_image('crop', crop_image)
if 'blue' == reg_area_color(crop_image):
screenCnt = approx
break
# 如果找到了四边形
if screenCnt is not None:
# 根据四个顶点坐标对img画线(图像矩阵,轮廓坐标集,轮廓索引,颜色,线条粗细)
cv2.drawContours(img, [screenCnt], -1, (0, 0, 255), 3)
show_image('contour', img)
运行结果显示:
"""遮罩"""
# 创建一个灰度图一样大小的图像矩阵
mask = np.zeros(gray.shape, np.uint8)
# 将创建的图像矩阵的车牌区域画成白色
cv2.drawContours(mask, [screenCnt], 0, 255, -1, )
# 图像位运算进行遮罩
mask_image = cv2.bitwise_and(img, img, mask=mask)
show_image('mask_image', mask_image)
运行结果显示:
"""图像剪裁"""
# 获取车牌区域的所有坐标点
(x, y) = np.where(mask == 255)
# 获取底部顶点坐标
(topx, topy) = (np.min(x), np.min(y))
# 获取底部坐标
(bottomx, bottomy,) = (np.max(x), np.max(y))
# 剪裁
Cropped = gray[topx:bottomx, topy:bottomy]
运行结果显示:
paddleocr是一款轻量型字符识别工具库,支持多语言识别,支持pip安装与自定义训练。
pip install paddleocr -i https://mirror.baidu.com/pypi/simple
pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple
代码实现:
"""OCR识别"""
# 使用CPU预加载,不用GPU
ocr = PaddleOCR(use_angle_cls=True, use_gpu=False, ocr_version='PP-OCRv3')
text = ocr.ocr(cropped, cls=True)
for t in text:
print(t[0][1])
运行结果显示如下:
[2023/11/15 20:57:43] ppocr DEBUG: dt_boxes num : 1, elapsed : 0.016942501068115234
[2023/11/15 20:57:43] ppocr DEBUG: cls num : 1, elapsed : 0.013955354690551758
[2023/11/15 20:57:43] ppocr DEBUG: rec_res num : 1, elapsed : 0.12021970748901367
('苏A·0MR20', 0.8559348583221436)
import cv2
import numpy as np
from paddleocr import PaddleOCR
def show_image(desc, image):
cv2.imshow(desc, image)
cv2.waitKey(0)
cv2.destroyAllWindows()
def reg_area_color(image):
"""找到原图像最多的颜色,当该颜色为红色或蓝色时返回该颜色的名称"""
kernel = np.ones((35, 35), np.uint8)
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
# 以上为图像处理
Open = cv2.morphologyEx(hsv, cv2.MORPH_OPEN, kernel)
# 对Open图像的H通道进行直方图统计
hist = cv2.calcHist([Open], [0], None, [180], [0, 180])
# 找到直方图hist中列方向最大的点hist_max
hist_max = np.where(hist == np.max(hist))
# hist_max[0]为hist_max的行方向的值,即H的值,H在0~10为红色
if 0 < hist_max[0] < 10:
res_color = 'red'
elif 100 < hist_max[0] < 124: # H在100~124为蓝色
res_color = 'blue'
else:
# H不在前两者之间跳出函数
res_color = 'unknow'
return res_color
img = cv2.imread('../data/bmw01.jpg')
# 调整图片大小
img = cv2.resize(img, (1024, 768))
# 灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
show_image('gray', gray)
# 双边滤波
blf = cv2.bilateralFilter(gray, 13, 15, 15)
show_image('bilateralFilter', blf)
# 边缘检测
edged = cv2.Canny(blf, 30, 200)
show_image('canny', edged)
# 寻找轮廓(图像矩阵,输出模式,近似方法)
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 根据区域大小排序取前十
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:10]
screenCnt = None
# 遍历轮廓,找到车牌轮廓
for c in contours:
if cv2.contourArea(c) > 1024 * 768 * 0.05:
continue
# 计算轮廓周长(轮廓,是否闭合)
peri = cv2.arcLength(c, True)
# 折线化(轮廓,阈值(越小越接近曲线),是否闭合)返回折线顶点坐标
approx = cv2.approxPolyDP(c, 0.018 * peri, True)
# 获取四个顶点(即四边形, 左下/右下/右上/左上
if len(approx) == 4:
# [参数]左上角纵坐标:左下角纵坐标,左上角横坐标:右上角横坐标
crop_image = img[approx[3][0][1]:approx[0][0][1], approx[3][0][0]:approx[2][0][0]]
show_image('crop', crop_image)
if 'blue' == reg_area_color(crop_image):
screenCnt = approx
break
# 如果找到了四边形
if screenCnt is not None:
# 根据四个顶点坐标对img画线(图像矩阵,轮廓坐标集,轮廓索引,颜色,线条粗细)
cv2.drawContours(img, [screenCnt], -1, (0, 0, 255), 3)
show_image('contour', img)
"""遮罩"""
# 创建一个灰度图一样大小的图像矩阵
mask = np.zeros(gray.shape, np.uint8)
# 将创建的图像矩阵的车牌区域画成白色
cv2.drawContours(mask, [screenCnt], 0, 255, -1, )
# 图像位运算进行遮罩
mask_image = cv2.bitwise_and(img, img, mask=mask)
show_image('mask_image', mask_image)
"""图像剪裁"""
# 获取车牌区域的所有坐标点
(x, y) = np.where(mask == 255)
# 获取底部顶点坐标
(topx, topy) = (np.min(x), np.min(y))
# 获取底部坐标
(bottomx, bottomy,) = (np.max(x), np.max(y))
# 剪裁
cropped = gray[topx:bottomx, topy:bottomy]
show_image('cropped', cropped)
"""OCR识别"""
# 使用CPU预加载,不用GPU
ocr = PaddleOCR(use_angle_cls=True, use_gpu=False, ocr_version='PP-OCRv3')
text = ocr.ocr(cropped, cls=True)
for t in text:
print(t[0][1])