建筑工地隐患检测的YOLOv5数据集制作与避坑指南
2026/9/23 1:47:53 网站建设 项目流程

简介:面向目标检测与建筑安全管理的YOLOV5格式数据包,聚焦建筑工地安全隐患识别,覆盖安全帽、口罩等10个常见检测类别,适合训练施工现场人员安全监测、违规行为预警等模型。数据图像统一为640×640的RGB图片,已做mosaic增强(每张由四图拼接),可有效提升模型对小目标的泛化能力;整个数据集按yolov5目录结构组织,训练集2605张、验证集196张,每张图片均配有对应的txt标注文件,类别文本信息完整,拿到后可直接用于YOLOv5及其变体训练,无需额外转换或清洗。数据集包含接近真实工地环境的多样场景,四图拼接拓展了单张图像的上下文信息,有助于模型在复杂背景下保持稳定识别。压缩包共2000个文件,主体为1999个txt标签与1个可视化脚本show.py,压缩包大小148.9MB;脚本无需修改即可运行,随机传入一张图片就能绘制边界框并保存到当前目录,方便快速检查标注质量和模型效果。资源已有611人下载学习,适合需要现成工地安全数据集进行模型验证、算法调优或毕业设计开发的初学者和工程师,也能帮助开发者减少数据采集和标注的时间成本。

1. 建筑工地隐患检测的数据集困境:为什么YOLOv5目录格式比算法调参更值钱

拿YOLOv5目录格式做建筑工地安全隐患检测,最常见的误区是以为瓶颈在模型结构上,实际卡在数据上。我之前接过一个工地安全帽项目,客户给的4000多张图片里,光是「中文类名、txt空文件、标注框越界」这几类问题就折腾了两个晚上;等把目录格式和数据划分彻底理顺,YOLOv5的小模型还没调超参,精度就已经能落地了。

这篇想说的是:一套10类别的工地隐患数据集,难的不是拍照和标注,而是把原始标注转换成YOLOv5认可的images/labels双目录、归一化txt和data.yaml。做目标检测的人不少,愿意把数据当工程认真对待的人不多。整理出一套干净的数据集,比调三个月的参数都值钱。适合已经会跑YOLOv5的train.py,但被数据整理和隐藏的坑绊住的研究者或工程同学。

2. 先从10个类别和目录骨架下手:一套能直接喂给YOLOv5的标注规范

10类别听起来不多,但如果类别之间互相打架,标注员会崩溃,模型也会学到错误的相关性。设计类别时我只看三件事:能不能从单帧画面判定、类别之间是否互斥、样本是否容易收集。工地上的安全隐患类别很多,但落到单帧视觉能判定的,其实就那么几类。

2.1 先定10类:隐患类别怎么选才标得快、训得动

建筑工地安全隐患检测,圈内常见的做法是把「人员防护」和「环境危险」分开设计。人员防护类需要模型识别出「人」作为前提,否则「未戴安全帽」这种负类就没有参照物。下面这套10类是我在类似项目里常用的基础集合,覆盖了从佩戴检测到明火扬尘的大部分需求:

id类别名判定标准典型场景
0person只要是人就算任意姿态的工人、行人
1helmet头部戴有安全帽帽体完整覆盖头顶
2no_helmet头部没有安全帽光头、戴草帽或布帽
3vest穿着反光背心背心覆盖躯干
4no_vest未穿反光背心普通T恤或裸身
5smoke烟雾烟囱冒烟、焊接烟尘
6fire明火焊接火花、火堆
7excavator挖掘机等工程机械整车或挖机臂
8fall_zone临边、洞口等危险区楼梯口、屋面边缘
9bare_wire裸露电线线皮破损、私拉乱接

这套设计里最典型的手法是「person + 是否佩戴」的二元结构。如果只标no_helmet不标helmet,模型会把所有「没帽子的头」当正样本,却没机会认识「戴了帽子的头」,训练时正负样本严重失衡。把helmet和no_helmet都作为类别,标注员只需要盯住头,判断标准就清晰了,模型也能学到帽子和头之间的空间关系。这是反复试验后最稳的一种组织方式。

类别交叉的坑也在这里:一张图里同一个头,不能既出现helmet框又出现no_helmet框。标注规范里我会明确写一条——「同一个目标只允许出现在一个类别中,以遮挡更少、特征更完整的那帧为准」。另外,遮挡超过70%的目标不标注,标注框小于12像素的一律丢弃,这些规则看着苛刻,却能省下后面清洗数据的力气。

