简介:面向车牌识别与目标检测开发者,这份数据集提供1019张带标签图像,可直接用于YOLO系列模型的训练与验证。压缩包共2000个文件,包含YOLO格式txt标签980个、VOC格式xml标签1019个,以及数据集配置文件data.yaml,已清晰划分训练集、验证集与测试集,免去手动划分与格式转换的麻烦。TXT标签采用 <x_center> <y_center> 格式,坐标均归一化到0~1之间,适配yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流算法,可直接加载训练并对比验证效果。资源整体大小47.28MB,已有153人学习,适合正在做车牌检测项目或入门YOLO实战的开发者直接调用与快速上手。
1. 这批车牌检测数据集到底值不值得解压:先看清它的边界
做车牌检测的人最容易卡住的往往不是模型,而是数据。想用 yolo 算法跑通一个车牌检测 demo,打开标注软件才发现要自己拉几百个框;等真拿到一份“1019张图像带标签”的 zip,又开始怀疑里面标签格式对不对、能不能直接喂给 YOLOv8。这个数据集就是干这个用的:1019 张真实场景的车牌图像,每张配好了 YOLO 格式的 txt 标签,解压、整理、写个 data.yaml 就能开始训练。适合刚跑通 YOLO 环境、想拿真实数据做车牌检测的入门者,也适合需要在白天/夜间/不同车牌颜色上快速验证模型的从业者。不过别高兴太早,这份数据能省你一周打标时间,但能不能训练出稳定模型,还得看你会不会处理类别不平衡、坐标归一化这些隐藏问题。
2. 解压与目录摸底:从 zip 到可训练数据集的第一步
拿到这个 zip 之后,先别急着双击解压,也别急着往 YOLOv8 里塞。车牌检测数据集这种资源,最常见的翻车点不是模型,而是文件本身在传输过程中损坏、标签和图像对不上、标签格式根本不是 YOLO 能认的。先花十分钟把压缩包摸清楚,后面能省下好几个小时的排错时间。
2.1 先校验 zip 完整性再解压:避免训到一半才发现图片损坏
压缩包是 zip 格式,但 zip 也有损坏的可能。尤其从网盘、微信、邮件附件下下来的文件,经常出现“能解压但解出来一半是坏的”这种状态。最直接的办法是先用测试模式跑一遍,不实际解压,只校验压缩包结构。
# Linux 下先测试压缩包完整性,不实际解压 unzip -t "yolo算法-车牌检测数据集-1019张图像带标签.zip" | tail -n 20 # Windows 下用 7-Zip 的命令行版本 7z t "yolo算法-车牌检测数据集-1019张图像带标签.zip"-t是 unzip 的 test 选项,它会逐个读取压缩包里的文件并做 CRC 校验,最后告诉你有多少个文件通过、多少个出错。输出比较多,所以用tail -n 20只看最后几行。Windows 下用 7-Zip 的命令行7z t也是同样的逻辑,它不会把文件解出来,只做完整性测试。这一步看着多余,实际很值得做,尤其是文件超过 1GB 或者从非官方渠道下载的时候。如果测试结果里出现 “bad CRC” 或 “packed data CRC failed”,别犹豫,重新下载或者找对方重新打包,硬解压只会让你在训练时突然遇到打不开的图片。
校验通过后再正常解压。建议单独建一个目录,不要把文件直接甩到桌面或项目根目录里,否则后面写 data.yaml 时路径很容易乱。
# Linux/macOS 下解压到专门的数据目录 mkdir -p license_plate_data unzip "yolo算法-车牌检测数据集-1019张图像带标签.zip" -d license_plate_data/ # 解压后先看一层目录结构 find license_plate_data -maxdepth 2 -type d | head -n 30-d指定了解压目标目录,find加上maxdepth 2只列两层目录,避免一次性刷出几百个文件。这一步的目的是确认压缩包顶层是不是套了一个外层文件夹,如果套了,后面写路径时就要多包一层。
2.2 看懂 YOLO 标签格式:txt 里的五个数字到底是什么
解压之后,重点看labels或annotations目录下的.txt文件。YOLO 系列算法(从 YOLOv5 到 YOLOv8)用的都是同一种标签格式:每一行代表一个目标,五个数字分别是class_id, x_center, y_center, width, height,注意后四个都做了归一化,除以了图片宽高。用文本编辑器打开一个标签文件,典型内容长这样:
# 假设这是某张车牌图片对应的 label 文件内容 # class_id x_center y_center width height 0 0.520157 0.432031 0.073438 0.026563第一列的0是类别编号,对应训练配置里names列表的下标。后四个数全是 0 到 1 之间的小数,表示车牌框中心点的相对位置和框的相对宽高。这里最容易被坑的是:有人拿到的是 VOC 格式的 XML 或 JSON 标注,里面存的是像素坐标,如果不做转换直接当成 YOLO 标签用,训练时 loss 会异常,甚至直接报错。如果你发现 txt 里出现大于 1 的数,基本可以确定这份标签不是 YOLO 原生格式,需要转换后再训练。
检验标签格式是否规范,可以写个几行脚本扫描一遍所有 txt,看看每行的数字数量和范围是否合法。常见的做法是直接用 Python 读文件,不加载图片,速度很快。
from pathlib import Path label_dir = Path("license_plate_data/labels") bad_label = [] for txt in label_dir.glob("*.txt"): for line in txt.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line: continue parts = line.split() # 每行必须是 class + 4 个坐标,且坐标值在 [0, 1] 区间 if len(parts) != 5: bad_label.append((txt.name, "字段数不对", line)) else: try: cls = int(parts[0]) coords = [float(v) for v in parts[1:]] if cls < 0 or not all(0.0 <= v <= 1.0 for v in coords): bad_label.append((txt.name, "数值越界", line)) except ValueError: bad_label.append((txt.name, "非数字内容", line)) print(f"异常标签文件数: {len(bad_label)}") for item in bad_label[:10]: print(item)这个脚本的价值在于快速筛查:len(parts) != 5能抓住多列少列的标签,坐标越界能抓住没归一化的像素值。如果你的标签里有空 txt 文件,说明那张图可能没有标注任何车牌,这在数据集中不算罕见,但要留意数量占比,空标签太多会干扰训练。
2.3 用脚本核对图像和标签是否一一对应
有了图像目录和标签目录之后,下一步就是确认每一张图都有且只有一个对应的 txt 文件。这个检查必须做,因为模型训练是随机读取图像和标签的,如果某个图像没有标签,训练过程的collate_fn可能会崩,或者在验证时 mAP 统计出现异常。
from pathlib import Path image_dir = Path("license_plate_data/images") label_dir = Path("license_plate_data/labels") images = {p.stem for p in image_dir.glob("*.jpg")} labels = {p.stem for p in label_dir.glob("*.txt")} no_label = images - labels # 有图无标签 no_image = labels - images # 有标签无图 print(f"图像总数: {len(images)}") print(f"标签总数: {len(labels)}") print(f"缺标签的图像: {len(no_label)}") print(f"缺图像的标签: {len(no_image)}") if no_label: print("示例:", sorted(no_label)[:5])这里用Path.glob("*.jpg")只匹配了 jpg,如果数据里有 png 或 bmp,需要一并加上。如果你解压后发现图像总数不是 1019,或者标签数和图像数对不上,先别急着训练,先看看到底是差在了哪些文件上。多数情况是解压不完整,或者有人提前删过失败样本,这两种情况都会直接影响训练集分布,需要手动处理。核对完成后,再把图像和标签整理成 YOLOv8 默认认的目录结构,方便后面直接引 data.yaml。
3. 用 YOLOv8 训练车牌检测模型:最小可复现流程
目录结构和标签格式确认无误之后,就可以正式碰模型了。这一章我按最常见、最稳的路线来写:YOLOv8 + 官方 CLI,配合一个 1019 张的小型车牌检测数据集,目标是把训练流程跑通,同时不把参数调得过于激进。很多新手在这一步会直接套用网上默认训练指令,结果发现 loss 不降或者 mAP 停在 0.1 上下,问题往往出在 data.yaml 和超参配置上。
3.1 准备 data.yaml:路径、类别数、类别名一个都不能错
YOLOv8 训练时读的 data.yaml 是它的“地图”,里面写死了训练集、验证集路径、类别数和类别名。很多人在这一步翻车:有人用 Windows 路径直接塞进去导致转义出错,有人把验证集路径指向不存在的目录,还有人类别名写成了0、1这种数字。正确写法很简单,但每一步都要核对。
# data.yaml # 建议用绝对路径,避免 CLI 在不同工作目录下执行时找不到数据 path: /home/user/license_plate_data train: images/train val: images/val nc: 3 names: 0: blue_plate 1: green_plate 2: yellow_plate需要注意,path是数据集根目录,train和val是相对path的子目录。如果你的数据集没有预先划分 train/val,只有一个 images 目录,最常见的做法是先按 8:2 划分,然后用软链接或直接移动文件来组织目录。这里类别数和类别名必须和标签里的 class_id 完全对应,如果标签里只有 0 这个类别,nc填 3 不会直接报错,但训练出来大概率是乱的。先确认数据实际有几个类别,再写这个文件。
分数据集时推荐写个固定随机种子的小脚本,保证每次划分结果一致,避免反复调参时训练集不固定导致无法对比。
import random from pathlib import Path import shutil random.seed(42) image_dir = Path("license_plate_data/images") train_dir = Path("license_plate_data/images/train") val_dir = Path("license_plate_data/images/val") for split_dir in (train_dir, val_dir): split_dir.mkdir(parents=True, exist_ok=True) all_images = list(image_dir.glob("*.jpg")) random.shuffle(all_images) split_index = int(len(all_images) * 0.8) for img in all_images[:split_index]: shutil.move(str(img), str(train_dir / img.name)) for img in all_images[split_index:]: shutil.move(str(img), str(val_dir / img.name))注意,这个脚本只移动了图像,没有移动标签,所以还要同步把 labels 目录拆成和 images 对应的结构。多数 YOLO 训练框架会按图像路径的 stem 去找同名 txt,如果标签目录仍然是扁平结构,训练时会报找不到 label 的警告。拆标签时最稳的方法是从图像文件名反推:训练集里的图,它的同名标签就放到 labels/train 下。这一步可以用shutil.move的变体完成,但更推荐直接写一个按 stem 匹配的脚本,避免操作失误。
3.2 训练命令与关键参数:batch、epochs、imgsz 怎么定
目录组织好、data.yaml 写好后,训练命令本身不长,但参数选择直接影响 1019 张这种小数据量下的训练效果。
# 用 YOLOv8n 做基础版训练 yolo detect train \ data=/home/user/license_plate_data/data.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=640 \ patience=20 \ project=runs/plate_detect \ name=baseline这里逐项说下关键参数。model=yolov8n.pt表示用 YOLOv8n 的预训练权重做起点,n 是 nano 版本,模型最小、训练最快,对 1019 张数据最合适。epochs=100看着多,但因为加了patience=20,模型在验证集上连续 20 轮不提升就会提前停,实际通常 50 轮左右就能收敛。batch=16取决于显存,如果是 6GB 显存就把 batch 降到 8,否则容易爆显存。imgsz=640是输入分辨率,车牌是中小目标,不建议低于 640,否则小车牌在缩小后几乎不可辨。
训练跑起来后,你要盯的不是终端打印的进度条,而是两个指标:Box P和mAP50。正常情况下,前 20 轮 mAP50 会从 0 爬到 0.5 以上,如果 30 轮后还在 0.2 以下徘徊,先别继续等了,检查标签格式和数据划分。
用 YOLOv8 的 Python API 也能跑,效果一样,只是便于把训练记录嵌入你自己的训练脚本:
from ultralytics import YOLO model = YOLO("yolov8n.pt") results = model.train( data="/home/user/license_plate_data/data.yaml", epochs=100, batch=16, imgsz=640, patience=20, project="runs/plate_detect", name="baseline", )两种方式底层走的是同一套训练逻辑,CLI 适合快速跑基线,Python API 适合后面要做学习率调度、自定义回调的进阶场景。数据量小的时候,我建议你先跑 CLI,因为输出排版更直观,方便直接截图对比实验结果。
3.3 验证集与指标:mAP 要看到什么样的数才算能上车
训练结束后,项目目录下会生成runs/plate_detect/baseline文件夹,里面有weights/last.pt、weights/best.pt和一堆图表。注意先区分 last 和 best:best 是验证集 mAP 最高时的权重,last 是最后一轮的权重,平时测试、部署都用 best,不要习惯性拿 last 当成品。用 best.pt 跑一遍验证集,看最终指标:
yolo detect val \ model=runs/plate_detect/baseline/weights/best.pt \ data=/home/user/license_plate_data/data.yaml输出里的关键指标有三个:mAP50是 IoU 阈值 0.5 时的平均精度,mAP50-95是 0.5 到 0.95 每隔 0.05 的平均值,Precision是所有预测框里真正检测到车牌的比例。对于车牌检测这种单类目标、背景相对简单的任务,mAP50低于 0.8 说明模型还没学好;mAP50-95因为 IoU 要求高,0.5 左右是正常水平。如果 mAP50 能到 0.85 以上,这个模型基本可以拿去跑实际图片测试了。
这里有个容易误判的点:验证集 mAP 高,不代表手机拍一张就一定能框中,因为数据集的图片和实际场景分布可能差别很大。所以训练完第一件事不是急着部署,而是拿几十张没见过的图做推理,看看误检和漏检都发生在什么场景下。
4. 车牌检测训练与推理的五个必踩坑:从现象到排查
这部分是我最想写的。1019 张的车牌数据集本身不大,训练起来问题也特别典型,每个坑都是真实场景里反复出现过的。按照“现象 → 原因 → 解决”的顺序来记录,方便你在自己的项目里对照排查。
4.1 训练 loss 正常但预测框乱跳:标签坐标归一化被二次缩放
现象是训练 loss 一路下降,推理时框也能落在车牌附近,但框的大小和位置每张图都不太对,比如框总是偏左上角,或者框的宽高明显是真实车牌的 1.5 倍。最初我以为是模型没收敛,后来发现是标签坐标出了问题。原因很简单:这份数据集的标签虽然叫 YOLO 格式,但坐标可能是基于一张 1920x1080 的原图归一化的,而训练时imgsz=640是直接缩放整张图,没有做 letterbox,导致坐标和实际输入图像的空间映射错位。另一个常见原因是有人用 LabelImg 导出了 YOLO 格式,但导出时又把图像做了一次裁剪,标签没跟着裁。解决方法是随机抽出几张图,把标签坐标画回去看看:
import cv2 img = cv2.imread("license_plate_data/images/train/0001.jpg") label_path = "license_plate_data/labels/train/0001.txt" h, w = img.shape[:2] with open(label_path, encoding="utf-8") as f: for line in f: cls, x_center, y_center, box_w, box_h = map(float, line.split()) x1 = int((x_center - box_w / 2) * w) y1 = int((y_center - box_h / 2) * h) x2 = int((x_center + box_w / 2) * w) y2 = int((y_center + box_h / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imwrite("check_visualized.jpg", img)把可视化结果和原图叠着看,如果框没有贴合车牌,说明标签坐标和图像不是同一次 resize 下的产物,这时要么用原始分辨率去训练,要么统一重算归一化坐标。
4.2 蓝牌能检、绿牌黄牌全漏:类别不平衡与样本倾向
现象是训练完模型后,蓝牌检测效果不错,但绿牌、黄牌几乎一个都检不出来。原因基本是数据集里蓝牌图片占了绝大多数,其他颜色的车牌样本太少,模型训练时对少数类的梯度贡献被淹没了。1019 张图如果按 7:2:1 分给蓝绿黄,绿牌黄牌每类可能只有 100 张不到,这么少的样本很难学到稳定的特征。解决这类不平衡问题,最直接的办法不是马上加增广,而是先统计一下各类别的真实分布。如果少数类样本确实太少,可以考虑把类别合并成“车牌”单类,先把检测能力跑通,再做细分类;如果坚持分颜色,就要对少数类做过采样复制,或在训练时提高少数类在 loss 中的权重。实际项目里我更推荐先合并成单类,因为车牌检测的本质是先定位,颜色识别可以放在检测框之后加一个轻量分类器,这个思路对数据量小的场景友好得多。
4.3 解压后图片打不开:zip 传输损坏与隐藏的截断文件
这里说的不只是解压时报错,更是解压后部分图片在读取时cv2.imread返回 None。现象是训练过程中突然报出错在某张图上,或者验证集 mAP 极低,经过排查发现那部分损坏图片根本加载不出来。原因一般有两个:一是 zip 在传输过程中损坏,解压程序没报错但写出的文件不完整;二是源文件本身就没有写完整,比如相机中断导致 jpg 文件缺尾。解决的第一步是把损坏文件找出来:
import cv2 from pathlib import Path bad_images = [] for p in Path("license_plate_data/images").rglob("*.jpg"): img = cv2.imread(str(p)) if img is None: bad_images.append(p) print(f"损坏图片数量: {len(bad_images)}") for p in bad_images[:20]: print(p)找到之后,把损坏图片和它对应的标签一起移到 backup 目录,不要直接删除,防止后面需要重新从原始 zip 恢复时找不到参考。处理完后再看一眼总数,如果缺失超过 5%,建议回头重新校验原始 zip。
4.4 标签数量对不上图像数量:空 txt 和漏标
现象是训练前脚本报“found no labels”警告,或者训练过程完全没有车牌类别在跑。原因多数是标签目录里某些 txt 文件是空的,还有一部分图根本没有对应的 txt。这种问题在下载的数据集里特别常见,属于打包时的疏漏。解决方法是把空 txt 对应的图找出来,根据实际情况做两种处理之一:如果那辆车确实太模糊、人或物遮挡严重,直接把这组图像和空标签都移除,避免干扰;如果图像明显包含清晰车牌但没标,建议自己补标。但 1019 张数据集里如果空标签只占一两个,最省事的做法是直接剔除。剔除后注意重新统计个数,保持一致。
4.5 置信度门限调高后框没了:看看你用的是 P 还是 R 曲线
这种现象出现在推理阶段:训练时 mAP 看着不错,但部署时把conf_thres从 0.25 调到 0.5 之后,一张图上原本能框出的车牌全消失了。很多人第一反应是模型不行,实际上可能是模型处于“低置信度高召回”的状态,也就是它学到了非常保守的特征,预测出的置信度普遍不高。YOLOv8 默认推理conf=0.25,这个门限对车牌检测来说偏松,会有很多误检,但调到 0.5 可能把真车牌也过滤掉了。解决方法是先跑一遍验证集,把 precision-recall 曲线打出来,看置信度在哪个区间可以让 precision 和 recall 达到一个可接受的平衡点。常见做法是写一段循环,统计不同 conf 下的 P/R 值,找到一个“框基本可信且不会漏太多”的阈值。这个阀值通常不是常规的 0.5,而是 0.3 到 0.4 之间,取决于你的训练数据和部署场景的相似程度。
5. 数据增广与模型选型:把 1019 张的价值榨干
数据量就 1019 张,模型性能的天花板很大程度上由数据增广和模型容量决定。很多人在这一步会犯两个极端:要么完全不开增广,导致模型严重过拟合;要么把 mosaic、mixup、copy-paste 全量开满,结果小模型学不动,loss 震荡。正确姿势是先跑一个裸参数基线,再根据过拟合程度逐步加增广。
5.1 先跑基线再谈增广:1019张什么时候会过拟合
判断过拟合不需要等训练结束,观察训练集和验证集 loss 的分离程度就行。在 YOLOv8 的输出目录里,results.png里有两条 loss 曲线,如果训练集 loss 持续下降、验证集 loss 在第 20 轮开始反弹,基本就是过拟合了。1019 张数据在二三十轮之后出现这种分离是大概率事件,区别只是严重程度。处理顺序很重要:先用不做任何增广的默认参数跑一次,确认数据本身没硬伤;再开数据增广,观察过拟合是否缓解。如果跳过基线直接调增广参数,你根本分不清指标变化是因为增广起作用还是因为随机种子变了。
判断过拟合也可以直接看推理结果:训练集图片检测得很好,换几张没见过的同场景图片就开始漏检。这说明模型记住了训练图的具体纹理而不是车牌共性。
5.2 YOLOv8 内置增广参数怎么调:mosaic、copy_paste、hsv
YOLOv8 在训练时默认已经开了部分增广,例如 mosaic、hsv 增强、随机平移和缩放。对小数据集来说,最有用的三个是:
mosaic=1.0:把 4 张图拼成一张训练,能显著增加单图里的目标数量,也可以让模型看到更多样的背景。但 mosaic 对车牌这种小目标偶尔会带来截断问题,如果发现小目标漏检,可以降到 0.5。hsv_h、hsv_s、hsv_v:对颜色做扰动,车牌颜色本身是强特征,不要扰动过大,否则蓝牌会被调到偏紫。建议保持默认或把hsv_v降到 0.3。copy_paste:把一张图里的目标复制粘贴到另一张图,相当于白送样本。这个增广对车牌这类形体规整的目标效果不错,但要注意如果场景里有重叠的车牌,粘贴会制造错误标注。
修改方法很简单,可以在 CLI 里直接指定:
yolo detect train \ data=/home/user/license_plate_data/data.yaml \ model=yolov8n.pt \ epochs=100 \ batch=16 \ imgsz=640 \ mosaic=0.8 \ copy_paste=0.5 \ hsv_v=0.3这些参数值是经验值,不是固定标准。如果你发现验证集 mAP50 在加了增广后反而轻微下降,不要慌,可能只是模型在验证集上见过的等价样本变难了,再继续多训几十轮,往往能超过未增广的效果。
5.3 选 yolov8n 还是 yolov8s:按推理设备倒推参数
模型选型最忌讳的是盲目追大。1019 张数据撑不起 yolov8x 这种大模型,强行训练大概率在验证集上过拟合到无法用。常见选择是yolov8n和yolov8s,不能说哪个绝对更好,要看你的部署条件。
| 模型 | 参数量 | 车牌小目标倾向 | 训练显存需求 | 推理速度 | 适用场景 |
|---|---|---|---|---|---|
| yolov8n | 约 3.2M | 够用,适合漏检率要求不极端的场景 | 4GB 可跑 | 快,CPU 也能勉强推理 | 移动端、嵌入式、实时视频流 |
| yolov8s | 约 11.2M | 稍好,尤其对远距离小车牌 | 建议 8GB 以上 | 中速,GPU 流畅 | 服务端、抓拍机后处理 |
选型建议很简单:如果你部署在 Jetson Nano、树莓派或者低功耗 IPC 上,直接选 n;如果你有 GPU 服务器,而且对检测精度有要求,可以先从 s 开始试。用 s 训练 1019 张数据时,batch 可以降到 8,epochs 适当增加,但 patience 要保留,防止后期训练浪费。
还有一个容易忽略的点:无论选 n 还是 s,都要在训练前统一imgsz和部署时的输入尺寸。训练用 640,部署时却用 1280,精度和速度都不会达到预期。
6. 验证效果时最容易忽略的一个小动作:按车牌形态分组测一次
很多人在跑完训练、看到 mAP50 有 0.85 之后就觉得大功告成,直接拿 best.pt 去做演示,结果现场一测就翻车。原因很简单:整体指标掩盖了场景差异。1019 张数据里可能大部分是白天正面拍摄的清晰蓝牌,夜间、逆光、倾斜、双层货车牌这些极端样本数量少,整体 mAP 会被“好样本”拉高。所以我习惯在最终验证时多做一步:把测试图片按车牌形态分组,分别统计精度和召回。
分组的维度不用太复杂,常见做法是分成“常规蓝牌”“绿牌/黄牌”“倾斜或模糊”“夜间或逆光”四组。如果手头没有这么多分类好的图,也可以用一句话说清楚:找 30 张普通干净的车牌图,再找 30 张有明显遮挡、模糊、偏转的车牌图,分别跑推理。
from ultralytics import YOLO from pathlib import Path model = YOLO("runs/plate_detect/baseline/weights/best.pt") groups = { "normal": Path("test_images/normal"), "hard": Path("test_images/hard"), } for group_name, img_dir in groups.items(): total = 0 detected = 0 for img_path in img_dir.glob("*.jpg"): result = model.predict(str(img_path), conf=0.35, verbose=False) boxes = result[0].boxes total += 1 if boxes is not None and len(boxes) > 0: detected += 1 print(f"{group_name}: {detected}/{total} = {detected / total:.2%}")这段代码不追求复杂,就干一件事:按分组统计检出率。conf=0.35是前面排错后定的推理阈值,你不用照抄,先跑几次看看哪些图漏检,再决定是提高阈值还是换更大模型。如果 normal 组 100% 检出但 hard 组只有 30%,说明模型的泛化能力还撑不住实际环境,这不是调参能解决的问题,得考虑补充困难样本继续训练。
分组验证这个习惯帮我避过好几次上线前的重大失误。印象最深的一次是模型整体 mAP 看起来很好,上线前一天拿停车场夜间照片一测,基本全漏,才发现训练集里只有不到 10 张夜间图。从那以后,我再也不只看单一指标,而是先问自己:这些测试图里,最难的那部分占比多少?希望这个分组验证的思路也能帮你少踩一次坑。
验证完成之后,如果你发现自己手里的这份车牌检测数据集本身偏科,别急着删,把读不懂的图片挑出来,配合已知的模型短板去补充采集或爬取,才是让模型真正具备落地能力的关键路径。祝你训练顺利,希望帮到你。
本文还有配套的精品资源,点击获取