简介这份资源是面向高校学生与深度学习初学者的车牌识别系统完整项目包适用于毕业设计、课程设计及期末大作业等场景帮助读者将卷积神经网络与OCR技术落地到真实图像识别任务中。包内共1177个文件以Python脚本、Markdown文档、JPG示例图、TXT说明、YAML配置为主另含少量C、Rust、Jupyter笔记本及模型权重文件压缩包约39.43MB覆盖数据准备、模型训练、验证评估与预测推理全流程。系统以yolov11n完成车牌区域定位再借助paddleocr实现文字识别训练、验证与预测脚本分工明确示例图片可辅助理解运行流程。目前已有85人学习下载读者可据此掌握从数据预处理到模型部署的完整开发链路积累目标检测与OCR结合的工程经验并作为进一步优化识别精度与响应速度的实验平台。1. 车牌识别系统选型为什么是 yolov11n 加 paddleocr 这套组合车牌识别这件事单靠一个模型很难端到端搞定。真实场景里一张卡口图里车牌可能只占几十个像素还伴随逆光、雨雾、倾斜、遮挡直接上 OCR 大模型去读整图准确率和速度都撑不住。所以工程上更稳的做法是拆成两段先用目标检测把车牌框出来再对裁剪出来的小图做文字识别。这套「检测 识别」的流水线就是 yolov11n_paddleocr 车牌识别系统的核心思路。yolov11n 是 YOLO 系列里偏轻量的 n 版本参数量小、推理快适合部署在边缘设备或者单卡服务器上跑实时视频流paddleocr 负责把检测框里的车牌字符读出来它对中文车牌、双层黄牌、新能源绿牌的字符集支持比较完整而且自带方向分类器能处理 180 度倒置的车牌。两者拼起来一个管「在哪」一个管「是什么」职责清晰替换和调优都方便。这套方案适合谁做智慧停车、园区卡口、交通违章抓拍、车辆出入管理的团队尤其是预算有限、要在 Jetson 或普通 GPU 上跑实时推理的场景。如果你手上只有几百到几千张标注图又不想从零训一个端到端大模型这条路线落地周期短、可控性强。下面按数据准备、检测训练、识别对接、避坑、进阶的顺序把每一步的参数和坑讲清楚。2. 数据准备与标注让 yolov11n 学得动的车牌数据集怎么造2.1 车牌检测数据集的组织格式yolov11n 用的是 YOLO 格式标注每张图对应一个同名 txt每行是class x_center y_center width height坐标都归一化到 0 到 1。车牌检测通常只有一个类别class 写 0 就行。目录结构按 ultralytics 的约定来dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml里写清楚路径和类别名path: /home/user/dataset train: images/train val: images/val nc: 1 names: [plate]这里nc是类别数车牌检测只检「车牌」这一类所以是 1。names的顺序必须和标注里的 class 编号一致写反了模型会把车牌当背景学。路径建议用绝对路径相对路径在不同工作目录下跑训练容易翻车。2.2 标注的四个实操要点标注工具用 labelImg 或 X-AnyLabeling 都行导出 YOLO 格式。标注时注意几点框要贴紧车牌四角不要留太多背景否则 OCR 阶段裁出来的图会带干扰双层车牌黄牌大车要把上下两层整体框住不要只框一层倾斜车牌按最小外接矩形框别硬拉成水平框YOLO 的框是轴对齐的倾斜太厉害时框会包含大量背景这种情况要么增加倾斜样本要么在预处理阶段做矫正。数据量上单类别车牌检测train 集 2000 到 5000 张基本能出一个可用的模型val 集留 10% 到 15%。如果场景固定比如就一个停车场出口500 张左右也能收敛但泛化会差。样本要覆盖白天、夜间、逆光、雨雾、不同角度尤其是夜间和逆光这两类不专门补样本模型上线后大概率在这些场景集体翻车。2.3 用脚本检查标注是否合法标注完别急着训先跑个校验脚本把越界坐标、空标注、图片和标签不匹配的问题揪出来import os from pathlib import Path img_dir Path(dataset/images/train) lbl_dir Path(dataset/labels/train) for lbl in lbl_dir.glob(*.txt): img img_dir / (lbl.stem .jpg) if not img.exists(): print(f缺图: {lbl.name}) continue with open(lbl) as f: for i, line in enumerate(f): parts line.strip().split() if len(parts) ! 5: print(f{lbl.name} 第{i}行字段数不对: {line}) continue cls, x, y, w, h parts vals list(map(float, [x, y, w, h])) if any(v 0 or v 1 for v in vals): print(f{lbl.name} 第{i}行坐标越界: {line}) if float(w) 0 or float(h) 0: print(f{lbl.name} 第{i}行宽高非正: {line})这段脚本做三件事检查每张标签有没有对应图片、每行是不是 5 个字段、归一化坐标是否在 0 到 1 之间且宽高为正。越界坐标在训练时会被 ultralytics 静默裁剪导致框变形模型学到的位置就偏了所以必须在训练前清掉。宽高为 0 的框会让 loss 计算出 NaN训练直接崩。3. 训练 yolov11n 检测模型参数怎么设、指标怎么看3.1 从预训练权重起步的最小训练命令不要从随机初始化训用 COCO 预训练的 yolov11n 权重做迁移学习收敛快很多。ultralytics 的命令行方式最省事yolo detect train \ modelyolo11n.pt \ datadataset/data.yaml \ epochs100 \ imgsz640 \ batch16 \ lr00.01 \ patience20 \ device0 \ projectruns/plate \ nameyolo11n_plate逐项说下参数。modelyolo11n.pt是加载预训练权重第一次跑会自动下载。imgsz640是输入分辨率车牌在整图里偏小的话可以提到 960 或 1280但显存和速度会涨。batch16按显存调8G 显存跑 640 大概能到 16跑 1280 就得降到 4 或 8。lr00.01是初始学习率迁移学习常用 0.01如果 loss 震荡厉害降到 0.001。patience20是早停20 轮指标不涨就停省时间。device0指定第一块 GPUCPU 训练把 device 设成 cpu但速度会慢到没法接受。3.2 训练过程要盯的三个指标训练日志里重点看box_loss、cls_loss和验证集的mAP50。box_loss 管框的位置回归cls_loss 管分类车牌单类别时 cls_loss 会降得很快。mAP50 是 IoU 阈值 0.5 下的平均精度车牌检测一般能到 0.95 以上算不错。如果 mAP50 卡在 0.7 左右上不去先查标注质量再看是不是小目标太多——车牌在 640 分辨率下小于 16 像素的话模型很难学好这时候要么提高 imgsz要么在数据增强里加 mosaic 和 copy_paste。验证时用命令行跑一批图看效果yolo detect predict \ modelruns/plate/yolo11n_plate/weights/best.pt \ sourcetest_images/ \ conf0.25 \ saveTrueconf0.25是置信度阈值低于这个值的框不输出。车牌检测可以适当调低到 0.2宁可多检几个框让 OCR 去过滤也别漏检。saveTrue把画了框的结果存下来肉眼过一遍重点看夜间和逆光样本有没有漏。3.3 导出推理格式训练完的 best.pt 是 PyTorch 权重部署时按目标平台导出。GPU 服务器上跑 TensorRT 最快边缘设备用 ONNX 或 OpenVINOyolo export modelbest.pt formatonnx opset12 simplifyTrue yolo export modelbest.pt formatengine halfTrue device0opset12是 ONNX 算子集版本兼容性比较好。simplifyTrue会做图优化去掉冗余算子。TensorRT 导出加halfTrue用 FP16速度能再提一截精度掉得很少。注意导出 engine 时的 GPU 型号要和部署机器一致换卡要重新导出这个坑很多人踩过。4. 对接 paddleocr从检测框到车牌字符串的完整链路4.1 裁剪、矫正、识别的三步流程yolov11n 输出的是车牌框坐标paddleocr 吃的是裁剪后的小图。中间要做裁剪和可选的透视矫正。先装依赖pip install paddlepaddle-gpu paddleocr opencv-python如果只用 CPU 推理装paddlepaddle就行。然后写识别主流程import cv2 from paddleocr import PaddleOCR from ultralytics import YOLO det_model YOLO(best.pt) ocr PaddleOCR(use_angle_clsTrue, langch, show_logFalse) def recognize_plate(img_path): img cv2.imread(img_path) results det_model(img, conf0.25, verboseFalse) plates [] for box in results[0].boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 map(int, box) crop img[y1:y2, x1:x2] if crop.size 0: continue ocr_res ocr.ocr(crop, clsTrue) if not ocr_res or not ocr_res[0]: continue text .join([line[1][0] for line in ocr_res[0]]) score min([line[1][1] for line in ocr_res[0]]) plates.append({text: text, score: score, box: [x1, y1, x2, y2]}) return plates逻辑上分三步YOLO 出框、按框裁图、裁图送 OCR。use_angle_clsTrue开启方向分类处理倒置车牌。langch用中文模型能识别汉字省份简称。ocr.ocr返回的结构是「行」的列表每行含文本和置信度车牌一般只有一行多行的情况双层牌用 join 拼起来。score取所有行里最低的置信度作为整块车牌的可信度低于阈值就丢弃。4.2 透视矫正倾斜车牌的后悔药YOLO 的框是轴对齐的车牌倾斜时框里会混入大量背景OCR 容易读错。如果检测阶段能拿到四个角点可以做透视变换把车牌拉正。ultralytics 默认不输出角点需要自己加一个关键点分支或者用传统方法找轮廓。简单场景下用最小外接矩形旋转矫正也能救一部分import numpy as np def deskew(crop): gray cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) coords np.column_stack(np.where(thresh 0)) if len(coords) 10: return crop angle cv2.minAreaRect(coords)[-1] if angle -45: angle 90 angle h, w crop.shape[:2] M cv2.getRotationMatrix2D((w // 2, h // 2), angle, 1.0) return cv2.warpAffine(crop, M, (w, h), flagscv2.INTER_CUBIC, borderModecv2.BORDER_REPLICATE)这段用 Otsu 二值化后找前景点算最小外接矩形的角度再旋转回正。borderModecv2.BORDER_REPLICATE用边缘像素填充旋转后的空白避免出现黑边干扰 OCR。注意这个方法对背景复杂的图会失效前景点里混入背景时角度算不准所以只适合车牌区域比较干净的裁剪图。4.3 识别结果的清洗规则OCR 出来的字符串不能直接用车牌有固定格式得做规则校验。普通蓝牌是「省份简称 字母 5 位字母数字」新能源绿牌是 8 位。写个简单的校验import re PROVINCES 京津冀晋蒙辽吉黑沪苏浙皖闽赣鲁豫鄂湘粤桂琼渝川贵云藏陕甘青宁新 def clean_plate(text): text re.sub(r[^A-Z0-9\u4e00-\u9fa5], , text.upper()) if len(text) 7: return None if text[0] not in PROVINCES: return None if not re.match(r^[A-Z][A-Z0-9]{5,6}$, text[1:]): return None return text先去掉非字母数字汉字的字符再检查首位是不是合法省份简称后面是不是字母开头加 5 到 6 位。\u4e00-\u9fa5是汉字 Unicode 范围。这个规则能过滤掉大部分 OCR 误读比如把「京」读成「凉」的情况。规则不是万能的新能源牌和使馆牌格式特殊按实际业务调整。5. 避坑与排查车牌识别上线后最常见的五个问题5.1 夜间车牌漏检严重现象是白天检测正常夜间 mAP 掉到 0.5 以下大量车牌框不出来。原因是训练集里夜间样本太少模型没见过低照度下的车牌纹理。解决分两步一是补夜间样本至少占训练集 20%二是在预处理阶段做自适应直方图均衡提升暗部对比度。用 OpenCV 的 CLAHEclahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) lab cv2.cvtColor(img, cv2.COLOR_BGR2LAB) lab[:, :, 0] clahe.apply(lab[:, :, 0]) img cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)在 LAB 空间的 L 通道做均衡不影响颜色。clipLimit2.0控制对比度增强幅度太大噪声会被放大车牌边缘反而糊。5.2 OCR 把相似字符读混现象是「0」和「O」、「1」和「I」、「8」和「B」经常读错。原因是车牌字体和 OCR 训练用的通用字体有差异加上裁剪图分辨率低。解决办法一是把裁剪图放大到 OCR 推荐的输入尺寸一般高度 32 或 48 像素二是用规则后处理强制替换。车牌里字母 I 和 O 基本不出现避免和 1、0 混淆可以在清洗时把 I 换成 1、O 换成 0text text.replace(I, 1).replace(O, 0)这个替换要在省份简称校验之后做避免把汉字误伤。5.3 检测框偏大导致 OCR 读入背景现象是车牌能检出但 OCR 结果里混入广告字、车身贴纸。原因是标注时框留了太多背景或者模型回归不准。解决训练时把框贴紧车牌推理后对框做内缩按框宽高的 5% 到 10% 往里收pad_x int((x2 - x1) * 0.05) pad_y int((y2 - y1) * 0.05) crop img[y1 pad_y:y2 - pad_y, x1 pad_x:x2 - pad_x]内缩能去掉框边缘的背景但缩太多会切掉车牌字符5% 到 10% 是经验值按实际效果调。5.4 推理速度达不到实时现象是单帧处理超过 100ms视频流卡顿。排查顺序先看 YOLO 推理耗时再看 OCR 耗时。YOLO 用 TensorRT FP16 一般能到 5ms 以内OCR 是瓶颈paddleocr 的检测加识别在 GPU 上大概 20 到 40ms。优化手段OCR 只跑识别不跑检测因为车牌已经裁好了关掉不需要的模块把 OCR 的det设为 False直接用识别模型ocr PaddleOCR(use_angle_clsTrue, langch, detFalse, recTrue)detFalse跳过 OCR 自带的文本检测省一大半时间。另外批处理也能提吞吐把多块车牌拼成一个 batch 送 OCR。5.5 换部署机器后精度暴跌现象是开发机上 mAP 0.95换到另一台机器掉到 0.6。原因是导出的 TensorRT engine 绑定了特定 GPU 架构和 TensorRT 版本换卡后不兼容或者 FP16 在旧卡上精度损失大。解决部署机器上重新导出 engine别跨机器拷贝旧卡算力低于 7.0用 FP32 导出别开 half。ONNX 格式跨平台兼容性好实在搞不定就用 ONNX Runtime 跑。6. 进阶技巧用关键点回归把车牌矫正做到端到端前面用最小外接矩形矫正倾斜车牌背景复杂时会失效。更稳的做法是让 yolov11n 在检测的同时回归车牌的四个角点拿到角点后直接做透视变换一步到位。ultralytics 支持关键点任务把数据集标注从框扩展成框加四个角点格式是class x y w h px1 py1 px2 py2 px3 py3 px4 py4角点也归一化。训练时把任务设成 poseyolo pose train modelyolo11n-pose.pt dataplate_pose.yaml epochs100 imgsz640推理时拿到角点用cv2.getPerspectiveTransform把车牌拉成标准矩形def perspective_crop(img, pts): pts np.array(pts, dtypenp.float32).reshape(4, 2) rect np.zeros((4, 2), dtypenp.float32) s pts.sum(axis1) rect[0] pts[np.argmin(s)] rect[2] pts[np.argmax(s)] d np.diff(pts, axis1) rect[1] pts[np.argmin(d)] rect[3] pts[np.argmax(d)] w int(max(np.linalg.norm(rect[0] - rect[1]), np.linalg.norm(rect[2] - rect[3]))) h int(max(np.linalg.norm(rect[0] - rect[3]), np.linalg.norm(rect[1] - rect[2]))) dst np.array([[0, 0], [w - 1, 0], [w - 1, h - 1], [0, h - 1]], dtypenp.float32) M cv2.getPerspectiveTransform(rect, dst) return cv2.warpPerspective(img, M, (w, h))角点排序的逻辑是左上角坐标和最小右下角和最大右上角坐标差最小左下角差最大。透视变换后车牌变成水平矩形OCR 准确率能明显提升尤其是大角度倾斜的场景。代价是标注成本高每张图要标四个角点数据量需求也更大几千张起步。如果业务场景里车牌基本都是正对摄像头用前面的旋转矫正就够了不必上关键点。验证整套系统时我习惯准备一个包含 200 张图的回归测试集覆盖白天、夜间、逆光、倾斜、双层牌每次改完模型或参数都跑一遍记录端到端准确率检测框 IoU 大于 0.5 且 OCR 字符串完全正确。这个数字比单看 mAP 更能反映上线效果。踩过的坑里最亏的一次是没做回归测试就换了 OCR 版本结果新能源牌识别率掉了 15%上线后第二天才发现。现在我改任何一环都先跑回归集宁可多花十分钟也不赌线上不出事。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?