Opencv之RANSAC算法用于直线拟合及特征点集匹配详解

news2025/1/15 22:33:11

Opencv之RANSAC算法用于直线拟合及特征点集匹配详解

    1. 讲述Ransac拟合与最小二乘在曲线拟合上的优缺点
    1. 讲述在进行特征点匹配时,最近邻匹配与Ransac匹配的不同之处
    1. 另外,Ransac也被用于椭圆拟合、变换矩阵求解等

1. 直线拟合

1.1 原理

  • RANSAC(RANdom SAmple Consensus,随机采样一致)算法是从一组含有“外点”(outliers)的数据中正确估计数学模型参数的迭代算法。“外点”一般指的的数据中的噪声,比如说匹配中的误匹配和估计曲线中的离群点。故RANSAC也是一种“外点”检测算法。同时RANSAC是一个非确定性算法,在某种意义上说,它会产生一个在一定概率下合理的结果,其允许使用更多次的迭代来使其概率增加。

  • RANSAC算最早是由Fischler和Bolles在SRI上提出用来解决LDP(Location Determination Problem,位置确定问题)问题的。

  • 对于RANSAC算法来说一个基本的假设就是数据是由“内点”和“外点”组成的。“内点”就是组成模型参数的数据,“外点”就是不适合模型的数据。同时RANSAC假设:在给定一组含有少部分“内点”的数据,存在一个程序可以估计出符合“内点”的模型

  • 算法主要思想:

  • 给定一个数据集S,从中选择建立模型所需的最小样本数(空间直线最少可以由两个点确定,所以最小样本数是2,空间平面可以根据不共线三点确定,所以最小样本数为3,拟一个圆时,最小样本数是3),记选择数据集为S1
    使用选择的数据集S1计算得到一个数学模型M1
  • 用计算的模型M1去测试数据集中剩余的点,如果测试的数据点在误差允许的范围内,则将该数据点判为内点(inlier),否则判为外点(outlier),记所有内点组成的数据集为S1*,S1* 称作 S1的一致性集合
  • 比较当前模型和之前推出的最好的模型的“内点”的数量,记录最大“内点”数量时模型参数和“内点”数量
  • 重复1-4步,直到迭代结束或者当前模型已经足够好了(“内点数目大于设定的阈值”);每次产生的模型要么因为内点太少而被舍弃,要么因为比现有的模型更好而被选用
  • 其过程如下图所示:
    取点集中的两点确定一条直线,然后通过设定规则选取筛选内殿,拿最多的内点拟合出来的模型作为最终的可用模型
    在这里插入图片描述

1.2 迭代次数推导

  • 根据上面RANSAC基本原理的介绍,在这算法流程中存在两个重要的参数需要设置,迭代次数(采样次数)和距离阈值。
    迭代的次数我们应该选择多大呢?这个值是否可以事先知道应该设为多少呢?还是只能凭经验决定呢? 这个值其实是可以估算出来的。下面来推算一下。
    在这里插入图片描述

内点的概率t通常是一个先验值。然后P 是我们希望RANSAC得到正确模型的概率。如果事先不知道t 的值,可以使用自适应迭代次数的方法。也就是一开始设定一个无穷大的迭代次数,然后每次更新模型参数估计的时候,用当前的内点比值当成t 来估算出迭代次数。

1.3 与最小二乘区别

  • 最小二乘法尽量去适应包括外点在内的所有点。因此,最小二乘法只适合与误差较小的情况。假使需要从一个噪音较大的数据集中提取模型(比方说只有20%的数据时符合模型的)时,最小二乘法就显得力不从心了。
    在这里插入图片描述
  • RANSAC相当于一个概率模型,它通过计算内点出现的概率,找出噪点之外的点集拟合出的 最优模型,通常更能表示系统属性。其相当于迭代使用最小二乘法+抽样测试。

1.4 代码实现

  • C++实现:
//====================================================================//
//Program:RANSAC直线拟合,并与最小二乘法结果进行对比
//====================================================================//
#include <iostream>
#include <opencv2/opencv.hpp>


