Two Image mosaic (paranoma) based on SIFT / C++ source (OpenCV) / SIFT 특징 추출기를 이용한 두장의 영상을 모자익(파라노마) 영상으로 만들기

Created Date : 2011.2
Language : C/C++
Tool : Microsoft Visual C++ 2010
Library & Utilized : OpenCV 2.2
Reference : Interent Reference
etc. : 2 adjacent images


two adjacent iamges

Feature extraction by Surf(SIFT)

Feature matching

Mosaic (paranoma)

This program is conducted as follow process.
First, the program finds feature point in each image using SURF.
->cvExtractSURF
Second, feature points on each images is matched by similarity.
->FindMatchingPoints
Third, We get the Homography matrix.
->cvFindHomography
Last, we warp the image for attaching into one image.
->cvWarpPerspective

You can download source here.
If you have good idea or advanced opinion, please reply me.
Thank you.

-----------------------------------------------------------------------------

이웃된 두 장의 영상을 입력 받아 하나의 모자이크 영상(파라노마)으로 만든다.
특징 추출 및 비교 방법 : suft ->cvExtractSURF
특징 매칭 방법 : FindMatchingPoints
호모그라피 행렬 구하기 : cvFindHomography
영상 모자이크 방법 : warpping

전체 소스 코드는 여기서 받을 수 있습니다.
https://github.com/MareArts/Two-Image-mosaic-paranoma-based-on-SIFT
개선 사항이나 좋은 의견 있으시면 답변 주세요.
감사합니다.



#include < iostream>
#include < cv.h>
#include < cxcore.h>
#include < highgui.h>


using namespace std;

typedef struct MATCH_PAIR
{
 int nA;
 int nB;
} MATCH_PAIR;


void MergeImages(IplImage* Image1, IplImage* Image2, IplImage* dstImage);
int FindMatchingPoints(const CvSeq* tKeypoints, const CvSeq* tDescriptors, const CvSeq* srcKeypoints, const CvSeq* srcDescriptors, int descriptor_size, MATCH_PAIR *pMatchPair);
int FindNearestPoints(const float* pA, int laplacian, const CvSeq* srcKeypoints, const CvSeq* srcDescriptors, int descriptor_size);

