옵티컬 플로우

프레임 장면에서 객체가 어떻게 움직이고 있는지 추적


밀집 옵티컬 플로우

동영상에서 픽셀의 속도는 이전프레임과 현재 프레임사이에 픽셀이동 한 양(변위)와 관련이 있음을 이용

영상에 내부의 모든 픽셀에서 속도를 구한다.

혼-셩크 방법은 이러한 속도장을 계산하는 방법중 하나이다.

한프레임의 각 픽셀 윈도우를 설정하고 다음 프레임에서 이 윈도우와 가장 잘 일치하는 곳을 찾음

계산양이 매우 많다.


희소 옵티컬 플로우

코너와 같이 두드러진 속성을 가진 추적할 점을 미리 지정해준다.

연상량이 적은 희소 옵티컬 플로우 방법을 선호

루카스-카나데방법은 희소 옵티컬 플로우 방법을 이용한다.


루카스-카네데 방법

한프레임의 각 픽셀 윈도우를 설정하고 다음 프레임에서 이 윈도우와 가장 잘 일치하는 곳을 찾는다.

작은 지역 윈도우를 사용하기 때문에 이 윈도우보다 큰 움직임이 발생하였을 경우 움직임을 계산하지 못하는 단점이 있다.

  ☞ 이러한 문제점을 해결하기 위해 피라미드를 이용한다.

피라미드LK 알고리즘은 원본영상으로부터 영상 피라미드를 구성, 상위계층에서 하위계층으로 추적 => 커다란 움직임도 찾아낸다.


(잘사용안함) 루카스-카나데 코드 :  vCalcOpticalFlowLK()

피라미드를 사용하지 않는 루카스-카나데 밀집 옵티컬 플로우 알고리즘

 void cvCalcOpticalFlowLK(

        const CvArr* prev,

        const CvArr* curr,

        CvSize win_size,

        CvArr* velx,

        CvArr* vely

);

CvCalcOpticalFlowLK()함수는 최소 오차를 계산할 수 있는 픽셀들에 대해서만 그 결과를 산출한다. 신뢰할수 없는 오차를 발생시키는 픽셀에 대해서는 속도 백터의 값을 모두 0으로 설정한다.


피라미드 루카스-카나데 코드 : cvCalcOpticalFlowPyrLK()

추적하기 좋은 특징을 사용하고 각 점들의 추적이 얼마나 잘 수행되었는지를 알려준다.

 void cvCalcOpticalFlowPyrLK(

        const CvArr* prev,                       //이전프레임 영상(8비트 단일 채널영상)

        const CvArr* curr,                       //현재프레임 영상(8비트 단일 채널영상)

        CvArr* prev_pyr,                         //이전 프레임 피라미드 영상을 저장하기 위해 할당된 버퍼

        CvArr* curr_pyr,                         //현재 프레임 피라미드 영상을 저장하기 위해 할당된 버퍼

        const CvPoint2D32f* prev_features,       //움직임을 추정할 점들의 배열

        CvPoint2D32f* curr_features,             //점들이 이동한 점들의 위치

        int count,                               //prev_feature 저장된 점들의 개수

        CvSize win_size,                         //지역 움직임을 계산하기 위한 윈도우의 크기

        int level,                               //피라미드에서 생성할 최대 계층개수

        char* status,                            //움직임 추정 계산의 성공여부(1:성공, 0:실패)

        float* track_error,                      /* 실수형 배열을 가리키며 각원소는

                                                    이전프레임, 현재프레임간 특징점 사이 거리를 저장 */

        CvTermCriteria criteria,                 //알고리즘의 종료조건

        int flags                                //함수 실행시 내부 기록에 대한 세세한 조정을 지정하는 역할

);