2.2 YOLOv5目录骨架:同名txt与归一化坐标

YOLOv5目录格式是官方训练流程约定俗成的结构,核心就是images和labels双目录一一对应。我从一开始就会把目录名全部设成英文小写,禁止中文和空格,因为后续在shell里转义、在Windows和Linux之间搬运,空格和中文都是定时炸弹。

site_safety/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml

images里放jpg图片,labels里放同名txt标注文件。比如images/train/camera1_00001.jpg对应labels/train/camera1_00001.txt。图片统一用jpg,一是opencv读取png会比jpg慢一些,二是很多手机拍摄的png带着EXIF旋转信息,转成jpg时顺手把方向矫正掉,能少踩一个坑。

每个txt文件的每一行就是一个标注目标,格式固定为五列:类别id、归一化中心点x、归一化中心点y、归一化宽、归一化高。比如一个安全帽标注出来长这样:

1 0.518229 0.413542 0.093750 0.159028 2 0.618229 0.413542 0.093750 0.159028

第一列的0、1、2是类别id,对应data.yaml里names列表的下标,不是类别名称。后面四个浮点数全部除以图片实际宽高做归一化,取值范围在0到1之间。yolo训练时拿到txt会结合实际加载的图片尺寸去还原框,所以txt里存的必须是比例值,写绝对像素坐标会直接导致训练崩掉。

2.3 data.yaml的写法与路径三种坑

data.yaml是YOLOv5读取数据集信息的入口,内容很短,但坑不少。一套工地数据集的data.yaml长这样:

path: ../site_safety train: images/train val: images/val test: images/test nc: 10 names: 0: person 1: helmet 2: no_helmet 3: vest 4: no_vest 5: smoke 6: fire 7: excavator 8: fall_zone 9: bare_wire

path字段在较新的YOLOv5版本里写法是相对data.yaml文件所在位置的路径;如果你用的版本不认path字段,就把train、val改成绝对路径,例如train: /data/site_safety/images/train。写绝对路径最省心,缺点是换机器要改文件,我一般写相对路径加path,同时把整个数据集目录和YOLOv5仓库放在同一级,路径关系就很好维护。

names列表的顺序一旦固定,后面所有txt里的类别id都对应这个顺序。中途加类别、调顺序,等于所有标注全部重来,这是血泪教训。另一个隐藏坑是names里不要写中文,即使代码能读,控制台输出和日志也会乱码,排查起来非常痛苦。

提示:环境准备建议用conda单独建一个yolov5环境(conda create -n yolov5 python=3.8),把数据集丢进去直接训练。yolov5训练自己的数据集,第一步不是调参,是先保证数据加载不出错。

3. 用三个脚本把零散标注整理成标准数据集:转换、划分与体检

原始标注不可能一开始就是YOLOv5格式。目标检测常用标注工具里,LabelImg保存的是VOC XML,labelme保存的是JSON,Roboflow在线工具导出的格式五花八门。第一步永远是转换,第二步是划分,第三步是体检。这三步都做完,数据才算真正「能训练」。

3.1 把labelme/BBox标注转成YOLO txt:转换脚本与边界裁剪

labelme标注界面灵活,既能画矩形也能画多边形,工地上截取不规则烟雾、明火,用多边形比矩形准得多。但YOLOv5只吃矩形框,所以转换时取多边形外接矩形。下面这段脚本我从第1次做数据集用到现在,改改类别列表就能复用:

import json from pathlib import Path # 顺序必须和data.yaml的names一致,这是铁律 CLASS_NAMES = ['person', 'helmet', 'no_helmet', 'vest', 'no_vest', 'smoke', 'fire', 'excavator', 'fall_zone', 'bare_wire'] def labelme_json_to_yolo(json_path: Path, label_out_dir: Path) -> None: with open(json_path, encoding='utf-8') as f: data = json.load(f) img_w, img_h = data['imageWidth'], data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in CLASS_NAMES: continue cls_id = CLASS_NAMES.index(label) xs = [p[0] for p in shape['points']] ys = [p[1] for p in shape['points']] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 边界裁剪:把拖出画面的框拉回来,避免越界 x_min = max(0, min(x_min, img_w)) x_max = max(0, min(x_max, img_w)) y_min = max(0, min(y_min, img_h)) y_max = max(0, min(y_max, img_h)) w, h = x_max - x_min, y_max - y_min if w <= 1 or h <= 1: # 太小或不可见的框直接丢 continue xc = (x_min + x_max) / 2.0 / img_w yc = (y_min + y_max) / 2.0 / img_h nw = w / img_w nh = h / img_h lines.append(f"{cls_id} {xc:.6f} {yc:.6f} {nw:.6f} {nh:.6f}") if lines: out_txt = label_out_dir / (json_path.stem + '.txt') out_txt.write_text('\n'.join(lines), encoding='utf-8')

