仿射变换Affine+RANSAC

  • 仿射变换(平移、翻转、旋转、缩放、错切五种变换的组合)

  • RANSAC

RANSAC是"RANdom SAmple Consensus"(随机采样一致)的缩写。该算法的主要功能是通过一组包含“局外点”(即错误点)通过迭代的方式估计数学模型的参数。它是一种不确定的算法——有一定的概率得出一个合理的结果。

1)RANSAC的假设

  • 数据由“局内点组成,数据的分布可以用一些模型参数来解释。
  • 数据的“局外点”,是不能适应该模型的数据。
  • 给定一组局内点(数量相对于整体数据少),存在一个可以估计模型参数的过程,该模型能够解释或使用局内点。
  • 局外点的产生原因有:噪声,错误的观测,对数据的错误假设等。

2)基本思想

 RANSAC通过反复选择数据中的一组随机子集来达成目标。被选取的子集被假设为局内点,并用下述方法进行验证:

  1. 有一个模型适应于假设的局内点,即所有的未知参数都能从假设的局内点计算得出
  2. 用1中得到的模型去测试所有的其它数据,如果某个点适用于估计的模型,认为它也是局内点。
  3. 如果有足够多的点被归类为假设的局内点,那么估计的模型就足够合理
  4. 然后,用所有假设的局内点去重新估计模型,因为它仅仅被初始的假设局内点估计过。
  5. 最后,通过估计局内点与模型的错误率来评估模型。
  6. 这个过程被重复执行固定的次数,每次产生的模型要么因为局内点太少而被舍弃,要么因为比现有的模型更好而被选用。

3)RANSAC拟合直线

#include<iostream>
#include<opencv2/opencv.hpp>
#include<stdlib.h>
using namespace std;
using namespace cv;

//生成[0,1]之间符合均匀分布的数
double uniformRandom(void)
{
	return (double)rand() / (double)RAND_MAX;
}
//生成【0,1】之间的符合高斯分布的随机数
double gaussianRandom(void)
{
	static int next_gaussian = 0;
	static double saved_gaussian_value;
	double fac,rsq, v1, v2;
	if (next_gaussian == 0) {
		do {
			v1 = 2 * uniformRandom() - 1;
			v2 = 2 * uniformRandom() - 1;
			rsq = v1*v1 + v2*v2;
		} while (rsq >= 1.0 || rsq == 0.0);
		fac = sqrt(-2 * log(rsq) / rsq);
		saved_gaussian_value = v1*fac;
		next_gaussian = 1;
		return v2*fac;
	}
	else {
		next_gaussian = 0;
		return saved_gaussian_value;
	}

}
void get_inline_outline(vector<Point2d> &ptSet)
{
	//图像大小
	int width = 640;
	int height = 320;

	//直线参数
	double a = 1, b = 2, c = -640;
	//随机获取直线上20个点
	srand((unsigned int)time(NULL)); //设置随机数种子
	while (true)
	{
		double x = uniformRandom()*(width - 1);
		double y = -(a*x + c) / b;
		//加0.5高斯噪声
		x += gaussianRandom()*0.5;
		y += gaussianRandom()*0.5;
		if (x >= 640 && y >= 320)
			continue;
		Point2d pt(x, y);
		ptSet.push_back(pt);
		if (ptSet.size() == 20)
			break;
	}
	//随机获取10个野值点
	while (true)
	{
		double x = uniformRandom() * (width - 1);
		double y = uniformRandom() * (height - 1);

		if (fabs(a*x + b*y + c) < 10)  //野值点到直线距离不小于10个像素
			continue;
		Point2d pt(x, y);
		ptSet.push_back(pt);
		if (ptSet.size() == 30)
			break;
	}
}

//根据点集拟合直线ax+by+c=0
void calcLinePara(vector<Point2d> pts, double &a, double &b, double &c)
{
	Vec4f line;
	fitLine(pts, line, CV_DIST_L2, 0, 1e-2, 1e-2);
	a = line[1];
	b = -line[0];
	c = line[0] * line[3] - line[1] * line[2];

}

