움직임 추적(motion tracking)

컴퓨터비전/영상처리 2015. 4. 5. 21:58

움직임 추적에서 보통 많이 사용하는 mean-shift와 cam-shift에 대해 정리해본다.


1. mean-shift(민시프트)

mean-shift 알고리즘은 데이터 집합의 밀도 분포에서 지역 극값(local extrema)을 찾아내는 방법이다.



언덕오르기 알고리즘과 비슷한 방법으로 생각할 수 있다. 

데이터 집합이 있을때 보통 mean-shift는 데이터의 이상치(outlier)를 무시한다. 쉽게 말하면 데이터의

정점(peak)으로부터 너무 멀리 떨어진 데이터는 무시한다는 것을 의미한다. 

따라서 특정 지역내의 윈도우 안에 존재하는 데이터 점들만을 사용하여 윈도우를 움직인다.



mean-shift 알고리즘은 다음과 같이 동작한다.

  1. 탐색 윈도우내의 데이터들로 무게중심(center of mass)를 구한다.

  2. 윈도우의 중심을 무게 중심 위치로 옮긴다.

  3. 윈도우가 움직임을 멈출 때까지 2번 단계부터 반복한다.


보통 윈도우 내의 데이터들로 부터 무게중심을 구할때 가우시안 분포(Gaussian distribution)을 사용한다.

보통 중심으로 부터 가까이 있는 데이터에 가중치를 좀더 높여 주는 방식이 좋다.


위 그림은 mean-shift 알고리즘의 이동 분포를 표현한 것이다. 사진을 보면 데이터의 중심(많은 픽셀이 분포한 점)

으로 윈도우가 이동하는것을 확인할 수 있다.


mean-shift알고리즘은 지역적 정점을 찾기 때문에 여러 지점에서 정점이 발견 될 수도 있다.

이런 움직임으로 인하여 윈도우 아래 존재하는 데이터들이 바뀌게 되고, 반복적으로 중심 이동 작업을 수행할 

수행할 수 있다. 중심 이동이 반복되면 mean-shift 벡터가 0이 되는 경우가 생기면 이때 수렴하는 순간이라고

판단하면 된다.


OpenCV는 mean-shift 함수를 제공한다.


1
int meanShift(InputArray probImage, Rect& window, TermCriteria criteria)
cs

probImage는 역투영 히스토그램 이미지를 입력으로 받는다. calcBackProject()함수를 이용하면 된다.

window는 커널 윈도우 초기위치, 크기를 나타낸다. criteria는 반복연산의 종료조건을 입력하면 된다.


2. camshift(캠시프트)


[Bradski98] Bradski, Gary R. "Computer vision face tracking for use in a perceptual user interface." (1998).


meanshift와 다르게 camshift는 탐색 윈도우가 스스로 크기를 조정한다는 점에서 다르다.

예를 들어 얼굴 특징을 이용하여 추적하는 경우, 카메라로부터 사람이 가까워지거나 멀어짐에

따라 얼굴 크기에 맞게 자동으로 윈도우 크기를 변화시킨다. 


1
RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)
cs


mean shift와 cam shift 알고리즘은 색상 특징값을 이용하는 추적 알고리즘이라고 생각하기 쉬운데, 

이 생각은 잘못된 생각일 수 있다. 이 두 알고리즘은 확률적 분포에서 표현되는 모든 종류의 특징 분포를 추적한다.

집중력을 높여주는 백색 소음

카테고리 없음 2015. 4. 5. 03:54

 백색소음은 주변의 소음을 중화시켜 차단하고, 심신에 안정을 준다고 알려져 있다. 한국산업심리학회의 연구 결과에 따르면 백색소음은 집중력을 47.7% 향상시키며, 기억력을 9.6% 높여준다. 이와 함께 스트레스를 27.1% 낮춰준다. 이런 원리를 이용한 학습 보조 기기도 있다. 이런 기기를 통해 효과를 봤다는 사람도 있으니, 백색소음의 효과에 관한 이야기는 전혀 허황된 것은 아닌 듯하다.


1. RAINY MOOD

http://www.rainymood.com/

소나기가 오는 소리를 들을 수 있다. 가끔 천둥 소리도 들린다. 실제로 비가 오고 있는 듯한 착각을 불러 일으킨다. 



2. Jazz and Rain