void main()
{
 
 ///////////////////////////////////////////////////////////////////////////////////////////
 //load two image
 //first
 IplImage *T1Img, *mT1Img;
 if((T1Img = cvLoadImage("T1.jpg", CV_LOAD_IMAGE_GRAYSCALE)) == NULL)
 {
  printf("A Img open error\n");
  return ;
 }
 //
 mT1Img = cvCreateImage(cvSize(T1Img->width, T1Img->height),T1Img->depth, 3);
 cvCvtColor(T1Img, mT1Img, CV_GRAY2BGR);
 
 // second
 IplImage* T2Img, *mT2Img;
 if((T2Img = cvLoadImage("T2.jpg", CV_LOAD_IMAGE_GRAYSCALE)) == NULL)
 {
  printf("B Img open error\n");
  return ;
 }
 //
 mT2Img = cvCreateImage(cvSize(T2Img->width, T2Img->height),T2Img->depth, 3);
 cvCvtColor(T2Img, mT2Img, CV_GRAY2BGR);

 //make window
 cvNamedWindow("Origin T1 Img", 1);
 cvNamedWindow("Origin T2 Img", 1);
 
 cvShowImage("Origin T1 Img", T1Img);
 cvShowImage("Origin T2 Img", T2Img);
 ///////////////////////////////////////////////////////////////////////////////////////////


 ///////////////////////////////////////////////////////////////////////////////////////////
 //surf 
 CvMemStorage* storage =cvCreateMemStorage(0);
 CvSURFParams params = cvSURFParams(300, 0);

 //T1 Img
 CvSeq *T1_Keypoints = NULL;
 CvSeq *T1_Descriptors = NULL;
 cvExtractSURF(T1Img, NULL, &T1_Keypoints, &T1_Descriptors, storage, params);
 printf("T1 Img - Keypoint: %d\n", T1_Keypoints->total);
 printf("T1 Img - Descriptors : %d\n", T1_Descriptors->total);

 //T2 Img
 CvSeq *T2_Keypoints = NULL;
 CvSeq *T2_Descriptors = NULL;
 cvExtractSURF(T2Img, NULL, &T2_Keypoints, &T2_Descriptors, storage, params);
 printf("T2 Img - Keypoints : %d\n", T2_Keypoints->total);
 printf("T2 Img - Descriptors : %d\n", T2_Descriptors->total);
 
 //draw feature1
 CvSURFPoint* surf1;
 for(int i = 0; i <  (T1_Keypoints ? T1_Keypoints->total : 0); i++ )
    {
  surf1 = (CvSURFPoint*) cvGetSeqElem(T1_Keypoints, i);
  int r = surf1->size/2;
  cvCircle( mT1Img, cvPoint(surf1->pt.x, surf1->pt.y) , r, CV_RGB(0,255,0));
  cvLine( mT1Img, cvPoint(surf1->pt.x + r, surf1->pt.y + r), cvPoint(surf1->pt.x - r, surf1->pt.y - r), CV_RGB(0,255,0));
        cvLine( mT1Img, cvPoint(surf1->pt.x - r, surf1->pt.y + r), cvPoint(surf1->pt.x + r, surf1->pt.y - r), CV_RGB(0,255,0));
    }

 //draw feature2
 CvSURFPoint* surf2;
 for(int i = 0; i <  (T2_Keypoints ? T2_Keypoints->total : 0); i++ )
    {
  surf2 = (CvSURFPoint*) cvGetSeqElem(T2_Keypoints, i);
  int r = surf2->size/2;
  cvCircle( mT2Img, cvPoint(surf2->pt.x, surf2->pt.y) , r, CV_RGB(0,255,0));
  cvLine( mT2Img, cvPoint(surf2->pt.x + r, surf2->pt.y + r), cvPoint(surf2->pt.x - r, surf2->pt.y - r), CV_RGB(0,255,0));
        cvLine( mT2Img, cvPoint(surf2->pt.x - r, surf2->pt.y + r), cvPoint(surf2->pt.x + r, surf2->pt.y - r), CV_RGB(0,255,0));
    }

 //surf feature draw
 cvNamedWindow("Surf T1 Img", 1);
 cvNamedWindow("Surf T2 Img", 1);
 
 cvShowImage("Surf T1 Img", mT1Img);
 cvShowImage("Surf T2 Img", mT2Img);
 ///////////////////////////////////////////////////////////////////////////////////////////
 

 ///////////////////////////////////////////////////////////////////////////////////////////
 //Image merging
 IplImage* MergeImg = cvCreateImage(cvSize(T1Img->width+T2Img->width, T1Img->height), IPL_DEPTH_8U, 3);
 MergeImages(T1Img, T2Img, MergeImg); 
 ///////////////////////////////////////////////////////////////////////////////////////////


 ///////////////////////////////////////////////////////////////////////////////////////////
 //matching
 MATCH_PAIR *pMatchPair = new MATCH_PAIR[T1_Keypoints->total];
 int descriptor_size = params.extended? 128 : 64;
 int nMatchingCount = FindMatchingPoints(T1_Keypoints, T1_Descriptors, T2_Keypoints, T2_Descriptors, descriptor_size, pMatchPair);
 printf("matching count = %d\n", nMatchingCount);

 CvPoint2D32f *pt1 = new CvPoint2D32f[nMatchingCount];
 CvPoint2D32f *pt2 = new CvPoint2D32f[nMatchingCount];

 int x1, y1, x2, y2;
 for(int k=0; k< nMatchingCount; k++)
 {
  //keypoint
  surf1 = (CvSURFPoint*) cvGetSeqElem(T1_Keypoints, pMatchPair[k].nA);
  x1 = cvRound(surf1->pt.x);
  y1 = cvRound(surf1->pt.y);

  surf2 = (CvSURFPoint*) cvGetSeqElem(T2_Keypoints, pMatchPair[k].nB);
  x2 = cvRound(surf2->pt.x) + T1Img->width;
  y2 = cvRound(surf2->pt.y);

  //line draw
  CvPoint r1 = cvPoint(x1, y1);
  CvPoint r2 = cvPoint(x2, y2);
  cvLine(MergeImg, r1, r2, CV_RGB(0, 0, 255));

  pt1[k] = surf1->pt;
  pt2[k] = surf2->pt;
 }

 //draw merged image
 cvNamedWindow("Merging Img",1);
 cvShowImage("Merging Img", MergeImg);
 ///////////////////////////////////////////////////////////////////////////////////////////


 
 ///////////////////////////////////////////////////////////////////////////////////////////
 //calculate homography
 CvPoint corners[4]={{0, 0}, {T1Img->width, 0}, {T1Img->width, T1Img->height}, {0, T1Img->height}};
 if(nMatchingCount< 4)
 {
  printf("We need more than 4 matching points");
  printf("to calculate a homography transform\n");
  return ;
 }

 CvMat M1, M2; 
 double H[9];
 CvMat mxH = cvMat(3, 3, CV_64F, H);
 M1 = cvMat(1, nMatchingCount, CV_32FC2, pt1);
 M2 = cvMat(1, nMatchingCount, CV_32FC2, pt2);
 if( !cvFindHomography(&M1, &M2, &mxH, CV_RANSAC, 2))
 {
  printf("Find Homography Fail!\n");
  return ;
 }
 
 //display homography
 printf(" Homography matrix\n");
 for( int rows=0; rows< 3; rows++ )
 {
  for( int cols=0; cols< 3; cols++ )
  {
   printf("%lf ", cvmGet(&mxH, rows, cols) );
  }
  printf("\n");
 }
 ///////////////////////////////////////////////////////////////////////////////////////////


 ///////////////////////////////////////////////////////////////////////////////////////////
 //make stitching image
 IplImage* WarpImg = cvCreateImage(cvSize(T1Img->width*2, T1Img->height*2), T1Img->depth, T1Img->nChannels); 
 cvWarpPerspective(T1Img, WarpImg, &mxH);
 cvSetImageROI(WarpImg, cvRect(0, 0, T2Img->width, T2Img->height));
 cvCopy(T2Img, WarpImg);
 cvResetImageROI(WarpImg);
 
 //draw stiching image
 cvNamedWindow("WarpImg Img",1);
 cvShowImage("WarpImg Img", WarpImg);
 /////////////////////////////////////////////////////////////////////////////////////////// 
 cvWaitKey(0);

 delete pMatchPair;
 delete pt1;
 delete pt2;


 cvReleaseImage(&WarpImg);

 cvReleaseImage(&T1Img);
 cvReleaseImage(&T2Img);

 cvReleaseImage(&mT1Img);
 cvReleaseImage(&mT2Img);

 cvReleaseImage(&MergeImg);

}


