资讯详情 RangeDet复现:range view激光雷达3D检测选型与落地
📅 2026/10/26 11:07:18
激光雷达3D目标检测这条线上视角的选择几乎决定了一个方案的骨架。体素化走BEV投影走range view两条路各有拥趸也各有各的坑。RangeDet这篇工作做的事很有意思——它站出来给range view说话试图证明这个被不少人提前判了出局的表示方式其实远没到退场的时候。我把这篇东西反复读了几遍又照着自己的理解搭了一版复现流程踩了一些坑也有一些想明白了的地方。这篇文章就围绕RangeDet的核心思路展开它到底在辩护什么、辩护得有没有道理、如果自己要落地一套类似的range view检测器该怎么做、哪些地方最容易翻车。适合已经做过一两个3D检测项目、想搞清楚视角选型底层逻辑的人看也适合刚入门、被各种BEV方案刷屏之后想补一补另一条技术路线的同学。1. 路线之争为什么还要给range view翻案1.1 三种点云表示法的成本账聊range view之前得先把三条主流路线的账算清楚不然很难理解RangeDet的动机。点云本身是一堆无序的(x, y, z, intensity)网络吃不下必须重排成某种规则结构。目前工程上真正跑起来的主要是三种体素化到BEV、球面投影到range view、以及直接在原始点上做操作。体素路线的优势太明显了BEV是俯视图物体的物理尺寸在图上不随距离变化一个车长4.5米就是4.5米卷积核的感受野对应固定的物理范围检测头设计起来非常直觉。但代价也摆在那——体素化本身就是一次量化把连续坐标塞进离散格子边界上的点会被合并或丢弃空体素占比极高尤其在大范围场景里稀疏卷积虽然能绕开无效计算但索引构建和显存管理并不便宜。原始点路线保留了全部信息可邻居查询这一步在几万个点上做起来很费劲工业部署时对延迟敏感的场景往往不太敢用。range view走的是另一条路把点云按球面角度展开成一张二维距离图。每个像素记录的是这个方向上最近的那个点的距离顺带存x、y、z和反射强度。这张图可以直接喂给标准的2D卷积网络不需要任何3D算子不需要稀疏索引卷积核扫过去就是规整的张量运算。这就是RangeDet敢于为它辩护的底气——它的计算结构最简单硬件最友好。表示方式量化损失计算结构尺度一致性主要痛点BEV体素有格子边界合并稀疏3D卷积/柱卷积好物理尺度固定显存与索引开销range view无保留原始测量标准2D卷积差随距离变化尺度变化与形状变形原始点无点算子/邻域查询好邻居查询慢难部署这张表里最关键的一列是量化损失。range view在这一项上是干净的每个像素对应一条射线上的最近点距离值就是传感器测出来的原始值没有任何插值或合并。对于测距精度本身就是核心指标的检测任务这一点在某些场景下是有实际价值的。1.2 被诟病的两个原罪到底有多严重range view被批评得最多的两点一是尺度变化二是形状变形。RangeDet的辩护逻辑本质上是把这两条拆开来看指出它们并不是表示方式本身的死穴而是过去的方法没有针对性地处理。先说尺度变化。这一点经常被误读。在range view里一个物理尺寸固定的物体距离越远它在图像上占的像素越少这是几何投影的必然结果不是bug。有人会说BEV没有这个问题但BEV的没有是靠把点云摊平到地面换来的代价是高度信息被压缩、远处点云稀疏导致特征空洞。range view的尺度变化是真实几何的忠实反映问题在于卷积网络的多尺度机制原本是为语义多尺度设计的它并不知道这个像素的尺度小是因为它远缺乏距离这个先验融合时自然会失衡。再说形状变形。一个长方体的车投影到球面上会变成什么形状取决于它相对传感器的位置和朝向。正对着传感器的车投影后大致还是个矩形斜着的车投影后会被拉成梯形甚至更奇怪的四边形贴近地面的部分还会被扫描线的角度间隔拉扯。这个变形是真实存在的任何基于range view的方法都躲不掉。但RangeDet的观察是变形影响的是框怎么定义和框怎么合并而不是特征能不能提取。只要把回归目标和后处理按照变形的规律重新设计这个问题是可以被大幅缓解的。这里有个容易被忽略的点很多人在range view上直接套用BEV那一套IoU定义和NMS逻辑跑出来效果差就归咎于表示方式。实际上问题出在后处理上换什么视角都救不了。1.3 RangeDet的整体骨架长什么样把整条流水线摊开看结构其实很朴素原始点云先做球面投影得到一张H×W的多通道range image然后过一个标准的2D backbone提特征接一个FPN做多尺度融合融合之后插入一个专门处理距离条件的模块也就是RangeDet的核心创新点RCPM最后接一个anchor-free的检测头输出类别、中心偏移、三维尺寸和朝向推理阶段用带距离加权的NMS做后处理。这个骨架的说服力在于它的每一块都是成熟组件没有引入任何需要特殊算子支持的模块。backbone可以是任何2D网络FPN是现成的检测头是FCOS那一套改的NMS是自己写的几十行代码。整套东西可以在一张普通显卡上跑起来不依赖稀疏卷积库不依赖自定义CUDA算子。对于一个想快速搭原型或者想在边缘设备上部署的团队这个特性比多一个点的mAP更有吸引力。我自己的判断是RangeDet真正的贡献不在于刷了多高的分数而在于它把range view为什么不行这个模糊的论断拆解成了几个具体可解的子问题并且给出了其中两个的解法。这种把定性批评转化为定量工程问题的思路比结论本身更值得学。2. 核心机制拆解距离条件化与框的重新定义2.1 球面投影从点云到range image的每一步这一步是所有range view方法的地基做错一个细节后面全崩。给定一个点(x, y, z)先算它的斜距r再算俯仰角和方位角。写成代码是这样import numpy as np def points_to_range_image(points, H64, W2048, fov_up3.0, fov_down-25.0): # points: (N, 4) - x, y, z, intensity x, y, z, intensity points.T r np.sqrt(x**2 y**2 z**2) # 俯仰角单位度 pitch np.degrees(np.arcsin(z / np.maximum(r, 1e-6))) # 方位角映射到 [0, 360) yaw np.degrees(np.arctan2(y, x)) yaw np.mod(yaw, 360.0) # 行俯仰角从上往下 row (fov_up - pitch) / (fov_up - fov_down) * H # 列方位角从左往右 col yaw / 360.0 * W row np.floor(row).astype(np.int32) col np.floor(col).astype(np.int32) valid (row 0) (row H) (col 0) (col W) row, col row[valid], col[valid] # 每个像素只保留最近的点 order np.argsort(-r[valid]) # 从远到近近的覆盖远的 row, col row[order], col[order] range_img np.zeros((H, W, 5), dtypenp.float32) range_img[row, col, 0] r[valid][order] range_img[row, col, 1] x[valid][order] range_img[row, col, 2] y[valid][order] range_img[row, col, 3] z[valid][order] range_img[row, col, 4] intensity[valid][order] return range_img几个参数的选择是有讲究的。行数H一般取传感器线数或者线数的两倍64线和128线是常见值。列数W决定了方位角分辨率W取2048时每列大约0.176度取2650时约0.136度。列数越大远处物体保留的像素越多但计算量线性增长显存也跟着涨。俯仰角的范围必须覆盖传感器的实际视场写小了上方或下方的点会被裁掉写大了会多出一堆空行浪费算力。这一点在换数据集时特别容易出错——不同数据集的传感器安装角度和线束分布差别很大直接套用别人的fov_up/fov_down结果就是一半的行是空的。order np.argsort(-r)这行是关键同一像素可能落入多个点比如远处相邻两条扫描线在某列上重叠必须只保留最近的那个因为激光雷达测的是这条射线上的首个回波。如果反过来保留最远的会把背景当成前景训练直接崩掉。空的像素用0填充意味着range0。网络必须能区分这里是0米和这里没有点所以在损失计算和特征归一化时都要用掩码把无效像素排除掉。RangeDet的处理方式是把range作为特征通道输入同时在计算损失时只对有效像素负责。2.2 尺度变化到底有多剧烈算一笔具体的账光说尺度变化太抽象我们拿具体数字感受一下。假设一辆车宽度1.8米方位角分辨率是0.176度W2048。当车在10米处时它的方位角跨度约为 1.8/10 0.18弧度换算成度是10.3度在图上占 10.3/0.176 ≈ 58 列。当车在50米处时方位角跨度约为 1.8/50 0.036弧度约2.06度占 2.06/0.176 ≈ 12 列。线性尺寸差了将近5倍面积差了25倍。这个差距比常规图像检测里的多尺度问题还要夸张。更麻烦的是俯仰方向一辆高1.5米的车在50米处的俯仰跨度只有约1.7度如果线束角度间隔是0.4度那么它在垂直方向只占4行左右稍微一池化就没了。这就是为什么range view检测器在远距离小目标上总是吃亏。常规FPN的处理方式是把深层低分辨率特征和浅层高分辨率特征相加让大感受野负责大物体、小感受野负责小物体。但在range view里大物体和小物体的划分不是由语义决定的而是由距离决定的。一辆远处的卡车可能在图上比一辆近处的自行车还小。FPN那套按层级分配尺度的假设在这里失效了。2.3 RCPM把距离当作条件信号喂进去RangeDet对这个问题的解法很直接既然网络不知道尺度小的原因是距离远那就把距离明确告诉它。这就是Range Conditioned Pyramid Module的核心思想。具体做法是在FPN的每一层融合之后额外引入一份对应分辨率的range图经过几层轻量卷积和一个sigmoid生成一张0到1之间的权重图然后和该层的特征做逐元素相乘。用伪代码表示class RangeConditionedBlock(nn.Module): def __init__(self, channels): super().__init__() self.gate nn.Sequential( nn.Conv2d(1, channels // 4, 1), nn.ReLU(inplaceTrue), nn.Conv2d(channels // 4, channels, 1), nn.Sigmoid() ) def forward(self, feat, range_map): # range_map: (B, 1, H, W)已归一化到大致 0~1 w self.gate(range_map) return feat * w这段结构看起来像SE注意力但控制信号完全不同。SE是用全局池化后的特征自己生成通道权重RCPM是用空间上每个像素的距离值生成逐像素的权重。远处像素的range值大gate学到的响应就弱或者强具体是哪种由训练决定网络自己会找到合适的调制方式。为什么用range图而不是直接把range拼接到特征上我理解有两个考虑。拼接会让网络自己去学怎么用这个信息但卷积核是空间共享的在range view里同一个卷积核要同时处理近处和远处的像素学起来很吃力。用gate的方式相当于给了一个乘性的调制让同一套卷积核在不同距离上表现出不同的响应强度表达能力更直接。range图下采样到各层分辨率时用平均池化比最大池化合理。因为range是连续物理量平均池化保留了这一块区域的平均距离最大池化会偏向最远的那个点在物体边缘处容易产生跳变。这个细节看起来小但在远距离密集目标场景里能感觉到差别。还有一个容易做错的地方range值要不要归一化。原始range可能从0到80米甚至更远直接喂给卷积数值范围太大sigmoid容易饱和。我试过两种方式一种是除以最大量程一种是用对数变换。对数变换在近距离区分度更好但在远距离会压缩得太厉害。实际用的时候按数据集的量程做线性归一化再把大于某个阈值的截断效果比较稳。复现时的经验RCPM的gate分支不要做太深两层1x1卷积就够了。做深了反而会过拟合而且这部分参数在小数据集上很难训起来。2.4 检测头框在range view里该怎么定义这是整个方法里最容易被低估的部分。range view里的框和图像检测里的框不是一回事。一个三维框有中心(x, y, z)、尺寸(w, l, h)和朝向θ。把它投影到range image上八个角点会落在八个位置连起来是一个不规则的四边形。RangeDet的做法是把这八个角点都投影到图上取它们的最小外接矩形作为二维框用来做正样本分配。为什么要取外接矩形而不是直接用中心点投影因为在range view里物体的投影可能被拉长中心点的投影不一定在二维框的几何中心。如果按中心点附近的像素分配正样本在物体斜置的时候会偏出真实区域。用外接矩形能保证覆盖到物体在图上占据的全部像素。检测头是anchor-free的每个正样本像素输出类别分数、中心点相对该像素的偏移量、三维尺寸、朝向。朝向用sin和cos两个值编码避免角度在±π处的不连续。尺寸回归这里有个细节range view下的尺寸回归难度比BEV高因为同样的物理尺寸在图上对应的像素数随距离变化网络需要同时学到这个物体多大和它离多远。我看过一些复现实现在这里加了一个辅助分支让网络额外预测每个正样本的range值作为一种自监督信号。这个做法不是原文的必需项但对收敛速度有帮助因为range是已知的可以直接作为监督目标。正样本分配用中心度加权离物体中心投影越近的像素权重越高边缘像素权重衰减。这个设计在range view下比在普通图像检测里更有必要因为物体边缘的投影变形最严重那里的特征可靠性低。2.5 Weighted NMS为什么标准NMS在这里会失灵后处理这一块是RangeDet另一个值得说的点。标准NMS的逻辑是按分类分数排序然后依次抑制与已保留框IoU超过阈值的框。这套逻辑成立的前提是分数高的框质量更好。在range view里这个前提经常不成立。原因在于遮挡和投影截断。一个物体如果被前面的物体挡住一部分它在range image上会呈现为两块不连续的区域检测器很可能对每一块都输出一个框。被挡住的那块面积小、特征弱分类分数低露出来的那块分数高但框不完整。标准NMS会按分数保留那个不完整的框把另一个压掉最终结果就是一个偏小的框。RangeDet的思路是引入一个额外的预测分支让网络在输出框的同时预测这个框的质量可以理解为预测IoU然后在NMS阶段用分类分数乘以质量分数作为排序依据。这样那些分类分数不高但框得很准的检测结果有机会被保留下来。具体实现上加权的方式有两种常见选择一种是把质量分乘到分数上再排序另一种是在框合并时按质量分做加权平均。前者简单后者在密集场景下更稳。我两种都试过在小目标密集的场景里加权平均能减少框的抖动。阈值的选择也和BEV不一样。BEV里常用的NMS阈值是0.5到0.7range view里因为投影变形同一个物体的两个框在二维图上的IoU可能天然就偏低阈值设太高会保留一堆重复框。实际调的时候我一般从0.4开始试配合质量加权。3. 复现路径从数据到推理的完整链路3.1 数据预处理流水线复现的第一步是把数据集转成range image格式。这里以常见的激光雷达检测数据集为例流程大致是读取点云、过滤掉超出量程的点、做球面投影、保存为numpy或者直接在线生成。几个工程上的选择值得说。第一是离线生成还是在线生成。离线生成把每帧的range image存成文件训练时直接读IO压力小但占磁盘在线生成省空间但每轮都要重算投影CPU容易成为瓶颈。我倾向于离线生成一次用多进程并行几千帧的数据几分钟就能跑完。第二是坐标系。有些数据集的点云已经转到车体坐标系有些是传感器坐标系。球面投影必须在传感器坐标系下做因为投影的原点是传感器位置。如果拿车体坐标系的点直接投整个图会歪掉。这一步错了后面所有指标都是废的而且很难从loss曲线上看出来只会表现为mAP莫名其妙地低。第三是反射强度的归一化。不同传感器的intensity范围差别很大有的0到255有的0到1。统一归一化到0到1避免网络对这个通道的敏感度过高。预处理项常见取值踩坑提示俯仰视场需匹配传感器实际安装直接套用别人参数会导致大量空行方位分辨率每列0.14~0.18度列数过小则远处目标像素不足最大量程按数据集标注范围裁裁太狠会丢远处标注框无效像素置0并记录掩码不mask会在归一化时引入偏置强度归一化线性缩放到0~1忘记做会让训练不稳定3.2 网络结构与训练配置backbone我用了两种做对比。一种是ResNet的变体把第一层的步长改小避免一开始就把方位方向的分辨率砍掉太多。这一点很关键range image在垂直方向只有64行或者128行经不起几次下采样水平方向虽然有2000多列但远处目标本来就只占十几列下采样两次就剩几个像素了。常见的做法是垂直和水平方向用不同的下采样策略或者干脆在浅层保持较高分辨率。FPN部分输出三个或者四个尺度的特征每一层接一个RCPM。检测头共享权重还是各尺度独立我倾向于共享参数量小小数据集上不容易过拟合。训练配置上优化器用AdamW初始学习率在千分之一量级配合余弦退火和几百步的warmup。batch size受显存限制单卡能放4到8帧用梯度累积凑到等效16或者32。损失函数是分类用focal loss回归用smooth L1质量预测分支用BCE。权重上分类损失占1回归损失占2左右质量损失占0.5到1之间具体要看收敛情况调。有个细节值得单独提range image里无效像素占比可能很高尤其是有遮挡的场景。如果focal loss把无效像素也当负样本算正负样本比例会极端失衡训练会偏向于全部预测背景。解决办法是在loss里把无效像素的贡献置零只对有效像素计算。3.3 数据增强的几何一致性range view的数据增强比BEV更容易出错因为任何变换都必须保持几何一致性。水平翻转是常用增强但在range view里它等价于绕垂直轴做镜像对应的变换是x变成-xy不变方位角θ变成π减θ。如果只翻转图像而不改标注框的朝向训练出来的朝向预测会系统性地偏180度。这个错误很隐蔽因为框的位置看起来是对的只有朝向指标掉。全局旋转也是同理旋转角度α之后方位角要整体平移物体的朝向也要加α。旋转的填充区域会变成无效像素要用掩码标记出来。缩放要慎用。range view里的缩放不等价于物理世界的缩放你把图放大两倍并不对应物体变大两倍只对应方位分辨率变了。真要做尺度增强应该是改变传感器的模拟参数重投影而不是直接resize图像。我见过有人直接对range image做随机缩放结果模型在验证集上完全失效原因就在这里。3.4 推理与后处理推理阶段前向得到每个像素的预测先按类别分数做阈值过滤然后做带质量的NMS再把保留下来的框从range view反投影回三维坐标。反投影这一步要注意二维框的中心点对应的是球面上的一个方向加上预测的range值才能确定三维位置。如果检测头没有单独预测range就用框中心像素位置的原始range值。两种方式差别不大但在物体表面不连续的时候用预测值更稳。耗时方面在单张主流显卡上64×2048的输入一次前向大概在几十毫秒量级加上后处理整体能到实时或者接近实时。这比同精度的体素方案要快主要省在不需要构建稀疏索引和做3D卷积。显存占用主要来自高分辨率特征图。64×2048在浅层展开后通道数是64的话一张特征图就是八百多万个元素乘以batch size很容易吃满。控制显存的手段包括浅层通道数压低、用checkpoint换显存、把batch size降到2配合梯度累积。4. 常见问题与排查实录4.1 训练不收敛或者loss突然爆掉这是复现range view检测器最常见的第一个坎。表现是前几百步loss正常下降然后突然变成nan或者loss一直卡在一个较高的值不动。按发生概率排原因大概是这几个。range归一化没做原始距离值是几十米量级进了网络之后激活值爆炸反向传播时梯度溢出。解决很简单在输入端除以最大量程。无效像素没有mask归一化时这些0值被当成真实距离参与统计产生的均值方差完全错误。这个问题的表现是loss能降但降不下去验证指标一直很低。学习率太大range view的特征分布和普通图像差别大用图像分类那套学习率容易崩先降到千分之一以下试。还有一个比较隐蔽的原因朝向回归用的角度编码如果没做归一化sin和cos的预测值可能超出-1到1的范围反解角度时会出现异常值间接导致框的尺寸计算溢出。解决方式是在这个分支上加tanh或者在loss里对预测值做裁剪。4.2 远距离目标大面积漏检如果训练正常但远距离的召回明显偏低先别急着改网络先确认标注有没有被裁掉。有些预处理流程按距离过滤点云把远处点删了但标注框还留着这些框在range image上根本找不到对应的像素自然检不出来。确认标注没问题之后看range image上远处目标还剩几个像素。如果只剩两三列那网络确实无能为力要么提高输入分辨率要么接受这个精度上限。这两条路的取舍很现实提高分辨率显存和耗时会成倍增加。网络层面可以做的一个是把RCPM的gate分支在远距离的表现单独看一下可视化权重图看看是不是远处像素的权重被压得太低。如果确实如此可以在loss里给远距离正样本更高的权重。另一个是检查FPN的最深层是不是下采样过头了远处小目标在最深层已经消失融合时等于没贡献。4.3 重复框和朝向翻转重复框的典型表现是同一个物体输出了两三个位置接近但尺寸不同的框。前面说过这多半是投影截断导致的。先看NMS阈值从0.4往下调试试。如果调到0.3还是重复说明质量分支没学好检查质量标签的构造是否正确——质量标签应该是预测框和真实框的IoU如果在range view下算二维IoU而不是三维IoU标签本身就是错的。朝向翻转的表现是预测的框位置对、尺寸对但车头方向反了180度。这个在对称物体上尤其难分辨。排查方法是把预测框和真实框都画出来看如果大量呈现系统性反向多半是角度编码的问题如果只是零星几个可能是训练不充分。角度用sin/cos编码基本能避免这个问题如果还在用直接回归角度值的方式趁早换掉。4.4 问题速查表现象最可能原因排查动作loss变nanrange未归一化或学习率过大检查输入数值范围降学习率loss能降但指标不涨无效像素未mask打印有效像素占比加掩码远距离召回低输入分辨率不足或标注被裁统计远处目标像素数重复框多NMS阈值高或质量分支失效降阈值检查质量标签朝向系统性反向增强时未同步更新角度检查翻转和旋转的标注变换验证指标远低于训练反投影坐标系错误核对传感器与车体坐标系显存溢出浅层通道数过大压通道降batch用checkpoint除了表里这些再补一条我自己的经验复现这类方法时先在一个小规模子集上把过拟合跑通。取几十帧数据关掉所有增强让模型在这几十帧上把loss降到接近零指标刷到很高。这一步能验证整条链路的正确性包括投影、标注对齐、损失计算、反投影。如果连过拟合都做不到说明链路上有bug这时候去调超参是浪费时间。等过拟合跑通了再放开全量数据和增强问题会少很多。5. 适用边界什么时候该选range view5.1 选型的判断依据RangeDet的结论不是range view全面优于BEV这一点要读清楚。它证明的是range view在经过针对性设计后能达到和主流方法相当的精度同时在计算效率上有优势。那么工程上该怎么选我的判断依据主要看三条。第一是延迟预算。如果部署平台算力紧张需要极低的推理延迟range view的2D卷积结构有明显优势不需要稀疏算子对硬件友好。第二是测距精度的敏感度。如果应用场景对距离误差很敏感range view保留原始测量的特性有价值避免了体素化的量化误差。第三是目标尺寸分布。如果场景里远距离小目标占比很高range view的分辨率瓶颈会更早暴露这时候BEV或者多视角融合可能更合适。反过来说如果场景里主要是中近距离目标、对精度要求极高、算力充足那BEV仍然是更稳妥的选择生态成熟、调优经验多、出问题的概率低。5.2 在多传感器融合里的位置range view还有一个我觉得被低估的价值它和图像的对齐关系天然更好。相机的成像模型也是基于射线的把激光点投影到图像上做像素级对齐时range view的像素和图像的像素在角度上是一一对应的融合时的几何变换简单。BEV是对地面的俯视展开和相机视角差得远融合时需要更复杂的变换。如果要做激光雷达和相机的特征级融合range view提供了一个很自然的中间表示。这也是RangeDet这类方法在融合方案里经常被当作激光分支的原因。5.3 落地时的几个现实约束最后说几个实际问题。range image的列数在不同数据集之间不统一跨数据集迁移时预处理要重写这一点比BEV麻烦。BEV的网格尺寸虽然也要调但调整的粒度更粗影响没那么大。另一个是标注格式的转换。大多数数据集的标注是三维框要转到range view下做训练需要一套投影和反投影的工具链这套工具链的正确性直接决定训练效果。建议在正式训练前写一个可视化脚本把投影后的框画在range image上人工看几十帧确认框和点云对得上。这半小时的检查能省掉后面几天的debug。我在实际搭这套流程的时候最大的体会是range view方法的难点不在网络结构而在数据链路的几何一致性。投影公式、坐标系、增强变换、反投影这四个环节里任何一个出了偏差网络都不会给你明确的报错只会表现为指标上不去。所以复现这类工作时把验证几何正确性的工具先写好比急着调模型参数重要得多。模型结构可以参考论文但这条数据链路的坑只能自己一个个踩过去。