약한 비가 오는 소리가 자동 재생된다. 플레이 버튼을 클릭하면 빗소리에 잘 어울리는 재즈 음악이 나온다. 



3. SHOWER TIME

샤워하는 기분을 느끼고 싶다면 '샤워 타임' 사이트를 추천한다. 샤워기에서 물방울이 떨어지는 소리가 들린다. 
샤워룸의 사이즈, 수압 등도 선택할 수 있다. 라디오 볼륨을 높이면 유쾌한 분위기의 음악이 재생된다. 



4. COFFITIVITY

'커피티비티'를 이용하면 집에서도 마치 카페에 있는 듯한 분위기를 낼 수 있다. 사람들이 대화를 나누는 소리, 
문이 열리고 닫히는 소리, 커피를 내리는 소리 등이 들린다. 


5. SOUNDROWN 

'사운드 라운'은 간단하게 버튼을 눌러 다양한 소리를 조합할 수 있는 사이트다. '커피숍' 버튼을 누른 후
'비' 버튼을 누르면 카페에서 빗소리를 듣는 듯한 분위기를 연출할 수 있다. 소리 조절도 가능하다. 



6. FAUX FIRE

http://fauxfire.com/?nr=0

벽난로에서 나무가 타는 소리와 함께 음악이 재생된다. 음악을 듣고 싶지 않다면 끌 수도 있고 다른 음악을 선택할 수도 있다. 


7. AUGUST AMBIENCE

http://augustambience.com/

'어거스트 앰비언스'는 여름 밤 잔디밭에 앉아있는 듯한 기분이 들게 해주는 사이트다. 풀 벌레 소리, 
매미가 우는 소리 등이 담겨 있다. 


8.SNOWY MOOD

http://snowymood.demouth.net/

눈을 밟으며 걷는 소리

15년(하) SW멤버십 및 Friendship

카테고리 없음 2015. 4. 5. 03:08


[삼성전자 소프트웨어멤버십 회원 선발 공고]

삼성소프트웨어멤버십에서 2015년도 하반기 신입회원을 선발 합니다.



1. 모집 요강
  □ 모집대상 : IT분야 연구개발에 ‘재능’과 ‘열정’있는 국내 정규 4년제 대학(원)생
  □ 해당지역 : 서울, 수원, 대전, 대구, 부산, 광주, 전주
  □ 접수방법 : 온라인 접수( www.secmem.org )
  □ 모집일정
    * 서류접수 : 2015. 4. 29 ~ 2015. 5. 12 / 오후 3시까지
    * 기술면접 : 2015. 5. 26 ~ 약 2주
    * 결과발표 : 2015. 6월 중 예정
    ※ 선발 된 지원자를 대상으로 S/W CAMP 운영
    (기술 전형 합격자들에게 S/W Camp 교육 프로그램 제공 /
    이수 후 평가를 통해 정회원 자격을 부여함)

    ※ 2016년 상반기 서류접수/기술전형 : 2015년 11월 예정

2. 지원 분야
  □ 분   야 : S/W 및 H/W 전 분야(Application / Middleware / System / Robotics / Etc.)

3. 지원 자격
  □ IT 연구개발에 재능과 열정이 있는 자
  □ 정규 4년제 대학(원)생 (1~4학년, 석사)
  □ 전공 학과 불문
  □ 국내외 공모전 수상자 우대
  □ 대학 졸업 전 1년 이상 회원 활동이 가능한 자(대학 졸업과 동시에 수료)

4. 선발 인원 : 약 ○○○명

5. 회원 혜택
  □ 연구개발 활동 및 환경 지원
  □ 회원 활동 수료 후 입사 희망 시 SSAT 면제
   ※ 보다 자세한 사항은 홈페이지( www.secmem.org )를 참조하세요.


[삼성소프트웨어멤버십이란?]
1. 설립목적
  삼성소프트웨어멤버십이란, S/W 및 관련 분야에 대한 재능과 열정이 있는
  대학생들에게 연구, 개발에 필요한 모든 것들을 지원하여 창의적이고
  실력 있는 삼성의 Software 전문가가 되도록 지원하는 프로그램입니다.

2. S/W멤버십에서 바라는 인재상
  열정적이고 창의적인, Software 에 재능이 있는 학생들입니다.
  학년, 나이, 학교, 전공, 성별에 구애 받지 않고 S/W 분야에 대한
  열정, 그리고 그 열정을 꽃 피울만한 실력이 갖춰진 학생이라면
  S/W멤버십 회원이 될 수 있습니다.

