📌 前言

在自动驾驶场景中,理解周围车辆的细粒度状态对于安全行驶至关重要。例如,当路边停靠的车辆打开车门时,很可能有人要下车;当前车尾灯闪烁时,可能要变道或刹车。然而,现有的视觉感知系统主要关注边界框检测和实例分割,忽略了车辆的**非常见状态(Uncommon States)**理解。

本文将详细解读CVPR 2020论文,该论文首次系统性地研究了自动驾驶中车辆非常见状态的细粒度理解问题。核心创新包括:基于3D部件引导的图像编辑数据生成方法、双骨干多任务网络架构、以及首个车辆非常见状态数据集(CUS Dataset)。


1. 研究背景与动机

1.1 问题定义

车辆非常见状态(Cars in Uncommon States, CUS):

  • 车门打开(前左/前右/后左/后右)
  • 后备箱/引擎盖抬起
  • 车灯闪烁(转向灯、刹车灯、警示灯)

这些状态在实际驾驶中虽然不常见,但一旦发生却极其重要:

非常见状态潜在风险应对策略
车门打开有人下车减速、变道
后备箱抬起有人取物保持距离
左转向灯闪烁车辆要左转注意避让
刹车灯亮起前车制动跟随减速

1.2 现有方法的局限性

数据层面:

  • 现有AD数据集(KITTI、CityScapes、ApolloScape)中CUS样本极度稀缺
  • 手动标注成本高昂
  • 合成数据存在域差距

模型层面:

  • 现有检测/分割网络仅输出边界框或整体掩码
  • 缺乏对车辆部件级别的细粒度理解
  • 无法描述车辆的具体状态

1.3 核心贡献

  1. 3D部件引导的图像编辑:自动生成大量CUS训练数据
  2. 双骨干多任务网络:同时输出检测、分割、部件分割和状态描述
  3. CUS数据集:首个车辆非常见状态基准测试集

2. 3D部件引导的图像编辑

2.1 整体流程

输入:带有3D模型对齐的2D车辆图像(来自ApolloCar3D)
                ↓
[3D部件分割与运动轴标注]
                ↓
        ┌───────┴───────┐
    可见区域          不可见区域
        ↓                 ↓
[3D变换→2D投影]    [环境贴图渲染]
        ↓                 ↓
[孔洞填充+滤波]    [内饰区域填充]
        └───────┬───────┘
                ↓
输出:编辑后的CUS训练图像

2.2 3D部件定义

论文定义了10个动态部件和12种非常见状态:

部件类型具体部件可能状态
可移动部件引擎盖(bonnet)抬起
后备箱(trunk)抬起
四个车门打开
语义部件两个前大灯转向灯闪烁
两个尾灯转向/刹车/警示

每个可移动部件需要标注其运动轴(Motion Axis),用于约束部件的运动范围。

2.3 3D变换与2D投影

2.3.1 像素到3D点的转换

给定渲染的深度图DDD、全局旋转Rg\mathbf{R}_gRg​、平移tg\mathbf{t}_gtg​和相机内参K\mathbf{K}K,将2D像素u=(u,v)⊤\mathbf{u}=(u,v)^\topu=(u,v)⊤转换为3D点P=(x,y,z)⊤\mathbf{P}=(x,y,z)^\topP=(x,y,z)⊤:

P=Rg−1⋅(D(u)⋅K−1⋅u˙−tg)\mathbf{P} = \mathbf{R}_g^{-1} \cdot \left( D(\mathbf{u}) \cdot \mathbf{K}^{-1} \cdot \dot{\mathbf{u}} - \mathbf{t}_g \right)P=Rg−1​⋅(D(u)⋅K−1⋅u˙−tg​)

其中u˙=(u⊤∣1)⊤\dot{\mathbf{u}} = (\mathbf{u}^\top | 1)^\topu˙=(u⊤∣1)⊤是齐次坐标。

2.3.2 部件变换后的投影