prec_pyr, curr_pyr 통해 피라미드 영상을 저장하기 위한 버퍼의 크기는 ((img.width+8)×(img.height/3) 바이트 이상이어야한다. 만약 두개의 포인터를 NULL로 설정하면 피라미드 영상을 위해 필요한 메모리 공간은 내부에서 할당되고 사용후 해제 되지만 성능면에서 좋지 않다.

flag : 함수 실행시 내부 기록에 대한 세세한 조정을 지정하는 역할

- CV_LKFLOW_PYR_A_READY : 함수 호출전에 이미 이전 프레임에 대한 영상 피라미드가 계산되어 prev_pyr에 저장되어있다.

- CV_LKFLOW_PYR_B_READY : 함수 호출전에 이미 현재 프레임에 대한 영상 피라미드가 계산되어 curr_pyr에 저장되어있다.

- CV_LKFLOW_INITIAL_GUESSES : 함수가 호출되었을때, prev_feature배열을 이용하여 curr_feature배열의 초기값을 설정한다.


cvCalcOpticalFlowPyrLK()함수는 연속된 프레임들을 입력으로 제공하고, prev_feature에 추적하기 원하는 점들을 저장한후 함수를 호출한다.

함수가 반환되면, status배열을 확인하여 어떤점이 성공적으로 추적되었는지를 검사하고, 그 점들의 새로운 위치를 알기위해 curr_feature배열값을 확인한다.


추적하기 원하는점 즉 추적하기 좋은 특징은 cvGoodFeatureToTrack()함수를 이용하여 코너를 추적점으로 설정하고 cvCalcOpticalFlowPyrLK()함수를 이용하여 움직임을 추적한다.

#include <cv.h>

#include <cxcore.h>

#include <highgui.h>

#include <stdio.h>

 

const int MAX_CORNERS=500;

 

int main(int argc, char** argv) {

       

        //초기화. 두개의 영상을 불러오고, 결과를 저장한 영상을 생성한다.

        IplImage* imgA=cvLoadImage("OpticalFlow0.jpg", CV_LOAD_IMAGE_GRAYSCALE);

        IplImage* imgB=cvLoadImage("OpticalFlow1.jpg", CV_LOAD_IMAGE_GRAYSCALE);

 

        CvSize img_sz=cvGetSize(imgA);

        int win_size=10;

 

        IplImage* imgC=cvLoadImage("OpticalFlow1.jpg", CV_LOAD_IMAGE_UNCHANGED);

 

        //추적할 특징을 검출한다

        IplImage* eig_image=cvCreateImage(img_sz, IPL_DEPTH_32F, 1);

        IplImage* tmp_image=cvCreateImage(img_sz, IPL_DEPTH_32F, 1);

 

        int corner_count=MAX_CORNERS;

        CvPoint2D32f* cornersA=new CvPoint2D32f[MAX_CORNERS];

 

        //이미지에서 코너를 추출함

        cvGoodFeaturesToTrack(imgA, eig_image, tmp_image,

               cornersA, &corner_count, 0.01, 5.0, 0, 3, 0, 0.04);

 

        //서브픽셀을 검출하여 정확한 서브픽셀 위치를 산출해냄

        cvFindCornerSubPix(imgA, cornersA, corner_count, cvSize(win_size, win_size),

               cvSize(-1, -1), cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 20, 0.03));

 

        //루카스-카나데 알고리즘

        char feature_found[MAX_CORNERS];

        float feature_errors[MAX_CORNERS];

        CvSize pyr_sz=cvSize(imgA->width+8, imgB->height/3);

 

        IplImage* pyrA=cvCreateImage(pyr_sz, IPL_DEPTH_32F, 1);

        IplImage* pyrB=cvCreateImage(pyr_sz, IPL_DEPTH_32F, 1);

 

        CvPoint2D32f* cornersB=new CvPoint2D32f[MAX_CORNERS];

 

        //추출한 코너(cornerA) 추적함 -> 이동한 점들의 위치는 cornerB 저장된다.

        cvCalcOpticalFlowPyrLK(imgA, imgB, pyrA, pyrB, cornersA, cornersB, corner_count,

               cvSize(win_size, win_size), 5, feature_found, feature_errors,

               cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 20, .3), 0);

 

        for(int i=0; i<corner_count; i++) {

               if(feature_found[i]==0 || feature_errors[i] > 550) {

                       //feature_found[i]값이 0 리턴이 되면 대응점을 발견하지 못함

                       //feature_errors[i] 현재 프레임과 이전프레임 사이의 거리가 550 넘으면 예외로 처리

                       printf("Error is %f\n", feature_errors[i]);

                       continue;

               }

 

               printf("Got it\n");

               CvPoint p0=cvPoint(cvRound(cornersA[i].x), cvRound(cornersA[i].y));

               CvPoint p1=cvPoint(cvRound(cornersB[i].x), cvRound(cornersB[i].y));

               cvLine(imgC, p0, p1, CV_RGB(255, 0, 0), 2);

        }

 

        cvNamedWindow("ImageA", 0);

        cvNamedWindow("ImageB", 0);

        cvNamedWindow("Lkpyr_OpticalFlow", 0);

 

        cvShowImage("ImageA", imgA);

        cvShowImage("ImageB", imgB);

        cvShowImage("Lkpyr_OpticalFlow", imgC);

 

        cvWaitKey(0);

 

        cvDestroyAllWindows();

 

        cvReleaseImage(&imgA);

        cvReleaseImage(&imgB);

        cvReleaseImage(&imgC);

        cvReleaseImage(&eig_image);

        cvReleaseImage(&tmp_image);

        cvReleaseImage(&pyrA);

        cvReleaseImage(&pyrB);

       

        return 0;

}








Posted by No names
,