3. S/W멤버십 Program 이란?
  S/W멤버십 회원이 되기 위한 절차를 거쳐 모든 시험과정에 합격한 뒤
  회원이 되어 활동 과정을 수료하는 사람에 한해 삼성전자에 입사할 수
  있는 특전이 주어지는 제도입니다.

▷▶▷삼성소프트웨 어멤버십 홈페이지 바로가기
▷▶▷삼성소프트웨어멤버십 블로그 바로가기
▷▶▷삼성소프트웨어멤버십 페이스북 바로가기
▷▶▷Friendship 4기 모집 홍보 페이지 바로가기

옵티컬 플로우 Optical Flow -3

컴퓨터비전/영상처리 2015. 4. 5. 02:54

현재 가장 많이 사용하고 있는 옵티컬 플로우 방법은 Lucas와 Kanade방법이다.


[Lucas81] Lucas, Bruce D., and Takeo Kanade. "An iterative image registration technique with an application 

to stereo vision." IJCAI. Vol. 81. 1981.



위 영상을 보면 추적 영상의 외곡에도 상당히 추적을 잘하는 것을 볼수 있다.

각 화소에 대하여 특정 범위 안 윈도우 크기 내의 이웃 화소 들에 대해서 광류식(Optical Flow Equation)의 에러 제곱을

최소화하는 벡터를 구하기 위해 벡터를 지역 최소자승법(local least squares)로 계산한다.


예를 들면 윈도우 크기 3x3일 떄 9개의 화소에 대하여 에러 제곱을 최소화하는 (u,v)을 의사 역행렬(pseudo inverse matrix)을

최소자승법으로 계산한다.



위와 같이 광류식을 새워놓고 에러 제곱을 최소화 하는 광류 벡터(u,v)를 구하이 위해 u와 v로 각각 미분한다.

(에러 제곱을 최소화 하는 광류 벡터)


각각 미분한 식을 0으로 놓고 계산하면 아래 수식을 얻을 수 있다.


을 아래 수식으로 변형한다.



OpenCV에는 Lucas와 Kanade의 Optical Flow함수를 2가지 제공한다.

첫번째는 위 수식을 그대로 적용한 기본 방법이고,

1
void cvCalcOpticalFlowLK(const CvArr* prev, const CvArr* curr, CvSize win_size, CvArr* velx, CvArr* vely)
cs



두번째는 탐색시 피라미드 구조를 적용하여 부화소 단위까지 광류 속도 벡터를 계산하여 좀더 정확한 추적이 
가능하도록 구성한 방법이다.

1
void cvCalcOpticalFlowPyrLK(const CvArr* prev, const CvArr* curr, CvArr* prev_pyr, CvArr* curr_pyr, const CvPoint2D32f* prev_features, CvPoint2D32f* curr_features, int count, CvSize win_size, int level, char* status, float* track_error, CvTermCriteria criteria, int flags)
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <cv.h>
#include <highgui.h>
#include <stdio.h>
 
const int MAX_CORNERS = 200;
 
int main(int argc, char** argv) 
{
    //초기화. 두개의 영상을 불러오고, 결과를 저장한 영상을 생성한다.
    IplImage* imgA = cvLoadImage("img/prev.bmp", CV_LOAD_IMAGE_GRAYSCALE);
    IplImage* imgB = cvLoadImage("img/curr.bmp", CV_LOAD_IMAGE_GRAYSCALE);
 
    CvSize img_sz = cvGetSize(imgA);
    int win_size = 10;
 
    IplImage* imgC = cvLoadImage("img/curr.bmp", 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.015.00300.04);
 
    // 좀더 정확한 서브 픽셀 코너 계산
    cvFindCornerSubPix(imgA, cornersA, corner_count, cvSize(win_size, win_size),
        cvSize(-1-1), cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 200.03));
 
    // Lucas-Kanade 옵티컬 플로우
    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] == || 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(25500), 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;
}
cs

소스 참조 : http://lueseypid.tistory.com/archive/20130122

옵티컬 플로우 Optical Flow -2

컴퓨터비전/영상처리 2015. 4. 5. 01:24

지금 소개할 Optical Flow는 Horn이 1981년 

[Horn81] Horn, Berthold K., and Brian G. Schunck. "Determining optical flow." 1981 Technical Symposium East

