3、深度图预处理:深度图空洞填充、双边滤波降噪、深度值归一化与阈值裁剪、深度图转伪彩色图
深度相机拿到手的原始数据,说白了就是一堆「带洞的灰片」。你想想看,ToF 或者结构光相机在遇到黑物体、反光表面、远距离边缘时,像素点直接就罢工了——返回 0 值或者 NaN。这种数据你敢直接拿去用?我反正不敢。
所以预处理这一步,是 AR 交互里最容易被忽视、但影响最大的环节。今天我就把四个核心操作掰开揉碎讲清楚:空洞填充、双边滤波、归一化与裁剪、伪彩色映射。
核心逻辑一句话:深度图预处理的目标,是把「脏乱差」的原始深度数据,变成「干净、连续、可用」的交互输入。
3.1 深度图空洞填充:把「黑洞」补上
空洞是怎么来的?我举个例子。你拿深度相机对着一个黑色皮沙发拍,红外光打上去直接被吸收了,返回的信号几乎为零。这时候深度图上就是一个黑洞。还有玻璃、镜面、远距离边缘——都是空洞高发区。
空洞填充的常用方法有三种,我按推荐度排序:
| 方法 | 原理 | 适用场景 | 我踩过的坑 |
|---|---|---|---|
| 最近邻插值 | 用空洞周围最近的有效像素填充 | 小空洞(1-3像素) | 大空洞会留下明显「补丁感」 |
| 双线性插值 | 取周围四个方向的有效像素加权平均 | 中等空洞(3-10像素) | 边缘处容易模糊 |
| 联合双边填充 | 结合彩色图引导,保留边缘 | 大空洞 + 边缘敏感场景 | 计算量大,移动端慎用 |
我的个人习惯:先用形态学膨胀操作把孤立小空洞吃掉,再用联合双边填充处理大空洞。这样既快又稳。
// 伪代码:空洞填充核心逻辑
for each pixel (x, y) in depth_map:
if depth_map[x][y] == 0: // 空洞
neighbors = get_valid_neighbors(x, y, radius=3)
if len(neighbors) > 0:
depth_map[x][y] = median(neighbors) // 中值填充
else:
depth_map[x][y] = nearest_valid_pixel(x, y) // 向外搜索
3.2 双边滤波降噪:去噪不糊边
深度图的噪声和普通图像不一样。普通图像噪声是随机的,深度图噪声往往出现在物体边缘——因为深度跳变的地方,信号本身就不可靠。
普通高斯滤波会把边缘也磨平了。你想想看,一个 AR 交互里,手和桌面的边缘要是糊了,手势识别直接崩。所以必须用双边滤波。
双边滤波的核心思想:空间距离近的像素权重高,灰度值相近的像素权重也高。说白了就是「既要看距离,也要看深度值是否接近」。
// 双边滤波公式(理解即可)
I_filtered(x) = (1/W) * Σ I(xi) * G_s(||x - xi||) * G_r(|I(x) - I(xi)|)
// 其中 G_s 是空间高斯核,G_r 是值域高斯核
// W 是归一化系数
我曾经踩过的坑:双边滤波的参数 σ_s(空间标准差)和 σ_r(值域标准差)非常敏感。σ_s 设太大,边缘还是糊;σ_r 设太大,噪声去不掉。我一般从 σ_s=5, σ_r=0.1 开始调,根据实际深度范围微调。
3.3 深度值归一化与阈值裁剪:把数据「对齐」
原始深度值范围很离谱。ToF 相机可能输出 0~65535(16位),结构光相机可能是 0~4095。但 AR 交互里,我们只关心某个距离范围内的物体——比如 0.3 米到 3 米之间。
所以归一化之前,先做阈值裁剪:
- 近裁剪:太近的像素(比如 < 0.3m)直接丢弃,因为太近会导致手部遮挡严重
- 远裁剪:太远的像素(比如 > 3m)也丢弃,因为远距离深度精度太差
- 归一化:把裁剪后的范围映射到 [0, 1] 或 [0, 255]
// 归一化 + 阈值裁剪示例(Python风格)
def normalize_depth(depth_map, min_dist=0.3, max_dist=3.0):
# 1. 阈值裁剪
depth_clipped = np.clip(depth_map, min_dist, max_dist)
# 2. 线性归一化到 [0, 1]
depth_norm = (depth_clipped - min_dist) / (max_dist - min_dist)
# 3. 空洞位置保持 0
depth_norm[depth_map == 0] = 0
return depth_norm
注意:归一化后的深度图,0 代表空洞或无效区域,1 代表最远有效距离。这个约定在后续的手势识别、碰撞检测中非常关键。
3.4 深度图转伪彩色图:让深度「看得见」
深度图本身是灰度图,人眼看不出距离变化。但 AR 交互调试时,你需要一眼看出哪里近哪里远。伪彩色映射就是干这个的。
常用的映射方式:
- Jet 色图:蓝→青→绿→黄→红,近处偏蓝,远处偏红
- Turbo 色图:Google 改进版,色彩过渡更均匀
- 自定义色图:比如我只关心手部区域(0.3~1.0m),就把这个范围映射到红绿渐变
// 伪彩色映射核心逻辑
// 输入:归一化深度图 [0, 1]
// 输出:RGB 彩色图
for each pixel:
if depth == 0:
color = (0, 0, 0) // 空洞显示黑色
else:
// 使用 Jet 色图映射
r = jet_red(depth)
g = jet_green(depth)
b = jet_blue(depth)
color = (r, g, b)
我建议:调试阶段用 Turbo 色图,因为它的色彩对比度更高,能快速发现深度异常。正式产品里,如果不需要可视化,直接输出归一化深度图即可,省掉伪彩色这一步能省不少性能。
小结
深度图预处理这四个步骤,环环相扣。空洞填充解决「数据缺失」,双边滤波解决「噪声干扰」,归一化裁剪解决「量纲统一」,伪彩色解决「可视化调试」。嗯,每一步都有坑,但踩过之后你会发现——预处理做得好,后面的交互算法事半功倍。
公众号:蓝海资料掘金营,微信deep3321