一. 背景介绍

现在很多智能导航场景都涉及到激光(毫米波,固态等)雷达和相机视觉信息融合,这里激光雷达一般都是指多线激光雷达,16线,64线,甚至更多线数. 但多线激光雷达动不动数万的价格,让很多技术人员无法尝试.我前面写过一篇使用单线激光雷达和相机视觉融合的文章, 但那个过于基础,纯是技术学习目的. 这里我使用一款深度相机来做3D点云和相机视觉的融合, 构建一个彩色3D点云场景. 并基于该点云做稀疏化处理,以模拟出多线激光雷达的效果.

本实践基于ROS系统开发,硬件平台采用一款搭载Jetson Nano的四轮机器人.平台搭载一款深度相机,该深度相机是基于奥比中光 Astra Pro 方案的定制版相机,相机具备 1080P RGB 普通相机功能和基于结构光原理深度相机功能。

具体参数见下表。

内容 参数
RGB 像素 1080P
深度分辨率 640*480/320*240
深度最大帧率 30FPS
视频分辨率 1280*720
视频最大帧率 30FPS
视场角(FOV) H 58.4° x V 45.7°
精度 1m: ±3mm
工作范围 0.6-8m

由于这是一款基于结构光原理的的深度相机,决定了其与基于TOF原理的深度相机存在较大的差异,缺点主要表现在:

1)容易受环境光干扰,室外体验差;

2)随检测距离增加,精度会变差;

3)  检测距离近.
以上3点,确定了其只能应用小场景环境,比如室内. 由上表也可以看出,其有效距离为:0.6-8m.

二.准备工作

除了最基础的系统平台工作外,我们需要做一些必要准备工作:

1.RGB相机的内参标定,用以实现将相机3D坐标系中某点,映射到相机的成像像素平面;

2.深度相机和RGB相机的联合标定, 虽然他们在一个结构设备上,但仍存在位置差,比如我的相机拍出的RGB图像和点云图像实际是错位的,需要联合标定. 这一步的目的是为了实现将基于深度相机的点云通过坐标系变换, 转换到RGB相机的3D坐标系下;

3.基于步骤2,在ROS系统准备RGB相机和深度相机点云的TF坐标系, 即这两个物理设备在空间上相对于车辆质心的坐标. 以保证可以在相机坐标系和点云坐标系间利用ROS接口做坐标系变换(lookupTransform());

三.主要思路和步骤

有了上面的准备工作, 还不能着急编写代码, 我们需要捋清楚为了实现我们的目标还需要哪些更细节的步骤, 先想清楚再动手做. 这里先给出一张流程图:

结合上图思路:

1. 利用ROS接口订阅(camera_info,image,point_cloud)消息: 

cameraInfo_sub_ = node_h.subscribe("/camera/camera_info", 1, &EntryClass::cameraInfoCallback, this);cameraImage_sub_ = node_h.subscribe("/camera/image_raw", 1, &EntryClass::cameraImageCallback, this);pointCloud_sub_ = node_h.subscribe("/camera/depth/points", 1, &EntryClass::pointCloudCallback, this);//用于发布融合视觉图像后的彩色点云
fusion_cloud_pub_ = node_h.advertise<sensor_msgs::PointCloud2>("camera/depth/rgb_points", 1);//---------------------------------
void cameraInfoCallback(const sensor_msgs::CameraInfo::ConstPtr &intrinsic_value_msg);
void cameraImageCallback(const sensor_msgs::Image::ConstPtr &image_msg);
void pointCloudCallback(const sensor_msgs::PointCloud2::ConstPtr &point_cloud2_msg);

这里"/camera/camera_info"消息用于获取相机内参相关信息(消息名称请以你平台实际为准),主要包括图像尺寸,畸变系数,投影系数等参数.

详细信息,请参考sensor_msgs::CameraInfo: sensor_msgs/CameraInfo Documentation

    image_frame_size.height = cameraInfoMsg->height;image_frame_size.width = cameraInfoMsg->width;// 相机内参camera_intrinsic_value = cv::Mat(3, 3, CV_64F);//相机内参变换矩阵3x3,把3D点投影到2D像素平面时使用for (int row = 0; row < 3; row++){for (int col = 0; col < 3; col++){camera_intrinsic_value.at<double>(row, col) = cameraInfoMsg->K[row * 3 + col];}}// 相机畸变参数. For "plumb_bob"模式, the 5 parameters are: (k1, k2, t1, t2, k3).distortion_coefficients = cv::Mat(1, 5, CV_64F);for (int col = 0; col < 5; col++){distortion_coefficients.at<double>(col) = cameraInfoMsg->D[col];}// 投影系数,获取投影矩阵3x4的数组的fx,fy,cx,cy元素fx = static_cast<float>(cameraInfoMsg->P[0]);fy = static_cast<float>(cameraInfoMsg->P[5]);cx = static_cast<float>(cameraInfoMsg->P[2]);cy = static_cast<float>(cameraInfoMsg->P[6]);