脚本逻辑是逐条读取JSON里的shapes,把多边形的所有顶点坐标投影到(0, img_w)(0, img_h)之间,再做归一化。边界裁剪不是可选项:标注员在放大拖拽时很容易把框拖出画布几像素,不裁会导致中心点或宽高的归一化数值大于1或者为负,训练时要么报错要么出现莫名其妙的框。

类别名不在CLASS_NAMES里的标注会被直接跳过。这一步有意为之,防止原始标注里混入了「head」这类和类别表对不上的标签;如果不跳过,这些框会全部落到id=0的person上,整个数据集就被污染了。转换完成后,检查labels目录里的txt数量和json数量对得上,差太多就说明大部分标注框被过滤了,需要回去看标注质量。

3.2 划分train/val/test:按场景分组而不是随机打散

划分数据集是数据工程里最常见的「后悔药」入口。很多教程教随机划分,工地项目这么干会吃大亏。工地数据大多来自摄像头连续视频抽帧,同一个场景的相邻帧长得几乎一样,随机划分时train和val里会混入高度相似的画面,val精度虚高,一换到新工地直接打回原形。

我一般按场景分组:文件名前缀如果是camera1_00001.jpg这种结构,就取camera1作为场景ID,保证同一个摄像头的所有帧只进一个集合。这样才能让val真正模拟「没见过的工地」。

import random import shutil from pathlib import Path SRC_IMAGES = Path('labeled_imgs') # 待划分的jpg目录 SRC_LABELS = Path('labels_all') # 上一步转换出的txt目录 OUT = Path('site_safety') # 数据集根目录 RATIO = {'train': 0.75, 'val': 0.15, 'test': 0.10} groups = {} for img_path in SRC_IMAGES.glob('*.jpg'): scene_id = img_path.stem.split('_')[0] # 取前缀作为场景ID groups.setdefault(scene_id, []).append(img_path) gids = list(groups.keys()) random.seed(42) random.shuffle(gids) num_val = max(1, int(len(gids) * RATIO['val'])) num_test = max(1, int(len(gids) * RATIO['test'])) val_gids = set(gids[:num_val]) test_gids = set(gids[num_val:num_val + num_test]) train_gids = set(gids[num_val + num_test:]) for split_name, gid_set in [('train', train_gids), ('val', val_gids), ('test', test_gids)]: img_out = OUT / 'images' / split_name lbl_out = OUT / 'labels' / split_name img_out.mkdir(parents=True, exist_ok=True) lbl_out.mkdir(parents=True, exist_ok=True) for gid in gid_set: for img_path in groups[gid]: shutil.copy(img_path, img_out / img_path.name) txt_path = SRC_LABELS / (img_path.stem + '.txt') if txt_path.exists(): shutil.copy(txt_path, lbl_out / txt_path.name)

设定随机种子random.seed(42)是为了让划分结果可复现,换台机器重跑能拿到完全一样的train/val集合。比例上train:val:test我取75:15:10,test只在最后评估用,平时训练完全不碰它。

划分的另一个细节是「整个场景一起进val」。假设有两个摄像头对着同一个工地不同角度,它们在画面上完全不重叠,但拍的是同一批工人、同一个危险区,这两个摄像头也算同源场景;条件允许时把同工地、同时段的数据归到同一组,甚至直接用「工地A训练、工地B验证」的跨域验证来评估,比任何随机划分都真实。

3.3 数据体检:用一段脚本扫出空txt、越界框和缺失标注

服务和训练任务排了半天,开机就崩,多数时候不是GPU问题,是数据里有脏文件。第3.3节这个脚本就是用来在训练前把脏数据全部揪出来。它检查三件事:图片有没有同名txt、txt里每一行是不是5列、类别id和归一化坐标是否越界。

