YOLOv11-seg道路护栏实例分割实战:从数据集构建到部署全流程
2026/9/16 3:25:05 网站建设 项目流程

道路护栏检测这类任务,用目标检测只能给个框,真要做病害定位、形变分析、养护评估,必须得做到像素级分割。去年我在做道路设施巡检项目时,护栏分割这块踩了不少坑,正好YOLOv11-seg发布后性能提升明显,我拿它重新训练了道路护栏实例分割数据集,跑通了一套完整流程。这篇内容不只讲命令怎么敲,还会把数据怎么采、标注怎么避坑、训练参数为什么这么调、推理结果如何保存这些关键细节全部拆开,打算自己训练护栏分割模型的可以直接照抄。

1. 道路护栏实例分割的项目定位与整体设计思路

1.1 道路护栏实例分割到底在解决什么问题

道路护栏是公路基础设施里非常特殊的一类目标。它不像车辆、行人那样有相对固定的长宽比和姿态,而是一种典型的细长结构,在画面中往往呈现为连续的、弯曲的、部分遮挡的带状区域。传统的目标检测算法(比如标准YOLO检测模型)能把护栏用一个矩形框框出来,但框里面既包含护栏本身,又包含背景路面、绿化带、对面车道,这些干扰信息对后续的病害判断和尺寸测量完全没有帮助。

实例分割任务解决的是“每一个像素属于哪个目标实例”的问题。对于道路护栏来说,实例分割输出的是护栏轮廓的掩膜(mask),能精确到像素级别地把护栏和背景分开。有了这个掩膜,下游可以做几件很有价值的事:一是护栏变形检测,通过比对掩膜几何形状与标准波形梁的差异,判断有没有弯折或错位;二是净距测量,护栏边缘到车道线的距离直接影响行车安全;三是养护面积统计,需要涂装或更换的护栏长度、面积可以直接从掩膜像素数换算出来。

1.2 为什么选YOLOv11-seg而不是其他分割方案

做实例分割的可选方案其实不少,Mask R-CNN、SOLO、YOLOv8-seg、YOLOv11-seg都在考虑范围内。我在这个项目里最终选型YOLOv11-seg,主要基于三点考量。

第一是速度与精度的平衡。道路巡检的场景通常是在车辆行驶过程中实时采集视频流,对推理帧率有硬性要求。Mask R-CNN这类两阶段方法虽然精度上限高,但在边缘设备或普通GPU上的实时性满足不了巡检需求。YOLOv11-seg作为单阶段方法,在保持高精度的同时能跑到很高的FPS,实测在RTX 4060上处理1080P视频帧能达到55-60 FPS,完全满足实时处理。

第二是部署友好度。Ultralytics生态的模型从训练到导出ONNX、TensorRT的链路非常成熟,一行命令搞定。对于工程落地来说,这条链路的价值比模型涨零点几个mAP重要得多。

第三是YOLOv11本身的架构改进。相比YOLOv8-seg,v11引入了C3k2模块替代部分C2f结构,在保持轻量化的同时提升了特征提取效率;backbone中加入了C2PSA模块,通过注意力机制增强了对长条状小目标的特征表达能力。道路护栏恰恰就是长条状目标,这种改进对护栏分割任务非常契合。

1.3 整体技术链路设计

整个项目的技术链路可以拆成四个阶段:数据采集与标注、数据集构建与增强、模型训练与验证、推理部署与输出。每个阶段都有自己的关键决策点,我根据实际经验把整体流程图整理如下,后面每一节都会详细拆解。

  • 数据采集:行车记录仪、无人机、路测专业相机三种来源,覆盖不同场景
  • 数据标注:LabelMe或X-AnyLabeling手工标注多边形掩膜,统一导出为JSON
  • 数据集构建:转换为YOLO分割格式(TXT掩膜坐标文件),划分训练集、验证集、测试集,做数据增强
  • 模型训练:YOLOv11-seg预训练权重,配置数据集YAML,调节超参数
  • 验证评估:mAP50、mAP50-95、mask精度等指标分析
  • 推理部署:视频流推理、结果可视化、掩膜信息导出JSON/CSV