2. 在cameraImageCallback()回调函数中获取相机原始RGB图像,并利用获取到的相机内参,对原始RGB图像做去畸变处理.

cv_bridge::CvImagePtr cv_image = cv_bridge::toCvCopy(image_msg, "bgr8");
cv::Mat image = cv_image->image;//图像去畸变, 使用相机内参和畸变系数可以图像去畸变
if( true )cv::undistort(image, current_image_frame, camera_intrinsic_value, distortion_coefficients);
else //如果接口获取的图像已经做了去畸变处理,不用在处理current_image_frame = image;

处理后的图片current_image_frame作为当前图片帧,稍后用于和最近的同时间序列的点云做融合处理. 也可以publish出去用rqt_image_view测试观察使用.

3.在pointCloudCallback回调函数中处理点云信息.  主要工作是: 利用pcl的体素滤波,稀疏化点云;利用前期系统发布的RGB相机和深度相机的坐标系变换关系,将基于深度相机3D坐标系的点云坐标变换到RGB相机3D坐标系下的点云坐标:

pcl::PointCloud<pcl::PointXYZ> pointCloud;
pcl::fromROSMsg(*point_cloud2_msg, pointCloud); //ros sensor_msgs::PointCloud2 -> pcl::PointCloud//下采样后点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());//体素滤波,稀疏点云
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(pointCloud);
voxel_filter.setLeafSize(0.1f,0.1f,0.1f);
voxel_filter.filter(*cloud_filtered);pcl::PointXYZ camera_point;
for(int i = 0; i < cloud_filtered->points.size(); i++)
{//把点云里面的3D坐标从点云坐标系,变换到摄像机的坐标系[x',y',z']camera_point = transformPoint(cloud_filtered->points[i], camera_depth_tf);......
}

4. 基于第3步,将转换后的RGB相机坐标系下的点云坐标,利用前期获取的相机内参的投影系数,进一步将点云3D坐标,映射到RGB相机的成像像素平面:

pcl::PointCloud<pcl::PointXYZ> pointCloud;
pcl::fromROSMsg(*point_cloud2_msg, pointCloud); //ros sensor_msgs::PointCloud2 -> pcl::PointCloud//下采样后点云
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZ>());//体素滤波,稀疏点云
pcl::VoxelGrid<pcl::PointXYZ> voxel_filter;
voxel_filter.setInputCloud(pointCloud);
voxel_filter.setLeafSize(0.1f,0.1f,0.1f);
voxel_filter.filter(*cloud_filtered);pcl::PointXYZ camera_point;
for(int i = 0; i < cloud_filtered->points.size(); i++)
{//把点云里面的3D坐标从点云坐标系,变换到摄像机的坐标系[x',y',z']camera_point = transformPoint(cloud_filtered->points[i], camera_depth_tf);// 再使用相机内参将相机三维空间点投影到相机像素平面[x,y]int col = int(camera_point.x * fx / camera_point.z + cx);int row = int(camera_point.y * fy / camera_point.z + cy);...
}

此处得到的(col,row),为RGB相机成像像素平面的2D坐标(x,y), 也就是上面第2步保存的当前图片帧current_image_frame中的2D坐标(x,y).  注意时序上保持两者同步, 代码中同步相关代码已略.

5.  接上一步,从当前图片帧中提取出每个2D坐标(x,y)的像素颜色值(r,g,b,), 保存到该2D点对应的3D点云坐标点内, 形成基于RGB相机3D坐标系下的3D坐标彩色点云.

