基于YOLOv8的火星月球陨石坑小样本检测实践
2026/9/23 12:36:21 网站建设 项目流程

简介:面向目标检测研究与计算机视觉实践的火星月球陨石坑检测数据集,包含132张陨石坑图像,统一标注为单一类别“keng”,共1044个矩形框,适合用于YOLO、SSD、Faster R-CNN等模型的训练与算法验证。数据同时提供Pascal VOC格式xml和YOLO格式txt两种标注,可相互对应、直接使用,免去格式转换步骤,对初学者和科研人员都很友好。压缩包内共398个文件,以jpg原图、xml标注和txt标注为主,整体仅10.27MB,轻量易下载;标注由labelImg工具按统一矩形框规则完成,框体类别命名规范,便于训练前快速检查与筛选。目前已有243人浏览学习,适合天文图像分析、遥感影像识别及目标检测方向的课程设计、毕业设计或课题预研使用;对于入门者,可直接将txt标注导入YOLO项目完成一次完整训练,直观感受陨石坑检测流程。

1. 火星月球陨石坑检测数据集:132张图、1044个框,够不够训YOLOv8?

这个数据集打包名里写着"VCO",其实指的是Pascal VOC格式,解压后你会看到标准的VOC目录结构和YOLO格式的txt标注同时存在。132张火星月球表面影像,1个类别"keng",也就是陨石坑,总共1044个矩形框。这个量级对目标检测来说是小样本,但正因为小,反而能逼着你把数据增强、迁移学习、训练参数这些环节吃透。它适合三类人:想跑通自定义YOLO训练流程的新手、做行星遥感目标识别的学生、以及需要用少量标注验证检测方案可行性的工程师。我用YOLOv8n在这个数据集上做过完整实验,接下来的格式解析、数据划分、训练调参和验证技巧,都是实际跑过之后沉淀下来的做法。

2. 双格式标注解析:VOC XML与YOLO txt的读取和一致性校验

拿到压缩包第一步不是急着训练,而是先把标注文件的结构看清楚。解压命令在Linux下要注意文件名里的中文和空格,必须用引号包住:

7z x "火星月球陨石坑检测数据集VCO+YOLO格式132张1类别.7z" -o./crater_dataset cd ./crater_dataset && find . -type f | head -20

-o指定输出目录,find列出前20个文件用于确认是否解压完整。这个数据集每个样本由三件套组成:jpg原图、同名xml标注、同名txt标注。我在实际项目中还遇到过解压后文件损坏的情况,所以建议顺手做一次哈希校验:

sha256sum "火星月球陨石坑检测数据集VCO+YOLO格式132张1类别.7z"

记录下原始哈希值,解压后再比对。如果校验不一致,说明7z包在下载或传输过程中已经有字节位翻转,直接训练会导致标签和图像错位。

2.1 文件构成与命名关系

下表是单个样本的文件对应关系:

文件示例内容
firc_yunshi_27.jpg原始遥感影像裁剪图
firc_yunshi_27.xmlPascal VOC格式矩形框标注
firc_yunshi_27.txtYOLO格式归一化坐标标注

命名规律是firc_yunshi_编号,三个文件前缀完全一致,这样才能被后续训练脚本自动匹配。标注工具是labelImg,标注规则是对陨石坑画矩形外接框,类别名统一写keng。这里有一个容易忽略的点:labelImg导出VOC时会在xml里同时写入图片尺寸信息,而YOLO txt只存归一化比例,不存尺寸,所以千万不能只拿txt去做其他格式的转换,否则一旦图片被缩放,框会全部错位。

2.2 用Python解析VOC XML

我一般会先写一个VOC解析函数,把xml里的坐标和类别读出来:

import xml.etree.ElementTree as ET def parse_voc(xml_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') width = int(size.find('width').text) height = int(size.find('height').text) boxes = [] for obj in root.iter('object'): name = obj.find('name').text 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) boxes.append((name, xmin, ymin, xmax, ymax)) return width, height, boxes w, h, boxes = parse_voc('annotations/firc_yunshi_27.xml') print(w, h, boxes)

这段代码拿到图像原始宽高,以及每个目标框的绝对像素坐标,坐标原点在左上角,x向右递增,y向下递增。医学影像和遥感影像的标注规范不同,但voc格式是通用的。我这里用root.iter('object')而不是root.findall,是因为某些标注工具会在object节点下多套一层,iter能避免漏读取。

2.3 解析YOLO txt的归一化坐标

YOLO txt每行有五个字段:class_id x_center y_center width height,全部是相对图像宽高的比例。读取逻辑很简单:

def parse_yolo(txt_path): rows = [] with open(txt_path, 'r') as f: for line in f: line = line.strip() if not line: continue parts = line.split() rows.append({ 'class_id': int(parts[0]), 'x_center': float(parts[1]), 'y_center': float(parts[2]), 'width': float(parts[3]), 'height': float(parts[4]) }) return rows

注意这里widthheight是归一化后的框宽、框高,不是实际像素。如果jpg是1920x1080,txt里写0.5、0.5、0.1、0.1,对应实际中心点(960,540)、实际宽192、高108。由于陨石坑形状接近圆形,宽高比接近1,但有些坑因为拍摄角度和地形起伏会变成椭圆,所以不能假设宽高相等。

2.4 双格式一致性校验

LabelImg的VOC转YOLO脚本经常出现坐标精度丢失,比如把浮点转成int时边界溢出,或者某个框在xml里有但在txt里丢了。我建议训练前做一次全量对比,把xml里的绝对坐标转成归一化坐标后和txt逐字段比对:

import glob errors = [] for xml_path in sorted(glob.glob('annotations/*.xml')): txt_path = 'labels/' + xml_path.split('/')[-1].replace('.xml', '.txt') width, height, xml_boxes = parse_voc(xml_path) txt_boxes = parse_yolo(txt_path) if len(xml_boxes) != len(txt_boxes): errors.append((xml_path, 'box count mismatch', len(xml_boxes), len(txt_boxes))) continue for box, tbox in zip(xml_boxes, txt_boxes): xmin, ymin, xmax, ymax = box[1], box[2], box[3], box[4] x_center = (xmin + xmax) / 2.0 / width y_center = (ymin + ymax) / 2.0 / height w_box = (xmax - xmin) / width h_box = (ymax - ymin) / height if (abs(x_center - tbox['x_center']) > 1e-4 or abs(y_center - tbox['y_center']) > 1e-4 or abs(w_box - tbox['width']) > 1e-4 or abs(h_box - tbox['height']) > 1e-4): errors.append((xml_path, 'coordinate mismatch')) print('error count:', len(errors))

误差阈值设到1e-4,是因为常见的转换脚本会把坐标格式化成%.6f。如果你的txt里出现了整型坐标,比如0 500 300 200 150,说明转换脚本没有归一化,后面的训练一定会出现loss不降或预测框偏移的问题。

3. 小样本划分与数据增强:把132张陨石坑图用到极限

只有132张图,如果直接随机划分,很容易让某一边的陨石坑框数严重失衡。比如验证集分到几张包含40个坑的图,指标波动就会极大。我在类似的小样本遥感数据集上,通常按框数而不是图片数来做分层采样。

3.1 按框数比例划分训练集与验证集

先统计每张图的框数,然后按框数从多到少排序,取前80%的图进入训练集,剩余的进验证集:

import glob from collections import Counter samples = [] for xml_path in sorted(glob.glob('annotations/*.xml')): width, height, boxes = parse_voc(xml_path) samples.append((xml_path, len(boxes))) samples.sort(key=lambda x: x[1], reverse=True) train_ratio = 0.8 train_num = int(len(samples) * train_ratio) train_files = [x[0] for x in samples[:train_num]] val_files = [x[0] for x in samples[train_num:]] print('train images:', len(train_files), 'total boxes:', sum(x[1] for x in samples[:train_num])) print('val images:', len(val_files), 'total boxes:', sum(x[1] for x in samples[train_num:]))

这样划分后训练集和验证集的框数比例接近80/20,比随机切图要稳定得多。固定排序逻辑后,不需要随机种子也能复现。这里有一个坑:如果数据集的图来自不同探测器序列,比如firc_yunshi_这段前缀暗示了来源批次,最好按前缀做组划分,避免同一个批次的相似图像同时出现在训练和验证中,否则测出来是假高分。

3.2 数据增强参数:从翻转、HSV到马赛克

YOLOv8的ultralytics仓库把数据增强封在了训练流程里,不需要自己写离线增强代码。但参数怎么调对单类小样本影响很大。我常用的一组参数如下:

参数含义小样本建议值
flipud随机上下翻转概率0.5
fliplr随机左右翻转概率0.5
hsv_h色调增强幅度0.02
hsv_s饱和度增强幅度0.8
mosaic马赛克增强概率0.8
translate平移增强比例0.2
scale缩放增强比例0.6

火星月球陨石坑没有固定朝向,上下翻转是安全的。hsv_h不要调太大,因为遥感影像的光谱特征对模型识别坑壁阴影很重要,颜色失真严重会让模型学到错误的纹理。mosaic增强能有效增加单张图的上下文多样性,但在框数特别少时,马赛克会拼接出大量空白区域。如果训练初期loss降不下去,把mosaic降到0.5再观察。

3.3 用脚本把VOC统一转成YOLO格式

如果未来想加入更多类别,或者需要从其他数据源补充图片,手动用labelImg逐个导出太低效。我自己习惯写一个通用的VOC转YOLO脚本:

def voc_to_yolo(xml_path, class2id): width, height, boxes = parse_voc(xml_path) lines = [] for name, xmin, ymin, xmax, ymax in boxes: if name not in class2id: continue x_center = (xmin + xmax) / 2.0 / width y_center = (ymin + ymax) / 2.0 / height box_w = (xmax - xmin) / width box_h = (ymax - ymin) / height lines.append( f"{class2id[name]} {x_center:.6f} " f"{y_center:.6f} {box_w:.6f} {box_h:.6f}" ) return lines class2id = {'keng': 0} for xml_path in train_files: txt_path = 'labels/train/' + xml_path.split('/')[-1].replace('.xml', '.txt') lines = voc_to_yolo(xml_path, class2id) with open(txt_path, 'w') as f: f.write('\n'.join(lines))

注意x_center:.6f这种格式化写法,保留六位小数,刚好和前面校验脚本的1e-4阈值对应。如果没有做格式化,直接写原始浮点数,最终写入txt的可能是17位小数,读回时有微毫差,不影响训练,但会干扰严格的一致性校验。

4. 用YOLOv8训练"keng"类别:从data.yaml到loss曲线

这个数据集只有1个类别,用YOLOv8n就足够,不需要上YOLOv8x。在普通消费级显卡上,132张图100个epoch只需要几分钟,非常适合做参数实验。

4.1 编写data.yaml

先把数据整理成ultralytics要求的目录结构:

crater_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── crater.yaml

然后写crater.yaml

path: /workspace/crater_dataset train: images/train val: images/val nc: 1 names: 0: keng

这里path建议写绝对路径,否则在IDE或远程服务器上跑容易因为当前工作目录不同而报错找不到图片。trainval相对于path来解析。我实际踩过坑:把labels文件夹写成了label,结果ultralytics检查不到标注文件,训练直接退出。yaml里的键名必须用labels而不是annotations

4.2 训练命令与关键参数

在ultralytics的CLI下训练:

yolo detect train \ model=yolov8n.pt \ data=crater.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ lr0=0.005 \ device=0 \ patience=20 \ augment=True

参数说明如下:

参数作用调参建议
model=yolov8n.pt加载COCO预训练权重小样本首选n或s,x会过拟合
epochs=100训练轮数100轮足够,看patience触发
imgsz=640训练输入尺寸小坑多就试1280
batch=16批量大小显存允许就往上加
lr0=0.005初始学习率少于50张图降到0.003
patience=20早停耐心值防止后期过拟合
augment=True开启内置增强关闭可快速调试代码流程

加载COCO预训练权重本质上是迁移学习。虽然COCO里没有陨石坑,但预训练模型在前几层学到的是边缘、角点、纹理这些底层视觉特征,对小样本任务非常关键。如果从随机权重开始训,132张图根本学不出稳定的特征表示。

4.3 损失函数与训练日志

YOLOv8的损失函数由三个分量组成:box_loss是CIoU边界框回归损失,cls_loss是二分类交叉熵损失,dfl_loss是Distribution Focal Loss,用来细化框的坐标分布。训练日志长这样:

Epoch GPU_mem box_loss cls_loss dfl_loss Instances Size 10/100 2.1G 1.104 0.876 1.233 38 640 20/100 2.1G 0.932 0.451 1.021 41 640

看loss曲线时要注意:如果box_loss持续下降但dfl_loss不降,说明中心点定位已经不错,但框的宽高还不够精确。陨石坑边界在光照下会产生阴影,标注人员对阴影边缘的取舍会导致框线不一致,这种噪声会明显体现在dfl_loss上。如果cls_loss降得非常快而box_loss很慢,说明模型已经轻松区分前景和背景,但框的回归还需要更长训练。

训练结束后,runs/detect/train/weights/下会生成best.ptlast.pt,后面对验证集评估用的就是best.pt

5. mAP评估与过拟合控制:在单类别小数据集上的实用调参

单类检测任务没有类别间混淆,mAP@0.5是主要参考指标,但也更容易被过拟合欺骗。表现为训练loss持续降低,验证集mAP却停在某个值不再上涨,甚至开始下降。

5.1 用val命令生成指标和混淆矩阵

yolo detect val \ model=runs/detect/train/weights/best.pt \ data=crater.yaml \ conf=0.25 \ iou=0.5

conf是置信度阈值,低于0.25会被丢掉;iou是NMS去重阈值。评估结果里需要重点看两个数字:mAP50和mAP50-95。对于小样本数据集,mAP50的涨落比mAP50-95更大,因为坐标的小偏差对mAP50影响小。如果你发现mAP50高但mAP50-95低,说明框的定位精度不够,优先检查dfl_loss和相关损失权重。

5.2 单类别模型的边界框问题

因为只有一个类别,混淆矩阵只有前景和背景两类。此时最值得关注的是召回率。很多坑在图像里只占几十像素,imgsz=640时这些大图被缩放到640,小坑特征已经模糊。我一般会用imgsz=1280再训练一次对比。如果mAP50-95提升超过3个百分点,说明原模型输在输入分辨率上,不是模型能力问题。另外,遥感影像的坑经常出现在暗色阴影区域,置信度阈值设0.25可能漏检,可以降到0.1专门看漏检情况。

5.3 一个降低过拟合的技巧:冻结主干微调

132张图不能充分微调整个预训练网络,冻结早期层能保留预训练模型学到的通用边缘和纹理特征,只更新后面的检测头。用ultralytics的Python API可以这样控制:

from ultralytics import YOLO model = YOLO('yolov8n.pt') model.train( data='crater.yaml', epochs=100, freeze=10, name='freeze_head' )

freeze=10表示模型前10层参数不参与反向传播,只更新后面与检测任务相关的层。冻结层数不是越大越好,坑的纹理信息集中在中间层,如果冻结到20层,可能把陨石坑特有的阴影纹理也冻住了。建议分别试freeze=5freeze=10freeze=15,在验证集上选mAP最高的那个档位继续调。

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

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

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

立即咨询