拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

停车位、低矮障碍物与地面标识三合一目标检测实战解析

停车位、低矮障碍物与地面标识三合一目标检测实战解析 1. 为什么这三个检测任务要放进同一个模型需求边界与场景拆解前几天在园区做实地测试车子低速靠近一个矮石墩雷达没报、摄像头也没框出来要不是我踩了刹车保险杠就交代了。回来复盘时发现问题不在传感器而在模型任务拆分上——当时只做了车位线检测压根没想着去识别低矮障碍物。这个项目做完之后我的一个核心体会是停车位检测、低矮障碍物检测、地面标识检测这三件事看着各自独立实际在工程上必须放进同一个目标检测框架里去设计。1.1 三个任务各自到底在检测什么先说停车位检测。它要输出的不只是这里有个车位而是车位的具体位置、朝向和可驶入区域。常见做法是检测车位线的四个角点或三个可见角点再通过后处理还原出四边形。但角点检测本质上是一个关键点回归问题直接套用通用目标检测框架并不好用很多团队会专门设计解码头。再说低矮障碍物检测。这类目标包括石墩、防撞柱、路沿、锥桶、花坛边缘等高度普遍在车身底盘以下或刚好位于前后保险杠的盲区位置。它们的特点是靠近地面、尺度小、和路面纹理对比度低通用检测模型很容易漏检。更麻烦的是这类障碍物往往出现在停车位附近如果停车位检测和低矮障碍物检测是两套独立模型车位识别得再准入库过程中撞上障碍物整个系统依然不可用。地面标识检测则是指地面上印刷的交通符号和文字比如直行箭头、左转箭头、限速数字、禁停网格、残疾人车位标线等。这类目标有个显著特点——类别多但每个类别的样本量少而且文字类标识会被车辆压过、磨损形态变化大。通用的COCO预训练模型几乎不会覆盖这些类别必须专门采集数据重新训练。1.2 三合一模型的技术依据把三个任务放进同一个目标检测模型首要依据是它们的视觉共性都主要依赖俯视或前视相机图像目标都在地面平面上尺度结构相似对定位精度的要求都高于通用目标检测。一个共享backbone加一个检测头的架构完全可以同时输出车位线角点、低矮障碍物框、地面标识框三类结果。从工程角度看三合一还有两个额外好处。第一是算力开销嵌入式平台上每多跑一个模型就要多占一份内存和推理时间把三个任务合并成一个模型理论上推理时延可以压缩到单模型水平。第二是维护成本训练数据统一管理标注规范统一制定模型版本迭代也只需要维护一个权重文件现场调试时不用对着三个模型挨个排查。当然三合一也有代价。最典型的是任务冲突停车位检测关注车位线的细长几何结构低矮障碍物检测关注紧凑目标框地面标识检测关注纹理信息。默认的YOLO等通用检测头对三类目标的特征响应不同经常出现车位线检测挺好障碍物漏检一堆的情况。这个问题我后面会详细展开。2. 数据集决定上限采集、标注与难例清洗才是大头模型结构再先进数据不行全都白搭。这个项目的训练数据主要来自三个渠道公开数据集、自采数据、合成数据。公开数据集比如PKLot、PS2.0等提供车位线标注但对于低矮障碍物和地面标识标注格式和目标定义跟我们的需求差异太大基本只能做预训练或辅助。真正决定模型效果的是自采数据的质量和数量。2.1 标注规范的制定三类目标不能混在一起这是整个项目里我踩过最大的坑之一。最开始我们让标注团队对三类目标统一用矩形框标注结果训练出来的模型一团糟——车位线的框是细长的障碍物的框是紧凑的地面标识的框又是方正的模型在特征提取时不知道优先级该给谁。后面改成按目标类型分通道标注才把问题理顺停车位不标整框标角点。每个可见角点用一个极小的框4x4像素标注所有同一车位的角点用一个group_id关联。模型输出角点热图后再用聚类和几何约束还原车位四边形。低矮障碍物标完整外接框但要求框边必须贴合目标的实际物理边缘不能留多余背景。特别是石墩这类目标多标5像素背景训练出来的框预测就会偏大。地面标识按语义类别标完整轮廓单箭头、双箭头、直行、限速数字都单独设类。地面文字类标识用旋转框标注避免矩形框框进大量路面背景干扰分类。标注格式上如果只用标准YOLO格式车位线角点这种特殊标注没法表达。我们最后在YOLO格式基础上扩展了一个自定义字段用extra字段存放车位角点的group_id这样训练脚本和模型代码都能通过一个标注文件完成数据加载。2.2 数据增强策略要多用但别乱用停车位、低矮障碍物、地面标识这类地面目标对数据增强的敏感度和通用检测任务不太一样。我实测下来Mosaic增强在这类任务上收益明显因为模型能通过拼接图片被迫学习目标尺度不变性——一张图里既有近处大尺度障碍物又有远处小尺度车位线模型不会只记住一个固定的目标尺寸。旋转增强需要谨慎。车位和地面标识是有方向语义的比如斜列式车位和直列式车位方向一旦被旋转增强打乱模型容易把这两种车位混淆。我们最后把旋转范围限制在正负15度以内只做微小的角度扰动不做大幅度旋转。复制粘贴增强Copy-Paste反而是个利器。地面标识和低矮障碍物的训练样本天然不足我们把标注好的障碍物目标贴到没有障碍物的路面上合成新的训练样本能显著增加困难场景的覆盖量。对于这种增强关键是贴入目标后的光照和透视一致性不能贴上去像PS的模型在推理时就会对这类合成特征不敏感。2.3 难例挖掘的实战操作采集到的原始视频数据里80%以上的图像是重复的——车停在同一个位置拍10分钟画面基本不变。直接拿去训练会严重过拟合到这些重复场景。我们做了两轮清洗第一轮是手动抽帧从整段视频里每隔一定时间抽一帧再结合人工快速浏览删除画面几乎相同的帧。这一轮能砍掉大约60%的数据量。第二轮是自动难例筛选用一个训练初期的模型对未标注数据进行推理把置信度低的检测结果单独抽出来交给标注人员集中标注。这些难例通常来自逆光、雨天、夜间、遮挡等情况是手选抽帧很难覆盖的。经过两轮清洗后一个6000张原始帧的数据集最终能保留约2500张有效训练图其中约30%是难例样本。3. 模型选型的真实权衡轻量化检测模型凭什么够用训练数据准备得差不多之后就到了模型选型环节。网上关于用YOLOv8还是YOLOv5的讨论特别多但对于停车位检测这类嵌入式场景争论的点根本不是精度差那么一两个点而是能不能在目标开发板上跑得动。3.1 别一上来就上分割模型这个项目初期团队里有人提出用分割模型比如DeepLabv3或SegFormer来做地面标识检测理由是分割能输出像素级的结果对地面标识这种不规则的形状更友好。理论上没错但实际工程中分割模型的推理延迟和内存占用相对目标检测模型是数量级的差距。以MobileNetV3为backbone的DeepLabv3模型在嵌入式开发板上跑一次推理可能需要80毫秒以上而同样的需求用目标检测模型不到15毫秒就能完成。对低速泊车场景来说分割模型的高精度收益并无法弥补实时性的短板。此外分割标注的像素级工作量比目标框标注高一倍以上项目周期完全扛不住。这个项目最终的定位是检测为主、分割为辅模型输出目标框和角点后处理阶段再用图像处理算法在框内做精细的边缘拟合。比如地面标识检测到左转箭头之后在箭头框内做颜色分割和骨架提取既保证实时性又获得接近分割的精细化输出。3.2 轻量化模型怎么选5MB这个档位确实很关键最近macs仅5MB的目标检测模型这个概念在圈子里讨论挺多。这里需要澄清5MB通常指的是模型权重文件所占的存储空间不是计算量。实际对应的是经过量化比如INT8量化后的轻量级模型例如NanoDet、YOLOv5n、YOLOv8n这些它们的参数量普遍在200万到300万之间模型文件大小在3MB到6MB这个区间。我之前用NanoDet Plus做过对比测试效果挺有参考价值。NanoDet Plus使用ShuffleNetV2作为backbone整个模型文件在FP16精度下大约4.8MBINT8量化后可以压缩到2MB以内它的计算量确实很小在瑞芯微RK3588这类NPU上能跑到40到50FPSCPU上也能维持在10FPS以上。而YOLOv8n在同样的芯片上虽然精度可能会高一点但推理速度和内存占用都略逊一筹。模型参数量权重文件大小(FP16)输入尺寸推理速度(RK3588 NPU)适用性评估YOLOv5n176万约3.8MB640x640约35FPS通用性好精度均衡YOLOv8n315万约6.2MB640x640约28FPS精度略高部署稍重NanoDet Plus244万约4.8MB512x512约45FPS轻量首选速度优势明显NanoDet(macs版)约110万约3.2MB416x416约55FPS极轻量适合极致性能需求对停车位和障碍物检测这类任务输入分辨率往往比网络结构本身影响更大。低矮障碍物在画面里通常只有几十个像素的尺度把输入分辨率从416提到640检测精度能提升8%以上但推理速度会下降30%。项目中我的选择是用NanoDet Plus作为主模型输入分辨率设为512x512先保证在目标板上跑满30FPS再用高分辨率输入做离线精筛兼顾实时性和精度。3.3 为什么这个场景不盲追高精度大模型很多人第一反应是精度优先先上YOLOv8x实在不行再换小的。但这个思路在嵌入式泊车场景有个致命问题大模型在推理时占用大量NPU算力导致整车的其他感知任务比如环视拼接、超声波雷达融合分不到资源。实际部署中模型文件大小和内存占用同样关键一个90MB的YOLOv8x权重文件光加载进内存就要占掉上百MB这在很多工业级主板上是难以接受的。轻量化模型的另一个优势是训练和迭代速度明显更快。我们调整一次数据标注规则重训一次模型轻量化模型两小时就能收敛大模型可能要得一个通宵这在项目调试阶段非常影响节奏。4. 从训练到部署的完整落地流程很多人以为模型训出来就完事了其实真正的工作量有一半在训练之后的部署和联调上。下面按实际操作的顺序写清楚每一步是怎么做的。4.1 训练环境与数据规格首先是标注数据格式的统一。我们的训练框架最终选定基于YOLOv8的修改版标注文件统一为YOLO的txt格式但扩展了一个自定义字段。目录结构长这样dataset/ ├── images/ │ ├── train/ # 训练图像 │ └── val/ # 验证图像 ├── labels/ │ ├── train/ # 对应的标注txt │ └── val/ ├── data.yaml # 模型配置文件 └── classes.txt # 类别列表classes.txt里的类别划分如下0: parking_slot_point # 车位角点 1: low_obstacle # 低矮障碍物 2: ground_arrow_left # 左转箭头 3: ground_arrow_right # 右转箭头 4: ground_arrow_straight # 直行箭头 5: ground_text_limit # 限速数字 6: ground_no_parking # 禁停标识这里有个关键点车位角点单独作为一类不参与低矮障碍物和地面标识的类别平衡计算。因为一个车位有4个角点如果每张图标注20个车位就有80个角点目标相比每张图平均3到5个障碍物目标样本量悬殊如果不单独处理训练时模型会被车位角点带偏对障碍物类别视而不见。data.yaml配置如下path: dataset/ train: images/train val: images/val nc: 7 names: 0: parking_slot_point 1: low_obstacle 2: ground_arrow_left 3: ground_arrow_right 4: ground_arrow_straight 5: ground_text_limit 6: ground_no_parking4.2 训练参数设置与调参心得训练阶段用NanoDet Plus的话我推荐的参数组合是python train.py \ --cfg config/nanodet_plus_custom.yml \ --data dataset/data.yaml \ --batch_size 32 \ --epochs 120 \ --input_size 512 \ --optimizer AdamW \ --lr 0.001 \ --weight_decay 0.05 \ --warmup_epochs 3 \ --multi_scale [0.5, 0.75, 1.0, 1.25, 1.5]训练中有两个参数对这个项目影响特别大第一个是multi_scale。多尺度训练是提高低矮障碍物检测效果的最大功臣。障碍物在画面中的尺度从30像素到200像素都有如果固定单一输入尺度小目标特征在深层feature map上基本消失模型很难学会稳定的尺度表达。开启多尺度训练后模型能适应不同距离下的障碍物检测最终在验证集上的小目标检测精度提升了约7个百分点。第二个是batch_size。低矮障碍物样本在batch里分布很不均匀有的batch一个障碍物都没有有的batch有十来个。batch_size太小梯度更新波动大模型在障碍物类别上收敛慢。我们实测把batch_size从8调到32后同样的epoch下验证集的mAP从71%提到了78%提升非常明显。4.3 模型导出与端侧部署要点训练完成后导出环节建议先用ONNX验证一遍再转成目标平台的格式。很多坑都出在这步——模型的训练代码里带了自定义解码层导出时没有做算子映射导致ONNX里出现一堆不兼容算子。NanoDet Plus转ONNX的命令python tools/export_onnx.py \ --cfg config/nanodet_plus_custom.yml \ --model weights/nanodet_plus_custom.pth \ --out nanodet_plus_custom.onnx转出来的ONNX是端到端的模型输入为1x3x512x512的图像张量输出包括三个分支类别预测、框回归和角点热图。这里特别提醒一下导出时一定要关闭后处理中的NMS把原始解码输出留给推理引擎自己处理不然在端侧会出现重复框或漏检。部署到瑞芯微RK3588平台时用RKNN-Toolkit2做模型转换from rknn.api import RKNN rknn RKNN() rknn.config(mean_values[[0, 0, 0]], std_values[[255, 255, 255]], target_platformrk3588) rknn.load_onnx(modelnanodet_plus_custom.onnx) rknn.build(do_quantizationTrue, datasetdataset_quant.txt, quantized_dtypew8a8) rknn.export_rknn(nanodet_plus_custom.rknn)量化这一步很关键。INT8量化后模型大小能压缩到约2MB但精度通常会有1到3个百分点的损失。对于低矮障碍物这种本来就难检的目标量化损失可能让它更难被检出所以必须在量化后重新跑一遍验证集确认mAP下降幅度在可接受范围内。如果下降超过5%可以尝试只对权重做量化w8a16保留激活值的浮点精度速度略慢但精度损失明显变小。4.4 端侧后处理逻辑代码参考部署到端侧后模型的原始输出还需要后处理才能得到最终的检测结果。以下是RKNN推理后处理的关键代码这段代码实现了NMS过滤、车位角点热图峰值检测和箭头框输出import numpy as np def postprocess(outputs, conf_thres0.35, iou_thres0.45): outputs: dict containing model outputs - pred_boxes: (1, num_anchors, 4) - pred_scores: (1, num_anchors, num_classes) - corner_heatmap: (1, 1, H, W) for parking slot points pred_boxes outputs[pred_boxes][0] pred_scores outputs[pred_scores][0] # 1. 低矮障碍物和地面标识的NMS过滤 object_scores pred_scores[:, 1:].max(axis1) # 排除背景类 mask object_scores conf_thres boxes pred_boxes[mask] scores object_scores[mask] class_ids pred_scores[mask].argmax(axis1) 1 keep_indices nms(boxes, scores, iou_thres) final_boxes boxes[keep_indices] final_scores scores[keep_indices] final_class_ids class_ids[keep_indices] # 2. 车位角点热图峰值检测 corner_map outputs[corner_heatmap][0, 0] corners find_peaks(corner_map, threshold0.4, kernel_size3) return { detections: (final_boxes, final_scores, final_class_ids), corners: corners } def find_peaks(heatmap, threshold0.4, kernel_size3): # 极大值抑制找到局部峰值点作为车位角点 from scipy.ndimage import maximum_filter local_max maximum_filter(heatmap, sizekernel_size) peaks (heatmap local_max) (heatmap threshold) ys, xs np.where(peaks) return list(zip(xs, ys, heatmap[peaks]))NMS阈值别直接用通用检测的默认值0.45。低矮障碍物有一个特性同一个石墩从不同角度拍摄会产生多个重叠但中心偏移的预测框。把iou_thres从0.45降到0.3可以有效消除同一目标的重复框但对不同目标之间的保留影响很小。实测逆光场景下漏检率降低约18%。5. 低矮障碍物检测的三大坑贴地目标、尺度变化与密集遮挡这个项目做下来要说最难啃的部分绝对是对低矮障碍物的检测坑主要集中在三个点上。5.1 贴地目标与背景的对比度太低低矮障碍物比如灰色石墩、水泥路沿在柏油路面上的灰度差异极小。通用目标检测模型在特征提取时浅层特征可能还能看到边缘但经过深层特征融合后这些微弱边缘很容易被背景噪声淹没模型压根提不出有效特征。针对这个问题我用了一个比较有效的优化手段在backbone的浅层特征图上额外加了一个辅助检测头Auxiliary Head。具体做法是在Stride4的特征图相对于输入512分辨率上增加一个检测头专门负责小目标检测。代码层面的调整如下class NanoDetPlusAux(NanoDetPlus): def __init__(self, cfg): super().__init__(cfg) # 在浅层特征图上额外增加一个辅助检测头 self.aux_head DetectionHead(64, cfg.model.head.num_classes) def forward(self, x): # 原始前向传播 fpn_outs self.backbone(x) features self.fpn(fpn_outs) # 浅层特征图单独走辅助头 shallow_feat features[0] # stride 4 aux_pred self.aux_head(shallow_feat) # 主检测结果 head_pred self.head(features) return {head_pred: head_pred, aux_pred: aux_pred}加了辅助头之后小目标的召回率从51%提升到了63%。5.2 尺度变化剧烈导致的漏检车头和障碍物的距离从30米到0.5米障碍物在画面中的尺度比能相差20倍以上。模型在训练时学到的特征对尺度范围有限超出这个范围的障碍物就容易漏检。多尺度训练解决了大部分问题但还有个容易忽略的细节推理时要用图像金字塔做一次测试时增强TTA。虽然TTA会降低推理速度但对于低速泊车场景车速本来就不快每一帧多付出十几毫秒的代价换来回溯召回率的提升是完全值得的。def infer_with_tta(image, model, scales[0.75, 1.0, 1.5]): all_detections [] for scale in scales: resized cv2.resize(image, (int(512 * scale), int(512 * scale))) # 填充到模型输入尺寸 padded letterbox(resized, (512, 512)) # 推理 detections model(padded) # 把检测框坐标映射回原图尺寸 detections scale_boxes(detections, scale) all_detections.extend(detections) # 最后用NMS合并所有尺度的检测框 final_detections nms(all_detections, iou_thres0.5) return final_detections用了这个TTA策略后在夜间近距场景下的漏检率比单尺度策略下降了12%。5.3 密集障碍物的遮挡与重叠停车场里的防撞柱经常两三根密集排列彼此遮挡严重直接用NMS会强行保留置信度高的一个框另一个被过滤掉。这种情况下单靠检测模型很难解决我的方案是把检测和跟踪结合起来。先对检测结果做多目标跟踪即使在某一帧漏检了也能通过前后帧的跟踪轨迹补回来。我用ByteTrack做跟踪它能够利用低置信度的检测框信息对密集场景下被遮挡的目标特别有效。在密集防撞柱的场景下不加跟踪的F1值是0.62加了ByteTrack之后F1值提升到0.72。6. 实测数据与参数调优精度、速度与误检的三方平衡模型最终跑通之后我在园区停车场、室外公共停车场、地下车库三类场景做了完整的实测整理了评测数据和几个重要的调优心得。6.1 三类场景下的实测结果对比测试硬件平台是RK3588开发板输入分辨率512x512模型是INT8量化后的NanoDet Plus全程开启ByteTrack跟踪。测试场景车位角点F1低矮障碍物mAP地面标识mAP平均推理时延备注园区停车场0.890.710.8228ms光线稳定障碍物稀疏地下车库0.810.630.6931ms灯光昏暗柱子多室外公共停车场0.760.580.6134ms逆光强道路不平从数据看室外场景的精度下降最明显主要原因有两个一是逆光环境下低矮障碍物的边缘几乎被光晕吞没二是地面标识磨损严重部分箭头只剩一半模型很难给出完整识别。地下车库的问题则出在灯光偏暗很多障碍物的颜色和地面融为一体。6.2 置信度阈值的分目标设置这里要特别强调一个很多人忽略的细节不同类别应该用不同的置信度阈值。在默认设置下车位角点因为样本多、特征明确置信度普遍集中在0.5到0.9低矮障碍物置信度普遍偏低集中在0.3到0.6地面标识介于两者之间。如果统一用0.4的阈值过滤低矮障碍物的低置信度目标会被大批量滤掉漏检率迅速上升。我的解决方案是按类别分别设置阈值车位角点0.35、低矮障碍物0.30、地面标识0.40。低矮障碍物阈值放低之后病害的误检会增多但结合跟踪模块的滤波能力大部分误检都可以被滤除整体的F1值反而提升了约9%。6.3 误检根因与两条解决路径实测中最常见的误检有两类一类是把路面破损的阴影误判为低矮障碍物另一类是把地锁的金属反光误判为障碍物。这两类误检的根因都是低置信度目标混入没办法在检测层面完全杜绝。两条路径可以解决第一是时序一致性检查。把一个目标的检出帧率作为置信度的一部分——如果连续5帧中目标出现不足2帧就认定为误检。这类后处理逻辑可以借助ByteTrack的轨迹状态来过滤简单又高效。第二是场景约束。比如低速泊车场景下低矮障碍物不会出现在车位内部只会出现在车位边界附近。在车位角点确定之后可以用车位四边形区域做空间先验障碍物框中心落在车位内部时降低该检测框的置信度。这个约束在实测中能消除大约14%的低矮障碍物误检。6.4 停车位到可行驶区域的最终输出停车位角点检测的最终输出并不是简单的框列表而是带有方向信息的车位四边形。模型输出每个角点的坐标后后处理会做一步配准把同一group_id下的角点聚类再用最小二乘法拟合出车位四边形。这个四边形同时决定了车辆的可行驶方向和入库路径。低矮障碍物检测结果和这个四边形做一次空间交集判断如果障碍物框与车位四边形有重叠就在最终输出中把该车位标记为不可用同时把障碍物框单独发给下游的路径规划模块用于重新搜索可选的入库轨迹。实际测试中车位被障碍物侵入但模型没报警的情况主要发生在障碍物与车位边界只有很小重叠的情况比如石墩刚好卡在车位入口的斜角处。把重叠判定条件从中心点在车位内改为目标框与车位四边形的IoU0.1这类边缘场景的漏报率下降了一半。6.5 模型压缩和量化后的最终效果最终交付的模型经过FP16到INT8量化后权重文件从4.8MB压缩到1.9MB在RK3588上的推理延迟稳定在30毫秒以内。相比不量化的版本夜间低矮障碍物的检测精度下降了约3个百分点但考虑到部署板的算力限制这个损失在可接受范围内。如果对低矮障碍物的精度要求特别高可以考虑只对backbone部分做INT8量化检测头保持FP16模型体积大约2.8MB精度损失会收窄到1个百分点以内。我目前采用的就是这种非均匀量化方案实测效果最均衡。这个项目整体做下来最深的体会是停车位检测、低矮障碍物检测、地面标识检测三合一真正难的不是单个任务的精度而是三个任务在同一个模型框架下如何平衡资源、如何共享数据、如何协同优化。数据侧的功夫花得越多模型侧要解决的疑难杂症就越少。轻量化模型的全链路从训练到量化再到部署如果提前把格式和算子约束想清楚项目的迭代速度能快不少。如果你也在做类似的泊车感知项目建议先停下来把标注规范和部署目标定清楚再动手训模型这个顺序不要反。
返回列表