//RANSAC 拟合2D 直线
//输入参数:points--输入点集
//        iterations--迭代次数
//        sigma--数据和模型之间可接受的差值,车道线像素宽带一般为10左右
//              (Parameter use to compute the fitting score)
//        k_min/k_max--拟合的直线斜率的取值范围.
//                     考虑到左右车道线在图像中的斜率位于一定范围内,
//                      添加此参数,同时可以避免检测垂线和水平线
//输出参数:line--拟合的直线参数,It is a vector of 4 floats
//              (vx, vy, x0, y0) where (vx, vy) is a normalized
//              vector collinear to the line and (x0, y0) is some
//              point on the line.
//返回值:无
void fitLineRansac(const std::vector<cv::Point2f>& points,
                   cv::Vec4f &line,
                   int iterations = 1000,
                   double sigma = 1.,
                   double k_min = -7.,
                   double k_max = 7.)
{
    unsigned int n = points.size();

    if(n<2)
    {
        return;
    }

    cv::RNG rng;
    double bestScore = -1.;
    for(int k=0; k<iterations; k++)
    {
        int i1=0, i2=0;
        while(i1==i2)
        {
            i1 = rng(n);
            i2 = rng(n);
        }
        const cv::Point2f& p1 = points[i1];
        const cv::Point2f& p2 = points[i2];

        cv::Point2f dp = p2-p1;//直线的方向向量
        dp *= 1./norm(dp);
        double score = 0;

        if(dp.y/dp.x<=k_max && dp.y/dp.x>=k_min )
        {
            for(int i=0; i<n; i++)
            {
                cv::Point2f v = points[i]-p1;
                double d = v.y*dp.x - v.x*dp.y;//向量a与b叉乘/向量b的摸.||b||=1./norm(dp)
                //score += exp(-0.5*d*d/(sigma*sigma));//误差定义方式的一种
                if( fabs(d)<sigma )
                    score += 1;
            }
        }
        if(score > bestScore)
        {
            line = cv::Vec4f(dp.x, dp.y, p1.x, p1.y);
            bestScore = score;
        }
    }
}

int main()
{
    cv::Mat image(720,1280,CV_8UC3,cv::Scalar(125,125,125));

    //以车道线参数为(0.7657,-0.6432,534,548)生成一系列点
    double k = -0.6432/0.7657;
    double b = 548 - k*534;

    std::vector<cv::Point2f> points;

    for (int i = 360; i < 720; i+=10)
    {
        cv::Point2f point(int((i-b)/k),i);
        points.emplace_back(point);
    }

    //加入直线的随机噪声
    cv::RNG rng((unsigned)time(NULL));
    for (int i = 360; i < 720; i+=10)
    {
        int x = int((i-b)/k);
        x = rng.uniform(x-10,x+10);
        int y = i;
        y = rng.uniform(y-30,y+30);
        cv::Point2f point(x,y);
        points.emplace_back(point);
    }

    //加入噪声
    for (int i = 0; i < 720; i+=20)
    {
        int x = rng.uniform(1,640);
        int y = rng.uniform(1,360);

        cv::Point2f point(x,y);
        points.emplace_back(point);
    }





    int n = points.size();
    for (int j = 0; j < n; ++j)
    {
        cv::circle(image,points[j],5,cv::Scalar(0,0,0),-1);
    }


    //RANSAC 拟合
    if(1)
    {
        cv::Vec4f lineParam;
        fitLineRansac(points,lineParam,1000,10);
        double k = lineParam[1] / lineParam[0];
        double b = lineParam[3] - k*lineParam[2];

        cv::Point p1,p2;
        p1.y = 720;
        p1.x = ( p1.y - b) / k;

        p2.y = 360;
        p2.x = (p2.y-b) / k;

        cv::line(image,p1,p2,cv::Scalar(0,255,0),2);
    }


    //最小二乘法拟合
    if(1)
    {
        cv::Vec4f lineParam;
        cv::fitLine(points,lineParam,cv::DIST_L2,0,0.01,0.01);
        double k = lineParam[1] / lineParam[0];
        double b = lineParam[3] - k*lineParam[2];

        cv::Point p1,p2;
        p1.y = 720;
        p1.x = ( p1.y - b) / k;

        p2.y = 360;
        p2.x = (p2.y-b) / k;

        cv::line(image,p1,p2,cv::Scalar(0,0,255),2);
    }




    cv::imshow("image",image);
    cv::waitKey(0);

    return 0;
}

