ARTICLE DETAIL

资讯详情

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

遥感图像电塔检测数据集:VOC与YOLO双格式实战指南

遥感图像电塔检测数据集:VOC与YOLO双格式实战指南 简介面向遥感目标检测任务提供两百四十四张含电塔的遥感影像采用Pascal VOC与YOLO双格式标注类别仅dianta共五百零四个真实框适合快速验证YOLOv5、YOLOv8等主流检测流程。压缩包内共七百三十四个文件包含两百四十四张JPG原图、两百四十四份XML格式VOC标注文件、两百四十四份TXT格式YOLO标注文件另配两份说明文件整体大小约七十八点四兆。所有标注均由labelImg按矩形框规则完成类别与框数一一对应目录结构简洁用户可直接按比例划分训练集、验证集与测试集。目前已有两百八十一人浏览学习可据此开展电塔巡检、输电线路隐患识别等遥感实验对刚接触目标检测的开发者而言也能通过对比VOC与YOLO标注文件结构理解两种主流数据组织方式的差异。1. 遥感图像电塔检测数据集为什么244张图也能撑起一趟训练做输电线路巡检的工程师拿到航拍影像第一眼要找的是电塔——但卫星和无人机拍回来的图里电塔常常只有几十个像素混在植被、阴影、农田纹理里普通人用肉眼都要想一下“那是塔还是树”更别说让目标检测模型直接干活。市面上公开的遥感检测数据集大多是飞机、船舶、油罐这类大目标专门针对电塔的很少自己标注一张图就要画几十个框标完还得转格式、分训练集时间全耗在准备数据上。这正是“遥感图像电塔检测数据集VOCYOLO格式244张1类别”这个包存在的意义244张图、单类别“电塔”、同时给VOC和YOLO两套标注拿到手就能喂给训练脚本。它的体量不大但对“先跑通流程、再谈精度”的起步场景绰绰有余。2. VOC和YOLO两套标注格式格式差异、转换脚本与解析核对2.1 两种格式的命门XML存绝对坐标TXT存归一化坐标VOC格式的核心是每张图对应一个同名XML文件里面用bndbox记录xmin/ymin/xmax/ymax四个绝对像素值单位是“像素”坐标原点在图像左上角。读XML的人不需要关心图片尺寸框的坐标是写死的。YOLO格式则完全不同每张图对应一个TXT文件每行五个数依次是类别ID、x_center、y_center、width、height全部除以图像宽高做了归一化。假设一张1920×1080的图里有个电塔左上角在(480, 360)、右下角在(720, 540)那么YOLO格式写的就是0 0.3125 0.4167 0.125 0.1667。归一化坐标的好处是训练时无论模型把输入resize成640还是1280框的相对位置都不会变。早期YOLO系列v3/v5只吃TXT格式而Faster R-CNN、SSD以及MMDetection这类框架的习惯入口是VOC的XML目录结构。两种格式都给省掉的正是“训练A框架前先写脚本转标注”这一步。同时暴露两者也方便你在换框架时对照验证转换逻辑有没有写错——这是很多数据集包不做的事。2.2 用Python把VOC转成YOLO转换脚本、类别表与坐标截断如果你拿到的版本只有VOC标注或者需要把这份数据移植到另一个项目里最稳妥的方式是自己写一个可控的转换脚本而不是依赖在线工具——在线转换一旦出错你很难定位是哪张图被改坏了。import os import xml.etree.ElementTree as ET from pathlib import Path voc_root Path(VOCdevkit/VOC2007) yolo_root Path(yolo_labels) yolo_root.mkdir(exist_okTrue) # 只保留电塔这一类ID固定为0后续类别从1开始递增 class_id {electric_tower: 0} for xml_path in voc_root.glob(Annotations/*.xml): tree ET.parse(xml_path) root tree.getroot() size root.find(size) img_w int(size.find(width).text) img_h int(size.find(height).text) if img_w 0 or img_h 0: print(f跳过 {xml_path.stem}: 图像尺寸为0) continue lines [] for obj in root.iter(object): name obj.find(name).text.strip() if name not in class_id: continue # 非电塔类别直接忽略 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) # 越界框先截断再归一化避免宽高出现负值 xmin max(0, min(xmin, img_w - 1)) ymin max(0, min(ymin, img_h - 1)) xmax max(xmin 1, min(xmax, img_w - 1)) ymax max(ymin 1, min(ymax, img_h - 1)) x_center (xmin xmax) / 2 / img_w y_center (ymin ymax) / 2 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{class_id[name]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) if lines: out_file yolo_root / f{xml_path.stem}.txt out_file.write_text(\n.join(lines), encodingutf-8) print(转换完成共处理, len(list(voc_root.glob(Annotations/*.xml))), 个XML)这段脚本的核心逻辑是先读size拿到真实宽高再遍历所有object取bndbox的四角做归一化。注意两个参数细节——class_id字典决定了类别ID顺序一旦确定就别再改否则训练出来的权重类别会错乱越界截断这一步不能省遥感影像里电塔常被裁到图边缘标注软件偶尔会给出超出图像范围的坐标不截断会让宽高算成负数训练时直接报错。转换完成后建议随机挑三张图做一个“叠框验证”把TXT里的归一化坐标还原成像素值用OpenCV画回JPEG原图肉眼比对是否和原目标吻合。这个步骤能一次性抓出坐标系搞反、宽高写成绝对值这类低级错误。2.3 从TXT反向推导VOC还原则、边界补齐与双向校验反向转换用得少但换到MMDetection或Faster R-CNN时就得写。import os def yolo_to_voc(txt_path, img_w, img_h): with open(txt_path) as f: lines f.readlines() objects [] for line in lines: parts line.strip().split() if len(parts) ! 5: continue cid, xc, yc, w, h map(float, parts) xmin int((xc - w / 2) * img_w) ymin int((yc - h / 2) * img_h) xmax int((xc w / 2) * img_w) ymax int((yc h / 2) * img_h) # 还原后越界坐标裁回有效范围 xmin max(0, xmin) ymin max(0, ymin) xmax min(img_w, xmax) ymax min(img_h, ymax) objects.append({name: electric_tower, bbox: (xmin, ymin, xmax, ymax)}) return objects还原时最容易翻车的是坐标类型转换。TXT里的x_center是浮点乘上img_w后必须取整但取整方向会影响框大小几像素——对电塔这种动辄几十像素的大目标无所谓对后续要做实例分割任务就不够严谨。另一个易错点是VOC的xmax/ymax是包含边界像素的而YOLO的宽度是“右减左”两者天然差1像素。批量转换时别纠结这1像素YOLO训练会做随机平移裁剪模型根本感知不到。双向校验的做法是同一张图VOC格式算一遍、TXT还原一遍逐框计算IoU全部大于0.95才算转换正确。这比抽查一两张图可靠得多。3. 把244张图整理成能直接训练的数据集目录结构、划分脚本与检查清单3.1 解压后的目录动线JPEGImages、Annotations与labels如何对接拿到.7z压缩包第一步是解压并确认目录结构。7z x 遥感图像电塔检测数据集VOCYOLO格式244张1类别.7z tree -dtree -d的输出里应该能看到两类核心目录VOC风格的是JPEGImages/244张.jpg和Annotations/244个.xml外加一个ImageSets/Main/存划分清单YOLO风格的是images/和labels/后者放244个.txt。两类文件的名字一一对应——IMG_001.jpg对应IMG_001.xml和IMG_001.txt这是YOLO系列训练脚本查标注文件的默认约定任何一张图缺了同名标注训练时会直接跳过并在日志里报“label not found”。确认对应关系用一条命令最快ls JPEGImages/ | sed s/.jpg// jpg_names.txt ls Annotations/ | sed s/.xml// | sort xml_names.txt ls labels/ | sed s/.txt// | sort txt_names.txt diff jpg_names.txt xml_names.txt diff jpg_names.txt txt_names.txt三份文件名列表两两diff没有任何输出就说明对应关系完整。这一步比看任何README都实在。3.2 按8:2划分训练集与验证集固定随机种子或按场景分组244张图全量训练不现实需要划分。最省事的做法是纯随机抽样但遥感影像有个天然陷阱同一架无人机航拍的连续帧相邻图像高度相似如果随机划分时不加控制验证集里可能出现和训练集几乎一模一样的图导致验证指标虚高——这是这类小数据集的第一个隐形坑。import random import shutil from pathlib import Path src_images Path(JPEGImages) src_labels Path(labels) train_dir Path(dataset/images/train) val_dir Path(dataset/images/val) train_label_dir Path(dataset/labels/train) val_label_dir Path(dataset/labels/val) for d in [train_dir, val_dir, train_label_dir, val_label_dir]: d.mkdir(parentsTrue, exist_okTrue) image_files sorted(src_images.glob(*.jpg)) # 固定随机种子让划分结果可复现 random.seed(42) random.shuffle(image_files) val_count int(len(image_files) * 0.2) # 49张做验证195张做训练 val_files image_files[:val_count] train_files image_files[val_count:] for img in train_files: shutil.copy(img, train_dir / img.name) shutil.copy(src_labels / f{img.stem}.txt, train_label_dir / f{img.stem}.txt) for img in val_files: shutil.copy(img, val_dir / img.name) shutil.copy(src_labels / f{img.stem}.txt, val_label_dir / f{img.stem}.txt) print(f训练集 {len(train_files)} 张, 验证集 {len(val_files)} 张)这里有两个值得落地的参数选择。random.seed(42)是固定随机种子保证任何人用同一份数据执行同一脚本得到完全一样的划分——你后面调模型、改参数做对比实验时只有划分一致指标差异才能归因到模型本身而不是数据。val_count int(len * 0.2)取20%当验证集对小数据集来说验证集再小的话mAP曲线抖动会大到没法判断模型到底有没有变好。如果你知道这244张图来自哪几条线路更稳妥的划分是按“线路分组”而不是按“文件随机”分——把整条线路的图全放进训练集或验证集这样验证集衡量的是模型对新场景的泛化能力而不是对同一场景的“背答案”。具体做法是把文件名前缀如line03_提取出来做分组再按组划分。3.3 训练前的90秒体检空标签、类别越界、图像损坏划分完成不等于可以开训。YOLO训练时最烦的并不是模型不收敛而是数据文件本身就带病。from pathlib import Path label_dirs [Path(dataset/labels/train), Path(dataset/labels/val)] bad_cases [] for label_dir in label_dirs: for txt_path in label_dir.glob(*.txt): with open(txt_path) as f: lines [line.strip() for line in f if line.strip()] if not lines: bad_cases.append((txt_path.stem, 空标签文件)) for line in lines: parts line.split() if len(parts) ! 5: bad_cases.append((txt_path.stem, f列数不对: {line})) continue cid int(parts[0]) if cid ! 0: bad_cases.append((txt_path.stem, f类别ID越界: {cid})) vals list(map(float, parts[1:])) if any(v 0 or v 1 for v in vals): bad_cases.append((txt_path.stem, f归一化坐标超界: {line})) if bad_cases: for stem, reason in bad_cases: print(f{stem}: {reason}) else: print(全部标签文件体检通过)体检脚本检查三件事文件是否为空空TXT会让YOLO训练器把该图当作“无目标”图处理从而拉低置信度、每行是否恰好5列、归一化坐标是否落在0到1之间。任何一个异常都意味着标注文件残损挂着病训练轻则部分图白算重则loss整体被带偏。体检通过之后再启动训练如果模型还是不收敛至少可以排除数据格式问题把矛头指向模型参数。4. 用YOLO跑通电塔检测训练配置、损失函数与关键参数取值4.1 框架选型Ultralytics YOLO、MMDetection和yolov5各自的取舍针对这份244张的VOCYOLO双格式数据集主流选型有三个方向。Ultralytics YOLO即YOLOv8及其后续版本安装成本最低一条pip install ultralytics就能用data.yaml写好后一条命令开训适合第一次跑YOLO的新手YOLOv5是存量项目最多的老将教程多坑早被人踩平了缺点是新特性迭代慢MMDetection的优势在于训练可控性最强但环境配置复杂对只跑一份小数据集的团队来说性价比偏低。我的日常选择是Ultralytics YOLO——不是因为它精度最高而是这份数据集的瓶颈明显不在模型结构而在数据规模用最成熟的训练链路先把基线跑出来比纠结模型骨架更实际。等基线稳定后如果你想在结构上找提升可以再走efficient head这类轻量化改进方向但那是后话。4.2 写data.yaml与启动训练单类别配置、imgsz与epochs的参数语义YOLO训练第一步是写数据配置文件。针对这个数据集data.yaml里最关键的是names列表必须与标注的类别ID严格对齐。# 数据集根目录 path: ./dataset train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 # 单类别检测ID 0 对应电塔 nc: 1 names: 0: electric_tower训练命令用Ultralytics的标准入口yolo train data./data.yaml modelyolov8s.pt epochs200 imgsz640 batch16 patience50拆开说参数含义。modelyolov8s.pt表示以YOLOv8s的预训练权重为起点继续训练——遥感电塔和COCO里的“交通灯”“消防栓”结构上并不相似但预训练权重的底层纹理特征仍然值得继承这叫迁移学习。imgsz640是输入分辨率对电塔检测来说这个值偏保守但稳妥如果你后续用SAHI切片推理训练时也可以用640推理时把大图切块放大两者并不冲突。epochs200是最大训练轮数配合patience50做早停——如果验证集mAP连续50轮不提升训练自动终止。小数据集上一百轮左右通常已经收敛200轮是给模型一个“慢慢找”的上限空间。batch16的取值取决于显存。如果你的显卡只有8G显存batch16 imgsz640差不多是YOLOv8s的极限显存不够就降到8不要用batch2去硬撑——batch太小会让BatchNorm统计量不稳定遥感图像又背景复杂loss曲线会抖得像心电图。4.3 损失函数视角为什么电塔这类小目标吃分类损失回归权重怎么调训练启动后终端会实时打印box_loss、cls_loss、dfl_loss三个指标。很多人只看总loss下降就撒手不管但电塔检测恰恰需要分别盯这三项。cls_loss是分类损失衡量模型“把塔认成塔”的能力——电塔在遥感图上背景多样分类错判往往是“把粮仓、烟囱当成塔”box_loss是边界框回归损失衡量框得准不准——电塔细长、塔尖窄框偏几像素IoU就掉一大截。YOLO系列在回归分支用的损失函数随版本不同有差异但核心思想都是IoU及其变体。对小目标检测场景常规IoU损失对“预测框和真值框只有几个像素重叠”的情况梯度太小模型学不动。如果你发现box_loss收敛到0.03左右就再也降不下去可以试着把损失函数换成NWDNormalized Wasserstein Distance这类对小目标更友好的变体。NWD的核心思路是用分布距离替代IoU相似度对几个像素的偏移不再“一棒子打死”是我的经验里对遥感小目标提升最直接的一招。具体做法是用Ultralytics的loss参数或改损失函数模块不同版本入口不同但方向是对的。5. 电塔检测数据集的5个常见坑标注错位、小目标漏检与过拟合5.1 图像尺寸不一致导致标注错位现象训练正常跑完推理时框明显偏离电塔位置——有的框在塔尖上方有的框缩在塔身一半的位置。原因244张图来自不同传感器部分图是2048×1536部分图是1024×768。VOC格式的XML存的是绝对像素如果直接把不同尺寸的XML混合喂给YOLO训练而中间没有读size字段做归一化模型学到的框位置就是错位的。解决统一过一遍第2节的转换脚本确认每张图的XML里width/height和实际JPEG尺寸一致不一致的以JPEG实际尺寸为准重新计算。转换后抽10张图叠框检查别省这一步。5.2 电塔在图中占比小mAP50-95上不去现象mAP50已经到0.9看着挺漂亮但mAP50-95只有0.3上下换不同阈值IoU后精度断崖式下跌。原因mAP50允许预测框和真值框有大量重叠误差而mAP50-95对框的精准度极其苛刻。电塔在遥感图上通常只占几百个像素640分辨率下更是只有几十个像素高框偏5个像素IoU就从0.7掉到0.5以下。解决训练时用高分辨率输入比如imgsz1280推理时用SAHI切片把原图切成多个带重叠的patch分别推理再合并。如果你愿意折腾算法第4节提过的NWD损失函数对这类小目标回归也有实际提升。5.3 背景中“长得像塔”的目标被误检现象推理结果里烟囱、水塔、高压线杆甚至一排整齐的树被标成电塔precision低得没法看。原因244张训练图里电塔占了绝大多数正样本但负样本几乎没有——模型没有见过足够多“不是塔但长得像塔”的东西学到的判别特征不够。解决给训练集补充背景图把所有不包含电塔的遥感图裁成小块塞进images/train并生成空的标签TXT文件让模型把这些区域学会判为背景。另一个更省事的方向是收集一些公开遥感检测数据集如HRSC2016里的港口、船舶切片作为负样本混入训练只看效果不论出处好用就行。5.4 数据划分不当导致验证集形同虚设现象训练时验证集mAP100%稳定一部署到新线路的影像上就掉到0.6。原因数据划分偷懒用了纯随机划分同一无人机航线的连续帧高度重叠验证集和训练集“长得几乎一样”模型等于把验证答案提前背下来了。解决回到第3节改成分组划分——按拍摄任务、航线文件或时间戳把图片分组保证同一组的图只进入训练集或只进入验证集。这是小样本遥感项目里最容易让人误判模型真实水平的一个坑我在这上面翻过车现在每次划分数据都先看文件名前缀再动手。5.5 244张图训练几十轮后过拟合train loss低但val loss反弹现象训练日志显示train loss一路降到0.02但val loss从第80轮开始反弹验证集mAP波动。原因数据量太小模型参数远多于数据携带的信息学完电塔“共性”之后开始背具体图像细节。解决一是开启早停patience50不行就降到30让训练在val loss拐点处停下来二是给训练加增广——YOLO默认的Mosaic增广在这类遥感图上有时反而有害因为切碎的色块会更像其他地物对单类别的电塔检测来说关闭Mosaic换成随机翻转、色彩抖动、平移缩放效果往往更稳。记住这个原则小数据集上克制增广比堆叠增广更重要。6. 让244张图发挥更大价值数据增广、迁移学习与验证闭环见过太多人拿到小数据集就急着加数据——爬图、标框、扩充到几千张结果模型没提几个点时间烧掉两周。244张图只要策略对一样能训出一个够用的电塔检测器。第一个技巧是给增广做减法。训练前先把mosaic关掉scale区间控制在0.5到1.0之间hsv_h调大到0.02——遥感影像的地物颜色本来就五花八门让模型适应不同光照色偏比让它看拼贴碎片有用得多。我的经验是基于预训练权重的小数据训练增广的“多样性收益”和“信息破坏成本”几乎五五开保守的增广配置反而让val loss更稳定。第二个技巧是管好迁移学习的“冻结期”。预训练权重虽然是在自然图像上训的但前几层的边缘、纹理特征对遥感图依然有效。训练时前30轮freeze10冻结backbone前10层让检测头先把电塔的类别语义学明白30轮后解冻全部参数用小学习率微调backbone的高层特征。这个先粗后细的节奏能让244张图在200轮内的收敛质量接近翻倍。第三个技巧是验证闭环。训练结束后不要只看验证集的mAP数字就收工——从验证集里挑出那些包含多塔、小塔、塔与阴影重叠的“困难图”把推理结果叠加画回原图逐张看框的位置和置信度。这个动作能暴露出mAP曲线掩盖的问题比如塔间距近导致框合并、低置信度误检漏检等。我会把这类图像单独留一个hard_samples目录每次调整模型后都重新推理一遍对比新旧版本在同一批图上的表现。这个习惯帮我规避了至少三次“指标没变但实际效果变差”的假性提升。最后一点教训做小数据集项目一定要在第一天就把随机种子、数据划分版本、预训练权重版本全部固定下来否则你做的每一次实验对比都没有可比性损失函数、增广参数的调优全是浪费。我早期就是吃了这个亏同一个模型跑两遍结果差出3个点以为是算法问题最后发现是划分变了。希望帮到你。本文还有配套的精品资源点击获取
返回列表