集装箱在物流和场站里流转最表面的一层“皮”其实是最难管的一笔账。现场堆场上装卸、运输、堆叠都会在箱体上留下痕迹侧壁被叉车顶出一个凹陷顶角件附近锈出一条黄褐色的水流痕门框受力后轻微变形。以前这些全靠人眼巡检巡完了手写一堆记录拍一堆照片最后回到数据中心整理成 Excel 表。问题是同一个凹陷白天拍和晚上拍正面拍和斜着拍出来的“视觉结论”往往不一样判断标准全靠个人经验和心情。后来不少团队开始尝试用 AI 图像识别来解决这件事核心做法就是把现场采集到的集装箱表面图像做成标准化的 YOLO 格式数据集用目标检测模型把凹陷、变形、腐蚀、锈蚀这些缺陷类型自动框出来顺带输出每个类型的数量。这篇文章就围绕这套完整链路展开从数据怎么来、怎么标到模型怎么选、怎么训再到现场怎么部署把我在实际项目中反复调试出来的经验和坑都说一遍。这个方案最适合谁如果你正在做港口堆场信息化、场站闸口自动化、集装箱维修工单系统的升级或者你手头恰好有一堆集装箱外观照片想做一个能自动判定外观损伤的 demo这篇文章能帮你少走至少一个月的弯路。内容不依赖任何商用平台全程开源工具照着做就能跑通。1. 项目思路与缺陷分类先想清楚“识别什么”1.1 场景痛点为什么人工巡检看起来简单却总是漏集装箱外观检验这件事表面上是个纯视觉任务实际上受现场因素干扰非常大。露天堆场的自然光照是最大的变量晴天强光下金属表面反光严重凹陷位置看不清阴天光线柔和但暗面阴影会把正常褶皱误判成破损。更麻烦的是集装箱箱体表面并不是纯色上面有公司 LOGO、箱号、安全标签、警示标志这些图案在图像中和锈蚀、划痕的视觉特征天然接近。一个人如果连续看几十个箱子注意力会快速下降尤其碰到侧板和顶板这种大面积区域漏检几乎不可避免。我在实际项目里最大的体会是人工巡检的问题并不是“眼睛看不见”而是“标准不统一”。同样一处掉漆露锈操作员甲登记为“锈蚀”操作员乙登记为“腐蚀”到月底统计维修材料时数据对不上最后只能重新翻照片。AI 识别最大的价值不是替代人的眼睛而是把“看”变成一套可复制的标准决策流程图像进来模型输出类别和位置业务系统直接按结果归档。哪个箱子有凹陷哪个箱子锈蚀面积超阈值全部结构化任何人来查都能回溯。1.2 四类缺陷怎么界定凹隔离、变形、腐蚀、锈蚀标题里的“凹陷、变形、腐蚀、锈蚀”这四类听起来差别不大但在标注和模型训练时必须有非常明确的界定不然标出来的数据前后矛盾训练出来的模型只会变得更糊涂。我的建议是每类缺陷给一个现场可执行的判断口径。凹陷的定义是箱体表面受到外力撞击后形成的局部下陷特征是边缘过渡明显伴有折痕或皱褶通常在侧板、门板、顶板这些大面积区域出现。变形则是指箱体整体结构发生几何偏移典型的是角柱弯折、底梁翘曲、门框不再平行这种缺陷的位置相对固定多出现在结构受力点。腐蚀是金属材料在潮湿环境下发生的慢性劣化表面呈灰白色或灰绿色粉末状、片状剥落常沿着焊缝、底边和长期积水的位置蔓延。锈蚀是铁基材料氧化后的产物典型颜色是红褐色、橙褐色形态可能是点状、条状或大面积块状。很多人会把腐蚀和锈蚀当成同一种缺陷实际上从视觉和维修角度它们有明显区别。腐蚀区域表面失去金属光泽颜色偏灰暗往往伴随漆面起泡锈蚀则有明显的红褐色氧化物堆积。如果标注阶段不区分模型后期会在这两类之间来回横跳。总而言之标注手册就是给模型定的“法律”规则模糊后面所有环节都会跟着出错。1.3 为什么数据格式锁死 YOLO现在目标检测领域主流的数据格式不少COCO 的 JSON、Pascal VOC 的 XML、YOLO 的 TXT各有利弊。但在这个项目里最终选择 YOLO 格式有三个非常实际的原因。第一YOLO 系列模型生态成熟无论是跑在自己训练的 PyTorch 权重上还是导出 ONNX、TensorRT 到边缘设备社区资料和现成工具都是最多的遇到问题随便搜都能找到解法。第二YOLO 格式的标签文件就是一个纯文本一行一个目标包含类别序号和归一化后的中心点坐标、宽高不依赖复杂的嵌套结构方便在数据清洗、合并、增广时用脚本批量处理。第三它是所有格式里最容易做增量更新的今天新增一批现场照片跑一个脚本把新图片的标注信息追加进去训练集就更新了不需要重新转换整个数据集。考虑一下 YOLO 的 txt 标注文件内容2 0.5231 0.4187 0.1246 0.0835 0 0.1277 0.7632 0.0483 0.0419每一行从左到右依次是类别编号、目标中心的 x 坐标、目标中心的 y 坐标、目标框的宽度、目标框的高度。所有数值都是相对于图片宽高的比例范围在 0 到 1 之间。这种归一化方式让模型在不同分辨率输入下都能保持坐标一致性。2. 从零搭一个集装箱缺陷数据集2.1 图像怎么收集才最有代表性数据集的来源直接决定了模型能学到什么。如果你只是从开源数据集网站上拉几百张集装箱图片下来训练大概率会在现场部署时被打穿。集装箱缺陷检测的高频场景是露天堆场那么采集图像时要刻意覆盖不同时间段、不同天气、不同拍摄角度和不同新旧程度的箱体。我实际操作时采用了一个比较稳妥的采集方案用手机或场站固定的海康相机对同一缺陷在三个距离上各拍一张。远距离拍整箱全貌用于定位缺陷在箱体上的大概区域中距离拍缺陷所在面的完整画面提供上下文信息近距离拍缺陷细节让模型看到纹理特点。这种多尺度拍摄方式对于后续训练有非常大的帮助因为现场部署时摄像头安装的高度和角度不可能完全一致模型见过的尺度越丰富泛化能力就越强。这里要特别强调一个反直觉的经验不要只拍有缺陷的照片。我一开始犯过这个错误数据集中 99% 的图片都带缺陷训练出来的模型对正常集装箱表面完全没有抵抗力阴影、反光、箱体 LOGO 全被误报成缺陷。正确做法是保留至少 20% 的负样本图片也就是外观干净、无明显缺陷的集装箱照片让模型学会“什么都不框”的能力。负样本对于降低误报率的作用甚至比增加正样本更明显。2.2 标注规则的统一口径标注是数据建设里最枯燥但最关键的环节。如果标注规则不一致再多的图片都是噪音。我在项目里给标注团队写的规则文档核心内容就三条。第一边界框要贴紧缺陷的实际范围不能为了省事用一个大方框把整片区域框起来。比如一条锈蚀带要从锈迹实际延伸的起点拉到终点而不是把周围完好的漆面也包进去。第二当两个同类缺陷靠得很近且中间没有明显间隙时合并成一个框如果中间有至少一个缺陷宽度的空隙则分开标注。第三对于难以判断的边界情况统一记录到待确认列表每天集中跟项目负责人过一遍而不是现场标的人自己拍脑袋。这类规则听上去琐碎但它直接决定模型输出的边界框质量。我见过一个项目因为标注人员对凹陷框的尺寸理解不一致有的人框得紧、有的人框得松训练出来模型输出的框忽大忽小后处理阶段做面积统计时完全没法用。后来统一了规则重新过了一遍标注效果立刻改善。2.3 YOLO 格式目录结构和脚本转换当图片和标签准备好之后就要按照 YOLO 训练框架要求的目录结构整理数据。无论你用的是 YOLOv5 还是 YOLOv8官方训练代码都接受同一个约定图片和对应的标签文件放在两个平行目录下且文件名完全一致。一个标准的目录结构是这样container_defect_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ ├── labels/ │ ├── train/ │ ├── val/ │ └── test/ └── data.yaml其中data.yaml是数据配置文件内容指定类别名称和路径train: container_defect_dataset/images/train val: container_defect_dataset/images/val test: container_defect_dataset/images/test nc: 4 names: [dent, deformation, corrosion, rust]如果你手里的标注是用 LabelImg 存的 Pascal VOC XML 格式可以用一个简短的 Python 脚本完成转换。核心逻辑是把 XML 里的xmin, ymin, xmax, ymax像素坐标读取出来再除以图片宽高变成归一化坐标最后写入同名的 txt 文件。import os import xml.etree.ElementTree as ET def convert_voc_to_yolo(xml_file, txt_file, img_width, img_height): tree ET.parse(xml_file) root tree.getroot() lines [] for obj in root.iter(object): class_name obj.find(name).text class_id class_map[class_name] box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height width (xmax - xmin) / img_width height (ymax - ymin) / img_height lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) with open(txt_file, w) as f: f.write(\n.join(lines))2.4 数据清洗与类别平衡数据清洗这个环节经常被新手跳过但在集装箱缺陷场景里非常致命。现场采集的照片往往带有多余信息有人路过、有堆场设备遮挡、有阳光直射产生的光晕。这些图片如果不加筛选就直接进训练集模型会把“路人”“叉车”“光斑”的语义关联到缺陷上部署时处处误报。我一般会先用一个快速脚本把所有图片和标注框可视化出来把明显错标、漏标、异常框尺寸的样本批量删除保证送入训练集的每张图都是干净的。类别平衡问题同样需要面对。正常箱体顶多有点锈蚀但凹陷通常只在事故箱上出现所以四类缺陷的样本量天然不均。我做过一个统计锈蚀样本常常是凹陷样本的三四倍。解决方法是分两步走。第一步在采集阶段有意多找事故箱拍凹陷和变形缺哪类补哪类。第二步训练时开启数据增强里的 Mosaic 和 Copy-Paste把少数类样本和背景图拼接等效增加样本数量。千万不要直接对小类重复复制那样很容易过拟合模型记住的是复制图片的位置关系不是缺陷本身。3. 用 YOLO 训练损伤检测模型参数和实操记录3.1 模型选型与预训练权重当前 YOLO 系模型可选版本很多YOLOv5 稳定、社区庞大YOLOv8 结构更合理、训练更省心YOLOv9 和 YOLOv10 在精度上有升级但对显存的要求也更高。集装箱缺陷检测这个场景有几个特殊性缺陷目标大小差异大、箱体表面纹理复杂、现场设备算力往往有限。综合考虑我首选 YOLOv8m 作为主力模型显存占用中等精度和速度在大多数边缘设备上都能兼顾。如果没有 GPU 资源或者想要最快速度验证流程可以先用 YOLOv8n 跑一版目的是把训练到部署的整条链路打通。链路通了以后再换大模型追求精度。千万不要一开始就想直接上最重的模型调试过程中数据集会反复修改每改一版标注都要重新训练一轮验证轻量模型能让整个迭代周期缩短不少。使用预训练权重是必选项。虽然集装箱缺陷和 COCO 数据集的类别没有任何重合但从 COCO 上预训练得到的特征提取能力可以迁移过来。模型的浅层卷积学到的是边缘、纹理、颜色这些通用特征这些特征在任何视觉任务里都成立。我在实践中的做法是加载 YOLOv8 的 COCO 预训练权重冻结主干前 10 层先只训练检测头部分跑 50 个 epoch再解冻全部层微调。这个策略能避免一开始就震荡收敛速度明显更快。3.2 训练参数怎么定才靠谱训练参数没有一套万能配置要根据你的数据集、标注质量和部署目标来调。我的基准配置是输入尺寸 1280 或 1024批次大小 8 到 16训练轮数 200初始学习率 0.01采用 SGD 加余弦退火。下面拆开逐个说。输入尺寸是集装箱检测里最关键的一个参数。很多缺陷目标在整张 640×640 的输入下只有十几个像素主干网络下采样之后特征图上的信息几乎被磨平。我把输入尺寸从 640 提到 1280 之后小凹陷的召回率直接提升了将近十个百分点。代价是训练耗时几乎翻倍推理帧率减半。如果你的现场相机离集装箱比较远缺陷占比小1280 是值得的如果现场是近景特写拍摄640 可能就够用。批次大小要跟着显存走理论上越大越稳定。Batch 大小影响 BatchNorm 层的统计估计太小的批次比如 2 或 4会让模型训练早期非常不稳定。如果显存只够放 4就降低输入尺寸或者换轻量模型还是尽量维持批次不小于 8。学习率方面我见过很多人直接用默认值结果模型在十几轮之后就开始剧烈抖动损失曲线像锯齿。YOLOv8 的默认学习率其实是按照 Batch 16 设计的如果你的 Batch 是 8建议把初始学习率按比例降低一半。余弦退火调度器能帮助模型在后期更好地收敛到平坦的损失区域。训练轮数看数据量。1000 张左右的图片200 轮足够如果数据集扩充到 5000 张甚至更多300 轮也不是问题。关键是开启早停机制当验证集 mAP 连续 30 轮不提升时停止训练这样既不会欠拟合也不会白白浪费算力去学那些没有意义的噪声。3.3 数据增强模拟现场真实光线变化集装箱缺陷检测的数据增强策略不能照搬目标检测任务里那套通用的“平移、旋转、缩放”组合得围绕现场光线的变化来设计。露天堆场的太阳角度每小时都在变早上和下午的光照色温完全不同中午顶光会在箱体上形成大片阴影区。针对这个特点我在训练时启用了随机亮度扰动、对比度扰动和色相扰动幅度设为 ±25%。这组参数模拟了不同时间段、不同天气下的色差让模型不要过度依赖“影子就是凹陷”这个假相关性。还有一类现场干扰是相机运动模糊。场站的监控摄像头常年在户外工作镜头表面容易有灰尘和水渍图像清晰度并不理想。我加入了轻微的高斯模糊增强概率 0.2让模型在模糊图像上依然能保持基本检测能力。最终我的增强组合长这样augment: true hsv_h: 0.02 hsv_s: 0.6 hsv_v: 0.4 degrees: 10 translate: 0.1 scale: 0.3 fliplr: 0.5 mosaic: 1.0 mixup: 0.2 copy_paste: 0.2Mosaic 增强在缺少数别样本时很有用。它会随机选四张图拼成一张新图等于一次训练能看到四个不同场景。但我习惯在最后 30 轮把 Mosaic 关掉因为这种拼接图和真实场景仍有差异让模型在训练末期看到完整的真实图片有助于收敛到更精确的框。3.4 评估指标mAP、Precision、Recall 怎么解读训练结束后的评估环节不能只看一个 mAP 数字。集装箱缺陷检测更关注实际业务的两个角度能不能找到缺陷找出来的是不是真缺陷。前者对应召回率 Recall后者对应精确率 Precision。mAP0.5 表示 IoU 阈值在 50% 时各类别平均精度它衡量的是模型“大致框住目标”的能力贴近现场先要“找到”再谈“框准”的诉求。我一般要求 mAP0.5 至少到 0.85 才考虑进入现场测试。mAP0.5:0.95 则更苛刻它衡量的是边界框和真实框的重合度涉及到后续按坐标裁剪缺陷图、计算面积等精细化场景。每类缺陷单独看指标非常重要。实践经验里锈蚀和腐蚀的 AP 通常能到 0.9 以上因为目标面积大、纹理特征明显凹陷的 AP 往往最差因为小目标在高层特征里信息丢失严重。如果测试集里凹陷的召回率只有 0.6说明需要提高输入尺寸或者增加该类别的样本量而不是盲目增大模型。4. 实操落地一整套跑通流程4.1 从训练到权重输出的完整命令流数据准备好了配置好了就可以开始训练。我习惯用命令行方式进行训练便于记录每次迭代的参数组合。YOLOv8 的命令非常简洁yolo train \ modelyolov8m.pt \ datacontainer_defect_dataset/data.yaml \ imgsz1280 \ batch8 \ epochs200 \ lr00.005 \ patience30 \ augmenttrue \ projectcontainer_defect_runs \ nametrain_v1训练开始后不要干等盯住三个地方。第一损失曲线是否平滑下降如果前 20 轮 loss 剧烈波动不下降大概率需要调低学习率。第二每轮的验证集 mAP 是否同步上升train loss 降而 val mAP 不动说明过拟合已经出现。第三用训练过程中自动输出的预测可视化图直接查看同一张图上模型预测框和真实标注框的差异能快速发现标注质量问题。训练完成后模型权重会自动保存在container_defect_runs/train_v1/weights/目录下包含best.pt和last.pt。best.pt是验证集表现最好的权重直接用它做后续部署。导出的命令也很简单yolo export \ modelcontainer_defect_runs/train_v1/weights/best.pt \ formatonnx \ imgsz1280导出 ONNX 后可以用 ONNX Runtime 在纯 CPU 环境推理也可以进一步转换为 TensorRT 的 engine 文件跑在 Jetson 或 RTX 显卡上推理速度能翻几倍。4.2 不同部署场景的硬件和方案取舍集装箱缺陷检测在实际落地时有两种完全不同的部署形态需要提前想清楚摄像头安装和数据触发方式。第一种是道口抓拍模式适用于集装箱卡车经过闸口、龙门吊装卸区这种固定卡口。摄像头安装在支架上当箱体进入拍摄区域后通过地感线圈或雷达触发拍照一两秒内完成一张整箱侧面的拍摄然后自动识别并记录结果。这种场景对实时性要求不高但要求识别算法在弱光、逆光条件下依然稳定。我建议用工业相机加防眩光罩保证曝光均匀。第二种是堆场巡检模式适用于移动式巡检车或工作人员手持终端拍照。巡检车沿着箱区走车载终端连续抓拍每拍一张就调用模型推理一次实时叠加检测框在屏幕上由人工确认后一键入库。这个场景更看重推理速度因为图像是连续帧产生的。此时可以采用 TensorRT 加速模型量化成 FP16 甚至 INT8在 Jetson Orin Nano 这样的设备上能做到 30 帧以上的处理速度。部署时还有两个容易被忽略但影响很大的细节。一是摄像头分辨率要和模型输入尺寸匹配不能拍 4000×3000 的图直接压到 1280 输入这样会丢失大量细节正确方式是先把原图按比例裁剪出缺陷高发区域再缩小输入模型。二是相机拍摄视角要尽量正对箱体侧面斜视角过大会让边界框里的目标形变严重模型在正视角训练数据上学习到的特征会失效。4.3 检测结果如何接入业务系统模型最终输出的是类别编号和坐标但这对业务系统来说还不够。需要加一层转换逻辑把模型输出转成业务字段并汇总缺陷数量。我自己写了一个轻量的后处理函数输入是模型原始输出输出是结构化的 JSON 数据def parse_detections(results, class_names, conf_threshold0.35): parsed [] for det in results.boxes: conf float(det.conf) if conf conf_threshold: continue cls_id int(det.cls) xyxy det.xyxy[0].tolist() parsed.append({ defect_type: class_names[cls_id], confidence: round(conf, 3), bbox: [round(v, 3) for v in xyxy], }) return parsed返回的 JSON 可以直接提交到场站管理系统和箱号、时间、装卸任务单关联。后续维修班组拿到工单后根据 bbox 坐标裁剪出缺陷区域图片在修箱前做最终人工确认。这个流程里 AI 承担的是“初筛统计”工作人工只对模型输出进行快速复核效率比纯人工提升明显。5. 各种踩过的坑按现象排查的速查表5.1 锈蚀与腐蚀为什么分不清我在项目初期遇到的最头疼问题就是锈蚀和腐蚀互相误报模型在测试集上频繁把灰白色的腐蚀区域识别成红褐色的锈蚀。后来仔细分析标注数据才发现很多腐蚀样本在初期阶段也是带一点锈色的标注人员在边界情况下分成两类并没什么统一规则。解决思路有两条。第一把容易出现混淆的边界样本单独整理出来和标注团队重新过一遍标签明确“以主色调和表面状态为准”的原则区域以红褐色氧化物为主且可见铁锈堆积的记作 rust区域以漆面起泡、灰白/灰绿色粉末剥落为主的记作 corrosion。第二训练时对这两个类别使用稍大的权重系数让模型更关注它们之间的区分边界。实测下来混淆明显减少。5.2 小目标凹陷漏检怎么补都补不上漏检小凹陷这个问题困扰了我很长时间。后来我把每张图上目标框的像素面积做了个统计发现三分之一的凹陷标注框宽度不到图片宽度的 4%。在 YOLO 模型的特征金字塔里这些小目标主要落在较浅层的特征图上如果输入分辨率不够小目标的特征根本保留不下来。解决办法最有效的是提升输入分辨率到 1280其次是把训练数据里的小目标图片单独复制一份并做轻微裁剪放大相当于让模型多学几次小尺寸样本。第三种方法是启用 YOLOv8 的 P2 检测层让模型在最浅层额外做一次目标预测对小目标的召回有实质性帮助。代价是显存占用和推理耗时增加是否启用要看现场设备余量。5.3 光照干扰误报和漏报一起出现露天环境的光照问题往往把误报和漏报同时放大。正午强光下箱体的棱角会产生硬阴影模型容易把阴影边缘错判成凹陷反过来背光面细节淹没在暗部里真实的锈蚀又检测不出来。这个问题的根子还是训练数据里光照多样性不够。我试过在推理阶段对图像做自适应直方图均衡化但效果不稳定尤其是对颜色敏感的锈蚀类别过度增强会让原本红褐色的区域变成灰黑色。更稳妥的办法还是在训练阶段解决采集数据时专门安排早中晚三个时段拍摄并且刻意包含逆光和强反光样本。训练时也把光照扰动增强打开让模型见过足够多的光照形态比任何推理端的图像预处理都有效。5.4 同一处缺陷输出大量重叠框训练完模型跑测试图时常常见到模型对同一个凹陷连续输出四五个高度重叠的框置信度从 0.95 到 0.4 一路排下来。这种情况通常是 NMS 阈值设置过低导致相似的预测框没能被抑制合并。处理方法是调整后处理阶段的 NMS 参数把置信度阈值设为 0.35NMS IoU 阈值设为 0.6 左右。同时还要检查是不是训练时标注框本身不统一同一个缺陷被不同标注人员反复标记成多个小框。我在代码里加了一个合并逻辑对最终检测结果里的同类框做一次密度聚类当多个框的重叠度超过 60% 且中心点距离小于一定范围时保留置信度最高的那个其他全部舍去。这套逻辑上线后单箱误报数量明显下降人工复核的工作量也少了。5.5 现场部署常见问题速查症状可能原因处理办法推理结果全是误报负样本不足模型没见过正常的集装箱表面给训练集补充至少 20% 无明显缺陷的箱体照片目标框偏移严重摄像头安装角度斜视角过大调整安装位置保证镜头尽量垂直正对箱面模型对夜间图片检测效果差训练集中缺少夜间和弱光样本增加夜间补光条件下的采集数据用亮度增强模拟夜视效果导出 ONNX 后精度下降导出时没有固定输入尺寸动态尺寸模式下部分算子优化失效导出时锁定 imgsz避免导出端动态尺寸TensorRT 量化后误报激增INT8 量化时校准集数量不足或代表性不够校准集选不同光照、不同箱体颜色的 1000 张图重新校准6. 对整体工作流的建议我个人在实际操作中最大的体会是集装箱缺陷识别这个项目60% 的工作量在数据30% 在调参与部署真正训练模型只占很小一部分。很多人拿到开源数据集直接就跑训练脚本总觉得“效果不好就换更大的模型”但实际上绝大多数问题和模型架构无关而是数据集里的标注一致性、类别平衡、光照多样性没有做好。最后再分享一个小技巧。当训练出来的模型在现场表现不稳定时不要急着调整训练参数先拿现场拍的一百张图片用训练好的模型批量推理一遍把所有预测结果和原图叠加输出成一张大图盯住那些被模型错误框出来的位置反推训练集里缺了哪种场景。这个“现场图片反查训练集”的操作是最快定位问题的手段比看十遍损失曲线都有用。按这个方法迭代两三版数据项目基本就能达到稳定上线效果。