在这里插入图片描述

  • Python 实现:
#!/usr/bin/env python3
#coding=utf-8

#============================#
#Program:RANSAC_Line.py
===========#

import numpy as np
import random
import math

import cv2

def fitLineRansac(points,iterations=1000,sigma=1.0,k_min=-7,k_max=7):
    """
    RANSAC 拟合2D 直线
    :param points:输入点集,numpy [points_num,1,2],np.float32
    :param iterations:迭代次数
    :param sigma:数据和模型之间可接受的差值,车道线像素宽带一般为10左右
                (Parameter use to compute the fitting score)
    :param k_min:
    :param k_max:k_min/k_max--拟合的直线斜率的取值范围.
                考虑到左右车道线在图像中的斜率位于一定范围内,
                添加此参数,同时可以避免检测垂线和水平线
    :return:拟合的直线参数,It is a vector of 4 floats
                (vx, vy, x0, y0) where (vx, vy) is a normalized
                vector collinear to the line and (x0, y0) is some
                point on the line.
    """
    line = [0,0,0,0]
    points_num = points.shape[0]

    if points_num<2:
        return line

    bestScore = -1
    for k in range(iterations):
        i1,i2 = random.sample(range(points_num), 2)
        p1 = points[i1][0]
        p2 = points[i2][0]

        dp = p1 - p2 #直线的方向向量
        dp *= 1./np.linalg.norm(dp) # 除以模长,进行归一化

        score = 0
        a = dp[1]/dp[0]
        if a <= k_max and a>=k_min:
            for i in range(points_num):
                v = points[i][0] - p1
                dis = v[1]*dp[0] - v[0]*dp[1]#向量a与b叉乘/向量b的摸.||b||=1./norm(dp)
                # score += math.exp(-0.5*dis*dis/(sigma*sigma))误差定义方式的一种
                if math.fabs(dis)<sigma:
                    score += 1
        if score > bestScore:
            line = [dp[0],dp[1],p1[0],p1[1]]
            bestScore = score

    return line



if __name__ == '__main__':
    image = np.ones([720,1280,3],dtype=np.ubyte)*125

    # 以车道线参数为(0.7657, -0.6432, 534, 548)生成一系列点
    k = -0.6432 / 0.7657
    b = 548 - k * 534

    points = []
    for i in range(360,720,10):
        point = (int((i-b)/k),i)
        points.append(point)

    # 加入直线的随机噪声
    for i in range(360,720,10):
        x = int((i-b)/k)
        x = random.sample(range(x-10,x+10),1)
        y = i
        y = random.sample(range(y - 30, y + 30),1)

        point = (x[0],y[0])
        points.append(point)

    # 加入噪声
    for i in range(0,720,20):
        x = random.sample(range(1, 640), 1)
        y = random.sample(range(1, 360), 1)
        point = (x[0], y[0])
        points.append(point)

    for point in points:
        cv2.circle(image,point,5,(0,0,0),-1)


    points = np.array(points).astype(np.float32)
    points = points[:,np.newaxis,:]

    # RANSAC 拟合
    if 1:
        [vx, vy, x, y] = fitLineRansac(points,1000,10)
        k = float(vy) / float(vx)  # 直线斜率
        b = -k * x + y

        p1_y = 720
        p1_x = (p1_y-b) / k
        p2_y = 360
        p2_x = (p2_y-b) / k

        p1 = (int(p1_x),int(p1_y))
        p2 = (int(p2_x), int(p2_y))

        cv2.line(image,p1,p2,(0,255,0),2)

    # 最小二乘法拟合
    if 1:
        [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.1, 0.01)
        k = float(vy) / float(vx)  # 直线斜率
        b = -k * x + y

        p1_y = 720
        p1_x = (p1_y - b) / k
        p2_y = 360
        p2_x = (p2_y - b) / k

        p1 = (int(p1_x), int(p1_y))
        p2 = (int(p2_x), int(p2_y))

        cv2.line(image, p1, p2, (0, 0, 255), 2)


    cv2.imshow('image',image)
    cv2.waitKey(0)