International Society for Optics and Photonics, 1981. 

논문에서 소개한 방법이다.


Horn과 Schunck는 밝기값 패턴에서 임의의 화소에서의 발기값이 시간에 따라 변화하지 않는 상수로 가정하고, 

속도 분포가 영상의 모든 화소에서 부드럽게(smooth) 변화한다라는 제약조건을 추가했다. 쉽게 말하면 프레임 간의

간격은 매우 짧은 시간이므로 픽셀의 움직임이 크지 않다는 것을 가정한 것이다.






따라서 제곱에러 e^2를 최소화하는 속도 벡터 u와 v를 반복적으로 계산한다. (아래 수식)

  


Ex, Ey, Et를 2x2윈도우를 사용하여 다음과 같이 계산한다.


위 방법은 모든 화소에서 광류 계산을 수행하기 때문에 좀 느린 단점이 있다.

옵티컬 플로우 Optical Flow -1

컴퓨터비전/영상처리 2015. 4. 4. 22:28

어떤 물체를 추적할때 가장 간단한 방법이 해당 블록(영역)을 다음 프레임에서 어딨을지 찾아가는
방법이 가장 간단한 추적 방법일 것이다.



OpenCV에서는 이 방법을 하나의 메소드 형태로 제공한다.


1
2
void cvCalcOpticalFlowBM(const CvArr* prev, const CvArr* curr, CvSize block_size, 
            CvSize shift_size, CvSize max_range, int use_previous, CvArr* velx, CvArr* vely)
cs


파라메타로 prev는 이전프레임으로 (8비트, 1채널 영상)을 입력받으며, curr는 현재 프레임으로 

이전 프레임과 동일하게 8비트 1채널 영상을 입력 받는다.

block_size는 두 영상에서 비교할 블록의 크기를 의미하며, shift_size는 탐색할 영역 크기를 나타낸다.

velx와 vely는 속도 벡터를 나타내는 변수로 1채널 32비트 실수 영상이다. 

max_range는 curr에서 최대 탐색 범위를 나타내고 usePreviosu가 1이면 velx와 vely에 있는 이전 결과를 사용한다.


            

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <cv.h>
#include <highgui.h>
#include <opencv\cvaux.h>
 
int main() 
{
    IplImage *prev = NULL, *curr = NULL;
    IplImage *velx = NULL, *vely = NULL;
 
    prev = cvLoadImage("img/prev.bmp"0);
    curr = cvLoadImage("img/curr.bmp"0);
 
    CvSize block_Size = cvSize(1010);
    CvSize shift_size = cvSize(55);
    CvSize max_range = cvSize(55);
    int usePrevious = 0;
 
    int sX = (prev->width - block_Size.width) / shift_size.width + 1;
    int sY = (prev->height - block_Size.height) / shift_size.height + 1;
 
    velx = cvCreateImage(cvSize(sX, sY), IPL_DEPTH_32F, 1);
    vely = cvCreateImage(cvSize(sX, sY), IPL_DEPTH_32F, 1);
 
    cvCalcOpticalFlowBM(prev, curr, block_Size, shift_size, max_range, usePrevious, velx, vely);
 
    IplImage *result = cvLoadImage("img/curr.bmp"1);
    
    double dx, dy;
    CvPoint p1, p2;
    int threshold = 2;
 
    // Draw OpticalFlow Vector
    for (int y = 0; y < sY; y++)
    {
        for (int x = 0; x < sX; x++)
        {
            dx = cvGetReal2D(velx, y, x);
            dy = cvGetReal2D(vely, y, x);
 
            if (sqrt(dx * dx + dy * dy) < threshold)
                continue;
 
            p1.x = block_Size.width + x * shift_size.width;
            p1.y = block_Size.height + y * shift_size.height;
 
            p2.x = cvRound(p1.x + dx);
            p2.y = cvRound(p1.y + dy);
 
            cvLine(result, p1, p2, CV_RGB(02550), 1, CV_AA);
        }
    }
 
    cvNamedWindow("velx");
    cvShowImage("velx", velx);
    cvNamedWindow("vely");
    cvShowImage("vely", vely);
 
    cvNamedWindow("OpticalFlow Result");
    cvShowImage("OpticalFlow Result", result);
    cvWaitKey(0);
 
    cvDestroyAllWindows();
 
    return 0;
}
cs


입력 영상 및 결과 영상

    


 결과 영상      (vely)   (velx)


