尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

车牌识别毕设完整流水线:定位、分割与识别实战指南

发布时间:2026/9/29 18:19:05

资讯中心
01
ARTICLE

车牌识别毕设完整流水线:定位、分割与识别实战指南

车牌识别毕设完整流水线:定位、分割与识别实战指南
简介这套毕业设计项目聚焦数字图像处理与深度学习技术在车牌定位、字符分割及识别中的应用面向高校计算机专业毕业生提供从算法原理、代码实现到论文答辩的全流程参考方案也适合用于智能交通、车辆管理等相关课题的快速验证。包内共有五十六个文件包含十六张图片样本、多份程序源码文件与工程配置文件另有项目综述和毕业论文两篇文档、答辩PPT模板、三百套论文题目清单等压缩包大小约三百四十六兆目录划分清晰便于按学习阶段查阅。目前已有一千四百九十六人浏览学习是毕设选型阶段的热门参考。数据集覆盖典型车牌类型与多类场景卷积神经网络模型经过优化可直接运行论文对研究背景、技术路线和实验结果作了完整梳理答辩PPT模板还有助于缩短最终汇报准备时间。1. 车牌定位与字符识别毕业设计里最该先跑通的一条完整流水线毕设做到车牌识别最容易卡住你的往往不是模型选型而是没搞清楚“定位、分割、识别”这三步到底谁先谁后、各自处理什么数据。很多人一上来就翻“深度学习车牌识别”的代码结果拿到手只有推理脚本训练集、标签、预训练权重全是空的跑一步报一个错最后靠玄学调参才勉强出图。这个项目给出的是一条能自圆其说的完整链路数字图像处理负责把原始图片整理成模型吃得了的输入YOLO系检测器负责“车牌在哪”传统投影法做字符切分最后用CNN做单个字符分类。它能解决的核心问题是让你在答辩时有底气说清每一个模块为什么这么选定位为什么不用模板匹配、分割为什么不用连通域硬切、识别为什么不用全连接层硬扛。适合理工科做毕设、想快速拿到一份可运行且带论文对应章节的工程代码又不打算在数据集清洗上耗两周的人。2. 数据准备与预处理先让图像变成模型认得的数字2.1 车牌数据集的目录结构与标注格式拿到压缩包后先别急着跑 train.py第一步是确认数据目录长什么样这决定了你后面改路径、加数据集时会不会翻车。常见做法是数据根目录下分 images 和 annotations其中 images 放原始拍摄的车辆图片annotations 放同名 txt 文件每一行记录一个车牌的归一化坐标。以 YOLO 格式为例每行是class_id center_x center_y width height单位是像素除以图像宽高后的比例值。在项目源码里找到 dataset 或 config 目录下的 yaml 文件把路径指到你自己的数据根目录。我一般会先在终端里跑一段快速检查脚本确认标注没有越界或为空。下面这段代码负责可视化标注框能帮你一眼看出坐标有没有标反。import cv2 import os def draw_boxes(img_path, txt_path): img cv2.imread(img_path) h, w img.shape[:2] if not os.path.exists(txt_path): print(标注文件不存在请检查目录对应关系) return with open(txt_path, r) as f: for line in f.readlines(): parts line.strip().split() # parts: [class_id, cx, cy, bw, bh]按比例换算回像素坐标 cx, cy, bw, bh map(float, parts[1:]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0) # 单张检查示例basepath 替换成你的数据集路径 draw_boxes(dataset/images/001.jpg, dataset/annotations/001.txt)draw_boxes 的核心是把 txt 里的相对坐标还原成图像像素坐标如果显示出来的框明显偏离车牌本体说明标注坐标系不是 YOLO 的 xywh而是 VOC 的 x1y1x2y2。两者差别很大这类资源里常见的是 YOLO 格式但最好先确认否则训练时 loss 会居高不下。2.2 图像预处理参数尺寸、颜色空间与增强策略车牌识别不能直接把原图塞进去因为拍摄角度、光照、远处小目标都会干扰定位。这个项目里图像处理部分主要做三件事灰度化、直方图均衡化、归一化尺寸。灰度化不是给模型看的而是给字符分割提供稳定的二值化输入直方图均衡化用来缓解逆光时车牌区域过暗的问题归一化则是把不同分辨率的图片统一到模型输入尺寸。预处理的具体参数值得记一下答辩时经常被追问。灰度化直接走 OpenCV 的cv2.cvtColor均衡化使用 CLAHE限制对比度参数 clipLimit 取 2.0tileGridSize 取 (8,8)。尺寸不是统一到 640而是先等比缩放让长边不超过 1024避免车牌区域被过度压扁。增强策略里比较有效的是 HSV 空间的亮度扰动和轻微旋转旋转角度控制在正负 5 度以内超过这个范围字符形变太严重。import cv2 import numpy as np def preprocess(img_path, target_long_edge1024): img cv2.imread(img_path) h, w img.shape[:2] # 等比缩放保证车牌字符宽高比不被破坏 scale target_long_edge / max(h, w) if scale 1.0: img cv2.resize(img, (int(w * scale), int(h * scale))) # CLAHE 均衡化亮度不均时效果明显 lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) lab[:, :, 0] clahe.apply(lab[:, :, 0]) img cv2.cvtColor(lab, cv2.COLOR_LAB2BGR) return img预处理里最容易忽视的是 CLAHE 要转 LAB 空间操作亮度通道而不是直接在 BGR 上做直接做会偏色后期字符分割时会莫名丢失边缘信息。scale 判断小于 1.0 才缩放是为了避免小图被放大后出现像素噪声训练时这类噪声会让模型把纹理当特征。3. 车牌定位与字符分割把“找车牌”和“切出字符”拆成两件事3.1 定位模型选型为什么用 YOLO 而不是传统形态学车牌定位有两条路线传统数字图像处理里的颜色分割加形态学操作以及基于深度学习的回归检测。前者在干净背景、固定角度的数据集上效果不错一旦遇到倾斜、遮挡、夜间反光就扛不住。这个项目给的是深度学习方案定位网络用 YOLOv8 结构原因在于它把检测头和解耦分类分支拆开对小目标的召回明显好过老版 YOLOv5而车牌在整张图上通常只占很小面积。模型输入大小选了 640 而不是 416因为车牌字符密集输入太小字符在特征图上的像素太少后续分割步骤会切不出边界清晰的字符。换到 640 后 mAP 大概能涨 2 到 3 个点代价是训练显存多占 2G 左右如果你的显卡是 8G 显存batch size 直接设 8 就可能爆显存需要降到 4。# 训练定位模型hyp 文件可调整 lr 和 mosaic 概率 yolo train datalicense_plate.yaml modelyolov8n.pt epochs100 imgsz640 batch4训练参数里真正影响效果的并不是 epoch 数而是 mosaic 增强概率和 lr0。mosaic 把四张图拼在一起训练能让模型在小目标上更鲁棒但到了最后 10 个 epoch 通常建议把 mosaic 关掉否则定位框的稳定性会变差。lr0 从 0.01 降为 0.005 是常见操作数据集规模不大时学习率太高容易震荡。3.2 字符分割投影法与连通域的选择边界定位网络输出的是车牌外接框拿到这个框之后要做的不是直接交给识别网络而是先二值化、再切出每个字符。这一步传统图像处理仍然是性价比最高的方案。核心流程是把车牌区域灰度化 → 大津法求阈值 → 二值化 → 水平投影找到字符行的上下边界 → 垂直投影找到每个字符的左右边界。投影法的问题在于字符粘连和边框干扰所以二值化之前建议先做一次边缘腐蚀操作把车牌上下边框和铆钉区域去掉。具体做法是取车牌区域高度的 10% 作为腐蚀核尺寸腐蚀两轮。下面是字符切分的完整实现它处理的是已经按车牌宽高比裁剪好的区域不是整张图。import cv2 import numpy as np def char_segmentation(plate_img): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 大津法二值化背景和字符形成双峰分布时效果最稳定 _, binary cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) h, w binary.shape # 腐蚀掉上下边框和铆钉核宽度取整张图高度的 1/10 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (int(h * 0.1), 1)) binary cv2.erode(binary, kernel, iterations2) # 水平投影确定字符行的纵向范围 horiz np.sum(binary 255, axis1) rows np.where(horiz 0)[0] top, bottom rows[0], rows[-1] binary binary[top:bottom, :] # 垂直投影按列统计连续不为零的区间视为一个字符 vert np.sum(binary 255, axis0) col_has_char vert 0 chars [] start None for i, flag in enumerate(col_has_char): if flag and start is None: start i elif not flag and start is not None: chars.append((start, i)) start None if start is not None: chars.append((start, len(col_has_char))) # 过滤掉过窄的区间这类通常是噪点而不是字符 chars [c for c in chars if c[1] - c[0] 5] return binary, chars投影法能成立的前提是字符本身是连通的而且车牌没有严重倾斜。如果切出来的字符数量明显多于或少于真实值先检查二值化是不是把车牌底色也当成了前景。蓝色车牌的 BGR 通道里蓝色分量偏高直接大津法容易把蓝色区域全判成白色正确的预处理是先把 RGB 转到 HSV对蓝色通道单独提取掩膜再二值化这一步不写进代码里很容易被忽略。3.3 识别网络轻量 CNN 吃掉单字符字符切出来之后每张图就是一个 20×40 左右的二值化字符块识别网络的输入就定为 28×28 灰度图。这里没必要上 ResNet用一个三层 CNN 加一层全连接就够因为字符类别只有 65 个左右省份简称加字母数字特征复杂度远低于 ImageNet 那类任务。import torch.nn as nn class CharCNN(nn.Module): def __init__(self, num_classes65): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(), ) self.classifier nn.Linear(128 * 7 * 7, num_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)网络结构里有几个点对复试答辩很重要。卷积核全部是 3×3两个 3×3 堆叠等价于一个 5×5 的感受野但参数量更少。池化层只在前面两层加了最后一层卷积不加池化是为了保留更多字符纹理细节。全连接层之前算出来的 7×7 是 28×28 输入经过三次池化后的尺寸如果改输入尺寸这一段要同步调整否则view会报尺寸不匹配。4. 从训练到推理把定位、分割、识别串成一条可复现的流水线4.1 训练流程与数据划分整个项目需要训练两个模型定位模型和字符识别模型。它们的训练数据来源完全不同。定位模型的训练数据是整张车辆图加车牌框标注字符识别模型的训练数据则是从定位训练集里裁出来的单个字符。一个非常关键的细节是字符识别的训练集必须和定位模型的测试集来源分开否则会数据泄露。也就是说定位模型测试集里的图片永远不能出现在字符识别训练集里。常见做法是按车辆图为单位划分先用 7:2:1 把图片分成训练、验证、测试三份再分别从每份里裁字符而不是先把所有图片混在一起再划分。# 从定位数据集裁出字符图生成字符识别训练集 python tools/crop_chars.py \ --images dataset/images \ --labels dataset/annotations \ --output dataset/chars/train \ --split traincrop_chars 脚本内部会先调定位模型对图片做推理但这里有个坑既然有标注框就不需要让定位模型先预测直接从标注文件里读坐标裁图。用定位模型预测会引入额外误差特别是模型没收敛时裁出来的字符可能缺胳膊少腿影响识别训练。4.2 推理脚本解读从图片路径到车牌字符串推理部分是这个项目最值得反复看的模块它把前两章的内容组装成一条流水线。输入一张图片先走 YOLO 检测得到车牌框再对框内区域做透视矫正然后二值化和投影法切字符最后逐字符过识别模型。透视矫正常被忽略但它决定了字符识别上限。车牌在照片里往往是平行四边形直接切片会导致字符倾斜识别模型没见过倾斜样本时就容易错判。矫正方式是检测车牌框的四个角点按固定宽高比映射到正矩形。def plate_recognize(img, detector, char_model): # 1. 定位 results detector.predict(img, conf0.4) boxes results[0].boxes.xyxy.cpu().numpy() if len(boxes) 0: return None x1, y1, x2, y2 [int(v) for v in boxes[0]] plate_roi img[y1:y2, x1:x2] # 2. 预处理 分割复用前面定义好的函数 binary, char_boxes char_segmentation(plate_roi) if len(char_boxes) 0: return None # 3. 识别每一个字符顺序拼接 plate_str for (s, e) in char_boxes: char_img binary[0:binary.shape[0], s:e] char_img cv2.resize(char_img, (28, 28)) char_img char_img.reshape(1, 1, 28, 28) / 255.0 pred char_model.predict(char_img) plate_str idx_to_char[pred.argmax(dim1).item()] return plate_str这段代码里 conf0.4 这个置信度阈值值得细说。阈值设低能多召回边界框但误检也会增多被误检的区域切出来的字符通常是一片噪点。我一般先在测试集上小批量试 0.3、0.4、0.5 三个档位数一下漏检和误检数量的变化再定下来。实际项目里 0.4 是多数场景的平衡点。4.3 模型精度与运行效率的平衡点车牌识别场景对速度的要求不算极端但要能流畅跑起来给答辩演示。YOLOv8 模型文件有 n、s、m、l 四种规格这个项目里默认给的是 n也就是 nano 版本推理一张 1280×960 图片在 CPU 上大概要 1.2 秒GPU 上 30 毫秒以内。如果换用 s 版本mAP 大概涨 2 到 3 个点但 CPU 推理时间几乎翻倍需要自己在精度和演示流畅度之间做取舍。字符识别模型本身计算量非常小瓶颈全在定位网络。如果你的电脑只有 CPU可以在推理脚本里把imgsz从 640 降到 480检测速度能快 40% 左右代价是远距离小目标召回率下降。这个改法在答辩数据集上通常还能接受因为照片里的车牌一般不会小到离谱。5. 避坑指南车牌识别项目最容易翻车的五个细节5.1 现象二值化后字符和背景糊成一片原因车牌底色是蓝色时BGR 图像直接做灰度化蓝色通道贡献的灰度值偏高车牌底色的灰度级和白色字符非常接近大津法计算出的阈值无法把二者分开。解决不要直接对灰度图做 Otsu改用 HSV 颜色空间先取蓝色区域掩膜把掩膜区域外的像素统一置为黑色再做灰度化和二值化。取掩膜时 H 通道范围设在 100 到 130S 通道大于 60经验值在多数路灯场景下都好用。5.2 现象字符分割切出的块数量不对有时把汉字切成两半原因投影法按列统计连续前景区域汉字本身笔画结构特殊像“豫”“冀”这类字形在垂直投影上可能分成两个不相连的区间。解决字符数已经明确是 7 位的场景下切完做一次数量校验如果切出 8 段就对宽度最短的两段判断它们的间隔是否小于一个字符宽度是则合并。这一步要放在代码里而不是手工改图因为测试集里必然会出现多个类似样本。5.3 现象定位模型训练 loss 正常但实测经常漏检远处车辆原因数据集里的标注框大多覆盖近处的清晰车牌模型没有见过小尺度目标。YOLO 的通用增强能将就但特征金字塔对小目标的特征融合权重不足。解决训练时将图片按 0.5、1.0、1.5 三档随机缩放后再送入模型并且把 mosaic 概率调高到 0.8。如果改动后 mAP 没变化检查数据集中是否本身就没有远处带车牌的图缺啥补啥是更直接的方案。5.4 现象字符识别训练精度高但整体车牌识别率上不去原因识别模型过拟合了裁好的干净字符图推理时从真实图片里切出来的字符有倾斜、断裂、噪声分布完全不同。解决训练字符识别模型时加入在线增强对每个字符图做随机旋转正负 3 度、随机擦除 5% 像素区域。这模拟的是推理阶段分割不完美的真实情况比单纯堆训练数据更有效。5.5 现象坑在依赖版本上torch 和 torchvision 版本不匹配原因这类项目给的 requirements.txt 通常锁了一批常用版本但很多人机器上预装的 PyTorch 版本旧加载 YOLO 权重时提示缺少 key或者某些算子的行为变了。解决不要直接 pip install -r requirements.txt 覆盖现有环境单独建一个 conda 环境按项目要求固定 Python 版本为 3.8 或 3.9然后重新装 torch 和 torchvision。之前遇到过一位同行在 3.11 上跑通了训练推理时死活报错最后发现是依赖库在 3.11 下的兼容性踩了坑。6. 答辩验证技巧用可量化的数据让项目站得住脚答辩时最容易露怯的环节是评委问“你的准确率是多少”回答“大概挺高的”等于没回答。这个项目支持你端到端评估整个流水线而不仅是模分别算。我建议准备三个层面的数据定位 mAP、字符识别准确率、端到端车牌识别率前面两个是中间指标最后一个是答辩现场的硬通货。端到端评估不用额外写复杂框架在测试集上跑完识别脚本后做字符串级比对就行。具体指标有两个完全正确率也就是预测结果和标注完全一致的比例字符正确率统计所有测试图片里识别正确的字符数除以总字符数。前者严格后者能看出整体水平。如果完全正确率不到 80%大概率是字符分割阶段出了问题而不是识别网络的问题用第 5 章说的数量校验能先补上一部分。另一个容易被追问的点是数据集规模。不要只报总数要把“多少张训练图、多少张测试图、多少个字符类别、每类字符的样本分布”说清楚。字符类别分布尤其重要如果数字 0 和字母 O 在训练集里样本数悬殊识别模型天然偏向样本多的那一类。我处理这类问题时会先从训练集里统计混淆矩阵把高频混淆对列出来比如 0 和 O、1 和 I、B 和 8。能主动说出来“模型在 B 和 8 之间容易混淆因为数据集里这两个字符的形态相似且样本不均衡”比等着评委发现问题要好得多。最后的习惯是我这些年养成的每次跑完一轮实验强制把测试结果导出成 JSON 文件里面记录图片名、预测结果、标注结果、置信度再写一行脚本统计三类指标。这样答辩前不用临时跑程序而且评委问“这个错误案例是怎么回事”的时候可以直接拍出具体的图片和输出数据来分析。项目里带的论文和答辩 PPT 也是按这个思路组织的先摆出问题再给出方案然后用数据说话。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。