본 페이지는 OpenCV 수업을 하기 이전에 강의했던 부분으로 순수 C코드로 구성한 영상처리 라이브러리로 캘리브레이션을 수행하기 위해 사용했던 방법이다. 현재에도 회사에서 사용하는 방법 중 하나이기도 하다.
호모그래피 http://www.slideshare.net/mimimau/introduction-to-homography
호모그래프 https://towardsdatascience.com/understanding-homography-a-k-a-perspective-transformation-cacaed5ca17
호모그래피 http://6.869.csail.mit.edu/fa17/lecture/lecture14sift_homography.pdf
Homography = Perspective projection transform
호모그래피를 사용하여 영상 보정을 해보자. 왼쪽 영상은 보정 전이고 좌표를 설정하여 H 변환하면 오른쪽 영상처럼 보정을 한다.
X는 실제 세계의 평면에 있는 한 점이고, x는 영상좌표에 있는 한 점이라고 하면 다음 그림과 같은 Homography와 같은 관계식으로 표현할 수 있다.
Homography의 식을 살펴보면 x'을 H로 변환하여 x를 구한다.
Ax=b 공식에서 A는 입력 영상 b는 출력 영상이라고 했을 때 x 계수를 구한다.
위 식에서 H를 구하기 위해서는 4개의 점 (x1,y1)~(x4,y4)이 필요하다. 이 점으로 식을 구성하면 다음과 같다.
위 식에서 h를 x 벡터라고 놓고 Ax=b에서 x를 구하고자 한다. 역행렬을 이용하여 구한다. (x = A^-1 * b)
x=A^-1b 식에서 유사 역행렬 (Pseudo Inverse Matrix) 을 이용하여 다음과 같이 변환한다.
보충설명
0. 프로그램에 필요한 수학 라이브러리
다음 소스를 다운받아 project 폴더에 복사하고 include "./ezmtl/Matrix.h" 를 소스에 추가한다. 혹 max,min관련 에러가 나면 다음과 같이 undefine 해준다.
| #ifdef max #undef max #endif #ifdef min #undef min #endif #include "./ezmtl/Matrix.h" |
수학 라이브러리 (v6.0 사용가능)
수학 라이브러리 (vs2013 사용가능) - 수정함. C.H.Han
Homography계산 (vs2015 가능) - 콘솔에서 Homography 계산하기
Homography_console 프로그램을 실행시킨 결과 영상이다.
다음 vfwCalib2D 프로그램에서는 왼쪽 입력 영상에서 4 개의 점을 마우스로 찍으면 ListBox에 좌표가 입력되며, 오른쪽 출력영상에서 찍으면 좌표가 입력 된다. 그리고 나서 Calibration 체크버튼을 누르면 영상보정이 시작된다.
VFW웹캠 캡쳐: 카페참조 (http://cafe.daum.net/smhan/darS/1) - 웹캠 구동 프로그램
실행 파일 (Visual Studio 6.0), 소스코드는 없음.
1. 호모그래피 구하기. (p355)
| #include "Matrix.h" // 영상 보정 하기 // _x1[4] 네 개의 실측좌표 // _x2[4] 네 개의 영상좌표 // _x2 = H*_x1 Matrix<double> GetHomography(CPoint _x1[4], CPoint _x2[4]) { Matrix<double> A(10,8), x1(5,2), x2(5,2), b(10,1); int i; for(i=0;i<4;i++) { x1(i+1,1) = _x1[i].x; x1(i+1,2) = _x1[i].y; x2(i+1,1) = _x2[i].x; x2(i+1,2) = _x2[i].y; } for(i=0; i<4; i++) { A(2*i+1,3) = 1.0; A(2*i+1,4) = A(2*i+1,5) = A(2*i+1,6) = 0.0; A(2*i+2,1) = A(2*i+2,2) = A(2*i+2,3) = 0.0; A(2*i+2,6) = 1.0; } for(i=0; i<4; i++) { A(2*i+1,1) = x1(i+1,1); A(2*i+1,2) = x1(i+1,2); A(2*i+1,7) = -x2(i+1,1)*x1(i+1,1); A(2*i+1,8) = -x2(i+1,1)*x1(i+1,2); A(2*i+2,4) = x1(i+1,1); A(2*i+2,5) = x1(i+1,2); A(2*i+2,7) = -x2(i+1,2)*x1(i+1,1); A(2*i+2,8) = -x2(i+1,2)*x1(i+1,2); b(2*i+1,1) = x2(i+1,1); b(2*i+2,1) = x2(i+1,2); } A.Print(cout,"A = "); int retCode; Matrix<double> x(8,1); x = Inv(Transpose(A) * A, &retCode) * Transpose(A) * b; x.Print(cout,"x = "); Matrix<double> hh(3,3), w(3,1), c(3,1); hh(1,1) = x(1,1); hh(1,2) = x(2,1); hh(1,3) = x(3,1); hh(2,1) = x(4,1); hh(2,2) = x(5,1); hh(2,3) = x(6,1); hh(3,1) = x(7,1); hh(3,2) = x(8,1); hh(3,3) = 1.0; hh.Print(cout, "H = "); return hh; } |
2. 호모그래피를 이용하여 변환된 좌표를 구한다.
| Matrix<double> HH(3, 3); // 위에서 이미 구한 Homography 행렬이다. // _uv : 실측좌표 // _x,_y: 영상좌표 void uv2xy(int _u,int _v,double* _x,double* _y) // 속도가 느리다. { // xy=hh*uv Matrix<double> xy(3,1),uv(3,1); uv(1,1) = _u; uv(2,1) = _v; uv(3,1) = 1.0; xy = HH*uv; *_x = xy(1,1)/xy(3,1); *_y = xy(2,1)/xy(3,1); } void uv2xy(int _u,int _v,double* _x,double* _y) // 위 함수와 동일함 { double _z = HH(3, 1) * _u + HH2(3, 2) * _v + HH(3, 3) * 1.0; *_x = (HH(1, 1) * _u + HH(1, 2) * _v + HH(1, 3) * 1.0) / _z; *_y = (HH(2, 1) * _u + HH(2, 2) * _v + HH(2, 3) * 1.0) / _z; } // 속도를 빠르게 하기위해 HH -> HH2로 변환하여 계산한다. double HH2[9]; for(int r=0;r<3;r++) for(int c=0;c<3;c++) HH2[r*3+c] = HH(r+1,c+1); void uv2xy(int _u,int _v,double* _x,double* _y) { double _z = HH2[6]*_u + HH2[7]*_v + HH2[8]*1.0; *_x = (HH2[0]*_u + HH2[1]*_v + HH2[2]*1.0) / _z; *_y = (HH2[3]*_u + HH2[4]*_v + HH2[5]*1.0) / _z; } |
3. 영상을 복원한다. (p203)
영상을 변형을 주면 다음 그림과 같이 비어있는 영상 픽셀들이 존재한다. 이부분을 메꿔줘야 한다. 양선형 보간법을 이용하여 보정해 보자.
확대/축소/회전의 보간법을 이용하여 영상을 복원할 수 있다.
void CWinTestDoc::m_ZoomOut(int height, int width, float zoomoutfactor) { BYTE *pZoomImg; BYTE newValue; int new_height=(int)(height*zoomoutfactor);//새로운 이미지의 높이 계산 int new_width=(int)(width*zoomoutfactor);//새로운 이미지의 폭 계산 int heightm1=height-1; int widthm1=width-1; int where,org_where; int r,c;//타겟 이미지 좌표 float r_orgr,r_orgc;//원 이미지 상의 해당 좌표 (실수값) int i_orgr,i_orgc;//원 이미지 상의 해당 좌표 (정수값) float sr,sc;// 예 1.24-1=0.24 float I1,I2,I3,I4; //Zoom Image를 위한 동적 메모리 할당 pZoomImg=new BYTE[new_height*new_width]; for(r=0;r<new_height;r++) for(c=0;c<new_width;c++) { r_orgr=r/zoomoutfactor; // 이부분을 변경해야 합니다. 즉, r_orgr과 r_orgc는 입력영상좌표 이므로 x=HX가 된다. r_orgc=c/zoomoutfactor; // 따라서 uv2xy(c,r,&r_orgc,&r_orgr); 로 영상좌표 변환을 하여 구할 수 있다. i_orgr=floor(r_orgr);//예: floor(2.8)=2.0 i_orgc=floor(r_orgc); sr=r_orgr-i_orgr; sc=r_orgc-i_orgc; //범위 조사 //원이미지의 범위를 벗어나는 경우 0값 할당 if(i_orgr<0 || i_orgr>heightm1 || i_orgc<0 || i_orgc>widthm1) { where=r*new_width+c; pZoomImg[where]=0; } //원 이미지의 범위 내에 존재 이중 선형 보간 수행 else { I1=(float)m_InImg[i_orgr][i_orgc];//(org_r,org_c) I2=(float)m_InImg[i_orgr][i_orgc+1];//(org_r,org_c+1) I3=(float)m_InImg[i_orgr+1][i_orgc+1];//(org_r+1,org_c+1) I4=(float)m_InImg[i_orgr+1][i_orgc];//(org_r+1,org_c) //이중 선형 보간을 통한 새로운 밝기값 계산 newValue=(BYTE)(I1*(1-sc)*(1-sr)+I2*sc*(1-sr)+I3*sc*sr+I4*(1-sc)*sr); where=r*new_width+c; pZoomImg[where]=newValue; } } //일단 영상의 일부만 복사하는 것으로 함. for(r=0;r<new_height;r++) for(c=0;c<new_width;c++) { m_OutImg[r][c]=pZoomImg[r*new_width+c]; } //동적 할당 메모리 해제 delete [] pZoomImg; } |
다음은 학생들 작품이다.