zoukankan      html  css  js  c++  java
  • PrimeSense 三维重建开发小谈 (2)

    1 综述

      与多点云处理有关的任务,点云的配准(Registration)是一个绕不开的问题。如何采用适当的算法,对特定的点云数据进行相对更优的配准,是点云配准过程中的关键任务。

      配准结果通常被表达为一个代表缩放,旋转,平移的刚体变换的矩阵,该矩阵代表了两个点云的位置关系,即将其中一个点云(源点云,Source)施以这个刚体变换可以使它与另一个点云(目标点云,Target)配准。

    图 1   目标点云(Target)

    图 2   源点云(Source)

    图 3   配准结果(该结果即通过本文所述的算法实现)

      配准算法主要分为两类,一类采用迭代法,利用逐步逼近的方法使配准结果逐步趋于最优,常用于两个点云的位置相差不大的场合下的精确配准;一类通过确定两个点云的点的对应关系(分别在两个点云中映射的在实际空间中“相同的”点)并根据这些点对确定两幅点云的位置关系,常用于原始位置相差较大的粗略配准。本文所述的基于灰度特征的配准算法属于第二类。

      部分针对点云进行配准的判据大多针对三维形状特征。对于缺乏三维形状特征的点云来说(例如图 2),该类型的配准通常不会获得理想的效果。但是,基于 RGB-D 传感器可以获得二维彩色点阵图片的特点,通过二维特征确定点云位置关系,是解决这一问题的一个思路。这也是本文所述的基于灰度特征的关系对配准算法的主要原理。

    2 源代码

      为了描述上的方便,我们使用函数

    void getGrayScaleRegMatrix(const Cloud &cloud_target, const Cloud &cloud_source, Matrix &matrix);

    代表算法执行的入口,配准结果输出在 matrix 中。其中,Cloud / CloudPtr 是点云库 PCL 中的点云类和点云类的 boost 指针,Point 是 PCL 的点类,Matrix 是矩阵类型(常用 Eigen::Matrix4f)。

    P.S. 该函数使用 void 作为函数返回的原因,是 Eigen 线性代数类库强烈建议不使用 pass-by-value 传递方法。不仅因为这一方法本身不在 C++ 的这种场合下被建议,同时,使用 pass-by-value 会因为编译器的数据类型对齐特性造成致命的运行时错误。此处,调用该函数的代码应保证存储结果的矩阵已经被事先分配内存。(参考:http://eigen.tuxfamily.org/dox-devel/TopicUnalignedArrayAssert.html

    //Surf_Threshold 是限定提取特征阈值的常量;Max_Remove_Threshold 是限定最大距离阈值的常量。详见下面的代码。
    const double Max_Remove_Threshold = 750;
    const int Surf_Threshold = 750;
    
    //获取点云对应的二维点阵图信息。
    IplImage* getIplImage(const CloudPtr &input_cloud)
    {
        //创建图像。
        IplImage *image = cvCreateImage(cvSize((*input_cloud).width, (*input_cloud).height), IPL_DEPTH_8U, 3);
        //填充图像。
        cv::Scalar pixel;
        for (int i = 0; i < (*image).height; i++)
        {
            for (int j = 0; j < (*image).width; j++)
            {
                pixel.val[0] = (*input_cloud).points[i*(*image).width + j].b;
                pixel.val[1] = (*input_cloud).points[i*(*image).width + j].g;
                pixel.val[2] = (*input_cloud).points[i*(*image).width + j].r;
                cvSet2D(image, i, j, pixel);
            }
        }
        //返回创建的图像。
        return image;
    }
    
    //配准主函数。
    void getGrayScaleRegMatrix(const CloudPtr &cloud_target, const CloudPtr &cloud_source, Matrix &matrix)
    {
        //获取原始点云对应的二维点阵图信息。参考 getIplImage 函数。
        IplImage *oriImageTarget = getIplImage(cloud_target);
        IplImage *oriImageSource = getIplImage(cloud_source);
    
        //将原始图像转换为灰度图像,备 SURF 特征点提取使用。
        IplImage *grayImageTarget = cvCreateImage(cvGetSize(oriImageTarget), IPL_DEPTH_8U, 1);
        cvCvtColor(oriImageTarget, grayImageTarget, CV_RGB2GRAY);
        IplImage *grayImageSource = cvCreateImage(cvGetSize(oriImageSource), IPL_DEPTH_8U, 1);
        cvCvtColor(oriImageSource, grayImageSource, CV_RGB2GRAY);
    
        //提取 SURF 特征点,并存入 keypointsTarget 和 keypointsSource 向量。Surf_Threshold 是限定提取特征阈值的常量。
        cv::SurfFeatureDetector detector(Surf_Threshold);
        std::vector<cv::KeyPoint> keypointsTarget;
        detector.detect(grayImageTarget, keypointsTarget);
        std::vector<cv::KeyPoint> keypointsSource;
        detector.detect(grayImageSource, keypointsSource);
    
        //计算 SURF 特征点的描述符。
        cv::SurfDescriptorExtractor extractor;
        cv::Mat descriptorsSource, descriptorsTarget;
        extractor.compute(grayImageTarget, keypointsTarget, descriptorsTarget);
        extractor.compute(grayImageSource, keypointsSource, descriptorsSource);
    
        //将两幅点云的描述符通过 Flann 算法进行特征点的关系对应。
        cv::FlannBasedMatcher matcher;
        std::vector<cv::DMatch> matches;
        matcher.match(descriptorsSource, descriptorsTarget, matches);
    
        //获得对应关系列表。
        std::vector<cv::Point2f> sourcePoints;
        std::vector<cv::Point2f> targetPoints;
        for (int i = 0; i < matches.size(); i++)
        {
            sourcePoints.push_back(keypointsSource[matches[i].queryIdx].pt);
            targetPoints.push_back(keypointsTarget[matches[i].trainIdx].pt);
        }
    
        //使用 RANSAC 算法进行错误对应关系的去除。特征点对的保留与丢弃结果存储在 m_RANSACStatus 中。
        std::vector<uchar> m_RANSACStatus;
        cv::findHomography(sourcePoints, targetPoints, CV_RANSAC, 3.0, m_RANSACStatus);
    
        //创建 PCL 适用的关系列表。
        pcl::Correspondences correspondences;
        correspondences.empty();
    
        int temp_x_target, temp_y_target, temp_index_target, temp_x_source, temp_y_source, temp_index_source;
        for (int i = 0; i < matches.size(); i++)
        {
            if (m_RANSACStatus[i] == 1) //保留的对应点对。
            {
                //四舍五入计算X、Y坐标,并计算其在点云中存储的位置。
                temp_x_target = (int)(targetPoints[i].x + 0.5);
                temp_y_target = (int)(targetPoints[i].y + 0.5);
                temp_index_target = temp_y_target * (*grayImageTarget).width + temp_x_target;
    
                temp_x_source = (int)(sourcePoints[i].x + 0.5);
                temp_y_source = (int)(sourcePoints[i].y + 0.5);
                temp_index_source = temp_y_source * (*grayImageSource).width + temp_x_source;
    
                //若特征点对在三维点云中没有对应的空间点(没有深度信息)则不考虑该对应关系。
                if ((*cloud_source).points[temp_index_source].z != (*cloud_source).points[temp_index_source].z)
                    continue;
                if ((*cloud_target).points[temp_index_target].z != (*cloud_target).points[temp_index_target].z)
                    continue;
    
                //存储对应关系。
                pcl::Correspondence correspondence;
                correspondence.index_query = temp_index_source;
                correspondence.index_match = temp_index_target;
                correspondences.push_back(correspondence);
            }
        }
    
        //利用点云的空间特点,如果对应关系的相差距离过大,则去除该关系。Max_Remove_Threshold 是限定最大距离阈值的常量。
        for (int i = 0;;)
        {
            if (i == correspondences.size())
                break;
            if (Utils::getDistance((*cloud_target).points[correspondences[i].index_match], (*cloud_source).points[correspondences[i].index_query]) > Max_Remove_Threshold)
            {
                correspondences.erase(correspondences.begin() + i);
            }
            else
                i++;
        }
        
        //根据对应关系计算变换矩阵。
        pcl::registration::TransformationEstimationLM<Point, Point> estimation;
        estimation.estimateRigidTransformation((*cloud_source), (*cloud_target), correspondences, matrix);
    }

    3 算法待改进点

      对于灰度 SURF 特征点及对应关系的确定,还可以对特征点的选取和关系的确定进行进一步的优化,避免严重错误的配准结果的出现。

      从工程角度,上述代码对于特殊条件下可能出现的极端结果没有进行必要的处理。

    4 参考文献

      OpenCV Feature Description http://docs.opencv.org/doc/tutorials/features2d/feature_description/feature_description.html

      OpenCV学习笔记(九)——2维特征Feature2D http://blog.csdn.net/yang_xian521/article/details/6901762

    Flaris帕 版权所有
    转载请注明出处(●'◡'●)
    20141019 V1.0版

  • 相关阅读:
    2021寒假每日一题《棋盘挑战》
    2021寒假每日一题《货币系统》
    2021寒假每日一题《红与黑》
    2021寒假每日一题《字母图形》
    2021寒假每日一题《完全背包问题》
    2021寒假每日一题《找硬币》
    python 迭代器和生成器
    python for循环
    python集合
    python字符串常用操作
  • 原文地址:https://www.cnblogs.com/Flaris/p/PrimeSense2.html
Copyright © 2011-2022 走看看