☰
基于YOLOv5的车牌识别系统实战:从毕设论文到可运行Demo
2026/10/6 11:34:14 网站建设 项目流程

简介:这份资源是一篇完整的本科毕业设计论文,主题为基于YOLOv5的深度学习车牌识别系统,面向计算机、人工智能及相关专业需要完成毕业设计的学生,也适合希望了解目标检测落地应用的开发者参考。论文围绕图像采集、预处理、模型训练、车牌定位、字符分割与字符识别等关键环节展开,并采用Python结合OpenCV等库完成系统实现,对深度学习在智能交通场景中的应用做了较系统的论述。资源包共1个文件,为docx格式的论文文档,整体约1.14MB,包含摘要、目录及绪论等章节,结构完整,便于直接查阅与借鉴。目前已有1006人学习下载,说明该选题在毕业设计群体中具有较高关注度。读者可从中获取完整的论文框架、技术路线与实现思路,用于选题参考、开题撰写或系统设计借鉴,尤其适合需要快速搭建车牌识别方案并整理成文的中高级学习者。

1. 从一份本科毕设论文拆出的车牌识别系统:YOLOv5 到底能跑出什么

如果你手头正卡在「深度学习车牌识别」这个选题上,或者想找一个能跑通、能写进论文、还能演示的系统骨架,这份基于 YOLOv5 的本科毕业设计论文值得先翻一遍。它不是纯理论综述,而是把需求分析、数据库表设计、功能模块划分、系统测试串成了一条完整链路,技术栈锁定 Python + YOLOv5 + MySQL,功能覆盖车牌识别、统计分析、车牌地图、车牌管理和用户管理五个模块。适合两类人:一是正在做毕设、需要一份结构完整参考的本科生;二是想快速搭一个车牌识别 Demo、验证 YOLOv5 在中文车牌场景下实际表现的工程师。论文里最值钱的部分不是绪论,而是它把「检测—定位—分割—识别」这条流水线拆成了可落地的模块,并且给出了数据表字段和页面交互逻辑,照着改就能跑。

2. 系统骨架与数据层:先搞清楚模块怎么切、表怎么建

2.1 五个功能模块的职责边界

论文把系统切成首页、车牌识别、用户管理、统计分析、车牌地图、车牌管理六个页面,其中首页是入口,车牌识别是核心,统计分析、车牌地图、车牌管理是数据消费端。这个切法在毕设里算合理,因为每个模块都能独立演示,答辩时不容易被问倒。

真正需要留意的是模块之间的数据流:车牌识别页面负责上传图片并调用 YOLOv5 推理,推理结果写入 MySQL;统计分析页面从数据库读聚合数据,按车型、车牌颜色、时间维度出图;车牌地图页面把车牌归属地映射到中国地图上做数量分布;车牌管理页面提供列表查询和删除。用户管理只做管理员账号的增删改查,不涉及复杂权限。

如果你要复现,建议先把「识别结果表」设计好,因为后面三个页面全依赖它。论文里只给了 user 和 news 两张表,识别结果表没展开,这是原稿的一个缺口。常见做法是补一张plate_record表,字段至少包含:id、plate_number、plate_color、vehicle_type、image_path、recognize_time、confidence、location。这样统计分析可以按plate_color和vehicle_type分组,地图页面可以按location聚合,管理页面直接分页查这张表。

2.2 数据表设计与 MySQL 建表实操

论文给出的 user 表和 news 表结构可以直接用,但字段类型有几个地方需要调整。比如mima用 VARCHAR(50) 存 MD5 值够用,但neirong用 Longtext 在毕设场景下偏重,改成 TEXT 更合适。下面是我按论文结构补全后的建表 SQL,包含 user、news 和补上的 plate_record 三张表:

-- 用户表:管理员登录用,密码存 MD5 CREATE TABLE `user` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `yonghuming` VARCHAR(50) DEFAULT NULL COMMENT '用户名', `mima` VARCHAR(50) DEFAULT NULL COMMENT 'MD5密码', `xingming` VARCHAR(50) DEFAULT NULL COMMENT '姓名', `xingbie` VARCHAR(50) DEFAULT NULL COMMENT '性别', `chushengnianyue` VARCHAR(50) DEFAULT NULL COMMENT '出生年月', `QQ` VARCHAR(50) DEFAULT NULL, `youxiang` VARCHAR(50) DEFAULT NULL COMMENT '邮箱', `dianhua` VARCHAR(50) DEFAULT NULL COMMENT '电话', `neirong` TEXT COMMENT '备注内容', `tianjiaren` VARCHAR(50) DEFAULT NULL COMMENT '添加人', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 识别记录表:统计分析、地图、管理三个页面的数据源 CREATE TABLE `plate_record` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `plate_number` VARCHAR(20) DEFAULT NULL COMMENT '车牌号', `plate_color` VARCHAR(10) DEFAULT NULL COMMENT '蓝牌/绿牌/黄牌', `vehicle_type` VARCHAR(20) DEFAULT NULL COMMENT '车型', `image_path` VARCHAR(255) DEFAULT NULL COMMENT '原图路径', `recognize_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '识别时间', `confidence` FLOAT DEFAULT NULL COMMENT '置信度', `location` VARCHAR(50) DEFAULT NULL COMMENT '归属地', PRIMARY KEY (`id`), KEY `idx_time` (`recognize_time`), KEY `idx_color` (`plate_color`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

建表时注意两个点:一是plate_record上建了recognize_time和plate_color两个索引,因为统计分析页面会频繁按时间和颜色分组,没索引数据量一上来查询就明显变慢;二是字符集统一用 utf8mb4,车牌里可能出现生僻字或特殊字符,utf8 在某些情况下会丢数据。论文里没提索引,这是实际跑起来之后才会暴露的坑。

2.3 登录与安全处理的实现细节

论文提到密码用 MD5 加密,这个在毕设里够用,但要注意 MD5 不加盐等于裸奔。我一般会在 MD5 之前拼一个固定盐值,比如md5(username + password + salt),这样即使数据库被拖库,彩虹表也打不出来。Python 里实现很简单:

import hashlib def hash_password(username, password, salt="lpr_system_2024"): """登录密码哈希:用户名+密码+盐 做 MD5""" raw = f"{username}{password}{salt}" return hashlib.md5(raw.encode("utf-8")).hexdigest() # 注册时存 hash_password,登录时比对 stored = hash_password("admin", "123456") print(stored) # 存进 user.mima 字段

参数说明:salt是固定字符串,实际项目里应该每个用户随机生成并单独存字段,但毕设场景下固定盐已经比裸 MD5 好很多。username参与哈希是为了防止两个用户密码相同导致哈希值一样。登录校验时从数据库取出mima字段,用同样的函数算一遍比对即可。

3. YOLOv5 车牌检测流水线:从图片上传到字符输出

3.1 为什么选 YOLOv5 而不是两阶段检测器

车牌识别本质上是「先检测车牌位置,再识别车牌字符」的两步任务。传统方案用 OpenCV 做边缘检测 + 颜色分割定位车牌,再套 OCR,优点是轻量,缺点是一换光照、一换角度就翻车。论文选 YOLOv5 做检测端,核心原因是它在保持实时性的同时对小目标有不错的召回,而车牌在整张图里恰恰属于小目标。

和 Faster R-CNN 这类两阶段检测器比,YOLOv5 的优势是推理速度快,单张图在普通 GPU 上能压到 10ms 以内,适合视频流场景。和 YOLOv8 比,YOLOv5 的生态更成熟,网上预训练权重和教程多,毕设场景下遇到问题更容易搜到答案。论文里没写具体用的是哪个版本,常见做法是用 YOLOv5s 或 YOLOv5m,s 版本权重文件小、推理快,m 版本精度略高,按你的硬件选。

3.2 数据准备与标注格式转换

YOLOv5 训练需要 YOLO 格式的标注:每张图对应一个 txt 文件,每行是class_id x_center y_center width height,全部归一化到 0-1。如果你手头是 LabelImg 标的 XML(VOC 格式),需要转一道。下面这个脚本是我常用的转换逻辑:

import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_dir, out_dir, classes): """把 VOC XML 标注转成 YOLO txt 格式""" if not os.path.exists(out_dir): os.makedirs(out_dir) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue tree = ET.parse(os.path.join(xml_dir, xml_file)) root = tree.getroot() size = root.find("size") w = int(size.find("width").text) h = int(size.find("height").text) lines = [] for obj in root.iter("object"): cls_name = obj.find("name").text if cls_name not in classes: continue cls_id = classes.index(cls_name) bbox = obj.find("bndbox") x1 = float(bbox.find("xmin").text) y1 = float(bbox.find("ymin").text) x2 = float(bbox.find("xmax").text) y2 = float(bbox.find("ymax").text) # 归一化并转成中心点+宽高 x_c = (x1 + x2) / 2.0 / w y_c = (y1 + y2) / 2.0 / h bw = (x2 - x1) / w bh = (y2 - y1) / h lines.append(f"{cls_id} {x_c:.6f} {y_c:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, xml_file.replace(".xml", ".txt")) with open(out_path, "w") as f: f.write("\n".join(lines)) # 单类别:只检测车牌 voc_to_yolo("./annotations", "./labels", classes=["plate"])

逻辑说明:classes列表决定类别索引,单类车牌就写["plate"]。归一化用图像实际宽高,不能用固定值,否则不同分辨率图片的标注会错位。转换完建议抽查几张,用labelImg或cv2画框验证,确认框位置没偏。常见翻车点是 XML 里size字段缺失或为 0,脚本会除零报错,遇到这种图直接跳过并记录。

3.3 训练配置与超参数调整

YOLOv5 的训练入口是train.py,关键参数就几个:--img输入尺寸、--batch批大小、--epochs训练轮数、--data数据集配置文件、--weights预训练权重。车牌检测建议--img 640,因为车牌在图中占比小,输入太小特征会丢;--batch按显存来,8G 显存跑 640 尺寸大概能上 16;--epochs毕设场景 100-150 轮够用,再多容易过拟合。

数据集配置文件plate.yaml长这样:

# 车牌检测数据集配置 path: ./dataset train: images/train val: images/val nc: 1 names: ["plate"]

nc是类别数,单类车牌写 1。names顺序必须和标注里的 class_id 对应,否则训练出来的模型会把车牌识别成别的类。训练命令:

python train.py --img 640 --batch 16 --epochs 120 --data plate.yaml --weights yolov5s.pt --project runs/train --name plate_exp

跑起来之后重点看mAP@0.5和mAP@0.5:0.95两个指标,前者到 0.9 以上基本可用,后者能到 0.6 以上算不错。如果 loss 一直不降,先检查标注有没有问题,再看学习率是不是太大,YOLOv5 默认用余弦退火,一般不用手动调。

3.4 推理与字符识别串联

检测出车牌框之后,下一步是字符识别。论文里提到字符分割 + 字符识别,但没给具体方案。常见做法有两种:一是用 CRNN + CTC 做端到端识别,二是用 PaddleOCR 直接对裁剪出的车牌图做识别。毕设场景下我推荐第二种,省事且效果好。

import cv2 import torch from paddleocr import PaddleOCR # 加载 YOLOv5 模型和 OCR model = torch.hub.load("ultralytics/yolov5", "custom", path="runs/train/plate_exp/weights/best.pt") ocr = PaddleOCR(use_angle_cls=True, lang="ch") def recognize_plate(img_path): """检测车牌并识别字符""" img = cv2.imread(img_path) results = model(img) plates = results.xyxy[0].cpu().numpy() # [x1,y1,x2,y2,conf,cls] output = [] for x1, y1, x2, y2, conf, cls in plates: if conf < 0.5: continue crop = img[int(y1):int(y2), int(x1):int(x2)] ocr_result = ocr.ocr(crop, cls=True) text = "".join([line[1][0] for line in ocr_result[0]]) if ocr_result[0] else "" output.append({"plate": text, "confidence": float(conf)}) return output print(recognize_plate("test_car.jpg"))

参数说明:conf < 0.5是置信度过滤阈值,低于这个值的框直接丢,避免误检;use_angle_cls=True让 OCR 支持倾斜文字,车牌有角度时有用;lang="ch"指定中文模型,因为车牌含汉字。裁剪时坐标要转 int,否则 OpenCV 切片报错。识别结果写回plate_record表,后面统计和地图页面就能用了。

4. 避坑与排查:那些论文里不会写的翻车现场

4.1 训练 loss 不降反升

现象:训练前几轮 loss 正常下降,到 20 轮左右突然飙升,mAP 掉到接近 0。原因通常是学习率过大导致梯度爆炸,或者标注里有大量错误框。解决:先把--lr0从默认 0.01 降到 0.001 重跑,如果还不行就用脚本抽查标注,重点看有没有框超出图像边界或宽高为 0 的脏数据。

4.2 推理时检测框偏移

现象:训练时 mAP 很高,但推理时框位置明显偏。原因多半是推理输入尺寸和训练不一致,或者图像预处理时做了 resize 但没同步缩放框坐标。解决:推理时统一用--img 640,并且用 YOLOv5 自带的results.xyxy拿框,它已经做了坐标还原,不要自己手动算。

4.3 MySQL 中文乱码

现象:车牌号里的汉字存进数据库变成问号。原因:建表时字符集用了 latin1 或 utf8,连接串没指定编码。解决:建表和数据库统一用 utf8mb4,Python 连接时加charset='utf8mb4',比如pymysql.connect(host, user, password, db, charset='utf8mb4')。

4.4 统计分析页面查询慢

现象:识别记录到几千条后,统计分析页面加载要好几秒。原因:plate_record表没建索引,全表扫描。解决:在recognize_time和plate_color上建索引,如果按车型统计多,再加vehicle_type索引。索引不是越多越好,写多读少的场景要权衡。

4.5 地图页面归属地映射错误

现象:车牌地图上各省数量对不上。原因:车牌归属地是按第一个汉字映射的,但有些车牌是使领馆或特殊号段,第一个字符不是省份简称。解决:在映射逻辑里加白名单,只处理常见省份简称,其余归入「其他」,避免统计口径混乱。

5. 进阶技巧:把识别结果做成可验证的闭环

系统跑通之后,真正决定它能不能写进论文、能不能答辩的,是你能不能证明识别结果可信。我一般会加一个「人工复核」环节:在车牌管理页面每条记录旁边加一个「确认/修正」按钮,人工修正后的数据单独存一个字段verified,然后用修正前后的差异算准确率。这个动作看起来小,但它让你的系统从「能跑」变成「可评估」。

具体实现上,在plate_record表加两个字段:verified TINYINT DEFAULT 0和corrected_number VARCHAR(20)。管理页面查询时如果verified=1就显示corrected_number,否则显示plate_number。统计准确率时:

def calc_accuracy(conn): """计算人工复核后的识别准确率""" cursor = conn.cursor() cursor.execute("SELECT COUNT(*) FROM plate_record WHERE verified = 1") total = cursor.fetchone()[0] cursor.execute("SELECT COUNT(*) FROM plate_record WHERE verified = 1 AND plate_number = corrected_number") correct = cursor.fetchone()[0] return correct / total if total > 0 else 0.0

这个准确率比单纯看 mAP 更有说服力,因为它是端到端的结果,包含了检测和识别两段的误差。答辩时老师问「你这个系统识别率多少」,你直接给这个数,比背 mAP 数值硬气得多。

还有一个技巧是批量测试:准备 200 张不同光照、不同角度的车牌图,写个脚本批量跑推理,把结果和人工标注对比,输出混淆矩阵。这样你能清楚看到系统在哪些场景下弱,比如夜间或倾斜角度大时准确率掉多少,论文里写「局限性分析」就有素材了。

从那以后我每次做完检测类项目,都会强制走一遍「人工复核 + 批量测试」的闭环,不然心里没底。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询