ARTICLE DETAIL

资讯详情

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

HHA编码详解:将深度图转化为CNN友好的几何特征表示

HHA编码详解:将深度图转化为CNN友好的几何特征表示 我第一次在NYU Depth V2数据集上下载预处理脚本时看到里面有个函数叫computeHHA第一反应是“哈哈”——以为作者随手起了个卖萌的名字。直到我把数据跑进网络、又去翻了Saurabh Gupta那篇ECCV 2014论文才意识到HHA是三个几何通道的缩写Height above ground离地高度、Horizontal disparity水平视差、Angle表面法向量与重力方向的夹角。它不是花活而是把“深度图”翻译成神经网络更容易消化的“几何语言”。这篇内容想聊清楚三件事HHA到底是什么、为什么当年的RGB-D识别几乎绕不开它、以及今天做检测/分割/显著性任务时你还该不该用它。适合刚接触RGB-D感知的入门者也适合在工程里踩过深度图预处理坑、想把手头数据换一种编码方式的老手。1. RGB-D感知到底在感知什么从“深度图能用就行”说起1.1 RGB-D里的“D”不是一张普通的灰度图很多人对深度图有一个直觉它跟灰度图差不多只是每个像素记录的不是亮度而是距离。这句话对了一半。深度图确实可以当成单通道图像去卷积但它和自然图像有个本质区别——自然图像里像素值的大小、比值、纹理方向都有稳定的语义规律而深度图里的数值是“绝对距离”单位是毫米或米并且这个距离跟相机位置、相机朝向强相关。同一个杯子放在0.5米处和放在1.5米处它在深度图里的像素值差了3倍但在RGB图里它的外观几乎不变。也就是说把原始depth直接送给卷积网络网络学到的其实是“离相机多远的杯子”而不是“杯子长什么样”。这一点在物体检测和分割任务上特别致命因为模型很难在不同距离下对同一物体形成统一特征。深度图还会有各种传感器噪声结构光在黑色物体上容易丢点ToF在透明玻璃上会产生飞点双目在无纹理墙面会算出差很多的视差。这些无效点和噪声如果直接进网络卷积核会被带偏。我之前在RealSense上采集数据把深度图直接可视化成灰度图发现边缘处有一圈一圈的“花边”那些都是深度跳变造成的假轮廓。1.2 把深度图直接丢给卷积网络会遇到三个麻烦第一个麻烦是尺度。深度值动辄几十厘米到几米直接归一化到0到1之后远处目标的像素值几乎被压成同一个常数。比如在室内场景里1.5米和3米的物体在归一化后差距很小但它们的视觉轮廓完全不同。CNN又不是真正的“人眼”它只能从数值梯度里去猜边界结果就是远处物体学不出细节。第二个麻烦是噪声。深度图的无效点通常置0或NaN可卷积网络不知道“0是无效”这回事它会把0当成一个真实的距离值去参与计算。结果在深度边界一圈会出现一个奇怪的“暗边”严重影响分割和检测。早期很多论文里“深度图边缘补洞”“膨胀腐蚀”之类的预处理本质上就是在跟这个问题搏斗。第三个麻烦是相对信息。深度图是相对相机坐标系的距离而不是物体的固有属性。相机稍微绕光轴转一下原本同一朝向的表面在深度图里就变了相机俯仰角变了地板的梯度也变了。识别任务想要的是“物体相对重力是什么姿态”而不是“物体相对相机是什么距离”。这时候就需要一种把深度图从“相机坐标系”搬到“重力坐标系”的编码——HHA就是干这个的。2. HHA三个字母拆开讲高度、视差、夹角分别是干什么的2.1 HHA的出身从NYU Depth V2里长出来的编码HHA出自Gupta、Girshick、Arbelaez和Malik在ECCV 2014年发表的论文《Learning Rich Features from RGB-D Images for Object Detection and Segmentation》。当时的背景是RGB-D数据已经有了但怎么把深度信息喂给深度卷积网络还没有标准做法。直接把depth当第四通道效果很差把depth转成伪彩色图也只是换个颜色通道没有物理意义。他们提出的方案是对深度图做一次“以地心为参照”的几何变换得到三个通道每个通道都有明确的物理含义离地高度、水平视差、表面法向量与重力方向的夹角。这个编码后来被简称为HHA。严格按顺序H是Height第二个H是Horizontal disparityA是Angle。有人会把第二个H误会成“Height”其实不是它是视差。为什么这样做有效他们的核心洞察是卷积网络擅长从局部纹理梯度里学特征但前提是输入特征的统计规律相对稳定。把深度从相机坐标系转到重力坐标系相当于先把传感器在位姿上的变化“消掉”了一部分再让网络去学几何结构难度自然就下降了。2.2 Height above ground离地高度帮网络建立“层”的概念离地高度字面意思就是每个三维点在重力方向上离地面有多远单位是米。你可以把它理解成给整个场景做了一次“楼层分层”。室内场景里地板高度约等于0桌面在0.7米左右桌面上的杯子可能到1米安装在墙上的开关面板在1.2到1.4米天花板上的灯在2.5米以上。当网络看到某个像素的高度值在0.8米附近它就已经有了“这可能是桌面平面上的物体”的预期。这个信息是原始深度图很难直接给的。原始depth给的是离相机距离同一个桌面倾斜一点各像素距离就完全不同但桌面在重力方向上的高度无论相机怎么摆都在同一个数值附近。所以H通道其实是在帮网络建立“物体在空间中处于哪个垂直层级”的不变量。离地高度不是简单的“相机高度减深度”。它依赖于重力和地平面的估计如果地面不在视野里或者相机高度发生变化这一通道就会出问题。这一点后面的实操部分会展开说。2.3 Horizontal disparity水平视差比深度更适合作为网络输入第二个H是水平视差。在立体视觉里视差就是同一个三维点在左右相机图像里水平位置的偏差。视差和深度成反比公式是d f·b/Z其中f是焦距像素b是双目基线长度Z是深度。对单目深度传感器来说并没有真实基线但可以设定一个虚拟基线把它当作对深度做一个单调变换。那为什么不直接用深度Z因为在感知任务里我们更关心的是“近处物体的微小距离变化”而不是“远处物体的绝对距离”。深度Z在近处变化很剧烈远处却很平缓而视差恰好反过来它放大了近处的差异压缩了远处的差异。这对网络提取近处物体的轮廓非常有利。另一个原因是传感器的噪声特性ToF和双目深度传感器的误差通常随距离平方增大但在视差域基本是均匀的。让输入特征域的噪声统计更平稳网络学起来就更稳。视差通道还有一个好处它和深度是单调映射关系理论上可以从视差反推回深度也就是说这个通道保存了绝对距离信息。HHA并不是把距离丢了而是用了一种更利于学习的形式保留它。2.4 Angle表面法向量与重力方向的夹角局部朝向的描述第三个通道是表面法向量和重力方向的夹角单位是弧度范围在0到π之间。表面法向量描述的是局部平面的朝向。地板的法向量如果朝上和重力方向的夹角是0°或180°取决于法向量朝向定义墙面的法向量是水平的夹角接近90°桌面的法向量朝向也接近90°。这个通道给网络的是一种“局部表面朝向”信息。有了它网络可以区分一个像素是墙、地板还是桌面即使RGB纹理上看不出明显差别。比如一个纯白色的桌子平面和一个纯白色的墙面在RGB里很难分但如果知道其中一个表面法向量与重力夹角是90°、另一个也是90°再加上高度不同就能区分桌子高度在0.7米附近墙面则延伸整个高度。夹角通道的计算依赖法向量估计的精度。在深度边界处法向量估计通常很不稳定所以HHA整体对深度图的“孔洞修复”有一定要求。如果深度图有大量缺失法向量通道会出现大片噪声反而是拖累。这也是为什么工业落地时大家一般会先做深度补全再转HHA。3. 深度图到HHA一步步数学变换拆解3.1 先把深度图还原成三维点云HHA的所有计算都发生在三维空间第一步是把深度图反投影到相机坐标系下的三维点云。假设你有相机内参fx、fy、cx、cy对图像每个像素(u, v)和它的深度值Z以米为单位相机坐标是X (u - cx) * Z / fx Y (v - cy) * Z / fy Z Z这其实是在做“针孔相机模型”的逆变换。注意深度单位必须转成米很多传感器原始数据是毫米直接算会得到大得离谱的点云坐标。我在早期实现里就犯过这个错把毫米当米结果后面所有高度计算都错了三个数量级训练损失直接不收敛。这一步得到的点云X轴向右Y轴向下Z轴朝前——这是OpenCV和多数RGB-D相机的约定坐标系。做HHA之前务必先确认你的深度图和RGB图像的对齐关系。如果深度图和RGB图分辨率不一致要么先做对齐要么就用深度图自带的内参单独反投影不要在未对齐的图上直接计算。3.2 重力方向与地平面估计H通道的地基HHA的“以重力为参照”不是玄学它需要一个明确的重力方向向量通常由地平面的法向量来定义。在纽约大学NYU Depth V2数据集的官方工具箱里地平面是从标注或传感器记录里获得的对一般数据集常见做法有几种用RANSAC在点云里拟合最大的水平平面把平面法向量作为重力方向。用IMU直接获取重力方向再把相机坐标系下的点云旋转到重力对齐的坐标系。假设相机光轴大致水平、地平面在相机下方固定高度用于一些固定安装的室内摄像头。官方实现里高度计算依赖“相机离地高度”和“重力方向”。假设你已经把重力方向单位向量g指向上方求出来了地平面过某个点P_ground那么任意三维点P的离地高度就是height (P - P_ground) · g如果地平面法向量正好是g这个式子非常简洁。从动捕或IMU拿到重力方向后也可以把点云整体旋转到“重力方向朝上”的坐标系再算高度。很多人会在这里混淆H通道不是“相机高度减去深度”而是把三维点投影到重力方向上得到一个绝对高度。相机自身的高度变化会直接改变所有点的高度值所以同一个场景如果相机高度不同H通道的分布是完全不同的。3.3 计算三个通道拿到点云和重力方向后三个通道的计算可以依次完成。高度通道如上所述高度 (P - P_ground)·g。实际操作中地面可能不在视野范围无法直接得到地面点。一个折中方案是用“相机高度加上点的相对高度”。假设相机位于重力方向上的高度为h_cam那么某点的离地高度可以写成h_cam减去该点相对相机在重力方向上的分量。这时候h_cam是相机离地面的高度如果相机装在机器人顶部这个值是固定的如果相机随云台旋转就需要实时更新。视差通道直接对深度做倒数再乘一个尺度常数。官方工具箱里通常用类似disparity min(f * b / Z, max_val)的形式。如果你用的是单目ToFb设为1或按经验设一个值如果你想保持和深度图相同的方向也可以用1 / Z再缩放到合适范围。重点不是具体常数而是保持训练和推理时参数一致且数值范围稳定。角度通道先用点云估计每个像素的表面法向量再计算该法向量与重力方向的夹角。法向量估计的常用方法包括对局部邻域做平面拟合用协方差矩阵的最小特征向量或者用相邻像素差值的叉积。后者的实现很快但噪声也大。我会在下面的代码示例里给一个简化版本。总的来说HHA三个通道的计算虽然各有各的细节但核心逻辑都围绕“把相机坐标系的深度信息变换到重力坐标系的特征表示”。这也是它比原始深度图更“好读”的根本原因。3.4 归一化与存储数值范围决定了训练稳定性原始HHA计算出来之后数值范围其实很乱。高度可能从0到几米视差从0到几百像素角度则是0到π。如果不做归一化直接当一个三通道图喂给卷积网络训练初期梯度会被大数值的特征主导。常见的处理方法是高度截断到一个合理范围比如0到2米再除以最大值得到0到1视差截断到0到某个阈值再归一化角度直接除以π。有些工具箱会把这些通道缩放到0到255存成uint8的图像。这个做法在当年是为了节省磁盘、兼容ImageNet预训练模型的输入习惯但也带来了量化误差。我个人更推荐在工程里存float16的npy或HDF5避免把高度0.73米和0.74米之间的差异抹掉。归一化参数必须在训练集上统计出来然后固定不能每个batch重新算。否则网络相当于每次都在跟一个数值分布不断变化的输入作斗争收敛速度会明显下降。4. 同样的网络输入HHA和原始depth差距到底有多大4.1 我在目标检测和显著性任务上的实测印象我在做RGB-D显著性目标检测的时候用过一段时间的Faster R-CNN和后来的三分支网络。一个很直观的感受是在相同网络结构下把Depth分支的输入从原始depth换成HHA在PASCAL VOC风格的RGB-D检测任务上mAP大约有1到3个百分点的提升在显著性检测上边界质量指标比如BF score提升更明显。它不是那种“换了网络结构才有的质变”而是“同样的模型却更稳了”的量变。原因出在预训练上。那时候大家普遍用ImageNet预训练模型做初始化而ImageNet模型是为三通道自然图像设计的。原始depth单通道要复制三次才能凑成三通道但它的数值分布和自然图像差太远HHA虽然也不是自然图像但它的三个通道数值都在0到1附近的连续范围空间梯度分布也更接近自然图像的纹理分布预训练模型迁移过去的特征会更有效。4.2 HHA为什么更适合迁移学习和预训练HHA在结构上有一个先天优势它的三个通道都来自同一个几何变换但各自“看”的是不同的物理量——高度、视差、朝向。这和RGB的“红色通道、绿色通道、蓝色通道”有某种结构相似性卷积网络在预训练阶段学到的“通道间相关性”能力可以被迁移到HHA上。相比之下很多自制的“深度伪彩色图”只是把单通道深度用colormap映射成三通道本质上还是一张单通道图套了三层皮通道间的信息冗余极高预训练模型迁移过去的效果自然差。HHA每个通道的梯度模式不同网络反而能在早期卷积层学到更丰富的几何特征。4.3 不是每个任务都需要HHA也不是说所有RGB-D任务都非得用HHA。如果你做的是三维重建、点云分类直接用点云坐标和法向量本身效果更好。如果你的深度数据质量极差比如大量黑色物体导致结构光深度缺失HHA的高度和角度通道会充满噪声这时候直接用原始depth可能还好一些。我的经验是HHA最适合“把RGB和Depth都当图像模态处理”的识别类任务特别是目标检测、语义分割、显著性检测。对于逐点几何推理的任务它反而是降维了。5. 踩坑记录从官方工具箱到自己的Python实现5.1 官方工具箱里的隐藏假设网上流传最广的HHA实现是Saurabh Gupta的Matlab工具箱包含在rcnn-depth项目里。这个工具箱有几个隐藏假设不太好发现。第一它的内参默认是Kinect v1的fx/fy/cx/cy都是NYU Depth V2的固定值。你换成RealSense或者Kinect v2的数据如果不管内参直接跑出来的视差通道尺度会不对。第二它默认深度图已经做了对齐深度图尺寸和RGB一致。如果你的深度图是640x480而RGB是1920x1080要先做对齐。第三它把重力方向当作已知量传入如果你从数据里估计地平面的方法不够稳H通道会在局部区域出现不连续。5.2 内参不一致导致视差通道失真我第一次换到自采数据时用的是D435i视差通道出来的图明显比NYU上看到的“扁”很多。查了半天发现是fx不一样。Kinect v1的fx约等于570像素D435i的fx根据分辨率不同可能只有300多视差归一化时阈值没改结果近处物体全部被截断到同一个值。这个坑很容易出在“换相机”之后。解决方式不要用固定常数直接从相机内参读取fx把视差阈值也根据数据集统计出来。离线预处理时把统计好的归一化参数存成JSON训练和测试用同一份。5.3 重力方向估计失败的常见场景重力方向是HHA的“坐标轴原点”一旦估计错整个HHA就废了。我遇到过几种典型场景机器人下坡或云台俯仰角变化IMU外参没标定导致重力方向在相机坐标里有偏差。室内场景里地板被桌子大面积遮挡RANSAC拟合地平面时拟合到了桌面于是“离地高度”变成了“离桌面高度”。无人机俯拍场景地面根本不在视野范围内地平面估计完全失效。处理办法也分几种如果固定安装手动标一次外参如果场景里有明显大面积地面RANSAC要限制平面面积阈值如果既没有地面又没有IMU坦白说HHA在这类场景里不合适不如换用表面法向量或直接点云输入。5.4 保存格式对HHA精度的影响HHA的视差通道在近处数值变化剧烈你在uint8里保存时可能把0.5米到0.6米之间的视差压到几个灰度级里网络根本区分不出来。高度通道在接近地面时变化也很细微同样容易量化损失。建议保存方式分两种如果只是训练基线可以存uint8 PNG磁盘占用小跑起来快如果对精度有要求尤其做缺陷检测、分割任务存float16的npy或者HDF5。不要用JPEG压缩HHA不是给人看的图JPEG的块效应会直接污染表面法向量和高度信息。5.5 一个可参考的简化实现这里给一个思路清晰的Python简化版本方便理解不保证直接投入生产。它省略了法向量估计的精细优化和地面RANSAC重点展示流程。import numpy as np def depth_to_hha(depth, fx, fy, cx, cy, gnp.array([0.0, -1.0, 0.0]), h_cam1.2, baseline1.0, max_disparity2.0): depth: HxW float, 单位米, 0表示无效 g: 重力方向单位向量相机坐标系这里默认相机水平朝前、重力向下 h_cam: 相机离地高度单位米 h, w depth.shape u, v np.meshgrid(np.arange(w), np.arange(h)) # 反投影到相机坐标 Z depth.astype(np.float32) X (u - cx) * Z / fx Y (v - cy) * Z / fy points np.stack([X, Y, Z], axis-1) # HxWx3 # 高度通道相机高度 - 点沿重力方向的分量简化 height h_cam - np.sum(points * g, axis-1) # 注意符号取决于g方向 # 视差通道 disparity (fx * baseline) / np.maximum(Z, 1e-3) disparity np.clip(disparity, 0, max_disparity) # 表面法向量简化按行差和列差的叉积 dz_dx np.zeros_like(Z) dz_dy np.zeros_like(Z) dz_dx[:, 1:-1] Z[:, 2:] - Z[:, :-2] dz_dy[1:-1, :] Z[2:, :] - Z[:-2, :] # 法向量近似(-dz_dx*fx, -dz_dy*fy, 1) 再归一化 normals np.stack([-dz_dx * fx, -dz_dy * fy, np.ones_like(Z)], axis-1) norm np.linalg.norm(normals, axis-1, keepdimsTrue) normals normals / np.maximum(norm, 1e-6) # 夹角通道法向量与重力方向夹角 angle np.arccos(np.clip(np.sum(normals * g, axis-1), -1.0, 1.0)) # 简单归一化 height np.clip(height / 2.0, 0, 1) disparity disparity / max_disparity angle angle / np.pi hha np.stack([height, disparity, angle], axis-1) return hha.astype(np.float32)这段代码没有做无效点掩膜、没有精细的地面估计法向量也只是梯度近似但它能让你直观感受到HHA不是某种不可复现的“黑盒预处理”。投入生产前一定要补上深度补全、无效点抑制和更稳的法向量估计。6. 现在做RGB-D识别还值得用HHA吗6.1 从三分支网络看HHA的生态位近两年的RGB-D显著性检测、缺陷检测论文里经常能看到“分支1是RGB分支2是Depth分支3是HHA”这样的设计。像“三分支交叉模式交互网络”这类结构本质上是在用不同语义层次的输入做互补RGB提供纹理和颜色Depth提供绝对距离HHA提供几何属性。HHA在这种框架下不是一个被淘汰的旧东西反而因为计算成熟、物理意义清晰成了一个稳定的“几何特征基准分支”。很多新方法其实可以把HHA换成任何其他几何编码比如表面法向量、点云坐标、深度补全后的距离变换但替换之后性能不一定更好。原因就在于HHA的视差通道保留了绝对距离高度和角度提供了结构信息三者之间信息重叠小、互补性强。这种“低成本、高信息密度”的特点让它至今仍是一个强基线。6.2 和其他深度编码方式比HHA的优势与劣势我这里把常见的几种深度编码放在一起比过。编码方式信息维度优点缺点原始depth距离信息无损直观尺度敏感噪声大HHA高度视差角度几何语义清晰利于预训练依赖重力方向地面估计有风险表面法向量局部朝向对姿态稳健没有绝对距离信息HAA高度角度去掉了视差通道近处细节丢失深度补全距离变换距离距离梯度边界清晰补全误差会放大HHA的优势在于它是三者兼顾劣势则是它至少需要“相机相对地平面的姿态”这一额外假设。如果你的传感器是固定安装、姿态稳定HHA几乎是白赚的收益如果你的相机装在四足机器人或无人机上姿态随时在变HHA的重力估计就会成为系统中最脆弱的一环。6.3 我的选择什么时候继续用HHA什么时候换掉回到我自己做项目的习惯我会这样选如果任务是把深度当“第二张图像”来用比如RGB-D检测、显著性检测、语义分割我优先用HHA而且把它直接作为三分支或双分支结构的几何输入。主要原因是省心它不需要PointNet那样的点云分支也不需要在训练时处理稀疏点云整个pipeline在图像域里就能跑通。如果任务是抓取位姿估计、三维重建、机器人操作这类需要精细几何理解的任务HHA就不够用了。这时候我更倾向用点云或TSDF让网络直接感知三维结构HHA的2.5D编码会丢失太多几何细节。另一个判断依据是数据量。HHA像是给CNN的一副“拐杖”在小数据集、预训练迁移场景下特别管用。数据量真正大到可以端到端学出来的时候模型自己也能够从原始depth里学会类似的特征HHA的优势就没那么明显了。可现实是很多项目根本没有那么多标注数据所以我仍然会在pipeline里保留HHA——它是我能花最小代价换来稳定提升的一个预处理步骤。
返回列表