因为RANSAC不考虑所有可能的样本子集,RANSAC比确定算法运行速度更快,但是它的性能仍然可以通过一些启发式改进。这里我们采用Adaptive Parameter Calculation(自适应参数计算)来增强RANSAC算法。

关于RANSAC可以参考我的这篇文章

Adaptive Parameter Calculation

Adaptive Parameter Calculation(自适应参数计算)

自适应参数计算的主要思想是在运行时,计算内群(inliers)ww 的比例,然后根据新的 ww 的值修改其他参数。

它不仅可以用于降低计算的复杂性,而且可以用于当数据集没有关于 ww 的信息时。

自适应参数计算从最坏的情况开始计算 ww,当找到一致集(consensus set)的数据样本在整个数据集中的比例高于当前 ww 时,重新分配更好的值给 ww。之后用新的 ww 来更新迭代次数 kk 和最小一致阈值 dd

例如,我们可以选择 w=0w=0,这样迭代次数 kk\infty ,最小一致阈值自然为 00

让我们考虑一个问题,当找到一个一致集具有一半的数据集样本时,这样此时的 ww 比开始对 ww 的估计要好,所以将 ww 更新为0.5,并重新计算 kkdd 的值。同时由于 ww 的值较高,kk 肯定会减少,所以改进后的RANSAC算法会在不失去其原有可靠性的前提下,提前结束。

此外,dd 被更新,并且会变为一个更大的值,这将会使用更少的一致集来拟合我们的模型。 因此,它隐含地降低了复杂性。

Basic Flow

以下是RANSAC的自适应参数计算的基本流程:

1
2
3
4
5
6
7
8
k = infinity , i = 0
while k > i
RANSAC()
v = proportion of inliers found
if v > w
w = v
update k and d
increment i by 1

Python 实现

这里实现的时候要略微修改下原有的RANSAC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# -*- coding: utf-8 -*-
"""
Created on Fri May 4 00:31:43 2018

@author: Sikasjc
"""
import numpy as np
import random, sys

class Model(object):
def fit(self, data):
raise NotImplementedError

def distance(self, data):
raise NotImplementedError

class LineModel(Model):
"""
A 2D line model.
"""
def __init__(self):
self.params = None
self.dists = None

def fit(self, data):
"""
Fits the model to the data, minimizing the sum of absolute errors.
"""
X = data[:,0]
Y = data[:,1]
# denom = (X[-1] - X[0])
# if denom == 0:
# raise ZeroDivisionError
# k = (Y[-1] - Y[0]) / denom
# m = Y[0] - k * X[0]
A = np.vstack([X, np.ones(len(X))]).T
k, m = np.linalg.lstsq(A, Y, None)[0]
X_ = np.mean(X)
Y_ = np.mean(Y)
self.params = [k, m]
self.residual = abs(k * X_ + m - Y_)

def distance(self, samples):
"""
Calculates the vertical distances from the samples to the model.
"""
X = samples[:,0]
Y = samples[:,1]
k = self.params[0]
m = self.params[1]
dists = abs(k * X + m - Y)
# dists = abs(-k * X + Y - m) / math.sqrt(k**2 + 1)

return dists

def ransac_APC(data, model, min_samples, min_inliers, eps=1e-10, P = 0.99, random_seed=42):
"""
Fits a model to observed data.

Uses the RANSC iterative method of fitting a model to observed data.
"""
random.seed(random_seed)

if len(data) <= min_samples:
raise ValueError("Not enough input data to fit the model.")

iterations = sys.maxsize
count = 0
min_inliers = 0

best_params = None
best_inliers = None
best_residual = np.inf

while iterations > count:
if 0 < min_inliers and min_inliers < 1:
min_inliers = int(min_inliers * len(data))

indices = list(range(len(data)))
random.shuffle(indices)
inliers = np.asarray([data[i] for i in indices[:min_samples]])
shuffled_data = np.asarray([data[i] for i in indices[min_samples:]])

try:
model.fit(inliers)
dists = model.distance(shuffled_data)
more_inliers = shuffled_data[np.where(dists <= eps)[0]]
inliers = np.concatenate((inliers, more_inliers))

if len(inliers) >= min_inliers:
model.fit(inliers)
if model.residual < best_residual:
best_params = model.params
best_inliers = inliers
best_residual = model.residual

except ZeroDivisionError as e:
print(e)

ratio = len(inliers) / len(data)
if min_inliers/len(data) < ratio:
best_residual = np.inf
min_inliers = ratio
iterations = int(np.log(1 - P) / np.log(1 - min_inliers**min_samples)) + 1
count += 1

if best_params is None:
raise ValueError("RANSAC failed to find a sufficiently good fit for the data.")
else:
return (best_params, best_inliers, best_residual, count)

测试一下,我们实现的自适应参数的RANSAC效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# -*- coding: utf-8 -*-
"""
Created on Fri May 4 00:17:50 2018

@author: Sikasjc
"""
import numpy as np
import matplotlib.pyplot as plt

import random
import ransac_APC
import time

num_samples = 1000
noise_ratio = 0.8
num_noise = int(noise_ratio * num_samples)

def setup():
global ax1
X = np.asarray(range(num_samples))
Y = 0.5 * X
noise = [random.randint(0, 2 * (num_samples - 1)) for i in range(num_noise)]
Y[random.sample(range(len(Y)), num_noise)] = noise
data = np.asarray([X, Y]).T
model = ransac_APC.LineModel()
plt.figure(figsize=(10,5))
ax1 = plt.subplot(1,2,1)
plt.plot(X, Y, 'bx')

return data, model

def run(data, model):
random_seed = random.randint(0,100)
start_time = time.time()
(params, inliers, residual, iterations) = ransac_APC.ransac_APC(data, model, 2, min_inliers = 0.1, eps=1e-10, P=0.99, random_seed=random_seed)
end_time = time.time()
mean_time = (end_time - start_time) / iterations
return params, residual, mean_time, iterations


def summary(params, residual, mean_time, iterations):
print(" Paramters ".center(40, '='))
print(params)
print(" Residual ".center(40, '='))
print(residual)
print(" Iteration ".center(40, '='))
print(iterations, "the number of iterations in the running")
print(" Time ".center(40, '='))
print("%.1f msecs mean time spent per iteration" % (1000 * mean_time))
X = np.asanyarray([0, num_samples - 1])
Y = params[0] * X + params[1]
plt.subplot(1,2,2, sharey = ax1)
plt.plot(X, Y, 'g-', linewidth=2)

plt.show()

if __name__ == '__main__':
data, model = setup()
try:
params, residual, mean_time, iterations = run(data, model)
summary(params, residual, mean_time, iterations)
except ValueError as e:
print(e)
1
2
3
4
5
6
7
8
============== Paramters ===============
[0.5000000000000001, -1.4098251542937202e-15]
=============== Residual ===============
1.135175287103607e-14
============== Iteration ===============
113 the number of iterations in the running
================= Time =================
2.4 msecs mean time spent per iteration

1

从迭代次数中,可以看出RANSAC的运行速度有了明显的提升

Reference

Wang, H., and D. Suter. “Robust Adaptive-Scale Parametric Model Estimation for Computer Vision.” IEEE Transactions on Pattern Analysis and Machine Intelligence 26, no. 11 (November 2004): 1459–74. https://doi.org/10.1109/TPAMI.2004.109.