ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

车牌识别系统设计与工程实现:从YOLOv8到ONNX部署

2026/9/18 13:48:33 拓冰建站 浏览量
车牌识别系统设计与工程实现:从YOLOv8到ONNX部署 简介车牌识别系统设计报告PDF是一份面向计算机视觉、模式识别课程设计或智能交通项目学习的完整设计文档。报告系统梳理了车牌识别的五大模块图像预处理、边缘提取、车牌定位、字符分割与字符识别并结合MATLAB编程实现给出灰度校正、平滑处理、Canny/Sobel边缘检测、二值化与连通域分析等关键步骤同时讨论了模板匹配、神经网络等识别算法对系统性能与运行环境要求也有说明。资源为1个PDF文件压缩包总大小约921KB内容以文字、公式和算法流程为主适合需要了解车牌识别整体架构、撰写课程报告或开展相关课题的学生与开发者参考。当前已有48人学习报告从设计目的、原理到各模块实现均有展开可作为方案设计、算法选型和论文写作的参考资料。1. 车牌识别设计报告要解决的核心问题当领导丢给你一份“车牌识别设计报告.pdf”的模板时你真正需要交付的不是那几十页文档而是一套能跑通的代码和一组可信的指标。车牌识别License Plate Recognition, LPR在停车场、卡口、高速收费和园区安防里都是刚需但它的难点不在“识别”二字而在从摄像头到字符串的整条链路光照变化、车牌倾斜、字符缺损、中文字符类别多任何一个环节没处理干净识别率就会掉到不可用。我一般会把设计报告拆成两个部分一是方案选型二是工程实现。方案选型决定你用传统图像处理、深度学习还是两者混合工程实现决定你真上了生产环境后是稳定识别还是频繁出bug。这篇博文就按我自己做这类项目时的推进顺序展开结论和代码都可以直接抄到你的项目和报告里。2. 车牌识别系统架构与数据流车牌识别不是单个模型而是一条流水线。先把整条链路理清楚后面每一步才不会白做。2.1 从图像采集到车牌输出的四个阶段常见做法是把车牌识别拆成四个阶段图像采集与预处理、车牌检测、透视校正与字符分割、字符识别与后处理。图像采集阶段要考虑摄像头的分辨率、帧率、曝光时间和补光灯设计报告里这部分往往被忽略。但实际上一个补光不合理的摄像头拍出的图像会把后续所有算法的精度上限压死。预处理通常是转灰度、高斯模糊、直方图均衡化但深度学习模型对预处理的依赖很小我一般只做归一化和缩放不用做太重的滤波。车牌检测阶段负责从整张图中找到车牌的位置和旋转角度。传统方法用颜色或边缘信息深度学习模型则输出矩形框和置信度。透视校正阶段为了把倾斜的车牌拉正否则字符分割和识别都会出错。字符识别阶段输出最终的车牌字符串比如“京A12345”。2.2 检测与识别的分工先定位再读字符为什么要先检测再识别而不是直接端到端我做过对比端到端模型虽然简单但需要海量的整图-字符串标注数据而且当摄像头安装角度变化时模型需要重新训练。两阶段方案里检测模型只学“车牌长什么样”字符识别模型只学“字符是什么”两个模型的训练数据可以独立扩充。比如新增一种新能源绿色车牌时只需要补充检测模型的训练数据字符识别模型的数据可以复用。方案优点缺点传统图像处理无需训练速度快对光照和背景敏感两阶段深度学习精度高易维护需要两步推理延迟略高端到端深度学习部署简单数据需求大难调试这里的“两阶段”是当前工程上最主流的选择也是我推荐你在设计报告中写明的方案。2.3 基于深度学习的整体流水线具体到代码整个流水线可以串成一个Python函数。下面是一个伪代码级的示意后面章节会逐步替换成可运行版本。import cv2 import numpy as np def plate_recognition_pipeline(frame): # 阶段1: 预处理 img_resized cv2.resize(frame, (640, 640)) # 阶段2: 车牌检测 boxes, scores, classes detect_plate(img_resized) if len(boxes) 0: return None box boxes[0] # 阶段3: 透视校正 plate_rect four_point_transform(frame, box) # 阶段4: 字符识别 text recognize_chars(plate_rect) return textdetect_plate和recognize_chars是后续章节要实现的函数。参数方面输入尺寸为什么是640而不是原始分辨率因为YOLOv8默认输入是640x640而不是原始分辨率你会丢失一部分小目标信息。如果你的摄像头画面里车牌特别小可以把输入提高到1280但推理速度会明显变慢。3. 车牌检测从传统边缘检测到开源模型车牌检测是整个系统的地基。检测框不准确后面的矫正和识别都无从谈起。3.1 传统方法不够用的三个案例我最早用OpenCV的颜色过滤加边缘检测来定位车牌。蓝色车牌在HSV空间里H值在100到124之间配合Canny边缘和轮廓外接矩形确实能刷掉一部分背景。但碰到这三个场景就崩了车身上有蓝色装饰条车牌被泥污遮挡或者傍晚光线把车牌拍成灰蓝色。颜色阈值的参数看起来很好调实际上每个摄像头都要重新标定因为白平衡不同。边缘检测则会把汽车格栅、车窗线条都框进来。所以现在做车牌检测我几乎不用纯传统方法。3.2 开源车牌检测模型与选型对比目前开源社区里可以直接用的车牌检测模型不少。YOLOv8是最容易上手的因为权重格式统一导出ONNX也方便。HyperLPR自带车牌检测和识别但它的检测部分相对老旧在复杂背景上容易漏检。PaddleOCR里的文本检测模型也能用来检测车牌因为车牌本质上是文本区域不过它对单行车牌的支持好对多行新能源车牌要额外处理。开源车牌检测识别模型在选型时我主要看三点是否支持中文车牌类别、模型体积是否适合目标设备、是否有明确的预处理要求。YOLOv8的新能源车牌类别需要自己添加但基座模型的能力足够。3.3 最小可运行示例YOLOv8检测车牌区域下面是用YOLOv8做车牌检测的最小代码。from ultralytics import YOLO # 加载预训练模型 model YOLO(license_plate.pt) results model.predict( sourceparking.jpg, conf0.25, iou0.6, imgsz640, verboseFalse ) for r in results: for box in r.boxes: x1, y1, x2, y2 map(int, box.xyxy[0]) conf float(box.conf[0]) cls int(box.cls[0]) print(fplate: ({x1}, {y1}, {x2}, {y2}), conf{conf:.2f})这里conf是置信度阈值低于0.25的框会被丢弃。如果你发现漏检多把conf降到0.1但误检会增多iou是NMS的IoU阈值0.6意味着两个重叠超过60%的框只保留一个值越大保留的框越多。imgsz是输入尺寸和上一节的640对应。3.4 参数说明conf阈值、NMS、输入尺寸conf阈值需要根据场景细调。停车场的摄像头固定角度稳定conf可以设到0.4以上卡口的抓拍图光线复杂我一般设0.25。NMS阈值通常不动。如果车牌在图中占比小imgsz可以调到960甚至1280但显存占用和推理时间翻倍。训练数据也影响检测鲁棒性。如果你的项目需要识别新能源绿牌建议单独采集绿牌数据做微调。微调时不要只更换最后几层而是对整个检测头做少量epoch的训练否则模型容易过拟合到新类别上。4. 字符分割与识别车牌识别的核心细节检测出车牌区域后下一步是把车牌图像变成可读的字符串。这一步比检测更容易被低估。4.1 为什么需要透视校正车牌在图像中往往是倾斜的。透视校正用四个点在原图和目标图之间的映射关系把车牌拉成正面视角。OpenCV的getPerspectiveTransform需要输入四个点我一般取检测框的四个角再根据车牌的宽高比比如440x140毫米计算目标矩形的四个角。四点变换的系数矩阵import cv2 import numpy as np def four_point_transform(image, pts): # pts: 检测框的四个角顺序需要是左上、右上、右下、左下 rect np.array(pts, dtypenp.float32) # 计算宽度右上角x减去左上角x右下角x减去左下角x取最大值 width_top np.linalg.norm(rect[1] - rect[0]) width_bottom np.linalg.norm(rect[2] - rect[3]) max_width max(int(width_top), int(width_bottom)) # 同理计算高度 height_left np.linalg.norm(rect[3] - rect[0]) height_right np.linalg.norm(rect[2] - rect[1]) max_height max(int(height_left), int(height_right)) dst np.array([ [0, 0], [max_width - 1, 0], [max_width - 1, max_height - 1], [0, max_height - 1] ], dtypenp.float32) M cv2.getPerspectiveTransform(rect, dst) warped cv2.warpPerspective(image, M, (max_width, max_height)) return warpedpts的四个点如果顺序错乱矫正后的图像就会翻转。YOLO输出的默认是左上、右上、右下、左下需要你自己确认你的模型输出顺序。这里有个坑如果检测框不是水平矩形而是旋转矩形你需要额外回归旋转角度或者用文本检测模型输出的四边形四点。我建议用四边形回归因为车牌在路面上多数是水平旋转竖直方向的倾斜不太夸张。4.2 字符分割的两种做法传统做法是投影法把矫正后的车牌图像转灰度二值化然后按列求和得到垂直投影字符之间的投影值接近零用这个间隙分割。但二值化的阈值在光照不均时很难选我常用自适应阈值import cv2 import numpy as np def segment_chars(plate_img): gray cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY) # 自适应阈值blockSize必须是奇数C是常数偏差 binary cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 31, 15 ) # 去掉小的噪点 binary cv2.medianBlur(binary, 3) # 垂直投影 h, w binary.shape col_sum np.sum(binary 255, axis0) # 找到连续非零列区间 chars [] in_char False start 0 for i in range(w): 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 i - start 10: chars.append(binary[:, start:i]) return chars自适应阈值的blockSize和C是关键参数。blockSize太小内部纹理被当成字符太大光照阴影被当成字符。这里31和15是针对300x100左右的车牌图如果你的车牌图分辨率不同要按比例调整。另一种做法是不分割直接用检测模型识别整块车牌区域里的文本。PaddleOCR的PP-OCRv3文本识别模型可以把整行字符一次性输出省去分割步骤但中文车牌字符少且结构特殊直接用通用OCR有时会把“京”识别成“京”旁边的噪声。4.3 基于CNN的字符识别模型如果你打算自己训练字符识别模型最简单的是把每个分割后的字符缩放到统一的尺寸比如32x64然后用一个3到4层卷积的小网络做分类。类别数分两类汉字31个各省简称字母和数字34个I和O通常不用再加一个无字符类共66类。训练时需要注意的是汉字和数字在车牌上其实是同一个模型不需要分开但汉字类别少需要做数据增强。4.4 用PaddleOCR做整牌识别的替代方案PaddleOCR提供了文本检测和识别模型可以直接对车牌区域做识别。调用方式from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch) result ocr.ocr(warped_plate, clsTrue) text result[0][0][1][0]use_angle_cls是方向分类用于检测旋转180度的文本。车牌本身已经矫正过方向分类可以关闭能省一点延迟。langch是中文模型也可以换成车牌专用的微调模型。PaddleOCR的优点是预训练模型覆盖全缺点是模型文件大部署时需要考虑磁盘占用。5. 导出ONNX并在Java中部署设计报告写到部署部分时很多人会直接写“提供HTTP服务”但实际落地中Java后端才是停车场系统的主流。5.1 为什么选择ONNX Runtime如果你只在Python环境跑PyTorch直接serve就行。但在企业项目里摄像头抓拍服务往往跑在Java写的Spring Boot服务里把Python模型包成独立服务又要考虑进程管理和延迟。ONNX Runtime提供了跨语言推理能力Java可以直接加载ONNX模型不需要Python环境推理速度和PyTorch差不多。5.2 从PyTorch导出ONNX的步骤导出时最需要注意的是输入输出的维度和动态轴。我以YOLOv8的检测模型为例import torch from ultralytics import YOLO model YOLO(license_plate.pt) # 拿到底层PyTorch模型 net model.model net.eval() # 输入尺寸: batch1, channels3, height640, width640 dummy_input torch.zeros(1, 3, 640, 640) torch.onnx.export( net, dummy_input, license_plate.onnx, input_names[images], output_names[output0], dynamic_axes{images: {0: batch}, output0: {0: batch}} )dynamic_axes把batch维设为动态这样同一份ONNX可以处理单张和多张输入。输出层YOLOv8的输出维度是(batch, 84, 8400)其中84是4个框坐标加80个类别概率。如果你的模型是自定义的类别数这个数字要对应改变。5.3 Java端推理代码与参数设置Java端使用onnxruntime开源库。以下是采用maven依赖后推理的骨架import ai.onnxruntime.*; import org.opencv.core.*; public class PlateDetector { private OrtSession session; public PlateDetector(String modelPath) throws OrtException { OrtEnvironment env OrtEnvironment.getEnvironment(); session env.createSession(modelPath, new OrtSession.SessionOptions()); } public float[][] detect(float[] imageData, int height, int width) throws OrtException { // imageData 是 1*3*640*640 的 float 数组 long[] inputShape {1, 3, 640, 640}; OnnxTensor inputTensor OnnxTensor.createTensor( OrtEnvironment.getEnvironment(), FloatBuffer.wrap(imageData), inputShape); OrtSession.Result result session.run( Map.of(images, inputTensor)); // 取输出的 float 数组 float[][] output (float[][]) result.get(0).getValue(); return output; } }注意ONNX Runtime的Java API中getValue返回类型对三维以上输出可能是float[][][]需要仔细处理。我在实际代码里会先打印输出维度而不是直接假设形状。另外输入图像数据需要转换为CHW格式并且归一化到0到1之间。如果直接用OpenCV的Mat需要做一次转换Mat normalized new Mat(); img.convertTo(normalized, CvType.CV_32FC3, 1.0 / 255.0); // 然后按通道拆分并排列成CHW5.4 与Matlab方案的对比很多教科书和旧报告里车牌识别用Matlab实现。Matlab在图像处理上的函数丰富适合快速验证算法但它的运行时是商业授权Java集成复杂而且深度学习模型部署需要MATLAB Compiler SDK成本高。如果你的设计报告要拿去答辩或验收我会建议把Matlab版本作为算法原型最终交付用Python训练ONNX部署的版本这样团队里的Java工程师也能维护。Java onnx 车牌识别这种组合在推理线程管理上还需要注意ONNX Runtime的session不是线程安全的但你可以创建多个session实例或者用线程池串行化推理。我一般用一个阻塞队列加固定大小线程池控制在4到8个并发防止摄像头抓拍高峰期CPU打满。6. 性能验证与常见坑设计报告如果没有量化指标说服力就大打折扣。我一般用两个指标检测IoU和字符准确率。6.1 用IoU和字符准确率做评估IoU是检测框和人工标注框的交并比高于0.5就算检测正确。字符准确率是识别结果和车牌真值的逐字符比对比如预测“京A12345”真值是“京A12345”则准确率100%。还要记录不同场景下的识别率比如白天、夜晚、雨雾、倾斜。6.2 三个高频排错点第一个坑是检测框比车牌大很多导致矫正后车牌周围有大片背景字符识别把背景噪声当成字符。解决方法是检测模型的anchor大小要与你的摄像头分辨率匹配。第二个坑是夜间补光灯把车牌过曝字符发白识别率骤降。传统算法的二值化阈值无法自适应深度学习模型在训练时没有加入过曝样本。我常用的办法是采集夜间样本加入训练而不是调图像增强。第三个坑是新能源车牌绿牌字符数多传统投影分割会失败。绿牌的字符间距和蓝牌不一样建议用检测模型直接输出字符位置或者用PaddleOCR这类文本识别模型。最后验证时不要在训练集上挑图要专门留出一批没参与训练的真实抓拍图。简单记录一下conf阈值调整前后同一张图的变化就能判断是检测问题还是识别问题。写设计报告时把这几条写进“测试与调优”一节比贴上十页网络结构图更有说服力。本文还有配套的精品资源点击获取