
简介一套基于YOLOv8的车牌识别系统完整工程代码面向目标检测与OCR方向开发者、算法工程师及智能交通项目学习者解决从自然场景中实时定位并识别车牌的端到端实现问题。压缩包共61个文件约6.36MB以37个Python脚本为主体涵盖模型推理、实时/批量处理、Web界面及数据库管理模块同时包含yolov8n.pt预训练权重、YAML配置、XML工程文件、示例图像与Docker部署文件便于快速搭建运行环境。已有95人学习下载。工程提供主识别程序、实时处理模块、批量识别工具及Web可视化入口可结合摄像头完成车辆捕获、车牌定位、字符分割与OCR识别全流程并借助数据库模块完成结果持久化。代码结构清晰接口分层合理对理解YOLOv8工程部署、OCR管线拼接与完整系统集成具有直接参考价值适合作为课程设计、毕业设计或工业预研的起点。1. 为什么用 YOLOv8 做车牌识别从检测框到端到端识别智慧停车道闸的第一现场不是安静的车位而是车流高速通过的路口。YOLOv8 做车牌识别和传统 OCR 逐字符抠字不同它先由检测模型把车牌区域完整框出来再由识别模块输出字符串图像识别、机器学习、人工智能三层技术栈压在一套工程里单帧延迟能压到几十毫秒。这份资源包就是围绕 YOLOv8 车牌识别搭起来的最小可用工程涵盖环境搭建、数据集处理、模型训练、推理部署的完整链条。适合正在做智慧停车、出入口管理、毕业设计以及想从零把 YOLOv8 跑通再自己改进的开发者。它能解决的问题很具体摄像头前一辆车驶过系统在几百毫秒内返回准确的车牌号。2. 搭建 YOLOv8 环境CPU 能跑、GPU 能训、边缘端能部署跑车牌识别之前环境这块是最容易出现玄学问题的。很多人直接往系统 Python 里装依赖装到一半发现和已有包冲突后面所有的训练报错都说不清来源。我一般习惯先建一个独立的 conda 环境把 Python 版本和依赖都隔离出来后面换机器、换项目都省心。2.1 conda 环境与 ultralytics 安装先创建一个干净环境并激活再安装 ultralytics。这是最稳妥的起步方式Windows 和 Ubuntu 20.04 上都适用conda create -n plate python3.8 -y conda activate plate pip install ultralytics第一行创建名为 plate 的 Python 3.8 环境-y 表示跳过确认。Python 3.8 是 YOLOv8 官方支持比较成熟的版本3.9、3.10 也行但我遇到过某些第三方 OCR 库在 3.10 上编译出问题所以这里扣住 3.8。第二行激活环境之后的 pip 安装都会落在这个环境里不会污染系统自带 Python。第三行会把这些年 YOLOv8 需要的核心依赖一起拉下来包括 torch、torchvision、opencv-python 等所以正常情况不需要手动逐个装。接着要根据本机是否带 NVIDIA GPU 来装 PyTorch。有 GPU 时用 CUDA 11.8 对应的版本pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118只有 CPU 的机器就装 CPU 版pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu这里有个常见误解以为 torch 必须和本机安装的 CUDA 工具包版本完全一致。实际上 PyTorch 在 cu118 这个包名里已经自带了 CUDA 运行库只要 NVIDIA 驱动版本够新就能跑不需要单独装 CUDA SDK。驱动太旧时可以先用 nvidia-smi 看驱动对应的 CUDA 版本再选 cu118、cu121 或者更新的 cu124。CPU 版 torch 也能跑训练和推理但训练速度会慢很多这个后面说。2.2 快速验证安装首轮推理跑通环境装完不能直接开训练先跑一次推理验证链路是通的。用 YOLOv8 官方提供的预训练权重做一次目标检测如果图片里的人和车能被框出来说明安装没问题yolo predict modelyolov8n.pt sourcehttps://ultralytics.com/images/bus.jpg这条命令要说明几个行为。model 参数指定 yolov8n.ptn 是 nano 版本也是整个 YOLOv8 家族里参数量最小、推理最快的档位车牌检测这种单类别任务用 n 起步完全够。source 可以是一个本地图片路径、视频路径或者摄像头序号这里直接用了官方示例图的 URL。第一次运行时ultralytics 会自动下载预训练权重到当前目录所以需要联网如果网络波动导致下载失败就去 ultralytics 的 GitHub Releases 页面手工下载对应 .pt 文件再把命令里的模型路径改成本地文件即可。多提一句下载预训练权重这件事很多新手会卡住以为是代码问题。实际上拉不下来就把 .pt 文件放到运行目录命令不变它会优先读本地文件。出结果后终端会打印出检测到的目标类别、置信度和坐标同时把标注好的图片存到 runs/detect 目录。提示验证时如果选的测试图里没有车牌检测结果为空是正常的不代表环境有问题。先用官方示例图确认安装再用自己的车牌图。2.3 硬件选型与模型档位CPU、GPU、边缘板卡各用什么车牌识别落地的硬件差异很大不能一套配置走天下。我根据自己的实测经验把常见场景整理成下面这张表场景硬件推荐模型单帧耗时参考推理演示、调参测试CPU 笔记本yolov8n640 输入约 200~500ms小数据集训练单张 GTX 1660 Ti / RTX 3060 8GByolov8n / yolov8s100 轮约 2~4 小时边缘部署RK3588 / Jetson Orin Nanoyolov8n 转 ONNX 再转 RKNN / TensorRT预热后约 20~80ms表格里的耗时是经验值不是精确基准因为输入分辨率、视频解码开销和板卡散热都会影响实际表现。选型逻辑不复杂CPU 训练不是不能做但一个几百张的车牌数据集跑 300 轮可能要十几个小时除非只是验证代码流程否则我建议至少找一块二手 GPU。显存 8GB 以上时batch16、imgsz640 跑 yolov8n 没有压力想用 yolov8s 就得把 batch 降到 8否则显存容易爆。边缘端是另一个常见场景现在 RK3588 这类板卡跑 YOLOv8 的方案已经很成熟但准备环境时就要做一次格式转换。提前把训练好的 .pt 权重导出为 ONNX后面切其他推理框架都方便yolo export modelbest.pt formatonnx opset12 imgsz640导出命令里formatonnx 是目标格式opset12 是 ONNX 算子集版本imgsz640 要和训练时的输入尺寸保持一致不然部署后精度会掉。导出的 best.onnx 是中间产物在 RK3588 上还要用 RKNN-Toolkit2 转成 .rknn 才能跑。这一步常见坑是模型里的算子不被 RKNN 编译器支持后面避坑章节会细说。3. 车牌数据集准备从原始图片到可训练的 YOLO 标签YOLOv8 能做的只是“在图像里找到车牌”它不认识车牌上的字符所以第一步是准备边界框标注数据。数据集质量直接决定训练上限这章把公开数据集、自采数据、Labelme 标注转格式和训练集划分一次讲完。3.1 车牌数据从哪来公开数据集与自采的取舍车牌识别领域最常用的公开数据集是 CCPD停车场场景下拍摄的中国车牌数据包含不同光照、角度和天气条件规模大标注信息也比较完整。但 CCPD 的标注格式是 JSON 结构边界框信息嵌在字段里直接用不了要写脚本解析。另一个问题是 CCPD 的场景集中在停车场如果你要部署的路口机位和停车场视角差异大迁移效果不会太好。我的建议是混合策略。先用公开数据集做预训练再采集 500 到 1000 张实际机位的抓拍图做微调。自采数据要注意覆盖度同一块车牌在不同的光照、角度、距离下多拍几张夜间、逆光、雨天都要有车牌倾斜角度尽量均匀分布。数量不是重点覆盖度才是。很多项目翻车不是因为数据少而是因为所有样本都是同一个角度同一个光线模型学到的其实是“停车场固定机位的亮度分布”换到户外就废。标注时用 Labelme 或多边形标注工具都行输出是 JSON 文件。这里要注意车牌在图像里往往是倾斜的Labelme 里可以用矩形框也可以用四个点框出旋转车牌。四个点标注能保留角度信息对后续识别更有用但转换脚本要处理点坐标不能简单取左上右下。3.2 Labelme 标注转 YOLO 格式转换脚本与归一化YOLO 训练需要的标签格式是每张图一个 .txt 文件每行内容是“类别 x_center y_center width height”坐标都归一化到 0~1 之间。下面这个脚本把 Labelme 生成的 JSON 转成 YOLO 格式是车牌项目里最常见的一段工具代码import json import os def labelme_to_yolo(json_path, out_dir, class_id0): with open(json_path, r, encodingutf-8) as f: data json.load(f) width data[imageWidth] height data[imageHeight] txt_lines [] for shape in data[shapes]: if shape[label] ! plate: continue points shape[points] xs [p[0] for p in points] ys [p[1] for p in points] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) x_center (x_min x_max) / 2 / width y_center (y_min y_max) / 2 / height box_w (x_max - x_min) / width box_h (y_max - y_min) / height txt_lines.append( f{class_id} {x_center:.6f} {y_center:.6f} {box_w:.6f} {box_h:.6f} ) out_name os.path.splitext(os.path.basename(json_path))[0] .txt with open(os.path.join(out_dir, out_name), w, encodingutf-8) as f: f.write(.join(txt_lines)) # 对 data/jsons 目录下所有 JSON 执行转换 for jf in os.listdir(data/jsons): if jf.endswith(.json): labelme_to_yolo(os.path.join(data/jsons, jf), data/labels)这段脚本的逻辑分四步读取 JSON 里的图片宽高遍历标注对象只保留 label 为 plate 的标注然后把多边形点的外接矩形换算成中心点加宽高再归一化。参数上class_id0 是类别编号如果有多个类别比如 plate 和 fake_plate就按类别映射表手动指定。div width 和 height 这一步很关键漏掉会导致训练时 loss 直接飞掉后面避坑章节会专门说。这里用的是最小外接矩形思路x_min、x_max 那个逻辑其实就是取四个点的最外侧。车牌倾斜角度在 15 度以内时这个矩形还能用倾斜超过 30 度矩形会框进大量背景模型学到的特征被干扰。所以脚本只是基础生产级项目应该在转换前先把图像按车牌角度旋转校正或者改用旋转目标检测思路。3.3 数据集划分与增强策略小目标场景要关 mosaic标注转换完成后要按 YOLO 的目录规范把数据组织好。常见做法是 9:1 划分训练集和验证集避免随机划分在数据分布上撞车。下面这段脚本完成自动划分import os import random import shutil random.seed(42) images os.listdir(data/images) random.shuffle(images) split_idx int(len(images) * 0.9) train_list images[:split_idx] val_list images[split_idx:] os.makedirs(dataset/train/images, exist_okTrue) os.makedirs(dataset/train/labels, exist_okTrue) os.makedirs(dataset/val/images, exist_okTrue) os.makedirs(dataset/val/labels, exist_okTrue) for img in train_list: shutil.copy(fdata/images/{img}, dataset/train/images/) shutil.copy( fdata/labels/{img.replace(.jpg, .txt)}, dataset/train/labels/ ) for img in val_list: shutil.copy(fdata/images/{img}, dataset/val/images/) shutil.copy( fdata/labels/{img.replace(.jpg, .txt)}, dataset/val/labels/ )random.seed(42) 保证每次划分结果一致调参时能对比。split_idx 算出训练集数量这里用 0.9 偏保守如果数据量超过 5000 张可以改成 0.95验证集小一点不影响评估。增强策略上要特别提醒YOLOv8 默认开启 mosaic 增强把四张图拼成一张训练。这个策略对大目标检测效果很好但车牌是小目标mosaic 会把车牌切成碎片导致小样本上训练不稳定。我在车牌项目里的习惯是关掉 mosaic保留平移、缩放和 HSV 扰动yolo detect train dataplate.yaml modelyolov8n.pt epochs300 imgsz640 mosaic0.0mosaic0.0 是直接覆盖 ultralytics 默认增强配置。用命令行传这个参数比改 yaml 更直观训练结束后从日志里能看到增强关闭生效。小数据集上这个改动经常能把 mAP 拉高好几个点属于性价比极高的调试手段。4. 模型训练与调优把“能跑”变成“能用”数据集准备好之后训练就是核心环节。YOLOv8 的命令行训练非常简单但参数怎么选、损失曲线怎么看、mAP 怎么解读这些决定最终模型能不能实际用。4.1 dataset.yaml 配置绝对路径与类别映射训练前要先写一个 dataset.yaml告诉 ultralytics 数据集在哪、有几类、类别名是什么。车牌识别一般只有一个类别就是 plate配置文件里类别顺序必须和标签文件里的 class_id 对应path: /your_abs_path/dataset train: train/images val: val/images nc: 1 names: 0: platepath 字段建议写绝对路径。相对路径在 ultralytics 某些版本里解析会出问题尤其是把 yaml 文件放在项目子目录下时train 和 val 的相对路径基准会变。nc 是类别数量这里填 1。names 是类别名映射我写成了带缩进的列表形式也可以写成 names: [plate]两种写法都识别。4.2 训练命令与关键参数从几千行日志里读信息训练命令不长但每个参数都值得抠一遍yolo detect train dataplate.yaml modelyolov8n.pt epochs300 imgsz640 batch16 device0 patience30modelyolov8n.pt 有两种用法传预训练权重路径时会基于官方 COCO 权重做迁移学习收敛更快这是默认推荐传 yolov8n.yaml 时则从随机初始化开始训练耗时更长。车牌检测属于简单任务迁移学习收益明显所以用 .pt 不用 .yaml。epochs300 的意思是最大训练轮数。车牌数据集不大300 轮足够收敛。imgsz640 是输入分辨率想提升小目标检测精度可以提到 960但显存占用和训练时间都会涨效果不一定对等。batch16 要看显存调整显存不够的时候优先降 batch而不是降 imgsz因为 640 这个分辨率对车牌这种小目标已经很敏感。device0 指定使用第一张 GPUCPU 机器改成 devicecpu。patience30 是早停参数验证集指标连续 30 轮没有提升就停止训练防止过拟合。训练脚本开始后终端会每轮打印一组指标包括 box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95。真正需要盯的是 mAP50 和 mAP50-95。车牌检测目标单一mAP50 能到 0.95 以上才算“能用”0.85 到 0.95 属于“有漏检需要进一步调”0.8 以下基本要回头查数据。如果不需要命令行交互可以把这个训练过程写成 Python 脚本方便批量试验参数from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( dataplate.yaml, epochs300, imgsz640, batch16, device0, patience30, mosaic0.0, )这段代码和命令行等价。用 Python 脚本的好处是能同时跑多个实验比如对比 n 和 s 两种模型大小或者对比不同 imgsz 的效果都不用手动改命令。train 方法返回的 results 对象里保存了训练过程的完整指标后面画损失曲线可以直接读。4.3 损失曲线与 mAP 评估别只看一张结果图训练结束后runs/detect/train 目录下会生成 results.png包含各阶段损失曲线和指标曲线。我习惯同时启动 TensorBoard 看更细的曲线tensorboard --logdir runs/detect/train启动后在浏览器打开 localhost:6006能看到每一轮的 loss、learning rate、batch 变化等细节。读曲线有几个固定的判断标准。第一train 和 val 的 box_loss 应该同步下降如果 train_loss 降而 val_loss 开始反弹说明过拟合应该提前停或加数据增强。第二val 的 cls_loss 在车牌这类单类别任务里一般会降得很低因为所有目标的类别都一样分类压力小这个指标不具备参考价值主要看 box_loss 和 dfl_loss。第三mAP50 曲线在训练后期应该是缓慢上升的如果出现明显波动甚至下降通常是数据里有标签错误最常见的错误是标注框没覆盖完整车牌。训练完成后会自动在验证集上评估输出一个包含 mAP 的表格同时会跑混淆矩阵和几张预测样例图。样例图上如果看到明显漏检但 mAP 数值不低说明验证集和训练机位接近不能代表真实场景需要另找一些完全没有见过的图片做人工测试。5. 避坑与常见问题排查五个必踩的坑和解决记录车牌识别项目里训练不收敛、推理崩、部署精度掉这类问题大部分不是模型结构的问题而是工程细节。这里整理了五个高频坑每一条都按现象、原因、解决给出来可以直接对照排查。5.1 用 OpenCV 推理时识别率断崖式下降现象用 YOLO 自带的 predict 方法跑测试图检测效果很好换成自己的 OpenCV 推理脚本后车牌框要么找不到要么框位置偏得离谱。原因ultralytics 内部用 Pillow 读取图像读进来是 RGB 三通道OpenCV 的 cv2.imread 读进来是 BGR 顺序而且很多惯性写法直接用 OpenCV 的 frame 送进模型。模型在训练时看到的是 RGB 图像推理时输入却是 BGR通道顺序不对特征完全错乱。解决在送入模型前强制转成 RGB。常见做法是import cv2 frame cv2.imread(test.jpg) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model.predict(rgb, conf0.5, imgsz640)这个转换必须放在 model.predict 之前。如果模型已经接在了视频流里每一帧都要转不能只在第一帧转一次。5.2 标签没归一化训练 loss 飞掉现象训练一开始 box_loss 就冲到十几几轮之后不下降输出框整个画面乱飘。原因YOLO 标签要求 x_center、y_center、width、height 都是 0 到 1 之间的归一化值但转换脚本里忘记除以图片宽高直接把像素坐标写进 txt。模型把上千像素的数值当成归一化坐标来解释自然学不到任何东西。解决转换后抽几个 txt 文件检查一下数值范围。我常用的一段验证代码with open(dataset/train/labels/0001.txt) as f: for line in f: parts line.strip().split() print(parts) for i in range(1, 5): assert 0 float(parts[i]) 1, 坐标未归一化这段脚本逐行读标签文件把每行的五个字段拆出来然后检查类别编号之后的四个坐标值是否都在 0 到 1 区间内只要有一个越界就立刻报错。这个检查应该纳入数据准备的固定步骤不然后面训练花的几个小时全白费。5.3 CPU 机器训练卡住日志长时间不动现象没有 GPU用 devicecpu 训练终端打印了第一轮信息后长时间没反应任务看起来像死锁。原因ultralytics 默认会根据 CPU 核心数自动设置 DataLoader 的 num_workers在部分 Windows 机器上多进程数据加载会和主进程反复冲突导致训练流程卡在数据读取阶段而不是真的在计算。解决手动调低 workers 数量把输入尺寸降到 416 也能缓解 CPU 压力yolo detect train dataplate.yaml modelyolov8n.pt epochs100 imgsz416 batch8 devicecpu workers2workers2 是关键参数把它从默认的 8 降到 2卡顿问题基本消失。CPU 训练本身就是低频方案如果数据量超过 500 张还是建议租一张云 GPU时间成本差十倍以上。5.4 ONNX 导出后推理结果尺寸对不上现象把 best.pt 导出成 best.onnx用 ONNXRuntime 做推理输出的张量维度不是预期的 [1, 84, 8400]解不出坐标框。原因导出时没有固定输入尺寸ONNX 的输入是动态 shape不同推理框架对动态维度的解析方式不一致导致输出结构变化。另一个常见原因是 opset 版本太高老版本的推理框架不认新算子。解决导出时固定参数不要用默认值yolo export modelbest.pt formatonnx opset12 imgsz640 dynamicFalsedynamicFalse 表示固定输入尺寸opset12 是兼容性最稳的算子集版本。导出后可以用 onnxruntime 做一次标准推理确认输出 shape 符合 yolov8n 的 84 通道结构再做后续板卡转换。5.5 边缘板卡量化后精度掉得没法用现象同样的模型在 PC 上 mAP50 有 0.95转到 RK3588 上做 INT8 量化后掉到 0.7 左右漏检大量增加。原因INT8 量化用校准集来计算激活值的数值范围校准集如果只放了十几张车头正面照没有覆盖雨雾、夜间、倾斜车牌这些分布量化参数就偏向一面遇到分布外的图像直接跑偏。解决量化校准集至少要覆盖三类情况不同光照、不同角度、不同距离下的车牌数量取 100 到 300 张比较稳。另外量化前先确认浮点模型的 mAP50 已经达到 0.9 以上如果浮点模型本身不达标量化只会更差不要指望量化这一步能兜底。6. 推理脚本与部署边界把模型接进视频流之前先想清楚训练得到的 best.pt 要真正用起来还得写一个推理程序接视频流。下面这个脚本是最小的可用版本支持图片和视频文件也能接摄像头import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(test.mp4) while cap.isOpened(): ret, frame cap.read() if not ret: break rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results model.predict(rgb, conf0.5, imgsz640, verboseFalse) for r in results: for box in r.boxes.xyxy: x1, y1, x2, y2 map(int, box[:4]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(plate, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段脚本的关键点有三个。第一rgb 转换那一行不能省略原因已经在避坑章节说过了。第二conf0.5 是检测置信度阈值车牌场景建议在 0.4 到 0.6 之间调太高漏检多太低会出现大量误检框。第三verboseFalse 关掉逐帧日志否则视频流推理时终端会被打印刷屏影响实际性能。如果需要输出车牌字符串要在检测框基础上再接一个 OCR 模块或者用字符识别模型对框内区域单独做识别YOLOv8 本身只负责定位。部署边界也在这里说清楚车牌检测不等于车牌识别这是两个环节。这套工程里 YOLOv8 解决的是“车牌在哪”字符识别还要另外接 OCR 网络或者传统模板匹配方案。摄像头安装位置、车牌倾斜角度、夜间补光条件都会直接影响检测结果。相机架太高、角度太斜YOLOv8 再强也难稳定框住车牌这不是模型能弥补的。最后说一个我自己的习惯。自从有一次在户外测试时发现识别率从 0.95 掉到 0.7排查半天才发现是摄像头曝光参数被现场施工调过从那以后我每次把模型接到新的摄像头或者新场景都强制走一遍流程先采集 20 张现场抓拍图用训练好的模型跑一次批量推理看 mAP 是否稳定再看检测框是否完整覆盖车牌。这一套十几分钟就能跑完但能省掉后面调试的半天时间。希望帮到你。本文还有配套的精品资源点击获取