锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

激光雷达和相机联合标定

时间:2023-06-01 07:07:00 a80150激光3d轮廓传感器

前言

最近做了激光和相机的联合校准,分别使用c艹 pclpython open3d写了写linux/windows记录相应的联合标定软件。

绪论

在这里插入图片描述
该方法以标定板为基础,目的是寻求
传感器之间的外参。
小编的代码主要做了一件事,那就是选择每个传感器对应的关键点,比如激光中标定板角3d图像中标定板角的坐标和像素坐标。
整个过程分为以下四部分:
1. 提取标定板点云
2. 提取标定板点云和图像角点

  1. 解决外参矩阵
  2. 重投影验证标定结果
    ps:open3d的api查起来比较麻烦,有很多pcl或eigen在open3d手鲁了一遍

1. 提取标定板点云

1.1 初筛

采用直通滤波初步限制点云范围(甚至根据航向角限制)opencv滑动条和pcl、open3d库,我们可以在限制范围后实时调整和显示点云。
然后用统计
滤波器去除一些离群点

分别给下面python和c 的核心代码
python的
调用部分

    # 直通滤波     create_trackbar()     while 1:         xyz = get_trackbar()         point_cloud_copy = cloud_passthrough(point_cloud_numpy, xyz)         pcd.points = o3d.utility.Vector3dVector(point_cloud_copy)         vis.update_geometry(pcd) #open3d is terrible 这句话可有可无          vis.poll_events()         vis.update_renderer()         key = cv2.waitKey(1)         if key == ord('q'):             vis.remove_geometry(pcd)             break     # 统计滤波     out, _ = pcd.remove_statistical_outlier(20, 1) # parms: (统计时考虑的临近点数是否为离群点的阀值)     out.paint_uniform_color([1, 1, 1])     vis.add_geometry(out)     while 1:         vis.update_geometry(out)         vis.poll_events()         vis.update_renderer()         key = cv2.waitKey(1)
        if key == ord('q'):
            vis.remove_geometry(out)
            break

函数实现

def cloud_passthrough(cloud_np, xyz):
    x_min = xyz[0]
    x_max = xyz[1]
    y_min = xyz[2]
    y_max = xyz[3]
    z_min = xyz[4]
    z_max = xyz[5]

    cloud_np = cloud_np[cloud_np[:, 0] < x_max] 
    cloud_np = cloud_np[cloud_np[:, 0] > x_min]                    
    cloud_np = cloud_np[cloud_np[:, 1] < y_max]
    cloud_np = cloud_np[cloud_np[:, 1] > y_min]
    cloud_np = cloud_np[cloud_np[:, 2] < z_max]
    cloud_np = cloud_np[cloud_np[:, 2] > z_min]
  
    return cloud_np

c++的

while(!viewer1->wasStopped())
    { 
        
        // 判断标定板点云是否直通滤波完毕
        if(chose_bd) 
        { 
        
            chose_bd = 0;
            break;
        }
        // 每次都对原始点云进行直通滤波操作
        pcl::copyPointCloud(*cloud, *cloud_show);
        // 更新直通滤波参数
        xyzMutex.lock();
        x0 = x_min;
        x1 = x_max;
        y0 = y_min;
        y1 = y_max;
        z0 = z_min;
        z1 = z_max;
        xyzMutex.unlock();
        // 进行直通滤波
        cloudPassThrough(cloud_show, "y", y0, y1);
        cloudPassThrough(cloud_show, "x", x0, x1);
        cloudPassThrough(cloud_show, "z", z0, z1);
        // 更新可视化viewer
        viewer1->removePointCloud("test");
        pcl::visualization::PointCloudColorHandlerGenericField<pcl::PointXYZ> fildColor(cloud_show, "z");
        viewer1->addPointCloud(cloud_show,fildColor, "test");
        boost::this_thread::sleep(boost::posix_time::microseconds(1000));
	    viewer1->spinOnce(100);
    }
    // 统计滤波 去除离群点
    cloudStatisticalOutlierRemoval(cloud_show);

1.2 拟合平面并投影

