ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

车牌识别工程实践:从YOLOv5车辆检测到CNN字符识别的完整流水线

车牌识别工程实践:从YOLOv5车辆检测到CNN字符识别的完整流水线 简介一套完整可运行的车牌识别系统源码包覆盖车辆检测、图像采集、图像预处理、车牌定位、字符分割与字符识别等全流程单元面向计算机视觉、人工智能、自动化等专业的学生和开发者既可辅助课程设计与毕业设计也可作为项目初期立项演示的基础版本。压缩包内含133个文件以jpg/bmp车牌样本图、wav音频数据、m脚本、doc文档、fig图片等类型为主整体体积约6.16MB其中doc文档对车牌识别流程进行了详细解析配合m文件和fig可还原算法设计与实验过程。目前已有57人学习下载内容包含可直接运行的测试工程、多元化的车牌图像素材、结构清晰的说明文档及MATLAB绘图源文件便于按模块拆解学习。若在实际运行中遇到问题可直接私信交流支持远程教学也可在此代码基础上扩展其他识别功能。1. 车牌识别不是单个模型而是一条流水线很多初学者以为车牌识别就是“找个识别库传一张图返回车牌号”。真正上手做一套完整系统就会发现识别准确率只是结果真正决定成败的是前面的车辆检测、图像采集、图像预处理、车牌定位、字符分割这些前置单元。任何一个环节处理不干净后面的字符识别模型再好也无济于事。把整个流程拆开看它本质是一条流水线先把车从场景里找出来再把车牌从车上裁下来把车牌图像校正到标准姿态把字符一个个切分开最后才轮到字符识别模型做分类。本文按这条流水线的顺序把每个单元的常见实现方案、关键参数和踩坑点讲清楚给出一套可直接落地的工程路径。2. 车辆检测与图像采集先解决“车在哪”再谈“牌在哪”2.1 检测方案选型YOLOv5是当前最稳的起点车牌识别系统里车辆检测的目的不是识别车型而是为后续的车牌定位提供候选区域。常见做法是先用目标检测模型框出车辆再在车辆区域内找车牌。这样做的好处是大幅缩小车牌搜索范围减少背景中类似车牌纹理的干扰。工程中最常用的方案是YOLOv5系列模型。这里不讨论巅峰对决式的框架对比只说一个实际的选型逻辑YOLOv5的部署生态最成熟从PyTorch训练到ONNX导出再到TensorRT加速每一步都有大量现成资料遇到问题容易搜到解决方案。对于毕设或中小型项目YOLOv5s就够用如果算力紧张或需要边缘端部署YOLOv5n是更轻的选择。训练车辆检测模型需要准备车辆数据集标注类别只设一个“vehicle”即可。如果场景固定比如小区出入口、停车场闸机建议采集该场景的实景数据做微调泛化效果远好于直接使用预训练权重。数据增强方面Mosaic增强对车辆这种大目标效果明显建议开启。# 基于YOLOv5的车辆检测推理示例 import torch model torch.hub.load(ultralytics/yolov5, yolov5s, pretrainedTrue) model.conf 0.4 # 置信度阈值低于此值的检测框被过滤 model.iou 0.5 # NMS的IoU阈值用于抑制重叠框 model.classes [2] # COCO数据集中类别2是car按需调整 results model(frame) detections results.pandas().xyxy[0] # 返回x1,y1,x2,y2,confidence,class参数说明conf设为0.4是为了在漏检和误检之间取平衡。设得太低会把背景误判为车辆设得太高会漏掉远处或部分遮挡的车。iou保留默认0.5即可车辆目标之间很少重叠不需要特殊调整。classes只保留car类减少其他类别的干扰。2.2 图像采集分辨率与帧率的实际取舍图像采集单元决定了整个系统的输入质量上限。一个常被忽视的问题是摄像头的安装角度和高度直接决定了后续车牌定位的难度。推荐使用分辨率不低于1080p的摄像头帧率15fps以上即可车牌识别不需要高帧率但需要低延迟。安装高度2.5至3.5米俯视角度控制在15到30度之间。角度过大车牌会产生严重透视畸变增加后续矫正难度角度过小容易被前车遮挡。实际项目中经常遇到的是光照问题特别是夜间逆光场景。优先选择带宽动态功能WDR的摄像头如果条件受限也可以在图像预处理阶段做补偿但这会增加计算开销。另外建议关闭摄像头的自动白平衡和自动曝光改为固定参数。原因很简单自动调节会让同一块车牌在不同帧中的颜色表现不一致后续基于颜色的车牌定位算法会因此不稳定。2.3 视频流抽帧策略连续帧之间差异很小逐帧处理纯属浪费算力。工程上常见的做法是每隔N帧处理一次或者在检测到运动目标时才触发完整识别流程。import cv2 cap cv2.VideoCapture(rtsp://your_camera_stream) frame_interval 3 # 每3帧处理一次 frame_count 0 while cap.isOpened(): ret, frame cap.read() if not ret: break if frame_count % frame_interval 0: # 此处调用车辆检测和车牌识别流程 process_frame(frame) frame_count 1 cap.release()frame_interval设3的考量是25fps的输入下约8fps的处理频率既保证不会漏掉快速通过的车又不会让CPU或GPU持续满载。如果部署在边缘设备上这个值可以调到5甚至更高。3. 图像预处理为车牌定位扫清障碍3.1 预处理管线的基本顺序图像预处理在车牌识别中不是可选步骤它直接决定车牌定位的稳定性。标准管线包含去噪、色彩空间转换、对比度增强、边缘检测四个环节。顺序不能乱先去噪再增强否则噪声会被放大。import cv2 import numpy as np def preprocess_for_plate(frame): # 1. 高斯去噪去除传感器噪声和压缩伪影 blurred cv2.GaussianBlur(frame, (5, 5), 1.5) # 2. 转灰度用于边缘检测 gray cv2.cvtColor(blurred, cv2.COLOR_BGR2GRAY) # 3. 直方图均衡化增强对比度 gray_eq cv2.equalizeHist(gray) # 4. 转HSV用于颜色筛选定位 hsv cv2.cvtColor(blurred, cv2.COLOR_BGR2HSV) return gray_eq, hsv参数说明高斯核大小(5, 5)适合1080p图像。分辨率更高时核大小可以不变但sigma值建议相应增大。equalizeHist是全局均衡化对整个画面做处理在光照不均的场景中效果一般后续会讲到更优方案。3.2 光照不均场景的自适应处理夜间或逆光场景是车牌识别的重灾区。全局直方图均衡化在画面明暗差异大时会失效——暗区被过度提亮亮区被压暗。更稳妥的方案是CLAHE对比度受限的自适应直方图均衡化它把图像分成小块分别做均衡化并对对比度放大倍数做限制避免噪声被同步放大。def clahe_preprocess(gray): clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) return clahe.apply(gray)clipLimit的含义是限制对比度放大的阈值数值越小越保守。设为2.0对车牌这种本身就带有高对比度图案的目标足够。tileGridSize是把图像分割成8×8的小块块数越多局部适应能力越强但计算量也越大。需要留意的是臻识这类专用车牌识别设备在硬件层面已经做了宽动态处理不需要在软件里再做CLAHE。但对基于通用摄像头搭建的软件系统来说CLAHE是不可或缺的一环。3.3 颜色空间选择HSV为何更适合车牌定位车牌定位有两个主流方向基于边缘特征和基于颜色特征。中国车牌的颜色特征极其鲜明——蓝底白字、黄底黑字、绿底黑字——这让HSV颜色空间成为定位的天然利器。HSV把颜色分解为色调、饱和度和明度三个维度相比RGB空间最大的优势是光照变化主要影响V通道H和S相对稳定。基于颜色定位时只需要对H和S通道做阈值筛选就能在复杂背景中稳定地找出蓝色或黄色区域。def extract_blue_regions(hsv): # 蓝色车牌的HSV范围根据实际摄像头调参 lower_blue np.array([100, 80, 60]) upper_blue np.array([124, 255, 255]) mask cv2.inRange(hsv, lower_blue, upper_blue) # 形态学闭运算填充车牌区域内的字符空洞 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (15, 5)) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) return mask参数说明H通道100到124对应蓝色色调范围。S通道下界设80是为了过滤掉偏灰白的蓝色。V通道下界60过滤掉过暗的阴影区域。(15, 5)的矩形核形状是根据车牌宽高比设计的——车牌宽高比约3比1横向15像素、纵向5像素的核能把字符间隙填平而不把相邻区域连通。4. 车牌定位从候选区域到精确裁剪4.1 基于HSV掩码和轮廓的车牌框选颜色筛选得到的是二值掩码图接下来需要在掩码图中找到车牌轮廓。常规流程是轮廓提取、多边形逼近、按面积和宽高比过滤、获取最小外接矩形。def locate_plate(mask, origin_frame): contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) candidates [] for cnt in contours: area cv2.contourArea(cnt) if area 500: # 过滤小面积噪声 continue rect cv2.minAreaRect(cnt) # 返回旋转矩形 box cv2.boxPoints(rect) box np.int0(box) width rect[1][0] height rect[1][1] if height 0: continue aspect_ratio max(width, height) / min(width, height) # 车牌宽高比约在2.5到4.0之间 if 2.0 aspect_ratio 5.0: candidates.append((rect, box)) return candidates参数说明RETR_EXTERNAL只提取外轮廓不需要内层轮廓。CHAIN_APPROX_SIMPLE压缩水平垂直方向的冗余点降低计算量。面积阈值500针对的是1080p图像在更低分辨率下需要相应下调。aspect_ratio范围2.0到5.0是一个相对宽松的过滤条件目的是尽量不遗漏候选区域宁可多召回再精排。这是因为某些特殊车牌或拍摄角度会压缩宽高比卡得太紧会直接漏检。4.2 透视矫正与仿射变换摄像头俯拍会让车牌产生透视变形矩形变成梯形。矫正的目的是把车牌区域变换成正面视角否则后续的字符分割会继承这种畸变导致分割线歪斜。def rectify_plate(origin_frame, rect): # 获取旋转矩形的四个顶点 box cv2.boxPoints(rect) box np.float32(box) # 按坐标排序左上、右上、右下、左下 box order_points(box) # 目标矩形按车牌标准比例设定宽度和高度 width 240 height int(width * 0.4) # 车牌标准宽高比约为2.5:1取40% dst np.float32([[0, 0], [width, 0], [width, height], [0, height]]) # 计算透视变换矩阵并应用 M cv2.getPerspectiveTransform(box, dst) warped cv2.warpPerspective(origin_frame, M, (width, height)) return warped透视变换矩阵M把任意四边形映射到目标矩形。这里值得注意的一个工程细节是车牌的标准宽高比是440×140mm即约3.14比1而常见蓝牌的字符区域加上边框后比例略小。取0.4倍高度在后续字符分割时更宽松不会把上下边缘切得过紧。4.3 边缘检测与MSER的互补使用HSV颜色定位在蓝色车牌上效果最好但遇到新能源绿色车牌时H通道范围不同且夜间颜色失真严重。工程上的补强方案是边缘检测和MSER最大稳定极值区域双路并行最后做候选框合并。def edge_based_locate(gray): # Canny边缘检测 edges cv2.Canny(gray, 100, 200) # 膨胀连接车辆边缘造成的断裂 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) dilated cv2.dilate(edges, kernel) # 再走轮廓提取和宽高比过滤 contours, _ cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) return contoursCanny的双阈值(100, 200)意味着梯度幅值高于200的像素确定为边缘低于100的排除介于之间的根据连通性决定。车牌字符和背景之间的边缘梯度很强100的下阈值足够捕捉。膨胀核(17, 5)的设计和闭运算同理横向连字符之间的断口。MSER对光照变化不敏感特别适合识别车牌边缘模糊但纹理稳定的情况。两种方法的定位结果可以用IoU合并重叠候选框保留置信度更高的一路。5. 字符分割与字符识别最后一步决定成败5.1 投影法分割的完整流程字符分割是整个流水线中出错率最高的环节。投影法是最经典的方案先对矫正后的车牌做二值化然后计算垂直方向的像素投影投影谷底就是字符间隙。def segment_characters(plate_gray): # 大津二值化自动计算最佳阈值 _, binary cv2.threshold(plate_gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 垂直投影 vertical_projection np.sum(binary, axis0) // 255 # 找出投影中像素数大于0的所有列区间 in_char False char_ranges [] for col, value in enumerate(vertical_projection): if value 0 and not in_char: start col in_char True elif value 0 and in_char: end col in_char False char_ranges.append((start, end)) # 过滤太窄的区间噪声太宽的区间可能是多个字符粘连 char_widths [end - start for start, end in char_ranges] median_width np.median(char_widths) valid_chars [] for start, end in char_ranges: width end - start if width median_width * 0.3: valid_chars.append((start, end)) return binary, valid_charsTHRESH_OTSU自动计算阈值不需要手动指定。投影值按//255归一化使得列方向上的像素计数变成0或1的累加。median_width * 0.3作为过滤阈值是因为车牌上的分隔符如蓝牌中间的小圆点宽度明显窄于字符需要剔除。字符分割中的经典难题是汉字“京”“沪”等左右结构的字内部存在明显断裂投影会出现多个区间导致一个汉字被切成几块。解决思路是合并相邻且间距极小的字符区间或者使用时序连接组件分析。5.2 模板匹配与CNN识别的选型边界字符识别有两类方案传统模板匹配和深度学习分类。模板匹配的原理是将分割出的字符和标准模板库中的字符做相似度对比。它的优势是无需训练数据、推理速度快、部署成本极低适合字符集固定的场景。缺陷是对倾斜、模糊、光照变化的鲁棒性差一个字符有轻微变形就可能导致误识别。CNN分类网络更适合实际环境。输入统一缩放到固定尺寸如64×32网络结构不用复杂几层卷积加全连接即可。中文车牌字符集大小为各省简称31个 字母24个不含I和O 数字10个共约65类。import torch import torch.nn as nn class PlateCharCNN(nn.Module): def __init__(self, num_classes65): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 32x16 nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 16x8 nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 8x4 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 8 * 4, 256), nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(256, num_classes) ) def forward(self, x): return self.classifier(self.features(x))网络结构设计逻辑三层卷积逐步把特征通道从32扩到128每层加最大池化缩小特征图尺寸。输入的灰度图是64×32经过三次池化后变成8×4的特征图展平后是4096维接入全连接层。Dropout设为0.5防止过拟合训练数据不足时这个比例很重要。现实项目里我一般倾向于加载预训练模型做微调用超网字符识别模型提供的权重作为起始点。从零训练需要准备至少每类500张样本这对个人项目是个不小的数据工程。微调时冻结前几层卷积只训练最后一层卷积和全连接层数据量可以降到每类50张左右。5.3 模板匹配与CNN的准确率对比参考方案字符级准确率推理耗时CPU适用场景模板匹配85%~92%1ms固定环境、楷体标准车牌轻量CNN97%~99%3~5ms自然光照、多角度场景重模型ResNet等99%10~20ms高精度要求、有GPU模板匹配在理想条件下能达到90%左右的准确率但真实场景中车牌倾斜、二值化噪声、笔画粗细变化都会让这个数字快速下滑。CNN的优势在于学习到的特征对变形和噪声有天然容忍度。6. 用形态学迭代次数和调试面板做工程调优定位不准确很多情况不是算法不对而是参数需要调试。这里给出手动调参时的核心工具和心法。先做一张可用于debug的图表在划定车牌区域时可视化显示检测的过程这是最快定位问题在哪一环节的方法。Flask搭建一个本地调试面板展示中间结果例如包含检测流程图的页面可以直观看到HSV视图、掩码、形态学处理后的二值图以及分割出的单个字符图。某个环节不对时一眼就能定位问题不需要打印每行代码的信息。形态学操作是最常被调优的参数闭运算的核大小(15, 5)决定了掩码图中字符间隙能否被填充。核太小字是连不起来的核太大把相邻的车牌和车身边缘融为一体。迭代次数同理morphologyEx里iterations参数默认是1某些场景需要调到2iterations参数但要注意每增加一次迭代阈值化的区域边界就会向内收缩几个像素。针对STM32边缘端等硬件受限的场景建议把YOLOv5n的输入分辨率降低到320×320并把形态学核尺寸下调以控制计算量。YOLOv5量化到INT8后精度损失通常控制在1%以内但图像预处理里的浮点操作在STM32上要改成整数运算否则帧率卡到不可用。最后给一个通用的验证方法准备100张包含白天、夜间、雨天、逆光场景的车牌数据统计端到端的准确率。定位失败看HSV阈值和形态学参数分割失败看二值化和投影法的合并逻辑识别失败看CNN的输入校验按这个顺序排查即可。本文还有配套的精品资源点击获取
返回列表