
简介面向遥感目标检测任务的数据集资源聚焦电塔目标识别场景适合需要训练或微调电塔检测模型的算法工程师、研究人员及学习者使用。资源采用Pascal VOCYOLO双格式存放包含244张jpg原图、244个xml标注文件与244个txt标注文件共734个文件压缩包大小78.4MB。类别仅设dianta一类提供504个电塔实例框标注由labelImg工具完成采用矩形框标注格式规范、边界框准确可直接接入主流检测框架进行训练与评估。虽然没有提供训练好的模型或权重文件但对数据准备和标注流程而言是一份简洁、干净的训练集。该资源已吸引280人关注学习适合作为电塔检测方向的基础训练数据帮助使用者省去数据采集与人工标注的时间快速开展模型验证和实验对比。1. 遥感电塔检测数据集244张图能干什么做遥感目标检测的人大概都有这种经历——要么自己用 labelImg 一张张框到眼花要么从网盘里拖下来一个几个 G 的大数据集解压一看标注格式五花八门类别名还有中文训练脚本直接原地爆炸。这份《遥感图像电塔检测数据集 VOCYOLO 格式 244 张 1 类别》属于典型的“小而刚需”资源244 张 jpg配 244 个 VOC 格式的.xml 和 244 个 YOLO 格式的.txt单类别 dianta总共 504 个框。它解决的不是“数据够不够多”的问题而是“从标注到训练流程通不通”的问题——你不需要再花一下午做格式转换和标签清洗直接拖进 YOLOv5/v8 就能跑通全流程。适合谁用我得说清楚如果你正在做电力巡检、遥感小目标检测相关的课程设计或课题预研想先用一份干净的标注数据把训练、验证、测试的 pipeline 跑通这份资源非常合适。但如果你是冲着训练一个能直接上线的模型来的我劝你冷静——244 张图 504 个框连“小规模”都谈不上充其量是“微型”。这篇文章我会把数据集的目录结构、格式对应关系、标注细节、训练前的检查点、以及最容易翻车的地方全部拆开讲你拿到手不用再瞎试。2. VOC 与 YOLO 双格式解析文件名、坐标与框数的三重对齐2.1 目录结构图片、xml、txt 一一对应解压之后你会看到这个 .7z 包里的内容其实非常规整。没有额外的文件夹嵌套也没有把图片分到 train、val、test 子目录里——244 张 jpg、244 个.xml、244 个.txt 全部平铺在同一层。这种结构有几个优点也有一个隐藏的坑后面避坑章再细说。firc_yaogan_10.jpg firc_yaogan_10.xml firc_yaogan_10.txt firc_yaogan_13.jpg firc_yaogan_13.xml firc_yaogan_13.txt ...文件名前缀统一是firc_yaogan_加数字编号jpg、xml、txt 三个后缀共用同一文件名主干。这个命名习惯很重要后续不管用脚本做数据处理还是划分训练集都可以直接用文件名做 key 对齐三个文件不需要额外建立映射表。我一般在拿到这类数据集时会先跑一个脚本验证对齐情况防止有些版本里图片和标注文件数量对不上。import os from pathlib import Path img_files set(Path(p).stem for p in os.listdir(.) if p.endswith(.jpg)) xml_files set(Path(p).stem for p in os.listdir(.) if p.endswith(.xml)) txt_files set(Path(p).stem for p in os.listdir(.) if p.endswith(.txt)) print(fjpg: {len(img_files)}, xml: {len(xml_files)}, txt: {len(txt_files)}) print(只在jpg中:, img_files - xml_files - txt_files) print(只在xml中:, xml_files - img_files - txt_files) print(只在txt中:, txt_files - img_files - xml_files)这个脚本就是纯文件名集合运算jpg、xml、txt 三个 set 做差集。正常情况下三个集合完全一致输出的数量都是 244。如果出现某个文件只有 xml 没有 jpg说明原数据集在整理时漏了图训练时就会报“找不到图片”的错误。这里有一个细节值得注意——这种平铺结构缺少类别标签class labels目录层级因为总共就一个类别 dianta不需要像多类别数据集那样按类别分子目录所以目录结构反而是最简单可靠的。2.2 XML 里到底存了什么object 节点与 bndbox 坐标VOC 格式的标注文件本质就是一个 XML每个annotation folderfirc_yaogan/folder filenamefirc_yaogan_10.jpg/filename size width1024/width height768/height depth3/depth /size object namedianta/name poseUnspecified/pose truncated0/truncated difficult0/difficult bndbox xmin312/xmin ymin245/ymin xmax365/xmax ymax290/ymax /bndbox /object /annotation这里有个非常容易被忽视的参数size节点中的 width 和 height 必须和实际 jpg 图片的分辨率严格一致否则后续转 YOLO 格式时归一化坐标会整体偏移。有些第三方数据集在压缩图片后忘了更新 xml 里的 size导致训练时框全部错位。我拿到任何 VOC 数据集的第一件事就是写个脚本批量核对 size 和图片真实尺寸。参数说明一下bndbox 中的四个值都是绝对像素坐标xmin、ymin 是左上角xmax、ymax 是右下角基于 0 索引。比如 312 意味着框左边缘在图片第 312 列像素上。如果图片宽 1024那么 xmax 最大不会超过 1023如果出现 xmax1024 这种越界值说明标注工具有 off-by-one 的问题训练时 YOLO 会报错或自动裁剪。2.3 YOLO txt 格式五个数字的归一化换算YOLO 格式的 txt 每一行对应 xml 里的一个 object格式是class_id x_center y_center width height注意不是 VOC 的左上角右下角而是归一化后的中心点坐标和宽高。这份数据集里类别 dianta 的 id 是 0所以每行都是 0 开头的五个浮点数。0 0.330566 0.348307 0.051758 0.058594 0 0.512695 0.416667 0.048828 0.066406换算关系是这样的x_center ((xmin xmax) / 2) / widthwidth (xmax - xmin) / widthy 方向同理。以第一行为例反推回去就能验证标注坐标是否合理xmin (0.330566 - 0.051758 / 2) * 1024 ≈ 312 xmax (0.330566 0.051758 / 2) * 1024 ≈ 365 ymin (0.348307 - 0.058594 / 2) * 768 ≈ 245 ymax (0.348307 0.058594 / 2) * 768 ≈ 290正好和上面的 xml 坐标完全对上。这个验证动作特别重要很多数据集的 VOC 和 YOLO 两个版本是不同工具生成的换算系数稍有偏差就会导致两个格式的坐标对不上。你训练时用的是 txt但可视化验证时打开的是 xml 画的框两边不一致会让排查变得非常痛苦。这里有两点需要特别注意。第一txt 中每行末尾如果有空格或者空行YOLO 训练时一般能容忍但如果文件用了 CRLF 换行符在 Linux 环境下某些旧版脚本会解析异常建议统一转成 LF。第二如果某个 txt 文件是空的0 字节说明这张图没有标注任何目标训练时 YOLO 会跳过它但划分数据集时要注意——不能把空标注图全扔进测试集否则模型没见过的场景就缺失了。2.4 框数统计504 个框意味着什么摘要里写得清楚dianta 类别框数 504总框数 504因为只有一类。平均每张图约 2.06 个框属于稀疏标注。对比一下遥感场景的特点——电塔不是密集目标一张图里 1 到 3 个是常态最密的情况可能也就 5 个左右。这种分布对训练有个直接影响单张图的 positive anchor 数量太少模型容易偏向预测背景推理时误检率会比密集场景高。我在做目标检测训练时习惯先写一个统计脚本把数据集的框数分布、框尺寸分布、长宽比分布拉出来看一眼。这份数据集如果框的尺寸普遍在 30~80 像素之间相对 1024×768 的原图那就是典型的小目标场景需要在 YOLO 里调小 anchor 或开启多尺度训练。import xml.etree.ElementTree as ET import os total_boxes 0 box_areas [] for xml_file in os.listdir(.): if not xml_file.endswith(.xml): continue tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) for obj in root.findall(object): bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) w xmax - xmin h ymax - ymin total_boxes 1 box_areas.append((w / img_w) * (h / img_h)) print(f总框数: {total_boxes}) print(f平均面积占比: {sum(box_areas) / len(box_areas):.4%})这段脚本直接遍历当前目录下的所有 xml把每一帧的框数和归一化面积算出来。归一化面积在 0.01 到 0.03 之间属于小目标0.1 以上就是大目标。电塔这类目标在遥感影像里通常纵向长、横向窄长宽比普遍大于 1.5如果统计结果符合这个规律说明标注质量整体可靠。3. 标签核查用脚本读 xml 排查五个质量指标3.1 类别名、文件名编码与非法字符拿到数据集第一件事不是直接跑训练而是做标签体检。遥感数据集的标注文件经常混着各种怪问题类别名大小写不一致比如 dianta 和 Dianta 两个类在代码里被当成不同类别xml 文件里混入中文注释导致编码解析失败文件名里有空格或者括号OpenCV 读图直接报错。这份数据集用 labelImg 生成类别名规范统一但为了稳妥我还是建议走一遍检查流程。import xml.etree.ElementTree as ET import os import re for xml_file in sorted(os.listdir(.)): if not xml_file.endswith(.xml): continue try: tree ET.parse(xml_file) root tree.getroot() except Exception as e: print(f解析失败: {xml_file} - {e}) continue for obj in root.findall(object): name obj.find(name).text if not re.match(r^[a-zA-Z0-9_]$, name): print(f非标准类别名: {xml_file} - {name})这段代码里我用正则^[a-zA-Z0-9_]$校验类别名是否只包含字母、数字和下划线。如果出现中文类别名YOLO 训练时会在读取 class names 文件时出现 UnicodeDecodeError或者生成的标签类别 id 对不上。这里要说明正则只是检测纯字符串安全性实际训练时还要确保 xml 里的 name 和 data.yaml 里的 names 列表顺序完全一致——顺序错了模型就会把电塔当背景学。文件名校验同样值得做。标注里的 节点理论上应该和实际文件名一致但有些数据集会在这里埋坑。我见过 写的是firc_yaogan_10.JPG大写后缀实际文件是小写.jpgWindows 系统不区分大小写能正常打开但训练脚本跑在 Linux 服务器上就直接 FileNotFoundError。这份数据集统一小写但我还是建议你实际跑一下对比别省这一步。3.2 图片分辨率的分布为什么统一尺寸不现实遥感图像有一个极其麻烦的特点——分辨率不统一。有的图是 1024×768有的是 800×600还有直接从卫星影像上截的 512×512。labelImg 支持打开任意尺寸的图片做标注不会强制 resize所以这份数据集的原始分辨率很可能也是参差不齐的。from PIL import Image import os resolutions {} for img_file in sorted(os.listdir(.)): if not img_file.endswith(.jpg): continue with Image.open(img_file) as im: resolutions.setdefault(im.size, 0) resolutions[im.size] 1 for res, count in sorted(resolutions.items()): print(f{res[0]}x{res[1]}: {count} 张)为什么要查这个因为数据集的原始分辨率直接决定训练时的超参数设置。如果 244 张图全是一个分辨率那你可以用固定的输入尺寸如 640×640直接训练如果分辨率跨度大就需要开启 YOLO 的矩形训练rect training功能或者统一 letterbox 到固定尺寸。这份数据集我预判分辨率跨度会比较大因为遥感影像切片本身就很难做到像素级统一。训练时如果发现 mAP 比较低优先怀疑 letterbox 拉伸导致的标注偏移而不是模型结构问题。3.3 边界越界与零面积框标注工具留下的后门labelImg 有个上手期常见失误——画框时鼠标没拖完就松了或者不小心把框拖出了图片边界。管理严格的标注任务会要求框必须完全落在图片内部但小规模自标注数据集经常不管这些。越界框在 VOC 格式里表现为 xmax 大于图片宽度或 ymax 大于图片高度甚至出现负数坐标。import xml.etree.ElementTree as ET import os for xml_file in sorted(os.listdir(.)): if not xml_file.endswith(.xml): continue tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) for obj in root.findall(object): bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) if xmin xmax or ymin ymax: print(f零面积框: {xml_file}) if xmin 0 or ymin 0 or xmax img_w or ymax img_h: print(f越界框: {xml_file} - ({xmin},{ymin},{xmax},{ymax}) / 图幅 {img_w}x{img_h})越界框的影响取决于训练框架。YOLOv5 遇到越界框会做自动 clip把坐标裁剪到图片范围内但会损失一部分框的面积YOLOv8 也有类似处理。然而自训练的损失函数会把越界部分的梯度计算进去坐标回归头会被带偏。这里的关键经验是与其依赖框架自动裁剪不如在训练前把越界框修正好。这份数据集是 labelImg 手工标注的越界概率不高但零面积框偶尔会出现——画框后没保存就生成 xml 的情况不算罕见。3.4 框大小分布小目标比例决定训练策略前面第 2.4 节提到了框面积占比这一步应该看得更细。遥感电塔在 1024×768 原图中一般占据 40×80 到 80×120 像素换算下来面积占比 0.4% 到 1.5%。如果统计结果显示有大量框小于 20×20 像素那它们大概率是标注误差也可能是远距离电塔的正当标框。from PIL import Image import xml.etree.ElementTree as ET import os small medium large 0 for xml_file in sorted(os.listdir(.)): if not xml_file.endswith(.xml): continue tree ET.parse(xml_file) root tree.getroot() img_file root.find(filename).text with Image.open(img_file) as im: img_w, img_h im.size for obj in root.findall(object): bndbox obj.find(bndbox) w int(bndbox.find(xmax).text) - int(bndbox.find(xmin).text) h int(bndbox.find(ymax).text) - int(bndbox.find(ymin).text) area_ratio (w * h) / (img_w * img_h) if area_ratio 0.01: small 1 elif area_ratio 0.1: medium 1 else: large 1 print(f小目标(1%): {small}, 中目标(1%~10%): {medium}, 大目标(10%): {large})这个三段分类法对应 YOLO 里的 anchor 设计。小目标占比高意味着默认的 anchor 可能偏大一般做法是把最小 anchor 从 10×13 调成 5×6 这种更小的尺寸或者在 YOLOv8 里用anchor_ratio参数做全局缩放。电塔的纹理特征简单、颜色对比度高只要 anchor 合适小目标也能学到可用的特征表达。3.5 单图框数排除漏标与重复标注最容易被忽略的质量指标是单图框数分布。电塔在遥感影像中是离散目标正常情况下每张图 0 到 4 个框。如果某张图出现 10 个以上的框要么是这张图是 5000×5000 大图的切片电塔密集排列要么是同一个电塔被重复画了多次这就是标注事故了。import xml.etree.ElementTree as ET import os for xml_file in sorted(os.listdir(.)): if not xml_file.endswith(.xml): continue tree ET.parse(xml_file) root tree.getroot() names [obj.find(name).text for obj in root.findall(object)] if len(names) 5: print(f{xml_file}: {len(names)} 个框)重复标注的危害比漏标更大——同一目标被框两次训练时相当于一个真实目标对应两个 anchor模型会学到“目标中心附近输出两层置信度”在 NMS 阶段容易把两个高置信度框一起保留造成重复检测。如果单图框数明显多于类别合理值我会直接用 labelImg 打开原图查看确认到底是真的电塔密集还是误操作。这一步属于人工介入环节脚本只能提示风险不能替代肉眼判断。4. 避坑从二值类别陷阱到 anchor 策略的五个教训4.1 标注类别名为 dianta但 yaml 里写成了英文这份数据集的类别名就是拼音dianta不是tower或electric_pylon。很多人拿到手习惯性把 yaml 写成names: [tower]结果训练集加载时 txt 里第一个数字是 0 没问题但可视化预测时类别名显示不对。更致命的是如果你改动了类别 id 映射关系比如把 names 列表重新排序所有 txt 的 class_id 都要跟着改——这份数据只有 1 个类别所以 id 永远是 0问题不大但一旦你把它合并进其他数据集训练多类别模型这个坑就出来了。提示data.yaml 里 names 的索引顺序必须和 txt 里的 class_id 一一对应。单类别时 names: [dianta] 就是唯一正确写法不要自作聪明去改成别的名字。4.2 零字节 txt 与空标注图划分数据集时被忽略现象训练集 mAP 正常验证集 mAP 变成 0排查半天发现验证集里混了一张没有任何标注的图。原因空 txt 文件不报错YOLO 会把它当背景图训练但验证集如果空标注比例高mAP 计算会异常。解决划分数据集前先过滤掉空标注文件或者把空标注图单独放到一个子目录里不参与训练和验证。import os from pathlib import Path empty_txts [f for f in os.listdir(.) if f.endswith(.txt) and Path(f).stat().st_size 0] print(f空标注文件数: {len(empty_txts)}) for f in empty_txts: os.rename(f, f.replace(.txt, _empty.txt))这个操作是把空标注文件改名让它在训练集划分时不会被当成普通标注。更严谨的做法是把对应 jpg 也从训练列表里删掉——背景图虽然能提高模型对负样本的判别力但在小数据集上背景图占比过大会加剧正负样本不平衡。4.3 坐标零点索引与 1 索引混用现象训练出来的模型预测框整体往右下偏移约 1 到 2 个像素。原因XML 里 bndbox 的坐标是 0 索引像素从 0 开始计数但某些转 YOLO 格式的脚本里做了xmin 1或xmax 1的偏移修正导致两个版本坐标不一致。解决转格式时只用一套标准这里 VOC 转 YOLO 的公式是x_center ((xmin xmax) / 2) / img_width不接受任何额外偏移修正。这个坑极其隐蔽因为 1 到 2 个像素的偏移在推理可视化时肉眼看不出大问题但如果你做的是电力部件检测这种对定位精度敏感的任务或者后续要做目标追踪、测距那这个误差会累积。我的判断标准很简单同一张图的 xml 和 txt 反算坐标差的绝对值超过 1 个像素就要检查转换脚本。4.4 训练时不看样本图直接开跑现象训练 50 个 epoch 后 loss 不下降可视化预测结果发现所有预测框都框在了空白区域或树冠上。原因电塔在遥感图像中呈现明显的灰白色几何结构但如果训练集里包含大量大尺寸遥感影像直接缩放的样本电塔的纹理细节已经被压缩成噪点了。解决训练前用可视化脚本把标注画到原图上抽查 20 到 30 张确认标注框贴合电塔轮廓、没有出现“框住了半座塔”的情况。import cv2 import xml.etree.ElementTree as ET img_file firc_yaogan_10.jpg xml_file firc_yaogan_10.xml img cv2.imread(img_file) tree ET.parse(xml_file) root tree.getroot() for obj in root.findall(object): bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) cv2.rectangle(img, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2) cv2.putText(img, dianta, (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(visual_check.jpg, img)这种可视化检查是训练前唯一靠谱的“人工抽检”手段脚本自动画框到图上绿色框如果基本贴合塔身轮廓标注质量就算过线。如果出现大量框只覆盖了塔的一半或者框里包含了大片天空背景就要考虑重新标注或删掉异常样本。4.5 小目标检测的 anchor 策略选错电磁塔在遥感影像中的典型尺寸是 40×80 像素而 YOLOv8 默认的 anchor 设置是针对 COCO 数据集的。COCO 里的小目标也有 32×32 以上如果你直接按默认参数训练下采样 32 倍后的特征图输入 640×640 时是 20×20上每个像素对应原图 32×32 的区域电塔这种 40×80 的物体在最大的特征图上还能分到 1 到 2 个像素其实很勉强。经验做法是把输入尺寸提到 960 或 1280或者用 SAHISlicing Aided Hyper Inference做切片推理。5. YOLOv8 训练自己的遥感数据集从环境配置到超参调优5.1 数据集划分stratify 保持正样本比例244 张图划分成 train 和 val 时不能简单按文件顺序前 200 后 44因为遥感图像切片之间往往存在空间相关性连续编号的图片可能来自同一个大图的不同切片如果所有训练样本都来自前半部分、验证样本都来自后半部分地理位置分布上天然有偏移。正确做法是随机打乱后按比例划分。import os import random from pathlib import Path img_files [f for f in os.listdir(.) if f.endswith(.jpg)] random.seed(42) random.shuffle(img_files) val_ratio 0.2 val_count int(len(img_files) * val_ratio) val_files img_files[:val_count] train_files img_files[val_count:] with open(train.txt, w) as f: for img in train_files: f.write(str(Path(img).resolve()) \n) with open(val.txt, w) as f: for img in val_files: f.write(str(Path(img).resolve()) \n) print(ftrain: {len(train_files)}, val: {len(val_files)})这个脚本生成 YOLO 风格的 train.txt 和 val.txt里面写的是图片的绝对路径。YOLOv8 支持直接传 txt 文件作为训练数据路径也可以把图片放在 images/train 和 images/val 子目录下配 labels 目录两种方式都行。我更推荐用 txt 的方式因为遥感数据集经常需要做多种划分实验txt 改动成本最低。这里的 random.seed(42) 是固定随机种子保证每次划分结果一致方便对比实验。如果你需要保证验证集里有足够的正样本即包含至少一个框可以在划分前先统计每张图是否为空标注把空标注图全部留在训练集。这份数据集如果所有图都有标注那就无所谓。5.2 配置 data.yaml 与模型 yamlYOLOv8 训练需要准备一个 data.yaml内容是数据集路径、类别数和类别名。这里有个细节路径可以写相对路径但强烈建议写成绝对路径尤其是你后续要把代码放到服务器上跑的时候相对路径频繁报错会浪费大量时间排错。# data.yaml path: /home/user/dianta_dataset # 数据集根目录 train: train.txt # 训练集图片列表 val: val.txt # 验证集图片列表 nc: 1 # 类别数 names: [dianta] # 类别名称训练命令可以这样起。--rect是矩形训练开关允许同一 batch 内的图片保持原始宽高比做 letterbox而不是全部缩放到 640×640这对分辨率不统一的遥感数据集很有帮助能减少背景拉伸带来的标注偏移。yolo detect train datadata.yaml modelyolov8n.pt epochs100 imgsz640 batch16 rectTrue patience20参数说明imgsz640是输入尺寸电塔目标较小如果显存允许可以改 960patience20是早停轮数小数据集上训练 100 轮通常 30 到 40 轮就开始收敛patience 设太小容易在 loss 还在振荡时提前停掉modelyolov8n.pt是权重初始化文件用预训练权重做迁移学习比从头训练收敛快得多但要注意预训练权重的类别数和你数据集的类别数不一致也没关系YOLOv8 会自动替换最后一层分类头。5.3 必要的超参调整关闭 mosaic 前后的对比小数据集训练遥感小目标最值得调的超参是mosaic。YOLOv8 默认开启 mosaic 数据增强将 4 张图拼成一张好处是增加了样本多样性坏处是拼接后小目标被进一步缩小且标注边界在拼接缝处容易出现错位。244 张图本身就不大mosaic 对训练集多样性的提升有限但对小目标特征学习的干扰是实打实的。# 训练时通过参数关闭或调整 yolo detect train datadata.yaml modelyolov8n.pt \ epochs100 imgsz640 batch16 \ mosaic0.0 close_mosaic10 scale0.3 hsv_h0.01 hsv_s0.5 hsv_v0.3参数说明mosaic0.0表示完全关闭 mosaic 增强close_mosaic10是最后 10 个 epoch 强制关闭 mosaic默认就有这个机制但如果前面已经关闭就不用设scale0.3控制随机缩放比例对电塔这种尺寸变化不大的目标 0.3 足够太大反而会把塔拉伸变形HSV 增强参数全部调小遥感图像的颜色偏移本身就不大过度的颜色扰动会让模型学到错误的色彩特征。我的经验是在小数据集上mosaic 从 1.0 降到 0.0 通常带来 2 到 3 个点的 mAP 提升代价是训练收敛速度略慢。如果显存够大也可以把scale0.9配合多尺度训练让模型适应不同分辨率的输入但这在小数据集上效果不稳定容易过拟合。5.4 训练过程中看什么指标mAP50 与 loss 曲线小数据集训练最忌讳死盯测试集 mAP因为验证集只有 44 张图单张图的预测好坏就能让 mAP 波动 5 个点。应该关注的是训练日志里的box_loss和cls_loss曲线——两条曲线在 30 到 50 轮左右不再下降且进入平台期说明模型基本学满了如果 box_loss 还在继续下降但 val/box_loss 开始上升就是过拟合信号用patience20早停即可。在推理阶段推荐用置信度 0.25 作为初始阈值。遥感小目标天然容易被漏检置信度阈值调太高会丢框调太低会冒出大量误检。先跑一遍验证集看F1-Confidence曲线取 F1 最高的置信度作为你实际使用的阈值这是调阈值最科学的方式——不靠感觉靠数据说话。测试单张图推理命令yolo predict modelruns/detect/train/weights/best.pt sourcefirc_yaogan_88.jpg conf0.25 iou0.56. 从复现到迁移坐标映射、切片推理与格式互转实操6.1 用 xml 直接画框脱离 YOLO 框架的验证训练前和训练后都要画框验证。训练前画框是为了检查标注质量训练后画框是为了对比预测结果和真实标注的差异。如果不想每次都用 OpenCV 写脚本可以直接用 labelImg 打开原图和 xml 查看。标注后的可视化不仅是一个流程它能让你一眼看出框的贴合度——电塔的塔身是细长桁架结构框如果只有 30×30 像素基本可以确定只框了塔顶没有框全塔身。6.2 训练后迁移到 5000×5000 大图的切片推理这份数据集的原图应该是截取后的样本图意味着你的模型在真实推理时面对的是更大的遥感影像。比如 5000×5000 的分片直接喂给 YOLO 是行不通的——显存放不下而且 640×640 的输入下电塔目标会缩小到十几个像素甚至消失。标准做法是滑窗切片推理把大图切成 640×640 的块每块之间保留 20% 的重叠所有块都独立推理后再把框映射回原图坐标做 NMS。import cv2 import numpy as np from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) img_large cv2.imread(large_tile_5000x5000.jpg) H, W img_large.shape[:2] crop_size 640 stride int(crop_size * 0.8) all_boxes [] for y in range(0, H - crop_size 1, stride): for x in range(0, W - crop_size 1, stride): crop img_large[y:ycrop_size, x:xcrop_size] results model(crop, conf0.25, imgsz640, verboseFalse) for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) all_boxes.append((x x1, y y1, x x2, y y2, conf)) # 全局NMS去除重叠区域重复框 from ultralytics.utils.ops import non_max_suppression # 简单做法直接把所有框转成 tensor 再过一次 NMS boxes_np np.array(all_boxes, dtypenp.float32) # 这里省略 tensor 转换实际使用 torch 的 NMS 更高效 for x1, y1, x2, y2, conf in all_boxes: cv2.rectangle(img_large, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 3) cv2.imwrite(predicted_large.jpg, img_large)这段代码里的stride crop_size * 0.8是关键参数——重叠率 20% 是为了防止电塔恰好被切在滑窗边缘的情况。重叠率越高召回率越好但推理耗时也线性增长。电塔这类目标尺寸较大20% 重叠够用如果做绝缘子这种极小目标重叠率要提到 50%。6.3 一份标签同时用于两个框架VOC 转 YOLO 的脚本备份最后给你留一个通用工具把之前的 VOC 格式 xml 重新转成 YOLO txt。有时候从网上下载的数据集可能只有 VOC 格式没有 YOLO 格式或者你想拆了重新划分训练集时发现 txt 路径不对这个脚本就能救急。我把这个脚本存成voc2yolo.py每次拿到新数据集第一件事就是跑一遍输出格式统一了再进训练管线。import xml.etree.ElementTree as ET import os CLASS_MAPPING {dianta: 0} def convert_annotation(xml_file, output_dir): tree ET.parse(xml_file) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) txt_path os.path.join(output_dir, xml_file.replace(.xml, .txt)) with open(txt_path, w) as f: for obj in root.findall(object): name obj.find(name).text if name not in CLASS_MAPPING: continue cls_id CLASS_MAPPING[name] bndbox obj.find(bndbox) xmin int(bndbox.find(xmin).text) ymin int(bndbox.find(ymin).text) xmax int(bndbox.find(xmax).text) ymax int(bndbox.find(ymax).text) x_center ((xmin xmax) / 2) / img_w y_center ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h f.write(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}\n) if __name__ __main__: output_dir yolo_labels os.makedirs(output_dir, exist_okTrue) for xml_file in os.listdir(.): if xml_file.endswith(.xml): convert_annotation(xml_file, output_dir) print(f转换完成: {xml_file})这段代码用的是 0 索引坐标转归一化中心点坐标没有做任何 1 或 -1 的偏移修正。如果你从网上下载的 VOC 数据集转出来的 YOLO 格式始终偏 1 到 2 个像素多半是源标注工具用了 1 索引这种情况下要统一在 xmin、ymin 上减 1xmax、ymax 不变其他都不动。我试过各种偏移组合只有这一种能完美对齐 labelImg 的标注。这个脚本我每次拿到新数据集都会强制走一遍然后立刻用第 3 章的可视化脚本抽查输出。从那以后因为格式问题导致的训练事故率基本归零了。224 张也好244 张也好数据规模不重要标注格式的严谨程度才决定你后面要花多少时间在排错上。希望这份数据集的拆解对你有用。本文还有配套的精品资源点击获取