红外多目标检测数据集与YOLO训练实战:标签格式转换与避坑指南
2026/9/16 2:34:11 网站建设 项目流程

简介:面向红外场景目标检测任务的数据集包,内含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文件,用imagesannotationscategories三个数组组织,坐标用[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-CNN
COCO一个JSON文件像素绝对 x, y, width, heightMMDetection, Detectron2
YOLO每张图一个TXT归一化 x_center, y_center, w, hUltralytics YOLO

这套数据集已经把同一批图片分别用三种格式标好,意味着你可以直接喂给任意的YOLO系列模型,或者在切换框架时不用重新标注。训练脚本里最省事的做法是直接用YOLO格式,因为Ultralytics的data.yaml只需指定图片和标签目录即可,不需要额外解析逻辑。而COCO格式常用于需要mAP评估的严格对比实验,VOC格式则方便人工抽查标注质量。三种格式并存,本质上是为了让同一份标注成本能覆盖不同工具链。

2.2 类别映射与标注一致性检查

在动手转换前,先要确认类别ID的顺序。VOC的XML里写的是类别名称字符串,而YOLO的TXT里是数字ID,COCO的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_ok=True) 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.txtval.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, seed=42): 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_train+n_val]), ("test", images[n_train+n_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_ok=True) os.makedirs(lab_out, exist_ok=True) 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", required=True) parser.add_argument("--label_dir", required=True) parser.add_argument("--output_dir", required=True) parser.add_argument("--ratios", nargs=3, type=float, default=[0.7, 0.2, 0.1]) parser.add_argument("--seed", type=int, default=42) args = parser.parse_args() split_dataset(args.image_dir, args.label_dir, args.output_dir, args.ratios, args.seed)

这里ratios三个数之和必须等于1,脚本按顺序切片,所以第1个是训练集占比,第2个是验证集占比,剩余为测试集。设置seed=42后,无论运行多少次,切分结果都一致;同一批数据集在不同机器上做消融实验时,这个随机种子尤其重要。如果后续要增加数据,建议增大训练集占比而不是重新随机,避免新旧数据分布混在一起导致验证结果失真。

值得注意的是,脚本里使用了shutil.copy而不是move,这保证原始全量数据不被破坏。如果数据集很大,可以改成os.link做硬链接,秒级完成且不占用双倍磁盘空间。硬链接在机械硬盘和SSD上都能工作,但跨文件系统时会失败,所以不要跨盘使用。

3.3 将划分结果组织成YOLO可用的目录结构

YOLO训练要求图片和标签分别放在imageslabels目录下,且文件名完全一致。划分脚本输出的结构已经是dataset/train/images/xxx.jpgdataset/train/labels/xxx.txt,这正好匹配Ultralytics YOLO的约定。

如果你还需要一份路径列表文件,比如Darknet格式的train.txt,可以用一段简单的命令生成:

find dataset/train/images -name "*.jpg" > train.txt find dataset/val/images -name "*.jpg" > val.txt

train.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.jpg000001.txt,建议先统一改名,否则划分脚本复制标签时会丢失对不上的样本。

4. YOLO环境搭建与训练自己的红外数据集

4.1 Linux/Windows双平台的环境配置差异

压缩包内的环境搭建教程分Linux和Windows两个版本,核心思路一致:先创建Python虚拟环境,再安装PyTorch和CUDA版匹配的torch版本,最后安装Ultralytics或YOLOv5的依赖。这里说几个最容易出问题的地方。

Linux下,常见的坑是Ubuntu系统自带Python版本与PyTorch轮子不兼容。建议直接用Miniconda建环境:

conda create -n yolov8 python=3.9 -y conda activate yolov8 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics

Windows下则不推荐用conda,直接装Python 3.9/3.10,安装PyTorch时先到官网用nvidia-smi确认自己的CUDA版本,再选对应的cu121cu118轮子。很多人在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"]

这里的trainval路径是相对于path的,路径中不要写中文,Linux下注意是正斜杠。nc必须和names列表长度一致,同时和前面VOC转YOLO脚本里的class_to_id顺序一致。如果你的红外数据集里只有个位数的类别,不需要修改模型超参;但如果目标是小目标(比如远距离行人),要重点调整imgszanchors

启动训练时,常见命令是这样的:

yolo train model=yolov8n.pt data=infrared.yaml epochs=100 imgsz=640 batch=16 device=0

model=yolov8n.pt表示使用COCO预训练权重作为初始化,这不是必须的,但对红外数据集来说,从COCO权重迁移的效果一般比随机初始化好,因为底层特征提取器已经学会了通用边缘和纹理特征。imgsz=640是输入分辨率,红外图像若本身是384×288之类的非方形,建议直接设为640,让模型在预处理时自适应缩放,而不是强行改变原始宽高比。batch=16需要根据显卡显存调整,用device=0指使用第一张GPU。

对于训练参数,我习惯用一个表格来快速对照调整:

参数默认值红外场景推荐理由
imgsz640640或832保留非方形图像的宽高比,不过度下采样
epochs100150-200红外特征少,收敛慢
batch16显卡显存允许时尽量大小批量易导致BN统计不稳定
lr00.010.005迁移学习时降低初始学习率
mosaic1.00.5红外目标多为小目标,过度mosaic破坏上下文
conf0.250.35推理时提高阈降低热源误检

mosaic是Ultralytics默认开启的数据增强,同一batch中四张图拼成一张。对于红外数据集,如果目标本身很小,拼图会进一步压缩目标尺寸,导致学习困难。我一般会降低到0.5甚至关闭。调整方法是在训练命令中追加mosaic=0.5,或者直接在yaml中不设置,使用默认训练参数。

4.3 训练过程中的日志指标怎么看

训练开始后,终端会滚动输出box_losscls_lossdfl_lossmetrics/precision(B)metrics/recall(B)metrics/mAP50(B)等指标。不要只看mAP,还要关注cls_loss是否下降平稳。红外数据集普遍存在类别不平衡:行人往往比卡车多得多,如果看到recall很低而precision很高,说明模型把多数类都学会了、少数类被忽略了。这时可以在命令后追加cls=0.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或14bit,OpenCV读取时可能被当作uint16,在训练前统一转成8bit PNG会减少异常。

5. 红外场景下YOLO训练的常见坑与验证技巧

5.1 红外图像的质量预处理与标注边界

这套红外数据集已经标注好,但如果你后续补充自己的红外图片,要注意两点。一是红外图通常是灰度图,YOLO的输入层期望三通道,很多人在训练时报维度错误,实际上只要用cv2.cvtColor把单通道复制成三通道即可。二是红外成像的噪点更多,物体的边缘不如可见光锐利,LabelImg里标注时很容易把框画得过大、把背景包进去。推荐在标注前先用中值滤波去除颗粒噪声,再在放大视图下紧贴目标轮廓画框,避免边界框里混入大量背景导致训练时特征混淆。

另一种常见问题是红外图像常带有十字光标、温度条或者OSD叠加字符,这些人工叠加信息会形成强烈的固定特征。模型如果只在这个数据集上训练,很可能把温度条误当成一个类别。建议在训练前用cv2.inRange做一个简单掩膜,把图像底部或右上角的温度条区域裁剪掉,或者在标注时直接把复杂叠加区域排除。不要指望数据增强能消除这类固定位置脏数据。

5.2 用少量样本快速验证训练流水线

第一次跑通前,不要直接拿全部5000张图训100个epoch。先把训练集缩小到每个类别20张样本,epochs=10跑一轮,确认数据加载、损失计算、验证评估全链路正常。这个快速验证做法能在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", conf=0.35) for r in results: print(r.pandas().xyxy[0])

conf=0.35表示置信度阈值,红外图像的对比度低,默认的0.25会产生较多误检,我一般会调到0.35到0.4之间。如果检测框连续闪跳,说明阈值偏低,调高后能过滤掉热源噪声。这套验证技巧能帮助你在提交最终模型前,快速判断是标注问题、数据分布问题还是推理参数问题。

另外有一个很实用的习惯:训练命令后追加2>&1 | tee train.log,把完整日志存成文件,每轮loss和mAP都自动落盘。对比实验时,我用grep metrics/mAP50 train.log快速提取所有epoch的mAP,省去手动翻屏。这个做法在你连续跑几十组参数时,能明显减少人为记录误差。

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

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

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

立即咨询