☰
火星月球陨石坑检测:VCO转YOLO格式与YOLOv8训练实战
2026/10/10 20:16:46 网站建设 项目流程

简介:面向目标检测初学者与行星科学图像分析人员的火星月球陨石坑识别数据集,采用Pascal VOC与YOLO双格式组织,适合训练陨石坑检测模型、验证数据标注流程或开展行星表面特征识别实验。压缩包约10.27MB,共398个文件,主体为132张jpg图像、132个xml标注文件和132个txt标注文件,其中xml便于Pascal VOC工具链读取,txt可直接用于YOLO系列模型训练。标注类别唯一(keng),由LabelImg工具手工绘制矩形框,总计1044个框,覆盖不同尺寸与形态的陨石坑样本;仅提供矩形框检测标注,不包含分割掩码。目前已有244人浏览学习,适用于需要快速获取规范标注数据、搭建YOLO或Faster R-CNN等检测模型训练流程的开发者与研究者。

1. 火星月球陨石坑检测数据集VCO+YOLO格式:132张能训出能用的检测器吗

做深空探测背景下的目标检测,最难的不是模型选型,而是手里没有数据。这个火星月球陨石坑检测数据集VCO+YOLO格式132张1类别.7z,解压之后就是132张带标注的火星/月球表面影像,标注格式同时给了一份VCO(VOC风格的XML)和一份YOLO(TXT)标注,类别只有1个:crater(陨石坑)。132张图单类别,听起来少得可怜,但反直觉的是,只要你会用迁移学习、会处理小数据集的增广和验证策略,这个量级完全能训出一个在同类影像上可用的检测器,甚至能拿来跑通完整的“数据标注→格式转换→训练→评估”管线。适合三类人:刚入行遥感目标检测的学生、需要快速验证检测方案的行星科学研究者、以及想用手头小数据集练手YOLO的工程师。这篇文章就按我自己的操作习惯,从格式解析一路写到训练和避坑。

2. 数据集长什么样:VCO与YOLO格式的字段解析和标注边界

2.1 目录结构与文件清单:一张图对应一个标注文件

拿到 .7z 压缩包解压后,常见做法是里面会有两个文件夹,一个放图片,一个放标注。VCO 格式沿用了 PASCAL VOC 的目录习惯,通常长这样:

. ├── JPEGImages/ # 存放 132 张原始影像,jpg/png 均有可能 │ ├── mars_001.jpg │ ├── moon_001.jpg │ └── ... ├── Annotations/ # VCO 标注:每张图一个同名 XML │ ├── mars_001.xml │ ├── moon_001.xml │ └── ... ├── labels/ # YOLO 标注:每张图一个同名 TXT │ ├── mars_001.txt │ ├── moon_001.txt │ └── ... └── data.yaml # 作者可能已经给了一份 YOLO 训练配置

如果解压后没有 data.yaml,也不影响,稍后自己写一份就行。这里的关键是一个对一:JPEGImages/mars_001.jpg对应Annotations/mars_001.xml,也对应labels/mars_001.txt。文件名的前缀必须完全一致,后缀不算,否则训练阶段会“找不到标注”直接跳过这张图。

VCO 的 XML 标注,核心字段我拆给你看:

<annotation> <folder>JPEGImages</folder> <filename>mars_001.jpg</filename> <size> <width>1024</width> <height>768</height> <depth>3</depth> </size> <object> <name>crater</name> <bndbox> <xmin>230</xmin> <ymin>440</ymin> <xmax>520</xmax> <ymax>640</ymax> </bndbox> </object> </annotation>

这里的<size>是整个影像的宽高,不是陨石坑的尺寸。<bndbox>里存的是绝对像素坐标,左上角 (xmin, ymin) 和右下角 (xmax, ymax)。一张图里如果有多个陨石坑,就会出现多个<object>块。这个 XML 结构是所有转换脚本的基础,你只靠读它就能拿到全部标注信息。

YOLO 格式的 TXT 就简单得多,每一行代表一个目标,格式是:

class_id x_center y_center width height

全部是归一化数值,范围 0~1。比如上面那个 crater 框,在 1024x768 图上换算成 YOLO 格式就是:

0 0.3662 0.7031 0.2832 0.2604

四个小数分别对应中心点 x、中心点 y、框宽、框高,各自除以图片宽度和高度。注意宽高是除以各自维度:宽除以 width,高除以 height,不是都除以同一个数。这一点很多人第一次写转换脚本时容易搞混。

2.2 VCO 与 YOLO 格式的核心差异:归一化坐标与类别编号