void MergeImages(IplImage* Image1, IplImage* Image2, IplImage* dstImage)
{
 cvSet(dstImage, CV_RGB(255, 255, 255));
 cvSetImageROI(dstImage, cvRect(0, 0, Image1->width, Image1->height));
 cvSetImageCOI(dstImage, 1); //채널 1
 cvCopy(Image1, dstImage); 
 cvSetImageCOI(dstImage, 2); //채널 2
 cvCopy(Image1, dstImage); 
 cvSetImageCOI(dstImage, 3); //채널 3
 cvCopy(Image1, dstImage);

 
 cvSetImageROI(dstImage, cvRect(Image1->width, 0, Image2->width, Image2->height));
 cvSetImageCOI(dstImage, 1); //채널 1
 cvCopy(Image2, dstImage);
 cvSetImageCOI(dstImage, 2); //채널 2
 cvCopy(Image2, dstImage);
 cvSetImageCOI(dstImage, 3); //채널 3
 cvCopy(Image2, dstImage);
 cvResetImageROI(dstImage);
 
}


int FindMatchingPoints(const CvSeq* tKeypoints, const CvSeq* tDescriptors, const CvSeq* srcKeypoints, const CvSeq* srcDescriptors, int descriptor_size, MATCH_PAIR *pMatchPair)
{
 int i;
 float* pA;
 int nMatchB;
 CvSURFPoint* surfA;
 int k=0;
 for(i=0; i< tDescriptors->total; i++)
 {
  pA = (float*) cvGetSeqElem(tDescriptors, i);
  surfA = (CvSURFPoint*) cvGetSeqElem(tKeypoints, i);
  nMatchB = FindNearestPoints(pA, surfA->laplacian, srcKeypoints, srcDescriptors, descriptor_size);
  if(nMatchB > 0)
  {
   pMatchPair[k].nA=i;
   pMatchPair[k].nB = nMatchB;
   k++;
  }
 }

 return k;
}


int FindNearestPoints(const float* pA, int laplacian, const CvSeq* srcKeypoints, const CvSeq* srcDescriptors, int descriptor_size)
{
 int i, k;
 float* pB;
 CvSURFPoint *surfB;
 int nMatch = -1;
 double sum2, min1 = 10000, min2 = 10000;
 for(i=0; i< srcDescriptors->total; i++)
 {
  surfB = (CvSURFPoint*) cvGetSeqElem(srcKeypoints, i);
  pB = (float*) cvGetSeqElem(srcDescriptors, i);
  if(laplacian != surfB->laplacian)
   continue;

  sum2 = 0.0f;
  for(k=0; k< descriptor_size; k++) { sum2 +=(pA[k]-pB[k])*(pA[k]-pB[k]); }

  if(sum2 <  min1)
  {
   min2 = min1;
   min1 = sum2;
   nMatch = i;
  }
  else if(sum2< min2) { min2 = sum2; }
 }
 if(min1< 0.6*min2)
  return nMatch;

 return -1;
}