2. 道路护栏数据集的采集、标注与预处理实操

2.1 数据采集的场景覆盖和采集要点

道路护栏数据集的质量直接决定分割模型的上限,这一步不能省。理想的数据集应该覆盖不同道路等级、不同护栏类型、不同天气光照条件。

护栏类型方面,国内最常见的三种是波形梁护栏(高速公路和一级公路的主流配置,特征是有波浪形起伏的钢板)、混凝土护栏(桥面和中央分隔带常见,表面平整或有纹理)、组合式护栏(混凝土底座加金属横梁)。训练数据必须包含这三种类型,否则模型在实际场景中遇到没见过的护栏形态,分割效果会明显下降。

采集场景的多样性也很关键。我当时的采集渠道有三个:行车记录仪视频抽帧、无人机航拍、手机随拍。行车记录仪的画面视角低、距离近,能覆盖护栏的细节纹理;无人机航拍能获得俯视视角,对护栏顶部轮廓和整体走向的标注很有价值;手机随拍则可以补充服务区、收费站、匝道等特殊场景。不同来源的视频抽帧时要注意相邻帧不能太相似,一般每隔5到10帧抽一张,避免训练集中出现大量近乎重复的图像导致过拟合。

采集时的光照条件尽量覆盖白天的顺光、逆光、阴天、晴天,夜间数据可以暂时不放,因为夜间护栏反光特性完全不同,混入夜间数据反而会干扰模型学习。如果项目后续确实需要夜间检测能力,建议单独采集夜间数据单独训练一个模型。

2.2 标注规范:怎么让掩膜质量对得起训练代价

标注这一步是整个项目最耗时的环节,也是决定分割精度的最大变量。标注规范定得是否合理,直接影响标注效率和最终效果。

第一,明确“护栏”的边界定义。波形梁护栏包含横梁面板、立柱和连接件,建议把连续连接的横梁面板作为一个完整的实例来标注,立柱是否包含可以根据后续业务需求决定。如果下游关注的是护栏面板变形检测,就应该标注面板部分,立柱单独标注反而会干扰面板轮廓的准确性。我当时的做法是只标注横梁面板区域,立柱不标,这样模型学到的轮廓更纯粹。

第二,遮挡情况的标注策略。护栏被车辆、植被遮挡时,标注软件里画出来的多边形会穿过遮挡物,把护栏被遮挡的部分框出来。这样做对实例分割来说问题不大,因为模型在推理时本身就会尽量还原可见部分的轮廓,标注完整轮廓反而能帮助模型学习护栏的延展形态。但如果遮挡部分过大、超过护栏总长度的40%,就建议直接跳过这张图,避免引入过多噪声。

第三,标注工具的选择。我推荐X-AnyLabeling,它支持自动分割模型辅助标注,先用SAM或YOLOv8-seg模型做预标注,然后人工修正多边形顶点。这样能大幅提升标注效率,纯手工画一个细长护栏的多边形要1到2分钟,有预标注辅助后30秒就能完成一张图。如果对工具有特殊偏好,LabelMe也是可以的,只是没有预标注辅助功能。

2.3 标注结果转成YOLO分割格式

YOLO实例分割训练需要的标签格式是:每个TXT文件对应一张图片,每行代表一个实例,格式为“类别ID 多边形x1 y1 x2 y2 ... xn yn”,所有坐标值都归一化到0到1之间,用图片宽度和高度分别归一化。

从LabelMe导出的JSON转成YOLO格式需要写个转换脚本,核心逻辑如下:

import json import os def convert_labelme_to_yolo(json_path, output_dir, class_dict, img_width, img_height): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) output_txt = os.path.join(output_dir, os.path.basename(json_path).replace('.json', '.txt')) with open(output_txt, 'w') as out_f: for shape in data['shapes']: label = shape['label'] if label not in class_dict: continue class_id = class_dict[label] points = shape['points'] normalized_points = [] for x, y in points: nx = min(max(x / img_width, 0.0), 1.0) ny = min(max(y / img_height, 0.0), 1.0) normalized_points.append(f"{nx:.6f} {ny:.6f}") line = f"{class_id} " + " ".join(normalized_points) out_f.write(line + "\n")