//得到直线拟合样本,即在直线采样点集上随机选2个点
bool getSample(vector<int> set, vector<int> &sset)
{
	int i[2];
	if (set.size() > 2)
	{
		do
		{
			for (int n = 0; n < 2; n++)
				i[n] = int(uniformRandom() * (set.size() - 1));
		} while (!(i[1] != i[0]));
		for (int n = 0; n < 2; n++)
		{
			sset.push_back(i[n]);
		}
	}
	else
	{
		return false;
	}
	return true;
}

//RANSAC直线拟合
void fitLineRANSAC(vector<Point2d> ptSet, double &a, double &b, double &c, vector<bool> &inlierFlag)
{
	double residual_error = 2.99; //内点阈值

	bool stop_loop = false;
	int maximum = 0;  //最大内点数

					  //最终内点标识及其残差
	inlierFlag = vector<bool>(ptSet.size(), false);
	vector<double> resids_(ptSet.size(), 3);
	int sample_count = 0;
	int N = 500;
	// RANSAC
	srand((unsigned int)time(NULL)); //设置随机数种子
	vector<int> ptsID;
	for (unsigned int i = 0; i < ptSet.size(); i++)
		ptsID.push_back(i);

	while (N > sample_count && !stop_loop)
	{
		vector<bool> inlierstemp;
		vector<double> residualstemp;
		vector<int> ptss;
		int inlier_count = 0;
		if (!getSample(ptsID, ptss))
		{
			stop_loop = true;
			continue;
		}

		vector<Point2d> pt_sam;
		pt_sam.push_back(ptSet[ptss[0]]);
		pt_sam.push_back(ptSet[ptss[1]]);

		if (abs(pt_sam[0].x - pt_sam[1].x) < 5 && abs(pt_sam[0].y - pt_sam[1].y) < 5)
		{
			++sample_count;
			continue;
		}

		// 计算直线方程
		calcLinePara(pt_sam, a, b, c);
		//内点检验
		for (unsigned int i = 0; i < ptSet.size(); i++)
		{
			Point2d pt = ptSet[i];
			double resid_ = fabs(pt.x * a + pt.y * b + c);
			residualstemp.push_back(resid_);
			inlierstemp.push_back(false);
			if (resid_ < residual_error)
			{
				++inlier_count;
				inlierstemp[i] = true;
			}
		}
		// 找到最佳拟合直线
		if (inlier_count >= maximum)
		{
			maximum = inlier_count;
			resids_ = residualstemp;
			inlierFlag = inlierstemp;
		}
		// 更新RANSAC迭代次数,以及内点概率
		if (inlier_count == 0)
		{
			N = 500;
		}
		else
		{
			double epsilon = 1.0 - double(inlier_count) / (double)ptSet.size(); //野值点比例
			double p = 0.99; //所有样本中存在1个好样本的概率
			double s = 2.0;
			N = int(log(1.0 - p) / log(1.0 - pow((1.0 - epsilon), s)));
		}
		++sample_count;
	}

	//利用所有内点重新拟合直线
	vector<Point2d> pset;
	for (unsigned int i = 0; i < ptSet.size(); i++)
	{
		if (inlierFlag[i])
			pset.push_back(ptSet[i]);
	}

	calcLinePara(pset, a, b, c);
}
int main()
{
	vector<Point2d> ptSet;
	get_inline_outline(ptSet);

	//绘制点集中所有点
	Mat img(321, 641, CV_8UC3, Scalar(255, 255, 255));

	double A, B, C;
	vector<bool> inliers;
	fitLineRANSAC(ptSet, A, B, C, inliers);
	for (unsigned int i = 0; i < ptSet.size(); i++) {
		if (inliers[i])
			circle(img, ptSet[i], 3, Scalar(0, 255, 0), 3, 16);
		else
			circle(img, ptSet[i], 3, Scalar(0, 0, 255), 3, 16);
	}

	B = B / A;
	C = C / A;
	A = A / A;

	//绘制直线
	Point2d ptStart, ptEnd;
	ptStart.x = 0;
	ptStart.y = -(A*ptStart.x + C) / B;
	ptEnd.x = -(B*ptEnd.y + C) / A;
	ptEnd.y = 0;
	line(img, ptStart, ptEnd, Scalar(0, 0, 0), 1, 16);
	cout << "A:" << A << " " << "B:" << B << " " << "C:" << C << " " << endl;

	imshow("line fitting", img);
	waitKey();
}

 3)RANSAC的优缺点:

  1. 优点:鲁棒估计模型参数,从包含大量的外点的数据集中估计出高精度参数
  2. 缺点:随机估计,并设置迭代上限,有可能得不到最优结果甚至是错误结果。只能从特定的数据模型中估计一个模型,如果存在两个或多个模型,则无法估计别的模型。

 4)放射变换ransac实现