def check_split(images_dir: Path, labels_dir: Path, nc: int): problems = [] for img_path in sorted(images_dir.glob('*.jpg')): txt_path = labels_dir / (img_path.stem + '.txt') if not txt_path.exists(): problems.append((img_path.name, '缺少同名txt')) continue lines = txt_path.read_text(encoding='utf-8').strip().splitlines() if not lines: problems.append((img_path.name, 'txt为空')) continue for line in lines: parts = line.split() if len(parts) != 5: problems.append((img_path.name, f'行格式错误: {line}')) break cls_id, xc, yc, w, h = map(float, parts) if int(cls_id) >= nc: problems.append((img_path.name, f'类别id越界: {cls_id}')) break if not (0 <= xc <= 1 and 0 <= yc <= 1 and 1e-6 <= w <= 1 and 1e-6 <= h <= 1): problems.append((img_path.name, f'坐标越界: {line}')) break for img_name, err in problems: print(f'{img_name}: {err}') return problems

体检脚本输出的「缺少同名txt」和「类别id越界」是两类致命伤:前者会让YOLOv5在训练中遇到「图片没有任何标注可加载」,后者会在数据加载时直接抛出IndexError。空txt不致命但也不该留在训练集里,它会让模型把这张图当纯背景处理;如果原图确实有目标,说明漏标了,需要补标而不是删除。

跑完脚本修复数据后,再跑一遍确认problems为空。这一步完成后,目录格式才算真正达到「能直接喂给train.py」的标准。

4. 首轮训练怎么验证数据质量:可视化检查、冒烟训练与增强边界

数据格式化之后,很多人急着开长训练,我建议先花半小时做三件事:画框看标注、跑一次短训练、检查增强设置。这三件事能提前暴露大多数数据问题,比跑完50轮再看结果省时得多。

4.1 标注可视化:把每张图上的框画出来人眼复核

数值体检能查出格式错,查不出语义错。框是不是偏移了、类别是不是标反了、漏标了几个目标,这些问题只有人能看出来。常见做法是写脚本把所有标注框画到图上,按类别抽检,每类随机看20到30个样本就够。

