Image Rectification은 두 이미지 간의 대응점을 찾기 위해 시차 계산을 단순화하는 것이다. 이것은 두 카메라가 수평으로 배치된 경우 두 이미지의 행을 일치시키는 것이 되고, 두 카메라가 수직으로 배치된 경우 두 이미지의 열을 일치시키는 것에 해당한다.
카메라가 이동하거나 회전한다면 epipolar geometry가 변경되기 때문에 Image Rectification을 매번 다시 수행해야 한다.
epipolar geometry를 이용한 Image Rectification
epipolar geometry framework에서 두 카메라가 평행하다고 가정하자. 이때 두 카메라가 동일한 를 가졌다고 하면, 상대적 회전이 없고 (), 축을 따라 translation만 존재하므로 Essential matrix 는 다음과 같이 정의할 수 있다.
가 알려지면, 이미지 평면의 점과 연관된 epipolar line의 방향을 찾을 수 있다. 점 와 연관된 epipolar line 의 방향은 다음과 같이 계산된다.
이에 대해 epipolar 제약조건 을 적용하면 아래와 같고,
가 되어 와 가 동일한 -좌표를 공유한다는 사실을 알 수 있다. 결과적으로 대응점 사이에 매우 간단한 관계가 존재한다. 그러므로 주어진 두 이미지를 평행으로 만드는 과정인 rectification은 stereo matching을 위한 이미지에서 대응점 사이의 관계를 식별할 때 유용하다.
epipole 추정
이미지 쌍을 rectifying 하는데 두 카메라 행렬 이나 그들 사이의 상대적 변환 에 대한 지식이 필요하지 않다. 대신 Normalized Eight Point Algorithm에 의해 추정된 Fundamental matrix를 사용할 수 있다. Fundamental matrix를 얻으면 각 대응점 와 에 대한 epipolar line 와 를 계산할 수 있다.
모든 epipolar line이 epipole을 교차하므로 epipolar line을 구하면, epipole을 과 를 추정할 수 있다. 그러나 실제로는 noisy 측정 때문에 epipolar line이 단일점에서 교차하지 않는다. 따라서 epipole을 찾기 위해 least square를 사용해야 한다.
각 epipolar line은 벡터 로 표현할 수 있고, 직선 위의 모든 점은 를 만족한다. 각 epipolar line을 직선의 방정식의 homogeneous 표현인 로 정의한 다음 선형 시스템의 방정식을 세우고 SVD를 사용하여 epipole 를 찾을 수 있다.
그러나 이렇게 찾은 는 무한대의 점이 아니다. 따라서 를 horizontal axis를 따라 무한대로 매핑하는 별도의 homography 를 찾아야 한다.
homography 추정
Homography 추정
우선 homography 을 찾는 것에서 시작하면, 두 번째 이미지에 대해 homogeneous 좌표에서 중심이 이 되도록 translate 하는 translation matrix를 정의한다.
translation을 적용한 후에 epipole을 horizontal axis 상에 어떤 점 에서 배치하도록 rotation을 적용한다. 평행이동 된 epipole 가 homogeneous 좌표 에 위치한다면 적용되는 rotation은 다음과 같다.
여기서 이면 이고 그렇지 않으면 이다.
rotation을 적용한 후에 의 임의의 점을 horizontal axis 상의 무한대 점 으로 가져오려면 다음의 변환만 적용하면 된다.
이 변환을 적용한 후에 마침내 무한대에 있는 epipole을 갖게 된다.
최종적으로 을 이용해서 homogeneous 좌표를 regular 이미지 공간으로 다시 translate 한다. 따라서 두 번째 이미지를 rectify 하기 위해 적용하는 homography 는 다음 형식이 된다.
Homography 추정
를 찾으면 첫 번째 이미지에 대한 을 찾을 수 있다. 이것은 이미지의 대응점 사이의 제곱 거리의 합을 최소화하는 방식으로 찾을 수 있다.
유도 없이 이 다음 형식을 갖는다는 것을 보인다.
여기서 Fundamental Matrix 이고
여기서 은 나중에 계산될 특정한 벡터 의 성분을 구성한다.
을 알기 전에 임의의 skew-symmetric matrix 에 대해 (scale까지)라는 것을 이용한다. 임의의 cross product matrix 가 skew-symmetric이고 Fundamental matrix 는 scale까지만 알 수 있으므로
이므로 를 다음과 같이 정리할 수 있다.
의 열에 의 임의의 스칼라 곱을 추가해도 은 여전히 scale까지 성립한다. 그러므로 임의의 벡터 에 대해 을 다음과 같이 더 일반적인 형식으로 정의할 수 있다.
로 설정하면 을 매우 잘 정의할 수 있다.
와 의 값을 알고 있으므로, 의 최소화 식에 대해 와 로 치환하면 최소화 문제는 다음이 된다.
과 를 설정하면 최소화 문제는 다음과 같이 바꿀 수 있다.
이 상수 값이므로 최소화 문제는 추가로 다음으로 축소된다.
궁극적으로 이것은 에 대한 least-square 문제 를 해결하는 것으로 분해될 수 있다.
최종적으로 를 계산한 후에 를 계산하고 마지막으로 을 구할 수 있다.
Rectify 절차
최종적으로 이미지 Rectify 절차는 다음과 같다.
1.
Normalized Eight Point Algorithm을 이용하여 Fundamental matrix 계산
2.
Fundamental matrix 이용하여 각 대응점 와 에 대한 epipolar line 와 를 계산
3.
각 epipolar line 을 이용하여 선형 방정식을 세우고 SVD를 해결하여 epipole 계산
4.
를 무한대로 매핑하는 homography 계산
a.
두 번째 이미지에 대해 homogeneous 좌표에서 중심이 이 되도록 translate하는 translation matrix 적용
b.
epipole을 horizontal axis 상에 어떤 점 에서 배치하도록 rotation 하는 rotation matrix 적용
c.
rotation 적용 후 의 점을 horizontal axis 상의 무한대 점 으로 매핑하는 행렬 적용
d.
최종적으로 이미지 좌표로 되돌리도록 translation matrix의 역행렬 적용
5.
를 이용하여 을 무한대로 매핑하는 homography 계산
a.
이미지의 대응점 사이의 제곱 거리의 합을 최소화하는 식 정의. 여기서 로 정의된다.
b.
Fundamental Matrix 를 이용하여 계산.
c.
와 을 이용하여 최소화 문제로 계산
d.
최종적으로 구해진 를 이용하여 계산
카메라 Intrinsic과 Extrinsic을 이용한 Image Rectification
만일 카메라의 Intrinsic과 Extrinsic과 왜곡이 알려져 있다면, Fundamental Matrix와 Epipolar Line을 찾을 필요 없이 Intrinsic과 Extrinsic을 이용하여 Rectify를 직접 수행할 수 있다.
Rotation, Projection, Disparity-to-depth mapping 행렬 계산
우선 rectify를 수행하기 위한 rotation, projection, disparity-to-depth mapping 행렬을 계산해야 한다.
를 두 카메라의 카메라 행렬, 를 두 카메라의 distortion coefficients, 를 두 카메라의 extrinsic(Rotation과 Translation)이라 할 때, 다음의 단계를 통해 rectification을 위한 rotation 행렬과 projection 행렬과 disparity-to-depth 매핑 행렬을 구할 수 있다.
1.
두 카메라의 이미지 중심점 계산
2.
두 카메라의 rotation matrix 계산
첫 번째 카메라의 회전 행렬 을 단위 행렬 로 설정한다.
두 번째 카메라의 회전 행렬 을 두 카메라 간의 회전 행렬 과 첫 번째 카메라의 회전 행렬 을 곱한 것으로 설정한다.
3.
두 카메라의 projection matrix 계산
첫 번째 카메라의 projection matrix 를 카메라 행렬 과 2에서 구한 rotation 행렬 을 이용하여 다음처럼 정의한다.
두 번째 카메라의 projection matrix 를 카메라 행렬 과 2에서 구한 rotation 행렬 과 두 카메라 사이의 translation vector 를 이용하여 다음처럼 정의한다.
4.
Disparity-to-depth mapping matrix 계산
1에서 구한 새로운 이미지 중심점과 focal length 와 두 카메라 사이의 baseline 을 이용하여 Disparity-to-depth mapping 행렬 을 다음처럼 정의한다.
Rectification 계산
다음으로 위에서 계산된 Rotation 행렬과 Projection 행렬을 이용하여 Rectification Map을 생성한다. 이것은 그 자체로 이미지는 아니고, 원본 이미지를 Rectified 이미지로 변환하는데 사용되는 매핑 행렬이다.
1.
왜곡 보정
카메라 intrinsic의 radial distortion 계수 과 tangential distortion 계수 를 사용하여 다음과 같이 Radial Distortion 보정 와 Tangential Distortion 보정 을 수행한다.
여기서 .
2개의 보정값을 구했으면 원래의 값 에 대해 다음처럼 보정된 값 을 구한다.
2.
rectification 변환
왜곡 보정을 수행한 에 대해 Rotation 행렬을 적용하여 새로운 좌표를 구한다.
3.
projection 수행
rectification 변환을 수행한 결과에 대해 Projection 행렬을 적용하여 최종 rectified 좌표를 구한다.
최종적으로 와 를 로 나누어 정규화한다.
이렇게 얻은 는 기존 이미지의 좌표에 해당하는 rectified 이미지의 픽셀 좌표가 된다. 그러나 이 값은 정수가 아니라 실수값이기 때문에 좌표값으로 그대로 사용할 수 없다. 따라서 다음과 같이 후처리를 통해 최종적으로 픽셀 값을 생성한다.
후처리 Interpolation
rectified 된 좌표의 값을 그대로 사용하지 않고 해당 좌표의 인접 픽셀과 보간하여 최종 Rectified Image를 생성한다.
1.
좌표 클리핑
rectified를 통해 얻은 좌표가 원래 이미지의 영역을 벗어날 수 있기 때문에 다음과 같이 clipping한다.
2.
정수와 소수 부분 분리
가 실수값이기 때문에 그대로 좌표로 사용할 수 없으므로 다음과 같이 정수와 소수 부분을 분리한다.
3.
4개 인접 픽셀 값 가져오기
분리한 정수 부분을 이용하여 인접 픽셀의 값을 가져온다. 의 소수 부분이 0에 가까웠다면 이 가장 가까운 픽셀에 해당하고, 소수 부분이 1에 가까웠다면 가 가장 가까운 픽셀에 해당한다.
4.
선형 보간
는 소수 부분에 해당하므로, 4개의 인접 픽셀을 합하는데, 비중을 조절하는 역할을 한다.
Sample Code
위의 절차는 opencv에서 stereoRectify(), initUndistortRectifyMap(), remap() 함수로 구현되어 있다. 아래 코드 참조. stereoRectify()는 두 카메라에 대해 한 번만 수행하지만, initUndistortRectifyMap()와 remap()은 두 카메라에 대해 각각 수행한다는 것에 유의.
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
// 이미지 캡처 (예제 이미지 사용)
Mat img1 = imread("left_image.jpg", IMREAD_GRAYSCALE);
Mat img2 = imread("right_image.jpg", IMREAD_GRAYSCALE);
if (img1.empty() || img2.empty()) {
cerr << "Could not open or find the images!\n";
return -1;
}
// 카메라 캘리브레이션 파라미터 (예제 데이터)
Mat K1 = (Mat_<double>(3, 3) << 700, 0, 320, 0, 700, 240, 0, 0, 1);
Mat K2 = (Mat_<double>(3, 3) << 700, 0, 320, 0, 700, 240, 0, 0, 1);
Mat D1 = Mat::zeros(1, 5, CV_64F); // 왜곡 계수
Mat D2 = Mat::zeros(1, 5, CV_64F); // 왜곡 계수
Mat R = (Mat_<double>(3, 3) << 0.9998, -0.0176, 0.0045, 0.0176, 0.9998, -0.0045, -0.0045, 0.0045, 1); // 예제 회전 행렬
Mat T = (Mat_<double>(3, 1) << 0.1, 0, 0); // 두 카메라 간의 거리 (baseline)
// Rectification 변환 행렬 계산
Mat R1, R2, P1, P2, Q;
stereoRectify(K1, D1, K2, D2, img1.size, R, T, R1, R2, P1, P2, Q);
// Rectification 맵 생성
// initUndistortRectifyMap()의 output은 2개인데, distortion이 보정된 x값 행렬과 y값 행렬에 해당한다.
Mat map1x, map1y, map2x, map2y;
initUndistortRectifyMap(K1, D1, R1, P1, imageSize, CV_32FC1, map1x, map1y);
initUndistortRectifyMap(K2, D2, R2, P2, imageSize, CV_32FC1, map2x, map2y);
// 이미지 보정 및 변환
Mat rectifiedImage1, rectifiedImage2;
remap(img1, rectifiedImage1, map1x, map1y, INTER_LINEAR);
remap(img2, rectifiedImage2, map2x, map2y, INTER_LINEAR);
// 결과 출력
imshow("Rectified Left Image", rectifiedImage1);
imshow("Rectified Right Image", rectifiedImage2);
waitKey(0);
return 0;
}
C++
복사