假设部件绕运动轴进行局部旋转Ro\mathbf{R}_oRo​,轴在全局坐标系中的平移为to\mathbf{t}_oto​,变换后的像素位置u′\mathbf{u}'u′为:

u′=⌊π(K⋅(Rg(Ro(P−to)+to)+tg))⌋\mathbf{u}' = \left\lfloor \pi \left( \mathbf{K} \cdot \left( \mathbf{R}_g(\mathbf{R}_o(\mathbf{P} - \mathbf{t}_o) + \mathbf{t}_o) + \mathbf{t}_g \right) \right) \right\rflooru′=⌊π(K⋅(Rg​(Ro​(P−to​)+to​)+tg​))⌋

其中π(P)\pi(\mathbf{P})π(P)为透视投影函数,包含齐次化操作。

2.4 后处理技术

2.4.1 孔洞填充

变换后的像素在部件区域通常是稀疏且有噪声的。使用线性混合算法填充空洞像素的RGB值,然后应用双边滤波器平滑结果。

2.4.2 不可见区域生成

当车门向外打开时,会暴露原图中不可见的区域,分为两类:

区域类型处理方法
车辆内饰(座椅、方向盘等)填充灰色(光照不足的自然表现)
部件背面基于环境贴图的真实感渲染

环境贴图渲染:

  • 构建专业设计的3D部件模型库
  • 标注材质属性和BRDF参数
  • 从ApolloCar3D计算环境贴图
  • 使用PBR管线渲染部件背面

2.5 编辑效果展示

编辑方法能够生成多种非常见状态:

  • 后备箱/引擎盖抬起
  • 四个车门分别打开
  • 车灯闪烁效果(黄色转向灯、红色刹车灯)

编辑效率:每辆车约3秒

  • 3D点变换和投影:0.5s
  • 孔洞填充和滤波:0.5s
  • 不可见区域生成:2s

3. 双骨干多任务网络

3.1 网络架构设计

                    ┌─────────────────────┐
                    │   主骨干(Main)       │
编辑图像 ──────────→│  ResNet50-FPN      │──┐
                    │  (车辆检测预训练)    │  │
                    └─────────────────────┘  │
                                             │  特征
                    ┌─────────────────────┐  │  拼接   ┌──────────────┐
                    │  辅助骨干(Auxiliary) │  │ ──────→│   RoI Align  │
编辑图像 ──────────→│  ResNet50-FPN      │──┘         └──────────────┘
                    │  (COCO通用预训练)   │                   │
                    └─────────────────────┘                   ↓
                                                    ┌─────────┴─────────┐
                                                    │    多任务头        │
                                                    ├───────────────────┤
                                                    │ • 类别分类         │
                                                    │ • 边界框回归       │
                                                    │ • 实例分割         │
                                                    │ • 部件分割         │
                                                    │ • 状态描述         │
                                                    └───────────────────┘

3.2 双骨干设计动机

骨干类型预训练任务提取特征
主骨干ApolloCar3D + CityScapes车辆检测车身整体特征
辅助骨干COCO通用目标检测编辑区域(渲染部件)的通用特征

关键策略:训练时冻结两个骨干的参数,仅训练任务头。这样可以:

  1. 避免在合成数据上过拟合
  2. 保持对真实图像的泛化能力
  3. 充分利用预训练特征

3.3 动态部件分割

基于Mask-RCNN扩展,将部件分割作为额外的输出通道:

  • 原始Mask-RCNN输出:K×m2K \times m^2K×m2(K类,m×m分辨率)
  • 扩展后输出:(K+1)×m2(K+1) \times m^2(K+1)×m2(增加部件通道)

网络结构:

RoI Aligned Feature (14×14)
        ↓
4 × Conv2d (256-d, 3×3)
        ↓
Deconv2d (2×2) → 28×28
        ↓
Sigmoid Cross Entropy Loss

3.4 状态描述

