点云_PCL点云过滤_C++开发
C++代码
1.源文件
在一个C++工程中,并不是所有代码都会编译成可执行文件。
2.可执行文件
只有带main函数的文件才会生成可执行程序,
而另一些代码,我们只想把它们打包成一个东西,供其他程序调用,这个东西叫做库(library)
没有main函数,这意味着这个库里没有可执行文件
3.库文件
Linux中,库文件分成静态库和共享库两种。静态库以.a作为后缀名,共享库以.so结尾。
所有库都是一些函数打包后的集合,
差别在于静态库每次被调用都会生成一个副本,而共享库则只有一个副本,更省空间。
Linux系统库文件的头文件路径在usr/include,系统的下载好的库文件一般存放在/lib64/这个路径下
常见的库文件有.lib和.dll两种形式,分别是静态库和动态库。
静态库在形成exe文件时,全部被打包进去。
动态库只是在运行的时候用到的函数才会被导入到exe中,可以动态的加载和卸载
当可执行文件用到动态库的某个函数时,链接到可执行文件当中的只有这些函数入口的地址,并没有这些函数的具体实现方法。
动态链接是指在可执行文件运行之前,操作系统就会将该可执行文件运用到的动态库函数的具体实现代码从磁盘加载到内存里。
生成动态库---.发布动态库---使用动态库
4.头文件
库文件是一个压缩包,里面有编译好的二进制函数。
如果仅有.a或.so库文件,那么我们并不知道里面的函数具体是什么,调用的形式又是什么样的
为了让别人(或自己)使用那个库,我们需要提供一个头文件,说明这些库里都有些什么。
因此,对库的使用者,只要拿到了头文件和库文件。就可以调用这个库
5.hpp文件
6.CMakeLists.txt
一个小型的C++项目可能含有十几个类,各类间还存在着复杂的依赖关系。其中一部分要编译成可执行文件,另一部分编译成库文件
我们对项目的编译管理工作从输入一串g++命令,
变成了维护若干个比较直观的CMakeLists.txt文件,这将明显降低维护整个工程的难度
点云格式再回顾
PCL point Cloud Library
1)width(int) :指定了点云数据中的宽度。width有两层含义:
- 对于无序点云而言, 指的是点云的数量。
- 在有序点云中,一行点云的数量。
2)height(int) :指定了点云数据中的高度。height有两层含义:
指定有序点云中,点云行的数量。
对于无序点云,将height设为1(它的width即为点云的大小),区分点云是否有序。
Fields type size
(3)pcl::PointCloud<pcl::PointXYZRGB> //PointXYZRGB 成员:float x,y,z,rgb; 表示XYZ信息加上RGB信息,RGB存储为一个float。
(4) pcl::PointCloud<pcl::PointXYZRGBA> //PointXYZRGBA 成员:float x , y, z; uint32_t rgba; 表示XYZ信息加上RGBA信息,RGBA用32bit的int型存储的。
通过SIZE和TYPE结合来获取FIELDS字段的数据类型
SIZE
指定每个维度的大小(以字节为单位)。示例:
unsigned char/char 为 1 byte
unsigned short/short 为 2 bytes
unsigned int/int/float为 4 bytes
double 为 8 bytes
TYPE
将每个维度的类型指定为一个字符。当前接受的类型包括:
I - 表示带符号的类型 int8 (char), int16 (short), int32 (int)
U - 表示无符号类型 uint8 (unsigned char), uint16 (unsigned short), uint32 (unsigned int)
F - 表示浮点类型 float types
例如 4U 即 uint32
C++
pcl
#include <pcl/io/pcd_io.h> //PCD读写类相关的头文件
#include <pcl/point_types.h> //PCL中支持的点类型的头文件
pcl::PointCloud<pcl::PointXYZ> cloud;
pcl::io::savePCDFileASCII("test_pcd.pcd", cloud);
头文件(.h),在预处理阶段,头文件被包含到源文件后,它的使命就基本结束了。头文件包含了程序运行中可能需要用到的变量和函数等
为什么引入.hpp文件
hpp文件,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,
则该类的调用者只需要include该cpp文件即可,无需再将cpp加入到project中进行编译。
而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,
采用hpp将大幅度减少调用 project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库
同时,引入hpp文件很大一个原因就是类模板 模板被具体化(specialization)之后(用在特定的类型上),编译器才会根据具体的类型对模板进行编译
Eigen采用源码的方式提供给用户使用,其主体是一系列.hpp文件,用户在使用时只需要包含Eigen的头文件即可。
Eigen采用模板方式实现,由于模板函数不支持分离编译,所以只能提供源码与用户代码一起编译,而无法以预编译的动态库形式提供给用户使用。
另一方面,这也给用户使用带来了便利,由于Eigen只包含头文件, 可以方便地跨平台使用,而且也不需要进行复杂的环境配置,
美中不足的是,由于大量使用模板而带来的编译时间延长
Eigen
sudo apt install libeigen3-dev
#include <Eigen>
#include <eigen3/Eigen/Dense>
代码示例
##安装
##cmake编译配置
cmake_minimum_required(VERSION 3.10.0)
project(pcd_test)
add_compile_options(-std=c++11)
set(CMAKE_BUILD_TYPE "Release") #Release
find_package(PCL REQUIRED)
include_directories(${PCL_INCLUDE_DIRS})
add_definitions(${PCL_DEFINITIONS})
link_directories(${PCL_LIBRARY_DIRS})
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})
include_directories(include)
add_executable(main src/main.cpp)
target_link_libraries(main ${PCL_LIBRARIES} )
###代码示例
#include <iostream>
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/crop_box.h>
using namespace std;
int main(int argc,char** argv) {
std::cout << "Hello, World!" << std::endl;
if(argc<2){
std::cout<<"参数个数太少: ./demo_voxel_grid input.pcd "<<std::endl;
return -1;
}
//创建cloud_in输入结果
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_in(new pcl::PointCloud<pcl::PointXYZI>);
//创建cloud_out保存结果
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filtered(new pcl::PointCloud<pcl::PointXYZI>);
//loadPCDFile实际上是调用PCDReader
std::cout<<argv[1]<<std::endl;
pcl::io::loadPCDFile(argv[1],*cloud_in);
std::cerr<<"PointCloud before filtering: "<<cloud_in->size()<<" data points ("<<pcl::getFieldsList(*cloud_in)<<" )"<<std::endl;
//cerr输出为红色
//体素滤波
//创建体素网格采样处理对象
pcl::VoxelGrid<pcl::PointXYZI> sor;
//设置输入点云
sor.setInputCloud(cloud_in);
//设置体素大小,单位:m
sor.setLeafSize(0.25,0.25,0.25);
//进行下采样
sor.filter(*cloud_filtered);
std::cout<<"voxel filter succeed!"<<std::endl;
//getFieldsList(*cloud_filtered) 获取点的field(类似于维度)
std::cerr<<"PointCloud after filtering: "<<cloud_filtered->size()<<" data points ("<<pcl::getFieldsList(*cloud_filtered)<<" )"<<std::endl;
// 创建滤波器对象--直通滤波
pcl::PassThrough<pcl::PointXYZI> pass;//创建滤波器对象
pass.setInputCloud (cloud_filtered); //设置待滤波的点云
pass.setFilterFieldName ("intensity"); //设置在Z轴方向上进行滤波
pass.setFilterLimits (0.0, 50); //设置滤波范围为0~1,在范围之外的点会被剪除
//pass.setFilterLimitsNegative (true);//是否反向过滤,默认为false
pass.filter (*cloud_filtered);
std::cerr<<"PointCloud after filtering: "<<cloud_filtered->size()<<" data points ("<<pcl::getFieldsList(*cloud_filtered)<<" )"<<std::endl;
// 创建滤波器对象--直通滤波
pcl::PassThrough<pcl::PointXYZI> zFilter;//创建滤波器对象
zFilter.setInputCloud (cloud_filtered); //设置待滤波的点云
zFilter.setFilterFieldName ("z"); //设置在Z轴方向上进行滤波
zFilter.setFilterLimits (-5, 1); //设置滤波范围为0~1,在范围之外的点会被剪除
//pass.setFilterLimitsNegative (true);//是否反向过滤,默认为false
zFilter.filter (*cloud_filtered);
std::cerr<<"PointCloud after filtering: "<<cloud_filtered->size()<<" data points ("<<pcl::getFieldsList(*cloud_filtered)<<" )"<<std::endl;
// 创建滤波器对象
pcl::CropBox<pcl::PointXYZI> cropBoxFilter;//创建滤波器对象
//立方体过滤
// 定义立方体框:指定框的长宽高及其相对于坐标系的位置和姿态
int radius = 150;
Eigen::Vector4f min_point(-radius,-radius,-radius,1.0);
Eigen::Vector4f max_point(radius,radius,radius,1.0);
cropBoxFilter.setMin(min_point);
cropBoxFilter.setMax(max_point);
//clipper.setTranslation (translation);
cropBoxFilter.setInputCloud(cloud_filtered);
cropBoxFilter.setNegative(false);//true是将盒子内的点去除,反之保留, 默认为false.
cropBoxFilter.filter(*cloud_filtered);
std::cerr<<"PointCloud after filtering: "<<cloud_filtered->size()<<" data points ("<<pcl::getFieldsList(*cloud_filtered)<<" )"<<std::endl;
pcl::io::savePCDFile("test.pcd",*cloud_filtered,true);
std::cout<<"save succeed!"<<std::endl;
return 0;
}
点云补偿
迭代最近点(ICP)方法是采用ICP算法来匹配两个点云,通过不断地算法迭代后,将点云之间的误差缩至最小
惯性测量单元(IMU)方法是在IMU队列中查找相邻两帧IMU的数据,然后通过球面线性插值的方式计算扫描点所在时刻的激光雷达位姿,
并应用齐次坐标系变化将两个点云坐标变换至同一坐标系下
轮式里程计(ODOM)方法
点云组帧
帧间匹配,也叫子图匹配 与 高精地图匹配
发了NanoGICP,这是一个自定义迭代最近点解算器,
本质上是将开源的FastGICP和NanoFLANN两个包进行合并,
使用NanoGICP的 NanoFLANN来高效构建KD-tree, 然后通过FastGICP进行高效匹配
数据级融合
予点云(数据级融合):将点云投影到图像,提取图像RGB信息,反投影到LiDAR空间,发布着色的点云
要求:相机内参,相机-激光雷达外参,校正图像
将点云文件投影到图片上形成深度图
RGB+ 深度--投影到点云
获取图像上的RGB
print(pro_y[0], pro_x[0]) # 打印坐标
img = cv2.imread(img_path)[:,:,:3]
B,G,R = img[pro_y[0], pro_x[0], :]
rgb = np.array((rgb[:, 0] << 16) | (rgb[:, 1] << 8) | (rgb[:, 2] << 0),
dtype=np.uint32)
open3d color 点云的RGB颜色。数值类型为float64, 范围为[0, 1], 若用户输入的值越界则被截断
换算
r = int(data/256**2%256)
g = int(data/256%256)
b = int(data%256)
由r g b得到rgb(float):int rgb = ((int)r << 16 | (int)g << 8 | (int)b); 式中的“<<”为左移符号;
rgb = R<<16 | G<<8 | B | 1<<24
np.bitwise_or(a,b)
c++中,移位运算符有双目移位运算符:<<(左移)和>>(右移)。移位运算符组成的表达式也属于算术表达式,其值为算术值
C++ 的逻辑运算符
C++按位与、或、异或运算
“与”运算是计算机中一种基本的逻辑运算方式,符号表示为&,与之相对应的词是“或”,符号为 |注:“与” 和 “或” 不是逆运算
frgb=((int)array[m][3]<<16|(int)array[m][4]<<8|(int)array[m][5]);
cloud.points[m].rgb=*reinterpret_cast<float*>(&frgb);//frgb在这由int型转换成了float型。
int rgb = ((int)r) << 16 | ((int)g) << 8 | ((int)b);
paint_uniform_color:将点云进行单一RGB颜色赋值,R、G、B分量的范围为[0,1]
pcd文件中的格式为x y z rgb而不是分开的r g b,所以在原数据的基础上要进行变形,
// unpack rgb into r/g/b
uint32_t rgb = *reinterpret_cast<int*>(&p.rgb);
uint8_t r = (rgb >> 16) & 0x0000ff;
uint8_t g = (rgb >> 8) & 0x0000ff;
uint8_t b = (rgb) & 0x0000ff;
r = np.asarray((rgb >> 16) & 0xFF, dtype=np.uint8)
g = np.asarray((rgb >> 8) & 0xFF, dtype=np.uint8)
b = np.asarray(rgb & 0xFF, dtype=np.uint8)
rgb_arr = np.zeros((len(rgb), 3), dtype=np.uint8)
3)PointXYZRGBA
pcl::PointCloud<pcl::PointXYZRGBA>
成员变量: float x, y, z; uint32_t rgba;
表示XYZ信息加上RGBA信息,RGBA用32bit的int型存储的。
4)PointXYZRGB
pcl::PointCloud<pcl::PointXYZRGB>
成员变量: float x, y, z, rgb; 表示XYZ信息加上RGB信息,RGB存储为一个float。
pcd = o3d.geometry.create_point_cloud_from_rgbd_image(
/注意opencv 中Scalar(B,G,R)不是R,G,B这个顺序
测绘信息
基岩点、基本点、普通点和验潮站
控制点分平面控制点和高程控制点,
平面控制点又分为三角点、导线点、GPS点等多种类型,
高程控制点按观测方法分有水准点和三角高程点,按等级分有一、二、三、四等和等外级
分三角点、导线点、水准点和天文点等。每点均具有高程和平面直角坐标(或经纬度)的数据
精度在毫米级
规划局提供的控制点(从测量技术上讲,一般要求三个及以上)为基准
测绘基准点,而这些基准点可以用来校正GPS,且对外保密
高程基准点是规定地球表面的标高的测量原点
基准站观测数据
作战指挥工程和重要军事设施区域内的基准站的观测数据为机密级国家秘密事项;
其他军事禁区内的基准站的观测数据为秘密级国家秘密事项;
普通基准站的观测数据不属于国家秘密事项,但属于受控管理的内容。
点云取色
# #删除深度x=0的点避免前后覆盖
camera_array = np.delete(camera_array,np.where(camera_array[2,:]<0),axis=1)
### 值设置为1
camera_array[:,(camera_array[2:]<0)]
IndexError: boolean index did not match indexed array test dimension 0
值进行比较-需要括号
mask = (r < rt) & (g < gt) & (b < bt)
image[mask] = 0
点云压缩
点云几何压缩方法多基于树结构或块结构组织划分点云,通过对结构化的点云进行描述与编码,
一种基于八叉树的几何编码方法,首先,在相邻帧之间作异或运算;
然后,用八叉树编码运算的结果。
该方法既能取得较高的压缩效率,也能有效降低编码计算复杂度,
是点云库(point cloud library,PCL)中推荐的点云压缩框架算法
库文件
C++11提供了chrono模版库,实现了一系列时间相关的操作(时间长度、系统时间和计时器)。
头文件:#include <chrono>
命名空间:std::chrono
参考
点云下采样 https://www.cnblogs.com/yibeimingyue/p/15592088.html
关于点云畸变与 LiDAR imagery 表示 https://www.zhihu.com/tardis/bd/art/478937479
激光点云系列之二:激光雷达点云处理中遇到的问题及对策 https://zhuanlan.zhihu.com/p/608102704
激光点云系列之一:详解激光雷达点云数据的处理过程
PCL体素滤波(VoxelGrid filter)进行下采样及其可视化 https://blog.51cto.com/u_14355665/6099248
源码 https://github.com/vectr-ucla/direct_lidar_odometry
作为package https://github.com/Toushine/Nano-GICP
经典文献阅读之--DLO https://blog.51cto.com/u_13157605/6038059
生成带有rgb值的pcd文件并显示 https://blog.csdn.net/wkxxuanzijie920129/article/details/51433626
将rgb颜色编码成一个float32浮点数 https://blog.csdn.net/B_DATA_NUIST/article/details/105835142
https://github.com/PointCloudLibrary/pcl
(学习笔记)制作一个简单的C++工程 https://blog.csdn.net/qq_59475883/article/details/123152360