最开始接到化石识别需求的时候,我其实是有点心虚的。化石不像猫狗,没有清晰的标准正面照,很多标本就埋在杂乱的岩石背景里,边缘和基质颜色几乎融为一体;同一个种在不同保存状态下可能长成完全不同的外形,不同种在局部特征上又可能高度趋同。当时我们手里的标本照片和扫描图像加起来不到三千张,类别却分了二十多种,用传统图像处理做特征工程基本等于给自己挖坑。团队讨论了几轮之后,把方向定在目标检测上,然后顺理成章走到了YOLO这一系。这篇文章就把整个“化石识别与分类:数据集构建与多YOLO模型训练实践”的过程拆开讲,从数据整理、标注格式、模型选择、调参,到最后的评估和部署,给正好也在碰这类小样本、高相似度识别任务的朋友做个参考。
1. 化石识别为什么直奔目标检测
1.1 从图片分类到目标检测:一个换框的转变
我最开始其实想偷个懒。化石识别听起来就是“给一张图,判断里面是什么”,那直接用图像分类网络不就行了?但这个想法在拿到真实野外图像之后马上被否掉了。一张照片里经常有好几个目标,有的完整贝体占了大半幅画面,有的只是碎石中露出的硬币大小的断面。如果对整图做分类,模型会很自然地学习岩石背景的颜色、光照阴影和整体纹理,因为化石本身占的像素比例太小,对最终类别的贡献被稀释掉了。
所以我们把问题重新定义成目标检测:模型不但要输出这是哪一类化石,还要输出这个化石在图像里的位置。表面上看只是多了一组边界框,实际上训练时的特征学习逻辑完全变了。检测框会迫使网络把注意力集中在局部区域,背景干扰被约束在框外。对化石这种边界模糊、和围岩同色的对象,这种约束非常关键。另外,目标检测天然支持一图多目标,不需要像分类那样先把每个标本裁出来,一张密集排列多个化石的图板也能一次处理完。
1.2 化石图像识别真正的难点在哪里
化石不是自然图像里的猫猫狗狗,它有几个非常实际的问题。
一是类内差异极大。同一属种的化石,因为保存状态、风化程度、出露角度不同,外形可以差很多,幼体标本和成年体经常看起来像两个物种。二是类间差异在某些局部特征上极小。不同属的腹足类和双壳类,外轮廓接近的时候,只能靠壳顶方向、生长线密度这种非常细微的结构来区分,图像分辨率只要低一点,模型就很容易错。三是类别数量天然不平衡。常见类有几百个样本,冷门类只有三五张图,连做训练集都很勉强。
还有一个容易漏掉的问题:化石照片很多是带着比例尺、标签卡、工具锤一起拍的原始记录图。如果这些元素不处理干净,模型很容易把纸片标签识别成化石目标,或者把比例尺当作帮助分类的背景特征,一旦换了拍摄方式就立刻失灵。这一类问题在数据预处理阶段就要解决,不能只靠模型自己学会忽略。
1.3 多YOLO模型对比的初衷
为什么选YOLO,而不是Faster R-CNN或者DETR?不是它们不好,而是化石识别是一个典型的资源有限、还要到现场部署的工程场景。YOLO系列最大的优势是生态完整,从训练到导出,再到ONNX、TensorRT部署,整个链路都有成熟工具支撑。同一个框架里又能方便地切换不同版本、不同规模的模型,特别适合我们这种需要做版本选型的项目。
YOLOv5是anchor-based的代表,稳定、社区资料最多;YOLOv8改成anchor-free加解耦检测头,通用目标上通常更稳;YOLOv9引入了一些梯度信息相关机制,在小样本任务上表现时好时坏。与其在纸面上争哪个模型理论更强,不如在同一份化石数据上老老实实跑一遍。多YOLO模型对比不是“哪个分数高用哪个”,而是看在同样的数据划分下,哪个模型收敛更稳定、对低质量图像更鲁棒、部署成本更低。化石识别没有公开benchmark,我们只能自己当裁判。
2. 数据是化石模型的地基,也是最费时间的一环
2.1 三类图像源与统一预处理流程
实践中化石图像来源比想象中杂很多,我们手里的数据大致分成三类。
第一类是野外现场拍摄,手机或者微距相机都有,常见问题是白平衡严重偏色,尤其黄光下拍出来的照片整体发黄。第二类是标本馆扫描或显微镜拍摄,分辨率高,但每张图的比例尺不统一,有的还带着原始编号标签。第三类是文献图版和公开图库翻拍,风格五花八门,一般只作为补充数据,需要注意版权和学术引用边界。
预处理我建议只做四件事:统一EXIF方向、去重、直方图匹配、分桶管理。去重不只是去掉完全一样的图,更重要的是去掉同一化石在同一角度下连拍的照片,保留一张质量最高的;但不同角度的图不能删,它们记录了形态变化,对模型学习类内差异很有帮助。直方图匹配可以缓解不同光源带来的色偏,但化石的灰度信息非常微妙,我不建议做二值化或者过分锐化,那样会把关键纹理细节抹掉。最后一定要分桶:每个标本或者采集点分配一个组ID,后续划分训练集和验证集时会非常依赖这个字段。
2.2 类别体系怎么定,直接决定训练难度
类别标签设计是整个项目里最考验领域知识的环节。我们刚开始想按生物学分类的“属”来做标签,很快发现有些属的样本数量只有个位数,模型根本学不出来。后来换成“属级别+形态型”的混合策略。
具体规则可以这样定:
- 数据量足够的属,直接用属名作为标签;
- 数据量不足但外轮廓高度相近的,合并成“未知双壳类”“未知腹足类”这样的合并类;
- 同一属内保存状态差异大到肉眼能区分的,单独建一个形态型标签,让网络先把不同形态各自学出来;
- 实在无法归类的破碎标本,统一标“未知类”,不要硬塞给某个具体类别。
还要保证标签互斥。同一张图里的同一个化石,不能既标成A又标成B,交叉部位或半暴露标本,宁可标成“未知类”,也不要给模型输出自相矛盾的监督信号。一个经验阈值是:如果某个类别的标注样本少于50个,在二十几个类别里基本会被强类压垮,建议先合并。
2.3 标注规范与一致性:三个容易被低估的细节
标注是化石识别项目里最耗时、也最容易返工的环节。我们一开始用开源标注工具快速标了一批,后来发现两个人标同一张图,框的交并比经常只有0.55,模型训练时框都在抖,指标自然上不去。后来花了两天时间把标注规则彻底定死,才慢慢稳定下来。
第一,定义“什么东西值得标”。我们当时统一的规定是:目标面积小于整体可见面积的20%不标;破碎到无法判断类别的标本不标;边缘和围岩粘连到无法确认边界的,宁可保守一点只标可辨认部分,也不能凭感觉框一个完整轮廓。第二,确定框的原则。对化石这种不规则轮廓,我推荐用外接矩形,不要试图贴合轮廓。YOLO的矩形框是特征裁剪工具,不是分割工具,外接框效率最高,只要包含主要可辨识部位就行。第三,双人复核。每一批标注完,第二个人随机抽20%复核,两个人的平均IoU低于0.5就必须重新培训规范。这个动作直接决定后面mAP能不能稳定在0.7以上。
2.4 数据增强:学过拟合,不如学“合成”
小样本数据集的增强策略非常讲究。常用的Mosaic增强会把四张图随机拼成一张,变相提高每个batch里的小目标数量,对化石密集场景特别有效。另一个我更推荐的技术是Copy-Paste:把标注好的目标区域抠出来,贴到新的裸岩或基质背景上,同时调整亮度、对比度、饱和度,让它看起来不那么突兀。这等于增加了场景多样性,而不是简单在原图做几何变换。
但增强绝不是越猛越好。我踩过坑,把同一张原图增强出几十个版本丢进训练集,结果验证集指标虚高,换一张没见过的图立刻露馅。模型记住的是固定的旋转角度和亮度组合,而不是真实的化石特征。所以增强后的副本需要控制在合理范围,原始图像也要始终保留在训练批次里。对少数类,单独做Copy-Paste,把多数类控制在合理范围,否则类别失衡会被增强进一步放大,少数类反而更难学。
3. 多YOLO模型同台训练,差的不只是版本号
3.1 训练环境、预训练权重和数据切分
先交代训练环境。我们用的是PyTorch和YOLO官方仓库的Release版本,框架版本固定,避免模型实现细节漂移。显存24GB,输入分辨率经过初期试验确定在1280×1280,batch size开不到32就加梯度累积,显存不够就降到16。预训练权重统一用COCO预训练模型,而不是从头训练,这个决定对化石识别尤其关键,因为数据量小,从头训练很难收敛到有用的特征空间。
数据切分是这一章里最值得强调的:一定不能按文件名随机分配。化石数据有大量同一标本的多角度照片,同一块化石如果同时出现在训练集和验证集,验证分数会虚高。我们用标本组ID做分组划分,按组切分,保证同一化石的照片只在训练集或只在验证集。实际测下来,分组划分后的验证mAP比随机划分低了六到八个点,这才是模型真实水平。
3.2 让不同结构在同一基线上跑起来
做多YOLO模型对比,最重要是保证基线一致。输入尺寸、epoch数、优化器、初始学习率、batch、预训练权重、数据增强策略都尽量一致,只换模型结构。我们当时对比了YOLOv5l、YOLOv8m、YOLOv9c三个版本。
下面是某一次对比结果,仅供参考:
| 模型 | 架构特点 | 验证集mAP50-95 | 单帧GPU推理耗时 | 整体印象 |
|---|---|---|---|---|
| YOLOv5l | anchor-based,CSP结构 | 0.682 | 8.8ms | 稳定,但小目标漏检偏多 |
| YOLOv8m | anchor-free,C2f结构 | 0.724 | 7.1ms | 综合表现最好 |
| YOLOv9c | 梯度信息规划相关 | 0.731 | 9.6ms | 精度略高,训练震荡明显 |
这个表不代表通用结论,但在我们的化石分布和标注规则下,YOLOv8m在中长期迭代中最省心。如果你的数据更偏向单一目标、大目标,YOLOv5l的稳定性也值得考虑。
3.3 从损失曲线看模型在学习什么
训练时不要只盯着最终mAP,损失曲线能告诉你很多细节。YOLOv5l前50轮box_loss和cls_loss下降很快,但到120轮左右cls_loss开始震荡,不再明显下降。YOLOv8m的cls_loss全程平滑下降,验证mAP更早进入高平台期。YOLOv9c在小样本下前30轮会出现明显的损失震荡,需要把学习率从0.01降到0.005才能稳住。
这说明,对于化石这种小样本数据,模型不是越新越复杂就越好,关键是损失函数和正样本分配机制能不能在小样本下给到足够正确的监督信号。YOLOv8这种解耦检测头加任务对齐分配方式,在我们这种形状变化大、目标密集的数据上明显更友好。不过也要时刻留意过拟合:训练损失降到非常低,验证损失却回升,说明增强策略不够,或者模型学到了背景信号,这时候先去检查数据和标注,不要急着继续堆epoch。
4. 小样本化石识别的调参实战
4.1 先冻结还是先全量?迁移学习的节奏
很多教程直接让全量微调,但化石数据和自然图像差异很大,直接全量微调很容易把训练集噪声背下来。我们更推荐两阶段训练。
第一阶段冻结backbone的前10层,只训练检测头和深层特征,学习率设在0.001左右,跑50轮。第二阶段解冻全部层,学习率降到0.0005,用余弦退火再跑100到150轮。这个设计的基本逻辑是:backbone浅层提取的是边缘、颜色、纹理原子,和化石图像有共性;深层特征高度任务相关,应该让模型从零开始适应。冻结太久会造成特征表达能力不足,完全不冻结又容易过拟合。
如果第二阶段训练损失掉得非常快但验证损失不动,就把学习率继续调低,并考虑增加标签平滑。如果验证损失和训练损失一起掉,但mAP不涨,那多半是标注覆盖率有问题,属于数据问题,不是超参能解决的。
4.2 小目标与高密场景:输入尺寸和切片策略
化石很多是高清大图,直接resize到640会让较小的化石变成十几个像素的模糊点,关键结构直接丢失。在投入训练之前,先统计所有标注框的尺寸分布,我是建议把这个统计做成强制流程。如果大量边框短边集中在20到50像素,就必须提高输入分辨率或做切片。
我们的做法分两档。前期训练直接用1280分辨率,把原图等比缩放;后期为了解决更小的幼体标本和密集牡蛎层,采用切片策略,把大图切成1024×1024、重叠率30%的图块,对每块单独训练和推理,最后用NMS合并。切块训练能覆盖更多小目标,但训练时间和推理显存都明显上升。边缘设备不适合跑高分辨率整图,现场一般先用640推理做第一遍,对置信度不高的区域再切出来二次放大识别。这个“先粗筛再精查”的流程,兼顾了部署功耗和准确率。
4.3 正样本分配、标签平滑和损失权重
这一节更适合有一定检测基础的读者。YOLOv5和YOLOv8的正样本分配逻辑完全不同。anchor-based版本依赖预设锚框和目标框的IoU匹配,锚框尺寸如果跟化石标注框不匹配,小目标容易漏检。YOLOv8是anchor-free,使用任务对齐分配方式,把分类得分和定位IoU结合来选正样本,对形状变化大的化石目标更友好。
超参方面我关注三个点。一是标签平滑系数,小样本下设为0.05到0.1,能缓解标注噪声和过度置信。二是cls_loss和box_loss的相对权重,如果模型框画得准但分错类,就提高分类损失的权重;反过来如果框偏了但类别对,就提高定位损失权重,而且一次只动一个参数,不要一起调。三是NMS置信度阈值。化石场景背景噪声多,现场部署时conf阈值我会设到0.4到0.5,不要照搬COCO默认的0.25,否则岩石纹理误报会非常多。没有哪个参数是通用的最优解,都要对着混淆矩阵去判断当前问题出在哪一类,再回去调。
5. 指标好看不等于能用:评估、泛化与部署
5.1 混淆矩阵比mAP诚实得多
模型验证集mAP50-95看起来不错,不代表现场能直接发版。我们打印出混淆矩阵后发现两个问题。第一,某两类化石经常互相误检,因为幼体阶段的螺纹轮廓过于接近。第二,样本量最少的那一类召回率很低,但整体mAP被大量高置信度的样本平均掉了,从总分完全看不出来。
mAP是一个高度聚合的数字,它会把某些类的严重问题平均掉。所以评估时除了mAP,必须逐类看Precision、Recall和F1。如果某一类样本特别少且召回率极低,先查这类样本在训练集中是否被漏标过,很多时候不是模型学不会,是标注只有几十个框,模型根本没“见过”几个正例。自己写一个评估脚本,统计所有GT框没有被模型匹配到的样本,往往能发现一批标注漏标,补完这些数据,比换模型效果更明显。
5.2 按采集点切分,才能看出真实泛化能力
前面说按标本组划分,这里还要更进一步:按采集点划分。我们第一次随机划分验证集时,验证mAP大约0.79,看着已经可以用了。后来为了模拟真实野外条件,改成“训练集和验证集来自不同的采集点”,mAP直接降到0.69。这不是模型坏了,而是模型之前“偷看”了同一个采集点的背景纹理,换个场地之后背景完全不同,分数立刻露出原形。
这个经验对所有地质类视觉项目都适用。做模型评估的时候,最好在训练集之外留出两个从未参与训练的野外点位,让模型在“没见过的地方”考试。真正决定模型能不能落地的是这种跨点位分数,而不是在同一个拍摄环境下随机切出来的分数。同理,汇报项目进度时,我会把两组mAP分开列,随机划分的分数只作为参考,跨点位分数才作为发版依据。
5.3 导出和现场部署的几次翻车记录
部署环节的坑比训练环节更密集。PyTorch导出ONNX时,如果动态batch和动态尺寸配置不当,不同推理引擎输出的检测框顺序和坐标会不一致,现场调试非常痛苦。我们最后统一用固定尺寸导出,避免动态问题。后面用TensorRT加速时,尝试过int8量化,结果小目标漏检严重。化石缝合线这类高频纹理信息在量化过程中很容易被丢掉,所以现场设备只用了FP16,没有硬上int8。
边缘设备显存不够时,我们做了切块推理再合并。切块推理必须要处理重叠区域:目标跨块时会被重复检测两次,合并阶段对重叠框做加权融合,不要用普通NMS一刀切。现场部署还有两个容易被忽略的点。一个是用相机自动白平衡拍同一块化石,在不同光源下颜色差别很大,最好统一拍摄光源并做白平衡校正。另一个是现场光照不足时,模型容易把岩石凹痕误检成化石,我们在软逻辑里加了亮度判断,图像整体太暗时直接不输出结果,宁漏勿错。
5.4 把部署现场变成数据回流端
最后说一个我认为比调参更重要的习惯。化石识别模型不该是一次性交付的模型,部署现场本身就应该是数据采集端。我们在部署设备里加了一个简单回流流程:保存所有置信度介于0.3到0.9之间的检测结果截图,每周由标注员复核一轮,把判错的框修正后放进增量训练集。这样跑几个周期之后,误检率下降非常明显。
这个流程本质上是在做难例挖掘。置信度太低的框往往信息不足,人工复核成本高,置信度太高的框一般是对的,看多了也是浪费时间,反而是中间置信度区间最需要人看。每一轮增量训练花的时间和算力都不多,但能让模型持续适应新的采集点、新的岩性背景,而不是在旧数据上自嗨。化石识别这个领域,数据只会越攒越多,模型也要跟着数据一起迭代,才能保持现场的可用性。
如果让我重新做一遍这个项目,我会把数据标注规范再往前提,先让标注一致性达到标准,再考虑模型版本。YOLO的版本更新很快,但决定一个化石识别项目上限的,往往不是模型参数量,而是数据闭环的颗粒度。化石识别特别考验耐心,模型学到的经常是你没意识到的背景信号,只有不断用现场数据反喂模型,才能把它逼回真正的化石特征上。以后再做类似的小样本细粒度识别项目,我会从第一天就建立切片推理和现场数据回流通道,而不是到最后再来补课。