做一个YOLOv7海上船舶检测项目,从数据准备到代码调试再到最终落地,前前后后折腾了不短时间。今天不聊论文复现,也不贴大段理论,就拿着这套系统的代码目录,把每个模块为什么要这么写、里面有哪些坑、实际跑起来效果怎么样,一次说清楚。如果你正准备拿YOLO系列做海面目标识别,或者想给自己的检测项目加点工程化细节,这篇内容应该能帮你少走不少弯路。先说清楚这套系统解决了什么问题:海面场景下的船舶检测,最麻烦的不是检测框准不准,而是船小、背景杂、天气变化大。一条渔船在远海可能只有几十个像素大小,加上浪花反光、海雾干扰,常规目标检测配置很容易漏检误检。这套项目围绕YOLOv7做了针对性优化,从数据增强策略到推理参数调优都做了适配,最终在高分辨率输入下能稳定识别中小型船舶。1. 项目形态与整体设计思路拆解1.1 为什么选 YOLOv7 而不是其他版本选型的时候也纠结过YOLOv5和后来的YOLOv8,最终定在YOLOv7上,核心原因是它在精度和速度之间拿捏得比较稳。YOLOv7的E-ELAN结构在不增加太多计算量的前提下提升了特征提取效率,对于船舶这种纹理简单但尺寸跨度大的目标很合适。另外YOLOv7的辅助训练头机制在训练阶段能提供额外梯度监督,收敛更稳定,对小目标尤其友好。部署层面,YOLOv7的导出链路成熟,ONNX到TensorRT都有现成方案,后续做边缘设备部署不吃力。YOLOv8虽然新,但当时它的检测头改动导致部分工具链需要重新适配,项目周期不允许花太多时间在这上面。提示:如果你只是快速验证、不追求极致部署效率,选YOLOv5s起步也行。但要做到地级市海岸线这种规模的多路视频流识别,YOLOv7的性价比更高。1.2 海面场景的特殊约束与对策海面检测和通用目标检测有本质区别。通用数据集里目标是画面主体,占比较大;海面上的船往往只有几百甚至几十个像素,同时海面本身的纹理结构(波纹、反光、泡沫带)会在相邻帧产生剧烈变化。这意味着模型不能只依赖颜色或纹理边缘来识别。项目里做了三个针对性设计:训练图像分辨率提高到1280x1280,用小目标保住召回率;在数据增强里引入海面特有的扰动方式,比如随机亮度抖动模拟不同时段光照、随机水平翻转模拟船向变化;过滤掉纯背景帧和船只占比过低的样本,避免模型学到“大海即船只”的偷懒策略。1.3 整体代码结构规划一套完整的船舶检测代码不是只有训练脚本,还要包含数据预处理、模型配置、推理脚本和工具函数。这个项目的目录规划如下:ship_detection/ ├── config/ # 模型和训练超参数配置 │ ├── ship_yolov7.yaml │ └── ship_data.yaml ├── data/ # 数据集路径与标注文件存放 ├── models/ # YOLOv7模型定义与配置文件 ├── utils/ # 工具函数:数据增强、指标计算、可视化 ├── train.py # 训练入口 ├── detect.py # 推理入口 ├── export.py # 模型导出脚本 └── tests/ # 单元测试与验证脚本这样的结构好处是职责清晰。训练和推理分离,模型配置和数据配置分离,后续换数据集或者调整网络结构不会牵一发动全身。2. 核心代码模块功能解析2.1 数据读取与标注处理YOLOv7原生支持YOLO格式的标注文件,每张图片对应一个txt,每一行是“类别 x_center y_center width height”,坐标都归一化到0到1。海面船舶检测的标注重点在于处理遮挡和极小目标。我在数据读取模块里加了一个过滤逻辑:def filter_tiny_objects(annotations, min_size=2): """过滤掉标注宽高小于min_size像素的目标,减少噪声""" valid = [] for ann in annotations: w = ann[2] * img_width h = ann[3] * img_height if w >= min_size and h >= min_size: valid.append(ann) return valid为什么要过滤?标注人员容易把极远处的模糊船影也标成一个几像素的框,这种标注噪声会让训练过程不稳定。实践下来,过滤掉小于2像素的目标后,mAP反而提升了0.3到0.5个百分点。数据集划分上,海面场景经常遇到同一艘船在连续帧反复出现的问题。如果随机划分训练集和验证集,模型会“记住”特定船只,导致验证指标虚高。正确做法是按时间段切分,用前70%时间的视频帧做训练,后30%做验证,确保验证集里的船只没有出现在训练集里。2.2 模型定义与配置文件YOLOv7的模型结构通过yaml文件描述。船舶检测项目用的配置在官方yolov7.yaml基础上做了调整,主要是把检测头里的anchor尺寸重新聚类成适合船舶的比例。船的形状可以细分为三类:狭长渔船、方形货船、小型快艇。它们的宽高比差异大。直接用默认anchor会导致定位不准。聚类代码很短:python train.py --noautoanchor --cfg config/ship_yolov7.yaml先用官方anchor跑50轮,统计训练集标注框的分布,再用k-means重新聚类得到9组anchor。实际操作时要注意:聚类需要在原始像素尺度上做,而不是归一化坐标,否则小目标anchor会被大目标主导。模型结构本身不用大改,重点在输入分辨率和anchor的适配。使用1280输入时,如果显存不够,可以降低batch_size并开启梯度累积。2.3 损失函数与训练流程YOLOv7的损失由三部分组成:box损失(CIoU)、objectness损失和classification损失。项目里根据船只检测的特点调整了权重:损失项默认权重本项目权重调整原因box_loss0.050.08船舶框定位精度要求更高,避免框抖动obj_loss1.00.9背景占多数,稍微降低背景权重缓解类别不平衡cls_loss0.50.5船舶类别少且差异大,保持默认训练启动参数大致如下:python train.py \ --data config/ship_data.yaml \ --cfg config/ship_yolov7.yaml \ --weights yolov7.pt \ --batch-size 8 \ --img-size 1280 \ --epochs 200 \ --device 0 \ --workers 4训练过程中重点看两个指标:loss曲线的下降趋势和验证集mAP的爬升。如果mAP在100轮后还在明显上升,说明还没收敛,可适当延长训练;如果loss降不下去,先检查学习率和batch_size是否匹配。optimizer建议使用SGD配合cosine学习率调度,初始学习率0.01,warmup 3个epoch。AdamW收敛快但容易过拟合,在船舶这种样本量不大的场景下表现一般。2.4 推理与后处理detect.py的推理逻辑不复杂,但有几个参数对海面场景影响很大:conf_thres:置信度阈值,海面检测建议设0.3到0.35。设太低会有大量浪花反光的误检,设太高会漏掉远处小船。iou_thres:NMS的IoU阈值,建议0.45。海上船舶经常密集停靠或并排行驶,阈值太严会把同一艘船分成多个框。imgsz:推理分辨率,必须和训练分辨率保持一致(1280),否则小目标检测效果会明显下降。def detect_ships(image, model, conf_thres=0.3, iou_thres=0.45): results = model(image, size=1280, conf_thres=conf_thres, iou_thres=iou_thres) return results在测试中还发现一个现象:同一个模型,推理时不加任何预处理直接输入原始图像,和在输入端做归一化(除以255)效果差别不大,但用uint8输入比float32输入在TensorRT上能快不少。这点在部署环节非常重要。3. 实操过程:从数据集到落地部署3.1 数据集制作与格式转换数据来源主要有两个方向:公开航拍/卫星数据集,以及自己标注的监控视频截图。公开数据集里有部分标注质量一般,尤其是小船目标存在漏标,训练前需要清洗。清洗方法很直观:把训练集的图片和标注可视化出来,一张张过。如果发现某类船明显漏标,要么补标,要么直接把图片从训练集剔除。自己标注时,推荐用带辅助标注的工具,但标注完一定要导出成YOLO格式。我踩过的坑是:标注工具自动生成的类别编号和训练配置里的类别顺序不一致,导致模型训练效果极差。常见格式转换逻辑,以VOC格式为例:def voc_to_yolo(xml_path, class_names): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) objects = [] for obj in root.iter('object'): name = obj.find('name').text cls_id = class_names.index(name) bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h objects.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") return "\n".join(objects)标注质量比数量重要得多。我宁愿要800张高质量标注,也不要5000张带噪声的低质量数据。这个判断直接影响后面模型的收敛上限。3.2 训练超参数选择与迁移学习训练时用COCO预训练权重yolov7.pt做初始化,这样模型已经学会了基础的边缘、纹理特征,微调到船舶场景只需要200轮左右就能达到较好效果。随机初始化从头训,数据量不够的情况下mAP会低很多。硬件条件有限的话,可以分两个阶段训练:阶段一:冻结backbone层,只训练head层,batch_size可以开大,学习率0.005左右,训练50轮。这个阶段让模型快速适配船舶目标的位置输出。阶段二:解冻全部层,学习率降到0.001,训练剩余150轮,让backbone的特征提取能力也向着船舶特征迁移。阶段一的好处是省显存、稳定。阶段二才是真正提升精度的关键,不能省。训练时间参考:单张3090级别的显卡,1280输入、batch_size 8,一个200轮的训练流程大约需要40到60小时。这个时间在可接受范围内。3.3 模型测试与性能评估训练完成后不能用mAP一个数字概括所有表现。船舶检测场景建议多看三类指标:小目标召回率(AR@0.5 for small objects):船只在几十像素以下时的召回情况。不同距离下的F1值:将验证集按船只目标大小分桶,分别看F1。误检率:在纯海面背景图像上的行为,看是否会报出假目标。用项目里的评估脚本得出结果:python utils/val.py --data config/ship_data.yaml \ --weights runs/train/exp/weights/best.pt \ --img 1280 --conf-thres 0.3 --iou-thres 0.45输出会包含mAP@0.5、mAP@0.5:0.95、precision、recall等信息。我比较在意的是mAP@0.5:0.95,它对定位精度更敏感,更能反映实际检测效果。3.4 模型导出与推理加速训练好的PyTorch模型直接跑视频流非常慢,必须转成ONNX再转TensorRT做FP16推理。导出命令:python export.py --weights runs/train/exp/weights/best.pt \ --img-size 1280 1280 --batch-size 1 \ --simplify --include onnx engine几个实际经验:ONNX必须开--simplify,否则后续TensorRT转换容易失败。batch-size在导出时设为1,运行时如果要用动态batch再单独处理,省去一堆麻烦。FP16精度损失在检测任务里几乎可以忽略,但推理速度能翻一倍。转换TensorRT引擎时,工作空间设为2GB以上,同时开启FP16模式:trtexec --onnx=best.onnx --saveEngine=best.engine --fp16 --workspace=2048实测下来,TensorRT FP16推理在边缘设备上能达到单路视频20fps以上,CPU跟不上的场景也能流畅跑。4. 常见问题与排查心得4.1 海面小目标漏检严重症状:大船检测正常,远处的小渔船完全不出框。排查顺序:确认推理分辨率是否是1280。很多人训练时用1280,推理时嫌慢改回640,小目标直接丢掉。确认anchor是否重新聚类。默认COCO anchor对船舶宽高比适配性差。训练数据里小目标占比是否过低。如果小目标样本不到全部样本的10%,模型会趋向忽略小目标。解决办法是对小目标样本做过采样或复制粘贴增强。数据增强中mosaic比例不要太高。mosaic把四张图拼接,每个目标在原图中占比被缩小,高mosaic比例会进一步损害小目标检测。最后的杀手锏是切图推理:把1280x1280的输入切成两半,每半单独推理,再把结果映射回原图。虽然推理时间翻倍,但小目标召回能提升5个百分点以上。4.2 反光浪花导致的误检症状:海面波浪反光区域被识别成船,晴天尤其严重。原因:船只在远距离时,特征退化为“亮点+暗影”组合,浪花反光也有类似特征。解决办法:训练集里加入包含大量浪花反光的纯背景负样本。数据配置里负样本可以放,YOLO会把这些图像当作背景学习。在数据增强里加入随机亮度反转,模拟阳光在海面形成的高光。推理时将置信度阈值从0.25提高到0.35,代价是少量漏检,但误检会大幅减少。如果在实际监控场景,误检比漏检更容易引起值守人员疲劳,需要平衡。4.3 训练时loss不降或训练崩溃症状:loss曲线平坦,或直接变成nan。常见原因:学习率过大。batch_size=8时lr=0.01可能偏大,调成0.005—0.008。标注文件里出现负数坐标或宽高为0,这会直接导致计算异常。训练前加数据清洗:assert all(w > 0 and h > 0 for _, _, w, h in annotations)多GPU训练时batch_size必须能被GPU数量整除,否则BN统计出错。4.4 推理速度与精度的平衡如果实时性要求极高,最简单的优化不是换模型,而是降低推理分辨率+增大检测输入的不对称策略:用1280训练出来的模型跑960推理,精度损失不大,速度提升明显。如果还不够,TensorRT的FP16是必须的一步。另外,如果画面里大部分区域是海面,可以先用一个背景分割模块切出海面区域,只在海面区域运行船舶检测。这个做法把有效推理面积缩小到原图的40%,速度直接翻倍。5. 后续扩展与个人体会我个人在实际操作中,这轮项目最大的体会是:船舶检测的难点不在模型结构,而在对海面场景的理解和对数据分布的控制。同样的YOLOv7,换一套合理的数据策略和超参数,效果能差出一整个量级。一个小技巧想分享给同行:训练集的船只出现频次统计非常关键。如果某类船(比如渔船)在训练集里占了70%,但实际场景里货船才是主流,训练出来的模型实用性会大打折扣。建议在训练前按类别统计目标数量,用加权采样把不同类别船只的目标数量拉平,这个操作在实战中比调任何模型参数都有效。另外,这套系统后续要做多海域适配的话,可以留一个“增量数据微调”分支:用几个海域的少量数据做迁移学习,权重微调50轮左右,就能适配不同水质、光照特点的海面环境。工程项目调整起来比重新训练省钱省力得多。最后想说,市面上的教程很多,但海面船舶检测这个场景比较冷门,像小目标过采样、负样本增强这些细节,教程里很少系统讲过。希望这份代码功能说明,能帮到正在做相关方向的朋友——这些细节看着不起眼,但每一条都是实打实从踩坑里换来的。
做一个YOLOv7海上船舶检测项目,从数据准备到代码调试再到最终落地,前前后后折腾了不短时间。今天不聊论文复现,也不贴大段理论,就拿着这套系统的代码目录,把每个模块为什么要这么写、里面有哪些坑、实际跑起来效果怎么样,一次说清楚。如果你正准备拿YOLO系列做海面目标识别,或者想给自己的检测项目加点工程化细节,这篇内容应该能帮你少走不少弯路。先说清楚这套系统解决了什么问题:海面场景下的船舶检测,最麻烦的不是检测框准不准,而是船小、背景杂、天气变化大。一条渔船在远海可能只有几十个像素大小,加上浪花反光、海雾干扰,常规目标检测配置很容易漏检误检。这套项目围绕YOLOv7做了针对性优化,从数据增强策略到推理参数调优都做了适配,最终在高分辨率输入下能稳定识别中小型船舶。1. 项目形态与整体设计思路拆解1.1 为什么选 YOLOv7 而不是其他版本选型的时候也纠结过YOLOv5和后来的YOLOv8,最终定在YOLOv7上,核心原因是它在精度和速度之间拿捏得比较稳。YOLOv7的E-ELAN结构在不增加太多计算量的前提下提升了特征提取效率,对于船舶这种纹理简单但尺寸跨度大的目标很合适。另外YOLOv7的辅助训练头机制在训练阶段能提供额外梯度监督,收敛更稳定,对小目标尤其友好。部署层面,YOLOv7的导出链路成熟,ONNX到TensorRT都有现成方案,后续做边缘设备部署不吃力。YOLOv8虽然新,但当时它的检测头改动导致部分工具链需要重新适配,项目周期不允许花太多时间在这上面。提示:如果你只是快速验证、不追求极致部署效率,选YOLOv5s起步也行。但要做到地级市海岸线这种规模的多路视频流识别,YOLOv7的性价比更高。1.2 海面场景的特殊约束与对策海面检测和通用目标检测有本质区别。通用数据集里目标是画面主体,占比较大;海面上的船往往只有几百甚至几十个像素,同时海面本身的纹理结构(波纹、反光、泡沫带)会在相邻帧产生剧烈变化。这意味着模型不能只依赖颜色或纹理边缘来识别。项目里做了三个针对性设计:训练图像分辨率提高到1280x1280,用小目标保住召回率;在数据增强里引入海面特有的扰动方式,比如随机亮度抖动模拟不同时段光照、随机水平翻转模拟船向变化;过滤掉纯背景帧和船只占比过低的样本,避免模型学到“大海即船只”的偷懒策略。1.3 整体代码结构规划一套完整的船舶检测代码不是只有训练脚本,还要包含数据预处理、模型配置、推理脚本和工具函数。这个项目的目录规划如下:ship_detection/ ├── config/ # 模型和训练超参数配置 │ ├── ship_yolov7.yaml │ └── ship_data.yaml ├── data/ # 数据集路径与标注文件存放 ├── models/ # YOLOv7模型定义与配置文件 ├── utils/ # 工具函数:数据增强、指标计算、可视化 ├── train.py # 训练入口 ├── detect.py # 推理入口 ├── export.py # 模型导出脚本 └── tests/ # 单元测试与验证脚本这样的结构好处是职责清晰。训练和推理分离,模型配置和数据配置分离,后续换数据集或者调整网络结构不会牵一发动全身。2. 核心代码模块功能解析2.1 数据读取与标注处理YOLOv7原生支持YOLO格式的标注文件,每张图片对应一个txt,每一行是“类别 x_center y_center width height”,坐标都归一化到0到1。海面船舶检测的标注重点在于处理遮挡和极小目标。我在数据读取模块里加了一个过滤逻辑:def filter_tiny_objects(annotations, min_size=2): """过滤掉标注宽高小于min_size像素的目标,减少噪声""" valid = [] for ann in annotations: w = ann[2] * img_width h = ann[3] * img_height if w >= min_size and h >= min_size: valid.append(ann) return valid为什么要过滤?标注人员容易把极远处的模糊船影也标成一个几像素的框,这种标注噪声会让训练过程不稳定。实践下来,过滤掉小于2像素的目标后,mAP反而提升了0.3到0.5个百分点。数据集划分上,海面场景经常遇到同一艘船在连续帧反复出现的问题。如果随机划分训练集和验证集,模型会“记住”特定船只,导致验证指标虚高。正确做法是按时间段切分,用前70%时间的视频帧做训练,后30%做验证,确保验证集里的船只没有出现在训练集里。2.2 模型定义与配置文件YOLOv7的模型结构通过yaml文件描述。船舶检测项目用的配置在官方yolov7.yaml基础上做了调整,主要是把检测头里的anchor尺寸重新聚类成适合船舶的比例。船的形状可以细分为三类:狭长渔船、方形货船、小型快艇。它们的宽高比差异大。直接用默认anchor会导致定位不准。聚类代码很短:python train.py --noautoanchor --cfg config/ship_yolov7.yaml先用官方anchor跑50轮,统计训练集标注框的分布,再用k-means重新聚类得到9组anchor。实际操作时要注意:聚类需要在原始像素尺度上做,而不是归一化坐标,否则小目标anchor会被大目标主导。模型结构本身不用大改,重点在输入分辨率和anchor的适配。使用1280输入时,如果显存不够,可以降低batch_size并开启梯度累积。2.3 损失函数与训练流程YOLOv7的损失由三部分组成:box损失(CIoU)、objectness损失和classification损失。项目里根据船只检测的特点调整了权重:损失项默认权重本项目权重调整原因box_loss0.050.08船舶框定位精度要求更高,避免框抖动obj_loss1.00.9背景占多数,稍微降低背景权重缓解类别不平衡cls_loss0.50.5船舶类别少且差异大,保持默认训练启动参数大致如下:python train.py \ --data config/ship_data.yaml \ --cfg config/ship_yolov7.yaml \ --weights yolov7.pt \ --batch-size 8 \ --img-size 1280 \ --epochs 200 \ --device 0 \ --workers 4训练过程中重点看两个指标:loss曲线的下降趋势和验证集mAP的爬升。如果mAP在100轮后还在明显上升,说明还没收敛,可适当延长训练;如果loss降不下去,先检查学习率和batch_size是否匹配。optimizer建议使用SGD配合cosine学习率调度,初始学习率0.01,warmup 3个epoch。AdamW收敛快但容易过拟合,在船舶这种样本量不大的场景下表现一般。2.4 推理与后处理detect.py的推理逻辑不复杂,但有几个参数对海面场景影响很大:conf_thres:置信度阈值,海面检测建议设0.3到0.35。设太低会有大量浪花反光的误检,设太高会漏掉远处小船。iou_thres:NMS的IoU阈值,建议0.45。海上船舶经常密集停靠或并排行驶,阈值太严会把同一艘船分成多个框。imgsz:推理分辨率,必须和训练分辨率保持一致(1280),否则小目标检测效果会明显下降。def detect_ships(image, model, conf_thres=0.3, iou_thres=0.45): results = model(image, size=1280, conf_thres=conf_thres, iou_thres=iou_thres) return results在测试中还发现一个现象:同一个模型,推理时不加任何预处理直接输入原始图像,和在输入端做归一化(除以255)效果差别不大,但用uint8输入比float32输入在TensorRT上能快不少。这点在部署环节非常重要。3. 实操过程:从数据集到落地部署3.1 数据集制作与格式转换数据来源主要有两个方向:公开航拍/卫星数据集,以及自己标注的监控视频截图。公开数据集里有部分标注质量一般,尤其是小船目标存在漏标,训练前需要清洗。清洗方法很直观:把训练集的图片和标注可视化出来,一张张过。如果发现某类船明显漏标,要么补标,要么直接把图片从训练集剔除。自己标注时,推荐用带辅助标注的工具,但标注完一定要导出成YOLO格式。我踩过的坑是:标注工具自动生成的类别编号和训练配置里的类别顺序不一致,导致模型训练效果极差。常见格式转换逻辑,以VOC格式为例:def voc_to_yolo(xml_path, class_names): tree = ET.parse(xml_path) root = tree.getroot() size = root.find('size') img_w = int(size.find('width').text) img_h = int(size.find('height').text) objects = [] for obj in root.iter('object'): name = obj.find('name').text cls_id = class_names.index(name) bbox = obj.find('bndbox') xmin = int(bbox.find('xmin').text) ymin = int(bbox.find('ymin').text) xmax = int(bbox.find('xmax').text) ymax = int(bbox.find('ymax').text) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h width = (xmax - xmin) / img_w height = (ymax - ymin) / img_h objects.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") return "\n".join(objects)标注质量比数量重要得多。我宁愿要800张高质量标注,也不要5000张带噪声的低质量数据。这个判断直接影响后面模型的收敛上限。3.2 训练超参数选择与迁移学习训练时用COCO预训练权重yolov7.pt做初始化,这样模型已经学会了基础的边缘、纹理特征,微调到船舶场景只需要200轮左右就能达到较好效果。随机初始化从头训,数据量不够的情况下mAP会低很多。硬件条件有限的话,可以分两个阶段训练:阶段一:冻结backbone层,只训练head层,batch_size可以开大,学习率0.005左右,训练50轮。这个阶段让模型快速适配船舶目标的位置输出。阶段二:解冻全部层,学习率降到0.001,训练剩余150轮,让backbone的特征提取能力也向着船舶特征迁移。阶段一的好处是省显存、稳定。阶段二才是真正提升精度的关键,不能省。训练时间参考:单张3090级别的显卡,1280输入、batch_size 8,一个200轮的训练流程大约需要40到60小时。这个时间在可接受范围内。3.3 模型测试与性能评估训练完成后不能用mAP一个数字概括所有表现。船舶检测场景建议多看三类指标:小目标召回率(AR@0.5 for small objects):船只在几十像素以下时的召回情况。不同距离下的F1值:将验证集按船只目标大小分桶,分别看F1。误检率:在纯海面背景图像上的行为,看是否会报出假目标。用项目里的评估脚本得出结果:python utils/val.py --data config/ship_data.yaml \ --weights runs/train/exp/weights/best.pt \ --img 1280 --conf-thres 0.3 --iou-thres 0.45输出会包含mAP@0.5、mAP@0.5:0.95、precision、recall等信息。我比较在意的是mAP@0.5:0.95,它对定位精度更敏感,更能反映实际检测效果。3.4 模型导出与推理加速训练好的PyTorch模型直接跑视频流非常慢,必须转成ONNX再转TensorRT做FP16推理。导出命令:python export.py --weights runs/train/exp/weights/best.pt \ --img-size 1280 1280 --batch-size 1 \ --simplify --include onnx engine几个实际经验:ONNX必须开--simplify,否则后续TensorRT转换容易失败。batch-size在导出时设为1,运行时如果要用动态batch再单独处理,省去一堆麻烦。FP16精度损失在检测任务里几乎可以忽略,但推理速度能翻一倍。转换TensorRT引擎时,工作空间设为2GB以上,同时开启FP16模式:trtexec --onnx=best.onnx --saveEngine=best.engine --fp16 --workspace=2048实测下来,TensorRT FP16推理在边缘设备上能达到单路视频20fps以上,CPU跟不上的场景也能流畅跑。4. 常见问题与排查心得4.1 海面小目标漏检严重症状:大船检测正常,远处的小渔船完全不出框。排查顺序:确认推理分辨率是否是1280。很多人训练时用1280,推理时嫌慢改回640,小目标直接丢掉。确认anchor是否重新聚类。默认COCO anchor对船舶宽高比适配性差。训练数据里小目标占比是否过低。如果小目标样本不到全部样本的10%,模型会趋向忽略小目标。解决办法是对小目标样本做过采样或复制粘贴增强。数据增强中mosaic比例不要太高。mosaic把四张图拼接,每个目标在原图中占比被缩小,高mosaic比例会进一步损害小目标检测。最后的杀手锏是切图推理:把1280x1280的输入切成两半,每半单独推理,再把结果映射回原图。虽然推理时间翻倍,但小目标召回能提升5个百分点以上。4.2 反光浪花导致的误检症状:海面波浪反光区域被识别成船,晴天尤其严重。原因:船只在远距离时,特征退化为“亮点+暗影”组合,浪花反光也有类似特征。解决办法:训练集里加入包含大量浪花反光的纯背景负样本。数据配置里负样本可以放,YOLO会把这些图像当作背景学习。在数据增强里加入随机亮度反转,模拟阳光在海面形成的高光。推理时将置信度阈值从0.25提高到0.35,代价是少量漏检,但误检会大幅减少。如果在实际监控场景,误检比漏检更容易引起值守人员疲劳,需要平衡。4.3 训练时loss不降或训练崩溃症状:loss曲线平坦,或直接变成nan。常见原因:学习率过大。batch_size=8时lr=0.01可能偏大,调成0.005—0.008。标注文件里出现负数坐标或宽高为0,这会直接导致计算异常。训练前加数据清洗:assert all(w > 0 and h > 0 for _, _, w, h in annotations)多GPU训练时batch_size必须能被GPU数量整除,否则BN统计出错。4.4 推理速度与精度的平衡如果实时性要求极高,最简单的优化不是换模型,而是降低推理分辨率+增大检测输入的不对称策略:用1280训练出来的模型跑960推理,精度损失不大,速度提升明显。如果还不够,TensorRT的FP16是必须的一步。另外,如果画面里大部分区域是海面,可以先用一个背景分割模块切出海面区域,只在海面区域运行船舶检测。这个做法把有效推理面积缩小到原图的40%,速度直接翻倍。5. 后续扩展与个人体会我个人在实际操作中,这轮项目最大的体会是:船舶检测的难点不在模型结构,而在对海面场景的理解和对数据分布的控制。同样的YOLOv7,换一套合理的数据策略和超参数,效果能差出一整个量级。一个小技巧想分享给同行:训练集的船只出现频次统计非常关键。如果某类船(比如渔船)在训练集里占了70%,但实际场景里货船才是主流,训练出来的模型实用性会大打折扣。建议在训练前按类别统计目标数量,用加权采样把不同类别船只的目标数量拉平,这个操作在实战中比调任何模型参数都有效。另外,这套系统后续要做多海域适配的话,可以留一个“增量数据微调”分支:用几个海域的少量数据做迁移学习,权重微调50轮左右,就能适配不同水质、光照特点的海面环境。工程项目调整起来比重新训练省钱省力得多。最后想说,市面上的教程很多,但海面船舶检测这个场景比较冷门,像小目标过采样、负样本增强这些细节,教程里很少系统讲过。希望这份代码功能说明,能帮到正在做相关方向的朋友——这些细节看着不起眼,但每一条都是实打实从踩坑里换来的。