해당 내용은 공부 목적으로 작성된 글입니다.
잘못된 부분이나 수정할 부분이 있다면 댓글이나 메일로 알려주시면 확인 후 수정하도록 하겠습니다.
본 포스트는 MVG part1에서 설명한 "Recovery of affine and metric properties from images" 부분을 C++ 코드로 구현한 내용을 리뷰한다. 예제 코드는 우분투 18.04 환경에서 테스트하였으며 cmake 3.16, opencv 4.2, eigen 3.3.7 버전을 사용하여 정상적으로 작동하는 것을 확인하였다. 예제 코드는 링크를 클릭하여 다운로드하면 된다.
Affine & Metric Recfitication

일반적으로 현실에 존재하는 물체를 이미지 평면 상에 프로젝션(촬영)하는 경우 직선의 성질만 보존될 뿐 평행한 성분과 직각인 성분들의 특징은 보존되지 않는다. 이는 projective 변환의 특징이다. affine & metric rectification은 이러한 임의의 이미지가 주어졌을 때 이미지 상에서 평행한 선들과 직교한 선들을 사용하여 affine 성질과 metric 성질을 복원하는 것을 의미한다. affine rectification을 수행하면 평행한 선들의 특징을 복원할 수 있고 metric rectification을 수행하면 서로 직각인 직선들의 특징을 복원할 수 있다.
Input Image Setup

우선, 임의의 직사각형 물체를 촬영한 후 복원하고자하는 직선의 선분들의 픽셀 위치를 알아야 한다. 이 때, 직선들을 1,2,3,4 순서대로 코드에 잘 입력하는 것이 중요한데 헷갈리지 않기 위해 그림판 도구로 각 선분에 직선을 미리 그려본 후 각 직선의 픽셀 위치를 알아낸다. 예제 사진에서는 카메라의 렌즈로 인한 왜곡(lens distortion)은 무시한다.

Affine Rectification

affine 성질을 복원한다는 의미는 실제 세계에서 평행하지만 이미지 평면상에서 projective 변환에 의해 평행하지 않은 두 직선을 다시 복원한다는 의미이다.
임의의 affine homography
무한대 직선 위의 한 점
이 성립하므로
하지만 현실의 카메라를 통해 촬영한 영상에서는 projective 변환이 적용되므로

따라서 위와 같이 투영된 임의의 직선

임의의 한 직선은
다음으로
이 중,
위 형태를 만족하면서
지금까지 affine rectification 순서를 정리하면 다음과 같다.
1. 실제 세계에서 평행한 직선 2쌍의 좌표를 구한다.
2. 평행한 직선 1쌍 당 소실점(=image of point at infinity)
3.
4.
5. 이미지 전체에
Metric Rectification
metric성질을 복원한다는 의미는 실제 세계에서 수직이지만 이미지 평면상에서 projective 변환에 의해 직교하지 않은 두 직선을 다시 복원한다는 의미이다. 이 때, 복원된 이미지는 정확한 스케일 값까지는 알 수 없다(up to similarity, up to scale). 즉, metric rectification은 원본 이미지와 스케일 값만 다른 영상까지 복원한다는 의미이다. 이를 수행하기 위해서는 absolute dual conic
Circular Point
circular point (또는 absolute point)
-
-
임의의 homography
따라서
-
-
Dual Conic Properties
-
이 때,
직선
Absolute Dual Conic
absolute dual conic

이 때, 위 식을
-
-
-
Homography of Dual Conic
dual conic과
앞서 설명한 위 공식에 Homography
이 때,
Image of Absolute Dual Conic
만약
-
-
이를 전개하면 다음과 같다.
최종적으로 projective 변환이 없는 similarity 변환이라고 가정하면
Metric Rectification

