4. 联合双边滤波利用彩色图引导深度图增强
任务说明
本案例实现联合双边滤波(Joint Bilateral Filter, JBF),利用高分辨率彩色图作为引导图,对低质量ToF深度图进行边缘保持的增强处理。输入为一张与深度图已对齐的彩色引导图(单通道灰度或三通道彩色均可)和一张待增强的深度图,输出为滤波后的增强深度图。核心目标是:在平滑深度噪声的同时,利用彩色图的边缘信息防止深度边缘被模糊。
算法原理与实现
联合双边滤波的核心思想是在传统双边滤波的基础上,将像素间的相似性度量从“深度值差异”替换为“引导图颜色差异”。对于像素\(p\)和邻域像素\(q\),权重由两部分构成:空间邻近度(高斯核)和颜色相似度(高斯核)。由于彩色图通常比深度图具有更清晰、准确的边缘,因此颜色差异项能有效引导滤波器在深度边缘处停止平滑,从而保持深度不连续。
本实现采用如下计算流程:
- 读取引导图(彩色或灰度)与深度图,确保二者尺寸一致;
- 对引导图进行灰度化(若为彩色)并归一化到[0,1];
- 对深度图进行归一化处理,便于统一计算;
- 对每个像素,在设定的窗口半径内计算空间权重与颜色权重,加权求和得到滤波结果;
- 将结果反归一化并保存为16位PNG(与ToF深度图常见格式一致)。
为控制计算复杂度,本实现使用C++编写,并采用OpenMP并行化(可选)。代码中所有参数(窗口半径、空间sigma、颜色sigma)均在main函数中显式定义,便于复现。
代码实现
// jbf_depth_enhance.cpp
// 编译: g++ -O3 -fopenmp jbf_depth_enhance.cpp -o jbf -lopencv_core -lopencv_imgcodecs -lopencv_imgproc
// 运行: ./jbf guide.png depth.png output.png 5 2.0 0.1
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>
cv::Mat jointBilateralFilter(const cv::Mat& guide, const cv::Mat& depth,
int radius, float sigmaSpace, float sigmaColor) {
CV_Assert(guide.size() == depth.size());
CV_Assert(depth.type() == CV_16UC1 || depth.type() == CV_32FC1);
// 转换为浮点并归一化
cv::Mat guideFloat, depthFloat;
if (guide.channels() == 3) {
cv::cvtColor(guide, guideFloat, cv::COLOR_BGR2GRAY);
} else {
guideFloat = guide.clone();
}
guideFloat.convertTo(guideFloat, CV_32FC1, 1.0/255.0);
if (depth.type() == CV_16UC1) {
depth.convertTo(depthFloat, CV_32FC1, 1.0/65535.0);
} else {
depthFloat = depth.clone();
}
cv::Mat result = cv::Mat::zeros(depth.size(), CV_32FC1);
int rows = depth.rows, cols = depth.cols;
// 预计算空间权重
int winSize = 2 * radius + 1;
std::vector<float> spaceWeight(winSize * winSize);
float spaceSigma2 = 2.0f * sigmaSpace * sigmaSpace;
float colorSigma2 = 2.0f * sigmaColor * sigmaColor;
for (int dy = -radius; dy <= radius; ++dy) {
for (int dx = -radius; dx <= radius; ++dx) {
float dist2 = dx*dx + dy*dy;
spaceWeight[(dy+radius)*winSize + (dx+radius)] = std::exp(-dist2 / spaceSigma2);
}
}
#pragma omp parallel for schedule(dynamic)
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
float centerGuide = guideFloat.at<float>(y, x);
float centerDepth = depthFloat.at<float>(y, x);
float sumW = 0.0f, sumD = 0.0f;
for (int dy = -radius; dy <= radius; ++dy) {
int ny = y + dy;
if (ny < 0 || ny >= rows) continue;
for (int dx = -radius; dx <= radius; ++dx) {
int nx = x + dx;
if (nx < 0 || nx >= cols) continue;
float guideDiff = guideFloat.at<float>(ny, nx) - centerGuide;
float colorW = std::exp(-(guideDiff*guideDiff) / colorSigma2);
float w = spaceWeight[(dy+radius)*winSize + (dx+radius)] * colorW;
sumW += w;
sumD += w * depthFloat.at<float>(ny, nx);
}
}
result.at<float>(y, x) = (sumW > 1e-6f) ? (sumD / sumW) : centerDepth;
}
}
// 反归一化到16位
cv::Mat output16U;
result.convertTo(output16U, CV_16UC1, 65535.0);
return output16U;
}
int main(int argc, char** argv) {
if (argc != 7) {
std::cerr << "用法: " << argv[0] << " guide.png depth.png output.png radius sigmaSpace sigmaColor\n";
return -1;
}
cv::Mat guide = cv::imread(argv[1], cv::IMREAD_UNCHANGED);
cv::Mat depth = cv::imread(argv[2], cv::IMREAD_UNCHANGED);
if (guide.empty() || depth.empty()) {
std::cerr << "图像读取失败\n";
return -1;
}
int radius = std::atoi(argv[4]);
float sigmaSpace = std::atof(argv[5]);
float sigmaColor = std::atof(argv[6]);
cv::Mat enhanced = jointBilateralFilter(guide, depth, radius, sigmaSpace, sigmaColor);
cv::imwrite(argv[3], enhanced);
std::cout << "滤波完成,输出已保存至 " << argv[3] << "\n";
return 0;
}
落地与验证
依赖环境:OpenCV 3.4+(需包含imgcodecs、imgproc模块),支持C++11的编译器,可选OpenMP。
输入数据准备:准备一张彩色引导图(如RGB纹理图)和一张16位深度图(单位通常为毫米),二者需像素级对齐且尺寸一致。若无真实ToF数据,可用仿真数据:取一张彩色图作为引导图,将深度图设为平滑渐变并在边缘处添加噪声来模拟。
运行命令:
./jbf guide.png depth.png enhanced.png 5 2.0 0.1
验证方法:
- 目视检查输出图像:深度图平坦区域噪声应明显减少,而物体边缘(对应彩色图颜色突变处)应保持锐利,不应出现边缘过度平滑或“光晕”伪影;
- 定量评估(可选):若存在真实深度真值,计算滤波前后的均方根误差(RMSE),增强后RMSE应下降;
- 参数敏感性测试:增大sigmaColor(如从0.1增至0.3)会增强平滑程度但可能损失边缘;减小sigmaSpace会使滤波更局部化。通过调整参数观察输出变化是否符合联合双边滤波的预期行为。
判定标准:程序正常退出且输出PNG文件可读;在深度平坦区域噪声明显低于输入,同时深度边缘(与彩色图边缘对齐处)的梯度变化未被明显削弱,即视为通过。