做水上目标检测有一阵子了最近终于把手里的船舰检测项目整理成了一套完整可跑的代码顺手写一篇复盘记录。这个项目是基于YOLOv8做的船舰检测系统输入视频或图片输出带有检测框和置信度的结果可用于水上交通监管、港区安防、航道异物监测等场景。源码已经打包好了包含完整训练、验证、推理脚本拿到手改改路径就能跑。很多初学者一上来就纠结“用什么模型”其实模型选型只占项目的一小部分。真正决定检测效果上限的是数据和训练细节。以船舰检测为例水面反光、船体尺度差异、遮挡目标、远距离小目标这些都是非常现实的工程问题光靠换模型解决不了。这篇文章不打算写成YOLOv8的简单调用教程而是把整个项目从数据准备、训练调参、代码实现到部署优化的全链路细节过一遍写清楚每一步的取舍依据和踩过的坑。适合刚接触目标检测、想拿实际项目练手的朋友也适合已经在做检测任务但想提升精度的从业者。1. 项目与选型解析为什么是YOLOv81.1 项目背景与核心需求这个船舰检测系统最初是为了解决一个很实际的监控需求某水域管理方希望用摄像头自动识别进出监控区域的各种船只替代人工盯屏。项目要求不算复杂核心就三点实时性要够、小目标不能漏、部署机器性能不理想。实时性意味着不能用Faster R-CNN这类两阶段检测器即使它的精度在某些场景更好但视频流场景下每秒十几帧的推理速度根本扛不住。漏检小目标则更棘手水域监控画面里远处的船可能只有几十个像素在整张图上占比不到百分之一。再加上水面反光、雾天成像、船舶颜色与水面接近等因素误检漏检的概率非常高。这些约束条件直接决定了模型选型方向需要一个在精度和速度之间平衡较好的单阶段检测器。项目组当时对比了YOLOv5、YOLOv7、YOLOv8三个候选方案。YOLOv5胜在资料多、社区大但结构相对老YOLOv7性能不错可部署生态略乱YOLOv8则改了C2f结构、解耦检测头训练和推理流程一体化官方给了非常顺畅的API而且导出ONNX、TensorRT都非常方便。最终选了YOLOv8不是因为它在所有指标上最强而是因为它在工程落地环节足够省心。1.2 YOLOv8相比前代的关键改动理解YOLOv8的改动对后面调参非常有帮助。它相比YOLOv5有几个核心变化。骨干网络里的C3模块换成了C2f模块这个模块结构上相当于把更多的梯度分支并联起来让特征在层间流动得更充分对小目标的信息保留更友好。检测头从Coupled Head换成了Decoupled Head分类和回归各走各的分支收敛速度更快精度也有小幅提升。另一个重要改动是Anchor-Free化。YOLOv5还是基于预定义锚框的需要针对数据集聚类出合适的锚框尺寸YOLOv8直接去掉了锚框改成从特征图每个位置直接预测目标中心和尺寸。这意味着不用再为不同数据集重新聚类锚框泛化性和训练稳定性都更好。锚框聚类这件事看似简单但对船舰这种尺度变化极大的目标来说聚类效果不佳会导致训练初期损失波动大YOLOv8把这个坑抹平了。损失函数方面分类用BCE回归用CIoU和DFL的组合。DFL可以让模型学习边框的分布对边界模糊的目标预测更准。理解这些细节的作用是项目里遇到损失不降、边框不准时能快速判断问题出在数据还是模型而不是盲目改参数。2. 数据集处理决定效果上限的关键环节2.1 数据来源与筛选注意事项做检测项目最忌一上来就开训练脚本数据才是真正决定模型上限的东西。这个项目的船舰数据主要来源于两个渠道公开的船舰检测数据集和补充自采的监控视频截帧。公开数据集的好处是量大、类别标注基本靠谱缺点是场景相对单一很多标注目标都比较大、背景干净直接拿这种数据训练出来的模型放到真实水域监控画面上会出现严重的域迁移问题。我建议把公开数据当成“底座”用它让模型先学到船舰的基本形态特征再用自采的真实场景数据做微调。数据筛选环节有3个很实际的坑。第一是重复帧视频抽帧得到的图片大量相似如果直接全部拿进去训练模型会严重过拟合到这些重复画面上表现为训练损失很好看、验证集mAP也不错但真实场景一测就露馅。第二是类别严重不平衡这个项目虽然只需要检测一个类别“船”但渔船、货轮、快艇的数量差距极大快艇样本可能只有货轮的十分之一。我在采样时做了简单的过采样处理让每类数量控制在5:1以内。第三是坏图摄像头偶尔会输出花屏、全黑或严重畸变的帧这些必须在预处理阶段删掉否则训练过程偶发异常就是它们在作怪。2.2 标注规范与格式转换标注工具我用的是LabelImg虽然界面朴素但胜在轻量和稳定。标注时统一使用矩形框标注原则只有一条目标可见轮廓超过三分之一就框。不要奢望把每艘船都框得严丝合缝检测框有一点冗余完全不影响模型收敛反而能把标注工作量压下来。YOLO格式的标注文件是每个图片对应一个txt每行是“类别 x_center y_center width height”坐标值是相对于图片宽高的比例归一化到0到1之间。转换的时候有两点容易出错。类别索引必须从0开始不要写成1否则训练时数据加载会直接错位。坐标一定是中心点坐标和宽高和VOC格式的左上右下角坐标不同转换时需要计算一遍。我当时写了一个批量转换脚本几行代码的事情但能省大量手工时间。import os def voc_to_yolo(anno_path, out_path, img_w, img_h, class_map): with open(anno_path, r, encodingutf-8) as f: lines f.readlines() yolo_lines [] for line in lines: parts line.strip().split() cls_name parts[0] x1, y1, x2, y2 map(float, parts[1:]) box_w (x2 - x1) / img_w box_h (y2 - y1) / img_h x_c (x1 x2) / 2.0 / img_w y_c (y1 y2) / 2.0 / img_h yolo_lines.append(f{class_map[cls_name]} {x_c:.6f} {y_c:.6f} {box_w:.6f} {box_h:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(yolo_lines))数据集目录结构按YOLOv8官方约定安排images和labels两个目录下分train和val不搞花活因为后续yaml配置要严格对应这个结构。2.3 数据增强策略YOLOv8训练时会自动启用Mosaic增强把4张图拼成一张这个策略对小目标检测帮助很大因为拼接后目标相对变小变相让模型见识了更多小尺度目标。但Mosaic在训练后期可以适当关闭不然目标尺度一直被压缩模型可能对正常尺度目标不够敏感。我设置最终模型训练时用ultralytics的参数做了动态关闭让最后几十轮恢复到正常尺度分布。除了MosaicHSV扰动、随机翻转、缩放旋转这些增强我都开了。但有一个建议水面场景本身色彩单一、反光强HSV饱和度扰动幅度不要开太大否则会把水面颜色扰动成完全不可见的伪彩色让模型学到奇怪的颜色关联。我用的是饱和度扰动0.5亮度扰动0.4色相扰动保持非常小0.02左右。增强不是越多越好尤其对于真实场景数据过强的几何增强会让船体变形失真。如果一个数据集里大量目标是侧视船只你非要旋转45度增强模型学到的是“不存在的形态”测试时反而拖后腿。我习惯用增强后的数据先做一轮小规模实验再根据验证集效果调整增强强度这样最稳。3. YOLOv8训练流程与参数调优3.1 环境搭建与预训练权重选择训练环境的坑主要是版本匹配。Python版本建议3.8到3.11之间PyTorch选2.x版本安装GPU版一定要确认CUDA版本和显卡驱动匹配。我用的是CUDA 11.8配合PyTorch 2.0的组合实测比较稳。安装YOLOv8官方包很简单一条命令就搞定pip install ultralytics装完后可以用一个最小化脚本验证环境from ultralytics import YOLO model YOLO(yolov8s.pt) results model.predict(https://ultralytics.com/images/bus.jpg)能正常输出检测框说明环境没问题。预训练权重选哪个取决于你的资源。yolov8n.pt最快但精度低适合做初步验证yolov8s.pt是性价比之选模型体积适中、显存占用不高yolov8m.pt精度更好但训练时间明显变长。这个项目最后用的是yolov8s.pt作为预训练权重在1080Ti上训练一轮大约90秒整个训练流程可控。3.2 数据集配置与训练命令数据配置文件是训练前必须写好的。这里的重点是路径写对学习率等参数在训练命令里覆盖。path: D:/projects/ship_detection/datasets train: images/train val: images/val names: 0: ship训练命令我用的是官方CLI方式方便记录和复现yolo train dataship_detection.yaml modelyolov8s.pt epochs120 imgsz640 batch16 device0 workers4下面对几个关键参数做一些解释。imgsz设为640是默认值但船舰场景建议至少用640显存充足时可以用832或1024来提升小目标召回率。因为船在小分辨率下特征太弱大输入尺寸对水域远距离目标帮助非常明显。batch设为16是显存与速度的折中batch过小会导致BN层统计不稳定过大则容易显存溢出。epochs设120轮。不是说越多越好训练到后期loss已经收敛继续跑只会浪费时间甚至过拟合。我后面加了一个早停策略验证集mAP连续20轮不提升就自动停止。学习率用默认的0.01配合官方自带的调度器如果你没有充分理由不要乱动学习率。训练过程有一个值得说的经验我第一次训练时直接跑完全部120轮最后30轮明显已经过拟合了验证集mAP和损失都在原地不动甚至反弹。后来加了早停训练在第84轮就自动停了效果反而更好。训练不是跑得越久越好的。3.3 训练结果分析与质量判断训练结束后不要只盯着mAP数值看。项目里我优先看三个文件results.png的曲线、混淆矩阵、预测样例图。results.png里有loss曲线和mAP曲线。正常情况下训练和验证的box_loss、cls_loss应该同步平滑下降验证集曲线如果出现明显抬头就是过拟合信号。mAP50达到0.85以上基本满足大部分实际场景但要注意mAP50-95数值它更严格地考察边框质量如果mAP50高但mAP50-95只有0.5左右说明检测框不够准需要检查标注质量和后处理参数。混淆矩阵对于单类检测来说意义不大但对多类别场景非常直观。预测样例图一定要人工看一遍重点观察小目标有没有漏检、靠岸密集停泊的船有没有被漏检、非船目标有没有被当成船。机器算出来的指标永远无法替代人眼对真实场景的判断我每轮实验都会随机抽500张验证图肉眼过一遍这个习惯帮我发现过好几次数据标注问题。4. 检测系统实现从模型到可用工具4.1 推理脚本核心逻辑训练完的best.pt模型如何变成可用的检测系统这一步很多人会忽略中间工程细节。本项目提供两种推理方式命令行直跑和Python脚本调用。命令行方式适合快速验证官方CLI就可以完成yolo predict modelbest.pt sourcetest_video.mp4 conf0.25 saveTrue但实际项目里我更推荐写Python脚本因为需要弹性的后处理和结果输出逻辑。核心推理代码非常短from ultralytics import YOLO import cv2 model YOLO(runs/detect/train/weights/best.pt) results model.predict(sourceinput_video.mp4, conf0.25, iou0.5, streamTrue) for result in results: boxes result.boxes.xyxy.cpu().numpy() scores result.boxes.conf.cpu().numpy() for box, score in zip(boxes, scores): x1, y1, x2, y2 map(int, box) cv2.rectangle(result.orig_img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(result.orig_img, fship {score:.2f}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) # 这里可以把result.orig_img写入输出视频或推流这里有两个参数需要理解。conf是置信度阈值设太高会漏检设太低会出现大量误检框。航道监控场景我建议0.25作为初始值再根据实际误报率调整iou用于NMS去重设0.5是常规选择如果检测目标靠得近且高密度可以适当降到0.4。streamTrue很重要它会以生成器方式一帧一帧产出结果避免一次性把所有视频帧加载进内存导致内存爆炸。我见过不少人把这个参数漏掉跑长视频时中途内存溢出处理长视频或实时视频流时一定要加上。4.2 可视化与界面方案项目交付时管理方希望有一个可以上传图片或视频、看到检测结果的简单界面。我评估了几种方案最后选择了基于Gradio的轻量实现而不是搭一个完整的Web前后端。原因很简密。这个系统只服务于内部试用用户量个位数不需要鉴权、负载均衡这些复杂能力Gradio可以在几十行代码内完成上传、检测、展示的闭环对非技术用户也友好。关于界面代码项目源码里已经提供这里展示核心的推理封装函数import gradio as gr from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) def detect_image(image): results model.predict(sourceimage, conf0.25) return results[0].plot() if results else image demo gr.Interface( fndetect_image, inputsgr.Image(typenumpy), outputsgr.Image(typenumpy), title船舰检测系统, description上传图片/视频自动识别船舰目标 ) if __name__ __main__: demo.launch(server_name0.0.0.0, server_port7860)results[0].plot()是官方提供的画框方法返回已经绘制好检测框的图片省去手动调用OpenCV画框的代码。界面方案没有选择高大全够用、稳定、易维护才是这个项目最看重的。4.3 模型导出与部署加速训练好的模型要落地到真实监控环境通常不能直接跑PyTorch模型。目标机器性能有限我做了两步导出ONNX格式做跨平台推理再用TensorRT做GPU加速。导出ONNX非常方便yolo export modelbest.pt formatonnx opset12导出的onnx模型可以用ONNX Runtime加载CPU上也能跑但速度一般。如果目标机器有NVIDIA显卡更推荐转成TensorRT引擎推理速度可以达到PyTorch直接推理的2到3倍。这一步需要目标机器的显卡驱动和TensorRT版本匹配建议在部署机上现场构建引擎避免跨平台转移后的兼容性坑。关于部署有一点务必注意导出前要固定输入尺寸。推理时opencv读入的视频帧尺寸如果和模型输入不一致需要做resize并保持宽高比不能直接拉伸否则框的位置会偏移。我的习惯是推理前统一将帧resize到640x640同时记录缩放比例画框时再映射回原图坐标。这段逻辑虽然简单但处理不仔细显示的检测框就会错位。5. 常见问题排查与精度优化记录5.1 高频问题速查与解决方案整个项目下来收集了一批非常典型的报错和状态异常整理成表格方便检索。现象排查方向解决方案训练时报显存不足OOMbatch太大或imgsz太大降低batch或启用梯度累积loss一开始就不下降数据标注格式错误、类别索引错位抽查txt标注文件确认坐标归一化正确验证集mAP很高真实视频大量漏检训练数据与测试数据域差异大补充目标场景数据做微调加大imgsz检测框偏移或尺寸异常推理时未同步resize比例固定输入尺寸记录scale并在画框时还原视频推理内存持续上涨没有使用streamTrue改为streamTrue逐帧处理并释放结果模型把浮标、泡沫箱识别成船负样本不足收集无船负样本图加入训练set做背景类这里要特别说一下负样本的问题。很多人做单类检测时只准备正样本导致误检频发。我在数据集里特意加入了大约200张完全没有船的纯水面、岸线、浮标图片全部使用空标注txt让模型学会“什么都不是船”。这一步对降低误检率的效果极其明显往往比增加几百张正样本还有用。5.2 精度提升的实战经验项目做到第二版时mAP50已经到0.87但小目标漏检依然明显。我尝试了一系列优化最终有效的是以下几个。把imgsz从640提高到896。代价是训练速度下降约35%但小目标的召回率提升了接近8个点。对于船舰检测这类目标尺度分散的真实场景大输入尺寸是性价比最高的优化手段。补充小目标数据用切片方式把原图中包含船的局部区域裁剪出来放大后作为新样本相当于人工让小目标在数据中占比更高。这个方法配合原图一起训练模型对远距离小目标的识别能力提升明显。修改推理时的conf阈值并在检测后加一个基于目标长宽比的过滤规则。船舰目标的宽高比通常有规律极端细长的虚检目标往往不是船。我在后处理里加了一个可选的宽高比过滤粗暴但有效误报率又降了一截。训练后期关闭Mosaic增强。我通过ultralytics的参数对最后20轮关闭Mosaic让模型在一段时间内看到的是干净的整图数据而不是拼图数据校准目标尺度分布最终mAP有小幅提升。过拟合防护方面除了早停和增强我把dropout参数选了默认值没有额外调。数据量在1500张以上时正则化不是主要矛盾。6. 源码结构与使用说明6.1 项目文件组织源码目录结构如下简单但有明确分层ship_detection/ ├── datasets/ │ ├── images/ │ │ ├── train/ │ │ └── val/ │ └── labels/ │ ├── train/ │ └── val/ ├── configs/ │ └── ship_detection.yaml ├── src/ │ ├── train.py │ ├── detect.py │ └── utils.py ├── app/ │ └── gradio_app.py ├── weights/ │ └── best.pt └── requirements.txttrain.py里封装了完整训练流程支持命令行参数覆盖epochs、batch、imgsz等关键配置。detect.py负责推理和结果输出支持图片、视频和摄像头实时流。utils.py里放了一些辅助函数包括标注格式转换和结果可视化。gradio_app.py是上面提到的可视化界面入口。6.2 复现步骤拿到源码后建议按下面几个步骤操作。第一步安装依赖pip install -r requirements.txtrequirements.txt里列了ultralytics、opencv-python、torch、gradio等核心库建议先确认torch版本和CUDA匹配。第二步把数据集放到datasets目录下修改configs/ship_detection.yaml中的path为本地实际路径。第三步执行训练python src/train.py --data configs/ship_detection.yaml --weights yolov8s.pt --epochs 120第四步用训练好的权重测试图片python src/detect.py --source test.jpg --weights weights/best.pt第五步启动可视化界面python app/gradio_app.py然后浏览器打开提示的地址即可使用。6.3 后续扩展方向这个系统目前是单类船舰检测后续扩展有几个明确方向。类别细分从“船”扩展到“货轮、渔船、快艇、帆船”等多类检测需要重新标注数据并调整类别数。目标追踪接入ByteTrack或BoT-SORT实现视频中每艘船的ID跟踪和轨迹绘制这对航道流量统计价值很大。异常行为分析结合检测框位置变化判断船只是否逆行、停留超时属于更高层的业务逻辑。我个人在实际操作中最深的体会是目标检测项目的成败很少取决于模型本身的强弱更多取决于数据和工程细节。YOLOv8给了你一个非常好的起点但真正拉开差距的是你对数据有多了解、对部署环境有多敬畏。把本文里这些细节吃透船舰检测系统能做到可交付的水平。