RANSAC (RANdom SAmple Consensus) 개념 및 실습
2022, Mar 20
- 참조 : https://gnaseel.tistory.com/33
- 참조 : https://scikit-learn.org/stable/auto_examples/linear_model/plot_ransac.html
- 참조 : https://www.youtube.com/watch?v=Q7FqV_bglHo
- 참조 : https://github.com/anubhavparas/ransac-implementation
목차
-
RANSAC 이란
-
RANSAC의 필요성
-
RANSAC 개념
-
RANSAC의 파라미터 셋팅법
-
Early Stop 사용법
-
RANSAC의 장단점
-
RANSAC Python Code
-
Lo-RANSAC 개념
-
Lo-RANSAC Python Code
-
Computer Vision에서의 RANSAC 활용
RANSAC 이란
RANSAC
은Random Sample Consensus
의 줄임말로 데이터를 랜덤하게 샘플링하여 사용하고자 하는 모델을 fitting한 다음 fitting 결과가 원하는 목표치 (합의점, Consensus)에 도달하였는 지 확인하는 과정을 통해 모델을 데이터에 맞게 최적화하는 과정을 의미합니다.- 따라서
RANSAC
은 특정 모델식이나 알고리즘을 의미하는 것은 아니며① 데이터 샘플링
,② 모델 fitting
,③ 목표치 도달 확인
이라는 3가지 과정을 반복적으로 수행하는 과정을 의미합니다. - 선형 함수 모델, 다항 함수 모델, 다양한 비선형 함수 모델 등 어떤 모델과 상관없이
모델 fitting
과정을 적용하면 되므로RANSAC
은 모델을 fitting 하는 일종의Framework
라고 말하기도 합니다.
RANSAC의 필요성
- 앞의 설명을 참조하면
RANSAC
이라는 방법론을 사용하지 않더라도 모델 fitting이라는 과정이 있기 때문에 굳이RANSAC
절차를 거치지 않아도 됩니다. 그럼에도 불구하고RANSAC
이 널리 사용되는 이유는 무엇일까요?
RANSAC
이 널리 사용되는 이유는 다음 3가지를 동시에 만족하는 유연한 방법론이기 때문입니다.- ①
outliers
에 강건한 모델을 만들 수 있습니다. - ②
outliers
에 강건한 모델을 만드는 방법 중 매우 단순한 방법이므로 구현이 쉽고 응용하기도 쉽습니다. - ③ 어떤 모델을 사용하여도
RANSAC
을 이용할 수 있으므로 임의의 모델을outliers
에 강건하게 만들 수 있습니다.
RANSAC
은 간단한 절차에도outliers
에 강건한 모델 fitting을 할 수 있다는 이유로 널리 사용되고 있습니다.- 특히 컴퓨터 비전 관련 문제 해결 시, 다양한 이유로 노이즈나 오인식이 발생하게 되고 이러한 문제에 강건한 모델 설계를 위하여
RANSAC
은 현재까지 많이 사용되고 있습니다.
RANSAC 개념
- 앞에서 언급한 바와 같이
RANSAC
은① 데이터 샘플링
,② 모델 fitting
,③ 목표치 도달 확인
과정을 거치며 최적의 모델 fitting을 하게 됩니다. - 가장 기본적인
RANSAC
의 과정은 아래 flow-chart와 같습니다.
- ①
Pick n random points
:inlier
와outlier
가 섞여 있는 전체 데이터셋에서 \(n\) 개의 데이터 (포인트)를 랜덤 샘플합니다. \(n\) 의 갯수를 정하는 내용은 본 글의 뒷부분에서 다룰 예정입니다. - ②
Estimate model parameters
: 랜덤 샘플 데이터를 이용하여 사용하고자 하는 모델을 fitting 합니다. 본 글에서는 다항함수(polynomial function)를 사용하므로 다항 함수의 파라미터를 fitting 하는 방법을 사용하여 랜덤 샘플 데이터에 맞춰서 fitting 합니다. - ③
Calculate distance error
: ② 과정을 통해 fitting한 모델과 전체 데이터에 대하여 error를 구합니다. - ④
Count number of inliers
: ③ 과정에서 구한 error를 통해 각 데이터가inlier
인 지outlier
인 지 판단합니다. 이 때 판단하는 근거는threshold
기준을 정하여 판단합니다.threshold
가 크면 많은 실제outlier
또한inlier
가 될 수 있으므로 적당한 수준으로 정해야하며 본 글에서는 실험적으로threshold
를 정하는 방법을 소개합니다. - ⑤
Maximum Inliers ?
:inlier
목표치 또는inlier
데이터 비율의 목표치가 있고 현재 fitting한 모델이 이 목표치를 달성하였다면iteration
을 끝낼 수 있습니다. 이와 같은 방법을early stop
전략이라고 합니다. 만약 목표치를 달성하면 모델을 저장하고 전체 프로세스를 끝냅니다. 만약 목표치를 달성하지 못하였다면iteration
을 반복합니다. - ⑥
N iterations ?
: 위 flow-chart를 통해 최대 \(N\) 번의iteration
을 반복하여RANSAC
과정을 거치게 됩니다. \(N\) 이 커질수록 시도할 수 있는 횟수가 많아지기 때문에 좋은 모델을 선택할 수 있는 가능성이 커집니다. 하지만 그만큼 수행시간이 늘어나게 됩니다. 본 글의 뒷부분에서는 적당한 크기의 \(N\) 을 선택하는 방법을 소개하며 선택된 \(N\) 값을max iteration
횟수로 지정하여 사용합니다.
- 이와 같은 전체 flow를 이용하여 기본적인
RANSAC
이 동작하게 됩니다. 위 flow-chart를 구체적인 예시를 통해 한번 살펴보도록 하겠습니다.
- 위 그림과 같은 데이터 셋이 있다고 가정하겠습니다. 가로축의 값 \(X\) 에 따른 \(y\) 값을 추정하는 모델링에 관한 데이터셋이며 파란색은
inlier
데이터, 빨간색은outlier
데이터입니다.
- 예제에서 ④
Count number of inliers
과정을 위한threshold
는 1로 가정하며 `threshold 계산은 \(\vert y - y_{\text{pred}} \vert\) 로 계산합니다. 그래프 상으로는 세로축 방향 포인트와 선의 길이로 생각하셔도 됩니다. - 본 예제에서는 선형 모델을 사용할 예정이므로 매 iteration마다 샘플링할 데이터의 수는 2개를 사용합니다. 직선을 긋기 위해서는 최소 2개의 데이터가 필요로 하기 때문에 2개의 데이터를 랜덤 샘플링하며
RANSAC
에서는 모델링을 위한 최소 포인트를 샘플링 하는 것이 유리하기 때문에 단 2개의 데이터만 샘플링 합니다. 이와 관련된 내용은 본 글 뒷부분에 자세히 설명되어 있습니다.
- 예제에서는 ⑤
Maximum Inliers ?
의 기준을inlier
데이터의 비율이 50 % 이상인 경우로 가정하겠습니다. 즉, fitting된 모델을 이용하여threshold = 1
을 기준으로inlier
데이터의 비율을 계산하였을 때, 50 % 이상이면iteration
을 종료하겠습니다.
- 먼저 첫번째 예제입니다. 위 그림에서 빨간색 점선 원으로 선택된 2개의 포인트가 랜덤 샘플링된 포인트 입니다. 이 2개의 점을 잇는 선이 fitting한 선형 모델이 됩니다. 이 선을 기준으로
threshold
가 1인 범위를 검은색 점선으로 그렸습니다. - 노란색 점들은 fitting한 모델을 기준으로
threshold
이내에 존재하여inlier
로 판단되는 점들입니다. 위 예제에서는 7개의 점이inlier
로 판된되었으며 전체 데이터에서의 비율은 29.2 % 입니다. 따라서 flow-chart의 종료 조건인 50 % 를 초과하지 못하였으므로RANSAC
을 종료하지 못하고 다시iteration
을 반복하게 됩니다.
- 두번째 예제에서도 동작 방식 및 확인해야 할 점은 첫번째 예제와 동일합니다. 여기서 주의 깊게 볼 점은 새롭게 fitting한 모델의
inlier
갯수가 첫번째 예제 경우보다 더 적다는 점입니다. 이와 같은 경우에는 fitting된 모델이 갱신되지 못합니다. 따라서 첫번째 예제의 fitting한 모델이 아직 까지는 최적의 모델이 됩니다.
- 세번째 예제에서는 첫번째 예제보다
inlier
갯수가 늘어났으므로 최적의 모델은 세번재 예제에서 구한 모델로 갱신이 됩니다. 하지만RANSAC
종료 목표치인inlier
데이터 비율 50 %를 만족하지 못하였으므로iteration
은 계속 반복합니다.
- 네번째 예제에서는 세번째 예제보다
inlier
갯수가 늘어났고inlier
데이터 비율도 66.7 %로 50%를 초과하였습니다. 따라서RANSAC
종료조건을 만족하므로 전체 프로세스를 종료합니다. 네번째 예제에서 구한 모델이 최적의 모델이므로 이 모델의 결과가 최종 갱신됩니다.
- 이와 같은 과정을 거치는 방법이 기본적인
RANSAC
방법입니다. 지금부터는 예제를 설명하면서 언급한 파라미터들을 어떻게 설정하는 지 그 방법에 대하여 살펴보도록 하겠습니다.
RANSAC의 파라미터 셋팅법
- 앞에서 살펴본
RANSAC
의 개념 중 설정해 주어야 하는 파라미터가 3개 있습니다.threshold
와sample size
그리고sampling number
입니다. threshold
는 fitting한 모델을 이용하여 데이터 셋에서의inlier
가 몇 개인지 파악하는 데 사용하는 기준값이었습니다. 이 값을 어떤 값으로 사용하는 지에 따라서 모델의 성능이 달라지게 됩니다. 매우 중요한 값이므로threshold
를 정하는 다양한 접근 방법이 연구가 되었습니다. 사용중인 데이터셋의 통계적인 접근 방법이나 데이터셋에 대한 전문적인 지식으로 접근하는 방법등도 있지만 본 글에서는 가장 간단하고 확실한Grid Search
와 같은 방법으로 여러개의threshold
를 시도해보고 적합한threshold
를 찾는 방법을 사용해 볼 예정입니다.- 여러
threshold
에 따른 모델 fitting 양상을 보는 것이 좋은 모델을 선정하는 데 중요한 역할을 합니다.
- 단,
threshold
에 따른 변화를 살펴보기 위해서는 다른 파라미터인sample size
와sampling number
는 가능한 고정한 후 실험을 하는 것이 편리합니다. 일반적으로sample size
와sampling number
를 설정하기 위해서 다음 식을 이용합니다.
- \[p = 1 - (1 - (1 - e)^m)^{N}\]
- \[p \text{ : Probability of obtaining a sample consisting only of inliers – sampling success}\]
- \[e \text{ : ratio of outliers in dataset}\]
- \[m \text{ : Number of data sampled per time}\]
- \[N \text{ : Number of algorithm iterations}\]
- 위 식이 정의된 배경은 다음과 같습니다.
- \[p(\text{fail once}) = \text{ do not select only inliers}\]
- \[1 - p \text{ : At least, one random sample set is free from outliers}\]
- \[1 - p = 1 - (1 - e)^{m}\]
- \[p(\text{fail N times}) = \text{ select at least one outlier in all N times}\]
- \[1 - p \text{ : At least, one random sample set is free from outliers}\]
- \[1 - p = (1 - (1 - e)^{m})^{N}\]
- \[\therefore \quad p = 1 - (1 - (1 - e)^m)^{N}\]
- 위 식에서 \(p\) 는
inlier
로 이루어진 샘플을 얻을 확률이고 기대하는 값이기도 합니다. 따라서 \(p = 0.99\) 와 같이 매우 큰 값으로 설정합니다. 따라서 \(p\) 는 상수와 비슷하게 사용할 수 있습니다. - 그 다음 \(e\) 는 실제 데이터 확인을 통하여
outlier
의 비율을 확인해서 정할 수 있습니다. 만약outlier
의 비율을 알 수 없으면 보수적으로 0.5로 적용할 수도 있습니다. 실제로outlier
의 비율이 0.5 정도가 된다면 노이즈가 굉장히 많은 데이터이기 때문입니다. 단, 식을 이용하여 전체 경향을 살펴보면outlier
의 비율이 커질수록 시행 횟수가 늘어나게 됩니다. 아래 표의 각 셀은 확률 \(p\) 와 샘플링 횟수 \(m\) (도표에서는 \(s\) 로 표기함)에 따른 필요 시행횟수를 의미합니다. \(p\) 가 커질수록 \(m\) 이 커질수록 시행횟수가 늘어나야 하고outlier
의 비율이 커질수록 시행횟수 또한 커져야 합니다.
- 실제 정해주어야 하는 값은 \(m, N\) 입니다. \(m\) 은 크기가 작을수록 \(p\) 의 값이 1에 가까워지고 \(N\) 은 크기가 클수록 \(p\) 의 값이 1에 가까워 집니다.
- 예를 들어 \(m\) 이 클수록 샘플링 해야 하는 데이터의 수가 많아지기 때문에
outlier
가 선택될 가능성이 더 커지게 됩니다. 즉inlier
로만 이루어진 샘플을 얻을 확률이 낮아지므로 \(p\) 가 작아지게 됩니다. - 실제 값의 변화를 확인하여도 이유로 \(m\) 이 커질수록 \(p\) 가 작아지게 됩니다.\(e\) 가 비율이므로 \(1 - e\) 는 1보다 작은 값이고 \(m\) 만큼 거듭제곱이 되므로 \(m\) 이 커질수록 \((1 - e)^m\) 은 더욱 더 작아지게 됩니다. 따라서 \(p\) 또한 작아지도록 반영됩니다.
- 이러한 이유로 모델링에 필요한 최소 갯수를 샘플링 하는 방법을 많이 사용합니다. 예를 들면 선형 모델을 모델링할 때에는 2개의 샘플만 있으면 되기 때문에 \(m = 2\) 가 될 수 있습니다. 2차 모델의 경우 3개의 샘플이 필요하므로 \(m = 3\) 이 됩니다. 따라서
RANSAC
에 사용되는 모델에 따라서 \(m\) 은 자동으로 결정될 수 있습니다.
- 따라서 \(p = 1 - (1 - (1-e)^{m})^{N}\) 에서 \(N\) 을 제외하고 모두 정할 수 있습니다. 직접적으로 \(N\) 을 구하기 위하여 다음과 같이 식을 정리할 수 있습니다.
- \[\begin{align} N &= \frac{\log{(1 - p)}}{\log{(1 - (1-e)^{m})}} \\ &= \frac{\log{(1 - 0.99)}}{\log{(1 - (1 - 0.5)^{m})}} \end{align}\]
- 위 식을 조건으로 살펴보았을 때, \(m = 1\) 일 때, \(N \approx 7\), \(m = 2\) 일 때, \(N \approx 16\) 등으로 \(m = 3\) 일 때, \(N \approx 34\), \(m = 4\) 일 때, \(N \approx 71\), … 과 같이 급격하게 증가하는 것을 볼 수 있습니다.
- 즉, 모델의 복잡도가 커질수록 모델 fitting을 하기 위한 최소 필요한 샘플링 수가 많아지고 그만큼 반복 수행을 많이 해야 원하는 \(p\) 의 확률로
inlier
데이터를 뽑아낼 수 있습니다. 이러한 이유로 \(N\) 이 커지게 됩니다.
Early Stop 사용법
RANSAC
을 통해 반복적으로 모델을 최적화 할 때, 적합한 모델을 찾았다면 더 이상 모델 fitting 작업을 할 필요가 없습니다. 따라서 다음과 같이 3가지 파라미터를 정하여RANSAC
을 일찍 끝내는Early Stop
을 사용하는 것이 좋습니다. 3가지 파라미터는 다음과 같습니다.
- ①
min iteration
: 모델 fitting을 위한 최소 반복 횟수를 의미합니다. - ②
max iteration
: 모델 fitting을 위한 최대 반복 횟수로 최대 반복 횟수 만큼 반복하면 모델 fitting이 실패하였음을 의미합니다. - ③
stop inlier ratio
: 반복 작업을 끝내기 위한 최소inlier
의 비율을 의미합니다. 따라서inlier
의 비율이stop inlier ratio
를 초과하면RANSAC
작업을 끝냅니다.
- 앞에서 다룬 \(N\) 을 구하는 방법을 통하여 \(N\) 값을 얻은 뒤
max iteration
으로 설정하고stop inlier ratio
의 비율을 예상되는inlier
의 비율을 통하여 정하거나, 실험적으로 의미있는 비율을 정하여 설정하면 효과적으로Early Stop
을 적용할 수 있습니다.
RANSAC의 장단점
- 지금까지 살펴본 내용으로 설명을 하면 ①
RANSAC
의 가장 큰 장점은outlier
에 강건한 모델이라는 점입니다. 이 장점이RANSAC
을 사용하는 가장 큰 이유이기도 합니다. 따라서outlier
가 어느 정도 섞여있어도 그것들을 무시하고 모델링 할 수 있습니다. - 그리고 ②
RANSAC
은outlier
에 강건한 모델을 설계하는 방법 중 가장 쉬운 방법입니다.inlier
의 갯수만 세면 되기 때문에 구현도 쉽고 어떠한 모델이라도 적용하기도 쉽습니다.
- 반면 ①
RANSAC
은 랜덤 샘플이라는 방법을 이용하므로Non-deterministic
하다는 단점이 있습니다. 즉, 같은 데이터 셋을 이용하여 모델링하더라도 매번 실행 결과가 다를 수 있다는 것입니다. 이 점은 관점에 따라서RANSAC
의 장점이 될 수도 있고 단점이 될 수도 있다고 생각합니다. 하지만 모델의 재현성 관점에서는 단점이라고 볼 수 있습니다. - 두번째 단점은
RANSAC
의 가장 치명적인 단점입니다. 만약outlier
가 노이즈 처럼 생기지 않고 특정 분포를 가지게 되면 모델이outlier
를 fitting 할 수 있습니다. 따라서 데이터 셋을 미리 확인하고outlier
가 얼만큼 있는 지와outlier
가 특정 패턴 및 분포를 가지는 지 사전에 확인하는 것은 매우 중요합니다. 만약 특정 분포를 가진다면 오히려 다른 방법으로outlier
를 사전에 제거하는 것도 좋은 접근이 될 수 있기 때문입니다. - 또한
RANSAC
은threshold
파라미터에 큰 영향을 받는다는 단점이 있습니다.threshold
를 너무 작게 설정하면 모델이 안정적으로 fitting이 되지 않을 수 있고 데이터의 변화에 민감하게 반응합니다. 반면threshold
를 크게 잡으면outlier
까지inlier
로 판단할 수 있으므로 모델이 정상적으로 fitting할 수 없게됩니다. 뿐만 아니라inlier
의 분포가 계속 변하게 된다면threshold
기준 또한 adaptive하게 변해야 할 수 있습니다. 이러한 모든 것들을 반영하여threshold
를 정하는 것이 어렵다는 것이RANSAC
의 주요 단점 중의 하나입니다. - 마지막으로
RANSAC
은Loss
를 기반으로 동작하는 L1, L2 Loss와는 다르게Loss
를 기반으로 모델 fitting을 하지 않습니다. 이러한 동작 방식이 다른 알고리즘과 연계되어 한번에Loss
를 계산하는pipeline
에 연결시킬 수 없다는 점이 단점이 될 수 있습니다. 왜냐하면 다양한 알고리즘이 머신러닝, 딥러닝 방식의 학습 방식으로 이루어지기 때문에 같은pipeline
으로 연결할 수 있으면 한번에 전체pipeline
을 학습할 수 있어서 효율적이기 때문입니다.
RANSAC Python Code
- 앞에서 다룬 내용을 파이썬 코드를 통하여 살펴보도록 하겠습니다. 살펴볼 내용은 다항함수를 이용하여 모델 fitting 하는 것이며 전체 데이터셋에서 80%는 약간의 노이즈만 추가하고 20%는 꽤 심한 변형을 주어
outlier
가 되도록 하였습니다. 따라서early stop
의stop inlier ratio
는 0.8을 사용하고자 합니다. - 다항함수를 이용하는 것이므로 본 글에서는
np.polyfit
또는scipy.optimize.curve_fit
을 사용하여 모델 fitting 하는 방법을 사용하였습니다.
- 아래 코드의 전체적인 프로세스는 다음과 같습니다.
- ① 노이즈가 섞인 데이터를 생성합니다. 노이즈 또한 랜덤하게 생성합니다.
- ② \(p = 0.99, e = 0.5\) 인 조건에서
sampling number
\(N\) 을 구하고 이 값을max iteration
으로 사용합니다. 아래 코드의get_sampling_number
함수가 이 내용에 해당합니다. - ③
threshold
후보군을 작은 값부터 사용하여early stop
을 만족하는 만족하는threshold
를 찾습니다. 아래 코드의get_inlier_threshold
함수가 이 내용에 해당합니다. - ④ 새로운 데이터를 생성한 다음에 앞에서 사용한
threshold
와early stop
을 이용하여RANSAC
이 유효하게 동작하는 지 확인합니다. 아래 코드의get_model_with_ransac
함수가 이 내용에 해당합니다.
import numpy as np
import math
import matplotlib.pyplot as plt
def get_sampling_number(sample_size, p=0.99, e=0.5):
'''
sample size : Number of sample size per every iterations
p : Desired probability of choosing at least one sample free of outliers
e : Estimated probability that a point is an outlier
'''
# Calculate the required number of iterations based on the formula
n_iterations_calculated = math.ceil(math.log(1 - p) / math.log(1 - (1 - e)**sample_size))
print(f"Calculated number of iterations: {n_iterations_calculated}")
return n_iterations_calculated
def get_inlier_threshold(thresholds, data, polynomial_degree, sample_size,
min_iteration, max_iteration, stop_inlier_ratio, verbose=False):
early_stop_flag = False
inlier_threshold = None
for threshold in thresholds:
best_fit = None
best_error = 0
for i in range(max_iteration):
# Randomly select sample points
subset = data[np.random.choice(len(data), sample_size, replace=False)]
x_sample, y_sample = subset[:, 0], subset[:, 1]
# Fit a line to the sample points
p = np.polyfit(x_sample, y_sample, polynomial_degree)
# Compute error
y_pred = np.polyval(p, X)
error = np.abs(y - y_pred)
# Count inliers
inliers = error < threshold
n_inliers = np.sum(inliers)
# Update best fit if the current model is better
if n_inliers > best_error:
print("threshold : {}, index : {}, n_inliers : {}".format(threshold, i, n_inliers))
best_fit = p
best_error = n_inliers
if (i > min_iteration) and (n_inliers/len(data)) >= stop_inlier_ratio:
early_stop_flag = True
inlier_threshold = threshold
if early_stop_flag:
break
if verbose:
# Best curve
y_best = np.polyval(best_fit, X)
# Plotting
plt.scatter(X, y, label='Data Points')
plt.plot(X, y_best, color='red', label='RANSAC Fit')
plt.legend()
plt.show()
if early_stop_flag:
break
return inlier_threshold
def get_model_with_ransac(data, polynomial_degree, threshold, sample_size,
min_iteration, max_iteration, stop_inlier_ratio, verbose=False):
early_stop_flag = False
inlier_threshold = None
best_fit = None
best_error = 0
for i in range(max_iteration):
# Randomly select sample points
subset = data[np.random.choice(len(data), sample_size, replace=False)]
x_sample, y_sample = subset[:, 0], subset[:, 1]
# Fit a line to the sample points
p = np.polyfit(x_sample, y_sample, polynomial_degree)
# Compute error
y_pred = np.polyval(p, X)
error = np.abs(y - y_pred)
# Count inliers
inliers = error < threshold
n_inliers = np.sum(inliers)
# Update best fit if the current model is better
if n_inliers > best_error:
best_fit = p
best_error = n_inliers
if (i > min_iteration) and (n_inliers/len(data)) >= stop_inlier_ratio:
early_stop_flag = True
inlier_threshold = threshold
print("index : {}, n_inliers : {}".format(i, n_inliers))
if early_stop_flag:
break
if verbose:
# Best curve
y_best = np.polyval(best_fit, X)
# Plotting
plt.scatter(X, y, label='Data Points')
plt.plot(X, y_best, color='red', label='RANSAC Fit')
plt.legend()
plt.show()
return early_stop_flag, best_fit
- 위에서 정의한 함수들을 이용하면 아래와 같이 실행할 수 있습니다.
# Generate synthetic data
np.random.seed(0)
n_points = 100
X = np.linspace(0, 10, n_points)
y = 3 * X + 10 + np.random.normal(0, 3, n_points)
# Add outliers
n_outliers = 20
X[-n_outliers:] += int(30 * np.random.rand())
y[-n_outliers:] -= int(50 * np.random.rand())
X = np.expand_dims(X, -1)
y = np.expand_dims(y, -1)
data = np.hstack([X, y])
threshold_cadidates = [1,2,4,8,16,32,64,128]
threshold_cadidates.sort()
sample_size = 2
max_iteration = get_sampling_number(sample_size)
threshold = get_inlier_threshold(
threshold_cadidates, data, polynomial_degree=1, sample_size=sample_size,
min_iteration=-1, max_iteration=max_iteration, stop_inlier_ratio=0.50, verbose=True)
- 위 그림과 같이
threshold=4
를 사용하는 것으로 구하였고 위 파라미터를 기준으로RANSAC
을 수행하면 다음과 같습니다.
# Generate synthetic data
np.random.seed(np.random.seed())
n_points = 100
X = np.linspace(0, 10, n_points)
y = 3 * X + 10 + np.random.normal(0, 3, n_points)
# Add outliers
n_outliers = 20
X[-n_outliers:] += int(30 * np.random.rand())
y[-n_outliers:] -= int(50 * np.random.rand())
X = np.expand_dims(X, -1)
y = np.expand_dims(y, -1)
data = np.hstack([X, y])
success, param = get_model_with_ransac(data, polynomial_degree=1, threshold=threshold, sample_size=sample_size,
min_iteration=-1, max_iteration=max_iteration, stop_inlier_ratio=0.75, verbose=True)
- 이번에는 동일한 함수를 이용하여 2차 함수를 모델링 해보겠습니다. 절차는 동일합니다.
import numpy as np
import math
import matplotlib.pyplot as plt
# Generate synthetic data
np.random.seed(0)
n_points = 100
X = np.linspace(-10, 10, n_points)
y = 2 * X**2 + 3 * X + 4 + np.random.normal(0, 10, n_points)
# Add outliers
n_outliers = 20
X[-n_outliers:] += int(30 * np.random.rand())
y[-n_outliers:] -= int(500 * np.random.rand())
X = np.expand_dims(X, -1)
y = np.expand_dims(y, -1)
data = np.hstack([X, y])
threshold_cadidates = [1,2,4,8,16,32,64,128]
threshold_cadidates.sort()
sample_size = 3
max_iteration = get_sampling_number(sample_size)
threshold = get_inlier_threshold(
threshold_cadidates, data, polynomial_degree=2, sample_size=sample_size,
min_iteration=-1, max_iteration=max_iteration, stop_inlier_ratio=0.50, verbose=True)
- 위 그림과 같이
threshold=16
을 사용하는 것으로 구하였고 위 파라미터를 기준으로RANSAC
을 수행하면 다음과 같습니다.
# Generate synthetic data
np.random.seed(np.random.seed())
n_points = 100
X = np.linspace(-10, 10, n_points)
y = 2 * X**2 + 3 * X + 4 + np.random.normal(0, 10, n_points)
# Add outliers
n_outliers = 20
X[-n_outliers:] += int(30 * np.random.rand())
y[-n_outliers:] -= int(500 * np.random.rand())
X = np.expand_dims(X, -1)
y = np.expand_dims(y, -1)
data = np.hstack([X, y])
success, param = get_model_with_ransac(data, polynomial_degree=2, threshold=threshold, sample_size=sample_size,
min_iteration=-1, max_iteration=max_iteration, stop_inlier_ratio=0.50, verbose=True)
Lo-RANSAC 개념
- 지금까지 다룬
RANSAC
의 아쉬운점은 좋은 모델을 찾았음에도 다음 iteration에서 완전히 다른 랜덤 샘플을 추출하기 때문에 이전 시도와 상관없이 새로운 샘플 데이터로RANSAC
을 진행한다는 점입니다. Lo-RANSAC
은Locally Optimized RANSAC
의 줄임말로inlier
데이터는inlier
데이터 주변에 모인다는 특성을 이용하여RANSAC
의 결과에서inlier
들만을 이용하여local
영역에서의 최적화를 더 진행하는 과정을 의미합니다.- 핵심 방법론은 ①
RANSAC
을 수행한 후 ②inlier
를 기준으로 한번 더RANSAC
을 수행하고 ③inlier
로 선별된 데이터를 이용하여local optimization
을 한다는 것입니다. - 연산 과정이 추가되기 때문에 전체 연산이 더 늘어나는 것으로 보일 수 있으나
early stop
전략과 잘 엮어서 쓰면 기본적인RANSAC
에 비해 짧은iteration
만으로도 높은 정확성을 가진 모델을 얻을 수 있습니다.
Lo-RANSAC
의 전체 과정은 다음과 같습니다.- ① \(n\) 개의 데이터를 랜덤 샘플링 합니다.
- ② 랜덤 샘플한 데이터를 이용하여 모델 fitting을 합니다.
- ③ fitting된 모델을 이용하여 전체 데이터의
error
를 계산하고 이 값과threshold
를 이용하여inlier
의 갯수를 카운트합니다. - ④ 기존에 기록된 최대
inlier
갯수를 초과하면 다음 스텝으로 넘어가고 초과하지 못하면 ① 작업으로 돌아갑니다. 최대inlier
갯수를 초과하였다는 뜻은local optimization
을 할 만큼 가치있는 랜덤 샘플링이 되었다는 것을 의미합니다. - ⑤, ⑥, ⑦
inlier
를 기준으로 앞에서 수행한 ①, ②, ③ 작업을 그대로 수행합니다. ①, ②, ③의 경우 전체 데이터에서 랜덤 샘플링한 후RANSAC
작업을 한 것에 반해 ⑤, ⑥, ⑦ 은 한번 모델을 fitting한 결과를 기준으로inlier
에서 랜덤 샘플링한 후RANSAC
작업을 한 것에 차이가 있습니다. - ⑧
inlier
기준으로RANSAC
한 결과가 현재까지 기록된 최대inlier
갯수를 초과하는 지 확인합니다. 초과한다면 다음 스텝으로 넘어가고 초과하지 못하면 ① 작업으로 돌아갑니다. - ⑨ 최대
inlier
를 달성하였으므로 모델을 저장합니다. 이 지점에서 최대inlier
를 달성한 것의 의미는inlier
내에서 샘플링 하는 것이 의미가 있다는 뜻이며inlier
가 유효하다는 것을 의미합니다. 따라서 다음 스텝에서inlier
데이터 전체를 이용한local optimization
을 시도해 볼 수 있는 상황이 됩니다. - ⑩
inlier
데이터 전체를 이용하여local optimization
을 합니다.polynomial
함수의 경우inlier
데이터 전체를 이용하여curve fitting
을 해볼 수 있습니다. 또는Levenberg-Marquardt Optimization
과 같은 최적화 방법론을 이용하여 모델을 최적화할 수 있습니다. - ⑪
local optimization
의 결과가 최대inlier
를 달성하였는 지 확인합니다. - ⑫ 최대
inlier
를 달성하였으므로 모델을 저장합니다. - ⑬ 최대 N 번
iteration
을 반복하였는 지 확인합니다.iteration
을 더 반복해야 하면 ① 과정부터 다시 시작합니다. N번 모두iteration
이 끝났다면Lo-RANSAC
과정을 끝냅니다. - ⑭ 최종적으로 선택된 모델을 저장합니다.
- 위 flow-chart에서 ⑤ ~ ⑪ 과정이 기본적인
RANSAC
과 대비하여 개선된 내용입니다. 다소 연산 과정이 늘어나 보일 수 있지만,early stop
과 같이 쓸 경우iteration
을 몇 번 반복하지 않고 끝낼 수 있으므로 효과가 좋을 수 있습니다.
Lo-RANSAC Python Code
import numpy as np
import math
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
def get_sampling_number(sample_size, p=0.99, e=0.5):
'''
sample size : Number of sample size per every iterations
p : Desired probability of choosing at least one sample free of outliers
e : Estimated probability that a point is an outlier
'''
# Calculate the required number of iterations based on the formula
n_iterations_calculated = math.ceil(math.log(1 - p) / math.log(1 - (1 - e)**sample_size))
print(f"Calculated number of iterations: {n_iterations_calculated}")
return n_iterations_calculated
def polynomial(x, *coeffs):
return np.polyval(coeffs, x)
def fit_polynomial(subset, degree):
x, y = subset[:, 0], subset[:, 1]
params, _ = curve_fit(lambda x, *coeffs: polynomial(x, *coeffs), x, y, p0=[1]*(degree+1))
return params
def count_inliers(model, data, threshold):
x, y = data[:, 0], data[:, 1]
y_pred = np.polyval(model, x)
return np.where(np.abs(y - y_pred) < threshold)[0]
def local_optimization(model, inliers, degree):
return fit_polynomial(inliers, degree)
def lo_ransac_polynomial(data, degree=2, min_iterations=-1, max_iterations=100, threshold=1.0, stop_inlier_ratio=0.9):
best_model = None
max_inliers = 0
total_data = len(data)
for i in range(max_iterations):
# Step 1: Random Sampling
subset = data[np.random.choice(total_data, degree+1, replace=False)]
# Step 2: Fit Initial Model
model = fit_polynomial(subset, degree)
# Step 3: Count Inliers
inliers_idx = count_inliers(model, data, threshold)
inliers = data[inliers_idx]
# Step 4: Check Initial Model
if len(inliers) < max_inliers:
continue
# Step 5: Random Sampling within inliers
inner_subset = inliers[np.random.choice(len(inliers), degree+1, replace=False)]
# Step 6: Fit Model within inliers
inner_model = fit_polynomial(inner_subset, degree)
# Step 7: Count Inliers
inner_inliers_idx = count_inliers(inner_model, data, threshold)
inner_inliers = data[inner_inliers_idx]
# Step 8: Check Inner RANSAC Model and Save Best Model
if len(inner_inliers) > max_inliers:
best_model = inner_model
max_inliers = len(inner_inliers)
else:
continue
# Step 9: Local Optimization with inliers
refined_model = fit_polynomial(inner_inliers, degree)
refined_inliers = count_inliers(refined_model, data, threshold)
# Step 10: Check Local Optimization and Save Best Model
if len(refined_inliers) > max_inliers:
best_model = refined_model
max_inliers = len(refined_inliers)
# Step 11: Early stopping condition based on inlier ratio
inlier_ratio = max_inliers / total_data
if i >= min_iterations and inlier_ratio >= stop_inlier_ratio:
print(f"Early stopping at iteration {i}, inlier ratio: {inlier_ratio}")
break
print(f"Result : iteration {i}, inlier ratio: {inlier_ratio}")
return best_model
# Generate example data
np.random.seed(np.random.seed())
n_points = 100
X = np.linspace(-10, 10, n_points)
y = (np.random.random()*10)*X**2 + (np.random.random()*10)*X + np.random.normal(0, 10, n_points)
# Add outliers
n_outliers = 20
X[-n_outliers:] += int(10 * np.random.rand())
y[-n_outliers:] -= int(20 * np.random.rand())
X = np.expand_dims(X, -1)
y = np.expand_dims(y, -1)
data = np.hstack([X, y])
sample_size = 3
max_iteration = get_sampling_number(sample_size)
# Run Lo-RANSAC
best_model = lo_ransac_polynomial(data, degree=2, min_iterations=-1, max_iterations=max_iteration, threshold=16.0, stop_inlier_ratio=0.6)
# Plot the data and the best model
plt.scatter(data[:, 0], data[:, 1], label='Data Points')
x_values = np.linspace(min(data[:, 0]), max(data[:, 0]), 400)
y_values = np.polyval(best_model, x_values)
plt.plot(x_values, y_values, color='r', label=f'Best Model')
plt.legend()
plt.xlabel('X')
plt.ylabel('Y')
plt.title('Lo-RANSAC Polynomial Fitting with Early Stopping')
plt.show()
Computer Vision에서의 RANSAC 활용
- 참조 : https://www.ipb.uni-bonn.de/html/teaching/msr2-2020/sse2-11-ransac.pdf
- 컴퓨터 비전에서
RANSAC
을 사용하는 대표적인 사례는 유사한 2개의 이미지에서 같은 위치를 나타내는 점들을 이용하여homography
\(H\) 행렬을 구하거나fundamental matrix
등을 구하는 것에 많이 사용됩니다. 왜냐하면 각각의 이미지에서 같은 위치의 점인Feature point
를 찾고 어떤 점이 같은 점인 지 매칭을 해주는Feature matching
단계까지outlier
들이 많이 발생하기 때문입니다. - 따라서
outlier
에 강건한RANSAC
을 많이 사용합니다. 다음 2가지 예제를 살펴보도록 하겠습니다.
- 위 예제에서는 2개의 이미지를 이어붙이는 이미지 스티칭을 위하여 연결해야 하는 지점을 찾은 후 오른쪽 이미지를 왼쪽 이미지에 이어 붙일 수 있도록 변환하는
homography
를 찾는 과정으로 볼 수 있습니다. - 2번째 과정에서의
feature matning
결과를 보면 잘 연결이 된 점들도 있지만 연결이 잘못된outlier
들도 생긴것을 볼 수 있습니다. 이러한 점들을 제거한 후homography
를 구하고자RANSAC
을 사용합니다.
- 이번 예제도 앞의 예시와 유사합니다. 촬영 위치가 다른 2개의 이미지에서 같은 이미지 지점을 찾는 것이 목적이 됩니다. 따라서
feature point
를 추출하여 이미지 상의 어떤 지점을 기준으로 같은 이미지 지점을 비교할 지 정합니다. 그 다음feature matching
을 이용하여feature
들을 연결한 것이 두번째 그림입니다. 이번 예시에서도 잘 연결된 feature들이 있지만 잘못 연결된 점들도 보입니다. 이러한 점들을inlier
,outlier
로 구분하여 필요한inlier
를 사용하는 것이RANSAC
의 실제 사용 사례가 될 수 있습니다.