初筛完标定板点云后,仍有离群点和波动存在,我们通过拟合平面,并且将原始点云投影于该平面的方法,让标定板点云在空间处于统一平面。
python的
调用部分

    # 拟合平面
    inlier_cloud = copy.copy(out)
    plane_model, inliers = get_plane(inlier_cloud)
    [a, b, c, d] = plane_model
    print(f"Plane equation: { 
          a:.2f}x + { 
          b:.2f}y + { 
          c:.2f}z + { 
          d:.2f} = 0")
    inlier_cloud = inlier_cloud.select_by_index(inliers)
    inlier_cloud.paint_uniform_color([0, 1, 0])
    vis.add_geometry(inlier_cloud)
    while 1:
        vis.update_geometry(inlier_cloud)
        vis.poll_events()
        vis.update_renderer()
        key = cv2.waitKey(1)
        if key == ord('q'):
            vis.remove_geometry(inlier_cloud)
            break
    
    # 投影滤波
    project_cloud = copy.copy(inlier_cloud)
    project_cloud.points = project_filter(project_cloud, plane_model)
    vis.add_geometry(project_cloud)
    while 1:
        vis.update_geometry(project_cloud)
        vis.poll_events()
        vis.update_renderer()
        key = cv2.waitKey(1)
        if key == ord('q'):
            #vis.remove_geometry(inlier_cloud)
            break

函数实现部分

def get_plane(pcd):
    # parms: 
    # distance_threshold
    # ransac_n
    # num_iterations
    plane_model, inliers = pcd.segment_plane(0.09, 10, 1000)
    # dis应该在0.04-0.05,但是标定板中间凹陷
    return plane_model, inliers
    
def project_filter(cloud, plane_model):
    cloud = np.asarray(cloud.points)
    [a, b, c, d] = plane_model
    dis = a*a+b*b+c*c
    x = copy.deepcopy(cloud[:, 0])
    y = copy.deepcopy(cloud[:, 1])
    z = copy.deepcopy(cloud[:, 2])
    cloud[:, 0] = ((b*b+c*c)*x - a*(b*y + c*z + d))/dis
    cloud[:, 1] = ((a*a+c*c)*y - b*(a*x + c*z + d))/dis
    cloud[:, 2] = ((b*b+a*a)*z - c*(a*x + b*z + d))/dis
    return o3d.utility.Vector3dVector(cloud)

c++de

	//-------------------------- 模型估计 --------------------------
	cout << "->正在估计平面..." << endl;
	pcl::SampleConsensusModelPlane<PointT>::Ptr model_plane(new pcl::SampleConsensusModelPlane<PointT>(cloud_show));	//选择拟合点云与几何模型
	pcl::RandomSampleConsensus<PointT> ransac(model_plane);	//创建随机采样一致性对象
	ransac.setDistanceThreshold(0.1);	//设置距离阈值,与平面距离小于0.01的点作为内点
	ransac.computeModel();				//执行模型估计
	PointCloudT::Ptr cloud_plane(new PointCloudT);	
	//---------- 根据索引提取内点 ----------
	// 方法1
	vector<int> inliers;				//存储内点索引的向量
	ransac.getInliers(inliers);			//提取内点对应的索引
	pcl::copyPointCloud<PointT>(*cloud_show, inliers, *cloud_plane);
	// 输出模型参数Ax+By+Cz+D=0
	Eigen::VectorXf coefficient;
	ransac.getModelCoefficients(coefficient);
	cout << "平面方程为:\n"
		<< coefficient[0] << "x + "
		<< coefficient[1] << "y + "
		<< coefficient[2] << "z + "
		<< coefficient[3] << " = 0"
		<< endl;
	//----------------------- 可视化拟合结果 -----------------------
	viewer1->addPointCloud<pcl::PointXYZ>(cloud_show, "cloud");												//添加原始点云
	viewer1->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 1, 1, "cloud");	//颜色
	viewer1->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "cloud");	//点的大小

	viewer1->addPointCloud<pcl::PointXYZ>(cloud_plane, "plane");											//添加平面点云
	viewer1->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "plane");	//颜色
	viewer1->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, "plane");	//点的大
    // 创建投影器
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients());
    // 由ax+by+cz+d=0设置投影面参数
    coefficients->values.resize(4);
    coefficients->values[0] = coefficient[0];
    coefficients->values[1] = coefficient[1];
    coefficients->values[2] = coefficient[2];
    coefficients->values[3] = coefficient[3];
    // 创建投影滤波对象
    pcl::ProjectInliers<pcl::PointXYZ>proj;
    // 设置对象对应的投影模型
    proj.setModelType(pcl::SACMODEL_PLANE);
    // 设置输入点云
    proj.setInputCloud(cloud_plane);
    // 设置模型对应的系数
    proj.setModelCoefficients(coefficients);
    // 执行投影滤波
    PointCloudT::Ptr cloud_filtered(new PointCloudT);	
    proj.filter(*cloud_filtered);

