资讯动态

用红外图像数据集训练YOLO:小目标检测的标签处理与参数调优

发布时间:2026/10/10 18:20:04 来源:尧图企业网站定制
简介红外图像鸟类目标检测数据集面向使用yolo系列算法进行目标检测训练与验证的开发者包含2679张带标签图像已按训练、验证、测试划分并内置数据集配置文件data.yaml可无缝适配yolov5、yolov7、yolov8、yolov9、yolov10、yolo11等主流版本适合目标检测入门练习、红外场景模型迁移及算法消融实验。压缩包文件总数为2000主要文件类型为xml标注文档整体体积约49.14MB轻量易下载。标注内容同时提供yolo格式和voc格式两种标签分别存放于独立文件夹yolo格式以txt记录类别索引、归一化中心点坐标与宽高voc格式以xml保留标准检测框结构文件名末尾带有类别名称片段便于分类筛选与格式转换也方便在LabelImg、Roboflow等工具间切换使用。目前已有172人学习下载可用该基准数据直接启动训练、验证模型mAP或结合自己的网络结构做数据增强与特征分析省去重新采集和标注的时间成本。1. 红外图像里的鸟不好找这个数据集补上了YOLO训练最缺的一环夜间做鸟类观测或者给输电线路做异物巡检普通可见光摄像头经常拍不到鸟只有红外成像能看到那个发烫的温度点。但红外图像给目标检测算法出了新难题没有颜色、对比度低鸟的尺寸又普遍偏小直接拿预训练好的YOLO算法权重去跑漏检率比想象中高得多。这时候一份带标签的红外图像鸟类数据集就是最缺的东西。这个压缩包里放了2679张红外图像每张都标注了鸟的位置框数量足够训练和评估一个能用的YOLO检测模型。我按自己实际做这个方向的经验把从解压数据、确认标签格式、写转换脚本、配置训练参数到踩坑排错的全过程写在这里给正在做鸟类目标检测的数据集选型和落地的同行一份能照着跑的参考。2. 拆解2679张红外鸟类数据集标签格式、图像特性与分布统计2.1 解压后第一步先看目录结构和标签格式别急着训练拿到压缩包第一件事不是解压后直接扔给训练脚本而是先摸清数据的目录结构和标签格式。这个决定后面所有预处理的方向格式摸错了转换脚本和训练配置全都要推翻重来。我一般先在终端里解压并列出目录树再随机挑一个标签文件打开看内容。mkdir -p ~/datasets/bird_infrared cd ~/datasets/bird_infrared unzip -q ~/下载/yolo算法-红外图像鸟类数据集-2679张图像带标签-鸟.zip find . -maxdepth 2 -type d | sort解压命令里的-q是静默模式不打印文件清单。find . -maxdepth 2 -type d只显示两层目录名一屏就能看清是不是images和labels这种YOLO惯例结构还是JPEGImages和Annotations这种VOC惯例结构。如果压缩包外层套了一层目录解压后所有文件会在子目录里记得加cd进去再继续操作。目录结构确认后先统计一下图像和标签数量是否都等于2679。这个数字和标题声明对得上说明数据完整对不上就说明里面有图没标签或有标签没图这种数据不筛掉训练时会成为隐形的坑。find . -type f \( -name *.jpg -o -name *.png -o -name *.jpeg \) | wc -l find . -type f \( -name *.xml -o -name *.txt -o -name *.json \) | wc -l然后再随机挑一个标签文件看内容cat $(find . -type f -name *.txt | head -1)看到.txt文件里每行是5个数形如0 0.5261 0.4839 0.0423 0.0364并且都是0到1之间的小数那就是YOLO格式训练前不需要做格式转换。如果看到的是XML标签里面有namebird/name和bndbox节点那就是VOC格式如果是JSON且里面带images和annotations两个数组则是COCO格式。后两种都需要转换成YOLO的txt格式转换脚本在第3章给出。这里有一个所有人都踩过的细节标签格式不是看扩展名而是看内容结构我见过不少人把txt文件名当YOLO格式结果打开是类名 xmin ymin xmax ymax的绝对坐标格式直接训练后loss全部发散。2.2 红外图像和RGB照片的三个关键差异每一个都影响训练这个数据集的核心价值在于“红外图像”四个字不能把它当成普通灰度图来处理。从模型训练的角度看红外图像和日常的RGB照片有三个本质差异会直接影响预训练权重的迁移效果。第一红外图像是单通道灰度图。红外探测器输出的是强度值通常保存成8位或14位灰度图。14位raw数据在大多数人手里最终会转成8位的jpg或png这个过程如果只做简单线性拉伸图像对比度会非常差如果做百分比截断或直方图均衡鸟的细节会好很多。我建议在预处理阶段对所有图像统一做一次对比度增强后面章节会详细说。第二背景和目标的关系不固定。在黎明或夜间环境下鸟的体温比环境高在红外图里呈现为亮斑相对容易分辨。但到了夏天白天被太阳晒热的岩石、树干、甚至水泥地上的温度可能远高于鸟的体表温度鸟反而成了暗斑。这意味着模型不能只学“鸟是亮的”这个规则必须从形状轮廓和局部温度梯度去判别。普通RGB预训练模型没有见过这种反转关系所以直接从COCO权重做推理效果很差。第三目标尺度普遍小。红外鸟类观测通常距离较远一只成年鸟在640×640输入图像里往往只占20到40个像素的边长面积占比常常低于0.5%。这在YOLO里属于典型的小目标场景。YOLO的默认下采样倍率是32到了最深层的特征图一个小鸟目标可能只剩一两个像素检测头根本提取不到足够的语义信息。这个特性决定了后面输入分辨率要加大、anchor要重新聚类、甚至要考虑P6模型或SAHI切片推理这类方案。2.3 类别与目标尺度分布单类也不代表省心虽然鸟类检测通常只有一个类别bird但拿到标签后我还是会做一个分布统计而不是急着开训。这个统计有两个目的一是确认类别ID是从0开始且连续二是看目标尺度分布判断这个数据集到底有多“小目标”。import glob from collections import Counter cls_counter Counter() areas [] for label_file in glob.glob(./labels/*.txt): with open(label_file, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 5: continue cls_id int(parts[0]) w float(parts[3]) h float(parts[4]) cls_counter[cls_id] 1 areas.append(w * h) print(类别分布:, cls_counter) areas_sorted sorted(areas) print(目标面积占比 中位数: {:.4%}.format(areas_sorted[len(areas_sorted) // 2])) print(目标数量:, len(areas), 标注框总数)这段代码直接读取YOLO格式标签第一列是类别ID第三、四列是归一化框宽高。两个框的宽高相乘就是目标在整张图里的面积占比。如果中位数低于0.5%就说明数据集里一半以上的鸟都在图像中占不到0.5%的面积这个尺度下默认的anchor尺寸很难匹配必须重新聚类。这个统计脚本还有一个隐藏价值如果cls_counter里出现了大于0但不连续或大于1的ID比如有类别3但没有类别2说明标签里可能有脏数据要么是标注工具误操作要么是转换脚本里id映射写错了。这些情况下YOLO的类别数和yaml配置对不上训练时会出现class loss异常。3. 预处理把标签转成YOLO格式并划分训练集3.1 VOC格式转YOLO格式转换脚本与四个边界处理如果第2章确认标签是VOC格式的XML就需要把里面的边界框转成YOLO的txt格式。转换的核心逻辑是从XML里读出图像宽高和每个目标的xmin ymin xmax ymax绝对像素坐标换算成归一化的x_center y_center width height。import xml.etree.ElementTree as ET from pathlib import Path import os def voc_to_yolo(xml_dir, img_dir, output_dir, class_names): os.makedirs(output_dir, exist_okTrue) xml_files list(Path(xml_dir).glob(*.xml)) for xml_path in xml_files: tree ET.parse(xml_path) root tree.getroot() # 有些XML不带size节点退回到读图片尺寸 size_node root.find(size) if size_node is not None: img_w int(size_node.find(width).text) img_h int(size_node.find(height).text) else: import cv2 img_path Path(img_dir) / (xml_path.stem .jpg) img cv2.imread(str(img_path)) if img is None: print(f[warn] {xml_path.name} 对应图片读取失败跳过) continue img_h, img_w img.shape[:2] if img_w 0 or img_h 0: print(f[warn] {xml_path.name} 尺寸异常跳过) continue lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_names: continue cls_id class_names.index(name) bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 边界裁剪防止标注越界导致训练nan xmin max(0, min(xmin, img_w)) xmax max(0, min(xmax, img_w)) ymin max(0, min(ymin, img_h)) ymax max(0, min(ymax, img_h)) if xmax - xmin 1 or ymax - ymin 1: continue x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path Path(output_dir) / (xml_path.stem .txt) out_path.write_text(\n.join(lines) \n, encodingutf-8) print(f转换完成共输出 {len(list(Path(output_dir).glob(*.txt)))} 个标签文件) class_names [bird] # 按你训练时的类别顺序 voc_to_yolo(Annotations, JPEGImages, labels, class_names)代码里有四个边界处理很容易被忽略一是max(0, min(xmin, img_w))把越界的框拉回图像范围内红外标注经常因为目标太模糊而出现框超出边界的情况不裁剪的话YOLO在计算IOU时会出现负数面积导致loss异常二是过滤掉宽高小于1像素的退化框那些只有一条线的标注框会直接让边界回归变成无穷大三是class_names.index(name)决定了类别ID的顺序这个顺序必须和后续配置yaml里的names完全一致四是当XML里没有size节点时用OpenCV读实际图片取宽高这个回退逻辑保证脚本不会因为半路遇到脏XML而崩溃。3.2 训练集/验证集划分防止连续帧泄露红外视频抽帧得到的数据集有一个隐蔽问题相邻帧之间高度相似甚至目标的位置都几乎没变。如果直接用随机打乱划分训练集和验证集同一段连续帧会同时出现在两边模型在验证集上等于“看过答案”mAP虚高部署到真实视频流后立刻打回原形。我一般先看文件名语义很多红外采集系统会按frame_000001.jpg这样带时间序号的命名方式这种情况下按文件名顺序划分前80%做训练、后20%做验证。如果文件名完全没有顺序信息再考虑随机划分。import shutil from pathlib import Path import random random.seed(42) img_files sorted(list(Path(images).glob(*.jpg))) sorted(list(Path(images).glob(*.png))) label_index {p.stem: p for p in Path(labels).glob(*.txt)} split_idx int(len(img_files) * 0.8) train_files img_files[:split_idx] val_files img_files[split_idx:] def organize(split_name, files): img_out Path(split_name) / images label_out Path(split_name) / labels img_out.mkdir(parentsTrue, exist_okTrue) label_out.mkdir(parentsTrue, exist_okTrue) for img_path in files: shutil.copy(str(img_path), str(img_out / img_path.name)) label label_index.get(img_path.stem) if label is not None: shutil.copy(str(label), str(label_out / label.name)) else: print(f[warn] {img_path.name} 缺少标签文件) organize(train, train_files) organize(val, val_files) print(f训练集: {len(train_files)} 张, 验证集: {len(val_files)} 张)这段脚本用label_index字典把标签按文件名索引保证复制图片时能快速找到对应标签。只复制不移动原目录保留方便后面补数据和重新划分。有个细节值得注意sorted()按完整路径排序如果文件名不是纯数字而是frame_000001这种补零格式排序结果就是正确的时间顺序如果文件名是自然数但没有补零比如1.jpg、10.jpg、2.jpg排序会错乱最好用sorted(files, keylambda p: int(p.stem.split(_)[-1]))改成按序号数值排序。划分完还要检查验证集里是否出现了训练集相邻帧简单做法是抽样打印验证集前20个文件名肉眼确认没有连续序号混入。3.3 数据增强按红外图像特性调整增强策略YOLO自带的训练增强默认是基于RGB图像设计的尤其是色调和饱和度扰动但在红外灰图上完全没有意义反而给模型引入噪声。我在红外数据集上的增强策略和可见光训练有明显的区别。# hyp_bird.yaml (YOLOv5风格) hsv_h: 0.0 # 红外没有颜色概念色相增强无效 hsv_s: 0.0 # 饱和度同样关闭 hsv_v: 0.3 # 亮度扰动保留模拟环境温度变化 fliplr: 0.5 # 左右翻转 mosaic: 0.5 # 小目标场景降低mosaic概率 mixup: 0.2 # mixup慎开目标太小容易糊这段配置的逻辑是保留亮度扰动因为灰度的明暗差异在红外场景里对应温度差异适当扰动可以泛化到不同季节和不同温差的环境fliplr是安全增强鸟的朝向不固定左右翻转不会改变语义mosaic我从默认的1.0降到0.5原因是mosaic会把四张图拼接缩放红外鸟目标本来就只有几十个像素再缩到四分之一后几乎消失模型容易只学到背景纹理而不是目标本身。除了YOLO内置的增强红外图像还有一个可见光领域很少用的手段对灰度图做CLAHE自适应直方图均衡让暗环境下的鸟和背景分离得更干净。我通常在数据预处理阶段对整批图像做一次而不是在训练管线里在线做因为在线做的计算开销会拖慢迭代速度。预处理时每张图先做CLAHE再保存训练时就不再重复。4. 训练YOLO模型选型、参数配置与loss曲线解读4.1 YOLOv5还是YOLOv8红外小目标场景的选型逻辑很多人在YOLOv5和YOLOv8之间纠结。从实际效果看这个数据规模下两者的精度差距不会太大真正决定选型的是部署环境和迭代效率。YOLOv5s的优势是生态成熟、文档和踩坑记录最多改成P6模型也方便而且onnx导出和TensorRT部署的坑都有现成答案。YOLOv8的C2f结构和anchor-free head在小目标上略占优势尤其当输入分辨率提高到960和1280时v8在目标框定位上的表现更稳定。如果目标设备是嵌入式单板我会优先考虑YOLOv8n参数量最小量化后Latency表现更好。其实对于红外鸟类这种强小目标场景比选v5还是v8更要紧的是输入分辨率。我自己测试同样的模型和数据640输入下mAP0.5可能在0.45左右提升到960输入后能到0.52到0.55代价是显存占用和训练时间几乎翻倍。如果你的显卡显存大于等于12G建议直接上960起步。4.2 训练参数配置表红外鸟类场景必调的9个参数下面这组参数是我在红外鸟类数据集上反复试出来的基线值直接用YOLOv5或YOLOv8命令即可生效。这里列出的是和默认值差异较大、并且对红外小目标有明显影响的几项。参数默认值建议值说明imgsz640960鸟目标太小分辨率提高是收益最大的单项batch16168G显存降到816G及以上可以保持16epochs300300配合早停使用patience10050小数据集过拟合快早停耐心缩短一半lr00.010.005迁移学习情况下初始学习率低一点更稳mosaic1.00.5避免小目标被mosaic缩放后消失hsv_h0.0150.0红外无色彩信息hsv_s0.70.0红外无色彩信息hsv_v0.40.3仅保留亮度扰动lr0从0.01降一半的原因是红外图像与ImageNet域差异较大浅层特征对模型来说完全陌生过大的学习率容易在训练初期就把预训练权重冲坏。如果发现前20个epoch loss不仅不降还在抖动就把lr0继续降到0.002再试。4.3 训练命令与训练中的日志监控数据集准备好后训练命令本身不复杂关键是训练过程中盯着哪几个指标。我自己执行时的命令示例# YOLOv5 python train.py \ --data bird_infrared.yaml \ --weights yolov5s.pt \ --img 960 \ --batch 16 \ --epochs 300 \ --patience 50 \ --hyp hyp_bird.yaml \ --cache # YOLOv8 yolo train \ databird_infrared.yaml \ modelyolov8s.pt \ imgsz960 \ batch16 \ epochs300 \ patience50 \ hsv_h0.0 hsv_s0.0 hsv_v0.3 \ mosaic0.5--cache参数把图像一次性加载进内存2679张图大概占2到3G内存能显著减少每个epoch的磁盘IO等待。训练开始后我盯三样东西box_loss是否在前50个epoch内从大波动转为稳定下降mAP0.5是否在训练到三分之一时超过0.4mAP0.5:0.95和mAP0.5的差距如果后者高但前者很低说明框的定位精度不行优先考虑提高输入分辨率或重新聚anchor。顺便提一句如果要在团队内做技术同步或汇报训练日志里的 loss 曲线和验证集预测图就是最好的PPT素材比任何口头描述都有说服力。直接把runs/detect/train/下的results.png和val_batch*_pred.jpg截图贴进去就能把“yolo算法讲解ppt”的需求一起解决了。5. 避坑用这个数据集训练YOLO的五个常见问题5.1 类别ID从0还是1开始检查标签分布的脚本训练直接跑飞了loss一路飙到几十的常见原因之一就是类别ID不对。YOLO的类别编号从0开始只有一个类时ID只能是0。不少标注工具导出时习惯从1开始转换脚本没有减1导致第一个类别消失模型把背景当成了目标。一个30秒就能做完的排查方法统计标签文件里所有类别ID的最小值和最大值确认它们在yaml配置的nc范围内并且从0开始连续。import glob min_id, max_id 999, -1 for f in glob.glob(./labels/*.txt): with open(f) as fp: for line in fp: parts line.strip().split() if len(parts) 5: continue cls int(parts[0]) min_id min(min_id, cls) max_id max(max_id, cls) print(最小类别ID:, min_id, 最大类别ID:, max_id)同时再核对一下yaml文件里的names列表长度是否等于nc。如果最小ID是1而最大ID是1说明这个数据集只有一个类但从1计数需要在转换脚本里对类别ID做减1处理。5.2 标签与图片数量不匹配静态黑块导致内存泄漏这里的“内存泄漏”是一个比喻性的说法。实际遇到的问题往往是训练开始时图像数量显示为2679但加载到一半某个epoch突然少了若干张data loader直接报错或跳过文件。这类问题几乎都是标签文件存在但图片缺失或者图片存在但标签缺失、且缺失的文件名恰好导致dataloader的过滤逻辑跳过了整批。我的检查方式是写一行bash命令对比两个目录的文件名comm -3 (ls images | sed s/\.[^.]*$// | sort) (ls labels | sed s/\.[^.]*$// | sort) | head -20comm -3输出只在其中一个列表出现的文件名。如果输出为空白说明图片和标签一一对应如果有输出逐个定位缺失文件后决定是补标签还是过滤掉图片。5.3 小目标漏检anchor配置是关键问题验证集上漏检严重尤其是小目标这会让mAP0.5卡在0.3以下。深入原因是默认anchor尺寸是按COCO数据集聚类的COCO目标平均占图面积的比例远超红外鸟目标。YOLOv5在训练时会自动用k-means重新聚类anchor但YOLOv8的anchor-free设计对anchor不敏感出现“调了anchor没效果”的情况。解决这个问题的具体路径训练脚本里确保开启了自动anchor调整训练日志中会打印聚类结果。同时在推理时把conf_thres从默认的0.25降到0.1红外图像的对比度低导致模型对目标的置信度普遍低于可见光场景阈值太高等于是自己把低分正确检测滤掉了。5.4 过拟合2679张图训300轮验证mAP曲线掉头这个数据集只有2679张图如果模型是s或m级别训练到200轮以后验证mAP可能出现明显的掉头而训练loss还在低位震荡模型开始记样本噪声而不是学习泛化特征。解决的办法按优先级排先提前早停把patience设为30到50再增强augmentation尤其是亮度扰动模拟不同环境温度下的数据分布最后才考虑换更小的模型或着加dropout层。不要一上来就换数据增强还没有试过的小模型。5.5 训练中途loss变nan数据清洗脚本训练跑到一半box_loss突然变成nan几乎所有情况都是数据里有脏标签某个标注框的宽或高为0或box坐标超出图像边界太远导致IOU计算中出现除零。处理办法是在训练前跑一遍清洗脚本把所有尺寸异常的label行过滤掉import glob for f in glob.glob(./labels/*.txt): lines [] with open(f) as fp: for line in fp: parts line.strip().split() if len(parts) 5: continue cls, x_center, y_center, w, h map(float, parts[:5]) if w 0 or h 0 or w 1 or h 1: continue if x_center 0 or x_center 1 or y_center 0 or y_center 1: continue lines.append(line.strip()) with open(f, w) as fp: fp.write(\n.join(lines) \n)这个脚本的核心是五个if判断宽或高不大于0、归一化宽高大于1、中心点坐标超出0到1范围这些都是非法标签直接过滤。跑完后重新统计一遍标签数量确认清洗前后的差异是否符合预期。6. 进阶技巧把红外鸟类检测的mAP再往上推一截模型能正常收敛之后我还有四个固定动作用来榨取最后几个点的mAP。第一个是重新聚类anchor。即便YOLOv8对anchor不敏感如果你用的是YOLOv5或自己处理的P6模型anchor和数据集目标尺度的匹配程度直接影响召回。训练前单独跑一次k-means聚类把聚出来的六组尺寸直接写进模型配置文件再训练。第二个是SAHI切片推理。这个技巧专门解决小目标问题推理时先把原图切成多个256×256的滑窗分别检测后再把框映射回原图坐标做NMS合并。对2679张这种目标是“几个像素的亮斑”的数据集SAHI几乎是最值得试的推理优化手段mAP0.5通常能直接提高5个点以上。第三个是在采集源头上做红外两点校正。如果这个数据集是你自己用红外相机采集的两层校准的意义远比在图像层面做CLAHE大。非均匀性校正是用一个暗帧和一个亮帧计算出每个像素的增益和偏移再对后续所有帧做校正。很多机芯在导出数据时已经做了这一步但如果你拿到的是原始14bit数据先做非均匀性校正再转8bit能看到固定条纹噪声被明显压掉目标轮廓干净很多。第四个是测试时增强。推理时把同一张图做左右翻转和轻微尺度变化分别推理后合并结果取平均速度会慢2到3倍但置信度更稳。对于红外场景下的低对比度目标这个操作几乎是无脑回分的正确率提升手段。最后说一个我自己的教训千万不要因为训练好了一次就直接部署。红外图像在不同天气、不同时间段下的灰度分布完全不同你手上这个数据集如果主要来自夜间那白天场景的泛化一定需要额外验证。我习惯在训练结束后保留30张完全没参与训练的不同时段红外图做手动验证重点看误检率和漏检率而不是只看验证集上的mAP。有了这个习惯之后我再也没有被“验证集指标好看、上线一测就翻车”的假象坑过。希望这些经验和避坑记录能帮你在红外鸟类检测这条路上走得顺畅一点。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