앞서 언급한 내용과 같이
-
-
-
이를 다시 전개하면
-
따라서 2개의 수직인 직선 쌍으로부터
-
-
다시 diagonal matrix
다음으로
-
다음으로
지금까지 metric rectification 순서를 정리하면 다음과 같다.
1. 서로 수직인 직선 쌍
2.
3. cholesky 또는 SVD를 통해
4. 이미지에
Code Review
예제 코드는 링크를 클릭하여 다운로드하면 된다. 전체 코드는 다음과 같다.
#include "util.h"
#include <Eigen/Dense>
#include <opencv2/core/core.hpp>
#include <opencv2/core/eigen.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <vector>
void ApplyHomography(cv::Mat &img, cv::Mat &img_out, Eigen::Matrix3d H) {
cv::Mat _H;
cv::eigen2cv(H, _H);
cv::warpPerspective(img, img_out, _H,
cv::Size(img.size().width * 4, img.size().height * 1));
}
int main() {
std::string fn_img =
util::GetProjectPath() + "/picture/affine_metric_rectification/01.png";
cv::Mat img = cv::imread(fn_img, 1);
Eigen::Matrix<double, 8, 3> line_points;
line_points <<
113, 5, 1,
223, 846, 1,
435, 2, 1,
707, 843, 1,
2, 706, 1,
841, 780, 1,
4, 6, 1,
841, 445, 1;
Eigen::Matrix<double, 4, 3> lines;
for (int i = 0; i < 8; i += 2) {
Eigen::Vector3d v1, v2;
v1 = line_points.row(i);
v2 = line_points.row(i + 1);
lines.row(i / 2) = v1.cross(v2);
}
// Affine Rectification-------------------
// intersection of line #1 and #2.
Eigen::Matrix<double, 2, 3> A;
A << lines.row(0), lines.row(1);
Eigen::FullPivLU<Eigen::MatrixXd> lu(A);
Eigen::Vector3d nullspace1 = lu.kernel();
nullspace1 = nullspace1 / nullspace1(2);
// intersection of line #3 and #4.
Eigen::Matrix<double, 2, 3> A2;
A2 << lines.row(2), lines.row(3);
Eigen::FullPivLU<Eigen::MatrixXd> lu2(A2);
Eigen::Vector3d nullspace2 = lu2.kernel();
nullspace2 = nullspace2 / nullspace2(2);
// image of line at infinity.
Eigen::Matrix<double, 2, 3> A3;
A3 << nullspace1.transpose(), nullspace2.transpose();
Eigen::FullPivLU<Eigen::MatrixXd> lu3(A3);
Eigen::Vector3d image_of_line_at_inf = lu3.kernel();
Eigen::Matrix3d H_ar;
H_ar << 1, 0, 0, 0, 1, 0, image_of_line_at_inf.transpose();
// affine rectification.
cv::Mat img_aff;
ApplyHomography(img, img_aff, H_ar);
cv::resize(img_aff, img_aff, cv::Size(), 0.25, 0.25);
cv::imshow("affine", img_aff);
cv::waitKey(1);
// Metric Rectification-------------------
lines.row(0) /= lines.row(0)(2);
lines.row(1) /= lines.row(1)(2);
lines.row(2) /= lines.row(2)(2);
lines.row(3) /= lines.row(3)(2);
// remove last colmun.
Eigen::Vector3d l1 = lines.row(0);
Eigen::Vector3d l2 = lines.row(1);
Eigen::Vector3d m1 = lines.row(3);
Eigen::Vector3d m2 = lines.row(2);
Eigen::Matrix<double, 2, 3> ortho_constraint;
ortho_constraint <<
l1(0) * m1(0),
l1(0) * m1(1) + l1(1) * m1(0),
l1(1) * m1(1),
l2(0) * m2(0),
l2(0) * m2(1) + l2(1) * m2(0),
l2(1) * m2(1);
Eigen::JacobiSVD<Eigen::Matrix<double, 2, 3>> svd(
ortho_constraint, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Vector3d s = svd.matrixV().col(svd.matrixV().cols() - 1);
Eigen::Matrix2d S;
S << s(0), s(1),
s(1), s(2);
Eigen::JacobiSVD<Eigen::Matrix2d> svd2(S, Eigen::ComputeFullU |
Eigen::ComputeFullV);
Eigen::MatrixXd U = svd2.matrixU();
Eigen::MatrixXd D = svd2.singularValues().asDiagonal();
Eigen::Matrix2d K = U * D.cwiseSqrt() * U.transpose();
Eigen::Matrix3d H_mr;
H_mr <<
K(0, 0), K(0, 1), 0,
K(1, 0), K(1, 1), 0,
0, 0, 1;
// metric rectification.
cv::Mat img_metric;
ApplyHomography(img, img_metric, H_mr.inverse() * H_ar);
cv::resize(img_metric, img_metric, cv::Size(), 0.25, 0.25);
cv::imshow("metric", img_metric);
cv::moveWindow("metric", 1000, 0);
cv::waitKey(0);
return 0;
}
How to run
본 예제코드는 cmake 프로젝트로 작성하였으며 Ubuntu 18.04, cmake 3.16, opencv 4.2, eigen 3.3.7 버전에서 정상 작동하는 것을 확인하였다. 다음 순서로 프로그램을 빌드 및 실행하면 된다.
# clone the repository
$ git clone https://github.com/gyubeomim/mvg-example
$ cd mvg-example/affine-metric-rect
# cmake build
$ mkdir build
$ cd build
$ cmake ..
$ make
# run example
$ ./affine_metric_rectification
Code #1
void ApplyHomography(cv::Mat &img, cv::Mat &img_out, Eigen::Matrix3d H) {
cv::Mat _H;
cv::eigen2cv(H, _H);
cv::warpPerspective(img, img_out, _H,
cv::Size(img.size().width * 4, img.size().height * 1));
}
이미지 상의 모든 점을 homography 변환하는 함수이다. eigen 행렬로 구한
-
Code #2
int main() {
std::string fn_img =
util::GetProjectPath() + "/picture/affine_metric_rectification/01.png";
cv::Mat img = cv::imread(fn_img, 1);
Eigen::Matrix<double, 8, 3> line_points;
line_points <<
113, 5, 1,
223, 846, 1,
435, 2, 1,
707, 843, 1,
2, 706, 1,
841, 780, 1,
4, 6, 1,
841, 445, 1;
Eigen::Matrix<double, 4, 3> lines;
for (int i = 0; i < 8; i += 2) {
Eigen::Vector3d v1, v2;
v1 = line_points.row(i);
v2 = line_points.row(i + 1);
lines.row(i / 2) = v1.cross(v2);
}
...

예제 이미지를 불러온 후 이미지 상의 4개의 직선을 구하기 위해 8개의 점의 좌표를 line_points 변수에 입력한다. 이 때, 점들은
다음으로 lines 변수에 4개의 직선을 넣는다. 두 점
를 사용하여 4개의 직선을 구한 후 lines 변수에 넣어준다.
Code #3
int main() {
...
// Affine Rectification-------------------
// intersection of line #1 and #2.
Eigen::Matrix<double, 2, 3> A;
A << lines.row(0), lines.row(1);
Eigen::FullPivLU<Eigen::MatrixXd> lu(A);
Eigen::Vector3d nullspace1 = lu.kernel();
nullspace1 = nullspace1 / nullspace1(2);
// intersection of line #3 and #4.
Eigen::Matrix<double, 2, 3> A2;
A2 << lines.row(2), lines.row(3);
Eigen::FullPivLU<Eigen::MatrixXd> lu2(A2);
Eigen::Vector3d nullspace2 = lu2.kernel();
nullspace2 = nullspace2 / nullspace2(2);
// image of line at infinity.
Eigen::Matrix<double, 2, 3> A3;
A3 << nullspace1.transpose(), nullspace2.transpose();
Eigen::FullPivLU<Eigen::MatrixXd> lu3(A3);
Eigen::Vector3d image_of_line_at_inf = lu3.kernel();
Eigen::Matrix3d H_ar;
H_ar << 1, 0, 0, 0, 1, 0, image_of_line_at_inf.transpose();
// affine rectification.
cv::Mat img_aff;
ApplyHomography(img, img_aff, H_ar);
cv::resize(img_aff, img_aff, cv::Size(), 0.25, 0.25);
cv::imshow("affine", img_aff);
cv::waitKey(1);
...

다음으로 소실점
소실점
-
-
따라서 해당 선형시스템의 null space를 찾으면 이는 곧
다음으로 image of line at infinity
-
-
따라서 해당 선형시스템의 null space를 찾으면 이는 곧
최종적으로 원본 이미지에


affine rectification을 수행하면 위 그림과 같이 평행한 선들이 보존되는 것을 확인할 수 있다.
Code #4
int main() {
...
// Metric Rectification-------------------
lines.row(0) /= lines.row(0)(2);
lines.row(1) /= lines.row(1)(2);
lines.row(2) /= lines.row(2)(2);
lines.row(3) /= lines.row(3)(2);
// remove last colmun.
Eigen::Vector3d l1 = lines.row(0);
Eigen::Vector3d l2 = lines.row(1);
Eigen::Vector3d m1 = lines.row(3);
Eigen::Vector3d m2 = lines.row(2);
Eigen::Matrix<double, 2, 3> ortho_constraint;
ortho_constraint <<
l1(0) * m1(0),
l1(0) * m1(1) + l1(1) * m1(0),
l1(1) * m1(1),
l2(0) * m2(0),
l2(0) * m2(1) + l2(1) * m2(0),
l2(1) * m2(1);
Eigen::JacobiSVD<Eigen::Matrix<double, 2, 3>> svd(
ortho_constraint, Eigen::ComputeFullU | Eigen::ComputeFullV);
Eigen::Vector3d s = svd.matrixV().col(svd.matrixV().cols() - 1);
Eigen::Matrix2d S;
S << s(0), s(1),
s(1), s(2);
Eigen::JacobiSVD<Eigen::Matrix2d> svd2(S, Eigen::ComputeFullU |
Eigen::ComputeFullV);
Eigen::MatrixXd U = svd2.matrixU();
Eigen::MatrixXd D = svd2.singularValues().asDiagonal();
Eigen::Matrix2d K = U * D.cwiseSqrt() * U.transpose();
Eigen::Matrix3d H_mr;
H_mr <<
K(0, 0), K(0, 1), 0,
K(1, 0), K(1, 1), 0,
0, 0, 1;
// metric rectification.
cv::Mat img_metric;
ApplyHomography(img, img_metric, H_mr.inverse() * H_ar);
cv::resize(img_metric, img_metric, cv::Size(), 0.25, 0.25);
cv::imshow("metric", img_metric);
cv::moveWindow("metric", 1000, 0);
cv::waitKey(0);
return 0;
}
다음으로 metric rectification을 수행하기 위해 앞서 구한 서로 직교하는 직선 두 쌍
편의상
다음으로
와 같이 나타낼 수 있고 따라서
-
이 때 전개되는 복잡한 수식들은
이를 통해 homography
따라서 최종적인 metric rectification homography
이를 affine rectification된 이미지에 적용하면 최종적으로 affine & metric rectification된 이미지를 얻을 수 있다. 이를 통해 실제 월드 상 물체와 동일하게 평행한 선과 직교한 선을 가지며 스케일 값을 제외하고(up to scale) 동일한 이미지를 얻을 수 있다.

'Engineering' 카테고리의 다른 글
[MVG] Vanishing Point-based Metric Rectification 예제코드 및 설명 (C++) (0) | 2022.07.03 |
---|---|
[MVG] Zhang's Calibration 예제코드 및 설명 (C++) (0) | 2022.06.29 |
[SLAM] g2o - Alternative Parameterizations 논문 섹션 리뷰 (0) | 2022.06.09 |
[SLAM] g2o example 코드 리뷰 (0) | 2022.06.09 |
[SLAM] Visual LiDAR Odometry and Mapping (V-LOAM) 논문 리뷰 (0) | 2022.06.08 |