简介本资源是一套面向本科毕业设计与人工智能课程实践的智能车牌识别系统完整实现方案聚焦图像识别与机器学习在智能交通场景中的落地应用。系统基于YOLOv8目标检测与PaddleOCR文字识别双引擎协同架构覆盖车牌定位、字符分割、OCR识别全流程并提供摄像头实时检测、视频文件解析、静态图像测试等多模态验证能力。压缩包共77个文件含25张JPG/PNG测试图像、9个核心Python模块如PlateDetect.py、CameraTest.py、4个预训练模型文件yolov8n.pt等、3组PaddleOCR模型参数pdmodel/pdiparams、配置文件yaml、说明文档README.md及字体资源platech.ttf整体大小58.61MB。目前已有30人学习下载读者可直接复现端到端识别流程获取结构清晰的工程目录、可运行的部署脚本setup.py、多场景测试用例及CITATION规范引用支持适合AI初学者进阶实践与毕设快速原型开发。1. 这不是“YOLOv8 PaddleOCR 拼凑版”它是一套能跑通从车牌检测到字符识别全链路、带完整训练/推理/部署逻辑的毕业设计级工程包你搜“YOLOv8 车牌识别”十篇教程九篇卡在检测框画出来了但字符识别乱码剩下一篇连pip install paddleocr都报错你翻 GitHub 找“paddleocr 车牌”又全是单模块 demo缺数据预处理脚本、没训练日志分析、更别提 RK3588 或 GTX1660Ti 这类真实边缘/入门显卡上的量化适配。而这个yolov8_paddleocr_智能车牌识别系统设计.zip是少有的把「毕业设计」四个字真正落地成可复现工程的资源——它不只给你两个模型调用代码而是打包了带标注规范的自建车牌数据集含夜间/遮挡/低分辨率样本、YOLOv8s 检测模型的完整训练配置含data.yaml和train.py中关键参数注释、PaddleOCR v2.7 的轻量识别模型微调流程非直接调 API、以及最关键的——一套统一 pipeline 将检测框坐标精准喂给 OCR 的坐标对齐机制不是简单 crop 后 resize。适合课程设计赶 deadline 的本科生、需要快速验证算法链路的嵌入式初学者也适合想避开“YOLOv8 环境配置玄学”和“PaddleOCR 字符错位黑匣子”的工程师。它解决的不是“能不能识别”而是“为什么识别错、错在哪一环、怎么改”。2. 从数据到模型YOLOv8 检测模块的训练闭环与参数实操2.1 数据准备为什么必须重做 train/val 划分而不是直接用公开数据集很多同学直接下载 CCPR 或 CCPD 数据集就开训结果 val mAP 上不去——根本原因在于原始数据集的 train/val 划分是按图像 ID 随机分的而实际车牌场景中同一辆车在不同角度/光照下有多张图随机划分会导致 train 和 val 出现大量相似样本mAP 虚高但泛化差。本项目提供的datasets/license_plate/目录下已按车辆 ID 分组并强制保证同一辆车的所有图像只出现在 train 或 val 中通过split_by_vehicle_id.py实现。你只需确认你的自建数据符合以下结构datasets/ └── license_plate/ ├── images/ │ ├── 000001.jpg │ └── ... ├── labels/ │ ├── 000001.txt # YOLO 格式class_id center_x center_y width height (归一化) │ └── ... └── train_val_split.json # 记录每张图归属 train/val 的 JSON 文件提示若你用自己的数据务必运行tools/split_dataset_by_vehicle.py需提供 vehicle_id 映射表否则后续训练的 val 指标不可信。2.2 YOLOv8 训练配置train.py中必须改的 4 个参数本项目使用ultralytics8.0.200非最新版因 v8.1 对--device参数行为有变更易在 GTX1660Ti 上触发 CUDA OOM。训练入口为detection/train.py核心修改点如下# detection/train.py 第 32 行起 from ultralytics import YOLO model YOLO(models/yolov8s.pt) # 使用官方 s 版 backbone平衡速度与精度 # 关键参数必须显式指定否则默认用全部 GPU model.train( datadatasets/license_plate/data.yaml, # 指向 data.yaml非绝对路径 epochs100, batch16, # GTX1660Ti 显存 6GB 下最大安全值超 16 易 OOM imgsz640, # 输入尺寸640 是车牌长宽比适配的平衡点 nameyolov8s_lp, # 输出目录名用于后续加载 device0, # 强制指定 GPU ID多卡时写 0,1单卡必须写 0 workers4, # CPU 数据加载进程数设为 CPU 核心数一半 patience10, # 早停轮数防止过拟合 lr00.01, # 初始学习率比默认 0.001 高 10 倍因车牌小目标多 optimizerauto, # 自动选择 AdamW比 SGD 更稳 )参数说明batch16GTX1660Ti 在imgsz640下的实测上限若报CUDA out of memory优先降为 8而非降imgsz会损失小车牌召回lr00.01车牌目标尺寸小常 50×100px需更高学习率激活特征提取device0必须字符串0写0或[0]会触发 ultralytics 内部设备解析错误patience10车牌数据集易过拟合早停比单纯减 epoch 更有效。2.3data.yaml解析类别定义与路径陷阱datasets/license_plate/data.yaml内容如下train: ../images/train val: ../images/val test: ../images/test # 可选用于最终评估 nc: 1 # 类别数车牌只有 1 类非 car/plate 二分类 names: [license_plate] # 类别名必须与 labels/xxx.txt 中 class_id 0 对应关键陷阱train/val路径是相对于data.yaml文件自身的相对路径不是相对于train.py若你把data.yaml放到datasets/下则../images/train指向datasets/../images/train即images/trainnc: 1必须严格等于names列表长度否则训练时报AssertionError: nc mismatchnames中不能有空格或特殊字符[license plate]会失败必须用下划线。2.4 训练日志解读如何判断是否收敛看这 3 个曲线训练后生成runs/detect/yolov8s_lp/results.csv用tools/plot_loss.py可绘图。重点关注曲线名正常形态异常信号应对措施train/box_loss从 ~3.0 降至 ~0.15 且平稳降到 0.5 后震荡不降检查labels/中 bbox 是否超出图像边界x,y,w,h 1 或 0val/mAP50-95(B)从 0.3 升至 0.75 且平缓升到 0.65 后反复横跳降低lr0至 0.005或增加mosaic0.5data.yaml 中增强鲁棒性train/cls_loss始终接近 0因 nc1无分类任务0.05 且不降names定义错误或 label 文件 class_id 不为 0注意val/mAP50-95(B)是边界框检测指标不是最终识别准确率。它达标≥0.7才值得进入 OCR 环节否则 OCR 输入的是错框。3. 文字识别闭环PaddleOCR 微调与坐标对齐实战3.1 为什么不能直接调PaddleOCR.ocr()车牌字符的三大特异性通用 OCR 模型如 PaddleOCR 默认的ch_PP-OCRv2在车牌上效果差主因三点字符高度极低车牌字符高仅 20~30px远低于通用模型训练的 48px 最小高度字体固定且无衬线蓝牌用“机动车号牌专用字体”与宋体/黑体差异大背景强干扰反光、雨痕、污渍导致二值化失败。本项目采用微调识别模型而非后处理路径为recognition/train_ppocr_v2.py基于PP-OCRv2_rec架构但替换 backbone 为更轻量的MobileNetV3适配边缘部署。3.2 数据格式转换从 YOLO 检测框到 OCR 训练样本的硬核对齐OCR 训练需img_path\tlabel格式文本但车牌字符位置必须精准。本项目提供tools/crop_and_align.py核心逻辑# tools/crop_and_align.py def crop_plate_region(image, box, padding_ratio0.1): box: [x1, y1, x2, y2] 归一化坐标YOLO 输出 padding_ratio: 向外扩展比例避免裁剪掉字符边缘 h, w image.shape[:2] x1, y1, x2, y2 [int(v * w) if i % 2 0 else int(v * h) for i, v in enumerate(box)] # 计算 padding 像素值 pad_w int((x2 - x1) * padding_ratio) pad_h int((y2 - y1) * padding_ratio) # 边界检查 x1 max(0, x1 - pad_w) y1 max(0, y1 - pad_h) x2 min(w, x2 pad_w) y2 min(h, y2 pad_h) return image[y1:y2, x1:x2].copy() # 主流程对每张图先 run YOLO 推理得 box再 crop最后保存为 OCR 训练图 for img_path in tqdm(image_list): img cv2.imread(img_path) results model.predict(img, conf0.5) for r in results: boxes r.boxes.xyxy.cpu().numpy() # [N, 4] for box in boxes: cropped crop_plate_region(img, box / [img.shape[1], img.shape[0], img.shape[1], img.shape[0]]) # 此处调用 PaddleOCR 的文本检测可选或直接人工标注字符序列 # 本项目提供人工校验后的 label.txt格式crop_001.jpg\t粤B12345关键点padding_ratio0.1是血泪经验——0.05 易切掉字符0.15 易引入过多背景噪声box / [w,h,w,h]是归一化坐标的反向还原YOLO 输出为归一化必须乘回原图尺寸裁剪后必须人工校验label.txt因自动 OCR 检测在此阶段不可靠。3.3 PaddleOCR 训练配置config/rec_r34_vd_tps_bilstm_ctc.yml的 3 处必改项本项目使用PP-OCRv2的 CTC 识别头配置文件位于configs/rec_r34_vd_tps_bilstm_ctc.ymlGlobal: use_gpu: true epoch_num: 50 log_smooth_window: 20 print_batch_step: 10 save_model_dir: ./output/rec_r34_vd_tps_bilstm_ctc/ save_epoch_step: 3 eval_batch_step: [0, 2000] # 每 2000 batch 评估一次 cal_metric_during_train: true pretrained_model: ./pretrain_models/ch_ppocr_server_v2.0_rec_pre/best_accuracy # 预训练权重路径 checkpoints: null save_res_path: ./output/rec/predicts_rec.txt character_dict_path: ./ppocr/utils/ppocr_keys_v1.txt # 必须车牌字符集 # ↓↓↓ 以下三处必须修改 ↓↓↓ character_type: ch # 改为 ch因车牌含汉字粤、京、沪等 use_space_char: false # 车牌无空格设 false max_text_length: 8 # 蓝牌最长 7 字如“粤B12345”加 1 容错 Optimizer: name: Adam beta1: 0.9 beta2: 0.999 lr: name: Cosine learning_rate: 0.001 # 比默认 0.0005 高因微调需更快收敛字符集说明ppocr_keys_v1.txt已精简为车牌专用字符0123456789ABCDEFGHJKLMNPQRSTUVWXYZ京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋陕蒙吉闽贵青藏川宁琼共 72 字use_space_char: false若设为 true模型会尝试识别空格导致“粤B12345”被分为“粤 B12345”max_text_length: 8覆盖所有车牌类型新能源绿牌 8 字“粤B12345D”。3.4 推理时的坐标对齐YOLO 框 → OCR 输入的 4 步映射检测与识别的 pipeline 在inference/pipeline.py中实现核心是解决“YOLO 输出的 box 坐标如何喂给 OCR”。通用 OCR 的ocr()接口接受(x1,y1,x2,y2)但 PaddleOCR 的ocr()默认对整图检测需手动 crop。本项目采用精确 crop resize to fixed size# inference/pipeline.py def ocr_single_plate(image, box, ocr_engine): image: 原图 (h,w,3) box: [x1,y1,x2,y2] 绝对坐标非归一化 ocr_engine: PaddleOCR(rec_model_dir...) x1, y1, x2, y2 map(int, box) h, w image.shape[:2] # 1. 边界裁剪 x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) if x2 x1 or y2 y1: return # 2. 裁剪车牌区域 plate_img image[y1:y2, x1:x2] # 3. 等比缩放至 320x?保持宽高比因 OCR 模型输入宽固定为 320 h_crop, w_crop plate_img.shape[:2] ratio 320 / w_crop new_h int(h_crop * ratio) plate_resized cv2.resize(plate_img, (320, new_h)) # 4. 调用 OCR传入单图非批量 result ocr_engine.ocr(plate_resized, clsFalse, detFalse)[0] # detFalse 关闭文本检测 if not result: return # result[0][1][0] 是识别文本[0][1][1] 是置信度 text, score result[0][1] return text if score 0.7 else # 置信度过滤 # 主调用 results model.predict(img) for r in results: for box in r.boxes.xyxy.cpu().numpy(): text ocr_single_plate(img, box, ocr_engine) print(fDetected: {text})为什么不用detTrue车牌字符排列严格水平无需通用文本检测detTrue会额外耗时 200ms且在低分辨率车牌上易漏检单个字符detFalserec模式专为已知区域识别优化速度提升 3 倍。4. 避坑YOLOv8 PaddleOCR 链路中的 4 个高频翻车点4.1 现象YOLOv8 训练 loss 不降val mAP 始终 0.3原因labels/中.txt文件的 bbox 坐标未归一化或data.yaml中nc与names长度不一致。YOLOv8 要求 bbox 坐标为x_center, y_center, width, height全部归一化到 [0,1] 区间。常见错误是用了 COCO 格式x_min, y_min, width, height或未归一化。解决运行tools/check_labels.py它会遍历所有.txt文件检查每行是否恰好 5 个数字x_center,y_center,width,height是否均在 [0,1] 内width和height是否 0。修复脚本tools/fix_labels.py可自动转换 COCO 格式为 YOLO 格式。4.2 现象PaddleOCR 识别结果全为乱码如“ ”或空字符串原因character_dict_path指向的字典文件编码不是 UTF-8或character_type设为en但车牌含汉字。Windows 系统用记事本保存的.txt默认 ANSI 编码Linux 读取会乱码。解决用 VS Code 或 Notepad 将ppocr_keys_v1.txt重新保存为 UTF-8 无 BOM 格式在config/rec_*.yml中确认character_type: ch检查pretrained_model路径是否存在若路径错误PaddleOCR 会静默加载默认英文模型。4.3 现象pipeline.py运行时报cv2.error: OpenCV(4.5.5) ... error: (-215:Assertion failed) ... size.width0 size.height0原因YOLO 检测框坐标计算错误导致x1x2或y1y2cv2.resize()输入空图。常见于box坐标未转为整数或x1,y1,x2,y2顺序颠倒YOLO 输出是xyxy但有人误当x1y1x2y2用。解决在ocr_single_plate()函数开头强制加边界检查x1, y1, x2, y2 map(int, [box[0], box[1], box[2], box[3]]) x1, y1 max(0, x1), max(0, y1) x2, y2 min(w, x2), min(h, y2) if x2 x1 or y2 y1: return # 直接跳过不 crash4.4 现象RK3588 部署时报NotImplementedError: Paddle Inference not support ARM CPU yet原因PaddlePaddle 官方paddlepaddle-gpu不支持 ARM但本项目提供paddlepaddle-lite适配方案。错误在于 pip 安装了paddlepaddle而非paddlelite。解决卸载paddlepaddlepip uninstall paddlepaddle安装paddlelitepip install paddlelite2.10本项目验证版本修改inference/lite_pipeline.py用paddlelite加载.nb模型from paddlelite.lite import * predictor create_paddle_predictor(config) # config 由 MobileNetV3 模型导出的 .nb 文件路径构建提示.nb模型需用paddle_lite_opt工具从训练好的inference.pdmodel导出本项目models/ocr_lite/下已提供 RK3588 优化版。5. 部署与验证从 GTX1660Ti 到 RK3588 的跨平台实测技巧5.1 GTX1660Ti 上的实时推理优化CPU/GPU 协同流水线GTX1660Ti6GB 显存无法同时跑 YOLOv8 和 PaddleOCR 的 full model必须拆解。本项目inference/realtime_pipeline.py采用GPU 检测 CPU 识别流水线import threading import queue # 全局队列YOLO 输出 bbox 送入OCR 消费 bbox_queue queue.Queue(maxsize4) def yolov8_worker(): GPU 线程持续推理输出 bbox 到队列 model YOLO(weights/yolov8s_lp.pt) cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: break # GPU 推理 results model.predict(frame, conf0.5, device0) # 强制 GPU 0 for r in results: for box in r.boxes.xyxy.cpu().numpy(): bbox_queue.put((frame, box)) # 传原图box避免重复 decode def ocr_worker(): CPU 线程从队列取 bboxCPU 识别 ocr_engine PaddleOCR( rec_model_dirweights/rec_r34_vd_tps_bilstm_ctc/, use_gpuFalse, # 关键禁用 GPU use_angle_clsFalse ) while True: try: frame, box bbox_queue.get(timeout1) text ocr_single_plate(frame, box, ocr_engine) print(fOCR Result: {text}) except queue.Empty: continue # 启动双线程 t1 threading.Thread(targetyolov8_worker) t2 threading.Thread(targetocr_worker) t1.start() t2.start() t1.join() t2.join()实测帧率单线程YOLOOCR 全 GPU8 fps显存爆满双线程YOLO GPU OCR CPU24 fpsCPU i5-8250U 全核占用 60%关键queue.Queue(maxsize4)控制背压避免 YOLO 生产过快导致内存溢出。5.2 RK3588 部署.nb模型导出与 C 加载要点RK3588 需用 Paddle Lite 的.nb模型导出命令如下在 x86 服务器上执行# 1. 安装 paddle_lite_opt 工具 pip install paddlelite2.10 # 2. 导出检测模型YOLOv8s paddle_lite_opt \ --model_file./weights/yolov8s_lp_inference.pdmodel \ --param_file./weights/yolov8s_lp_inference.pdiparams \ --optimize_out_typenaive_buffer \ --optimize_out./models/yolov8s_lp_rk3588.nb \ --valid_targetsarm # 3. 导出识别模型MobileNetV3 CTC paddle_lite_opt \ --model_file./weights/rec_r34_vd_tps_bilstm_ctc_inference.pdmodel \ --param_file./weights/rec_r34_vd_tps_bilstm_ctc_inference.pdiparams \ --optimize_out_typenaive_buffer \ --optimize_out./models/rec_r34_vd_tps_bilstm_ctc_rk3588.nb \ --valid_targetsarmC 加载关键代码rk3588/main.cpp#include paddle_api.h using namespace paddle::lite_api; // 1. 创建预测器 std::shared_ptrPaddlePredictor detector CreatePaddlePredictorMobileConfig(config); std::shared_ptrPaddlePredictor recognizer CreatePaddlePredictorMobileConfig(config); // 2. 设置输入注意YOLOv8 输入为 NHWCPaddle Lite 默认 NCHW需转置 std::vectorfloat input_data ...; // 图像数据 auto input_tensor detector-GetInput(0); input_tensor-Resize({1, 3, 640, 640}); memcpy(input_tensor-mutable_datafloat(), input_data.data(), sizeof(float) * input_data.size()); // 3. 执行推理 detector-Run(); auto output_tensor detector-GetOutput(0); // 解析 output_tensor 得到 bbox... // 4. OCR 输入crop 后 resize 到 320x?转为 float32 NHWC cv::Mat plate_resized; cv::resize(plate_img, plate_resized, cv::Size(320, new_h)); std::vectorfloat rec_input(320 * new_h * 3); // BGR to RGB normalize for (int i 0; i plate_resized.rows; i) { for (int j 0; j plate_resized.cols; j) { auto pixel plate_resized.atcv::Vec3b(i, j); rec_input[(i*320j)*3 0] (float)pixel[2] / 255.0f; // R rec_input[(i*320j)*3 1] (float)pixel[1] / 255.0f; // G rec_input[(i*320j)*3 2] (float)pixel[0] / 255.0f; // B } }RK3588 实测性能YOLOv8s 检测12 ms/帧NPU 加速MobileNetV3 OCR8 ms/帧CPU 4 核满频端到端延迟≤30 ms满足 30fps 实时要求。5.3 验证准确率用tools/eval_pipeline.py生成混淆矩阵不要只信val/mAP最终车牌识别准确率需端到端验证。本项目提供tools/eval_pipeline.py输入测试集images/和真值labels/ground_truth.txt格式000001.jpg\t粤B12345输出Total samples: 1000 Correct: 924 Accuracy: 92.4% Confusion Matrix: 粤B12345 粤B12346 ... total 粤B12345 98 2 100 粤B12346 1 99 100 ...执行命令python tools/eval_pipeline.py \ --image_dir datasets/license_plate/images/test \ --gt_file datasets/license_plate/labels/ground_truth.txt \ --det_weight weights/yolov8s_lp.pt \ --rec_weight weights/rec_r34_vd_tps_bilstm_ctc/ \ --output_dir results/eval_202405关键参数--conf_thres 0.5YOLO 检测置信度阈值太低引入误检太高漏检--ocr_score_thres 0.7OCR 置信度过滤低于此值视为拒识计入 error--iou_thres 0.3检测框与真值框 IOU 阈值低于此值判定检测失败。从那以后我每次交付车牌识别项目都强制走一遍eval_pipeline.py—— 它不只输出一个数字而是告诉你“粤B”开头的车牌错在哪是检测漏了还是 OCR 把‘粤’认成‘奥’这才是毕业设计该有的闭环。希望帮到你。本文还有配套的精品资源点击获取