将状态表示为二值向量,每个位置表示特定状态是否存在:

state_vector = [
    bonnet_lifted,      # 引擎盖抬起
    trunk_lifted,       # 后备箱抬起
    fl_door_opened,     # 前左门打开
    fr_door_opened,     # 前右门打开
    bl_door_opened,     # 后左门打开
    br_door_opened,     # 后右门打开
    left_turn_signal,   # 左转向灯
    right_turn_signal,  # 右转向灯
    ...
]

通过卷积层+全连接层回归状态向量,使用sigmoid交叉熵损失。

3.5 多任务损失函数

L=Lclassp+Lregp+Lclassr+Lboxr+Lmaskr+Lstater+Lpartr\mathcal{L} = \mathcal{L}_{class}^p + \mathcal{L}_{reg}^p + \mathcal{L}_{class}^r + \mathcal{L}_{box}^r + \mathcal{L}_{mask}^r + \mathcal{L}_{state}^r + \mathcal{L}_{part}^rL=Lclassp​+Lregp​+Lclassr​+Lboxr​+Lmaskr​+Lstater​+Lpartr​

其中:

  • (.)p(.)^p(.)p:RPN阶段的损失
  • (.)r(.)^r(.)r:RCNN阶段的损失
  • Lstate\mathcal{L}_{state}Lstate​:状态向量损失
  • Lpart\mathcal{L}_{part}Lpart​:部件分割损失

训练参数:

  • 优化器:SGD(weight_decay=0.0001, momentum=0.9)
  • 初始学习率:0.002
  • 学习率衰减:每5个epoch衰减0.1

4. CUS数据集

4.1 数据集构建

数据来源:

  1. 现有AD数据集筛选(KITTI、CityScapes、ApolloScape、ApolloCar3D)
  2. 自行采集(医院、公园、学校、城市道路,不同时段)

最终规模:

  • 1441张标注图像
  • 1850个车辆实例
  • 12种非常见状态

4.2 状态分布统计

数据来源引擎盖后备箱前左门前右门后左门后右门左转向右转向刹车灯警示灯总计
KITTI191005108028
CityScapes00145843215055
ApolloScape0232905915715183316400
ApolloCar3D0131910113521094
自行采集15405232667934619174471273
总计16450295721465234142121231850

4.3 标注内容

每个车辆实例包含:

  • 2D边界框
  • 实例分割掩码
  • 动态部件分割掩码
  • 状态描述向量

5. 实验结果与分析

5.1 与基线方法对比

方法训练数据网络2D检测(IoU)↑实例分割(IoU)↑
Baseline 1现有数据集Mask-RCNN0.7510.704
Baseline 2渲染数据Mask-RCNN0.7580.712
Baseline 3编辑数据Mask-RCNN0.7750.721
Baseline 4现有数据集双骨干网络0.7660.713
Baseline 5渲染数据双骨干网络0.7720.719
Ours编辑数据双骨干网络0.8620.815

关键发现:

  1. 编辑数据 > 渲染数据 > 现有数据集
  2. 双骨干网络 + 编辑数据的组合效果最佳
  3. 相比最佳基线,提升超过8%

5.2 消融实验:网络结构

网络配置2D检测(mAP)实例分割(mAP)部件分割(mAP)状态描述
单骨干-重训练0.1360.1140.1440.149
单骨干-冻结0.6720.5160.2730.837
双骨干-冻结0.7010.5630.3140.874

结论:

  • 冻结骨干参数可有效避免过拟合
  • 双骨干设计能提取更充分的特征

5.3 消融实验:训练数据量

数据量2D检测实例分割部件分割状态描述
5K64.3%47.6%21.2%30.2%
10K76.5%58.6%24.1%31.4%
15K83.2%64.2%27.9%31.7%
20K85.1%66.3%55.3%32.1%
25K87.4%68.5%55.8%32.3%
30K88.2%70.1%56.3%32.3%
35K88.9%70.9%57.0%32.3%
40K88.9%71.5%57.5%32.3%