2. 特征匹配

  • 基于特征的图像匹配中会存在误匹配对,因此为提高匹配率,在粗匹配的基础上实现精匹配,可采用下面两种方法:
    在这里插入图片描述
  • 用RANSAC算法来寻找最佳单应性矩阵H,在此先提取SIFT特征点进行最近邻粗匹配,然后采取Ransac进行细匹配,最后再进行变换矩阵求解
  • 代码实现如下:
//RANSAC算法
int main()
{
    Mat img_object = imread("./data/101.png", IMREAD_GRAYSCALE);
    Mat img_scene = imread("./data/100.png", IMREAD_GRAYSCALE);

    if (img_object.empty() || img_scene.empty())
    {
        cout << "Could not open or find the image!\n" << endl;
        return -1;
    }
    //-- Step 1: Detect the keypoints using SURF Detector, compute the descriptors
    int minHessian = 800; // default: 400
    Ptr<SURF> surf = SURF::create(800);
    std::vector<KeyPoint> keypoints_object, keypoints_scene;
    Mat descriptors_object, descriptors_scene;
    surf->detectAndCompute(img_object, noArray(), keypoints_object, descriptors_object);
    surf->detectAndCompute(img_scene, noArray(), keypoints_scene, descriptors_scene);

    //-- Step 2: Matching descriptor vectors with a FLANN based matcher
    // Since SURF is a floating-point descriptor NORM_L2 is used
    Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create(DescriptorMatcher::FLANNBASED);
    std::vector< std::vector<DMatch> > knn_matches;
    matcher->knnMatch(descriptors_object, descriptors_scene, knn_matches, 2);

    //-- Filter matches using the Lowe's ratio test
    const float ratio_thresh = 0.75f;
    std::vector<DMatch> good_matches;
    for (size_t 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]);
        }
    }

    //-- Draw matches
    Mat img_matches;
    drawMatches(img_object, keypoints_object, img_scene, keypoints_scene, good_matches, img_matches, Scalar::all(-1),
        Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);

    //-- Localize the object
    std::vector<Point2f> obj;
    std::vector<Point2f> scene;

    for (size_t i = 0; i < good_matches.size(); i++)
    {
        //-- Get the keypoints from the good matches
        obj.push_back(keypoints_object[good_matches[i].queryIdx].pt);
        scene.push_back(keypoints_scene[good_matches[i].trainIdx].pt);
    }
    vector<uchar>inliers;
    Mat H = findHomography(obj, scene, inliers, RANSAC);


    //-- Draw matches with RANSAC
    Mat img_matches_ransac;
    std::vector<DMatch> good_matches_ransac;
    for (size_t i = 0; i < inliers.size(); i++)
    {
        if (inliers[i])
        {
            good_matches_ransac.push_back(good_matches[i]);
        }
    }
    drawMatches(img_object, keypoints_object, img_scene, keypoints_scene, good_matches_ransac, img_matches_ransac, Scalar::all(-1),
        Scalar::all(-1), std::vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS);
    namedWindow("img_matches", WINDOW_NORMAL);
    imshow("img_matches", img_matches);
    imwrite("img_matches.jpg", img_matches);

    namedWindow("img_matches_ransac", WINDOW_NORMAL);
    imshow("img_matches_ransac", img_matches_ransac);
    imwrite("img_matches_ransac.jpg", img_matches_ransac);
	waitKey();

	return 0;
}
  • 只进行knn匹配与加上Ransac匹配的效果对比图如下:
    在这里插入图片描述