VCO 和 YOLO 不只是文件格式不同,它们的坐标体系和语义逻辑是两个路子:

对比项VCO(XML)YOLO(TXT)
坐标基准绝对像素坐标归一化相对坐标
边界框表示左上角+右下角 (xmin,ymin,xmax,ymax)中心点+宽高 (x_center,y_center,w,h)
类别表示字符串,如<name>crater</name>整数索引,如 0
缩放鲁棒性图缩放后坐标失效图缩放后坐标仍然有效
一张图多个目标多个<object>块多行文本

为什么 YOLO 要刻意用归一化坐标?因为它把检测当作回归问题,目标框的预测值本身就是在特征图上输出的相对偏移量。如果标注用绝对像素,训练时输入图片一 reshaped,标注就全错位了;而归一化坐标只跟长宽比例有关,跟实际分辨率无关,模型在不同分辨率输入下迁移时,不需要重新标注。

类别编号是个容易踩的隐性坑。VCO 里类别是“crater”这个字符串,转成 YOLO 时必须把它映射成整数。单类别数据集通常从 0 开始编号,也就是crater -> 0。但有些转换脚本习惯性写成 1,如果你再用 YOLOv8 默认的classes: 1配置去训,模型会认为类别 id 为 1,而标注文件里恰好是 1 的话,它能跑,但类别语义就错位了;更常见的是标注文件里是 0,配置文件里写 1,导致训练直接报错或者所有预测都落在错误的类别通道上。所以拿到数据集第一步,先统计一下 TXT 里每一行的第一个数字是 0 还是 1,再决定 data.yaml 怎么写。

另外注意 VCO 的<bndbox>坐标不一定严格落在图像范围内。某些影像边缘的陨石坑只露出一半,标注工具会直接标到边缘甚至超出几像素。这类框不处理,轻则训练损失波动,重则 YOLO 在输出层对宽高取 log 时计算出 NaN,模型直接报废。转换时统一做一次坐标修剪是必须的。

3. 把VCO转成YOLO格式:最小转换脚本与归一化坐标的坑

3.1 转换脚本:解析XML并写出txt

既然标题里同时给了 VCO 和 YOLO 两份标注,理论上你不需要自己转。但如果作者给的 YOLO 标注有缺失,或者你想改成别的类别编号顺序,还是得自己动手写转换脚本。这也是拿到任何 VOC 系数据集后必做的一步。

下面是我常用的最小转换脚本,只依赖 Python 标准库,不需要装额外包:

import os import xml.etree.ElementTree as ET JPEG_ROOT = "JPEGImages" # 图片目录,用于读取宽高 ANNO_ROOT = "Annotations" # VCO XML 目录 LABEL_ROOT = "labels" # 输出的 YOLO txt 目录 CLASS_MAP = {"crater": 0} # 类别名 -> 编号,单类别从0开始 os.makedirs(LABEL_ROOT, exist_ok=True) def convert_xml_to_yolo(xml_path): tree = ET.parse(xml_path) root = tree.getroot() # 图片宽高必须从XML的<size>读,不要自己去读图片 size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.findall("object"): name = obj.find("name").text if name not in CLASS_MAP: continue # 跳过未定义类别 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) # 坐标修剪:把越界部分裁回图像范围内 xmin = max(0.0, min(xmin, img_w - 1)) xmax = max(0.0, min(xmax, img_w - 1)) ymin = max(0.0, min(ymin, img_h - 1)) ymax = max(0.0, min(ymax, img_h - 1)) # 绝对坐标 -> 中心点 + 宽高的归一化 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 # 极端情况:修剪后宽高为0,跳过该目标 if w <= 0 or h <= 0: continue class_id = CLASS_MAP[name] lines.append(f"{class_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") return lines # 批量转换 for xml_name in os.listdir(ANNO_ROOT): if not xml_name.endswith(".xml"): continue yaml_name = xml_name.replace(".xml", ".txt") lines = convert_xml_to_yolo(os.path.join(ANNO_ROOT, xml_name)) # 即使没有任何目标,也要写空txt,否则训练时该图无标注会报错 with open(os.path.join(LABEL_ROOT, yaml_name), "w") as f: f.write("\n".join(lines))