#include <iostream>
#include<opencv2/xfeatures2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
#include <mynteyed/camera.h>
#include <mynteyed/utils.h>
#include <Eigen/Core>
#include <Eigen/Dense>
#include <time.h>
#include <cstdlib>

MYNTEYE_USE_NAMESPACE
cv::Mat depth;
int Num=500;

using namespace std;
class AffineRansac
{
public:
    AffineRansac(int N,double Epsilon):max_iterations(N),threshold(Epsilon)
    {
        srand((unsigned int)time(NULL));
    }
    bool AffineTransformer(std::vector<cv::Point2d> Image_Coords_1, std::vector<cv::Point2d> Image_Coords_2,Eigen::Matrix<double,3,3> &A)
    {
        if(Image_Coords_1.size()<3||Image_Coords_2.size()<3)
        {
            std::cout<<"Incorrect input dimensions"<<std::endl;
            return false;
        }
        else if(Image_Coords_1.size()!=Image_Coords_2.size())
        {
            std::cout<<"Mismatch between number of points in the image pair"<<std::endl;
            return false;
        }
        Eigen::Matrix<double,Eigen::Dynamic,3> P;
        Eigen::Matrix<double,Eigen::Dynamic,3> Q;
        P.resize(Image_Coords_1.size(),3);
        Q.resize(Image_Coords_1.size(),3);
        for(int i=0;i<Image_Coords_1.size();i++)
        {

            P(i,0)=Image_Coords_1[i].x;
            P(i,1)=Image_Coords_1[i].y;
            P(i,2)=1;

            Q(i,0)=Image_Coords_2[i].x;
            Q(i,1)=Image_Coords_2[i].y;
            Q(i,2)=1;
        }

        Eigen::Matrix<double,3,3> P_inv;
        Eigen::Matrix<double,3,Eigen::Dynamic> P_;
        P_inv=P.transpose()*P;
        P_=(P_inv.inverse())*(P.transpose());
        A =P_*Q;
        return true;
    }
    double SumEucDistance(cv::Point2d const &P1, cv::Point2d const &P1_Co,
                          cv::Point2d const &P2, cv::Point2d const &P2_Co)
    {
        double d=std::sqrt(pow((P1.x-P1_Co.x),2)+pow((P1.y-P1_Co.y),2))+std::sqrt(pow((P2.x-P2_Co.x),2)+pow((P2.y-P2_Co.y),2));
        return d;
    }
    void find_inliers (std::vector<cv::Point2d> Coordinates_Image_1, std::vector<cv::Point2d> Coordinates_Image_2,Eigen::Matrix<double,3,3> const &A, std::vector<int> &result)
    {
        result.resize(0);
        Eigen::Matrix<double,1,3> X2;
        Eigen::Matrix<double,1,3> X1;

        Eigen::Matrix<double,1,3> x2;
        Eigen::Matrix<double,1,3> x1;
        cv::Point2d P1,P2,P1_Co,P2_Co;
        double ssd;

        for(int i=0;i<Coordinates_Image_1.size();i++)
        {
            x1(0,0)=Coordinates_Image_1[i].x;
            x1(0,1)=Coordinates_Image_1[i].y;
            x1(0,2)=1;

            x2(0,0)=Coordinates_Image_2[i].x;
            x2(0,1)=Coordinates_Image_2[i].y;
            x2(0,2)=1;

            X2=A*x1.transpose();
            X1=A.inverse()*x2.transpose();

            P1.x=X1(0,0);
            P1.y=X1(0,1);
            P2.x=X2(0,0);
            P2.y=X2(0,1);

            P1_Co.x=x1(0,0);
            P1_Co.y=x1(0,1);
            P2_Co.x=x2(0,0);
            P2_Co.y=x2(0,1);
            //std::cout<<"******X1: "<<P1<<std::endl;
            //std::cout<<"******X2: "<<P2<<std::endl;
            //std::cout<<"******x1: "<<P1_Co<<std::endl;
            //std::cout<<"******x2: "<<P2_Co<<std::endl;

            ssd=SumEucDistance(P1,P1_Co,P2,P2_Co);
            //std::cout<<"ssd: "<<ssd<<std::endl;
            if(ssd<threshold)
            {
                result.push_back(i);
            }
        }
        //std::cout<<"*************"<<std::endl;
    }