Comments

  1. 안녕하세요. 파노라마 영상 만드는 소스코드가 필요했었는데, 이렇게 소스코드를 제공하여 제가 편하게 참조할 수 있게 되었습니다. 코드 공유에 감사드립니다.

    ReplyDelete
  2. 방문자중 처음으로 저한테 답글을 남겨주셨네요.. 그동안 아무도 답글이 없어서.. 소스만 받아가고 의견 교환은 없어 많이 섭섭했었어요.. ^^ 부족한 소스인데 유용하게 잘쓰시고 공부하면서 좋은 의견있으면 공유해요. 감사합니다~

    ReplyDelete
  3. 안녕하세요..다름이 아니라 메인 제목에는 SIFT라고 쓰셨는데요..구현하신것을 보면 SURF을 사용하셨습니다. 제가 좀 무지해서 그럽니다..SIFT와 SURF가 좀 다른게 아닌가요??
    왜 같이 쓰셨는지 좀 이해가 되지 않습니다.
    Scale Invariant라는 공통점은 가지고 있지만 처리 속도에 따라 다른게 아닌지요? 제가 좀 몰라서 배우고 싶어서 이렇게 글을 남깁니다. ㅡㅡ;;;.. 감사합니다.

    ReplyDelete
  4. 안녕하세요.
    SIFT 논문 이후 SIFT개념을 이용한 많은 파생 알고리즘이 나왔습니다. 그 중 SURF는 속도향상에 대한 방법입니다. SURF는 SIFT의 속도를 빠르게 하되 성능은 최대한 보장하는 몇개의 아이디어를 제안했습니다.
    SIFT가 나왔을때, 이 알고리즘을 사람들이 사용을 하고 싶었는데 특허 등록이 되어있어서 상업적으로 사용할 수 없고, opencv에도 포함되질 못했어요(최신 버전에는 포함되어있는지 모르겠음). 그래서 SIFT되신 SURF를 사용했고 자연스럽게 SIFT와 SURF를 동일시 했던 것 같습니다. 좋은 지적 감사합니다.

    ReplyDelete
    Replies
    1. Anonymous12/2/12 18:48

      좋은 소스 감사드립니다. 하지만 다른 그림 파일로 테스트해봤는데 안되는 경우도 있는것 같습니다. 제가 영상처리부분을 지금 공부하기 시작하여 뭐라 말 할 수 있는 부분이 없네요. 이 자료를 통하여 열심히 공부하겠습니다.

      Delete
    2. 네 감사합니다.
      특징 매칭 부분이 간단하게 구현되어 있어서 모든 경우에 잘 적용되지 않을 겁니다.
      하지만 이 소스를 기본으로 공부하시면 빠르게 시작할 수 있을 거예요.
      도움 되시길 바랍니다. ^^

      Delete
  5. Anonymous3/12/11 05:32

    muito bom post :)

    ReplyDelete
  6. 호모그래피 계산에서 " CvPoint corners[4] " 는 소스에 한 번 나오고 그 이후부터는 보이지 않던데, 어디에 이용된 것인가요? ^^;

    ReplyDelete
    Replies
    1. 네 좋은 지적 감사합니다. 소스를 보니깐 corners 변수는 사용을 하지 않네요. 삭제해도 좋습니다. ^^

      Delete
  7. JungHo Kim10/7/12 05:47

    좋은 정보 감사히 잘 보았습니다.
    근데 MergeImages()함수 부분에서 cvCopy하는 부분이 오류가 있더라구요 '-'
    제가 실수한 것일 수도 있지만 저와 같은 실수를 하시는 분들이 있을 수도 있을 거 같아서
    댓글을 달아 봅니다 ~
    전 cvCopy(Image1, dstImage); -> cvCopy(Image1, dstImage, NULL);
    위와 같이 수정하였습니다 ~

    아 그리고 한 가지 질문드릴게 있는데..
    warping 이후에 두 이미지 스티칭이하는 부분은 소스 상에 구현이 안되어 있는 건가요?
    사실 파노라마 이미지 생성의 전반적인 과정인
    특징점 추출 부터 이미지 왜곡 보정, 스티칭 부분까지 공부를 하고 있거든요^^;
    논문만으로는 한계가 있어서 소스를 참고하고 있는 중이라...^^;

    ReplyDelete
    Replies
    1. 좋은 지적 감사합니다.

      Delete
  8. Anonymous26/9/12 23:42

    OPENCV를 이용하여 파노라믹 구현 소스를 찾고 있었는데..
    정말 도움이 많이 되었습니다. 감사드려요 ^^
    이 소스를 더 확장하여 3장 이상의 영상일 시 어떤 부분을 수정하면 좋을지
    조언해 주실 수 있으신지요?
    초보다 보니 영상이 3장 이상일 시 어떤 부분을
    바꿔야 가능할지 모르겠네요..

    ReplyDelete
  9. 3번째 장에서 feature를 검출하고 2번째, 3번째 영상의 같은 특징점을 찾아주면 되겠네요... 너무 뻔한 답변인가요? ㅎㅎ

    ReplyDelete
    Replies
    1. Anonymous27/9/12 17:27

      ㅎㅎ 특징점을 찾고 매칭시키는 부분까지야 그냥 확장하면 될 것 같은데.. 호모그래피와 최종 모자이크 영상을 만들때가 살짝 고민이네요 ㅠ.ㅠ 이미 WARPING 된 T2Img에 세번째 영상을 어떻게 붙일 수 있을지... ㅎㅎ

      Delete
  10. Anonymous21/3/13 01:28

    좋은 소스 감사합니다.
    영상 모자익 공부 시작했는데 많은 도움이 될것 같습니다.

    ReplyDelete
  11. Anonymous11/4/13 08:42

    감사합니다.
    참고로 OpenCV 2.4를 이용하여 위 코드를 그대로 실행시킬 때 오류가 납니다.
    "OpenCV was built without SURF support"
    이는 OpenCV 2.4에서 SIFT와 SURF 코드가 nonfree module로 옮겨져서 그렇다고 하네요.
    코드 맨 위 #include단에
    #include
    추가하시고
    main함수 내부 surf특징점 찾는 부분 에서 cvExtractSURF 함수를 call하기 전에
    cv::initModule_nonfree();
    이거 추가해주면 잘 실행됩니다. :)

    ReplyDelete
  12. Anonymous4/7/13 22:29

    안녕하세요!! 좋은자료 정말 감사드립니다
    질문이 하나 있는데요
    이미지를 칼라로 출력할 수는 없는건가요?

    if((T1Img = cvLoadImage("T1.jpg", CV_LOAD_IMAGE_GRAYSCALE)) == NULL) 부분에서
    CV_LOAD_IMAGE_COLOR 로 변경해봐도 안돼네요ㅜㅡ
    가능하시다면 알려주시면 감사하겠습니다!!

    ReplyDelete
  13. This comment has been removed by the author.

    ReplyDelete
  14. This comment has been removed by the author.

    ReplyDelete
  15. 안녕하세요ㅎ대학교 3학년학생인데요.
    영상처리 수업시간에 와핑에 대해 공부하고있습니다.
    잘쓸게요~!

    ReplyDelete
  16. Anonymous3/4/16 21:25

    안녕하세요!!영상처리 공부하는 학생입니다.
    파노라마 결과 영상이 컬러로 나오게 하고싶어서
    소스를 계속 바꿔보고있는데 잘되지않아서
    어느 부분을 변경해야 될까요ㅜㅜ

    ReplyDelete
    Replies
    1. T1Img, T2Img is loaded as grayscale, because feature extraction.
      If you show color image result, how about make one more mat of color depth.
      And after all process, adapt the color mat instead gray mat in code line 186~196.
      mat을 컬러 모드로 따로 하나를 더 로드하고, 모든 계산후에 186~196에서 사용한 흑백 mat대신 컬러 mat으로 워핑하면 어떻까요?

      thank you.

      Delete

Post a Comment

Popular posts from this blog

OpenCV Stitching example (Stitcher class, Panorama)

(OpenCV Study) Background subtractor MOG, MOG2, GMG example source code (BackgroundSubtractorMOG, BackgroundSubtractorMOG2, BackgroundSubtractorGMG)

Example source code of extract HOG feature from images, save descriptor values to xml file, using opencv (using HOGDescriptor )

Real-time N camera stitching Class.

8 point algorithm (Matlab source code) / The method to get the Fundamental Matrix and the Essential matrix

Optical Flow sample source code using OpenCV

Video Stabilization example source code, (using cvFindHomography, cvWarpPerspective functions in openCV)

(OpenCV Study) calcOpticalFlowFarneback example source code ( dense optical flow )

yuv422(YUYV) to RGB and RGB to yuv422(YUYV), (Using OpenCV and TBB)

OpenCV Drawing Example, (line, circle, rectangle, ellipse, polyline, fillConvexPoly, putText, drawContours)