CAFE

컴퓨터비전

2D Calibration Homography

작성자한창호|작성시간16.05.16|조회수1,011 목록 댓글 0

본 페이지는 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;
}

 

다음은 학생들 작품이다.

 

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
맨위로

카페 검색

카페 검색어 입력폼