    void Affine_Ransac(std::vector<cv::Point2d> Coordinates_Image_1, std::vector<cv::Point2d> Coordinates_Image_2)
    {
        int In_count = 0;
        std::vector<int> inliers;
        inliers.reserve(Coordinates_Image_1.size());

        std::vector<cv::Point2d> Image_Coords_1;
        std::vector<cv::Point2d> Image_Coords_2;

        Image_Coords_1.reserve(Coordinates_Image_1.size()/2+1);
        Image_Coords_2.reserve(Coordinates_Image_1.size()/2+1);

        std::cout<<"max_iterations: "<<max_iterations<<std::endl;
        while (In_count < max_iterations)
        {
            // Randomly pick out points from the matches
            std::set<int> subsample;
            generateRandomArray(subsample,Coordinates_Image_1.size()/2+1,Coordinates_Image_1.size());
            for(std::set<int>::iterator it=subsample.begin();it!=subsample.end();it++)
            {
                //std::cout<<"set: "<<*it<<" ";
                Image_Coords_1.push_back(Coordinates_Image_1[*it]);
                //std::cout<<"p1: "<<Coordinates_Image_1[*it].x<<" "<<Coordinates_Image_1[*it].y<<" ";
                Image_Coords_2.push_back(Coordinates_Image_2[*it]);
                //std::cout<<"p2: "<<Coordinates_Image_2[*it].x<<" "<<Coordinates_Image_2[*it].y<<" ";
            }
            subsample.clear();
            //std::cout<<"size: "<<subsample.size()<<std::endl;
            //affine
            Eigen::Matrix<double,3,3> A;
            bool flag=AffineTransformer(Image_Coords_1, Image_Coords_2,A);
            //std::cout<<"A: "<<A<<std::endl;
            if(!flag)
                continue;
            find_inliers(Coordinates_Image_1,Coordinates_Image_2,A,inliers);
            //std::cout<<"size: "<<inliers.size()<<std::endl;
            if (inliers.size() > result_inliers.size())
            {
                std::cout<<"counter: "<<In_count<<std::endl;
                std::cout<<"size1: "<<inliers.size()<<std::endl;
                std::cout<<"size2: "<<Coordinates_Image_1.size()<<std::endl;
                AffineMatrix = A;
                std::swap(result_inliers, inliers);
                inliers.reserve(Coordinates_Image_1.size());
            }
            In_count++;
            //std::cout<<"counter: "<<In_count<<std::endl;
        }


    }


private:

    double uniformRandom(void)
    {

        return (double)std::rand()/(double)RAND_MAX;
    }
    // 创建一个 n个元素的数组
    void generateRandomArray(std::set<int> &arr, unsigned int n, int size)
    {

        // 随机种子
        while(arr.size()<n)
            arr.insert(int(uniformRandom()*(size-1)));
    }

    int max_iterations;