영상 정합(image patch similarity)

컴퓨터비전/영상처리 2015. 4. 4. 00:40

영상의 패치 정합

1. SSD(Sum of Squared Difference)

같은 위치에 있는 픽셀의 차를 구해서 제곱한 값을 다 더해 영역의 유사성 측정을 한다.

유사할 경우 0에 가깝다.


2. SAD(Sum Of Absolute Difference)

같은 위치에 있는 픽셀의 차를 절대값 하고 다 더해 영역의 유사성 측정을 한다.

SSD와 마찬가지로 유사할 경우 0에 가깝다.



3. NCC(Normalized Cross Correlation)

NCC는 SSD와 SAD가 갖는 밝기 차의 문제를 결하기 위해 사용하는 중 하나이다. 

두 영역의 픽셀 평균값과 표준편차를 계산하여 평균 값기가 0, 표준편차는 1이 되도록 정규화한다.


m은 평균 밝기를 나타내며 σ는 표준편차이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#include <cv.h>
#include <highgui.h>
#include <iostream>
 
using namespace cv;
using namespace std;
 
int SSD(Mat &src1, Mat &src2, Point pt1, Point pt2, int patch_size)
{
    // Range Check
    if ((pt1.x - patch_size) < || (pt2.x - patch_size) < || (pt1.x + patch_size) >= src1.rows ||
        (pt2.x + patch_size) >= src2.rows || (pt1.y - patch_size) < || (pt2.y - patch_size) < ||
        (pt1.y + patch_size) >= src1.cols || (pt2.y + patch_size) >= src2.cols)
        throw exception("SSD::Inavlid range");
 
    // Image Channel Check
    if (src1.channels() != src2.channels())
        throw exception("SSD::Image channel not same");
 
    int nSum = 0;
    
    unsigned char *src1_ptr = (unsigned char*)src1.data;
    unsigned char *src2_ptr = (unsigned char*)src2.data;
    int src1_cn = src1.channels();
    int src2_cn = src2.channels();
 
    for (int r = -patch_size; r <= patch_size; r++)
    {
        for (int c = -patch_size; c <= patch_size; c++)
        {
            for (int l = 0; l <= src1_cn; l++)
            {
                nSum += ((src1_ptr[(pt1.x + r) * src1.cols * src1_cn + (pt1.y + c) * src1_cn + l]) - (src2_ptr[(pt2.x + r) * src2.cols * src2_cn + (pt2.y + c) * src2_cn + l]))
                    * ((src1_ptr[(pt1.x + r) * src1.cols * src1_cn + (pt1.y + c) * src1_cn + l]) - (src2_ptr[(pt2.x + r) * src2.cols * src2_cn + (pt2.y + c) * src2_cn + l]));
            }
        }
    }
    
    return nSum;
}
 
int SAD(Mat &src1, Mat &src2, Point pt1, Point pt2, int patch_size)
{
    // Range Check
    if ((pt1.x - patch_size) < || (pt2.x - patch_size) < || (pt1.x + patch_size) >= src1.rows ||
        (pt2.x + patch_size) >= src2.rows || (pt1.y - patch_size) < || (pt2.y - patch_size) < ||
        (pt1.y + patch_size) >= src1.cols || (pt2.y + patch_size) >= src2.cols)
        throw exception("SAD::Inavlid range");
 
    // Image Channel Check
    if (src1.channels() != src2.channels())
        throw exception("SAD::Image channel not same");
    
    int nSum = 0;
 
    unsigned char *src1_ptr = (unsigned char*)src1.data;
    unsigned char *src2_ptr = (unsigned char*)src2.data;
    int src1_cn = src1.channels();
    int src2_cn = src2.channels();
 
    for (int r = -patch_size; r <= patch_size; r++)
    {
        for (int c = -patch_size; c <= patch_size; c++)
        {
            for (int l = 0; l < src1_cn; l++)
            {
                nSum += abs((src1_ptr[(pt1.x + r) * src1.cols * src1_cn + (pt1.y + c) * src1_cn + l]) - (src2_ptr[(pt2.x + r) * src2.cols * src2_cn + (pt2.y + c) * src2_cn + l]));    
            }
        }
    }
 
    return nSum;
}
 
