ARTICLE DETAIL

资讯详情

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

红外多目标检测数据集与YOLO训练实战:标签格式转换与避坑指南

红外多目标检测数据集与YOLO训练实战:标签格式转换与避坑指南 简介面向红外场景目标检测任务的数据集包内含5000张真实场景红外图像全部由LabelImg软件标注标注框质量高。提供VOC、COCO、YOLO三种格式标签并分别存放在不同文件夹可直接接入YOLO系列训练流程适用于夜间、低光照等条件下的多目标检测研究。压缩包内共包含2000个文件以xml标签文件占绝对主体1986个另含6个HTML格式的YOLO环境搭建与训练教程、3个Python划分脚本和5个txt文件整体约203MB。附带训练集/验证集/测试集划分脚本可自由生成ImageSets下的划分文件方便组织数据教程覆盖Linux与Windows版本支持参考案例修改后训练自定义数据集数据量充足可直接用于模型训练标签文件按格式分目录存放便于直接替换使用。已有208人浏览学习适合需要红外多目标检测数据或希望快速上手YOLO训练的中高级开发者。1. 红外多目标检测从数据集到YOLO训练落地的关键环节自动驾驶夜间感知、工业热成像检测、安防监控这类场景里可见光数据训练出来的模型一碰到红外图像就明显掉点原因不在网络结构而在数据分布完全不同。手上这套YOLO红外多目标检测数据集包含5000张真实场景红外图片并用LabelImg完成标注同时给出了VOC(xml)、COCO(json)、YOLO(txt)三种格式标签还附带训练集/验证集/测试集划分脚本和Windows、Linux双平台的环境搭建与训练教程。对正在做红外目标检测项目、想快速跑通YOLO流程的工程师来说最直接的价值就是省掉自己采集红外图像和反复折腾格式转换的几天时间。这篇文章会拆解三种标签格式的差异、划分脚本的执行逻辑以及把整个数据集跑进YOLO训练流程时容易踩的坑。2. VOC、COCO、YOLO三种标签格式的差异与转换原理2.1 三种格式的存储结构与适用场景拿到数据集后你会发现标签文件分布在三个文件夹里分别是.xml、.json、.txt后缀。这不是冗余而是对应不同训练框架的输入要求。VOC格式基于PASCAL VOC标准每个标注对象用一个XML文件描述包含annotation根节点、object块和bndbox坐标坐标是像素绝对值适合被人直接阅读和调试。COCO格式则把所有标注汇总进一个JSON文件用images、annotations、categories三个数组组织坐标用[x_min, y_min, width, height]表示适合pycocotools评估和MMDetection、Detectron2这类框架。YOLO格式则是原生为YOLO系列设计的TXT文件每行对应一个目标class_id center_x center_y width height其中坐标全部归一化到0-1区间训练时不需要再额外做坐标转换这也是Ultralytics YOLO仓库默认读取的方式。格式存储形式坐标表示对应典型框架VOC每张图一个XML像素绝对值 xmin, ymin, xmax, ymax老版SSD、Faster R-CNNCOCO一个JSON文件像素绝对 x, y, width, heightMMDetection, Detectron2YOLO每张图一个TXT归一化 x_center, y_center, w, hUltralytics YOLO这套数据集已经把同一批图片分别用三种格式标好意味着你可以直接喂给任意的YOLO系列模型或者在切换框架时不用重新标注。训练脚本里最省事的做法是直接用YOLO格式因为Ultralytics的data.yaml只需指定图片和标签目录即可不需要额外解析逻辑。而COCO格式常用于需要mAP评估的严格对比实验VOC格式则方便人工抽查标注质量。三种格式并存本质上是为了让同一份标注成本能覆盖不同工具链。2.2 类别映射与标注一致性检查在动手转换前先要确认类别ID的顺序。VOC的XML里写的是类别名称字符串而YOLO的TXT里是数字IDCOCO的JSON里既有类别名称又有ID。如果直接拿别人的YOLO标签文件去匹配自己的类别列表很可能出现“第0类是车你的第0类是行人”的错位。所以建议先把类别名导出核对三个格式的对应关系。常见做法是写一个小的Python脚本统计所有标签的类别集合import os import xml.etree.ElementTree as ET xml_dir Annotations target_classes set() for root, _, files in os.walk(xml_dir): for f in files: if not f.endswith(.xml): continue tree ET.parse(os.path.join(root, f)) for obj in tree.findall(object): target_classes.add(obj.find(name).text) print(总类别数:, len(target_classes)) print(类别列表:, sorted(target_classes))这段脚本遍历VOC标签目录把每个object节点里的name字段收集到一个集合里输出就是这份红外数据集的全部类别名。注意如果你的XML里存在同一张图多个目标脚本会全部收录如果遇到类别命名带空格或中文后面对应到YOLO ID时要显式处理不要直接当索引。除了类别名还要检查坐标是否越界。LabelImg在某些情况下会把目标画到图像边缘外几像素XML里会出现xmax width的情况。训练时这类坐标会导致anchor匹配异常轻则损失不降重则出现NaN。检查方法很简单读取XML的size节点再逐个比较bndbox四个值是否在图像尺寸范围内越界的可以先裁剪回图像边界或者直接删除该目标。2.3 VOC格式转YOLO格式的代码与参数说明虽然数据集自带YOLO标签但在实际项目中你经常需要把自己额外标注的红外图片合并进来这时候就要自己做转换。这里给出一个最常用的VOC转YOLO的脚本核心是坐标归一化import os import xml.etree.ElementTree as ET class_to_id {car: 0, person: 1, truck: 2, bike: 3} # 自定义映射 def convert_bbox(size, box): x_min, y_min, x_max, y_max box dw 1.0 / size[0] dh 1.0 / size[1] x_center (x_min x_max) / 2.0 y_center (y_min y_max) / 2.0 w x_max - x_min h y_max - y_min return x_center * dw, y_center * dh, w * dw, h * dh xml_dir Annotations txt_dir labels os.makedirs(txt_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, xml_file)) root tree.getroot() size root.find(size) w_img int(size.find(width).text) h_img int(size.find(height).text) txt [] for obj in root.findall(object): cls obj.find(name).text bbox obj.find(bndbox) box [float(bbox.find(xmin).text), float(bbox.find(ymin).text), float(bbox.find(xmax).text), float(bbox.find(ymax).text)] cx, cy, bw, bh convert_bbox((w_img, h_img), box) txt.append(f{class_to_id[cls]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(os.path.join(txt_dir, xml_file.replace(.xml, .txt)), w) as f: f.write(\n.join(txt))这段脚本的关键是convert_bbox函数先把绝对坐标换算成中心点和宽高再分别除以图片宽高进行归一化。注意XML中的坐标如果是浮点数也能直接转但数据集里LabelImg生成的都是整数。输出保留6位小数对YOLO训练来说精度足够。class_to_id的映射需要和之后训练用的data.yaml完全一致否则标签对不上目标。提示转换后随机抽几张图用OpenCV或LabelImg回读一次确认框位置与原图吻合避免坐标顺序或归一化方向错误。2.4 COCO格式转YOLO格式的注意事项如果某个下游工具只提供COCO格式的标注而你想用Ultralytics YOLO训练同样需要转换。COCO的JSON结构比XML复杂一个常见错误是混淆了bbox的存储方式——COCO里存的是[x, y, width, height]不是[x_min, y_min, x_max, y_max]。转换时需要先做一次加法得到右下角坐标再套用归一化公式。一个最小可用的转换片段如下import json with open(annotations.json) as f: coco json.load(f) img_id_to_info {img[id]: img for img in coco[images]} cat_id_to_idx {cat[id]: i for i, cat in enumerate(coco[categories])} for ann in coco[annotations]: img_info img_id_to_info[ann[image_id]] w_img, h_img img_info[width], img_info[height] x, y, w, h ann[bbox] x_center (x w / 2) / w_img y_center (y h / 2) / h_img norm_w w / w_img norm_h h / h_img class_idx cat_id_to_idx[ann[category_id]] # 写入对应TXT文件文件名与图片同名这段代码把annotations数组里每个标注框单独处理输出TXT时需要按image_id把多个目标聚合到同一个文件中。特别注意category_id在COCO里不连续的情况所以用cat_id_to_idx做了一个重映射把原始ID压缩到从0开始的连续整数。如果直接拿原始category_id当YOLO类别编号会造成类别ID空洞训练时类别数是错的。3. 数据划分脚本训练集/验证集/测试集的划分逻辑与实操3.1 三套划分脚本分别解决什么问题这个压缩包里提供了三个划分相关的脚本一个划分训练集、验证集、测试集并写入新文件夹一个只划分训练集和验证集还有split_train_val生成ImageSets下txt文件划分脚本.py生成VOC风格的ImageSets/Main下的txt文件。很多初学者一开始不理解为什么要脚本直接手动把图片和标签按比例拖进文件夹但一旦数据集达到几千张手拖极容易出错而且无法保证每次实验的划分一致。脚本的意义在于“从同一份全量数据中按固定随机种子切分”这样两次训练的比较才是公平的。以第一个脚本为例它的典型行为是读取全量图片文件名随机打乱后按比例切出三段然后把图片和对应的.txt或.xml标签复制到train/、val/、test/三个新目录每个目录下图片和标签保持相同的子文件结构。这个逻辑适用于YOLO格式因为YOLO训练时需要图片路径和标签路径一一对应。第三个脚本生成ImageSets/Main下的txt文件是VOC风格训练工具常用的输入格式。这类txt每一行是图片文件名不带扩展名本身不复制图片只改变数据集的组织索引。如果你使用的是老式Darknet YOLO或者需要生成train.txt、val.txt路径列表这种脚本更方便。我在实际项目中一般会把两个脚本都跑一遍先用第三个Script生成路径列表再用第一个脚本生成物理隔离的目录结构这样无论切换到哪种训练框架都有现成文件。3.2 执行划分脚本的参数与含义由于压缩包内脚本是独立.py文件没有统一命令行入口我会按常见做法组织成可调用函数。假设你希望把数据集按7:2:1划分为train/val/test并让结果可复现可以直接执行下面的代码框架python split_train_val_test.py \ --image_dir images \ --label_dir labels \ --output_dir dataset \ --ratios 0.7 0.2 0.1 \ --seed 42对应的split_train_val_test.py核心实现如下import os import shutil import random import argparse def split_dataset(image_dir, label_dir, output_dir, ratios, seed42): random.seed(seed) images sorted([f for f in os.listdir(image_dir) if f.endswith((.jpg, .png))]) random.shuffle(images) n_train int(len(images) * ratios[0]) n_val int(len(images) * ratios[1]) splits [ (train, images[:n_train]), (val, images[n_train:n_trainn_val]), (test, images[n_trainn_val:]), ] for split_name, files in splits: img_out os.path.join(output_dir, split_name, images) lab_out os.path.join(output_dir, split_name, labels) os.makedirs(img_out, exist_okTrue) os.makedirs(lab_out, exist_okTrue) for img_name in files: shutil.copy(os.path.join(image_dir, img_name), os.path.join(img_out, img_name)) base os.path.splitext(img_name)[0] lab_src os.path.join(label_dir, base .txt) if os.path.exists(lab_src): shutil.copy(lab_src, os.path.join(lab_out, base .txt)) else: print(f警告: {lab_src} 缺失) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--image_dir, requiredTrue) parser.add_argument(--label_dir, requiredTrue) parser.add_argument(--output_dir, requiredTrue) parser.add_argument(--ratios, nargs3, typefloat, default[0.7, 0.2, 0.1]) parser.add_argument(--seed, typeint, default42) args parser.parse_args() split_dataset(args.image_dir, args.label_dir, args.output_dir, args.ratios, args.seed)这里ratios三个数之和必须等于1脚本按顺序切片所以第1个是训练集占比第2个是验证集占比剩余为测试集。设置seed42后无论运行多少次切分结果都一致同一批数据集在不同机器上做消融实验时这个随机种子尤其重要。如果后续要增加数据建议增大训练集占比而不是重新随机避免新旧数据分布混在一起导致验证结果失真。值得注意的是脚本里使用了shutil.copy而不是move这保证原始全量数据不被破坏。如果数据集很大可以改成os.link做硬链接秒级完成且不占用双倍磁盘空间。硬链接在机械硬盘和SSD上都能工作但跨文件系统时会失败所以不要跨盘使用。3.3 将划分结果组织成YOLO可用的目录结构YOLO训练要求图片和标签分别放在images和labels目录下且文件名完全一致。划分脚本输出的结构已经是dataset/train/images/xxx.jpg和dataset/train/labels/xxx.txt这正好匹配Ultralytics YOLO的约定。如果你还需要一份路径列表文件比如Darknet格式的train.txt可以用一段简单的命令生成find dataset/train/images -name *.jpg train.txt find dataset/val/images -name *.jpg val.txttrain.txt里每一行是图片的绝对路径Darknet训练脚本会读取它并自动在同级目录寻找对应的.txt标签。注意find输出的路径顺序不会固定但训练时并不依赖顺序。如果要用相对路径可以加-printf %P\n参数这里不过多展开。3.4 验证划分结果是否正确的三个指标划分完成后不要直接开始训练先做三个检查避免“训练集里混进了测试图”这类低级错误。第一数量核对。统计每个子集下的图片和标签文件数应该严格一致除非有标注缺失。第二文件重名检查。用下面命令看是否有图片在某两个子集里重复出现比如通过文件名集合求交集comm -12 (ls dataset/train/images | sort) (ls dataset/test/images | sort)如果有输出说明划分脚本存在泄露需要重新检查。第三标签内容抽样。在验证集里随机挑10张图打印出对应的TXT文件框是否落在图片边界内、类别ID是否在合法范围。这一步可以用Ultralytics的辅助函数快速完成但直接看几行TXT更直观。例如cat dataset/val/labels/a_random_image.txt如果看到类似2 0.735461 0.485382 0.104687 0.158753的行第一列类别ID是2后面四个浮点数都在0到1之间说明标注格式正确。如果出现大于1的坐标值说明归一化转换时用的是不同尺寸的图片需要重新检查。提示如果原始数据集里图片和标签不是同名对应而是类似000001.jpg和000001.txt建议先统一改名否则划分脚本复制标签时会丢失对不上的样本。4. YOLO环境搭建与训练自己的红外数据集4.1 Linux/Windows双平台的环境配置差异压缩包内的环境搭建教程分Linux和Windows两个版本核心思路一致先创建Python虚拟环境再安装PyTorch和CUDA版匹配的torch版本最后安装Ultralytics或YOLOv5的依赖。这里说几个最容易出问题的地方。Linux下常见的坑是Ubuntu系统自带Python版本与PyTorch轮子不兼容。建议直接用Miniconda建环境conda create -n yolov8 python3.9 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralyticsWindows下则不推荐用conda直接装Python 3.9/3.10安装PyTorch时先到官网用nvidia-smi确认自己的CUDA版本再选对应的cu121或cu118轮子。很多人在Windows上训练卡住是因为装了CPU版torch速度会慢20倍以上。装完后用python -c import torch; print(torch.cuda.is_available())验证GPU是否可用。如果输出False优先检查显卡驱动版本而不是重装torch。另外Windows上如果出现“NCCL error”这样的报错通常是因为Windows对NCCL支持不完备。常见的解决方法是把torch.distributed相关参数去掉不使用多卡DDP直接单卡训练。另外Windows下路径分隔符和Linux不同配置文件里的路径写成正斜杠/在Windows也能识别但写成反斜杠\在Linux会出错所以建议所有yaml和脚本里统一用正斜杠。4.2 为红外数据集写data.yaml并调整关键参数训练YOLO系列模型前需要准备好数据配置文件。Ultralytics YOLO接受一个data.yaml其格式如下path: /home/user/infrared_dataset # 数据集根目录 train: train/images val: val/images test: test/images nc: 4 names: [car, person, truck, bike]这里的train和val路径是相对于path的路径中不要写中文Linux下注意是正斜杠。nc必须和names列表长度一致同时和前面VOC转YOLO脚本里的class_to_id顺序一致。如果你的红外数据集里只有个位数的类别不需要修改模型超参但如果目标是小目标比如远距离行人要重点调整imgsz和anchors。启动训练时常见命令是这样的yolo train modelyolov8n.pt datainfrared.yaml epochs100 imgsz640 batch16 device0modelyolov8n.pt表示使用COCO预训练权重作为初始化这不是必须的但对红外数据集来说从COCO权重迁移的效果一般比随机初始化好因为底层特征提取器已经学会了通用边缘和纹理特征。imgsz640是输入分辨率红外图像若本身是384×288之类的非方形建议直接设为640让模型在预处理时自适应缩放而不是强行改变原始宽高比。batch16需要根据显卡显存调整用device0指使用第一张GPU。对于训练参数我习惯用一个表格来快速对照调整参数默认值红外场景推荐理由imgsz640640或832保留非方形图像的宽高比不过度下采样epochs100150-200红外特征少收敛慢batch16显卡显存允许时尽量大小批量易导致BN统计不稳定lr00.010.005迁移学习时降低初始学习率mosaic1.00.5红外目标多为小目标过度mosaic破坏上下文conf0.250.35推理时提高阈降低热源误检mosaic是Ultralytics默认开启的数据增强同一batch中四张图拼成一张。对于红外数据集如果目标本身很小拼图会进一步压缩目标尺寸导致学习困难。我一般会降低到0.5甚至关闭。调整方法是在训练命令中追加mosaic0.5或者直接在yaml中不设置使用默认训练参数。4.3 训练过程中的日志指标怎么看训练开始后终端会滚动输出box_loss、cls_loss、dfl_loss和metrics/precision(B)、metrics/recall(B)、metrics/mAP50(B)等指标。不要只看mAP还要关注cls_loss是否下降平稳。红外数据集普遍存在类别不平衡行人往往比卡车多得多如果看到recall很低而precision很高说明模型把多数类都学会了、少数类被忽略了。这时可以在命令后追加cls0.7提高类别损失的权重或者增加一个类别频率调整的训练脚本。输出目录里的weights/best.pt是验证集上mAP最高的权重文件last.pt是最后一个epoch的权重。注意不要直接用last.pt做推理除非你确认验证集上没有过拟合。建议用best.pt进行后续验证。训练中还常看到warning: corrupt JPEG restored and saved之类的日志这是读取图片时解码异常。红外相机输出的一些RAW图压成JPG后会有损坏块Ultralytics会自动修复但如果连续出现大量警告说明该图已严重损坏建议直接删除否则模型会学到错误纹理。另外如果验证集上mAP波动巨大检查验证集图片是否和训练集存在时间相关性比如同一个摄像头在同一分钟内的相邻帧全部进了验证集就会造成评估结果虚高。提示如果训练时遇到“unable to open file”错误先检查data.yaml路径是否可读再确认标签文件里的类别ID没有超出nc-1范围。红外图像常见位深12bit或14bitOpenCV读取时可能被当作uint16在训练前统一转成8bit PNG会减少异常。5. 红外场景下YOLO训练的常见坑与验证技巧5.1 红外图像的质量预处理与标注边界这套红外数据集已经标注好但如果你后续补充自己的红外图片要注意两点。一是红外图通常是灰度图YOLO的输入层期望三通道很多人在训练时报维度错误实际上只要用cv2.cvtColor把单通道复制成三通道即可。二是红外成像的噪点更多物体的边缘不如可见光锐利LabelImg里标注时很容易把框画得过大、把背景包进去。推荐在标注前先用中值滤波去除颗粒噪声再在放大视图下紧贴目标轮廓画框避免边界框里混入大量背景导致训练时特征混淆。另一种常见问题是红外图像常带有十字光标、温度条或者OSD叠加字符这些人工叠加信息会形成强烈的固定特征。模型如果只在这个数据集上训练很可能把温度条误当成一个类别。建议在训练前用cv2.inRange做一个简单掩膜把图像底部或右上角的温度条区域裁剪掉或者在标注时直接把复杂叠加区域排除。不要指望数据增强能消除这类固定位置脏数据。5.2 用少量样本快速验证训练流水线第一次跑通前不要直接拿全部5000张图训100个epoch。先把训练集缩小到每个类别20张样本epochs10跑一轮确认数据加载、损失计算、验证评估全链路正常。这个快速验证做法能在10分钟内暴露90%的配置错误。常见的是标签文件里坐标出现0值或负数影响训练Loss出现NaN还有数据集的图片格式混有png和jpg导致读取失败。一个小技巧是以官方验证脚本为基准定义自己的验证函数加载best.pt读取一张测试图像打印出results.pandas().xyxy[0]可以直接看到每个检测框的坐标和置信度。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model(test_sample.png, conf0.35) for r in results: print(r.pandas().xyxy[0])conf0.35表示置信度阈值红外图像的对比度低默认的0.25会产生较多误检我一般会调到0.35到0.4之间。如果检测框连续闪跳说明阈值偏低调高后能过滤掉热源噪声。这套验证技巧能帮助你在提交最终模型前快速判断是标注问题、数据分布问题还是推理参数问题。另外有一个很实用的习惯训练命令后追加21 | tee train.log把完整日志存成文件每轮loss和mAP都自动落盘。对比实验时我用grep metrics/mAP50 train.log快速提取所有epoch的mAP省去手动翻屏。这个做法在你连续跑几十组参数时能明显减少人为记录误差。本文还有配套的精品资源点击获取
返回列表