参考:

1.https://blog.csdn.net/leonardohaig/article/details/104570965?spm=1001.2014.3001.5506
2.https://blog.csdn.net/H19981118/article/details/122014318?spm=1001.2014.3001.5506

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1111648.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

两分钟搞懂UiAutomator自动化测试框架

1. UiAutomator简介 UiAutomator是谷歌在Android4.1版本发布时推出的一款用Java编写的UI测试框架&#xff0c;基于Accessibility服务。其最大的特点就是可以跨进程操作&#xff0c;可以使用UiAutomator框架提供的一些方便的API来对安卓应用进行一系列的自动化测试操作&#xf…

Linux程序调试器——gdb的使用

gdb的概述 GDB 全称“GNU symbolic debugger”&#xff0c;从名称上不难看出&#xff0c;它诞生于 GNU 计划&#xff08;同时诞生的还有 GCC、Emacs 等&#xff09;&#xff0c;是 Linux 下常用的程序调试器。发展至今&#xff0c;GDB 已经迭代了诸多个版本&#xff0c;当下的…

C#上位机序列9: 批量读写+事件广播+数据类型处理

一、源码结构&#xff1a; 二、运行效果&#xff1a; 三、源码解析 1. 读取配置文件及创建变量信息&#xff08;点位名称&#xff0c;地址&#xff0c;数据类型&#xff08;bool/short/int/float/long/double&#xff09;&#xff09; 2. 异步任务处理&#xff1a;读任务&…

c++_learning-并发与多线程

并发与多线程 并发&#xff1a;进程&#xff1a;线程&#xff1a;基本概念&#xff1a;线程安全&#xff1a;问题出现的场景&#xff1a;涉及的性质&#xff1a;如何保证线程安全&#xff1f; 并发的实现手段&#xff08;优先使用多线程并发&#xff09;&#xff1a;多进程并发…

【特征重要性揭秘:为什么同一个数据集会有不同结果】

文章目录 特征重要性概要为什么特征重要性分析很重要特征重要性分析方法内置特征重要性(coef_或feature_importances_)Leave-one-out相关性分析递归特征消除 Recursive Feature EliminationXGBoost特性重要性主成分分析 PCA方差分析 ANOVA卡方检验&#xff08;Chi-Square Test&…

求最大公约数的几种常见的方法 【详解】

目录 一、关于公约数 二、计算最大公约数的方法 1. 辗转相除法&#xff08;欧几里得算法&#xff09; 2. 更相减损法&#xff08;辗转相减法&#xff09; 3. 分解质因数法 4. 穷举法 5. 递归法 6. 短除法 三、总结 一、关于公约数 首先 &#xff0c;先介绍一下公约…

matplotlib python 画图教程(2)

