精华内容
下载资源
问答
  • lidar 相机标定
    2022-06-25 11:32:21

    综述

    标定问题也是一种位姿估计问题,它本质上和各种激光里程计和视觉里程计解决的是一样的问题。标定采用的办法也可以在里程计问题中借鉴。它们都有着共同的流程:

    a.特征提取

    b.特征匹配

    c.位姿求解

    参考论文: Pixel-level Extrinsic Self Calibration of High Resolution LiDAR and Camera in Targetless Environments

    github: https://github.com/hku-mars/livox_camera_calib

    这篇论文是著名的港大Mars Lab出品,他们解决的是livox和camera的标定问题。并且,不需要在环境里另外放置仍何类似于标定板或者二维码这样的先验物体!论文很长,很多篇幅在介绍他们这么做的理由,这部分我们跳过,只看他们在代码里是怎么做的。

    1. camera image的特征提取

    提取鲁棒的,高度辨识度的特征是最重要的一点。相机标定的时候,我们会用棋盘格,因为它特征明显,结构已知,能很容易在不同照片上实现数据关联。对于lidar和image而言,他们的数据是不一样的,如何找到可以匹配对的特征是第一个难题,所以他们选择了线特征。在照片上提取线特征,有opencv的办法,也有learning-based(例子)的办法。这篇论文2D线特征提取的办法比较简单,他们就用了opencv自带的canny算法,canny只能提取照片中的边缘信息,也就是说,只能告诉你哪个像素是不是直线点,但是不能告诉你这个像素属于哪个直线。一般要在照片上识别出不同的直线,要在canny的基础上,再利用hough算法或者LSD算法进一步提取直线,这几个算法都被opencv内置了。

    void Calibration::edgeDetector(
        const int &canny_threshold, const int &edge_threshold,
        const cv::Mat &src_img, cv::Mat &edge_img,
        pcl::PointCloud<pcl::PointXYZ>::Ptr &edge_cloud)
    {
        //高斯模糊
        int gaussian_size = 5;
        cv::GaussianBlur(src_img, src_img, cv::Size(gaussian_size, gaussian_size), 0, 0);
    
        //提取边缘像素
        cv::Mat canny_result = cv::Mat::zeros(height_, width_, CV_8UC1);
        cv::Canny(src_img, canny_result, canny_threshold, canny_threshold * 3, 3, true);
        
        //对这些直线点进行分组
        std::vector<std::vector<cv::Point>> contours;
        std::vector<cv::Vec4i> hierarchy;
        cv::findContours(canny_result, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));
    
        //如果某一组的点数过少,那就不要,这相当于过滤操作
        edge_img = cv::Mat::zeros(height_, width_, CV_8UC1);
        edge_cloud = pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
        for (size_t i = 0; i < contours.size(); i++)//contours.size()
        {
            if (contours[i].size() > edge_threshold)
            {
                for (size_t j = 0; j < contours[i].size(); j++)
                {
                    //what the use of p ???????
                    pcl::PointXYZ p;
                    p.x = contours[i][j].x;
                    p.y = -contours[i][j].y;
                    p.z = 0;
                    edge_img.at<uchar>(-p.y, p.x) = 255;
                }
            }
        }
    
        //然后把直线点的像素坐标保存到pcl点云里
        //edge_cloud is the real output
        for (int x = 0; x < edge_img.cols; x++)
        {
            for (int y = 0; y < edge_img.rows; y++)
            {
                if (edge_img.at<uchar>(y, x) == 255)
                {
                    pcl::PointXYZ p;
                    p.x = x;
                    p.y = -y; //TODO -y?
                    p.z = 0;
                    edge_cloud->points.push_back(p);
                }
            }
        }
        edge_cloud->width = edge_cloud->points.size();
        edge_cloud->height = 1;
    }

    在代码里,他们先对照片进行gaussblur去除噪声,然后进行canny提取边缘像素。他们最后之所以把像素保存到pcl的点云里,用这些点构造了以后一个kdtree,是因为他们在进行特征匹配的时候要借用pcl的kdtree找最近邻。

    2. lidar cloud的特征提取

    接下来,问题变成了如何在点云中找到属于线特征的那些点。一般来说,在3D 点云中提线,需要先提面,面和面之间的交线就是直线了。github上也有点云提线的代码,例如这篇,但是提的线的位置不是特别的准。

    pcl有现成的面分割算法,也是用ransac的方式去拟合一个个平面。但是如果点云中包含的面比较多,那么ransac就会失效。所以这个代码里,他们把点云分成边长1m的体素,对于每一个体素,里面包含的平面就不会很多,减少错误的概率。然后用pcl的分割器采用RANSAC的方式提平面,保留那些相交且夹角30-150度的平面,提取交线。论文中还有一个depth-continuous edge的概念。这个概念在论文里花了很大篇幅介绍。大概的意思是说提取的直线并不是整段都要,只保留离模型点云近的那一段段小线段,如果交线的某个位置离两个平面点云都很近,那么就会被选取。这些小线段就是论文中 depth-continuous edge,代码里其实非常简短。

    所以说,标定的时候,你不能在荒郊野外深山老林,周围环境里最好有一些棱角分明的建筑,比如说港大校园。

    void Calibration::LiDAREdgeExtraction(
        const std::unordered_map<VOXEL_LOC, Voxel *> &voxel_map,
        const float ransac_dis_thre, const int plane_size_threshold, //0.02, 60
        pcl::PointCloud<pcl::PointXYZI>::Ptr &lidar_line_cloud_3d)
    {
        ROS_INFO_STREAM("Extracting Lidar Edge");
        ros::Rate loop(5000);
        lidar_line_cloud_3d = pcl::PointCloud<pcl::PointXYZI>::Ptr(new pcl::PointCloud<pcl::PointXYZI>);
    
        for (auto iter = voxel_map.begin(); iter != voxel_map.end(); iter++)
        {
            if (iter->second->cloud->size() > 50)
            {
                std::vector<Plane> plane_list;
                //1.创建一个体素滤波器
                pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_filter(new pcl::PointCloud<pcl::PointXYZI>);
                pcl::copyPointCloud(*iter->second->cloud, *cloud_filter);
    
                //创建一个模型参数对象,用于记录结果
                pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    
                // inliers表示误差能容忍的点,记录点云序号
                pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    
                //创建一个分割器
                pcl::SACSegmentation<pcl::PointXYZI> seg;
    
                // Optional,设置结果平面展示的点是分割掉的点还是分割剩下的点
                seg.setOptimizeCoefficients(true);
    
                // Mandatory-设置目标几何形状
                seg.setModelType(pcl::SACMODEL_PLANE);
    
                //分割方法:随机采样法
                seg.setMethodType(pcl::SAC_RANSAC);
    
                //设置误差容忍范围,也就是阈值
                if (iter->second->voxel_origin[0] < 10)
                {
                    seg.setDistanceThreshold(ransac_dis_thre);
                }
                else
                {
                    seg.setDistanceThreshold(ransac_dis_thre);
                }
    
                //2.点云分割,提取平面
                pcl::PointCloud<pcl::PointXYZRGB> color_planner_cloud;
                int plane_index = 0;
                while (cloud_filter->points.size() > 10)
                {
                    pcl::PointCloud<pcl::PointXYZI> planner_cloud;
                    pcl::ExtractIndices<pcl::PointXYZI> extract;
    
                    //输入点云
                    seg.setInputCloud(cloud_filter);
                    seg.setMaxIterations(500);
    
                    //分割点云
                    seg.segment(*inliers, *coefficients);
                    if (inliers->indices.size() == 0)
                    {
                        ROS_INFO_STREAM("Could not estimate a planner model for the given dataset");
                        break;
                    }
                    extract.setIndices(inliers);
                    extract.setInputCloud(cloud_filter);
                    extract.filter(planner_cloud);
    
                    if (planner_cloud.size() > plane_size_threshold)
                    {
                        pcl::PointCloud<pcl::PointXYZRGB> color_cloud;
                        std::vector<unsigned int> colors;
                        colors.push_back(static_cast<unsigned int>(rand() % 256));
                        colors.push_back(static_cast<unsigned int>(rand() % 256));
                        colors.push_back(static_cast<unsigned int>(rand() % 256));
                        pcl::PointXYZ p_center(0, 0, 0);
                        for (size_t i = 0; i < planner_cloud.points.size(); i++)
                        {
                            pcl::PointXYZRGB p;
                            p.x = planner_cloud.points[i].x;
                            p.y = planner_cloud.points[i].y;
                            p.z = planner_cloud.points[i].z;
                            p_center.x += p.x;
                            p_center.y += p.y;
                            p_center.z += p.z;
                            p.r = colors[0];
                            p.g = colors[1];
                            p.b = colors[2];
                            color_cloud.push_back(p);
                            color_planner_cloud.push_back(p);
                        }
                        p_center.x = p_center.x / planner_cloud.size();
                        p_center.y = p_center.y / planner_cloud.size();
                        p_center.z = p_center.z / planner_cloud.size();
                        Plane single_plane;
                        single_plane.cloud = planner_cloud;
                        single_plane.p_center = p_center;
                        single_plane.normal << coefficients->values[0], coefficients->values[1], coefficients->values[2];
                        single_plane.index = plane_index;
                        plane_list.push_back(single_plane);
                        plane_index++;
                    }
    
                    //3.提取平面后剩下的点云, unused
                    extract.setNegative(true);
                    pcl::PointCloud<pcl::PointXYZI> cloud_f;
                    extract.filter(cloud_f);
                    *cloud_filter = cloud_f;
                }
    
                if (plane_list.size() >= 2)
                {
                    sensor_msgs::PointCloud2 planner_cloud2;
                    pcl::toROSMsg(color_planner_cloud, planner_cloud2);
                    planner_cloud2.header.frame_id = "livox";
                    planner_cloud_pub_.publish(planner_cloud2);
                    //loop.sleep();
                }
    
                //4.获取平面交线点云
                std::vector<pcl::PointCloud<pcl::PointXYZI>> line_cloud_list;
                calcLine(plane_list, voxel_size_, iter->second->voxel_origin, line_cloud_list);
    
                // ouster 5,normal 3
                //if contains too many lines, it is more likely to have fake lines
                if (line_cloud_list.size() > 0 && line_cloud_list.size() <= 8)
                {
                    for (size_t cloud_index = 0; cloud_index < line_cloud_list.size(); cloud_index++)
                    {
                        for (size_t i = 0; i < line_cloud_list[cloud_index].size(); i++)
                        {
                            pcl::PointXYZI p = line_cloud_list[cloud_index].points[i];
                            plane_line_cloud_->points.push_back(p);
                            sensor_msgs::PointCloud2 pub_cloud;
                            pcl::toROSMsg(line_cloud_list[cloud_index], pub_cloud);
                            pub_cloud.header.frame_id = "livox";
                            line_cloud_pub_.publish(pub_cloud);
                            //loop.sleep();
                            plane_line_number_.push_back(line_number_);
                        }
                        line_number_++;
                    }
                }
            }
        }
    }

    把一个体素范围内的平面都分割出来以后,求直线就比较暴力了。因为一个体素里总共也没几个平面,那就俩俩之间暴力求交线就好,这部分代码在这个函数里:

    void Calibration::calcLine(
        const std::vector<Plane> &plane_list, const double voxel_size,
        const Eigen::Vector3d origin,
        std::vector<pcl::PointCloud<pcl::PointXYZI>> &line_cloud_list)
    {
        ...
        //5. if current location is close to both 2 plane clouds, so the point is on the depth contiuous edge
        if ((dis1 < min_line_dis_threshold_*min_line_dis_threshold_ && dis2 < max_line_dis_threshold_*max_line_dis_threshold_) || (dis1 < max_line_dis_threshold_*max_line_dis_threshold_ && dis2 < min_line_dis_threshold_*min_line_dis_threshold_))
        {
             line_cloud.push_back(p);
        }
        ...
    
    }

    那个if的判断就是关于depth-continuous edge。

    3. lidar-camera特征关联

    无论是点云中提取的3D线,还是image中提取的2D线,作者并没有用类似于Ax+By+Cz+D=0的形式去描述,仍然是以点的形式保存的。当3D线点重投影到平面时,就用image像素点构造的kdtree查询最近临作为一组关联的数据,不去用各种trick,简单高效。代码里,void Calibration::buildVPnp()是构造特征匹配的函数。

    //find the correspondance of 3D points and 2D points with their directions
    void Calibration::buildVPnp(
        const Vector6d &extrinsic_params, const int dis_threshold,
        const bool show_residual,
        const pcl::PointCloud<pcl::PointXYZ>::Ptr &cam_edge_cloud_2d,
        const pcl::PointCloud<pcl::PointXYZI>::Ptr &lidar_line_cloud_3d,
        std::vector<VPnPData> &pnp_list)
    {
        //1.initialize containers for 3D lines: for each pixel there is a corresponding cloud
        //because some closed 3D points may preject onto same pixel, so the container helpes to calculate averagy value
        pnp_list.clear();
        std::vector<std::vector<std::vector<pcl::PointXYZI>>> img_pts_container;
        for (int y = 0; y < height_; y++) 
        {
            std::vector<std::vector<pcl::PointXYZI>> row_pts_container;
            for (int x = 0; x < width_; x++)
            {
                std::vector<pcl::PointXYZI> col_pts_container;
                row_pts_container.push_back(col_pts_container);
            }
            img_pts_container.push_back(row_pts_container);
        }
    
        //2.get 3D lines, initial pose, intrinsics
        std::vector<cv::Point3d> pts_3d;
        Eigen::AngleAxisd rotation_vector3;
        rotation_vector3 = Eigen::AngleAxisd(extrinsic_params[0], Eigen::Vector3d::UnitZ()) *
                           Eigen::AngleAxisd(extrinsic_params[1], Eigen::Vector3d::UnitY()) *
                           Eigen::AngleAxisd(extrinsic_params[2], Eigen::Vector3d::UnitX());
    
        for (size_t i = 0; i < lidar_line_cloud_3d->size(); i++)
        {
            pcl::PointXYZI point_3d = lidar_line_cloud_3d->points[i];
            pts_3d.emplace_back(cv::Point3d(point_3d.x, point_3d.y, point_3d.z));
        }
        cv::Mat camera_matrix    = (cv::Mat_<double>(3, 3) << fx_, 0.0, cx_, 0.0, fy_, cy_, 0.0, 0.0, 1.0);
        cv::Mat distortion_coeff = (cv::Mat_<double>(1, 5) << k1_, k2_, p1_, p2_, k3_);
        cv::Mat r_vec            = (cv::Mat_<double>(3, 1)
                                 << rotation_vector3.angle() * rotation_vector3.axis().transpose()[0],
                                    rotation_vector3.angle() * rotation_vector3.axis().transpose()[1],
                                    rotation_vector3.angle() * rotation_vector3.axis().transpose()[2]);
        cv::Mat t_vec            = (cv::Mat_<double>(3, 1) << extrinsic_params[3], extrinsic_params[4], extrinsic_params[5]);
    
        // debug
        // std::cout << "camera_matrix:" << camera_matrix << std::endl;
        // std::cout << "distortion_coeff:" << distortion_coeff << std::endl;
        // std::cout << "r_vec:" << r_vec << std::endl;
        // std::cout << "t_vec:" << t_vec << std::endl;
        // std::cout << "pts 3d size:" << pts_3d.size() << std::endl;
    
        //3.project 3d-points into image view and fall into the container, look out these info are from 3D lines
        std::vector<cv::Point2d> pts_2d;
        cv::projectPoints(pts_3d, r_vec, t_vec, camera_matrix, distortion_coeff, pts_2d);
        pcl::PointCloud<pcl::PointXYZ>::Ptr line_edge_cloud_2d(new pcl::PointCloud<pcl::PointXYZ>);
        std::vector<int> line_edge_cloud_2d_number;
        for (size_t i = 0; i < pts_2d.size(); i++)
        {
            pcl::PointXYZ p;
            p.x = pts_2d[i].x;
            p.y = -pts_2d[i].y;
            p.z = 0;
            pcl::PointXYZI pi_3d;
            pi_3d.x = pts_3d[i].x;
            pi_3d.y = pts_3d[i].y;
            pi_3d.z = pts_3d[i].z;
            pi_3d.intensity = 1;
            if (p.x > 0 && p.x < width_ && pts_2d[i].y > 0 && pts_2d[i].y < height_)
            {
                if (img_pts_container[pts_2d[i].y][pts_2d[i].x].size() == 0)
                {
                    line_edge_cloud_2d->points.push_back(p);
                    line_edge_cloud_2d_number.push_back(plane_line_number_[i]);
                    img_pts_container[pts_2d[i].y][pts_2d[i].x].push_back(pi_3d);
                }
                else
                {
                    img_pts_container[pts_2d[i].y][pts_2d[i].x].push_back(pi_3d);
                }
            }
        }
        if (show_residual)
        {
            cv::Mat residual_img = getConnectImg(dis_threshold, cam_edge_cloud_2d, line_edge_cloud_2d);
            cv::imshow("residual", residual_img);
            cv::waitKey(50);
        }
    
        //4.build kdtree to find the closest 2D point of each 3D point
        pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>());
        pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree_lidar(new pcl::search::KdTree<pcl::PointXYZ>());
    
        pcl::PointCloud<pcl::PointXYZ>::Ptr search_cloud     = pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr tree_cloud       = pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
        pcl::PointCloud<pcl::PointXYZ>::Ptr tree_cloud_lidar = pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
    
        kdtree->setInputCloud(cam_edge_cloud_2d);
        kdtree_lidar->setInputCloud(line_edge_cloud_2d);
        tree_cloud = cam_edge_cloud_2d;
        tree_cloud_lidar = line_edge_cloud_2d;
        search_cloud = line_edge_cloud_2d;
    
        // 指定近邻个数
        int K = 5;
    
        // 创建两个向量,分别存放近邻的索引值、近邻的中心距
        std::vector<int> pointIdxNKNSearch(K);
        std::vector<float> pointNKNSquaredDistance(K);
        std::vector<int> pointIdxNKNSearchLidar(K);
        std::vector<float> pointNKNSquaredDistanceLidar(K);
    
        int match_count = 0;
        double mean_distance;
        int line_count = 0;
    
        std::vector<cv::Point2d> lidar_2d_list;
        std::vector<cv::Point2d> img_2d_list;
        std::vector<Eigen::Vector2d> camera_direction_list;
        std::vector<Eigen::Vector2d> lidar_direction_list;
        std::vector<int> lidar_2d_number;
    
        //scan each 3D point
        for (size_t i = 0; i < search_cloud->points.size(); i++)
        {
            pcl::PointXYZ searchPoint = search_cloud->points[i];
            if ((kdtree      ->nearestKSearch(searchPoint, K, pointIdxNKNSearch,      pointNKNSquaredDistance) > 0) &&
                (kdtree_lidar->nearestKSearch(searchPoint, K, pointIdxNKNSearchLidar, pointNKNSquaredDistanceLidar) > 0))
            {
                bool dis_check = true;
                for (int j = 0; j < K; j++)
                {
                    float distance = sqrt(pow(searchPoint.x - tree_cloud->points[pointIdxNKNSearch[j]].x, 2) +
                                          pow(searchPoint.y - tree_cloud->points[pointIdxNKNSearch[j]].y, 2));
                    if (distance > dis_threshold)
                    {
                        dis_check = false;
                    }
                }
    
                //5.calculate the direction of 3D lines and 2D lines on pixel frame
                //if the distances with all 5 closest 2D points is <20
                if (dis_check)
                {
                    Eigen::Vector2d direction_cam(0, 0);
                    std::vector<Eigen::Vector2d> points_cam;
                    for (size_t i = 0; i < pointIdxNKNSearch.size(); i++)
                    {
                        Eigen::Vector2d p(tree_cloud->points[pointIdxNKNSearch[i]].x, tree_cloud->points[pointIdxNKNSearch[i]].y);
                        points_cam.push_back(p);
                    }
                    calcDirection(points_cam, direction_cam);
    
                    Eigen::Vector2d direction_lidar(0, 0);
                    std::vector<Eigen::Vector2d> points_lidar;
                    for (size_t i = 0; i < pointIdxNKNSearch.size(); i++)
                    {
                        Eigen::Vector2d p(tree_cloud_lidar->points[pointIdxNKNSearchLidar[i]].x, tree_cloud_lidar->points[pointIdxNKNSearchLidar[i]].y);
                        points_lidar.push_back(p);
                    }
                    calcDirection(points_lidar, direction_lidar);
                    // direction.normalize();
    
                    cv::Point p_l_2d(search_cloud->points[i].x, -search_cloud->points[i].y);
                    cv::Point p_c_2d(tree_cloud->points[pointIdxNKNSearch[0]].x, -tree_cloud->points[pointIdxNKNSearch[0]].y);
                    if (checkFov(p_l_2d))
                    {
                        lidar_2d_list.push_back(p_l_2d);                          //projected point of the 3D point
                        img_2d_list.push_back(p_c_2d);                            //corresponding 2D point
                        camera_direction_list.push_back(direction_cam);           //direction of corresponding 2D point
                        lidar_direction_list.push_back(direction_lidar);          //direction of the projected point of the 3D point
                        lidar_2d_number.push_back(line_edge_cloud_2d_number[i]);  //the idx of each 3D lines
                    }
                }
            }
        }
    
        //6.build correspondance
        for (size_t i = 0; i < lidar_2d_list.size(); i++)
        {
            int y = lidar_2d_list[i].y;
            int x = lidar_2d_list[i].x;
            int pixel_points_size = img_pts_container[y][x].size();
            if (pixel_points_size > 0)
            {
                VPnPData pnp;
                pnp.x = 0;
                pnp.y = 0;
                pnp.z = 0;
    
                //corresponding 2D point
                pnp.u = img_2d_list[i].x;
                pnp.v = img_2d_list[i].y;
    
                //3D point (averaged), TODO what if they are far to each other?
                for (size_t j = 0; j < pixel_points_size; j++)
                {
                    pnp.x += img_pts_container[y][x][j].x;
                    pnp.y += img_pts_container[y][x][j].y;
                    pnp.z += img_pts_container[y][x][j].z;
                }
                pnp.x = pnp.x / pixel_points_size;
                pnp.y = pnp.y / pixel_points_size;
                pnp.z = pnp.z / pixel_points_size;
    
                //direction of corresponding 2D point
                pnp.direction = camera_direction_list[i];
    
                //direction of the projected point of the 3D point
                pnp.direction_lidar = lidar_direction_list[i];
    
                //the idx of the 3D line which the 3D point belongs to
                pnp.number = lidar_2d_number[i];
    
                float theta = pnp.direction.dot(pnp.direction_lidar);
                if (theta > direction_theta_min_ || theta < direction_theta_max_) //-30-+30, 150-270 deg.
                {
                    pnp_list.push_back(pnp);
                }
            }
        }
    }

    对于3D 点云的每一个线,把它的点都根据当前外参Tcl的初值,内参K投影到image上,获得一个像素坐标,然后查询kdree找到最近的几个刚才通过canny算法提取的2D像素,用这几个像素可以计算出一个均值,直线向量,就可以获得了一组特征匹配了:

    3D点+2D点/2D点向量。

    归根到底,获得的还是(lidar)点和(image)点之间的匹配关系,当然了也可以认为是(lidar)点和(image)线的匹配关系,因为后者保存了直线向量。也有别的方案保存的3D线和2D线都是以两个端点描述的,是真正意义上的(lidar)线和(image)线之间的匹配。

    4. 优化模型

    损失函数就是常见的点-线距离或者点-点距离了。对于3D点,根据待优化Tcl转到cam系,转成像素坐标,去畸变,在和匹配的2D点求出像素距离作为residual。

        vpnp_calib(VPnPData p) {pd = p;}
    
        template <typename T>
        bool operator()(const T *_q, const T *_t, T *residuals) const
        {
            //camera param
            Eigen::Matrix<T, 3, 3> innerT  = inner.cast<T>();
            Eigen::Matrix<T, 4, 1> distorT = distor.cast<T>();
            const T &fx = innerT.coeffRef(0, 0);
            const T &cx = innerT.coeffRef(0, 2);
            const T &fy = innerT.coeffRef(1, 1);
            const T &cy = innerT.coeffRef(1, 2);
    
            //initial value of Tcl
            Eigen::Quaternion<T>   q_incre{_q[3], _q[0], _q[1], _q[2]};
            Eigen::Matrix<T, 3, 1> t_incre{_t[0], _t[1], _t[2]};
    
            //project 3D point onto pixel frame
            Eigen::Matrix<T, 3, 1> p_l(T(pd.x), T(pd.y), T(pd.z));
            Eigen::Matrix<T, 3, 1> p_c = q_incre.toRotationMatrix() * p_l + t_incre;
            Eigen::Matrix<T, 3, 1> p_2 = innerT * p_c;
            T uo = p_2[0] / p_2[2];
            T vo = p_2[1] / p_2[2];
    
            //undistort
            T xo = (uo - cx) / fx;
            T yo = (vo - cy) / fy;
            T r2 = xo * xo + yo * yo;
            T r4 = r2 * r2;
            T distortion = 1.0 + distorT[0] * r2 + distorT[1] * r4;
            T xd = xo * distortion + (distorT[2] * xo * yo + distorT[2] * xo * yo) + distorT[3] * (r2 + xo * xo + xo * xo);
            T yd = yo * distortion +  distorT[3] * xo * yo + distorT[3] * xo * yo  + distorT[2] * (r2 + yo * yo + yo * yo);
            T ud = fx * xd + cx;
            T vd = fy * yd + cy;
    
            if (T(pd.direction(0)) == T(0.0) && T(pd.direction(1)) == T(0.0))
            {
                residuals[0] = ud - T(pd.u);
                residuals[1] = vd - T(pd.v);
            }
            else
            {
                residuals[0] = ud - T(pd.u);
                residuals[1] = vd - T(pd.v);
    
                Eigen::Matrix<T, 2, 2> I = Eigen::Matrix<float, 2, 2>::Identity().cast<T>();
                Eigen::Matrix<T, 2, 1> n = pd.direction.cast<T>();
                Eigen::Matrix<T, 1, 2> nt = pd.direction.transpose().cast<T>();
                Eigen::Matrix<T, 2, 2> V = n * nt;
                V = I - V;
    
                Eigen::Matrix<T, 2, 2> R = Eigen::Matrix<float, 2, 2>::Zero().cast<T>();
                R.coeffRef(0, 0) = residuals[0];
                R.coeffRef(1, 1) = residuals[1];
    
                R = V * R * V.transpose();
                residuals[0] = R.coeffRef(0, 0);
                residuals[1] = R.coeffRef(1, 1);
            }
            return true;
        }



     

    更多相关内容
  • Camera-LIDAR 联合标定方法总结

    千次阅读 2020-03-21 18:49:28
    项目需要融合雷达和相机,所以要做联合标定,记录下收集的标定方法。...Camera-LIDAR 联合标定分为 2 步: 相机内参标定 雷达相机外参标定 相机内参标定方法 ROS Camera Calibration Tools SLAM之相机标...

    项目需要融合雷达和相机,所以要做联合标定,记录下收集的标定方法。

    一、总体标定步骤

    标定就是找到雷达到相机的空间转换关系,在不同的坐标系之间转换需要旋转矩阵 R 和平移矩阵 T,为后续的雷达和相机数据融合做准备:

    Camera-LIDAR 联合标定分为 2 步:

    1. 相机内参标定
    2. 雷达相机外参标定

    相机内参标定方法

    外参标定:lidar_camera_calibration

    外参标定:but_calibration_camera_velodyne

    外参标定:Autoware

    外参标定:Apoll

    二、其他传感器联合标定方法

    2.1 LIDAR-IMU 联合标定

    2.2 Camera-IMU 联合标定

    持续更新…

    更多文章,微信搜索关注「登龙」

    展开全文
  • 参考项目链接:https://gitee.com/manifold/lidar_camera_calib/tree/master 项目提供的bag中的信息 pointgrey.yaml 文件内容 %YAML:1.0 K: !!opencv-matrix rows: 3 cols: 3 dt: d data: [1061.37439737547,...

    参考项目链接:https://gitee.com/manifold/lidar_camera_calib/tree/master

    • 项目提供的bag中的信息
      在这里插入图片描述
    • pointgrey.yaml 文件内容
    %YAML:1.0
    K: !!opencv-matrix
       rows: 3
       cols: 3
       dt: d
       data: [1061.37439737547, 0, 980.706836288949,0, 1061.02435228316, 601.685030610243,0, 0, 1]
    d: !!opencv-matrix
       rows: 5
       cols: 1
       dt: d
       data: [-0.149007007770170, 0.0729485326193990, 0.000257753168848673, -0.000207183134328829, 0]
    
    Camera.width: 1920
    Camera.height: 1200
    
    grid_length: 0.15
    corner_in_x: 7
    corner_in_y: 5
    
    • launch文件配置
    <?xml version="1.0"?>
    <launch>
    
      <node pkg="ilcc2" type="get_image_corners_bag" name="get_image_corners_bag" output="screen">
    
        <param name="bag_path_prefix"  value= "/home/stone/Desktop/rosws/src/bag/2018-12-03-" />
        <param name="bag_num"  value= "6" />
        <param name="camera_name"  value= "pointgrey" />
        <param name="image_topic"  value= "/camera/image_raw " />
        <param name="yaml_path"  value= "pointgrey.yaml" />
        <param name="save_image_flag"  value= "true" />
    
      </node>
    </launch>
    
    
    • 运行指令
    roslaunch ilcc2 image_corners.launch
    
    • 得到的结果(去畸变后的图片)
      在这里插入图片描述
    • 对应的代码
    #include <ros/ros.h>
    #include <ros/package.h>
    #include <iostream>
    
    /// read rosbag
    #include <rosbag/bag.h>
    #include <rosbag/view.h>
    #include <boost/foreach.hpp>
    #define foreach BOOST_FOREACH
    
    #include <sensor_msgs/Image.h>        /// sensor_msgs::Image
    #include <cv_bridge/cv_bridge.h>
    #include <image_transport/image_transport.h>
    #include <opencv2/highgui.hpp>
    
    
    #include "ilcc2/ImageCornersEst.h"
    //cv_bridge用于ROS图像和OpenCV图像的转换
    cv::Mat get_image_from_msg(const sensor_msgs::ImageConstPtr& msg_img)
    {
      //Create cv_brigde instance
      cv_bridge::CvImagePtr cv_ptr;
      try
      {
        cv_ptr=cv_bridge::toCvCopy(msg_img, sensor_msgs::image_encodings::MONO8);
      }
      catch (cv_bridge::Exception& e)
      {
        ROS_ERROR("Not able to convert sensor_msgs::Image to OpenCV::Mat format %s", e.what());
        return cv::Mat();
      }
      // sensor_msgs::Image to OpenCV Mat structure
      cv::Mat Image = cv_ptr->image;
      return Image;
    }
    
    
    int main(int argc, char **argv)
    {
        ros::init(argc, argv, "image_corners");
        ros::NodeHandle nh;
        //显示出来ilcc2 package的路径
        std::string package_path = ros::package::getPath("ilcc2");
        ROS_INFO("ilcc2 package at %s", package_path.c_str());
    	
        int bag_num;
        bool save_image_flag;
        std::string bag_path_prefix, image_topic, yaml_path, camera_name;
        ros::NodeHandle nh_private("~");
        //nh_private:一般用来参数配置。在launch文件中param标签会把参数解释为/node/param_name,相当于(~param_name)
        nh_private.param<std::string>("bag_path_prefix", bag_path_prefix, "20181101_");  //bag的前缀
        nh_private.param<int>("bag_num", bag_num, 1);                                    //bag的个数
        nh_private.param<std::string>("yaml_path", yaml_path, "30w.yaml");               //参数文件
        nh_private.param<std::string>("image_topic", image_topic, "/front");             // 对应的topic
        nh_private.param<std::string>("camera_name", camera_name, "front");             //输出的文件前缀
        nh_private.param<bool>("save_image_flag", save_image_flag, "true");
    
        std::cout << "bag_num: " << bag_num << std::endl;
        std::cout << package_path << "/config/" << yaml_path << std::endl;
    
        ImageCornersEst::Ptr image_corners_est(new ImageCornersEst);
        image_corners_est = ImageCornersEst::Ptr(new ImageCornersEst(package_path+"/config/"+yaml_path, true) );
    
    
        for(int bag_idx = 1; bag_idx <= bag_num; bag_idx++)
        {
            std::cout << bag_idx << "============================================================\n";
            std::string bag_path = bag_path_prefix+std::to_string(bag_idx)+".bag";
            ROS_INFO("bag_path: %s", bag_path.c_str());
    
            rosbag::Bag bag_read;
            bag_read.open(bag_path, rosbag::bagmode::Read);
            std::vector<std::string> topics;
            topics.push_back(image_topic);
    
            rosbag::View view(bag_read, rosbag::TopicQuery(topics));
            std::cout << "view: " << view.size() << std::endl;
    
            sensor_msgs::ImageConstPtr msg_image_last = NULL;
            foreach(rosbag::MessageInstance const m, view)
            {
                sensor_msgs::ImageConstPtr msg_image = m.instantiate<sensor_msgs::Image>();
                if (msg_image != NULL){
                  msg_image_last = msg_image;
                }
    
                if(msg_image_last != NULL)
                  break;
            }
            bag_read.close();
    
            if(msg_image_last == NULL)
            {
              ROS_WARN("can't read image topic");
              continue;
            }
    
    
            ROS_INFO("findCorners");
            cv::Mat image = get_image_from_msg(msg_image_last);
    
            cv::Mat rectifyImage;
            image_corners_est->undistort_image(image, rectifyImage);
    
            //image_corners_est->findCorners(image);
            printf(package_path.c_str());
            if(save_image_flag)
              cv::imwrite(package_path+"/process_data/"+camera_name+std::to_string(bag_idx)+".jpg", rectifyImage);
    
        }
    
    
        return 0;
    }
    

    图像提取标定板角点libcbdetect

    激光提取标定板角点ilcc2

    • 运行程序
    roslaunch ilcc2 lidar_corners.launch
    
    • 程序会弹出两个pcl_viewer,分别是visual_corners与visual_chessboard

    • 打开rviz ,更改Fixed Frame 并添加PointCloud2 (把topic改为volodyne_points)
      在这里插入图片描述

    • 选中rviz的Publish Point功能,在标定板点云的中间点一个点.之后在visual_chessboard界面会显示提取结果,提取结果用粉色点云表示.
      在这里插入图片描述

    • 若提取正确,按o键后,visual_corners界面会显示角点提取结果

    • 一共有两张标定板的图,一张是在激光原点,一张是在原始位置.其中在原始位置的标定板有粉色点,也就是激光角点.

    • 最终正确的结果如下图所示.粉色角点提取正确.按k键确认.

    • 循环操作完成其他的bag

    标定激光与相机外参

    • 运行程序
    roslaunch ilcc2 calib_lidar_cam.launch
    

    在这里插入图片描述

    • 若初始外参设置正确,在lidar_camera_corners窗口中可以看到绿色相机角点与红色激光角点.该主要是为了确保lidar角点与相机角点的对应关系一致,只要红色点和绿色的排序方式一样即可(一般不会出问题,按任意键跳过即可).
      在这里插入图片描述

    • 标定好之后,会显示重投影结果(如上图所示).最终标定的结果会在终端输出,也会保存为bin文件存在config文件夹下.

    激光投影回相机效果

    roslaunch ilcc2 pcd2image.launch
    
    rosbag play xxx.bag -l
    

    在这里插入图片描述

    • 对应代码
    #include <fstream>
    #include <iostream>
    #include <math.h>
    #include <ros/package.h>        /// ros::package::getPath
    
    #include <Eigen/Core>
    
    #include <opencv2/core/core.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <opencv2/highgui/highgui.hpp>
    
    #include <cv_bridge/cv_bridge.h>
    #include <sensor_msgs/PointCloud2.h>
    #include <sensor_msgs/Image.h>
    
    #include <message_filters/subscriber.h>
    #include <message_filters/synchronizer.h>
    #include <message_filters/sync_policies/approximate_time.h>
    
    
    #include <pcl_conversions/pcl_conversions.h>
    #include <pcl/common/transforms.h>          /// pcl::transformPointCloud
    
    #include "ilcc2/ImageCornersEst.h"
    
    using namespace std;
    using namespace message_filters;
    
    ImageCornersEst::Ptr image_corners_est(new ImageCornersEst);
    
    double distance_valid;
    
    
    // void cv::undistort	
    // (	InputArray 	src,                        // 原始图像
    //         OutputArray 	dst,                        // 矫正图像
    //         InputArray 	cameraMatrix,               // 原相机内参矩阵
    //         InputArray 	distCoeffs,                 // 相机畸变参数
    //         InputArray 	newCameraMatrix = noArray() // 新相机内参矩阵
    // )	
    
    void processData(cv::Mat image, pcl::PointCloud<pcl::PointXYZI>::Ptr cloud)  
    {
    
      cv::Mat rectifyImage;       //矫正的图像
      cv::undistort(image, rectifyImage, image_corners_est->camK, image_corners_est->distort_param, image_corners_est->camK);
    
      pcl::PointXYZI ptmp;
    
      /// 生成每个点的颜色
      double inten_low=255, inten_high=0;    
      std::vector<double> datas;            
      for(unsigned int index=0; index<cloud->size(); index++)  //找深度的最大值和最小值
      {
          ptmp = cloud->points[index];
          if(inten_low > ptmp.intensity)
              inten_low = ptmp.intensity;
          if(inten_high < ptmp.intensity)
              inten_high = ptmp.intensity;
          datas.push_back(ptmp.intensity);
      }
    
      inten_low = 0;
      inten_high = 60;
    
    
    //  std::cout<<"start project "<< cloud->size() << " ,  ";
      int counter = 0;
      for(unsigned int index=0; index<cloud->size(); index++)
      {
    
        ptmp = cloud->points[index];
        Eigen::Vector3d Pw(ptmp.x, ptmp.y, ptmp.z);
        Eigen::Vector2d Pcam;
           //转换平面
        if ( image_corners_est->spaceToPlane(Pw, Pcam, distance_valid) ) {
    
          int x = Pcam[0];
          int y = Pcam[1];
    
          unsigned char r, g, b;
    
          double h = (ptmp.intensity-inten_low)/(inten_high-inten_low)*255;  //对深度进行归一化
    
          image_corners_est->HSVtoRGB(h, 100, 100, &r, &g, &b);
    
          cv::circle(rectifyImage, cv::Point2d(x,y), 0.6, cv::Scalar(r,g,b), 2);
    //      cv::circle(rectifyImage, cv::Point2d(x,y), 0.8, cv::Scalar(r,g,b), 0.8);
    //      image.ptr<uchar>(y)[x*3] = 255;
    //      image.ptr<uchar>(y)[x*3+1] = 0;
    //      image.ptr<uchar>(y)[x*3+2] = 0;
    
          counter++;
        }
      }
    //  std::cout << counter << " points ok\n";
      cv::resize(rectifyImage, rectifyImage, cv::Size(rectifyImage.cols/1, rectifyImage.rows/1));
      cv::imshow("img_liar_point", rectifyImage);  //矫正后的图像
      cv::waitKey(5);
    }
    
    
    void callback_LidarCam(const sensor_msgs::PointCloud2ConstPtr& msg_pc,
              const sensor_msgs::ImageConstPtr& msg_img)
    {
    //  ROS_INFO_STREAM("Velodyne scan received at " << msg_pc->header.stamp.toSec());
    //  ROS_INFO_STREAM("image received at " << msg_img->header.stamp.toSec());
      pcl::PointCloud<pcl::PointXYZI> input_cloud;
      pcl::fromROSMsg(*msg_pc, input_cloud);                       //转换为模板点云fromROSMsg
      cv::Mat img = cv_bridge::toCvCopy(msg_img,"bgr8")->image;    //image从ros转为opencv常用格式
      processData(img, input_cloud.makeShared());
    }
    
    
    
    int main(int argc, char** argv){
    
      ros::init(argc,argv,"pcd2image");
      ros::NodeHandle nh;
      std::string package_path = ros::package::getPath("ilcc2");
      ROS_INFO("ilcc2 package at %s", package_path.c_str());
    
    
      std::string yaml_path, image_topic, lidar_topic;
      std::string extrinsic_file;
      ros::NodeHandle nh_private("~");
    
      nh_private.param<double>("distance_valid", distance_valid, 5);
      nh_private.param<std::string>("extrinsic_file", extrinsic_file, "/process_data/pose.bin");
      nh_private.param<std::string>("yaml_path", yaml_path, "30w.yaml");
      nh_private.param<std::string>("image_topic", image_topic, "/front");
      nh_private.param<std::string>("lidar_topic", lidar_topic, "/velodyne_points");
    
    
      std::string extrinsic_path = package_path + extrinsic_file;             //外参路径
      ROS_INFO("load T_lidar2cam from %s", extrinsic_path.c_str());             
    
      image_corners_est = ImageCornersEst::Ptr(new ImageCornersEst(package_path+"/config/"+yaml_path, true) ); //内参路径
      image_corners_est->txt2extrinsic( extrinsic_path );
    
      message_filters::Subscriber<sensor_msgs::PointCloud2> cloud_sub(nh, lidar_topic, 2);      //点云接收
      message_filters::Subscriber<sensor_msgs::Image> image_sub(nh, image_topic, 2);            // 图像接收
    
      typedef sync_policies::ApproximateTime<sensor_msgs::PointCloud2, sensor_msgs::Image> MySyncPolicy;   //时间戳同步
      Synchronizer<MySyncPolicy> sync(MySyncPolicy(2), cloud_sub, image_sub);
      sync.registerCallback(boost::bind(&callback_LidarCam, _1, _2));                                  //回调函数
      ROS_INFO("waiting for lidar image topic %s %s", lidar_topic.c_str(), image_topic.c_str());       
    
    
      while (ros::ok())
      {
        ros::spin();
      }
    }
    
    
    
    
    
    展开全文
  • 相机内参标定获取:3. velodyne16线4.联合标定 1.相机驱动 相机驱动安装 sudo apt-get install ros-melodic-usb_cam 查看相机的最大分辨率: 打开usb_cam-test.launch文件修改读取图片的长和宽为1920 1080; 启动...


    自AdamShan博主

    1.相机驱动

    相机驱动安装 sudo apt-get install ros-melodic-usb-cam
    查看相机参数的最大分辨率https://blog.csdn.net/hongge_smile/article/details/108978488

    v4l2-ctl -d /dev/video0 --all
    

    得到相机的参数是:

    Width/Height : 1920/1080

    打开usb_cam-test.launch文件修改读取图片的长和宽为1920,1080(原始的是640x480);
    usb_cam-test.launch路径: /opt/ros/melodic/share/usb_cam/launch
    启动相机roslaunch usb_cam usb_cam-test.launch
    可以查看图片,
    或者自己编写图像读取ros程序.
    图片的topic : /usb_cam/image_raw

    2.相机内参标定获取:

    方法一:matlab方法
    采集一系列棋盘格的图,放入matlab中自动标定得到内参。
    网址https://blog.csdn.net/heroacool/article/details/51023921

    方法二:ros自带标定工具标定
    网址https://blog.csdn.net/weixin_41074793/article/details/83616699
    该指令中,–size 8x6 为角点个数,–square 0.0245 为方格的边长单位米
    camera:=/usb_cam
    移动不同的位置,旋转棋盘格,使得calibration按钮变绿后点击,无需保存,终端输出结果。

    rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.0245 image:=/usb_cam/image_raw camera:=/usb_cam
    

    在这里插入图片描述

    工具为棋盘格,如9x7的棋盘格;12x9的棋盘格。

    3. velodyne16线

    在这里插入图片描述

    4.联合标定

    4.1. 数据采集:

    采集相机和雷达同时工作时的数据bag。
    手持棋盘格,棋盘格与地面成45度,处于图像中9个(3x3)不同的经典位置,每个位置保持静止10秒钟,保证程序识别足够多特征点。

    4.2. 内参填写入cfg

    具体参数内容见标定说明文档。在这里插入图片描述
    在这里插入图片描述

    4.3 启动标定主程序

    在这里插入图片描述
    若使用自带bag,也启动velodyne,如下;若是自己的包则不需要

    在这里插入图片描述

    4.4 ROI区域选择;

    在这里插入图片描述

    最后播放bag包。(所以开启顺序通常是开主程序-开rviz-播放bag包)
    查看点云,来调节界面中按钮滑动,把不需要的区域剔除,只留下标定板所在区域。

    在这里插入图片描述

    4.5开始标定:

    在看到棋盘格较好的图片时,在主程序终端界面,点击i,再点击enter,取样;
    若出现红色报错,可忽略,继续进行。
    采集够个点后,点击O;终端出现标定结果,
    6个值

    x,y,z,roll,pitch,yall

    rviz中也显示融合后的图,可以判断标定效果如何。

    4.6 查看效果

    将标定的结果添加入:
    在这里插入图片描述
    更改src文件中的/left_front 为 /velodyne,重新编译
    启动launch,打开rviz,播放bag.
    订阅/tf查看坐标系。
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • matlab的代码在相机上实现3D-LiDAR相机外部校准[] [] 引文 如果您发现我们的代码和方法对您的工作有用,请考虑引用此工作: @Article{WANG2017Lidar_camera_cali, AUTHOR = {Wang, Weimin and Sakurada, Ken and ...
  • 车辆中的激光雷达和相机传感器需要进行联合标定标定原理:(世界坐标系—》相机坐标系—》图像坐标系—》像素坐标系) 外部校准是将点从三维激光雷达坐标系映射到三维相机坐标系的刚性变换。外部参数包括旋转R...
  • Matlab相机标定方法及主要参数含义,坐标变换过程

    千次阅读 多人点赞 2021-11-01 21:33:14
    网上有很多关于matlab相机标定的资料,但找了很久没有相应的参数说明:怎样利用获得参数从世界坐标系变换到图像坐标系,所以这里为了记录一下,也方便新人理解。 首先由图像到参数的获取部分在网上有很多资料,也很...
  • 雷达相机标定文件---lidar_cam_54_1.yml,已经标定完成,标定的数据是velodyne和相机
  • 同时,我们可以获得更好的校准结果和更好的LiDAR相机融合。 使用以下链接下载数据示例,该数据用于校准内在和外在参数。 数据已经保存在默认路径中。 步骤1:环境配置 (以下校准过程是在Ubuntu 64位16.04环境下进行...
  • Matlab相机标定工具箱与自带Camera Calibrator工具箱的参数对应: Matlab相机标定工具箱所得数据图: 自带Camera Calibrator工具箱所得参数图: 参数对应如下: Matlab相机标定工具箱参数 Camera Calibrator...
  • 激光雷达和相机联合标定之cam_lidar_calibration

    千次阅读 热门讨论 2022-02-10 18:42:00
    关于cam_lidar_calibration(2021)安装使用 1.简介:在众多的lidar和camera标定的开源程序中,效果相对不错的就是cam_lidar_calibration了,其余开源要么标定过程复杂、要么误差太大,该开源包经过一些改版。...
  • Livox-Mid70与相机联合标定及数据集采集
  • MATLAB终于可以完成相机与激光雷达的标定啦!!

    千次阅读 热门讨论 2020-11-27 11:16:38
    文章目录一、相机与激光雷达之间的标定 一、相机与激光雷达之间的标定 1.MATLAB2020b (1)获取方式:百度网盘链接提取码:zadk (2)安装教程:详细说明 2.详细步骤 官方教程:仔细瞅瞅 (1)搭建实验平台,实验...
  • 基于地图的激光雷达相机校准工具该软件是基于高清矢量地图的LiDAR相机校准工具,可用于校准几乎没有手动标记的对应关系的非重叠LiDAR相机对,并检查校准结果的质量。 校准工具的重要功能: 以高清矢量图作为标定场的...
  • 激光雷达相机外参标定

    千次阅读 2022-02-09 10:12:41
    首要参考Matlab提供的方法: https://ww2.mathworks.cn/help/lidar/ug/lidar-and-camera-calibration.html
  • 这部分的理论概况我感觉apollo那个课讲得还可以 知乎有一个大佬非常详细总结,汇总了很多的教程Camera-LIDAR 联合标定方法总结 - 知乎我这里主要是对其他人博客的一些理解
  • 2020-06-14 更新:Autoware 还提供了一个 CalibrationToolKit 联合标定雷达和相机的工具,我也记录下自己的标定过程,建议使用这个工具来标定,精度要高点:登龙:Autoware 工具 CalibrToolKit 标定 Robosense 雷...
  • 使用3D-3D点对应关系的LiDAR相机校准 , ( ,毗湿 南(Vishnu Radhakrishnan),克里希纳(Krishna) ROS封装,用于校准相机和LiDAR。 该软件包用于通过相机校准LiDAR(配置为支持Hesai和Velodyne硬件)(适用于...
  • 简单记录一下使用Autoware对lidar和cam联合标定的步骤和一些注意事项。 首先,开源的lidar和cam标定方案不多,花了一天查资料大概有以下几个: but_velodyne https://github.com/robofit/but_velodyne ...
  • 激光雷达lidar标定

    千次阅读 2021-03-25 13:11:21
    相机标定类似,激光雷达也有内参(由厂家提供)和外参之分 。 内参是内部激光发射器坐标囍和雷达自身坐标器之间的转换关系。外参是激光雷达与移动机器人之间的坐标系转换关系。我们主要标定的是俯仰角(x轴)和侧...
  • camera-IMU标定可以使用Kalibr,但是单线激光雷达和相机的的标定目前能找到的只有贺老师开源的这个工具了 参考1:CamLaserCalibraTool 参考2:实践之Camera-Lidar标定 参考3:CSDN博客 如参考博客中博主所说,使用...
  • 激光雷达和相机的联合标定(Camera-LiDAR Calibration)之Autoware 激光雷达(lidar)和相机(camera)联合标定调研(基于Autoware的详细步骤) 无人驾驶汽车系统入门(二十二)——使用Autoware实践激光雷达与摄像机...
  • 利用棋盘格进行相机标定

    千次阅读 2022-02-28 11:34:53
    这里主要是利用棋盘格进行相机标定的实现,开发化境为Ubuntu18.04。相机标定的具体算法原理可自行参考2000年张正友发表的论文《A Flexible New Technique for Camera Calibration》。关于相机标定的实现其实很多,...
  • 多传感融合内外参标定工具介绍(一):lidar-imu外参标定工具lidar_align多传感融合传感器内参与外参lidar-imu外参标定工具lidar_align外参标定与laser slam的关系使用方法与局限挖坑 多传感融合 多传感融合是目前...
  • 相机内参标定
  • 具体方法可以参考博客: SLAM之相机标定 相机外参之前使用了Autoware的工具箱,但标出来的效果有一定的误差,不太满意。 velo2cam_calibration的单目相机和雷达标定是2021年才出来的一篇文章,而且算法更为鲁棒,但...
  • 雷达相机标定(ROS)

    千次阅读 2020-12-22 11:27:38
    4.2.2 标定相机内参,与上述标定相似 标定单目: rosrun autoware_camera_lidar_calibrator cameracalibrator.py --square 0.028 --size 7x6 image:=/camera/color/image_raw 标定双目: rosrun autoware_camera_...
  • 激光雷达与相机标定

    千次阅读 2020-10-28 13:29:57
    文章目录激光雷达与相机标定1.下载源码并编译2. 制作标定板3.配置4. 启动5. 过程操作遇到问题参考链接 激光雷达与相机标定 激光雷达与相机的标定方法有:Autoware, apollo, lidar_camera_calibration, 与but_...
  • velo2cam_calibration软件为由LiDAR和摄像头设备组成的任何一对传感器实现了最新的自动校准算法[1]。 该软件以ROS软件包的形式提供。 在马德里卡洛斯三世大学开发的软件包。 设置 要安装此ROS软件包: 将存储库...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,467
精华内容 586
关键字:

lidar 相机标定