结论:5K→25K性能显著提升,25K后趋于饱和。实践中选择25K作为效率与精度的平衡点。

5.4 消融实验:环境贴图

配置2D检测实例分割部件分割状态描述
无环境贴图0.6880.5380.2210.844
有环境贴图0.7010.5630.3140.874

结论:环境贴图渲染的部件背面显著提升网络性能,尤其是部件分割提升9.3%。


6. 核心代码实现

6.1 3D部件变换与投影

import numpy as np

def pixel_to_3d(u, v, depth, K, R_g, t_g):
    """
    将2D像素转换为3D点
    
    Args:
        u, v: 像素坐标
        depth: 深度值
        K: 相机内参矩阵 [3, 3]
        R_g: 全局旋转矩阵 [3, 3]
        t_g: 全局平移向量 [3, 1]
    
    Returns:
        P: 3D点坐标 [3, 1]
    """
    # 齐次像素坐标
    u_homo = np.array([[u], [v], [1.0]])
    
    # 反投影到相机坐标系
    K_inv = np.linalg.inv(K)
    P_cam = depth * K_inv @ u_homo
    
    # 转换到世界坐标系
    R_g_inv = np.linalg.inv(R_g)
    P_world = R_g_inv @ (P_cam - t_g)
    
    return P_world


def transform_and_project(P, R_o, t_o, R_g, t_g, K):
    """
    对3D点进行部件变换后投影回2D
    
    Args:
        P: 3D点 [3, 1]
        R_o: 部件局部旋转 [3, 3]
        t_o: 运动轴位置 [3, 1]
        R_g: 全局旋转 [3, 3]
        t_g: 全局平移 [3, 1]
        K: 相机内参 [3, 3]
    
    Returns:
        u_new: 新的像素坐标 (u, v)
    """
    # 局部变换:绕运动轴旋转
    P_local = R_o @ (P - t_o) + t_o
    
    # 全局变换
    P_cam = R_g @ P_local + t_g
    
    # 透视投影
    P_proj = K @ P_cam
    u_new = P_proj[0, 0] / P_proj[2, 0]
    v_new = P_proj[1, 0] / P_proj[2, 0]
    
    return int(u_new), int(v_new)

6.2 孔洞填充与滤波

import cv2
from scipy.interpolate import griddata

def fill_holes_and_smooth(image, mask, valid_mask):
    """
    孔洞填充和双边滤波
    
    Args:
        image: 输入图像 [H, W, 3]
        mask: 部件区域掩码 [H, W]
        valid_mask: 有效像素掩码 [H, W]
    
    Returns:
        result: 填充并平滑后的图像
    """
    H, W = mask.shape
    
    # 获取有效像素和孔洞像素的坐标
    valid_coords = np.where(valid_mask & mask)
    hole_coords = np.where((~valid_mask) & mask)
    
    # 使用线性插值填充孔洞
    for c in range(3):
        channel = image[:, :, c]
        valid_values = channel[valid_coords]
        
        # griddata插值
        filled_values = griddata(
            points=np.array(valid_coords).T,
            values=valid_values,
            xi=np.array(hole_coords).T,
            method='linear'
        )
        
        channel[hole_coords] = filled_values
    
    # 双边滤波平滑
    result = cv2.bilateralFilter(image, d=9, sigmaColor=75, sigmaSpace=75)
    
    return result

6.3 双骨干网络

import torch
import torch.nn as nn
from torchvision.models.detection import MaskRCNN
from torchvision.models.detection.backbone_utils import resnet_fpn_backbone

