
简介面向船舶航行安全领域的 YOLOv11 应用型技术文档共 35 页适合航海工程、计算机视觉研究人员及航运安全从业者参考。内容从 YOLOv11 原理出发系统讲解海上漂浮物检测模型的构建、训练与评估并在此基础上设计基于概率与模糊逻辑的碰撞风险评估算法覆盖数据采集、预处理、模型推理、系统实现与实验对比等完整流程配有清晰的目录章节便于快速定位所需内容。资源压缩包共 1 个 PDF 文件大小约 2.19MB文档结构完整文字、图表及公式均显示正常。目前已有 59 人学习浏览适合需要了解单阶段目标检测在海上安全场景中落地思路的读者。通过本文档读者可掌握从数据处理、YOLOv11 模型搭建到碰撞风险评估算法设计与系统集成的整体方法并获得实验环境配置、检测精度评估及算法优化等环节的详细说明。1. 海上漂浮物检测难点从来不是模型而是那几像素的目标YOLOv11做海上漂浮物检测听起来是个标准的检测任务真正上手你会发现难点几乎不在模型本身而在于海面这个极端环境——波浪反光在相机里跟白色浮标几乎长一个样远处集装箱落水物可能只有七八个像素雾天和傍晚的低对比度更是让误检率翻倍。而碰撞风险评估又比普通检测多一步你必须把检测框的像素位置换算成经纬度和距离再算两船或船与目标之间的最近会遇距离DCPA与最近会遇时间TCPA。这篇笔记从环境配置写到模型训练、小目标优化再把检测结果接到风险算法上覆盖一条能跑通的完整链路适合刚接触YOLOv11的船用视觉开发者也适合已经在用旧版YOLO做检测、想迁移到v11并在评估层做深的人。2. YOLOv11的环境配置与首轮推理先把权重文件下载这件小事做对2.1 Ultralytics安装与Python版本的选择YOLOv11由Ultralytics维护安装方式跟YOLOv8一脉相承用pip装ultralytics包就能用到完整的v11模型族。我一般建议在conda里单独建环境别往系统Python里硬塞船用设备或工业机上的Python环境往往被其他脚本占着版本冲突会让你排查到怀疑人生。conda create -n yolo11 python3.10 -y conda activate yolo11 pip install ultralytics安装时顺手把ipython和jupyter装上调试标注和可视化方便。python3.10是我常用的版本3.11和3.12也能跑但有些老牌的图像处理库对3.12的兼容性还有坑别给自己添麻烦。装完验证一下在Python里执行from ultralytics import YOLO不报错就说明基础环境通了。注意首次执行YOLO(yolo11n.pt)会自动下载权重文件这个下载在国内网络环境下可能很慢或者直接卡死建议直接在浏览器或迅雷里从GitHub Releases手动下。权重文件放哪放在项目根目录下的weights/文件夹里用绝对路径加载避免脚本换个目录跑就找不到模型——这个坑我踩了不止一次。2.2 预训练权重的选择yolo11n还是yolo11sYOLOv11提供了n、s、m、l、x五个规格从轻到重。海上漂浮物检测跑在船载工控机上算力有限我一般从yolo11n或yolo11s起步。yolo11n的权重文件小CPU都能跑实时推理虽然帧率不高适合先验证整条链路通不通yolo11s精度更高但显存需求也上来推理时要留意GPU占用。from ultralytics import YOLO # 加载预训练权重 model YOLO(weights/yolo11n.pt) # 对单张图片推理并保存结果 results model.predict( sourcesamples/harbor_day.jpg, imgsz640, conf0.25, saveTrue, projectruns/detect, namefirst_try ) # 打印检测到的类别、置信度和框坐标 for r in results: boxes r.boxes if boxes is not None: for box in boxes: print(box.cls, box.conf, box.xyxy)这里的imgsz640是输入分辨率首次验证用默认值就行conf0.25是置信度阈值低于这个值的检测结果会被丢弃。海面反光造成的虚警通常置信度不高调高到0.35能显著减少误报但也会牺牲部分远距离小目标的召回后面会专门讲怎么平衡。2.3 用视频验证检测效果别只看单帧单张图片验证通过不代表海上就能用海浪是动态的反光每帧都在变。我建议直接用视频流测试。model.predict( sourcesamples/harbor_night.mp4, imgsz640, conf0.25, saveTrue, vid_stride2, # 每隔一帧处理一次 projectruns/detect, namevideo_test )vid_stride2表示跳帧处理先把视频按原帧率读进来处理每隔一帧输出视频的帧率会减半但速度大幅提升。你在做算法验证时不需要实时主要看检测框是否稳定——一个漂浮物在连续帧里忽有忽无往往是阈值问题或者目标太小如果框在目标周围抖动明显可能是NMS参数没调好。这一轮做完你就有了一个能跑通的基础检测器。下一步的关键是预训练模型并不认识“海上漂浮物”。COCO数据集里没有“废弃集装箱”“浮标”“漂浮渔网”这些类别你需要自己采集数据、标注、训练。3. 从零搭建海上漂浮物数据集标注格式转换与类别策略3.1 数据从哪里来自采为主公开数据集为辅海上漂浮物检测的公开数据集极度匮乏这也是这个方向比较冷门的原因之一。常见做法是自己在港口、航道、海岸线架相机采集或者从船载摄像头的历史录像里抽帧。采集时注意覆盖不同时段——白天顺光、逆光、黄昏、夜间红外、雨雾天每个场景至少要几百张图否则模型会严重过拟合到某个光照条件。抽帧用ffmpeg一条命令搞定ffmpeg -i harbor_raw.mp4 -vf fps2 -qscale:v 2 frames/frame_%04d.jpgfps2表示每秒抽2帧。录像时长1小时能抽7200张但你不需要全部标注——按场景随机抽1000~2000张先跑一轮训练再看哪些场景漏检严重补采补标。这种做法叫“困难样本挖掘”比一次性标几千张高效得多。3.2 类别设计宁可少不要多很多新手上来就标十几个类别塑料瓶、泡沫、木板、油污……实际上模型会懵因为漂浮物在视觉上差异巨大同类物体在海上的外观变化也大。我建议按碰撞风险等级来设计类别而不是按材质毕竟你的最终目标是风险评估buoy浮标、航标固定或系泊基本无碰撞风险但需要避开debris废弃渔网、木板、集装箱等无动力漂浮物风险高vessel小船、渔船、工作艇有动力但可能不遵守航道风险最高三个类别足够支撑后续的风险评估逻辑。你可以在标注时用统一标签再在代码里通过目标类型附加不同的风险系数。3.3 VOC转YOLO格式一个顺手但坑多的脚本标注工具推荐LabelImg或X-AnyLabeling导出格式一般选VOCXML。但YOLOv11需要的标签是归一化的txt格式每个文件一行class_id x_center y_center width height。这个转换我写过不下十遍脚本拿过来就能用import os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_names): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) yolo_lines [] for obj in root.iter(object): cls obj.find(name).text if cls not in class_names: continue # 类别不在列表里就跳过 cls_id class_names.index(cls) bbox obj.find(bndbox) x1 float(bbox.find(xmin).text) y1 float(bbox.find(ymin).text) x2 float(bbox.find(xmax).text) y2 float(bbox.find(ymax).text) x_center (x1 x2) / 2 / img_w y_center (y1 y2) / 2 / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) out_path os.path.join(out_dir, os.path.splitext(os.path.basename(xml_path))[0] .txt) with open(out_path, w) as f: f.write(\n.join(yolo_lines)) # 用法 class_names [buoy, debris, vessel] os.makedirs(labels, exist_okTrue) for xml_file in os.listdir(annotations): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(annotations, xml_file), labels, class_names)注意几个细节坐标归一化必须以图片实际宽高为分母不能想当然用某个固定尺寸不同相机的分辨率可能不同。x_center和y_center是归一化后的中心点坐标很多新手转出来训练loss不收敛查了半天才发现把坐标算成了左上角点。类别名必须与训练时的data.yaml完全一致大小写都不能差。3.4 数据划分与目录结构YOLOv11认的就是这套YOLOv11训练时要求数据集按固定目录结构组织图片和标签分开放下面这个结构是最标准的dataset/ images/ train/ # 训练集图片 val/ # 验证集图片 labels/ train/ # 对应的txt标注 val/ data.yamldata.yaml内容如下path: /absolute/path/to/dataset train: images/train val: images/val nc: 3 names: [buoy, debris, vessel]路径最好写绝对路径写相对路径时训练命令在哪个目录下执行直接影响结果这个我踩过坑——本地跑得好好的换个路径就报数据集为空后来全部改绝对路径世界清净了。4. 训练YOLOv11自己的模型小目标优化是海上检测的命门4.1 训练命令与参数模板数据准备好了第一次训练我推荐直接用下面这组参数跑稳定谈不上惊喜但能给你一个可靠的baselineyolo train \ modelyolo11s.pt \ datadataset/data.yaml \ imgsz640 \ epochs100 \ batch16 \ lr00.01 \ lrf0.01 \ momentum0.937 \ weight_decay0.0005 \ projectruns/train \ namesea_debris_v1逐项说明modelyolo11s.pt在COCO预训练权重基础上微调收敛速度快很多。别从随机初始化开始训练海上数据集规模小随机初始化很难收敛。imgsz640训练分辨率。后续你会把它调到1280甚至1536因为海上漂浮物普遍是小目标640分辨率下很多目标只有十几个像素特征根本提不出来。epochs100海上数据量不大100轮足够。如果验证集mAP还在缓慢上升可以加到150~200但超过200轮基本就是过拟合了。batch16根据显卡显存调整。显存不足时优先调小batch而不是调小imgsz后面会讲为什么。lr00.01初始学习率YOLOv11默认就是这个值不用改。如果你用的是yolo11x.pt这类大模型建议降到0.005。4.2 小目标检测为什么这么难三个直接原因海上漂浮物检测的难点不是类别多而是目标太小、背景太杂。具体到技术层面三个原因特征图下采样太狠YOLOv11的检测头输出三个尺度的特征图分别是8倍、16倍、32倍下采样。一个640x640的输入图最小检测头对应的原图区域是20x20像素。如果你要检测的目标只有10像素宽在这个特征图上连一个像素都占不满怎么检测海面反光的类内干扰白色浮标和白色浪花在浅层特征上几乎不可区分模型很容易把高亮的波浪区域当成候选目标误检率飙升。小目标在损失函数中的权重天然偏低YOLOv11的损失函数对每个GT框计算损失小目标的框面积小对总loss的贡献就小模型自然倾向于“忽略”它们。4.3 小目标优化的三个实用手段第一把训练分辨率拉到1280。这是性价比最高的手段没有之一。yolo train \ modelyolo11s.pt \ datadataset/data.yaml \ imgsz1280 \ epochs100 \ batch8 \ projectruns/train \ namesea_debris_1280分辨率翻倍小目标的像素数变成原来的4倍特征提取能力天差地别。代价是显存翻倍、训练时间翻倍但船用的相机分辨率通常就是1920x1080或更高推理时你用1280处理跟真实使用场景一致不会出现训练和部署分辨率不匹配的问题。第二用切片推理工具SAHI。如果你的显卡确实扛不住1280训练或者你想进一步提升小目标召回率可以在推理阶段用SAHI把大图切成小块分别检测再把结果拼回去。from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction detection_model AutoDetectionModel.from_pretrained( model_typeyolov8, # SAHI对yolov11用yolov8的类型 model_pathweights/sea_debris_best.pt, confidence_threshold0.25, image_size640, ) result get_sliced_prediction( imagesamples/large_harbor.jpg, detection_modeldetection_model, slice_height512, slice_width512, overlap_height0.2, overlap_width0.2, )切片大小512、重叠率0.2是我的常用配置。切片越大速度越快但小目标提升不明显重叠率防止目标正好被切片边界切断。SAHI的好处是你不用重训练模型推理阶段就能拿到小目标召回率的提升对demo和验证特别友好。第三如果条件允许直接用yolo11m或yolo11l。更大的模型意味着更深的特征提取网络对细节的捕捉能力更强。海上数据量通常不会太大用大模型有轻微过拟合风险但配合数据增强和早停策略通常收益还是正的。4.4 训练后验证别只看mAP要看具体场景的PR曲线训练完看指标不能只盯着mAP50海上场景你得分开看验证集里远处的小目标小于16x16像素召回率是多少反光区域的误报率有没有被压下去from ultralytics import YOLO model YOLO(runs/train/sea_debris_1280/weights/best.pt) metrics model.val(datadataset/data.yaml, imgsz1280, splitval) print(metrics.box.map50)metrics.box.map50是IOU阈值0.5下的mAP海上检测任务主要看这个。如果map50在0.7以上初步可用了低于0.5说明数据质量或标注有很大问题回头检查标注框是否贴紧了物体边缘。5. 从检测框到碰撞风险评估像素坐标到DCPA/TCPA的完整链路5.1 风险评估需要什么输入不只是检测框YOLOv11给的输出是图像坐标系下的矩形框碰撞风险评估需要的是目标的真实方位、距离、速度、航向。所以这条链路上你还需要两个信息源相机参数相机安装高度、俯仰角、视场角、分辨率。如果相机有云台角度值要实时读取。本船状态船速、航向来自GPS/罗经或AIS。目标的速度和航向需要连续多帧检测框的变化推算。这个环节是整个项目中最容易被低估的部分。很多团队检测做得漂亮评估层却随便用像素距离算“碰撞风险”结果实际部署时误差大到不可用。5.2 从像素坐标到方位角与距离单目近似法我用的方法是简化的单目几何模型。假设相机固定安装、已知高度h和俯仰角θ用针孔相机模型把检测框底边中心点投影到海平面import math def pixel_to_bearing_range(u, v, img_w, img_h, fov_h, cam_height, cam_pitch): # 像素坐标转相机坐标系 cx img_w / 2 cy img_h / 2 fx (img_w / 2) / math.tan(math.radians(fov_h / 2)) fy fx # 假设像素是正方形 # 相机坐标系下的方向向量 x_cam (u - cx) / fx y_cam (v - cy) / fy # 考虑俯仰角旋转相机坐标到世界坐标 pitch math.radians(cam_pitch) x_world x_cam y_world y_cam * math.cos(pitch) 1.0 * math.sin(pitch) z_world -y_cam * math.sin(pitch) 1.0 * math.cos(pitch) # 目标在海平面上的距离假设z_world0的平面 if z_world 0: return None, None # 目标在相机下方或正好在水平线上 distance cam_height / z_world bearing math.degrees(math.atan2(x_world, y_world)) return bearing, distance说明一下这个模型假设海面是平的且相机无横滚角实际海况下会有误差但作为风险初筛够用。fov_h是相机水平视场角cam_pitch是相机俯仰角——这两个参数必须实测标定不能靠估算否则方位角误差会让你后续的DCPA计算彻底失真。5.3 DCPA与TCPA计算碰撞风险的两把尺子DCPADistance at Closest Point of Approach和TCPATime to Closest Point of Approach是航海领域判断碰撞风险的标准指标。计算需要本船和目标船各自的位置、速度、航向。import math def compute_dcpa_tcpa(own_lat, own_lon, own_cog, own_sog, target_lat, target_lon, target_cog, target_sog): # 将经纬度差转成距离简化球面近似 def latlon_to_xy(lat1, lon1, lat2, lon2): R 6371000.0 dx (lon2 - lon1) * math.radians(1) * R * math.cos(math.radians((lat1 lat2) / 2)) dy (lat2 - lat1) * math.radians(1) * R return dx, dy own_x, own_y 0.0, 0.0 tgt_x, tgt_y latlon_to_xy(own_lat, own_lon, target_lat, target_lon) # 本船和目标船速度分解 own_vx own_sog * math.sin(math.radians(own_cog)) own_vy own_sog * math.cos(math.radians(own_cog)) tgt_vx target_sog * math.sin(math.radians(target_cog)) tgt_vy target_sog * math.cos(math.radians(target_cog)) # 相对速度 rvx tgt_vx - own_vx rvy tgt_vy - own_vy # 相对位置 rx tgt_x - own_x ry tgt_y - own_y # 相对速度大小 v_rel math.hypot(rvx, rvy) if v_rel 0.1: return None, None # 两船相对静止或同向同速 # 最近会遇时间 tcpa -(rx * rvx ry * rvy) / (v_rel * v_rel) if tcpa 0: return None, None # 目标正在远离 # 最近会遇距离 closest_x own_x rvx * tcpa closest_y own_y rvy * tcpa dcpa math.hypot(closest_x - tgt_x, closest_y - tgt_y) return dcpa, tcpa判断逻辑简单直接DCPA小于某个阈值比如500米且TCPA小于10分钟触发碰撞预警。DCPA越接近0风险越高TCPA为负说明目标正在远离。对于无动力漂浮物debris类它的“目标航向”不好直接测通常用连续多帧的检测框位置变化来估算漂移速度和流向。这里有个重要的实操建议检测目标在连续帧中的位置噪声很大直接对检测框做差分算速度会非常不稳建议用卡尔曼滤波或简单的移动平均先平滑轨迹再做速度估算。否则DCPA计算的输出会剧烈跳动值班人员根本无法信任这个系统。6. 海上部署避坑从数据标注到推理帧率这六关最容易翻车6.1 标注框过紧导致训练不收敛现象训练loss下降正常但验证集mAP一直很低小目标几乎全部漏检。 原因标注时框贴物体贴得太紧甚至切掉了一两个像素的边缘导致IOU计算时GT框与实际检测框重叠度上不去。 解决标注时保留周围2~3像素的余量。特别是远距离小目标宁可框稍微松一点也不要紧贴目标轮廓。这个经验来自一次跨海大桥施工监控的翻车经历换了标注风格后mAP50直接从0.31跳到0.58。6.2 反光虚警大量出现模型把浪花当目标现象晴天顺光时检测正常逆光或傍晚时白色浪花频繁被识别为buoy或debris。 原因训练数据中缺少逆光、反光场景的负样本模型学到的是“高亮白色”这个特征。 解决采集数据时必须包含同时段的顺光、逆光、阴天、浪大、浪小等各种组合。负样本海面没有目标的图片也要放进训练集标签文件留空即可让模型学会“什么都不检测”。6.3 推理时OOM显存不足但训练时没事现象训练batch8跑得动推理时一张大图直接爆显存。 原因训练时YOLOv11有梯度累加和自动混合精度推理则没有更常见的是推理时用了imgsz1280但显卡是4GB的旧工控卡。 解决别在工控机上硬扛推理端用imgsz960SAHI切片或者直接把模型量化为FP16/INT8。Ultralytics导出engine格式时可以顺手做半精度yolo export modelweights/best.pt formatengine halfTrue imgsz960导出的TensorRT engine文件推理速度快2~3倍显存占用减半船载设备完全跑得动。6.4 DCPA计算值剧烈跳动报警声不断现象目标明明没动DCPA每帧都在几百米和几千米之间疯狂跳变报警器响个不停。 原因目标航向和速度来自连续帧检测框的差分框本身有像素级抖动差分后放大成速度噪声。 解决对轨迹做平滑滤波。我用的是一阶卡尔曼滤波状态量为位置和速度观测为检测框底边中心。平滑后再算DCPA/TCPA跳变幅度能压掉七八成。另一个参数是设置TCPA的更新频率——每2秒算一次而不是每帧都算。6.5 夜间和雨雾天气漏检率暴增现象白天mAP50 0.7换成夜间录像直接跌到0.2以下。 原因可见光相机在夜间对比度极低模型在低光条件下的特征提取能力本来就弱。 解决首先训练数据中要加入夜间红外或微光图像哪怕没有红外相机把白天图像做低光照增强也能撑一点。更可靠的做法是换用红外热像仪作为夜间视觉前端YOLOv11对红外图像的检测能力足够模型可以共用但训练时一定要混入红外数据不要只用可见光训练然后用红外推理。6.6 模型在联网下载权重时卡死导致部署流程中断现象新部署一台机器启动脚本自动下载yolo11n.pt结果卡在下载界面半小时不动。 原因机器在船载局域网内无法访问外网。 解决离线部署时把权重文件放到本地路径代码里用绝对路径加载同时设置环境变量YOLO_OFFLINE1让Ultralytics跳过联网检查。在代码里检测到权重文件不存在时主动报错并给出中文提示而不是让它默默卡死。7. 进阶验证用迁移学习提升域适应能力的最后一公里模型训练完、评估跑通、避坑也踩得差不多了最后一件事是验证模型在新场景的泛化能力。海上环境的最大问题是每个港口的光照、水色、浪况都不一样你在A港口训的模型直接丢到B港口mAP掉0.2是家常便饭。我常用的做法是小样本迁移在新场景采集30~50张带标注的图片不需要多把这批图片混入原训练集冻结骨干网络的前10层只训练检测头和最后几层特征提取层跑50轮左右。这样既保留了原模型学到的漂浮物通用特征又能快速适配新场景的水色和光照。yolo train \ modelruns/train/sea_debris_1280/weights/best.pt \ datadataset/new_port_data.yaml \ imgsz1280 \ epochs50 \ freeze10 \ batch8 \ projectruns/train \ namesea_debris_portBfreeze10表示冻结前10层参数只训练后面的层这样50轮的训练在弱GPU上也能十几分钟跑完。这个技巧让我在多个项目的跨场景部署中省下了大量重新采集数据的时间算是最具性价比的落地手段。另外提醒一个部署细节模型训练时的imgsz和推理时的imgsz必须一致否则精度会打折扣。如果船载设备性能一般宁可训练时就定960也不要训练1280推理960这个不一致会让小目标检测质量明显下降。最后说一个我的教训初次做海上漂浮物检测时我把大量精力花在调模型结构上反复对比C3k2、C2PSA这些YOLOv11网络结构里的模块差异后来发现mAP提升的绝大部分来自更高分辨率、更全的数据场景和靠谱的标注质量模型结构本身的收益远小于前三者。从评价指标里抬起头来看看整条链路找到真正的瓶颈去突破这个项目你才算真正接住了。希望这篇笔记能帮你在海上漂浮物检测这条路上少走几个弯路。本文还有配套的精品资源点击获取