☰
集装箱缺陷检测实战:从YOLO数据集到部署落地
2026/10/19 3:15:56 网站建设 项目流程

集装箱破损缺陷检测这个方向,我在实际项目里从采集到标注、从训练到部署完整走了一遍。先说结论:做好一个集装箱损伤识别模型,七八成功夫其实花在数据集和标注规范上,真正跑模型的时间反而很少。这篇就按我踩过的坑来说,围绕“集装箱凹陷、变形、铁锈腐蚀、破洞”这几类缺陷,讲清楚怎么把一张张现场照片变成能直接喂给YOLO系模型的YOLO格式数据集,以及训练和落地时那些文档里不太会写的事。

1. 集装箱缺陷检测到底在解决什么

1.1 场景痛点与需求拆解

港口码头、堆场、货运站每天进出大量集装箱,箱体在吊装、运输、堆叠过程中难免出现磕碰、挤压、锈蚀。以前主要靠人工巡检,老师傅拿着手电筒绕着箱子走一圈,凭经验判断哪块凹陷要修、哪块锈蚀需要补漆、哪个破洞影响箱体密封。这个模式有几个绕不开的问题:一是效率低,一个堆场几百个箱子,全检一遍耗时很久;二是标准不统一,同一个凹陷,不同人评估可能给出不同结论;三是高空和死角位置人工很难覆盖。

用智能AI图像识别来做这件事,核心不是“识别”本身,而是把“缺陷数别”这件事标准化。我拿到需求时,对方明确说要区分四种缺陷:破损缺陷(破洞、裂缝)、凹陷、腐蚀锈蚀、箱体变形。这四类在画面里长得不一样,但存在交叉,比如凹陷区域往往伴随锈迹,变形区域也可能出现裂纹。所以做数据集和标注时,类别边界越清晰,后面模型的判断就越稳定。

1.2 为什么最终选了目标检测而不是分类或分割

方案评审时有人提过用图像分类做“好坏判断”,也有人建议用语义分割把缺陷轮廓精确抠出来。我最终还是选择了目标检测方案,理由很实际:

  • 部署环节要求能同时框出画面中多个缺陷位置,分类只能给整张图一个结论,没法定位,完全不够用。
  • 分割虽然定位精细,但标注成本高不少,而且现场推理对算力要求高。集装箱表面的锈迹形状往往不规则,标注像素级掩膜会把人累垮。
  • 目标检测用边界框已经能支撑后续业务:框位置、框类别、框大小,加上置信度就能做排序和判断。

当然,如果后续要精确计算锈蚀面积或者凹陷体积,再结合分割模型做二次处理也不迟。第一步用YOLO格式数据集把检测能力跑通,是最务实的选择。

2. 数据集构建:从现场照片到YOLO格式标注

2.1 缺陷类别定义与判定边界

这个环节我吃了不少亏,一开始类别定义太粗,把“锈蚀”和“掉漆”混在一起,导致训练出来的模型经常误判。后来重新梳理了缺陷判定规则,每条都对应可执行的视觉标准:

类别视觉特征判定难点
破损缺陷箱体板面出现孔洞、裂缝,能看到内部或背光穿透裂纹细小,小目标识别困难
凹陷局部板面向内塌陷,呈凹坑状,有明显光影边界光照方向影响凹陷观感
腐蚀锈蚀表面出现红褐色锈斑,漆面起泡剥落并伴随锈色与掉漆、污渍易混淆
箱体变形结构件弯曲、箱体棱线不再笔直拍摄角度不同观感差异大

特别是“凹陷”和“变形”,很多人第一眼分不清。我给现场的采集人员定了个口诀:凹陷是“局部塌”,变形是“整体扭”。局部受压形成凹坑,边界相对清晰;变形通常涉及梁、柱、角件附近,表现为结构线条不直。标注时如果犹豫不决,就按“变形优先于凹陷”的原则处理,因为从安全角度,结构变形比表面凹陷更需要关注。

2.2 现场采集与初筛:决定模型上限的一步

数据集质量的上限在采集阶段就定了,后面标注、增强都只能补救,不能重生。我这边采集策略分三条线:

  • 覆盖多个光照条件:上午顺光、下午逆光、阴天散射光、夜晚补光,集装箱表面本身有瓦楞楞纹,不同光线下同一个凹陷看起来差很多。
  • 覆盖不同距离和角度:整箱远距离、局部近距离、俯视、侧视,因为最终部署的摄像头装在龙门吊、堆高机、智能闸口等多个位置,视角必须都覆盖到。
  • 覆盖不同新旧程度的箱子:新箱表面均匀但容易反光,旧箱本身就有大面积旧伤旧锈,模型要能区分“旧的常态”和“缺陷信号”。