做到这一步,我们搞个效果视频看一下,不然太抽象了

calibration

2. 标定板点云和图像角点提取

我们想通过3d to 2d,来简化点云聚类、拟合、最终寻找到角点的过程。

2.1 点云降维

我们先将投影得到同一平面上的点云进行平移、旋转,将其移动至xoy面,
经此操作,3d点云z坐标均为0,我们可以对点云进行图像层面操作。

c++的

    // 将标定板点云旋转平移至xoy面
    PointCloudT::Ptr cloud_hhh(new PointCloudT);
    PointCloudT::Ptr cloud_rotate(new PointCloudT);
    while(!viewer1->wasStopped())
    { 
        
        // 判断投影是否完成
        if(chose_bd)
        { 
        
            viewer1->removeAllPointClouds();
            PointCloudT::Ptr cloud_hhh(new PointCloudT);
            PointCloudT::Ptr cloud_rotate(new PointCloudT);
            // 计算过原点平面法线方程
            float A = coefficient[0];
            float B = coefficient[1];
            float C = coefficient[2];
            float D = coefficient[3];
            float Z0 = -D/C;
            float x0 = cloud_filtered->points[0].x;
            float y0 = cloud_filtered->points[0].y;
            float z0 = cloud_filtered->points[0].z;
            // 将点云平面平移至原点所在平面
            for (int i=0; i<cloud_filtered->points.size(); i++)
            { 
        
                float x = cloud_filtered->points[i].x-x0;
                float y = cloud_filtered->points[i].y-y0;
                float z = cloud_filtered->points[i].z-z0;
                PointT t0 = { 
        x, y, z};
                cloud_hhh->points.push_back(t0);
            }
            // 法线可视化
            PointT a,b;
            a.x = -10*A;
            a.y = -10*B;
            a.z = -10*C;
            b.x = 10*A;
            b.y = 10*B;
            b.z = 10*C;
            viewer1->addLine<pcl::PointXYZ>(a, b, 255, 255, 255, "line1"); 
            PointCloudT::Ptr linePoint(new PointCloudT);
            linePoint->points.push_back(a);
            linePoint->points.push_back(b);
            // 将点云绕Y轴旋转
            Eigen::Affine3f transform_2=Eigen::Affine3f::Identity();
            float theta = - atan(A/C);
            cout << theta*180/M_PI << endl;
            transform_2.rotate(Eigen::AngleAxisf(theta, Eigen::Vector3f::UnitY()));
            printf ("\n Method #2: using an Affine3f \n");
            std::cout << transform_2.matrix() << std::endl;
            pcl::transformPointCloud(*cloud_hhh, *cloud_rotate, transform_2);
            // 绘制旋转后平面法线
            pcl::transformPointCloud(*linePoint, *linePoint, transform_2);
            a = linePoint->points[0];
            b = linePoint->points[1];
            viewer1->addLine<pcl::PointXYZ>(a, b, 255, 0, 0, "line2"); 
            // 将点云绕X轴旋转
            float new_B = b.y/10;
            float new_C = b.z/10;
            Eigen::Affine3f transform_1=Eigen::Affine3f::Identity();
            float theta1 = atan(new_B/new_C);
            cout << theta1*180/M_PI << endl;
            transform_1.rotate(Eigen::AngleAxisf(theta1, Eigen::Vector3f::UnitX()));
            std::cout << transform_1.matrix() << std::endl;
            pcl::transformPointCloud(*cloud_rotate, *cloud_rotate, transform_1);
            // 绘制旋转后平面法线
            pcl::transformPointCloud(*linePoint, *linePoint, transform_1);
            a = linePoint->points[0];
            b = linePoint->points[1];
            viewer1->addLine<pcl::PointXYZ>(a, b, 0, 255, 0, "line3");

python的
调用部分

    # 平移旋转至xoy面
    trans_cloud = copy.copy(project_cloud)
    trans_cloud.points, euler, xyz0 = transform(trans_cloud, plane_model)
    vis.add_geometry(trans_cloud)
    while 1:
        vis.update_geometry(trans_cloud)
        vis.poll_events()
        vis.update_re

相关文章