RANSAC的基本原理(最小二乘法拟合的改进版)
转载:https://zhuanlan.zhihu.com/p/62238520
RANSAC简介
RANSAC(RAndom SAmple Consensus,随机采样一致)算法是从一组含有“外点”(outliers)的数据中正确估计数学模型参数的迭代算法。“外点”一般指的的数据中的噪声,比如说匹配中的误匹配和估计曲线中的离群点。所以,RANSAC也是一种“外点”检测算法。RANSAC算法是一种不确定算法,它只能在一种概率下产生结果,并且这个概率会随着迭代次数的增加而加大(之后会解释为什么这个算法是这样的)。RANSAC算最早是由Fischler和Bolles在SRI上提出用来解决LDP(Location Determination Proble)问题的。
对于RANSAC算法来说一个基本的假设就是数据是由“内点”和“外点”组成的。“内点”就是组成模型参数的数据,“外点”就是不适合模型的数据。同时RANSAC假设:在给定一组含有少部分“内点”的数据,存在一个程序可以估计出符合“内点”的模型。
RANSAC 与 最小二乘法的区别
从上述的内容我们可以得知其实这两个东西都是用来做拟合的,但是他们之间又有什么关系、有什么区别、应用场景有什么不同呢?
在实际开发或生产实践中,数据都会有一定的偏差,当我们已知两个变量之间的关系是线性回归函数,Y=aX+b,如果我们想知道具体的a和b的值,理论上说我们只需要两个点就能满足需求,但是由于误差的存在,我们任意选取的两个点所求出的a和b可能都不相同,我们最想要的就是最后的理论模型与测试值的误差最小
- 最小二乘法:通过计算最小均方差关于a和b的偏导数为0时的值,在很多情况下最小二乘法就是线性回归的代名词,但是它只适用于误差较小的情况
- RANSAC:在模型已确定且最大迭代次数允许的情况下,RANSAC总是可以找到最优解(对于包含80%误差的数据集,RANSAC的效果远远好过最小二乘法)
综上所述我们可以片面地认为最小二乘法适用于误差小的情况,RANSAC使用与误差稍大且最大迭代次数允许的情况,在图像处理的实际开发中,由于一张图片的像素个数庞大,采用最小二乘法运算量巨大且计算速度很慢
算法基本思想和流程
RANSAC是通过反复选择数据集去估计出模型,一直迭代到估计出认为比较好的模型。
具体的实现步骤可以分为以下几步:
- 选择出可以估计出模型的最小数据集;(对于直线拟合来说就是两个点,对于计算Homography矩阵就是4个点)
- 使用这个数据集来计算出数据模型;
- 将所有数据带入这个模型,计算出“内点”的数目;(累加在一定误差范围内的适合当前迭代推出模型的数据)
- 比较当前模型和之前推出的最好的模型的“内点“的数量,记录最大“内点”数的模型参数和“内点”数;
- 重复1-4步,直到迭代结束或者当前模型已经足够好了(“内点数目大于一定数量”)。
迭代次数推导
这里有一点就是迭代的次数我们应该选择多大呢?这个值是否可以事先知道应该设为多少呢?还是只能凭经验决定呢? 这个值其实是可以估算出来的。下面我们就来推算一下。
假设“内点”在数据中的占比为 t
t=ninliersninliers+noutliers
那么我们每次计算模型使用 N 个点的情况下,选取的点至少有一个外点的情况就是
1−tN
也就是说,在迭代 k 次的情况下, (1−tn)k 就是 k 次迭代计算模型都至少采样到一个“外点”去计算模型的概率。那么能采样到正确的 N 个点去计算出正确模型的概率就是
P=1−(1−tn)k
通过上式,可以求得
k=log(1−P)log(1−tn)
“内点”的概率 t 通常是一个先验值。然后 P 是我们希望RANSAC得到正确模型的概率。如果事先不知道 t 的值,可以使用自适应迭代次数的方法。也就是一开始设定一个无穷大的迭代次数,然后每次更新模型参数估计的时候,用当前的“内点”比值当成 t 来估算出迭代次数。
用Python实现直线拟合
import numpy as np
import matplotlib.pyplot as plt
import random
import math
# 数据量。
SIZE = 50
# 产生数据。np.linspace 返回一个一维数组,SIZE指定数组长度。
# 数组最小值是0,最大值是10。所有元素间隔相等。
X = np.linspace(0, 10, SIZE)
Y = 3 * X + 10
fig = plt.figure()
# 画图区域分成1行1列。选择第一块区域。
ax1 = fig.add_subplot(1,1, 1)
# 标题
ax1.set_title("RANSAC")
# 让散点图的数据更加随机并且添加一些噪声。
random_x = []
random_y = []
# 添加直线随机噪声
for i in range(SIZE):
random_x.append(X[i] + random.uniform(-0.5, 0.5))
random_y.append(Y[i] + random.uniform(-0.5, 0.5))
# 添加随机噪声
for i in range(SIZE):
random_x.append(random.uniform(0,10))
random_y.append(random.uniform(10,40))
RANDOM_X = np.array(random_x) # 散点图的横轴。
RANDOM_Y = np.array(random_y) # 散点图的纵轴。
# 画散点图。
ax1.scatter(RANDOM_X, RANDOM_Y)
# 横轴名称。
ax1.set_xlabel("x")
# 纵轴名称。
ax1.set_ylabel("y")
# 使用RANSAC算法估算模型
# 迭代最大次数,每次得到更好的估计会优化iters的数值
iters = 100000
# 数据和模型之间可接受的差值
sigma = 0.25
# 最好模型的参数估计和内点数目
best_a = 0
best_b = 0
pretotal = 0
# 希望的得到正确模型的概率
P = 0.99
for i in range(iters):
# 随机在数据中红选出两个点去求解模型
sample_index = random.sample(range(SIZE * 2),2)
x_1 = RANDOM_X[sample_index[0]]
x_2 = RANDOM_X[