随机采样一致性(Random Sample Consensus,RANSAC)由斯坦福国际研究院的Fischler和Bolles于1981年首次提出[1]。RANSAC算法是一种随机参数估计迭代算法;从一组包含异常数据的样本数据集中,通过迭代的方式,估计已知数学模型的参数,并得到有效样本数据的算法;也可以将其理解为一种点集中离散值的检测方法。
RANSAC存在2个假设:
①数据由“内点”(inliers)和“外点”(outliers)组成。“内点”是参与模型拟合的数据;“外点”是异常数据,不适用于或不符合模型的数据,噪声等。
②给定一组含有较少“内点”的数据,存在一个可以估计模型参数的过程或程序,得到的模型参数可以解释或适用于局内点。
RANSAC是数理统计规律和几何规律结合的算法,它对粗差有较好的抵抗力;但是,RANSAC是一种不确定的算法,不一定得到最佳结果,为了得到更佳的结果,必须提高迭代次数。
RANSAC通过反复选择数据集,估计模型,直到迭代出符合要求的模型。
①随机选择用于模型估计的最小数据集Q:即随机选择M个点用于拟合2D直线(n≤M≤N),一般令M=n。
②使用数据集Q计算模型L,得到k和b。
③将所有数据P带入模型L,计算“内点”比例:即计算点到直线的距离D,若D≤DisT,则该点为“内点”;计算得到该模型的“内点”比例为Pro_current。
④之前最好模型的“内点”比例与当前模型的“内点”比例比较大小,记录较大者的模型参数和“内点”。
⑤重复1-4步,直到迭代结束或者当前模型的“内点”比例大于ProT。得到模型参数和“内点”等。
以上是RANSAC的基本流程,为了增加结果精确度,可以和最小二乘等方法结合,例如,利用最后的“内点”数据,通过最小二乘法或其他方法,拟合模型,得到更佳的模型参数。
我们需要根据特定的问题和数据集通过实验来确定参数;然而迭代次数可以从理论结果推断。假设“内点”在数据中的占比为Pro_current
通常情况下,我们事先并不知道Pro_current的值,但是可以给出一些鲁棒的值。假设估计模型需要M个点,Pro_currentM是M个点均是内点的概率。1-Pro_currentM是M个点中至少有一个点为局外点的概率。此时表明我们从数据集中估计出了一个不好的模型。
(1-Pro_currentM)iter_number表示算法永远都不会选择到n个点均为局内点的概率,就代表永远无法达到期望的内点比例,即
上式取对数,得:
值得注意的是,这个结果假设M个点都是独立选择的;也就是说,某个点被选定之后,它可能会被后续的迭代过程重复选定到。这种方法通常都不合理,由此推导出的iter_number被看作是选取不重复点的上限。例如,要从上图中的数据集寻找适合的直线,RANSAC算法通常在每次迭代时选取2个点,计算通过这两点的直线maybe_model,要求这两点必须唯一。为了得到更可信的参数,标准偏差或它的乘积可以被加到iter_number上。iter_number的标准偏差定义为:
分享给有需要的人,代码质量勿喷
import numpy as np
import random
import math
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
#region data
N = 40
X = np.linspace(start = 0, stop = 20, num = N)
k = b = 2
Y = k * X + b + np.random.randn(N) * 3
data_X = []
data_Y = []
for i in range(N):
data_X.append(X[i])
data_Y.append(Y[i])
#添加粗差
for i in range(5):
data_X.append(i+15)
data_Y.append(1)
N = len(data_X)
#endregion
#region 直接用最小二乘拟合直线
# https://blog.csdn.net/xinjiang666/article/details/103782544
kLS = bLS = 0
sumX = sumY = 0
sumXX = sumXY = 0
for i in range(len(data_X)):
xi = round(data_X[i])
yi = round(data_Y[i])
sumX += xi
sumY += yi
sumXX += xi * xi
sumXY += xi * yi
deltaX = sumXX * N - sumX * sumX
if (abs(deltaX) > 1e-15):
kLS = (sumXY * N - sumX * sumY) / deltaX
bLS = (sumXX * sumY - sumX * sumXY) / deltaX
YLS_pred = kLS * np.asarray(data_X) + bLS
#endregion
#region RANSAC
#region 设置先验参数或期望
n = M = 2 #模型拟合所需的最小点数
Iter_Number = 1000 #最大迭代次数
DisT = 3 #点到直线的距离阈值
ProT = 0.95 #期望的最小内点比例
#endregion
best_k = 0
best_b = 0
best_inliers_number = N*0.1
best_X_inliers=[]
best_Y_inliers=[]
iter = 0
existed_k_b=[] #已经使用过的k、b
#region 5 迭代
for i in range(Iter_Number):
iter += 1
#region 1 随机取M个点用于模型估计
indx1, indx2 = random.sample(range(len(data_X)), M)
point1_x = data_X[indx1]
point1_y = data_Y[indx1]
point2_x = data_X[indx2]
point2_y = data_Y[indx2]
#endregion
#region 2 计算模型参数
k = round((point2_y - point1_y) / (point2_x - point1_x), 3)
b = round(point2_y - k * point2_x, 3)
current_k_b = [k,b]
if current_k_b in existed_k_b:#防止重复的两个点或参数用于计算
continue
else:
existed_k_b.append([k,b])
#endregion
#region 3 计算内点数量(或比例)
X_inliers = []
Y_inliers = []
inliers_current = 0 #当前模型的内点数量
for j in range(len(data_X)):
x_current = data_X[j]
y_current = data_Y[j]
dis_current = abs(k * x_current - y_current + b) / math.sqrt(k * k + 1) # 点到拟合直线的距离dis_current
if (dis_current <= DisT):
inliers_current += 1
X_inliers.append(x_current)
Y_inliers.append(y_current)
print("第{}次, 内点数量={}, 最佳内点数量={}"
.format(iter, inliers_current, best_inliers_number))
#endregion
#region 4 如果当前模型的内点数量大于之前最好的模型的内点数量,跟新模型参数和迭代次数
if (N>inliers_current >= best_inliers_number):
Pro_current = inliers_current / N #当前模型的内点比例Pro_current
best_inliers_number = inliers_current #更新最优内点的数量
best_k = k #更新模型参数
best_b = b #更新模型参数
i = 0 #当前迭代置为0
Iter_Number = math.log(1 - ProT) / math.log(1 - pow(Pro_current, M)) #更新迭代次数
best_X_inliers = X_inliers #更新内点
best_Y_inliers = Y_inliers #更新内点
print("更新结果:k={}, b={}, 当前内点比例={}, 最佳内点比例={}, 新的迭代次数={}"
.format(best_k, best_b, Pro_current, best_inliers_number/N, Iter_Number))
# endregion
#region plot
ax.cla()
ax.set_title("RANSAC and LS")
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.xlim((0, 20))
plt.ylim((0, 50))
plt.grid(True)
#points
ax.scatter(data_X, data_Y, color='k')
#LS fit line
ax.plot(data_X, YLS_pred, color='b', linewidth=3)
#RANSAC fit line
Y_R_pred = best_k * np.asarray(data_X) + best_b
ax.plot(data_X, Y_R_pred, color='g', linewidth=5)
plt.legend([
'LS fit line',
'RANSAC fit line',
'point data',
])
plt.pause(0.001)
#endregion
#region XXX 如果最佳的内点比例大于期望比例,则跳出
if ((best_inliers_number / N) > ProT):
print("终止:内点比例=", (best_inliers_number / N), "大于 期望内点比例=", ProT)
break
#endregion
#endregion
#endregion
#region plot inliers
plt.plot(best_X_inliers, best_Y_inliers, "ro")
plt.legend([
'LS',
'RANSAC',
'inlier',
'outlier',
])
plt.show()
#endregion
[1] Fischler M A, Bolles R C. Random Sample Consensus: A Paradigm for Model Fitting with Applications to Image Analysis and Automated Cartography[J]. Communications of the ACM, 1981, 24(6): 381-395.
[2] RANSAC算法理解
[3] RANSAC算法详解(附Python拟合直线模型代码)