初筛时我有两个硬性指标:第一,画面模糊、运动拖影严重的照片直接不要,宁可数量少也不要脏数据;第二,单个箱体在画面中占比过小的图不做目标检测样本,否则缺陷像素太小,标注了也很难学。集装箱表面大面积的旧漆剥落、轻微锈点,如果业务方不认定是缺陷,就不要强行标注,否则会让模型把“正常旧箱”当成“缺陷箱”,误报炸掉。

2.3 标注工具与边界框标注规则

标注工具我测试过几个,最顺手的是对YOLO格式支持好的开源工具。LabelImg老牌但功能略基础,Label Studio功能全但配置稍重,X-AnyLabeling对自动化辅助标注支持好。我实际主力用的是Label Studio,因为它在团队协作和项目分类管理上更好用,多人标注时能避免互相干扰。

标注规则比工具更重要,我定了这么几条供团队执行:

  • 边界框是缺陷区域的最小外接正矩形框,框的边与图像坐标轴平行,不旋转。
  • 锈蚀区域如果分散成一片小点,按“簇”来标,凡是肉眼可见成片的锈斑合并成一个框,零星孤立的一两个锈点可以忽略。
  • 凹陷和破损同时出现时,按两个独立目标分别标注,即使框有重叠也要标,后续模型后处理再做合并,而不是在标注阶段就省略。
  • 每个目标的下限尺寸:小于画面宽度3%的小缺陷不标,因为目标太小在推理阶段也基本会被忽略,标了反而给模型增加噪声。

标注完成后我做了一轮交叉复查,让A同学标完的数据由B同学抽查,格率低于95%的打回重做。数据库里的每张图都配了JSON和PNG掩码作为中间产物,后续如果要做实例分割可以直接转换,不用重新标一遍。

2.4 标注数据转为YOLO格式的详细步骤

YOLO格式数据集是很多人容易搞错的地方,其实规则非常死板:每一张图片对应一个同名txt文件,没有目标的对象就放一个空txt;每行内容是“类别ID 中心点x 中心点y 框宽 框高”,x、y、w、h全部除以图片宽高做归一化,取值在0到1之间。

我从Label Studio导出的是COCO格式的JSON,转到YOLO格式时写了段脚本来处理。这里有三个极易踩的坑:

  • 中心点坐标计算必须用归一化值,有的标注工具导出的是像素坐标,要手动除以图片宽高,粗心写错会出现框漂移。
  • 类别ID必须从0开始连续编号,不能从1开始,否则和模型类别映射对不上。
  • txt文件和图片文件必须同名同路径,否则代码加载时直接报FileNotFoundError。

贴一下我当时转换脚本的关键片段,可以直接参考:

import json import os def coco_to_yolo(json_path, img_dir, out_dir): with open(json_path, encoding="utf-8") as f: data = json.load(f) img_id_to_info = {img["id"]: img for img in data["images"]} img_id_to_anns = {} for ann in data["annotations"]: img_id = ann["image_id"] img_id_to_anns.setdefault(img_id, []).append(ann) for img_id, img_info in img_id_to_info.items(): img_path = os.path.join(img_dir, img_info["file_name"]) # 真实项目里这里需要额外处理图片是否被旋转过,标注JSON里已算好 w, h = img_info["width"], img_info["height"] txt_path = os.path.join(out_dir, img_info["file_name"].replace(img_ext, ".txt")) lines = [] for ann in img_id_to_anns.get(img_id, []): cat_id = ann["category_id"] # 这里允许映射成0,1,2,3 bbox = ann["bbox"] # x, y, w, h 像素坐标 x, y, bw, bh = bbox cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw, nh = bw / w, bh / h lines.append(f"{cat_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines))

转换完一定要做抽查可视化,把框画回图上人肉看一遍,这一步能救回很多隐蔽错误。

3. 训练环节的实战配置

3.1 数据划分与数据增强策略

划分数据我严格遵守一条原则:同一个集装箱的多张照片必须全部放到同一个集合里,不能一部分在训练集,一部分在验证集。原因很简单,同一集装箱在不同角度下的画面高度相似,如果跨集合分布,会让验证集的指标虚高,模型看起来很好用,一遇到新箱子就露馅。

划分比例用8:1:1,训练、验证、测试严格按集装箱实例维度来切分。

