简介:面向目标检测算法训练与危化品运输监管场景,提供一套覆盖油罐车、天然气运输车、化学品运输车等典型车型的图像数据集。数据分布均匀、标注精准,可直接用于YOLO系列模型的训练与验证,也可服务于智慧物流、园区安防等特种车辆识别任务。
包内包含3059张JPG原图,以及配套的3052个XML(VOC格式)与3052个TXT(YOLO格式)标签文件,适配主流检测框架与标注工具,压缩后约437.68MB,目录结构清晰、便于按需取用。目前已有2875人学习下载。
标注覆盖目标框位置与类别信息,训练时拟合表现良好;同时提供VOC与YOLO双套标签,省去自行格式转换的环节,尤其适合刚接触目标检测的开发者直接上手实验,也可为已有检测模型提供扩容样本,帮助快速搭建危化品车辆识别方案。
1. 危化品运输车目标检测数据集:3059张图与双格式标签的完整拆解
危化品运输车的检测在化工园区安防、高速危化品通道、物流园区闸口管理里是典型的硬需求,通用检测模型往往分不清罐式半挂和普通货车的差别。这份数据集一共3059张实拍图片,覆盖油罐车、天然气运输车等危化品运输车型,每张图同时提供VOC(XML)和YOLO(TXT)两套标签,拿到手就能直接喂给yolo系列目标检测模型,省掉大面积的格式转换工序。对做目标检测项目的开发者来说,最值钱的不是图的数量,而是标注字段是否规整、类别是否齐全、坐标是否跟原图对齐。这篇笔记会把目录结构、标注文件字段、格式转换脚本、训练前的数据排查、训练配置和训练后的验证技巧逐层拆开,新手能照着复现,熟手可以直接跳过铺垫看踩坑清单。
2. 数据集结构剖析:从目录布局到VOC与YOLO标注文件的字段细节
下载解压之后,第一件事不是急着跑训练,而是把目录结构理顺。这份危化品运输车数据集按目标检测的标准套路组织,解压后能看到三个主目录和一个类别清单文件。目录结构基本是下面这个形态:
| 目录/文件 | 承载内容 | 格式 |
|---|---|---|
| JPEGImages/ | 3059张原始实拍图 | jpg |
| Annotations/ | 与图片同名的VOC标注 | xml |
| labels/ | 与图片同名的YOLO标注 | txt |
| classes.txt | 类别清单,每行一个类名 | txt |
JPEGImages里是原始图片,命名一般带场景或序号信息;Annotations里是VOC格式的xml标注文件,文件名与图片一一对应;labels里是YOLO训练直接读取的txt标注。同一个文件名的三种扩展名,是串联这三部分的锚点。先把这张对应关系记牢,后面写转换脚本、写划分脚本都靠它。
提示:解压后如果发现labels目录里txt文件数量和JPEGImages里的图片数量对不上,先不要继续,去数Annotations数量,通常是某张图的xml漏转了。
2.1 XML标注字段逐行拆解
打开一个xml标注,内容是这个形态:
<annotation> <folder>JPEGImages</folder> <filename>tanker_001.jpg</filename> <size> <width>1920</width> <height>1080</height> <depth>3</depth> </size> <object> <name>oil_tanker</name> <pose>Unspecified</pose> <truncated>0</truncated> <difficult>0</difficult> <bndbox> <xmin>482</xmin> <ymin>310</ymin> <xmax>1254</xmax> <ymax>876</ymax> </bndbox> </object> </annotation>size节点里的width和height是标注工具写入时读取的图片宽高,后续把VOC坐标转成YOLO归一化坐标时,分母必须用这里面的值,而不是打开图片之后现读的尺寸。object节点下的name就是类别名,truncated表示目标是否被图片边缘截断,difficult表示这个目标是不是模糊难判。这两个字段在YOLO训练中没有直接对应的概念,转换时通常直接忽略,但如果一张图里所有目标都是truncated=1,需要确认这张图还有没有保留价值。
bndbox里四个值是像素级坐标,xmin和ymin是框左上角,xmax和ymax是右下角。后续公式用这四个值计算中心点和宽高,再除以图片宽高完成归一化。这里有一个细节需要留意:VOC约定的是左上角为原点,x向右增大,y向下增大。如果发现标注框和图片内容明显错位,先检查是不是坐标原点约定不一致。
注意:xmin和xmax的先后顺序不能写反。有些工具导出的xml会把坐标存成中心点加宽高,字段名却还叫xmin/xmax,这种文件看着字段齐全,实际转换出来全是偏的。
2.2 YOLO TXT标注的归一化坐标解读
YOLO的txt标签和xml完全不同,没有图片尺寸信息,没有类别名,只有纯粹的数值。用文本编辑器打开一个txt文件,内容大致是这样的:
0 0.4521 0.5486 0.4021 0.5231 0 0.7234 0.6312 0.1125 0.0987每一行包含五个值:第一个是类别编号,从0开始计数,和classes.txt里的行顺序一一对应;后四个是归一化后的中心点x、中心点y、框宽w、框高h,取值都在0到1之间。比如上面第一行,还原成像素坐标的计算方式是这样的:
def yolo_to_pixel(cls, x_center, y_center, w, h, img_width, img_height): xmin = int((x_center - w / 2) * img_width) ymin = int((y_center - h / 2) * img_height) xmax = int((x_center + w / 2) * img_width) ymax = int((y_center + h / 2) * img_height) return xmin, ymin, xmax, ymax逻辑说明:这个函数把YOLO的归一化坐标还原成像素坐标,参数x_center和y_center是目标中心点相对图片宽高的比例,w和h是目标大小占整张图的比例。因为txt里没有记录图片尺寸,调用时必须从原图单独获取img_width和img_height。这就是为什么YOLO训练时要求图片和标签严格同名且数量一致,少一个txt还好说,尺寸对不上就彻底偏了。
参数说明:还原后得到的xmin、ymin是像素坐标,可以直接拿来画框。x_center减去w/2再乘宽度,得到的是框左边界,加号对应右边界。如果计算结果出现负数或大于图片宽度,说明标签越界了。
2.3 类别分布与标签一致性初检
拿到数据集之后,我建议先跑一个类别分布统计,用Python写个简单的计数脚本:
import os from collections import Counter def count_boxes(label_dir): counter = Counter() for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname), 'r') as f: for line in f: parts = line.strip().split() if len(parts) == 5: counter[parts[0]] += 1 return counter if __name__ == '__main__': result = count_boxes('labels') for cls, cnt in result.most_common(): print(f'class {cls}: {cnt} boxes')逻辑说明:脚本遍历labels目录,把每个txt文件的每一行第一个字段当成类别编号统计。如果某个类别的框数比另一个少一个数量级,训练出来的模型大概率对这个类别召回偏弱,需要决定是补样本还是做增强。参数说明:代码按行split后取长度等于5的行,可以顺手过滤掉空行和畸形行。如果统计出来的类别编号数量比classes.txt的行数还多,说明标签文件里混入了未知类别,需要复查。
再做一步标签与图片的对应关系检查,用集合差集找出缺失或多余的标签:
import os def check_pair(image_dir, label_dir): images = {os.path.splitext(f)[0] for f in os.listdir(image_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))} labels = {os.path.splitext(f)[0] for f in os.listdir(label_dir) if f.endswith('.txt')} missing = images - labels extra = labels - images if missing: print('缺少标签的图片数量:', len(missing)) if extra: print('没有图片的标签数量:', len(extra))逻辑说明:先把图片文件名和标签文件名的前缀部分分别取出来,用集合做差集。missing是图片有但标签没有的文件,extra反过来。这一步是训练前性价比最高的检查,一次几秒钟,能避免训练时报错后大海捞针。参数说明:图片扩展名过滤用了lower(),兼容.JPG这类大写扩展名;labels目录里只要txt都会参与集合,所以别的杂散文件不要放进labels目录,否则会被误判为extra。
3. 格式转换与校验:VOC转YOLO的脚本实现与类别映射边界
虽然这份数据集已经同时提供了VOC和YOLO两套标签,大部分时候不用做转换,但真实项目里经常遇到只有xml标注、要训练YOLO模型的情况。转换逻辑核心就一个公式,难点全在类别映射和异常数据处理上。这章把常用脚本拆开,顺便说清楚哪些地方容易翻车。
3.1 VOC到YOLO的坐标转换原理
VOC给的是像素坐标,YOLO要的是归一化中心坐标和宽高,转换公式固定:
x_center = ((xmin + xmax) / 2) / img_width y_center = ((ymin + ymax) / 2) / img_height w = (xmax - xmin) / img_width h = (ymax - ymin) / img_height公式本身很标准,关键在img_width和img_height必须取xml里size节点的值,不能自己去图里现读。常见翻车场景是:标注工具在resize缩略图上标注,但xml里的size还写的是原图尺寸,转换出来的框整体偏移。另一个隐藏问题:如果xml里存在difficult=1的目标,训练时模型会强行学习这些模糊样本,建议在转换时把这类目标直接过滤掉,而不是留到训练里干扰收敛。
3.2 完整转换脚本实现
写一个完整的VOC转YOLO脚本,带异常保护,直接可复用:
import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, classes): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) lines = [] for obj in root.findall('object'): name = obj.find('name').text if name not in classes: print(f'{os.path.basename(xml_path)}: 跳过未知类别 {name}') continue class_id = classes.index(name) box = obj.find('bndbox') xmin = float(box.find('xmin').text) ymin = float(box.find('ymin').text) xmax = float(box.find('xmax').text) ymax = float(box.find('ymax').text) if xmin >= xmax or ymin >= ymax: print(f'{os.path.basename(xml_path)}: 坐标异常,已跳过') continue x_center = ((xmin + xmax) / 2) / img_w y_center = ((ymin + ymax) / 2) / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f'{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}') fname = os.path.splitext(os.path.basename(xml_path))[0] out_path = os.path.join(out_dir, fname + '.txt') with open(out_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines))逻辑说明:classes是字符串列表,顺序必须和训练时要用的data.yaml里names顺序完全一致。脚本对未知类别是跳过并打印,而不是硬塞一个编号,目的是避免写出一份看起来正常、实际错位的标签。坐标异常的情况也做了保护,xmin>=xmax的框直接跳过,并留日志供后面人工排查。
参数说明:输出文件采用和xml相同的文件名,只换扩展名,这是YOLO训练能够正确配对的前提。归一化坐标保留6位小数,足够训练使用。如果某个xml没有object节点,输出的txt是空文件,YOLO训练时会跳过该图,模型会把这张图当背景处理,不影响整体训练。
3.3 批量转换与类别映射边界
单文件转换没问题之后,用glob批量跑:
import glob import os classes = ['oil_tanker', 'gas_transporter'] os.makedirs('labels_yolo', exist_ok=True) for xml_file in glob.glob('Annotations/*.xml'): voc_to_yolo(xml_file, 'labels_yolo', classes) print('转换完成,共处理', len(glob.glob('Annotations/*.xml')), '个xml')批量跑之前,先处理三种边界情况。第一种是类别名不一致,比如有的标注员写oil_tank,有的写oil_tanker,建议先做一个别名映射字典,转换前把类别名归一到标准命名,否则它会出现在跳过列表里,最后统计出来的类别数量越来越少。第二种是xml数量多的时候,先拿5到10个文件试跑,肉眼检查输出txt再全量跑,不要一次性全量转换完成后才发现公式用错了。第三种是批量跑完后,用上一章的一致性检查脚本核对txt数量和图片数量。
类别别名映射的处理片段如下:
rename_map = {'oil_tank': 'oil_tanker', 'LNG': 'gas_transporter'} def normalize_name(name): return rename_map.get(name, name)逻辑说明:normalize_name函数先从字典里查别名,查不到就原样返回。配合转换脚本使用时,把obj.find('name').text先过一遍normalize_name,再判断是否在classes里。参数说明:字典里的key是数据集里实际出现的旧名称,value是标准名称;如果后续发现新的别名,直接在字典里加一行即可,不用改动主逻辑。
提示:不要把未知类别统一映射到编号0,这种操作会在类别之间互相污染,最后混淆矩阵上全是非对角线响应。
4. 训练前的避坑指南:五个数据质量问题与对应排查手段
这类数据集的坑不会在训练报错时暴露,而是藏在训练曲线和验证指标里。以下五条来自实际项目翻车记录,每条按现象、原因、解决的顺序写。
4.1 标签坐标与图片尺寸不一致:mAP虚高的隐形杀手
现象:训练时loss正常下降,验证mAP看着不低,拿实拍视频一测,框整体偏右下。
原因:xml里的size节点和图片真实分辨率对不上。常见的是标注工具生成xml后,某个脚本批量把图片resize了,却忘了更新xml,或者resize时只改了jpeg没改annotation。
解决:写个脚本用PIL读取图片真实宽高,再和xml里的size逐张对比:
from PIL import Image import os import xml.etree.ElementTree as ET def check_xml_size(xml_path, img_dir): root = ET.parse(xml_path).getroot() xml_w = int(root.find('size/width').text) xml_h = int(root.find('size/height').text) img_name = root.find('filename').text img_path = os.path.join(img_dir, img_name) with Image.open(img_path) as img: real_w, real_h = img.size if (xml_w, xml_h) != (real_w, real_h): print(f'{xml_path}: xml={xml_w}x{xml_h}, 实际={real_w}x{real_h}')逻辑说明:脚本直接访问xml的size节点和filename,然后从图片目录里找到原图,读真实分辨率做对比。只要输出里出现不一致的文件,就是问题样本。参数说明:PIL能处理jpg、png、bmp等常见格式;对比的是像素宽高对,不要只看宽或者只看高,宽高互换的样本同样会出问题。
4.2 油罐车和天然气运输车互相误检:相似类别的处理策略
现象:训练完的混淆矩阵里,oil_tanker和gas_transporter两个类别之间的非对角线数值很高,验证图片里油罐车被识别成天然气运输车,天然气运输车也有相当比例被识别成油罐车。
原因:这两类车远看都是罐式半挂结构,区别多在罐体涂装和车头造型,部分角度下人和模型都很难一眼分清楚。数据量只有千量级时,模型很容易学到“罐子”这个共有特征,却没有足够的细节样本区分涂装差异。
解决:优先考虑把这两个类别合并成一个大类tanker_truck,很多监管场景只需要知道“罐车经过”,并不需要细分装载物。如果业务方坚持细分,就在数据增强里加高饱和度扰动和对比度扰动,让模型更关注涂装色块,同时补充两类各自侧视、后视角度样本。这个问题的根源是类别定义粒度超过了数据信息量,不是调参能解决的。
4.3 标签越界:训练报错从txt坐标异常开始
现象:YOLO训练过程中提示某些标签超出边界,或者报错行内容不合法。
原因:某个txt里出现了大于1的归一化值,或者坐标倒挂。转换脚本中xmin>=xmax的判断不严格,比如浮点转换时精度问题导致w为负。
解决:写一个标签范围检查脚本,把所有越界项列出来:
import os def check_label_bounds(label_dir): bad_files = [] for fname in os.listdir(label_dir): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname), 'r') as f: for line in f: parts = line.strip().split() if len(parts) != 5: bad_files.append((fname, '字段数量错误')) break try: cls, xc, yc, w, h = map(float, parts) except ValueError: bad_files.append((fname, '数值解析失败')) break if not (0 <= xc <= 1 and 0 <= yc <= 1 and 0 < w <= 1 and 0 < h <= 1): bad_files.append((fname, '越界')) break if xc + w / 2 > 1 or yc + h / 2 > 1: bad_files.append((fname, '超出右/下边缘')) break return bad_files逻辑说明:检查分三层,先看字段数量是不是5个,再看数值能不能解析成浮点,最后看中心点和宽高组合起来有没有超出图片边界。第三层检查很关键,因为单个值在0到1之间不代表框没有越界,center加半宽可能超过1。参数说明:解决这类问题不能直接全局裁剪,裁剪会改变框的语义,应该先看异常文件分布,确认是单个标注错误还是转换脚本bug,再回到源头修。
4.4 验证集与训练集同源:指标漂亮但换场景就崩
现象:train/val随机划分后mAP刷到0.9以上,部署到新的园区摄像机视角时漏检严重。
原因:同一辆车在连续帧里出现多次,随机划分会把同一辆车同时分进训练集和验证集,模型已经见过验证集的“答案”了。
解决:按拍摄批次分组划分。文件名前缀是批次或拍摄时间的话,直接按前缀分组;否则看是否有伴随的拍摄元数据,再根据元数据里的时间段切片。实在拿不到信息,就先随机划分训练,然后额外收集一组完全没进过数据集的新图做二次验证,以那组新图的结果为准。500张新图的实测数据,比90%数据集的mAP都可信。
4.5 小目标检测率偏低:罐车在远处只有几十个像素
现象:近距离图片检测很好,但远景中罐车只有小汽车大小的时候,mAP暴跌。
原因:默认训练分辨率640,小目标在resize后被压缩成几个像素,特征基本消失。数据集中如果远距离样本占少数,模型学到的就偏向近距离大目标。
解决:训练时把imgsz提高到1280,显存不够就保持640训练、推理时用更高分辨率;或者在推理阶段做切片推理,把大图切成重叠块分别检测后再合并。这个场景下,与其盲目加epochs,不如先检查数据集里小目标样本占比。如果小目标框数量占总数不到10%,就要考虑给远距离样本单独做oversample,否则就算把imgsz调到1920也救不回来。
5. 训练配置与目录组织:dataset.yaml参数设置和train/val划分策略
训练前的最后一公里是把数据集配置文件写好、数据划分做对。YOLO系列对目录结构的要求并不苛刻,核心是约定一致的相对路径和类别顺序。目录组织常见有两种:一种是images/和labels/同级,另一种是整份数据按Annotations和JPEGImages分开保存。CLI训练建议用第一种:images/train、images/val、labels/train、labels/val四个目录。如果你的数据已经是Annotations加JPEGImages的VOC排列,先跑一遍第3章的转换脚本,再把图片和txt分别放进对应的train/val目录。这个动作看起来琐碎,但能避免训练时因为标签路径找不到而报错。
5.1 dataset.yaml配置要点
新建一个hazmat.yaml,内容如下:
train: ../datasets/hazmat/images/train val: ../datasets/hazmat/images/val nc: 2 names: ['oil_tanker', 'gas_transporter']逻辑说明:train和val指向图片目录而不是标签目录,训练时会自动去同名目录里找标签,比如images/train对应labels/train。nc是类别数量,必须和names列表长度一致。names顺序是类别编号的定义,训练和推理时都会按这个顺序读,第3章转换脚本里的classes列表也必须保持同样的顺序,否则类别张冠李戴。参数说明:路径建议用相对路径,换机器时只要保持目录相对关系,就不会出现找不到数据的尴尬;绝对路径一旦目录迁移就失效。
5.2 按批次划分训练集与验证集
划分不能简单random.shuffle,尤其对车辆视频类数据,同一辆车在不同帧反复出现是常态。下面是一段按文件名前缀分组的脚本:
import os import random import shutil def split_dataset(image_dir, label_dir, train_img_dir, val_img_dir, train_label_dir, val_label_dir, train_ratio=0.85): groups = {} for fname in os.listdir(image_dir): prefix = fname.split('_')[0] groups.setdefault(prefix, []).append(fname) for prefix, files in groups.items(): random.shuffle(files) split = int(len(files) * train_ratio) for fname in files[:split]: shutil.copy(os.path.join(image_dir, fname), train_img_dir) label_name = os.path.splitext(fname)[0] + '.txt' label_path = os.path.join(label_dir, label_name) if os.path.exists(label_path): shutil.copy(label_path, train_label_dir) for fname in files[split:]: shutil.copy(os.path.join(image_dir, fname), val_img_dir) label_name = os.path.splitext(fname)[0] + '.txt' label_path = os.path.join(label_dir, label_name) if os.path.exists(label_path): shutil.copy(label_path, val_label_dir) print('训练集图片数量:', len(os.listdir(train_img_dir))) print('验证集图片数量:', len(os.listdir(val_img_dir)))逻辑说明:划分单位是前缀分组而不是单张图片。假设文件名形如scene01_tanker_001.jpg,前缀scene01就代表一个拍摄场景,同一场景的连续帧不会被拆散,验证集里的车在训练集里基本不会出现。这个策略能显著降低数据泄漏导致的指标虚高。参数说明:train_ratio默认取0.85,如果数据集只有3000张,验证集保留15%大约450张,足够评估。labels目录里如果个别图没有txt,脚本会跳过,不会报错。
注意:划分完成后重新统计两个目录的类别分布,如果发现某个类别几乎全落在训练集或验证集,说明文件名前缀和类别有相关性,这个划分方式要调整。
5.3 超参初始建议与训练命令
按数据量3000张、两个类别的情况,初始参数我一般这么给:
| 参数 | 初始建议 | 说明 |
|---|---|---|
| model | yolov8s / yolov5m | 中等模型,避免l或x在3000张数据上过拟合 |
| imgsz | 640起步 | 小目标多再上1280,显存会明显增加 |
| batch | 16或32 | 显存不足就减半,不要改imgsz |
| epochs | 100 | 训练完看曲线再决定是否继续 |
| optimizer | SGD或AdamW | 数据量不大时,SGD配warmup跑得稳 |
| device | 0 | 多卡写0,1 |
训练命令就是标准的YOLO CLI:
yolo detect train data=hazmat.yaml model=yolov8s.pt epochs=100 imgsz=640 batch=16 device=0逻辑说明:YOLO训练会自动创建runs/detect目录保存权重和指标,best.pt和last.pt分别对应验证集最优和最后一轮权重,后面部署取best.pt。参数说明:imgsz抬到1280时,显存占用接近翻倍,16G显存的卡建议batch降到8。数据集里如果小目标占比高,一定优先考虑提高imgsz而不是盲目加模型复杂度,模型太大只会加速过拟合。
6. 训练后验证:从混淆矩阵到实拍推理的参数调整技巧
训练结束后不要只盯着总体mAP看,先打开混淆矩阵图和PR曲线。混淆矩阵的对角线数值代表每个类别的正确识别率,非对角线位置直接指出两类之间的错检方向。前面说的油罐车和天然气运输车串检,会在这个图上一目了然;这种结构性错检靠推理参数解决不了,只能回到数据增强或合并类别。真正可以通过推理参数调节的,是“检出多但误检也多”的情况:把预测置信度阈值从0.25提到0.4,误检明显变少,代价是小目标漏检变多,怎么取舍看实际场景对误检的容忍度。
推理命令里两个参数最值得反复试:
yolo detect predict model=runs/detect/train/weights/best.pt source=test_video.mp4 conf=0.25 iou=0.45 save_txt=Trueconf控制置信度阈值,iou控制NMS时两个框重叠多少算重复。如果同一个油罐车被输出两三个框,把iou往0.5调;如果目标都被滤掉了,优先降conf而不是降iou。小目标多的场景,推理时把imgsz设为1280,或者用切片推理,比调阈值更有效。我一般会先拿一段包含远景、近景、逆光三种情况的视频跑一遍,再根据漏检和误检的分布调整参数。
还有一个我常用的验证习惯:把推理结果txt按帧读取,跟踪同一辆车的检测框中心点坐标,看连续帧间的坐标方差:
import os def track_center(label_dir): prev_center = None for fname in sorted(os.listdir(label_dir)): if not fname.endswith('.txt'): continue with open(os.path.join(label_dir, fname), 'r') as f: for line in f: cls, xc, yc, w, h = map(float, line.strip().split()) if cls == 0: if prev_center: dx = xc - prev_center[0] dy = yc - prev_center[1] print(f'{fname}: 中心点位移 ({dx:.4f}, {dy:.4f})') prev_center = (xc, yc) break逻辑说明:这段脚本读取推理输出的txt,按帧顺序对比同一类别检测框中心点位移。如果位移跳变巨大,说明这一帧检测框不够稳定,可能是模型对该角度或光照下的目标置信度波动大。参数说明:cls按实际类别编号改,如果跟踪的是天然气运输车就改成1。坐标是归一化后的,位移值在0.05以内都算稳定,超过0.1就要注意了。连续帧坐标方差大,说明检测框在抖,部署到需要做轨迹跟踪的项目里会直接影响后端逻辑。
从那以后,我每次拿到目标检测数据集都会强制走一遍:统计类别分布,检查标签越界,按批次划分数据,训练后先看混淆矩阵,再拿一段没进过训练集的实拍视频做连续帧验证。这套流程看起来朴素,但几乎每次都拦下一两个“训练没问题、落地全翻车”的问题。希望帮到你。
本文还有配套的精品资源,点击获取