转换时要注意三点:一是归一化坐标必须夹紧到0到1之间,否则训练时Mosaic增强和随机仿射变换会产生越界坐标导致报错;二是多边形的顶点顺序要保持一致(顺时针或逆时针均可,但不宜混用),这会影响损失函数的收敛稳定性;三是每个实例至少要有6个顶点,太粗糙的多边形会让分割边缘锯齿感明显。

2.4 训练集、验证集、测试集划分与数据增强策略

数据集划分我建议按“场景”而不是按“图片”来分。如果同一路段的连续帧分别出现在训练集和验证集中,验证指标会虚高,模型相当于提前见过了场景。正确做法是先把不同路段、不同采集来源的视频整理成多个“场景组”,按7:2:1的比例在场景层面做划分,保证验证集和测试集中的场景与训练集不完全重叠。

数据增强策略采用Ultralytics默认配置基本够用,但有几个点值得调整。Mosaic增强在训练初期非常有效,它能把四张图拼接在一起,增加小目标的数量和多样性,建议保持开启。对于护栏这类纹理较弱的目标,HSV颜色增强的饱和度扰动值可以从默认的0.7稍微降低到0.5,避免护栏的色彩在增强后发生过度偏移。随机翻转要考虑护栏的语义对称性,左右翻转问题不大,但上下翻转在真实行车视角中几乎不会出现,建议关闭或仅在少量轮次开启。

最终我用的数据集规模是:训练集约3500张图像、标注实例约9800个,验证集约800张、实例约2200个,测试集约300张、实例约900个。这个量级对于单类别实例分割任务已经足够训练出泛化能力不错的模型,继续增加数据带来的精度提升会逐渐趋于饱和。

3. YOLOv11-seg环境配置与训练配置详解

3.1 环境配置与依赖安装

YOLOv11的环境配置和v8基本一致,核心依赖是PyTorch和Ultralytics。我的推荐配置如下:

# 创建Python虚拟环境 conda create -n yolov11 python=3.10 conda activate yolov11 # 安装PyTorch(以CUDA 11.8为例,根据实际GPU驱动版本调整) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装Ultralytics(v11需要8.3.0及以上版本) pip install ultralytics>=8.3.0

安装完成后可以验证一下GPU是否正常可用:

python -c "import torch; print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))"

需要提醒一个问题:不要图新鲜把Ultralytics升级到最新版本,因为最新版偶尔会调整部分默认参数。建议用8.3.x或8.4.x的稳定小版本,我这套流程验证下来的版本是ultralytics 8.3.77,整体运行稳定。

3.2 数据集YAML配置与目录结构

在工程目录下建一个数据集配置文件data_guardrail.yaml,内容如下:

# 数据集根路径 path: /home/user/datasets/guardrail_seg train: images/train val: images/val test: images/test # 类别配置(单类别场景,就一个护栏类) nc: 1 names: 0: guardrail

对应的目录结构应该是:

guardrail_seg/ ├── images/ │ ├── train/ # 训练集图片 │ ├── val/ # 验证集图片 │ └── test/ # 测试集图片 ├── labels/ │ ├── train/ # 训练集标签TXT │ ├── val/ # 验证集标签TXT │ └── test/ # 测试集标签TXT └── data_guardrail.yaml

一个常见坑是images和labels目录下的文件名必须完全一致(扩展名不同没关系,ultralytics会自动替换扩展名查找对应文件)。如果训练时报大量“image without labels”的警告,优先检查文件名是否对得上。

3.3 训练命令与超参数逐项解析

训练命令看起来简单,但里面的参数选择决定了最终精度和训练效率。我用的完整训练命令如下:

yolo segment train data=data_guardrail.yaml model=yolo11n-seg.pt epochs=150 imgsz=640 batch=16 device=0 optimizer=AdamW lr0=0.002 lrf=0.01 warmup_epochs=3 patience=20 workers=8

逐个参数说下依据:

  • model:用yolo11n-seg.pt作为预训练权重。n是nano版本,参数量最小,先跑通流程;如果最终精度不足,再换s或m版本加大模型容量。也可以用yolov8n-seg.pt作为初始化,但实测直接用yolo11n-seg.pt收敛更快,效果更好。
  • imgsz=640:训练输入图像尺寸。护栏是细长目标,如果原始图像中护栏占比很小,建议用768甚至896,但显存消耗会增加很多。我平衡后选640,配合Mosaic增强基本够用。
  • batch=16:显存16GB的RTX 4060/4080可以跑到16到24。显存不足时优先减batch而不是减imgsz,因为图像分辨率对分割精度的影响比batch size更显著。
  • optimizer=AdamW:Ultralytics默认的SGD在中小数据集上收敛速度偏慢。实测AdamW在护栏这类结构纹理相对简单的目标上,150轮就能达到SGD约250轮的效果。
  • lr0=0.002:AdamW对学习率更敏感,0.002是经验值,太小收敛慢,太大会震荡。配合warmup_epochs=3做预热,可以在前几轮用较小的学习率稳定初始化。
  • patience=20:早停策略,验证集mAP连续20轮不提升就提前终止训练。护栏任务50到120轮之间基本收敛,超过150轮后提升很小,提早停可以避免无效等待。

训练日志建议开启wandb或用Ultralytics自带的runs目录下的metrics曲线图,重点观察mask_loss的下降趋势,它比box_loss更能反映分割掩膜的学习情况。

3.4 训练过程监控与最优权重选择

训练过程中的监控指标主要看四类:box_loss、seg_loss、cls_loss和验证集mAP。前三个是训练损失,趋势应该是稳步下降并最终收敛到相对平稳的区间;mAP则应该逐步上升,最终在某个区间震荡。

关于最优权重的选择,Ultralytics会在每个epoch结束时自动保存best.pt和last.pt,其中best.pt是验证集mAP最优的权重。有两点要注意:第一,验证集mAP最优的epoch不一定等于训练损失最低的epoch,因为存在过拟合风险,所以直接信best.pt就行;第二,如果训练后期验证集mAP出现持续下降,说明模型开始过拟合,此时应该提前停止,而不是等patience触发。

4. 常见踩坑记录与推理结果输出技巧

4.1 训练报错的四类高频问题和排查思路

跑护栏分割训练时,最常遇到四类问题,我把排查思路和解决方案整理成了速查表。

问题现象可能原因解决方案
CUDA out of memorybatch过大或imgsz过大先从batch=8开始,逐项降低;或用auto参数自动调整batch
“image 1/1 ... 0 labels”警告图片和标签文件名不一致检查文件名,确保一一对应;不要有中文或空格
训练loss异常增高后不降学习率过高或数据集有坏样本降低lr0到0.001,用data可视化工具检查是否有标注严重错误的图片
seg_loss盘旋不降掩膜标注质量差,坐标未归一化检查TXT中坐标是否在0-1范围内;抽查10%标注是否有明显轮廓错误

排查问题最有效的手段不是看控制台日志,而是直接把带标注的图像可视化出来。Ultralytics提供了plot训练样本的函数,也可以用下面这段代码快速检查标签是否挂在正确位置:

from ultralytics import YOLO model = YOLO("yolo11n-seg.pt") model.val(data="data_guardrail.yaml", plots=True)

执行后会在runs/segment/val目录下生成带掩膜叠加的可视化图,一眼就能看出标注和图像是否对齐。

4.2 分割效果不佳的优化方向优先级

如果训练完成后验证集mAP50已经到了0.85以上但实际推理效果不理想,大概率是数据分布偏差而不是模型问题。优化方向按优先级排序:

第一优先是补充“难例数据”。很多实际场景中护栏会被路边树木的阴影遮盖、被泥水污染、被广告牌部分遮挡,这些场景如果没有进训练集,模型自然表现不佳。针对性地采集补充这类难例,比盲目增加整体数据量更有效。

第二优先是调大输入分辨率。验证时用imgsz=960推理,往往比训练分辨率更高的输入能显著提升细长目标的掩膜质量,代价只是推理变慢。

第三优先是增加模型容量。把模型从nano级别换成small或medium级别,即yolo11s-seg.ptyolo11m-seg.pt,在推理设备算力允许的情况下,mAP通常能提升2到4个点。

第四优先才是调整数据增强策略。比如将mosaic从默认的1.0降到0.5,有时能避免小目标被过度拼接导致的信息损失。但数据增强调整的收益通常不如前三个方向明显。

4.3 推理结果保存:视频、图片和掩膜数据导出

模型训练完成后,实际工程应用中需要把推理结果保存下来。Ultralytics提供了几种保存方式,根据场景选择。最简单的保存图片和视频结果:

# 保存带掩膜叠加的图片推理结果 yolo segment predict model=runs/segment/train/weights/best.pt source=/path/to/test_images save=True # 保存视频推理结果,同时导出JSON格式的检测信息 yolo segment predict model=runs/segment/train/weights/best.pt source=/path/to/test_video.mp4 save=True save_json=True

如果需要把掩膜输出成可直接用于业务分析的格式,用Python脚本做后处理更灵活:

from ultralytics import YOLO import numpy as np import json model = YOLO("runs/segment/train/weights/best.pt") results = model.predict(source="path/to/image.jpg", imgsz=960, conf=0.35) for result in results: if result.masks is not None: masks = result.masks.data.cpu().numpy() # 每个实例的布尔掩膜 boxes = result.boxes.xyxy.cpu().numpy() # 每个实例的检测框 for i, mask in enumerate(masks): mask_area = int(mask.sum()) # 像素面积 x1, y1, x2, y2 = boxes[i] print(f"检测到护栏实例,框体范围: ({x1:.1f}, {y1:.1f}) - ({x2:.1f}, {y2:.1f}),掩膜像素面积: {mask_area}")

掩膜的像素面积结合相机标定参数,可以换算成护栏的实际长度或表面积,这是后续护栏养护评估的关键输入。

4.4 模型部署时的精度与速度取舍

部署阶段最大的取舍是精度和速度的平衡。YOLOv11-seg从导出到TensorRT的流程很方便,实测在TensorRT FP16模式下推理速度能比PyTorch原生提升约3倍。操作分两步:

# 第一步导出为ONNX yolo export model=runs/segment/train/weights/best.pt format=onnx opset=12 # 第二步转换到TensorRT(需要安装tensorrt库) yolo export model=runs/segment/train/weights/best.pt format=engine device=0

如果目标是边缘设备(如Jetson Orin),建议用FP16精度;如果目标设备是普通办公GPU(如RTX 3060),FP16和FP32差距不大。实际部署时发现一个常见坑:TensorRT引擎和导出时的batch size绑定,如果导出时没有加batch参数,默认batch=1,推理时也必须是batch=1。如果要动态batch,导出时要加dynamic=True参数。

做道路护栏实例分割这一年多来,我最大的体会是:这种长条形细目标的标注质量比模型结构更决定效果上限。结构上YOLOv11-seg已经足够好,但形状细节是否能准确还原,很大程度取决于掩膜多边形顶点的标注密度。顶点太少轮廓粗糙,顶点太多标注耗时爆炸,我的经验是每条护栏边缘线段上至少保持10到15个顶点,能让最终分割边缘既平滑又不失真。另外一个小技巧:训练时不要把所有护栏类型混在一个类别里,如果项目经费和标注人力允许,按波形梁、混凝土、组合式分开标注成三个类别,后续做护栏类型性能统计和故障分析起来会省很多事。这套流程你拿自己的数据跑一遍,遇到瓶颈再对照排查就行,道路护栏分割这个方向,当前开源工具链的成熟度远超实际落地需求。

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

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

立即咨询