数据增强方面,集装箱缺陷识别的痛点主要是光照变化、小目标、类别不平衡。我从Ultralytics的标准增强之外,额外加了两组策略:

  • 针对光照变化的HSV增强,色调偏移控制在正负10度之间,饱和度和明度适当变化,模拟不同天气和补光条件。
  • 针对小缺陷目标的复制粘贴增强,把标注好的小锈斑抠下来复制到图片的空白区域,增加小目标样本量,效果立竿见影。

多尺度训练也开了,输入尺寸设置成640和960交替训练,大尺寸对小缺陷更友好,代价是训练时间增加,实测下来mAP能涨2到3个点。

3.2 模型选型与参数调优过程

模型选型我直接在YOLOv8和YOLOv9之间对比,也顺带看了YOLOv5做基线。最终选定YOLOv8m作为主模型,原因有三个:部署生态成熟,导出ONNX、TensorRT都很顺;中等规模模型在边缘设备上推理速度能控在200毫秒以内;精度比n/s版本高不少,又没有l/x版本那么多冗余。

训练参数是一次次试出来的,我给出最终一组参考值:

yolo detect train \ data=container_yolo.yaml \ model=yolov8m.yaml \ epochs=150 \ batch=16 \ imgsz=960 \ device=0 \ patience=25 \ optimizer=AdamW \ lr0=0.001 \ mosaic=1.0 \ mixup=0.2

这里有两个细节值得说明。一是用了AdamW优化器,YOLOv8默认是SGD,但集装箱缺陷数据集有类别不平衡问题,AdamW收敛更稳定。二是patience设成25,当验证集指标连续25个epoch不涨就早停,省时间,也防止过拟合。

3.3 训练监控与指标解读

训练过程中我最关心的指标不是总的mAP,而是每个类别的AP。集装箱凹陷和铁锈这两类数据量最多,AP往往刷到0.9以上,但破损和变形的AP可能只有0.6甚至更低。模型总mAP看着还行,一拆开就露馅。当时用混淆矩阵一看,破损缺陷经常被漏检,因为它标注框太小,模型倾向优先检测大目标。

这时候需要补充针对性策略:给小目标类别加大损失权重,或者把输入分辨率继续拉大。我实际采用的做法是把破损缺陷类的图像复制两份加进训练集,属于简单粗暴但有效的类别重采样。

另外要盯一个容易忽视的指标:训练集和验证集之间mAP差异。如果训练集mAP已经到0.98,验证集只有0.85,说明模型记住了训练集,泛化不足。集装箱场景里,这通常是因为同一批箱子的背景太相似,模型把背景特征也学进去了。解决做法是增加更多不同堆场背景的样本,或者增强背景区域的随机裁剪替换。

4. 实测中的翻车现场与排查记录

4.1 高频问题速查表

这部分直接记录我遇到过的典型问题,按症状、原因、解法整理成一张速查表:

症状原因解法
模型把正常掉漆误报为腐蚀锈蚀标注时掉漆与锈蚀边界不清晰重看标注规则,剔除非锈类样本,锈蚀必须有锈色特征
凹陷检测不稳定,换个角度就漏检训练集单一光照占比过高补充侧光和逆光样本,增强光照多样性
破损裂纹完全检测不到小目标框占比太小,模型忽略提高输入分辨率,重采样小目标类别,增加复制增强
变形和凹陷相互误判类别边界定义模糊严格按“整体变形优先”重新捋一遍标注,必要时合并类别
画面反光导致大片误检集装箱表面高光反光,边缘被误当裂纹增加偏振镜采集样本,训练时加高斯噪声增强
验证指标好但现场频繁误报测试集与真实分布不一致用现场新增数据做持续评测,按失败案例补充训练集

这里面最坑的就是反光误检。集装箱在强光下表面会拉出长条高光,边缘轮廓明暗对比剧烈,模型很容易在这些位置框出“破碎区域”。后来我把高光样本单独挑出来做了一轮针对性标注,并在增强管线里加了随机亮度跳跃,误报才压下去。

4.2 数据集标注中的独家避坑心得