pcl::PointCloud<pcl::PointXYZ> pointCloud;
pcl::fromROSMsg(*point_cloud2_msg, pointCloud); //ros sensor_msgs::PointCloud2 -> pcl::PointCloud// 单个彩色点: RGBXYZ
pcl::PointXYZRGB colored_3d_point;// 存储处理后的彩色点云: [RGBXYZ,,,]
pcl::PointCloud<pcl::PointXYZRGB>::Ptr newRGBPointCloud(new pcl::PointCloud<pcl::PointXYZRGB>);
newRGBPointCloud->points.clear();pcl::PointXYZ camera_point;
for(int i = 0; i < pointCloud.points.size(); i++)
{//把点云里面的3D坐标从点云坐标系,变换到摄像机的坐标系[x',y',z']camera_point = transformPoint(pointCloud.points[i], camera_depth_tf);// 再使用相机内参将相机三维空间点投影到相机像素平面[x,y]int col = int(camera_point.x * fx / camera_point.z + cx);int row = int(camera_point.y * fy / camera_point.z + cy);//根据映射后的坐标(col,row),从当前相机的当前图像帧current_image_frame上获取颜色值,并保存对应的3D位置信息if ((col >= 0) && (col < image_frame_size.width) && (row >= 0) && (row < image_frame_size.height) ) {colored_3d_point.x = camera_point.x;colored_3d_point.y = camera_point.y;colored_3d_point.z = camera_point.z;//RGBcv::Vec3b rgb_pixel = current_image_frame.at<cv::Vec3b>(row, col);colored_3d_point.r = rgb_pixel[2];// * 2;colored_3d_point.g = rgb_pixel[1];// * 2;colored_3d_point.b = rgb_pixel[0];// * 2;//存储RGBXYZ点到点云newRGBPointCloud->points.push_back(colored_3d_point);}
}

6. 至此, 我们已经得到一系列基于RGB相机坐标系的3D彩色点云, 下一步将其转化为ROS系统点云消息sensor_msgs::PointCloud2,发布即可.

sensor_msgs::PointCloud2 out_colored_cloud_msg;
pcl::toROSMsg(*newRGBPointCloud, out_colored_cloud_msg); //pcl::PointCloud --> sensor_msgs::PointCloud2//设置Header
out_colored_cloud_msg.header = point_cloud2_msg->header;//发布"colored_point_cloud" Topic
fusion_cloud_pub_.publish(out_colored_cloud_msg);

7. 第3步中的变换处理(transformPoint(point, camera_depth_tf)) 代码如下:

片段1:
tf::StampedTransform camera_depth_tf;//这里用于获取RGB相机和深度相机的坐标系变换关系
try
{transform_listener.lookupTransform(image_frame_id, depth_frame_id, ros::Time(0), camera_depth_tf);
}
catch (tf::TransformException ex)
{ROS_INFO("FindTransform : %s", ex.what());
}片段2:
//利用获取的camera_depth_tf变换关系,对点云坐标进行变换,  第2个参数为片段1的:camera_depth_tf
pcl::PointXYZ MyClass名::transformPoint(const pcl::PointXYZ& in_point, const tf::StampedTransform& transform)
{tf::Vector3 tf_point(in_point.x, in_point.y, in_point.z);tf::Vector3 tf_point_transformed = transform * tf_point;return pcl::PointXYZ(tf_point_transformed.x(), tf_point_transformed.y(), tf_point_transformed.z());
}

四.实现效果

1. 先看原始点云,由于点云过于密集即全部白色,我切换个角度,以便可以看出立体感.

正如前面讲基于结构光的深度相机,容易受环境光干扰,图中强光部分已经无点云分布.

2. 输出为RGB相机坐标系下的彩色点云,效果如下:

3. 换个角度,查看立体感效果, 如果深度相机不可见(盲区)的地方,比如桌面,呈空洞状态.

4. 由于这样的彩色点云过于密集, 对于下一步的任务处理说来,计算量较大,我们对彩色点云做稀疏化处理,使其更接近多线雷达点云. 方法一,利用pcl库的体素滤波下采样原始点云, 使其稀疏化,效果如下:

5. 方法二,在原始点云的3D空间做稀疏化,效果如下:

5. 方法三,从当前图片帧的2D空间,即相机成像平面, 做稀疏化,效果如下:

6. 同上,进一步稀疏化,如图 正如前面讲基于结构光的深度相机,容易受环境光干扰,图中强光部分已经无点云分布:

十九.在ROS系统基于点云和视觉图像数据融合构建3D点云场景相关推荐

  1. 从零开始自制实现WebServer(十九)---- 正式系统的学习一下Git 捣鼓捣鼓github以及一些其他的小组件

    文章目录 全流程实现博客链接 前引 (十九)---- 正式系统的学习一下Git 捣鼓捣鼓github以及一些其他的小组件 1.悔!为什么不在一开始做项目的时候就用Git 错过学习实践Git的最好机会 ...

  2. 第二十九章 使用系统监视器 - 应用程序监视器指标

    文章目录 第二十九章 使用系统监视器 - 应用程序监视器指标 生成指标 查看指标数据 第二十九章 使用系统监视器 - 应用程序监视器指标 Application Monitor自带的系统监控类调用各种 ...

  3. 基于数据空间的电子病历数据融合与应用平台

    基于数据空间的电子病历数据融合与应用平台 包小源1,2, 张凯3, 金梦1,2, 谢双莲3, 宋锴3 1 北京大学医学信息学中心,北京 100191 2 国家医疗服务数据中心,北京 100191 3  ...

  4. 基于遥感影像及轨迹数据融合的地图自动化生成器

    自动化的地图生成对于城市服务及基于位置服务非常重要,现有的工作研究主要利用遥感影像或可以充分反映地图路网情况的车辆轨迹数据生成地图,数据源较为单一,如果能将遥感影像数据及轨迹数据融合起来,地图生成的质 ...

  5. 基于嵌入式Linux的测温系统,基于嵌入式Linux的图像监控系统

    设计.发明的目的和基本思路.创新点.技术关键和主要技术指标 随着嵌入式技术的快速发展,产生了基于嵌入式系统的远程图像视频监控系统,为信息产业,管理行业.监控行业提供了快捷.高效.廉价的服务.丰富的网络 ...

  6. Hi3516开发笔记(十):Qt从VPSS中获取通道图像数据存储为jpg文件

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/123536470 红胖子(红模仿)的博文大全:开发技术集 ...

  7. Java云同桌学习系列(十九)——Linux系统

    本博客java云同桌学习系列,旨在记录本人学习java的过程,并与大家分享,对于想学习java的同学,我希望这个系列能够鼓励大家一同与我学习java,成为"云同桌". 每月预计保持 ...

  8. 通信算法之五十九:SC_FDE系统中的匹配滤波与频偏纠正

    1. 匹配滤波与初始位置无影响 2.频偏纠正与初始位置无影响,初相会变化 1279682290

  9. 第十九篇:UE4基于nDisplay开发Cave空间(一)

    一年没写博客了,主要太忙了,今年完成了人生中的几件大事,哈哈哈.....好不容易闲下来,之前写过两篇使用UE4 开发Cave空间的文章,后来发现UE4 有一个nDisplay的插件可以用于Cave的开 ...

最新文章

  1. NO.18 使用MVC实现的hello world!
  2. 退出python命令行-在cmd命令行里进入和退出Python程序的方法
  3. [C#] Direct2D 学习笔记 (一)vb.net转换为c#
  4. 牛客网暑期ACM多校训练营(第九场)
  5. 【转】ABP源码分析四十:ZERO的Application和Tenant
  6. nuget 包管理器
  7. java dijkstra算法代码_[转载]Java实现dijkstra算法: 地图中任意起点寻找最佳路径...
  8. linux fcntl注销信号,linux下fcntl的使用(转载)
  9. ExecuteNonQuery()的返回值
  10. 面经 - 计算机网络知识点
  11. iOS里的动态库和静态库
  12. 区块链专利申请全球过半 厉害了我的国
  13. 如何实现微信和淘宝的扫码登录 ?
  14. html/css简单描述
  15. STM32之SPI和W25Q128
  16. 职场“女神”,绝不会有的12个习惯
  17. 《面筋:拿到美团点评和去哪儿网的offer》
  18. 浙大数据结构课后题-堆的路径
  19. python 股市 无风险套利_Python期货期权无风险套利监控升级版
  20. ubuntu16 安装 jdk11

热门文章

  1. 刚在明珠台重温了遍‘阿甘正传’
  2. LInux如何上传文件(使用Linux系统下的软件lrzsz)
  3. C:\Program Files\TortoiseSVN\bin\SendRpt.exe not found.
  4. 公司使用的是千兆光纤网络,为什么还是慢?
  5. 微信晚上服务器,腾讯解释为什么微信没有夜间模式:不忍心占用你的夜晚时间...
  6. 基于最小通视高度的通视性算法
  7. java redis缓存使用_redis缓存在项目中的使用
  8. React 基础案例 | 可折叠的问题列表和按分类展示的美食菜谱(三)
  9. Java探针--javaagent--使用/实例
  10. 心中的感慨,当前的社会太现实了