如何将深度图转换为cv::rgbd::RgbdNormals适用的3D点输入格式
问题说明
尝试基于OpenCV实现表面法向量计算,初始实现代码如下:
// load 16bit uchar depth image (values are in millimeters) cv::Mat img = cv::imread("depth.png", CV_16UC1); // define our camera matrix cv::Mat k = (cv::Mat1d(3, 3) << 900, 0, 640, 0, 900, 360, 0, 0, 1); // compute surface normals cv::Mat normals; cv::rgbd::RgbdNormals normal_computer(img.rows, img.cols, CV_32F, k, 3); normal_computer(img, normals); std::cout << "normals" << std::endl << normals << std::endl << normals.size() << std::endl << normals.channels() << std::endl << std::endl;
代码运行时触发断言报错:
terminate called after throwing an instance of 'cv::Exception' what(): OpenCV(4.2.0) ../contrib/modules/rgbd/src/normal.cpp:776: error: (-215:Assertion failed) points3d_ori.channels() == 3 in function 'operator()' Aborted (core dumped)
根据OpenCV 4.2.0官方文档,cv::rgbd::RgbdNormals算子仅支持两类输入格式:
- 尺寸为
rows×cols×3、数据类型为CV_32F/CV_64F的三维点云矩阵 - 尺寸为
rows×cols×1、数据类型为CV_16S的单通道深度矩阵
直接加载得到的CV_16UC1格式深度图不符合输入要求,因此触发报错。当前运行环境为Ubuntu 20.04 LTS,OpenCV版本为v4.2.0。
解决方案
方案1:转换为3通道点云输入(推荐,兼容性最好)
该方案不会出现类型、单位匹配的隐式问题,是官方示例优先使用的方式,步骤如下:
- 将加载的16位无符号毫米单位深度图转换为32位浮点格式,同时把深度值单位从毫米转换为米(与相机内参量纲匹配)
- 调用
cv::rgbd::depthTo3d接口,结合相机内参将深度图转换为rows×cols×3规格的CV_32F类型三维点云 - 将生成的点云传入法向量计算器即可得到正确结果
修正后的完整代码片段:
#include <opencv2/rgbd.hpp> // 其余依赖头文件按实际需求引入 int main() { // 加载16位无符号单通道深度图,深度值单位为毫米 cv::Mat depth = cv::imread("depth.png", cv::IMREAD_UNCHANGED); // 校验加载结果与格式,避免后续异常 CV_Assert(!depth.empty() && depth.type() == CV_16UC1); // 定义相机内参矩阵 cv::Mat k = (cv::Mat1d(3, 3) << 900, 0, 640, 0, 900, 360, 0, 0, 1); // 深度值单位转换:毫米转米,格式转换为32位浮点 cv::Mat depth_float; depth.convertTo(depth_float, CV_32F, 1.0 / 1000.0); // 深度图转三维点云,输出为3通道CV_32F格式矩阵 cv::Mat points3d; cv::rgbd::depthTo3d(depth_float, k, points3d); // 初始化法向量计算器 cv::Mat normals; cv::rgbd::RgbdNormals normal_computer(depth.rows, depth.cols, CV_32F, k, 3); // 传入符合格式要求的点云计算法向量 normal_computer(points3d, normals); // 后续结果处理逻辑 std::cout << "normals size: " << normals.size() << ", channels: " << normals.channels() << std::endl; return 0; }
方案2:转换为符合要求的单通道深度输入
如果不需要显式生成点云,可以直接将现有CV_16UC1格式的深度图转换为接口要求的CV_16S类型,只要场景深度范围不超过32米(毫米单位下32767为CV_16S的最大正数值)就不会出现溢出问题,转换代码如下:
cv::Mat depth_16s; img.convertTo(depth_16s, CV_16S); // 后续直接将depth_16s传入normal_computer即可
注意:单通道输入方式对深度值范围、单位要求严格,若深度值超出CV_16S表示范围会出现数值溢出,导致法向量计算结果完全错误,生产环境优先选择方案1。
内容的提问来源于stack exchange,提问作者ravi
相关产品推荐
相关产品推荐