class TwoBackboneNetwork(nn.Module):
    def __init__(self, num_classes, num_parts, num_states):
        super().__init__()
        
        # 主骨干:车辆检测预训练
        self.main_backbone = resnet_fpn_backbone(
            'resnet50', pretrained=False
        )
        
        # 辅助骨干:COCO通用预训练
        self.aux_backbone = resnet_fpn_backbone(
            'resnet50', pretrained=True
        )
        
        # 冻结骨干参数
        for param in self.main_backbone.parameters():
            param.requires_grad = False
        for param in self.aux_backbone.parameters():
            param.requires_grad = False
        
        # 特征融合
        self.feature_fusion = nn.Conv2d(512, 256, kernel_size=1)
        
        # 多任务头
        self.box_head = BoxHead(256, num_classes)
        self.mask_head = MaskHead(256, num_classes)
        self.part_head = PartSegHead(256, num_parts)
        self.state_head = StateDescHead(256, num_states)
    
    def forward(self, images, targets=None):
        # 提取特征
        main_features = self.main_backbone(images)
        aux_features = self.aux_backbone(images)
        
        # 特征融合
        fused_features = {}
        for key in main_features.keys():
            concat_feat = torch.cat([
                main_features[key], 
                aux_features[key]
            ], dim=1)
            fused_features[key] = self.feature_fusion(concat_feat)
        
        # 多任务输出
        boxes, box_scores = self.box_head(fused_features)
        masks = self.mask_head(fused_features)
        parts = self.part_head(fused_features)
        states = self.state_head(fused_features)
        
        return {
            'boxes': boxes,
            'scores': box_scores,
            'masks': masks,
            'parts': parts,
            'states': states
        }

6.4 状态描述损失

def state_description_loss(pred_states, gt_states):
    """
    状态描述的Sigmoid交叉熵损失
    
    Args:
        pred_states: 预测状态向量 [B, num_states]
        gt_states: 真实状态向量 [B, num_states]
    
    Returns:
        loss: 平均损失值
    """
    loss_fn = nn.BCEWithLogitsLoss(reduction='mean')
    loss = loss_fn(pred_states, gt_states.float())
    return loss

7. 应用场景

7.1 动作推理

结合人体检测,可以推理人车交互:

场景CUS状态人体位置推理结果
后备箱旁站人后备箱抬起车尾附近正在取/放行李
车门边弯腰车门打开车门附近正在下车

7.2 身份推断

CUS状态人体位置推断身份
前左门打开门边驾驶员(左驾车)
前右门打开门边副驾乘客
后门打开门边后排乘客

8. 方法优势总结

维度传统方法本文方法
数据获取人工标注/纯渲染3D引导图像编辑
域差距大(纯合成)小(基于真实图像)
数据生成效率低(渲染慢)高(3秒/车)
理解粒度边界框/整体掩码部件级分割+状态描述
网络泛化性过拟合风险高双骨干冻结策略

9. 局限性与未来方向

9.1 当前局限

  1. 对象范围:仅针对车辆,未覆盖行人、道路等
  2. 输出维度:主要是2D结果,缺乏3D理解
  3. 时序建模:仅处理单帧图像,未利用视频序列
  4. 传感器融合:仅使用RGB相机,未融合LiDAR/Radar

9.2 未来方向

  • 扩展到更多对象类别的非常见状态
  • 3D检测、定位和重建
  • 视频序列中的状态变化跟踪
  • 多传感器融合的CUS感知

10. 总结

本文针对自动驾驶中被忽视但极其重要的车辆非常见状态理解问题,提出了完整的解决方案:

  1. 3D部件引导的图像编辑:巧妙利用已有的2D-3D对齐数据,通过部件变换和真实感渲染自动生成大量训练数据,在保持真实图像外观的同时创造多样的非常见状态

  2. 双骨干多任务网络:通过冻结预训练骨干避免过拟合,同时输出检测、分割、部件分割和状态描述

  3. CUS数据集:首个专注于车辆非常见状态的基准测试集,包含1850个细粒度标注的车辆实例

实验表明,该方法在CUS检测和理解上相比基线提升超过8%,为自动驾驶的安全感知提供了重要支撑。


Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