float NCC(Mat &src1, Mat &src2, Point pt1, Point pt2, int patch_size)
{
    // Range Check
    if ((pt1.x - patch_size) < || (pt2.x - patch_size) < || (pt1.x + patch_size) >= src1.rows ||
        (pt2.x + patch_size) >= src2.rows || (pt1.y - patch_size) < || (pt2.y - patch_size) < ||
        (pt1.y + patch_size) >= src1.cols || (pt2.y + patch_size) >= src2.cols)
        throw exception("NCC::Inavlid range");
 
    // Image Channel Check
    if (src1.channels() != src2.channels())
        throw exception("NCC::Image channel not same");
 
    // calcurate average, stendard deviation
    int nSum1 = 0, nSum2 = 0;
    int nSqr1 = 0, nSqr2 = 0;
    
    unsigned char *src1_ptr = (unsigned char*)src1.data;
    unsigned char *src2_ptr = (unsigned char*)src2.data;
    int src1_cn = src1.channels();
    int src2_cn = src2.channels();
        
    for (int r = -patch_size; r <= patch_size; r++)
    {
        for (int c = -patch_size; c <= patch_size; c++)
        {
            for (int l = 0; l < src1_cn; l++)
            {
                int t1 = src1_ptr[(pt1.x + r) * src1.cols * src1_cn + (pt1.y + c) * src1_cn + l];
                int t2 = src2_ptr[(pt2.x + r) * src2.cols * src2_cn + (pt2.y + c) * src2_cn + l];
 
                nSum1 += t1;
                nSum2 += t2;
                nSqr1 += t1 * t1;
                nSqr2 += t2 * t2;
            }
        }
    }
 
    int nPixels = (patch_size * + 1* (patch_size * + 1); // pixel num
    float fMean1 = (float)nSum1 / (float)nPixels;
    float fMean2 = (float)nSum2 / (float)nPixels;
    float fVar1 = (float)nSqr1 / (float)nPixels - (float)fMean1 * (float)fMean1;
    float fVar2 = (float)nSqr2 / (float)nPixels - (float)fMean2 * (float)fMean2;
 
    // NCC
    float fSumNCC = 0.0f;
 
    for (int r = -patch_size; r <= patch_size; r++)
    {
        for (int c = -patch_size; c <= patch_size; c++)
        {
            for (int l = 0; l < src1_cn; l++)
            {
                fSumNCC += (float)((src1_ptr[(pt1.x + r) * src1.cols * src1_cn + (pt1.y + c) * src1_cn + l]) - fMean1) 
                    * ((src2_ptr[(pt2.x + r) * src2.cols * src2_cn + (pt2.y + c) * src2_cn + l]) - fMean2);
            }
        }
    }
 
    return fSumNCC / (fVar1 * fVar2);
}
 
int main()
{
    Mat img, img2;
    img = imread("img/flower.jpg"1);
    img2 = imread("img/flower.jpg"1);
 
    int result_SSD = SSD(img, img2, Point(100100), Point(100100), 3);
    int result_SAD = SAD(img, img2, Point(100100), Point(100100), 3);
    float result_NCC = NCC(img, img2, Point(100100), Point(100100), 3);
 
    cout << "SSD = " << result_SSD << endl;
    cout << "SAD = " << result_SAD << endl;
    cout << "NCC = " << result_NCC << endl;
 
    namedWindow("image");
    imshow("image", img);
    waitKey();
 
    destroyAllWindows();
 
    return 0;
}
cs


해리스 코너(Harris Corner)

컴퓨터비전/영상처리 2015. 4. 3. 21:19

해리스 코너는 크리스 해리스(Chris Harris)라는 사람이 1988년 제안한 것이다.

해리스 코너 검출기는 미분값에 기반을 둔 검출 방법이다. 코너 응답 함수(CRF-Corner Response Function)을 기반으로

코너를 파악한다. 코너 응답 함수(CRF)는 코너가 얼마나 강한지 판별하는 함수이다.


※ 해리스 코너 검출은 다음 5가지 과정을 거친다.

   1. 소벨 마스크를 이용해 미분값을 계산

   2. 미분값의 곱 계산

   3. 미분값에 가우시안 마스크 적용

   4. 코너 응답 함수(CRF) 계산

   5. 비최대값억제(non-maximum supression) 으로 최종 코너 검출




위 행렬식은 2x2행렬의 고유 벡터(Eigen vector)가 얼마나 서로 수직인 방향으로 큰 값을 갖는지를 측정하는 값이다.

여기서 Ix와 Iy는 각각 영상에서 가로와 세로 방향의 미분값을 나타낸다. 이러한 행렬에서 고유 벡터를 구하면 경계선

방향에 수직인 벡터 두개를 얻을 수 있다. 아래 그림과 같이 두 방향의 벡터를 구할 수 있다.


각각 두 벡터는 수직일 경우와 수직보다 작을 경우, 수직보다 클 경우로 구할 수 있다. 

이때 수직인 경우가 가장 좋은 코너점으로 생각하면 된다. 코너의 벡터는 미분을 이용해 고유벡터를 구하면 된다.

고유 벡터를 구하는 것은 고유값 분해(Eigen Value Decomposition)을 계산해야 하지만 조금 복잡하고 시간이 많이 소요된다.

따라서 해리스 코너 검출기에서는 고유 벡터를 직접 구하지 않고 코너 응답 함수(CRF)를 사용한다.



여기서 det(M)은 행렬식을 뜻하고, Trace(M)은 행렬의 대각합을 뜻한다. K값은 보통 0.04를 사용한다.

위와 같이 모든 점들에 관해 코너 응답함수(CRF)를 구하고, 그 값이 적절한 임계값(Threshold)이상이면 코너로 판별하면 된다.



직선 검출 허프 변환

컴퓨터비전/영상처리 2015. 4. 3. 19:08

2차원 공간상의 직선을 표현하는 방법중 가장 쉬운 방법은 y = ax+b이다.

a값이 직선의 기울기, b 값이 y축과 만나는 y절편이다.

직선의 방정식을 이용하면 모든 직선을 실수 a와 b의 조합으로 나타낼 수 있으며, 입력 영상에 나타날 수 있는

직선이 가지는 a, b값의 범위는 매우 많다.



기울기와, y절편값을 조절하면 수많은 직선을 만들 수 있다. 

따라서 직선을 찾기 위해 이 모든 값을 조사하는건 현실적으로 불가능하다.


이 문제를 해결하기 위해 사용하는 방법이 바로 허프 변환(Hough Transform)이다.

좀더 효율적으로 변수를 조합하여 직선을 표현하여 문제를 해결하는 것이다.

허프 변환은 직선 뿐만 아니라 2차원 평면, 원, 3차원 공간평면 등 다양한 도형에 대해 적용이 가능하다.


허프 변환은 아주 신기한 방법이다. 직선의 방정식을 x, y 극좌표계에서 a값(기울기)와 b(y절편)을 아래와 같은

극좌표계로 변형했을때 같은 기울기를 갖는 직선은 한점에서 만난다는 점이다.

따라서 이를 이용하면 직선을 구할 수 있다.


직선을 위 그림과 같이 표현하면 ρ는 원점에서부터 직선 까지의 거리를 나타내며, Θ는 y축과 직선에서 수직으로 그린

선의 각도를 나타낸다. 위 직선의 방정식을 이용하면 유한한 범위의 ρ와 Θ값을 이용하여 평면 위의 직선을 표현할 수 있다.


따라서 기존 직선의 방정식을 위와 같은 직선의 방정식으로 변형하면 한정 범위안의 직선을 찾을 수 있을 것이다.

극좌표계를 사용하면 평면 위의 한점(x,y)을 지나는 모든 직선을 ρ와 Θ의 조합으로 표현할 수 있다.


영상에서 모든 경계선 좌표 (x,y)에 대해 위 작업을 수행하면 많은 수의 ρ와 Θ 조합을 얻을 수 있다.

만약 한 점에서 많은 직선이 교차할 경우 하나의 픽셀 에 많은 점이 교차할 것이다. 

이것을 이용하여 같은 직선상의 좌표를 구할 수 있다.


허프 변환을 구현할 때 각도 Θ값의 범위를 0'~360' 사이가 아닌 0'~180'로 하고, ρ 값이 음수 값을 가질 수 있도록 한다.






에이다부스트(adaboost)

패턴인식 & 기계학습 2015. 2. 27. 18:37

'패턴인식 & 기계학습' 카테고리의 다른 글

Deep Learning 두번째 세미나  (0) 2015.09.29
딥러닝 framework Theano 설치  (0) 2015.09.21
k-means clustering  (0) 2015.02.27
딥러닝 Deep Learning  (0) 2015.02.27
SVM(Support Vector Machine)  (0) 2014.11.04