车牌识别毕设全流程:从HSV定位到CNN字符识别的工程实践
简介这是一份面向高校计算机相关专业毕业设计的完整项目资料涵盖车牌自动识别从图像预处理、字符分割到深度学习模型训练识别的全流程方案。资源内包含多种场景下的车牌照数据集、已设计好的CNN模型文件以及配套的项目综述与毕业论文PDF并附赠答辩PPT模板和毕业设计选题参考适合需要快速搭建毕设框架、了解模型原理或准备答辩材料的学生使用。压缩包共56个文件主要类型为16张jpg样例图像、12个hpp头文件与6个cpp源文件另含若干配置文件、2份pdf文档及1个说明txt整体大小约346MB。已有1496人学习下载。项目不仅提供可直接运行的工程代码还以论文形式详细阐述了研究背景、技术路线与实验结果数据集覆盖不同车牌样式和真实场景尤其适合希望兼顾算法实践与论文撰写的学习者对照研读。1. 车牌识别毕设三个算法模块怎么串成一条可靠流水线拿到“基于数字图像处理和深度学习的车牌定位、字符分割识别”这个毕设题目多数人第一反应是找个现成代码跑通。真动手后才发现定位、分割、识别不是三个孤立算法而是一条前后咬合的流水线定位框决定分割喂进去什么图分割结果决定识别模型看到的是不是干净字符。这个项目让人熬夜的原因是它同时踩了数字图像处理和深度学习两摊事——既要懂HSV颜色空间、形态学、投影法这些经典操作又得会调CNN训练与推理。这篇笔记把整条链路拆开讲清楚数据、模型、论文和答辩PPT怎么凑齐也给出实操参数和踩坑记录适合想在一个月内完整交付的应届生也适合要接手类似视觉项目的工程师做参考。2. 车牌定位传统图像处理与深度学习检测两条路线怎么选车牌定位是整个项目的地基。定位框偏一格后面的字符分割就跟着错识别准确率再高也白搭。毕设里最常见的做法是同时实现两条路线一条用数字图像处理里的颜色与形态学定位作为基础方案一条用深度学习目标检测作为改进方案。两条都写进论文对比实验也顺手有了。2.1 颜色加形态学定位的经典流程为什么毕设还要会它传统定位的核心思路是“车牌有固定颜色和长宽比”。国内蓝牌是蓝底白字黄牌是黄底黑字车身、路面、绿植很少有整块同色区域。把这个先验用起来就能在没有GPU的机器上完成定位。我一般会先把BGR图转到HSV空间再做颜色阈值提取、形态学闭运算、轮廓外接矩形过滤三步。HSV比RGB抗光照变化因为色相H和亮度V分开强光下颜色色相变化相对小。import cv2 import numpy as np def locate_plate_by_color(img_bgr): hsv cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV) # 蓝牌区间H在100附近S和V都取中高值 blue_low np.array([100, 80, 80]) blue_high np.array([124, 255, 255]) mask_blue cv2.inRange(hsv, blue_low, blue_high) # 黄牌区间H在20到34附近S高一些减少车灯干扰 yellow_low np.array([20, 100, 80]) yellow_high np.array([34, 255, 255]) mask_yellow cv2.inRange(hsv, yellow_low, yellow_high) mask cv2.bitwise_or(mask_blue, mask_yellow) # 闭运算先膨胀再腐蚀把断裂的字符补成连通块 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (17, 5)) closed cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) boxes [] for cnt in contours: x, y, w, h cv2.boundingRect(cnt) area w * h ratio w / h # 车牌宽高比一般在2到4.5之间面积过滤小干扰 if area 800 and 2.0 ratio 4.5 and h 20: boxes.append((x, y, w, h)) return boxes这段代码逻辑很直白先分别提取蓝牌和黄牌像素合并成一张掩膜再用矩形核把字符间的空隙“填上”最后通过面积和宽高比筛掉树影、车标这类干扰块。参数里最容易翻车的是HSV阈值不同相机拍出来的蓝色偏差很大H取值太窄会把暗处车牌丢掉太宽又会把蓝色车身框进来。调参时的血泪经验是先单独输出mask图看哪些像素被选中别直接跳到轮廓提取。形态学核大小也有讲究。(17, 5)的矩形核适合宽度约440像素的图片如果处理的是高清大图必须按比例放大否则闭运算铺不满整个字符区域轮廓会被拆成好几块。面积阈值800也不是固定值取决于输入图分辨率。更稳妥的做法是先用一个固定宽高比范围找候选再在候选框内部做复检。传统定位的优点是快单张图几毫秒就能出结果而且可解释性强论文里容易画流程图。缺点是扛不住复杂场景大逆光车牌反光、车身贴膜颜色接近车牌、夜间车灯把画面打成一片黄白时颜色阈值基本失效。这正是深度学习方法要接棒的地方。2.2 用YOLO系检测网络做定位训练与推理的落地方式深度学习定位本质上是把“找车牌”变成目标检测任务。常见做法是选YOLOv5或YOLOv8这种成熟框架用自己的车牌图片重新训练。对毕设来说不需要从零写检测头直接用框架的Python接口就好。第一步是准备标注数据。常见做法是把车牌图片整理成目标检测标准格式每张图配一个同名的txt标注文件每行是“类别 x_center y_center width height”坐标都归一化到0到1之间。蓝牌、黄牌可以都归为“plate”一个类也可以在标注里分开类别分开后对颜色鲁棒性会好一点但训练数据量也得翻倍。训练命令在YOLO框架里很统一下面是一个可参考的脚本写法。yolo train \ modelyolov8s.pt \ dataplate.yaml \ epochs80 \ imgsz640 \ batch16 \ lr00.01 \ project./runsplate.yaml里要写清楚数据集路径和类别名modelyolov8s.pt表示加载官方预训练权重做迁移学习。车牌比通用目标简单80个epoch基本足够如果每类图片少于500张建议把epoch加到100以上同时用mosaic1.0数据增强。imgsz640是输入分辨率车牌小字多降到416会明显损失远处小车牌检测能力。推理代码同样简洁下面这段适合放在答辩演示脚本里。import cv2 from ultralytics import YOLO model YOLO(plate_best.pt) # 训练完成后保存的权重 frame cv2.imread(test_01.jpg) results model.predict(frame, conf0.45, iou0.5) for r in results: boxes r.boxes.xyxy.cpu().numpy() for box in boxes: x1, y1, x2, y2 map(int, box) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(plate detect, frame) cv2.waitKey(0)conf0.45是置信度阈值低于这个分数的框会被丢掉iou0.5是NMS的IoU阈值同一个车牌附近可能出现多个重叠候选框NMS负责保留最合适的那个。实际演示时我习惯把conf调到0.3宁可多出两三个误检框也不能漏掉真实车牌。选型上如果论文定位是“改进方案”就用深度学习检测做主线传统颜色定位当baseline如果环境没有GPU也没装CUDA那传统方法就是保底方案深度学习部分只写原理和实验对比不硬跑。3. 字符分割与识别投影法、连通域与CNN分类定位框拿到后下一步是把它切成单个字符。字符分割做得好不好直接决定识别模型的输入质量。很多毕设最后死在识别上根源其实是分割把字符边缘切歪了、把汉字劈成两半或者粘连的字母没分开。3.1 字符分割垂直投影原理与三个必调参数字符分割最常见的手段是垂直投影法。车牌字符在固定区域内按列统计白色像素数字符与字符之间总有一段接近全黑的间隔这段间隔就是天然分界线。预处理顺序非常关键。我的顺序是灰度化 → 大津法二值化 → 去除边框 → 垂直投影切分。如果不先去除车牌周围的白色边框和铆钉投影直方图里会多出几段噪声第一刀就会切错。import cv2 import numpy as np def segment_chars(plate_bgr): # 统一缩放窄边固定为120像素 h, w plate_bgr.shape[:2] scale 120 / h plate cv2.resize(plate_bgr, (int(w * scale), 120)) gray cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV cv2.THRESH_OTSU) # 去掉上下左右边缘的干扰连通域 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: x, y, w_, h_ cv2.boundingRect(cnt) if x 0 or y 0 or x w_ binary.shape[1]: cv2.drawContours(binary, [cnt], -1, 0, -1) col_sum binary.sum(axis0) / 255 chars [] in_char False start 0 for i in range(len(col_sum)): if col_sum[i] 0 and not in_char: in_char True start i elif col_sum[i] 0 and in_char: in_char False # 宽度过滤太窄是噪声太宽是两块粘连没分开 if 8 i - start 40: chars.append((start, i)) # 把切出的区域按原坐标输出 result [] for s, e in chars: char_img binary[:, s:e] result.append(char_img) return result这段代码里有三个参数要特别留意。第一是缩放后的固定高度120它能让后续宽度过滤参数在不同图上保持一致。第二是二值化方向我用的是THRESH_BINARY_INV因为车牌字符是浅色、背景是深色反转后字符变白。第三是字符宽度区间8 i - start 40前一个数过滤掉单像素噪声后一个数拦截没切开的粘连字符。单靠垂直投影处理汉字是最大的坑。汉字“京”内部结构密左右部分之间经常出现白像素连续“川”字三竖之间可能有投影间隙切出来会碎成几块。我一般会追加一条规则如果按4像素宽度切出的碎片数量明显多于标准车牌字符数就合并相邻碎片直到能拼出一个与中文字符宽度相当的完整块。连通域法是投影法之外的主流备选。它把每个连在一起的白色区域当作候选字符优点是不依赖列间距均匀缺点是在字体断裂时把一个字符拆成多个域。两种方法都可以在论文里做对比结论往往是“投影法适合结构清晰的车牌连通域法抗断笔能力更强”。3.2 字符识别从模板匹配到CNN为什么建议直接上分类网络分割完成后就是字符识别。字符类别集合是固定的省份汉字约31个字母24个数字10个有些项目还会加上军牌、警牌的特殊汉字类目总量通常在65到75之间。这是一个典型的小规模图像分类任务。早期毕设常用模板匹配把每个字符和标准模板做相似度比对。这套路的好处是不需要训练缺点是对字体、粗细、倾斜极其敏感同是“A”黑体和宋体能差出很大距离。我不建议毕设主线用模板匹配可以放论文里当对比方法。可靠的方案是训一个轻量CNN。下面这个网络是典型的“卷积提特征、全连接分类”结构输入是单通道灰度图归一化到20像素高、40像素宽。import torch import torch.nn as nn class CharCNN(nn.Module): def __init__(self, num_classes65): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, 3, 1, 1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 高20 - 10 nn.Conv2d(32, 64, 3, 1, 1), nn.ReLU(inplaceTrue), nn.MaxPool2d(2), # 高10 - 5 ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(64 * 10 * 5, 128), nn.ReLU(inplaceTrue), nn.Linear(128, num_classes) ) def forward(self, x): return self.classifier(self.features(x))这里输入宽40、高20两次池化后特征图变成5高、10宽64 * 10 * 5 3200就是全连接层的输入维度。改动输入尺寸时必须同步重算这个数否则会报尺寸不匹配错误。num_classes65对应省份简称31个、字母24个、数字10个。如果只做新能源绿牌识别类别会少一些脚本里改成实际类别数就行。训练时常用配置batch size取64优化器用Adam初始学习率0.001每10个epoch衰减0.1。数据集较小的时候我习惯把每个字符图随机做±5度的旋转、2像素左右的平移和轻微亮度扰动这对泛化很有效。MNIST手写数字识别里那套“归一化到固定尺寸再分类”的思路在这里完全复用。识别阶段把每个字符都过一遍CharCNN取softmax概率最大的类别作为结果。这里有个容易被忽略的工程细节整牌识别和单字符识别准确率不是一回事。单字符准确率到98%六个字符都对的概率只有0.98的6次方约88.6%所以论文里应该同时报告两个指标这也是答辩时最能体现思考深度的地方。4. 数据集、模型与论文三份交付物怎么凑齐毕设标题里明确写了“数据集模型论文”三件套说明评审看重的不只是能跑而是完整性。很多同学在数据这一环偷懒拿网上现成图片直接训练最后模型在测试图上能出结果但论文里连数据分布都说不清。下面按数据准备、模型训练、论文与答辩PPT三步展开。4.1 数据准备公开数据集与自采数据怎么组合车牌识别有不少开源中文车牌数据集常见的有城市停车场监控采集的“CCPD”风格数据以及各种论文附带的公开数据。用这些数据没问题但论文里一定要写清楚来源、数量、拍摄场景和分辨率避免学术不规范问题。我一般会按“公开数据打底 自采数据补齐”的方式组织。公开数据负责提供大量不同天气、不同角度、不同距离的样本自采数据则从学校停车场、路边车辆抓拍几十张专门覆盖公开数据里少见的场景比如新能源绿牌、黄牌货车牌、倾斜较大的侧方停车。文件组织上建议把整体随机划分成70%训练、20%验证、10%测试三份测试集单独放好训练过程中不要碰它。划分表长这样子集用途常见占比说明train训练模型参数70%每个类别都要覆盖到val调超参、选模型20%每轮训练后计算mAPtest最终指标报告10%只跑一次不来回调数据增强是让模型“抗造”的关键。对定位模型我常用Mosaic拼接、随机旋转、HSV扰动、随机遮挡对字符分割和识别则更看重透视变换和模糊。增强不是越多越好过度遮挡会让模型学出错误语义尤其车牌这种结构固定的目标轻微仿射就够。4.2 模型训练与评估指标三个数字决定答辩成败训练阶段要盯住三个指标定位用mAP、分割用交并比IoU、识别用字符级和整牌级准确率。mAP反映定位框和真实框贴不贴IoU反映分割边界准不准字符级准确率是最终体验的直接来源。字符识别训练脚本里加载图片、做归一化、切batch这几步可以照下面的框架写。import torch from torch.utils.data import DataLoader, Dataset from torchvision import transforms class CharDataset(Dataset): def __init__(self, img_paths, labels, transformNone): self.img_paths img_paths self.labels labels self.transform transform def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img cv2.imread(self.img_paths[idx], cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (40, 20)) img_tensor torch.from_numpy(img).float().unsqueeze(0) / 255.0 label self.labels[idx] return img_tensor, label transform transforms.Compose([ transforms.RandomAffine(degrees5, translate(0.05, 0.05)), transforms.RandomAdjustSharpness(2), ]) train_loader DataLoader(CharDataset(train_paths, train_labels, transform), batch_size64, shuffleTrue, num_workers4)img_tensor形状是(1, 20, 40)对应模型里的单通道输入。DataLoader的num_workers4会开四个子进程读图Windows上如果报多进程错误改成0就能解决。训练时每个epoch结束跑一次验证集记录loss和准确率模型收敛到验证准确率不再上升时保存。评估时整牌识别率的计算要严谨只有一张车牌的全部字符都识别正确才计为这一张识别成功。答辩PPT里的指标表我建议展示“单字符准确率98.5%整牌准确率91.2%”这种组合比单报一个更好看也更诚实。4.3 论文结构建议与答辩PPT组织思路论文不是代码的说明书要把“为什么用这个方法”讲清楚。章节结构可以这样安排第一章绪论写背景和国内外现状第二章写数字图像处理基础第三章写车牌定位第四章写字符分割与识别第五章写实验与结果分析。定位和识别两章是核心每种方法都要给流程图、关键公式和实验结果。答辩PPT我见过很多翻车现场最常见的问题是贴满代码。实际上评委更想看到三层东西系统架构图、典型结果图、对比实验表。架构图用Visio或PPT自带图形画画清“输入图像→定位→分割→识别→输出文本”五个方框就行。典型结果图要挑三张有代表性的一张清晰正面车牌、一张倾斜或模糊的难例、一张误检样例。把这三张放出来再配一句“本方法在xx情况下仍能正确识别”比念二十页代码有用得多。PPT最后一页放指标表mAP、整牌准确率、单张平均推理耗时三个数字就够了。如果时间和经费允许把实时演示录成短视频嵌入PPT比现场连摄像头稳定得多这也是一个有价值的备选方案。5. 车牌识别项目避坑记录环境、数据、结果三类高频问题这类项目翻车往往不是算法原理难而是细节没扛住。下面五条是反复出现的坑按“现象 → 原因 → 解决”写清楚能帮你少走很多弯路。5.1 环境配置OpenCV版本不一致导致读取图片直接报错现象代码在A电脑跑得好好的换到B电脑后cv2.imread返回None程序当场崩溃。原因OpenCV有两个容易混的包opencv-python和opencv-contrib-python同时安装或版本错位时会覆盖核心库另外SciPy、NumPy版本太新也可能触发二进制接口不兼容。解决统一用一个Python虚拟环境安装固定版本号来避免“科学幻想式”的依赖地狱。一个可复现的做法是pip install opencv-python4.8.0.74 numpy1.24.3 torch torchvision之后不再随意升级。换机器时直接用pip freeze requirements.txt导出依赖到新机器pip install -r requirements.txt。5.2 汉字被切成碎片车牌定位正确但分割出五六个零碎块现象定位框很准垂直投影切割后“京”被切成两三块“苏”字中间断开。原因汉字笔画多且左右结构密集垂直投影在字内部出现全零列分割器把它当成字符边界。解决加两个后处理规则。一是字符间距判定车牌字符间距相对稳定出现宽度只有正常字符一半的小块时优先与左右合并二是固定字符数约束标准车牌除点号外一共7个字符新能源8个切出超过预期数量时自动触发合并逻辑。这个方法治标更治本比单纯调投影阈值可靠。5.3 蓝色车身和树影被当成车牌定位误检率高企现象定位阶段框出一堆候选区域里面有蓝色卡车车身、蓝色路牌、树叶阴影字符分割阶段直接跑飞。原因颜色阈值只看了HSV通道变化大时暗部蓝色与深色车身在HSV空间很接近形态学闭运算又把这些干扰块连成了大块。解决在过滤条件里增加“内部纹理”特征车牌区域内有稳定的黑白交替边缘计算候选框内边缘密度低于阈值直接丢弃。另一个更省事的方法是把深度学习检测模型的conf从0.45提高到0.6误检率能明显下降代价是多一些漏检。5.4 单字符识别准但整牌识别率低结果总差一个字母现象每个字符单独看准确率有98%但整牌经常错一到两个字符尤其是“0”和“O”、“1”和“I”这类形近字。原因这类错误本质是类别间特征过于相似单字符分类器没有上下文约束。解决在识别后加一个车牌语法规则层第一位必须是省份汉字第二位必须是字母后面是字母和数字混合最后一位通常是数字。对不符合规则的识别结果强制在候选类别里换一个最接近的合法字符。这一步代码量不大但对整牌准确率的提升非常明显。5.5 答辩现场演示卡顿实时识别只有五六帧现象用视频流做演示时模型推理慢画面一卡一卡本来想展示的效果大打折扣。原因深度学习模型并行处理差CPU推理一张640分辨率的图要几十毫秒以上分割、识别再各跑一遍延迟叠加。解决演示前把视频先降采样到480p推理尺寸改为416字符识别不用全图做只对定位框区域做计算量会小很多。还有一个遗留下来的技巧是预录三段视频现场优先播录像实时演示作为备选这样既稳又不减精彩度。6. 让毕设显得“可落地”的小技巧用难度递进测试表收尾到了这一步定位、分割、识别都已经跑通但答辩时只放一张识别成功的图说服力不够。我习惯在项目收尾时做一张“难度递进测试表”故意把系统往坏环境里推看它什么时候撑不住。这个习惯帮我发现过不少问题也让老师觉得系统是真的测试过。测试表可以按四个维度组织光照、角度、模糊、装饰干扰。难度测试样本预期结果实测备注低晴朗白天、正面平视、清晰无遮挡整牌识别成功输入分辨率足够中逆光、车牌略微反光定位框仍在识别可能抖动调亮度后再识别中侧方停车、车牌倾斜20度定位框偏转字符倾斜加仿射校正高夜间路灯、车灯直射颜色定位失效换深度学习定位高车速较快、运动模糊字符边缘模糊、分割粘连识别置信度会下降每张测试图都记录“定位框是否准、分割是否完整、识别是否全对”三个结果。做完这张表论文实验章就有了骨架答辩时直接投影这张表再配合两三个例子讲解比空谈算法有效得多。还有一个具体技巧把置信度阈值做成可调参数方便演示时临时改动。可以在脚本里增加一个--conf参数或者用一个滑动条直接控制。演示环境下光线复杂提前设死的阈值很可能失灵临时调能救场我就在答辩前翻车过一次之后学乖了。项目收尾前我还会再做一次“从空白环境复现”的演练删除虚拟环境重新按文档装依赖、跑通全流程。这一遍能暴露所有没写进文档的暗坑也顺便验证了交付给别人的可靠运行承诺。这个项目真正值钱的地方不是某个模型的精度而是你能稳定复现、能讲清每个模块边界的能力。希望以上这些内容能帮你把毕设从“能跑”做到“能讲、能扛住提问”也希望你在答辩时稳得住。本文还有配套的精品资源点击获取