标注这个环节,表面看是体力活,实际上藏着很多专业判断。我分享几条踩了跟头才总结出来的经验:

  • 单张图片不要塞太多目标。如果一个画面里锈斑超过二十处,要么缩小画面范围重拍,要么只标主要缺陷簇。目标太多会让模型无所适从,而且框之间的重叠会导致后处理大量合并,影响定位精度。
  • 每条边界的标注人最好固定。不同标注人对“锈到什么程度算缺陷”的尺度完全不一致,A同学标得严,B同学标得松,模型在边界样本上会学成“摇摆决策”,表现就是置信度普遍不高。
  • 对“疑似但不典型”的框,加一个ignore标志单独存起来,不要直接丢进训练集。这类难点样本等模型训练完一轮,交给模型自己去判,比强标进数据集更有效。

4.3 数据量不足时的补全策略

很多团队做这个项目都没有充足的初试数据,几千张图都算多的。我遇到过只有六百张图的冷启动阶段,这时候硬训YOLO必然过拟合。

三板斧招式:

  • 从视频中抽帧。容器码头的监控视频每天产出大量画面,按每5秒一帧抽出来,一天就能积累上千帧,虽然重复度高,但能快速扩量。
  • 用扩散模型和风格迁移做数据合成。把已有缺陷区域裁剪后贴到不同集装箱背景上,配合背景的亮度变化、色调偏置,能稳定增加多样性。
  • 先用公开工业缺陷数据集做预训练,再做集装箱场景微调。工业表面缺陷检测数据集里有各种划痕、凹坑、锈蚀样本,模型先把纹理特征学好,再学集装箱场景的规格,收敛速度和最终精度都会好很多。

合成数据有个大前提:必须人工复查混合后的图片,防止出现目标与背景透视不匹配、缺陷形状怪异等明显破绽。模型对这类不真实样本学到的特征,会影响真实场景泛化。

5. 落地部署时容易被忽略的细节

5.1 推理管线与性能优化

训练完的模perl型要真正放进现场,需要走导出、量化、后处理三步。导出我首选ONNX,再用TensorRT做FP16量化,在带独立GPU的边缘设备上推理速度能从几十毫秒进一步降下来。如果部署在Jetson这类设备上,TensorRT是必然选择。

一个容易忽略的点是推理输入分辨率。训练时用了960,部署时为了速度往往想压到640。分辨率一降,小缺陷基本就废了。我的做法是保留至少800的推理尺寸,或者干脆训练时采用960和640交替策略,部署时再退回640,性能损失可以接受。

后处理方面,NMS的IoU阈值我设到0.5,置信度阈值设到0.35左右。集装箱缺陷检测业务宁可多一点重叠框,也不希望真正有问题的地方漏掉,所以阈值不能给太高。

5.2 误报与漏检的取舍策略

落地后业务方最烦的就是误报,如果一个正常箱被反复报警,巡检员很快就会对系统失去信任。但也不能一味提高置信度阈值来压低误报,那样漏检率会飙升。

我这边采用了一个状态机策略:单帧检测结果只是“候选”,连续三帧或者两次独立角度都检测到同一位置缺陷才触发报警。这个策略直接把瞬时误报降到可以忽略的水平,而真实缺陷因为通常在多个角度都能看到,不会丢。

业务侧还要求输出缺陷位置和面积估算。短板在于边界框面积只能粗略反映缺陷大小。我在部署端做了个近似换算:按照相机到箱体的距离和焦距做一个像素到厘米的比例因子,误差能控制在百分之十左右,对检修工单的排序已经够用。

5.3 模型迭代闭环与持续优化

这是我觉得整个项目最有价值的部分。数据集不能一次定型,模型上线后要建立“现场失败样本回流”机制。每天把现场产生的误检和漏检图收集起来,每周做一轮快速标注,补充进训练集,再用新数据重新训练一轮。

这里有个量化指标建议:每个类别持续追踪AP变化,如果连续两周某个类别AP下降,立即检查是否出现了新形态的缺陷或新的现场环境。

我实践中发现,集装箱锈蚀这个类别在回源数据中经常被扩大定义:一开始只标明显的锈斑,后来业务方希望把漆面起泡、漆面下锈线也纳入,这个边界扩大直接让模型行为发生了可感知的变化。所以每次迭代前先和业务方确认类别定义没有漂移,比调模型参数重要得多。

最后分享一个我个人体会最深的小技巧:集装箱缺陷检测项目的成败,往往不取决于模型方案多先进,而取决于数据集是不是真实覆盖了现场极端情况。高光、雨天、夜间、逆光、半遮挡这些考验全部上齐,模型才敢上产线。如果你刚开始准备这个方向的数据集,别急着标几千张,先把现场查勘拍回来一百张最脏、最乱、最反光的图片,标注好验证模型底线,再批量扩展,会省掉很多返工。

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

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

立即咨询