import cv2 import numpy as np from pathlib import Path class_names = ['person', 'helmet', 'no_helmet', 'vest', 'no_vest', 'smoke', 'fire', 'excavator', 'fall_zone', 'bare_wire'] def draw_yolo_boxes(img_path: Path, txt_path: Path, out_path: Path) -> None: img = cv2.imread(str(img_path)) h, w = img.shape[:2] rng = np.random.default_rng(2024) colors = rng.integers(0, 255, size=(len(class_names), 3)) for line in txt_path.read_text(encoding='utf-8').strip().splitlines(): parts = line.split() if len(parts) != 5: continue cls_id = int(parts[0]) xc, yc, bw, bh = map(float, parts[1:]) x1 = int((xc - bw / 2) * w) y1 = int((yc - bh / 2) * h) x2 = int((xc + bw / 2) * w) y2 = int((yc + bh / 2) * h) color = [int(c) for c in colors[cls_id]] cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[cls_id], (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imwrite(str(out_path), img)

画框用的宽高必须是cv2.imread读出来的实际尺寸,不是JSON里记录的尺寸。手机照片经过缩放、旋转后,文件头信息和实际像素可能已经不一致,用原始标注目录里的宽度直接乘坐标是很多踩坑的根源。框的坐标直接从归一化值还原到像素时,记得所有计算用浮点,最后再转int,否则精度损失会让框偏移好几个像素。

在无显示的服务器上,我会把每个类别抽出的检查图拼成一张大图再下载下来看,比一张张翻效率高得多。看到框和物体贴合、类别名称正确,再进行下一步。

4.2 首轮冒烟训练:epochs、batch-size、img该给多大

数据集检查完,第一轮训练我从来不用完整配置,而是跑一次「短冒烟」,目标只是确认数据加载链路通、loss正常下降。常见的完整训练命令长这样:

conda activate yolov5 cd yolov5 python train.py \ --data /data/site_safety/data.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 50 \ --name site_safety_v1 \ --workers 4

冒烟时可以把epochs压到10甚至5,其他参数保持不变,跑完看loss曲线有没有下降趋势。yolov5训练自己的数据集时,最容易翻车的不是模型而是数据加载:Windows下--workers设成大于0经常会导致卡死,我一般在Windows上直接设0,Linux服务器上设4或8。8G显存跑--batch-size 16 --img 640可能会显存溢出,直接降到8或4,别硬扛。

--img 640是性价比最高的默认值。如果工地摄像头是1080p,安全帽这种小目标在原图里可能只有30像素见方,640输入意味着特征图对应的尺寸又会缩小,训练会非常吃力。第一轮先用640把整个流程跑通,后续针对小目标问题再提到--img 1280。显存不够时,1280会把batch-size卡到4甚至2,这时候优先保img而不是batch。

看训练曲线有个简单判断:前10个epoch的box_loss明显下降,说明数据链路没问题;如果loss乱跳不降,先别调模型,回去看标注。训练结束后看val/box_loss比train高多少,差距过大基本就是过拟合,工地小数据集上最常见的现象,后面第5章展开讲。

4.3 数据增强的边界:mosaic、翻转、模糊不能照单全收

YOLOv5默认开启的增强很强,但工地场景有一些增强是「负优化」。我一般遵循一个原则:第一轮全默认,让模型先跑起来;等baseline有了,再逐步收紧增强配置。YOLOv5的增强参数在数据目录对应的hyp文件里,常用几个我会这么设:

增强参数我一般怎么设理由
mosaic0.5小目标多时可以保留,但1.0会让安全帽尺度失真
mixup0.1 或关工地样本重叠少,mixup收益有限
flipud0上下翻转等于把地面变天空,监控场景物理上不成立
fliplr0.5左右镜像在真实画面里常见,安全帽左右对称,可保留
degrees5固定视角监控不宜大角度旋转

mosaic是YOLOv5默认增强里影响最大的一个。它对小目标多、背景杂的工地数据有好处,能把4张图的上下文拼到一起;但mosaic=1.0意味着每张训练图都是拼接图,如果安全帽本身只有二三十像素,拼接后尺度变化太剧烈,模型容易学偏。第二轮训练我习惯把mosaic降到0.5。

上下翻转是另一个容易忽略的点。工地的摄像头全部高架俯视,flipud后的画面里地面在上、天空在下,这种样本在真实部署里永远不会出现,模型学到的是「带天空背景的地面」这种错误关联。YOLOv5默认的hyp里flipud一般是0,但如果你用的第三方配置开了它,建议直接关掉。

5. 避坑:建筑工地安全隐患数据集最容易翻车的5个地方

数据转换和划分本身不难,难的是排错。这一章写的是我做建筑工地隐患检测以来遇到的高频问题,每一条都按「现象、原因、解决」讲清楚,踩到直接照着修。

5.1 中文类别名导致的奇葩报错

现象:训练启动后立刻报UnicodeDecodeError,或者train.py日志里txt读取正常但类别显示乱码,val指标诡异。

原因:names里写了中文,比如安全帽。YOLOv5在数据加载、日志输出、结果绘图多个环节对字符串编码的处理并不一致,Windows控制台默认GBK,Linux默认UTF-8,两边一混就出问题。

解决:names全部用英文小写加下划线。helmetno_helmetfall_zone这类命名既友好又稳定,txt里存的是类别id,本来就不依赖names的显示。如果你已经标完且txt里写的是中文,别指望后续改名,直接重新转换一遍标注。

5.2 图片被翻转或旋转,标注却还是旧坐标

现象:训练loss正常下降,val精度也还行,但可视化检测时框的位置整体偏移,或者框永远贴着物体的一半。

原因:图片在采集后被翻转或旋转了,比如手机自动旋转存图、后期为了「正」把图手动转了一下,但txt坐标还是原图上的坐标。数据链路能读,语义全错了。

解决:在转化标注之前,先统一图片方向,再转换坐标。如果确实对已经标注好的图片做了水平翻转,txt坐标也要同步修正:水平翻转后中心点x坐标变成1 - xc,宽高不变。

# 水平翻转图片后,修正同名txt的坐标 for line in txt_path.read_text().strip().splitlines(): cls_id, xc, yc, w, h = line.split() new_lines.append(f"{cls_id} {1 - float(xc):.6f} {yc} {w} {h}")

这条是最容易发生的「黑匣子」式错误:怎么会loss正常下降?因为框的偏移是系统性的,模型照样能拟合一部分特征,等到了真实场景就原形毕露。每次做完图片变换,抽几张可视化是人眼复核的重点。

5.3 小目标太多,模型学不动

现象:训练50轮,class loss还在0.5以上,安全帽和no_helmet这两类recall始终上不去,val曲线像一条死线。

原因:工地摄像头全是高位俯拍,一个工人全身只有100x100像素,头部可能只有15x15像素。目标检测模型下采样32倍后,15x15的头部只剩不到1个像素,特征几乎全丢。

解决:首先是输入尺寸从640提到1280,小目标特征能保留更多;其次是做图像切片,把1080p原图按2x2切成4张640的图分别训练;第三是控制训练集里「中近景」和「远景」的比例,宁可少放一些远距离小目标,也不要让小目标占据一半以上样本,否则模型被小目标淹没,中近景也学不好。小目标检测本来就是目标检测里的难点,靠数据增强救不回来,从采集和划分阶段就该控制难度分布。

5.4 val精度虚高,换工地就崩

现象:本地val的mAP烧到0.93,模型拿到另一个工地跑真实摄像头,精度掉到0.4以下。

原因:数据集划分时用了随机抽样,同一个摄像头同一时段抽出的连续帧,一部分进了train一部分进了val。模型在val上看到的画面和训练时几乎一样,泛化能力被严重高估。

解决:回到第3.2节的按场景分组划分,用摄像头ID或时间段作为分组键。更进一步,工地上有条件就用「工地A训练、工地B验证」的方式评估,模拟真实部署的场景迁移。val数据一旦划分好就不要动了,增补数据只往train里加,这样才能保证不同版本模型在同一个val上可比。

5.5 空txt、越界框和类别id越界

现象:训练中途报IndexError: index 10 is out of bounds for axis 0 with size 10,或者某个epoch卡在加载阶段不动。

原因:txt里写入了一个类别id=10,但data.yaml里nc=10,合法id只到9。这通常在标注时用了「0到10」的计数习惯,多标了一个类,或者转换脚本里类别表对不上。空txt文件则会让模型把有目标的图当背景训练,越界框会让计算损耗变成负数。

解决:直接跑第3.3节的体检脚本,把生成的问题清单全部过一遍。类别id越界的删除该行;坐标越界的把数值clip到合法范围;空txt连同对应的图片一起移出训练集,另放一个目录人工确认是否漏标。修复后再跑一次体检,直到输出为空为止。数据不脏,训练才不会半路翻车。

6. 让数据集自转起来:半自动标注、badcase回灌与版本管理

数据集做完第一版,训练出了baseline,事情才刚开始。工地场景几个月一变,今天没有明火,明天就可能出现焊接作业;模型在A工地好用,换到B工地可能被塔吊背景干扰。数据是持续投入的工程,不是一次性交付物。

6.1 用训练好的模型做半自动标注

第一版模型再弱,也比人工从零画框快。对新采集的工地图片,用best.pt跑一遍推理,生成的txt直接作为预标注:

python detect.py \ --weights runs/train/site_safety_v1/weights/best.pt \ --source /data/new_frames \ --save-txt \ --conf-thres 0.5

生成的labels在runs/detect对应目录下,人工在标注工具里打开图片时,只做框的微调和增删,而不是从零画。这一步能把标注效率提升两三倍。伪标签直接用是有风险的,conf低于0.5的框基本是误检,宁可不标也不要放进来污染数据。

6.2 用badcase回灌维护长尾场景

线上真实跑起来后,把漏检和误检的图片攒起来,按时间加机位命名归档,每周补标一次,加入train集重新训练。badcase只进train,绝不动val,这样每次改完数据后,拿同一份val评估,能清楚看到改动是正向还是负向。顺手把最终训练的best.pt换到目标检测流程里,整个闭环就转起来了。

6.3 数据版本的后悔药

数据目录改起来很快,改坏一个文件就白干好几天。我会在每次重新标注后,把images和labels打包成一个带版本号的zip存档,至少保留最近三个版本。data.yaml和文件清单一起放进git,改了什么类别、增了什么图片都有记录。没有版本管理的数据集就是一颗定时炸弹,等后悔的时候已经来不及了。

我自己的习惯是每次跑长训练前,先跑一遍第3.3节的体检脚本,再随机抽20张图画框肉眼过一遍,确认没问题才开机。这个习惯帮我避开了至少两次「图片旋转过但标注没同步」的事故。数据工程慢就是快,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询