1、bar 柱状图 import matplotlib.pyplot as plt import numpy as np n12 xnp.arange(12) y1(1-x/float(n))*np.random.uniform(0.5,1,n) y2(1-x/float(n))*np.random.uniform(0.5,1,n) plt.xlim(-.5,n) plt.ylim(-1.25,1.25) plt.xticks([]) plt.yticks([]) plt.bar(x,y1,fac…

利用TypeScript 和 jsdom 库实现自动化抓取数据

以下是一个使用 TypeScript 和 jsdom 库的下载器程序&#xff0c;用于下载zhihu的内容。此程序使用了 duoip.cn/get_proxy 这段代码。 import { JSDOM } from jsdom; import { getProxy } from https://www.duoip.cn/get_proxy;const zhihuUrl https://www.zhihu.com;(async (…

01、Python 安装 ,Pycharm 安装

目录 安装安装 Python安装 Pycharm 创建项目简单添加文件运行 简单爬取下载小视频 安装 python-3.8.10-amd64.exe – 先安装这个 pycharm-community-2022.2.exe 再安装这个 安装 Python python-3.8.10-amd64.exe 安装&#xff08;这个是其他版本的安装&#xff0c;步骤一样…

Linux安装MINIO

MINIO简介MINIO目录 mkdir -p /opt/minio/data && cd /opt/minio MINIO下载 wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio MINIO授权 chmod x minio MINIO端口 firewall-cmd --zonepublic --add-port7171/tcp --permanent && firewal…

RTOS(6)任务管理

任务状态理论 我们是怎么实现&#xff0c;两个同优先级的任务之间交替执行的呢&#xff1f; 任务切换的基础&#xff1a;tick中断&#xff01; tick为1ms一个周期&#xff0c;可以通过修改时钟配置修改&#xff1b; running&#xff1a;正在进行的任务3为running&#xff…

20231019 filezilla 配置 Windows与Ubuntu文件传输

SFTP协议&#xff0c;传文件&#xff0c;否则会报无权限错

RTOS(7)同步互斥与通信概述

同步与互斥 同步的例子 循环检测有缺陷&#xff0c;还是得blocked掉&#xff0c;不然会很占用cpu&#xff0c;浪费资源&#xff1b; 互斥的例子 单纯的使用全局变量来实现互斥不太靠谱&#xff0c;当执行时间过长的时候会概率性的出现错误 通信的例子 FreeRtos的解决方案 …

Linux操作系统从BIOS到bootloader是如何运行的

操作系统一般都会在安装在硬盘上&#xff0c;在 BIOS 的界面上。你会看到一个启动盘的选项。启动盘有什么特点呢&#xff1f;它一般在第一个扇区&#xff0c;占 512 字节&#xff0c;而且以 0xAA55 结束。这是一个约定&#xff0c;当满足这个条件的时候&#xff0c;就说明这是一…

浅谈轨道交通建筑能耗分析及节能措施

叶根胜 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;面对城市轨道交通的能耗增长&#xff0c;优化地铁车站建筑、降低运营能耗是促进公共交通可持续化发展的必经之路。通风空调系统的能耗占比较大&#xff0c;节能潜力也是大的。本文以上海首条绿色地铁的项目实…

C#计数排序算法

前言 计数排序是一种非比较性的排序算法&#xff0c;适用于排序一定范围内的整数。它的基本思想是通过统计每个元素的出现次数&#xff0c;然后根据元素的大小依次输出排序结果。 实现原理 首先找出待排序数组中的最大值max和最小值min。创建一个长度为max-min1的数组count&a…

售后服务管理升级要怎么做?如何用好售后工单管理系统?

随着数字经济的发展&#xff0c;市场服务趋于多样化&#xff0c;客户对售后服务也有更高的要求&#xff0c;市场竞争日益激烈&#xff0c;越来越多的供应商企业认识到单凭优异的质量&#xff0c;颇具竞争力的价格已不能保证企业的竞争优势&#xff0c;事实表明&#xff0c;用户…

[原创] ElasticSearch集群故障案例分析: 警惕通配符查询

[携程旅行网&#xff1a; 吴晓刚] 许多有RDBMS/SQL背景的开发者&#xff0c;在初次踏入ElasticSearch世界的时候&#xff0c;很容易就想到使用(Wildcard Query)来实现模糊查询&#xff08;比如用户输入补全)&#xff0c;因为这是和SQL里like操作最相似的查询方式&#xff0c;用…

常见面试题-Redis专栏(一)

typora-copy-images-to: imgs了解 redis 中的大key吗&#xff1f;多大算是大key呢&#xff1f;如何解决&#xff1f; 答&#xff1a; redis 的大 key 指的是 key 对应的 value 所占用的内存比较大。 对于 string 类型来说&#xff0c;一般情况下超过 10KB 则认为是大 key&…

waf、yakit和ssh免密登录

WAF安全狗 脏数据适用于所有漏洞绕过waf&#xff0c;但是前提条件垃圾信息必须放在危险信息前&#xff0c;是不能打断原有数据包的结构&#xff0c;不能影响后端对数据包的解析。 以DVWA靶场文件上传为例 新建php文件 上传文件被安全狗拦截 使用bp抓包查看 在数据包Content-…