    /**
     * Threshold used to determine inliers. Defaults to 0.0015.
     * This threshold assumes that the input points are normalized.
     */
    double threshold;


    /**
 * The resulting AffineMatrix  which led to the inliers.
 * This is NOT the re-computed matrix from the inliers.
 */
    Eigen::Matrix<double,3,3> AffineMatrix;

    /**
     * The indices of inliers in the correspondences which led to the
     * homography matrix.
     */
    std::vector<int> result_inliers;

};

cv::Point2f pixel2cam ( const cv::Point2d& p, const cv::Mat& K )
{
    return cv::Point2f
            (
                    ( p.x - K.at<double>(0,2) ) / K.at<double>(0,0),
                    ( p.y - K.at<double>(1,2) ) / K.at<double>(1,1)
            );
}







int main(int argc, char const *argv[])
{

    //左右视图的特征点
    std::vector<cv::KeyPoint> keypoints_l,keypoints_r;

    cv::Mat descriptors_l,descriptors_r;

    //存储左图、右图的特征点
    std::vector<cv::KeyPoint> pre_keypoints_l,pre_keypoints_r;

    //存储左图和右图的描述子
    cv::Mat pre_descriptors_l,pre_descriptors_r;

    //前一帧和当前帧
    cv::Mat previous_frame_l,previous_frame_r,current_frame_l,current_frame_r;

    std::vector<cv::Point2d> Coordinates_Image_1, Coordinates_Image_2;

    //相机
    Camera cam;
    //设备
    DeviceInfo dev_info;
    //选择相机
    if (!util::select(cam, &dev_info)) {
        return 1;
    }
    util::print_stream_infos(cam, dev_info.index);
    std::cout << "Open device: " << dev_info.index << ", "
              << dev_info.name << std::endl << std::endl;
    //参数设置
    OpenParams params(dev_info.index);//构造函数显式调用成功
    params.color_mode=ColorMode ::COLOR_RECTIFIED;
    params.depth_mode = DepthMode::DEPTH_COLORFUL;
    params.stream_mode = StreamMode::STREAM_1280x480;
    params.ir_intensity = 10;
    params.framerate = 60;
    cam.Open(params);

    std::cout << std::endl;
    if (!cam.IsOpened()) {
        std::cerr << "Error: Open camera failed" << std::endl;
        return 1;
    }
    std::cout << "Open device success" << std::endl << std::endl;
    std::cout << "Press ESC/Q on Windows to terminate" << std::endl;

    cv::namedWindow("left");
    cv::namedWindow("right");
    cv::namedWindow("depth");

    cv::Ptr<cv::xfeatures2d::SIFT> f2d=cv::xfeatures2d::SIFT::create(2000);

    //获取第一帧图像
    auto right_color = cam.GetStreamData(ImageType::IMAGE_RIGHT_COLOR);
    auto left_color = cam.GetStreamData(ImageType::IMAGE_LEFT_COLOR);
    if (right_color.img && left_color.img)
    {
        auto left_img = left_color.img->To(ImageFormat::COLOR_BGR);
        previous_frame_l = cv::Mat(left_img->height(), left_img->width(), CV_8UC3,
                                   left_img->data());

        auto right_img = right_color.img->To(ImageFormat::COLOR_BGR);
        previous_frame_r = cv::Mat(right_img->height(), right_img->width(), CV_8UC3,
                                   right_img->data());

        //检测并计算前一帧的特征点和描述子
        f2d->detectAndCompute(previous_frame_l,cv::Mat(),keypoints_l,descriptors_l);
        f2d->detectAndCompute(previous_frame_r,cv::Mat(),keypoints_r,descriptors_r);

        //存储左图和右图的特征点
        pre_keypoints_l.insert(pre_keypoints_l.end(),keypoints_l.begin(),keypoints_l.end());
        pre_keypoints_r.insert(pre_keypoints_r.end(),keypoints_r.begin(),keypoints_r.end());
        //存储左图的描述子
        descriptors_l.copyTo(pre_descriptors_l);
    }
    while(true)
    {

        right_color = cam.GetStreamData(ImageType::IMAGE_RIGHT_COLOR);
        left_color = cam.GetStreamData(ImageType::IMAGE_LEFT_COLOR);
        if (right_color.img && left_color.img)
        {
            auto left_img = left_color.img->To(ImageFormat::COLOR_BGR);
            current_frame_l=cv::Mat(left_img->height(), left_img->width(), CV_8UC3,
                                    left_img->data());
            //获取当前帧图像
            auto right_img = right_color.img->To(ImageFormat::COLOR_BGR);
            current_frame_r=cv::Mat(right_img->height(), right_img->width(), CV_8UC3,
                                    right_img->data());
            //显示左图和右图
            cv::imshow("cright", current_frame_r);
            cv::imshow("cleft", current_frame_l);

            //检测并计算当前帧的特征点和描述子
            f2d->detectAndCompute(current_frame_r,cv::Mat(),keypoints_r,descriptors_r);
            f2d->detectAndCompute(current_frame_l,cv::Mat(),keypoints_l,descriptors_l);

            cv::Ptr<cv::DescriptorMatcher> matcher=cv::DescriptorMatcher::create(cv::DescriptorMatcher::FLANNBASED);
            std::vector<std::vector<cv::DMatch>> knn_matches;
            matcher->knnMatch(pre_descriptors_l,descriptors_l,knn_matches,2);

            const float ratio_thresh=0.8f;
            std::vector<cv::DMatch> good_matches;
            for(int i=0;i<knn_matches.size();i++)
            {
                if((knn_matches[i][0].distance<ratio_thresh*knn_matches[i][1].distance))
                    good_matches.push_back(knn_matches[i][0]);

            }
            cv::Mat img_matches;
            cv::drawMatches(previous_frame_l,pre_keypoints_l,current_frame_l,keypoints_l,good_matches,img_matches);
            cv::imshow("match",img_matches);

            for(int i=0;i<good_matches.size();i++)
            {
                Coordinates_Image_1.push_back(pre_keypoints_l[good_matches[i].queryIdx].pt);
                //std::cout<<"size: "<<Coordinates_Image_1.size()<<" x1: "<<pre_keypoints_l[good_matches[i].queryIdx].pt.x<<" y1: "<<pre_keypoints_l[good_matches[i].queryIdx].pt.y<<std::endl;
                Coordinates_Image_2.push_back(keypoints_l[good_matches[i].trainIdx].pt);
                //std::cout<<"size: "<<Coordinates_Image_1.size()<<" x2: "<<keypoints_l[good_matches[i].queryIdx].pt.x<<" y2: "<<keypoints_l[good_matches[i].queryIdx].pt.y<<std::endl;
            }
            AffineRansac affine(100,1.5);
            Eigen::Matrix<double,3,3> A;

            affine.Affine_Ransac(Coordinates_Image_1,Coordinates_Image_2);
            Coordinates_Image_1.clear();
            Coordinates_Image_2.clear();

            cv::swap(previous_frame_l,current_frame_l);
            cv::swap(pre_descriptors_l,descriptors_l);
            std::swap(pre_keypoints_l,keypoints_l);


            char key = static_cast<char>(cv::waitKey(1));
            if (key == 27 || key == 'q' || key == 'Q') {  // ESC/Q
                break;
            }
        }
        auto image_depth = cam.GetStreamData(ImageType::IMAGE_DEPTH);
        if (image_depth.img) {
            auto depth_img = image_depth.img->To(ImageFormat::COLOR_BGR);
            depth=cv::Mat(depth_img->height(), depth_img->width(), CV_8UC3,
                          depth_img->data());
            cv::imshow("depth", depth);
        }
    }
    cam.Close();
    cv::destroyAllWindows();
    return 0;
}


参考文章:

https://www.bbsmax.com/A/KE5QEOgy5L/

https://www.bbsmax.com/A/obzbym7QdE/

https://www.qingtingip.com/h_170146.html

发布了44 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/He3he3he/article/details/98058520