这段代码有四个地方值得说明:

  • 图片宽高直接从 XML 的<size>读,不要去读图片文件。因为 XML 里的宽高是标注那一刻的原始分辨率,如果你用 OpenCV 去读缩放后的图,会尺寸不一致。
  • 坐标修剪用了img_w - 1是因为像素坐标最大到 width-1,直接用img_w会导致归一化后出现 1.0 的边界值,YOLO 解码时没问题,但某些数据增强会越界。
  • 写文件时用continue跳过修剪后宽高为 0 的目标。这种目标多半是横贯整张图的狭长条,比如影像拼接缝上的伪影,保留它只会让模型去学一条线。
  • 空文件也必须生成。YOLO 训练时如果某张图没有对应的 txt,会被当作未标注数据跳过;如果 txt 文件存在但内容为空,则会被当作背景图参与训练,这两种语义完全不同。

3.2 边界框出界与空标注:转换时最容易翻车的两个细节

先说出界。火星和月球的影像常常是轨道器拼接出来的,陨石坑正好卡在影像边缘的概率不低。标注员点框的时候习惯往内收一点,但自动标注程序不会,它给出的是几何上完整的框。转换脚本里如果没做 clip,训练到一半 loss 变成 nan,排查一圈发现是某个 txt 里出现了1.0345这样的数值。YOLO 对宽高做对数变换时,输入负数或零直接崩溃。所以无论你拿到的标注看起来多“干净”,转换后都要跑一条统计命令验证:

awk '{if($3>1||$4>1||$3<0||$4<0) print $0}' labels/*.txt

如果输出不为空,说明有越界值,返回头去修转换脚本,而不是手改 txt。

再就是空标注。132 张图不可能每张都有陨石坑,总会有几张是纯背景。VCO 里这种图就是<annotation>下面没有<object>的 XML。统计一下:grep -L "<object>" Annotations/*.xml。如果有这种图,转换脚本必须照样生成一个 0 字节的 txt。很多人写转换脚本时如果没目标就continue跳过写文件,结果训练时 YOLO 读到一张没有任何 txt 的 JPEG,官方 Dataloader 会输出 warning 然后跳过这张图,图片数量从 132 变成 120 多,自己还不知道。

更微妙的是“有 XML 但 XML 为空”和“XML 里 object 为空”的区别。某些标注软件生成的 XML 里object块存在但没有实际的bndbox,上面脚本用obj.find("bndbox")会返回 None,然后.text直接报 AttributeError。稳妥的做法是加一个判断:if bbox is None: continue。我在实际数据集转换中遇到过不下三次这类问题。

4. 用YOLOv8在132张图上训练陨石坑检测器:配置与参数

4.1 准备data.yaml与第一次训练命令

格式转换完成后,就该进训练了。现在做目标检测,我一般直接选 YOLOv8,原因很实在:API 干净、默认超参数对中小数据集友好、训练和验证一条命令能跑完。你不需要先从分类任务做迁移再转检测,YOLOv8 的预训练权重本身就包含大量通用视觉特征。

首先写 data.yaml:

path: /your/dataset/root # 数据集的顶级目录,改成你自己的绝对路径 train: images # 训练集图片目录,相对path的路径 val: images # 验证集图片目录,没有独立验证集就用同一目录 nc: 1 # 类别数:只有陨石坑一类 names: ['crater'] # 类别名列表,顺序必须与标注txt中的class_id一致

这里有几个细节。132 张图的数据集,如果按常规 8:2 划分,训练集 106 张、验证集 26 张,你训练时 loss 曲线会非常抖,因为验证集太小。常见做法是先不分独立测试集,用全部 132 张训练,训练完成后把模型在原始大图上做推理做人工目检。如果要给论文写 mAP,那就做 K 折交叉验证,或者至少保证验证集里的陨石坑数量分布和训练集一致,而不是随机抽几张图。

然后运行训练:

yolo detect train \ model=yolov8n.pt \ data=data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=30 \ project=crater_det \ name=run1

这条命令里model=yolov8n.pt会自动下载官方 COCO 预训练权重,第一次运行需要联网。epochs=100对 132 张图来说不是要训 100 轮,而是要看验证集 mAP 的早停;patience=30表示 30 轮验证指标不上升就停止。batch=16在显存不足时降到 8 或 4,不要硬撑。

训练输出的关键日志在crater_det/run1/下,包括weights/best.pt、weights/last.pt、PR_curve.png、confusion_matrix.png、results.csv等。判断模型好坏不要只看 loss,要看mAP50-95和mAP50。陨石坑这种近似圆形的目标,mAP50 比 mAP50-95 高 0.15~0.2 都算正常,因为 IoU 阈值越严格,对小目标越苛刻。

4.2 针对小数据集的5个必调参数:epochs、batch、imgsz、优化器、冻结层数

小数据集训练最忌讳按默认参数一把梭。以下 5 个参数我每次都会调:

epochs。132 张图的微小数据集,100 轮足够。但要注意 YOLOv8 默认的epochs=100配合cos_lr=True,学习率会在后 1/4 轮次降到很低。如果你的模型在 70 轮时已经 mAP 不再上升,可以手动epochs=70重训,省时间且降低过拟合风险。不要天真地认为 300 轮一定比 100 轮好,小数据集上多余的轮次只是在记忆噪声。

batch。显存够的情况下尽量大,但不要因为显存大就无脑上 32。小数据集的梯度本身噪声就大,batch=32 会让每个 epoch 的更新次数太少。我通常 132 张图配 batch=16,相当于一个 epoch 只有 8 步更新,配合 shuffle 刚好合适。

imgsz。火星影像动不动是几千像素,直接imgsz=640会把陨石坑缩得很小,容易漏检。我一般先统计标注框的像素宽度分布:python -c "...",如果大多数框在原图上的尺寸小于 40x40,建议imgsz=1024甚至1280。代价是训练慢一倍。但深空影像的陨石坑轮廓清晰、对比度好,640 和 1024 之间的精度差距往往比想象中更大。显存不够就用rect=True让 YOLO 按原始宽高比做矩形训练,减少空白填充。

优化器。YOLOv8 默认optimizer='auto',实际会选 SGD。小数据集上我倾向显式设optimizer='AdamW',因为 AdamW 对初始学习率不那么敏感,收敛更稳定。但 AdamW 在后期容易刹不住,配合weight_decay=0.0005和cos_lr=True用。如果你想跟论文里的 SOTA 对比,再换回optimizer='SGD'。

冻结层数。这是小数据集迁移学习最关键的一步。YOLOv8 的骨干网络yolov8n.pt已经学到了边缘、纹理等底层特征,我们不需要它在陨石坑数据上重新学这些。加一个参数freeze=10,冻结前 10 层(骨干网络的大部分),只训练颈部检测头。效果是收敛速度快一倍,且不容易把预训练特征冲掉。冻结层数不是越大越好,冻结 20 层相当于只训检测头,用于 132 张图这种量级反而更稳。我会先跑freeze=0和freeze=10各一轮,对比验证集 mAP 再定。

5. 避坑与常见问题:标注错位、类别失衡、过拟合排查

5.1 现象:训练损失不降,mAP一直为0

现象:训练到 20 轮,loss 几乎不下降,验证集 mAP 一直是 0,偶尔输出 “All classes omitted from graph” 的警告。

原因:标注文件与图片不匹配。最常见是转换脚本把 XML 的坐标除以了错误的宽高,导致所有框的归一化坐标集中在图片某条边上;或者 data.yaml 里的nc与 txt 中的类别编号不一致,模型输出的类别通道是 0,但真实标注是 1,永远不会匹配。

解决:不要先调模型,先检查标注。写一段代码读任意一张图的 txt,把五个数字乘回图片宽高,用 OpenCV 画框看一下位置是否合理。再统计所有 txt 的类别编号 max 值是否等于nc-1。如果转换脚本里类别从 1 开始,把 CLASS_MAP 里的值改成从 0 开始,重新生成 txt。

5.2 现象:验证集mAP很高,但实际影像上检测不到目标

现象:训练日志里验证集 mAP50 到 0.9,可拿一张全新的火星影像去推理,一个陨石坑都检不出来,要么漏检要么框全在岩石阴影上。

原因:数据划分方式有问题。132 张图如果随机划分训练集和验证集,同一张原图被裁剪成不同 patches、或者同一个陨石坑出现在多张图的情况会同时存在于训练和验证集里,模型靠记忆就能拿高分;换成新影像,记忆失效。这是小数据集的“伪精度”陷阱。

解决:按“场景”划分数据,而不是按文件随机划分。火星和月球影像通常一个场景包含多张连续图,先按场景分组,再组间划分。如果原始数据没有场景信息,就人工检查验证集中的图与训练集是否来自同一块区域,有重叠就换掉。

5.3 现象:模型把岩石阴影当成陨石坑

现象:推理结果里出现大量误检,误检目标大多是明暗对比强烈的岩石阴影或地形边缘,形状也接近椭圆。

原因:132 张图的正样本数量太少,陨石坑的正样本特征没有盖过“暗色圆形区域”这种泛化模式。模型倾向于学习形状+对比度,而不是陨石坑特有的环形山边缘结构。

解决:做数据增广时不要只做翻转和旋转,要专门加入对比度扰动、伽马校正、光照方向模拟。陨石坑在太阳高度角不同时形状完全不同,用hsv_v=0.4和degrees=180能有效扩充阴影变化。另外可以考虑加入“背景负样本”图,也就是没有陨石坑的火星影像,标注为空 txt,让模型学会抑制这种地形。

5.4 现象:大尺寸影像训练时内存溢出或训练极慢

现象:原图是 2000x2000 左右,直接imgsz=1280训练,显存跑满,batch 调到 2 还 OOM;或者训练很慢,一个 epoch 几分钟。

原因:大图直接缩放会丢失小陨石坑信息,不缩放大图又没法装进显存。

解决:常见做法是“切块”。把原图切成 640x640 或 1024x1024 的 patches,带重叠率 10%,同时把 annotation 的坐标相对切块后的图像重新计算。132 张大图切成几百张小图,数据量反而更健康。这个操作要在转换脚本里做,不建议在训练时用 YOLO 的imgsz硬塞。切块时注意不要切断陨石坑,切断的框保留但要 clip。

5.5 现象:loss下降正常,但PR曲线面积很小

现象:训练 loss 正常下降到收敛,但验证集 PR_curve.png 里曲线快速掉到低 recall,mAP50 只有 0.4 左右。

原因:正负样本极度不平衡。陨石坑检测里大部分锚点都是背景,默认的 BCE 损失会让模型偏向“预测为背景”。另外小目标在特征图的下采样层可能只剩 2~3 个像素,特征弱。

解决:开启focal_loss_gamma参数(YOLOv8 中可设fl_gamma=1.5),让模型关注难分类的硬负样本。同时检查你用的模型尺寸,yolov8n的特征图对 1024 输入下采样到 32x32,单个陨石坑可能只有一个锚点,换成yolov8s或加一层的浅层 P2 检测头会明显改善小目标 recall。

6. 验证模型和进阶:mAP计算、置信度阈值选择、数据增广技巧

6.1 用YOLO自带工具导出PR曲线与混淆矩阵

训练结束后的模型往往不是最优置信度阈值下的表现,不要直接拿conf=0.25的默认结果去交差。用验证集评估:

yolo detect val \ model=crater_det/run1/weights/best.pt \ data=data.yaml \ conf=0.01 \ iou=0.5

这里刻意把conf=0.01调低,是为了让模型把低置信度框也输出来,从而画出完整的 PR 曲线。看crater_det/run1/val/PR_curve.png,在曲线上找 “置信度大于某个值且 precision 接近 1” 的拐点,然后把那个置信度作为实际部署阈值。对于陨石坑检测,我通常选择 precision 大于 0.9 时对应的置信度,哪怕 recall 低一点,也比让误检干扰后续科学分析的强。

6.2 三个让132张图出更好效果的小技巧:Mosaic关闭、冻结层、TTA

针对极小数据集,我有三个固定的操作习惯。

第一,训练时把 Mosaic 增强关掉或降频。YOLOv8 默认启用了mosaic=1.0,它把四张图拼成一张,对小数据集来说很容易让标注框被裁剪掉一半,模型反而学到奇怪的拼接缝。我习惯设mosaic=0.5,让一半 epoch 走正常增强。如果发现训练损失震荡严重,直接mosaic=0.0。

第二,冻结层不要训到最后。先用freeze=15训练 30 轮,让检测头收敛,然后解冻全部层,用小学习率lr0=0.0001再训 20 轮。这种两阶段训练法比从头到尾用一个学习率在小数据集上的效果更稳定,而且能避免早期把预训练特征冲掉。

第三,推理时开启 TTA。YOLOv8 自带的测试时增强会做多尺度缩放和翻转,对遥感影像的检测有明显提升。用yolo predict时加tta=True,模型会对同一张图做 3 个尺度推理,再把结果融合。代价是推理时间变为 3 倍,但陨石坑检测这种离线分析场景完全能接受。

最后说一个我自己的教训:小数据集训练时,不要看到测试集 mAP 稍微涨一点就切换超参数。132 张图的验证结果噪声非常大,同一组参数跑两遍,mAP 波动可能超过 0.05。我的习惯是固定一个配置至少跑 5 遍,取验证集 mAP 的中位数作为该配置的指标,再决定要不要改参数,而不是被单次训练的运气带着走。

希望这篇能帮你把火星月球陨石坑检测数据集跑通。数据少不是问题,问题是你敢不敢用小数据的方式去训练。

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

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

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

立即咨询