ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

室内外吸烟检测VOC数据集:从清理标签到YOLOv8训练全流程

室内外吸烟检测VOC数据集:从清理标签到YOLOv8训练全流程 简介这是一份面向计算机视觉初学者、算法工程师及安全监控项目开发者的吸烟检测数据集集中覆盖室内办公、走廊、室外街道等多种真实场景包含一千余张已标注图片。所有图像均配套VOC格式的XML标签文件类别信息清晰可直接用于训练YOLO、SSD、Faster R-CNN等主流目标检测模型也可用于模型精度对比、训练流程验证或论文实验。资源总文件数为2000个其中1507个XML标签文件与486张JPG、7张PNG图像一一对应压缩包整体约862MB目录组织直观便于按图像名称快速检索与划分训练集、验证集。当前已有240人学习下载适合希望跳过数据采集和标注环节、直接进入模型训练的开发者使用尤其适合开展吸烟行为识别、智慧安防巡检等场景的算法落地验证。1. 一千张室内外吸烟检测数据真正值钱的不是数量而是标签规范标题里“1000多张室内室外场景人员吸烟检测数据集voc格式标签”这句话我反复看了几遍想提醒你一个反直觉的点这1000多张图决定它能不能用的不是张数而是VOC格式的标签是否被严格校验过。做过目标检测的人都懂标注数据里十张坏图就能让你训练出来的模型在推理时集体翻车室内外光照差异更会把小目标检测的玄学放大好几倍。这篇笔记会从格式拆解、质量排查、转YOLO到训练调参把一条能复现的路径完整走一遍适合准备做禁烟检测、又在为数据和标签头疼的工程师。2. 把 VOC 格式拆开看目录结构、XML 字段与标注工具的配置VOC 格式是目标检测领域最经典的数据组织方式Faster R-CNN、SSD、YOLO 早期版本都原生吃这套格式。拿到一个声称是 VOC 格式的吸烟检测数据集第一件事不是打开图片看烟头标得准不准而是先核实目录结构完不完整。很多从网上下载或同事交接来的数据集往往只有 JPEGImages 和 Annotations 两个文件夹ImageSets 直接缺失训练脚本一跑就报找不到训练集划分文件。2.1 JPEGImages 与 Annotations先搞清谁是谁的索引VOC 格式的标准布局是三个目录加一个划分文件组JPEGImages 放原始图片Annotations 放与图片同名的 XML 标注文件ImageSets/Main 下放 train.txt、val.txt、trainval.txt 这些纯文本列表。文件名就是索引一张smoking_001.jpg对应一个smoking_001.xml严格同名才认。我见过不少数据集用图片压缩软件批量改名后XML 文件名没跟着改结果训练时一半样本读不出来。# 先跑一遍目录核对缺文件一目了然 cd smoking_dataset find JPEGImages -type f | wc -l find Annotations -type f | wc -l find ImageSets/Main -type f | wc -lfind分别统计三个目录的文件数时如果 JPEG 和 XML 数量对不上先别急着补文件要把差出来的名单打出来看。常见情况是标注遗漏图片存在但没人标另一种可能是重复标注同一张图存在两份 XML。用find列出文件名做 diff把缺的、多的都定位出来这一步是后面所有训练的前提。提示ImageSets/Main 下的 txt 文件里每行是文件名主部不带 .jpg 也不带 .xmlYOLO 的转换脚本和训练脚本都依赖这个约定不要自己在 txt 里加扩展名。2.2 XML 里真正有用的五个字段VOC 的 XML 结构不复杂但字段含义容易被忽略。外层是annotation根节点里面filename对应图片名size记录宽高和通道数object标签块里是目标的类别名和bndbox坐标。真正决定训练效果的是bndbox里 xmin、ymin、xmax、ymax 四个值它们必须和图片像素坐标系一致。字段作用检查重点filename关联图片与 JPEGImages 下文件名完全一致size/widthsize/height图片真实尺寸与图片文件头一致不能是标注工具的缩放窗口尺寸object/name类别名全数据集统一不能“smoking”和“smoke”混用bndbox/xmin目标左边界大于 0小于 xmaxbndbox/ymax目标下边界不超过图片高度解析 XML 时我习惯用xml.etree.ElementTree一个容易踩的细节是有些标注工具会把truncated、occluded、difficult这些字段省略而不是显式写 0。解析脚本里如果不做get()带默认值直接取find(difficult).text就会抛 NoneType 异常。更隐蔽的是size里的值如果是从旧文件复制的和图片实际宽高差一点点转成 YOLO 归一化坐标后整个框的位置就偏了。import xml.etree.ElementTree as ET tree ET.parse(Annotations/smoking_001.xml) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) for obj in root.findall(object): name obj.find(name).text bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) print(name, xmin, ymin, xmax, ymax)这段代码是标准的 XML 读取流程width和height是从 XML 里取的值后面所有坐标校验都依赖它们。常见做法是把这一步封装成独立函数因为后续转换脚本还要复用。需要特别注意的是xmax和ymax的取值惯例LabelImg 这类工具习惯从 1 开始计数但 VOC 官方评测代码按像素起始 0 处理转换成 YOLO 时统一按 0 起算避免坐标整体偏移一个像素。2.3 用 LabelImg 打开 1000 张图之前先改这三个配置如果数据集的标注需要补漏或修正LabelImg 是处理 VOC 格式最顺手的工具。安装完成后别急着打开图片先改三处配置否则后面每标一张图都想砸键盘。第一在Save菜单里选PascalVOC格式默认可能是 YOLO 格式存出来的是一堆 txt和 VOC 的 XML 对不上。第二在View里打开Auto Saving自动保存能让你标完一张就切下一张不会因为手滑丢标签。第三如果数据集只有一个类别在标注前把标签写成一个classes.txt别看它简单多人协作时命名不一致的问题多半出在这里。还有一个容易忽略的点LabelImg 的默认界面里有个difficult标志位如果你在标注时不小心勾上生成的 XML 里就会有difficult1/difficult。后面训 YOLO 时如果你的转换脚本忽略了困难样本这些框直接被丢弃数据量和类别分布就变了。对吸烟检测来说远处模糊的烟头恰恰是最需要保留的困难样本我一般不建议跳过。启动 LabelImg 的命令也简单但要在 Python 环境里先装好依赖库# LabelImg 基于 PyQt5安装后直接启动 pip install labelImg labelImg . --classes classes.txt第二个参数--classes指定类别文件启动后左侧标签列表里就只有你定义好的类别避免手输标签时打出smoking、Smoking、吸烟这种不一致的名字。labelImg .后面的点表示打开当前目录它会自动加载 JPEGImages 下的图片和 Annotations 下的 XML。2.4 室内外样本差异为什么室外图 300 张就够室内却要多备同样是“吸烟检测”室内和室外两个场景的视觉特征差异巨大。室内环境以荧光灯、白炽灯为主光照稳定但背景里白墙、瓷砖、玻璃反光多烟头发出的红光在浅色背景下对比度反而低。室外则是逆光、树影、黄昏、夜晚霓虹灯轮番上场烟头这种只有十几个像素的小目标在强逆光下很容易和背景融为一体。数据分布上一个相对稳妥的比例是如果总数据量只有 1000 多张室内约占六到七成室外占三到四成室外里再刻意补一些黄昏和夜晚的样本。否则模型训练完后大概率出现室内准确率尚可、室外漏检率飙高的情况。标注时也要按场景区别对待室内图把 bbox 卡紧烟支本身别把手指、嘴唇框进来室外逆光图允许适当放宽边界因为烟头边缘本身就不清晰框小了反而给模型制造噪音。3. 标注质量排查五条能把训练直接带翻车的坑拿到数据集先别高兴VOC 格式只能说明标签长那个样不代表标签是对的。我这几年代码敲下来一半的训练事故不是模型结构问题而是数据里的暗伤。以下五条按杀伤力排序每一条都是先给现象再讲原因最后给解决办法照着排查一遍能省下后面好几个调参的夜晚。3.1 文件名大小写不一致数据在 Windows 好好的到 Linux 训练就丢一半现象训练脚本报 FileNotFoundError报错文件是smoking_014.jpg但目录里明明躺着SMOKING_014.JPG。或者干脆不报错但训练 loss 降不下去因为数据加载时跳过了一批图片有效样本少了一截。原因VOC 格式里图片是 jpgXML 文件名却是 .JPG 或 .JPEG 混用。Windows 文件系统大小写不敏感资源管理器能正常打开Linux 严格区分大小写转换脚本和训练框架按小写去找文件就匹配不上。这个问题在多人协作、跨平台拷数据时最常见。解决写个几行的脚本统一把文件名转成小写并统一扩展名。注意顺序先改图片文件名再改 XML 里的filename字段最后用 XML 文件名反向核对一遍。import os, glob # 先把所有 jpg 统一成小写 .jpg避免 .JPG/.jpeg 混用 for path in glob.glob(JPEGImages/*): directory os.path.dirname(path) filename os.path.basename(path) new_name filename.lower().replace(.jpeg, .jpg) if new_name ! filename: os.rename(path, os.path.join(directory, new_name)) # 再改 XML 里的 filename 节点保持索引一致 import xml.etree.ElementTree as ET for xml_path in glob.glob(Annotations/*.xml): tree ET.parse(xml_path) root tree.getroot() old root.find(filename).text new old.lower().replace(.jpeg, .jpg) root.find(filename).text new tree.write(xml_path, encodingutf-8)这段脚本先把图片名统一成小写再把 XML 里的filename同步改成新名字。换完之后养成一个习惯拿 XML 文件名列表当作准绳逐个检查对应图片是否存在因为标注过程中丢掉一张图很容易补起来却很麻烦。3.2 XML 的size和图片实际尺寸对不上bbox 全被拉偏现象模型训练出来预测框整体往右下或左上偏移尤其是大目标偏得更明显小目标的框干脆飘到物体旁边。原因标注员在 LabelImg 里打开图片时如果系统缩放显示工具记录的size是基于原始文件还是显示窗口因版本而异。更常见的是图片在标注后被批量压缩过、裁剪过但 XML 没同步更新size和坐标。归一化时宽度和高度用错自然全盘错位。解决转换前做一次批量比对用 OpenCV 读图片真实尺寸和 XML 里的size对照不一致的直接报出来。import cv2, glob, xml.etree.ElementTree as ET for xml_path in glob.glob(Annotations/*.xml): tree ET.parse(xml_path) root tree.getroot() img_path os.path.join(JPEGImages, root.find(filename).text) h, w cv2.imread(img_path).shape[:2] xml_w int(root.find(size/width).text) xml_h int(root.find(size/height).text) if (w, h) ! (xml_w, xml_h): print(f{xml_path}: xml size {xml_w}x{xml_h}, real {w}x{h})打印出来的每一行都是一张需要重新导出的图。处理方式看时间成本错得不多就改size然后按比例缩放 bbox 坐标错得离谱就直接删掉这张样本1000 张数据里去掉几张坏图比带着错误坐标训练更划算。3.3 烟头只有 12 个像素小目标直接超出 YOLO 默认 anchor 的感知范围现象mAP50 数值还行mAP50-95 一路走低推理时近处的烟能框住画面里两三米外的烟头直接漏掉。原因YOLO 系列骨干网络下采样 32 倍一张 640 像素宽的图在特征图上只有 20 像素宽。烟头在原始图上可能才 12 像素落到特征图上不足 1 像素检测头根本感知不到。VOC 格式本身没有小目标专属标注全看原始抽烟标注时有没有把这种小框标出来。解决训练时把输入分辨率提到 1280相当于给模型一张更大尺寸的图来放大微小烟头或者对原始图片做滑窗切块把大图切成若干 640 小图再训练。前者显存占用翻倍后者训练时间变长但小目标召回率通常能明显改善。数据层面还要确认训练集里有没有大量把“手夹着烟”整个框进去的标注框越大模型越学不到烟头本身的纹理。3.4 把“烟”和“吸烟”混成一类模型学出来的是“手指夹物”现象测试集里有人拿笔、拿筷子甚至拿手机模型也报 smoking置信度还不低。原因标注规则没定义清楚。VOC 格式允许标注任意语义但算法不区分“烟”和“吸烟”它学的是标注框内所有像素的共性。如果框里既包含烟头又包含手指、嘴唇模型记住的其实是“手指夹一截浅色物体”的组合特征而不是“烟头燃烧”的特征。解决把类别名收敛成一个全数据集统一写smoking并且只框烟头或烟支本身绝对不要把整只手框进去。如果场景里需要区分点燃和未点燃的烟那就开两个类smoking和cigarette不要用同一个类硬扛。改完标签后要重新检查一遍类别文件和 XML 里的 name 字段。3.5 数据划分没固定随机种子验证集在作弊现象训练时 loss 曲线很漂亮跑完测试 mAP 却对不上号同一套参数一次比一次好一次比一次差。原因数据集划分脚本用了random.shuffle但没设random.seed(42)每次跑出来的 train.txt 和 val.txt 都不一样。最严重的时候一张图的增强版本或者同一个场景的连拍图被同时分进训练集和验证集相当于训练时已经见过了“答案”。解决划分脚本里先random.seed(42)划分完打印训练集和验证集的类别统计确认每个 set 里都有足够多的阳性样本。更稳妥的做法是直接按 ImageSets/Main 下已有的 trainval.txt 和 val.txt 划分不重新造轮子。检查一下 val.txt 里的图片名和训练集有没有交集一行命令的事别让玄学背这个锅。4. 把 VOC 转成 YOLO 格式转换脚本与四个边界坑YOLO 训练不认 XML只认每个图片对应一个.txt每行写类别 id、归一化后的中心点坐标。转换脚本本身不难真正的坑全在边界条件里。这一章先给可抄的脚本再逐个讲清楚容易出错的地方。4.1 先定目录骨架训练前把数据落成 YOLO 默认结构YOLO 默认的数据组织方式是一个父目录下分images和labels两个子目录各自再按 train、val 细分。这样训练脚本的配置最简单不会因为路径层级问题多绕弯。# 以 smoking 为项目目录建立 images/labels 下的 train/val 结构 mkdir -p smoking_dataset/images/train smoking_dataset/images/val mkdir -p smoking_dataset/labels/train smoking_dataset/labels/val四行命令建好基础目录。VOC 的 JPEGImages 全部对应训练集时就直接把图片复制到 images/train 下如果有 val 划分按 val.txt 里的文件名主部去复制对应图片。labels 目录的结构要和 images 完全一致否则训练时找不到标签文件。4.2 XML 转 txt坐标归一化脚本核心转换逻辑是读 XML 里的 bndbox 坐标除以图片宽高得到 0 到 1 之间的归一化值类别 id 按你定义的类别顺序从 0 开始。import os, glob import xml.etree.ElementTree as ET # 类别顺序要固定这个顺序会写进数据集的 yaml 文件 class_names [smoking] class_to_id {name: idx for idx, name in enumerate(class_names)} def convert_xml_to_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() size root.find(size) width int(size.find(width).text) height int(size.find(height).text) with open(txt_path, w) as f: for obj in root.findall(object): # 跳过 difficult 样本默认不跳过则删掉这行 if obj.find(difficult) is not None and int(obj.find(difficult).text) 1: continue name obj.find(name).text if name not in class_to_id: continue bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 坐标越界保护避免 xmax 超过图片宽度 xmin max(0, min(xmin, width)) xmax max(0, min(xmax, width)) ymin max(0, min(ymin, height)) ymax max(0, min(ymax, height)) center_x (xmin xmax) / 2 / width center_y (ymin ymax) / 2 / height box_w (xmax - xmin) / width box_h (ymax - ymin) / height f.write(f{class_to_id[name]} {center_x:.6f} {center_y:.6f} {box_w:.6f} {box_h:.6f}\n) for xml_path in glob.glob(Annotations/*.xml): filename os.path.basename(xml_path).replace(.xml, .txt) convert_xml_to_txt(xml_path, os.path.join(labels/train, filename))class_to_id这个字典决定了类别 id 的映射smoking是 0如果后续加了cigarette就是 1顺序必须和训练时 yaml 里 names 列表保持一致。坐标归一化时把中心点、宽、高都除以图片宽高得到 0 到 1 的浮点数YOLO 训练脚本读的就是这种格式。越界保护那段max和min是我后来加上去的因为你永远不知道标注工具会生成什么样的小数坐标防止 xmax 比 width 大 1 个像素这种问题直接让训练崩溃。参数说明convert_xml_to_txt函数接收 XML 路径和输出 txt 路径脚本主体遍历所有 XML 后写入 labels 目录。生成的 txt 和原始图片同名只是扩展名不同。4.3 转换结果自检把 YOLO 标签画回图上转换完别急着训练先画回去看一遍。很多人省了这一步结果训练过程中发现框全画在天上回头看是某个 XML 的尺寸字段是错的。这个自检脚本一次能把所有 txt 的框画到图片上攒成一张大图扫一眼。import cv2, os, glob def draw_yolo_boxes(img_path, txt_path, class_names, output_path): img cv2.imread(img_path) h, w img.shape[:2] if not os.path.exists(txt_path): cv2.imwrite(output_path, img) return with open(txt_path) as f: lines f.readlines() for line in lines: parts line.strip().split() cls_id, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) xmin int((cx - bw / 2) * w) ymin int((cy - bh / 2) * h) xmax int((cx bw / 2) * w) ymax int((cy bh / 2) * h) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(output_path, img) os.makedirs(check_visual, exist_okTrue) for txt_path in glob.glob(labels/train/*.txt): img_path os.path.join(JPEGImages, os.path.basename(txt_path).replace(.txt, .jpg)) draw_yolo_boxes(img_path, txt_path, class_names, os.path.join(check_visual, os.path.basename(img_path)))关键是最后一步用 OpenCV 把归一化坐标还原成像素坐标再画矩形这一步能直观看到标签是否贴住烟头。如果你发现有些 box 特别大把整个人都框进去了不是脚本错是原始标注的框就不准得回去改 XML。4.4 四个边界坑difficult、翻转、空标签、类别 id 重排第一个坑是 difficult 和 truncated 字段。以前在 VOC 时代这两个字段用于区分困难样本和截断目标YOLO 转换时要不要跳过看你的场景。吸烟检测里远处的不清晰烟头本身是困难样本舍弃会削弱模型的远距离检测能力所以我的建议是修正这些框的位置而不是直接删掉。第二个坑是数据增强里的图片翻转。YOLO 训练时开启 mosaic 和随机翻转后坐标由训练框架自己重算没问题但如果有人在数据层面先做了水平翻转增强翻转后的图片必须对应翻转后的 txt不能用原来的坐标。VOC 转 YOLO 之后增强脚本和训练时数据加载的翻转逻辑可能会叠加导致模型看到的框错位。第三个坑是空标签问题。如果某张图确实没有目标或所有目标都被过滤掉生成的 txt 文件就是零字节。YOLO 默认会跳过这种图片不参与训练如果你期待“负样本”参与训练需要在 yaml 里额外配置或者把这类图单独放一个背景类。常见做法是把空标签文件直接删掉避免训练框架报警。第四个坑是类别 id 重排。VOC 类别名是字符串YOLO 只认数字。如果你从别人那里拿到的数据集自带的labels.txt顺序是smoking在前而你转换脚本里 class_names 把smoking放在第二个位置id 就全错了。每个转换脚本的 class_names 要和最终训练 yaml 里的 names 完全一致这是最容易在交接时埋雷的地方。5. 用 YOLOv8 跑通吸烟检测最小训练流程数据格式处理好之后剩下的就是训练。YOLOv8 是目前把配置和训练命令封装得最省心的检测框架一个.yaml加一行命令就能跑起来。这一章给的是最小可用方案每个参数都代表一个你需要做决策的点。5.1 配置数据集 yaml路径用绝对还是相对YOLOv8 的数据配置用 YAML 文件描述三个键path 指向数据集根目录train 和 val 分别指向训练集和验证集图片目录的相对路径或绝对路径。# smoking.yaml path: /data/smoking_dataset train: images/train val: images/val names: 0: smoking写完 yaml 检查一遍path写成绝对路径最稳妥尤其用脚本训练的时候千万别用相对路径然后被当前工作目录坑了。names的键值映射要和转换脚本里 class_names 的顺序一致这里只有 0 对应 smoking如果以后加了类需要同步修改 yaml 和转换脚本。5.2 加载预训练权重跑最小训练命令YOLOv8 的预训练权重在首次使用时自动下载训练命令简洁但参数作用必须心里有数。yolo detect train \ datasmoking.yaml \ modelyolov8n.pt \ imgsz1280 \ epochs100 \ batch16modelyolov8n.pt是 nano 版本预训练权重参数最少、速度最快适合先跑通流程。imgsz1280前面说过对烟头这种小目标很重要代价是显存占用变成 640 分辨率下的四倍。epochs100对 1000 多张的数据集来说稍微偏多配合后续的早停策略可以接受。batch16在 1280 分辨率下大概需要 16GB 以上显存显存不够降到 8 或 4不要硬撑。注意如果第一次训这类数据集先跑通流程再谈优化。模型和参数可以在训完一轮后换大版本、加分辨率数据格式的问题越早暴露越省钱。5.3 四个必调参数imgsz、batch、mosaic、close_mosaic除了上面的基础命令YOLOv8 训练还有几个参数是针对室内外混合数据特别值得调的。mosaic1.0默认开启马赛克增强把四张图的随机区域拼成一张训练图对提升泛化能力很有帮助。但马赛克会缩放大图中每个目标的尺寸烟头这种小目标经过拼接和缩放经常缩得更小甚至消失造成模型对小目标更不敏感。常见做法是保留 mosaic 但降低概率比如mosaic0.5让模型一半时间看真实尺寸的图。close_mosaic10表示训练最后 10 个 epoch 关闭马赛克增强让模型适应真实的物体尺寸分布。这个参数我建议从 10 开始如果 loss 曲线在最后阶段抖得厉害就增加到 15。degrees0控制旋转增强的角度范围。吸烟检测场景里烟头本身有各种朝向但如果标注框是正矩形旋转角度太大会让框里混进大量非目标像素建议先保持degrees0训完一轮再决定要不要开。hsv_h、hsv_s、hsv_v控制色调饱和度亮度的扰动范围。室内外的光照差异主要落在亮度通道适当调大hsv_v0.6让模型对暗光、逆光更鲁棒代价是收敛变慢一点。yolo detect train \ datasmoking.yaml \ modelyolov8n.pt \ imgsz1280 \ epochs100 \ batch16 \ mosaic0.5 \ close_mosaic10 \ hsv_v0.6 \ patience20patience20是早停参数连续 20 个 epoch 验证集指标不提升就自动停止。这组参数组合是我在类似小目标场景里的常用起点具体数值要看着 loss 曲线微调。训完看runs/detect/train/目录下的results.csv里面记录了每个 epoch 的 box_loss、cls_loss 和验证集指标。5.4 训练过程看什么loss 曲线哪个信号要先停训练不是等它跑完就完事每轮结束后要瞄一眼两个信号。第一个是 box_loss 和 cls_loss 的下降曲线如果 cls_loss 在某个 epoch 后不再下探甚至反弹说明类别学够了再训会记住背景噪音。第二个是 val/box_loss 和 train/box_loss 的差距如果差距越拉越大明显过拟合这种时候提前停掉用验证集指标最好的那轮权重。吸烟检测是安全监控类用途评价指标上我习惯主要看召回率。漏掉一次真实吸烟行为比多报一次误检严重所以验证时别只盯着 mAP把 Precision 和 Recall 看全。YOLOv8 训练结束后会在runs/detect/train/下生成confusion_matrix.png里面能清晰看到烟头被分到了哪个类别、有没有大量和背景混淆。6. 室内外分开验证与半自动标注扩数据训完模型先别急着部署把验证集按室内和室外分开各跑一遍推理你会发现两个场景的 mAP 可能差十个百分点以上。这个差异才是后续数据扩充方向的依据。6.1 把测试集按室内室外拆开一张图看穿泛化缺陷在测试图片的命名或元数据里提前打上indoor、outdoor的标记推理时分别统计。室外组如果漏检集中在远距离小烟头就重点补远距离样本室内组如果误检集中在白色反光区域就补充反光背景的负样本。这一步比你闷头调参数效率高得多因为数据缺口往往是模型上限的天花板。6.2 用半自动标注把 1000 张扩到 5000 张训练的模型稳定后用它去标注新增图片输出一套 yolo 格式标签人工只修正框的位置和漏检的样本。这个流程能把你从标注地狱里捞出来重点补三类图室内夜晚、室外逆光、镜头远端的烟头。半自动标注产出后重新走一遍校验脚本把置信度低的框全部人工重标。# 用训练好的模型批量推理新图片输出 YOLO 格式标签供人工修正 from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(sourcenew_capture/, imgsz1280, conf0.25, save_txtTrue, save_confTrue)save_txtTrue会把预测框写为 YOLO 格式的 txtsave_confTrue额外保留置信度。人工修正时优先看置信度低于 0.5 的框和漏检的大图块修正完再和原标注合并生成一份更大的训练集。做这类项目时我最大的教训是花三天修标签胜过花三周调模型参数。数据里的脏标签不清理后面所有训练都是拿玄学当工程。室内外场景差异已经够模型喝一壶了别再让标注错误掺一脚。这 1000 多张 VOC 数据跑通流程后你会更清楚下一批数据该补什么而不是盲目堆数量。希望帮到你。本文还有配套的精品资源点击获取
返回列表