영상 정합(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'로 하고, ρ 값이 음수 값을 가질 수 있도록 한다.






이미지 비율 유지 크기 조절

컴퓨터비전/영상처리 2015. 2. 10. 00:30

Width 고정


Height 고정

'컴퓨터비전/영상처리' 카테고리의 다른 글

해리스 코너(Harris Corner)  (2) 2015.04.03
직선 검출 허프 변환  (0) 2015.04.03
Tracking Learning and Detection(TLD)  (0) 2014.10.30
random tree(임의 숲)  (0) 2014.10.25
영상처리 색공간 변환  (0) 2014.10.20

OpenCV 엠보싱, 수채화, 컬러 스케치 효과

OpenCV를 이용하여 엠보싱효과, 수채화 효과, 컬러 스케치 효과를 적용해 보았다.


                                            [그림 1] 기본 이미지


                                        [그림 2] 엠보싱 효과 적용


                                       [그림 3] 수채화 효과 적용


                                   [그림 4] 컬러 스케치 효과 적용


# 코드

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
#include <cv.h>
#include <highgui.h>
#include <iostream>
 
using namespace std;
using namespace cv;
 
void embose(Mat &), sketch(Mat &), waterColor(Mat &), display(Mat &, String);
 
int main()
{
    Mat image;
    image = imread("flower.jpg");
    
    if (image.empty())
        return -1;
    
    namedWindow("pure image");
    imshow("pure image", image);
    
    embose(image);
    sketch(image);
    waterColor(image);
 
    waitKey(0);
 
}
 
void embose(Mat &src)
{
    /* embose mask
      -1 0 0
       0 0 0
       0 0 1  */
    Mat mask(3, 3, CV_32F, Scalar(0)), dst;
 
    mask.at<float>(0, 0) = -1.0;
    mask.at<float>(2, 2) = 1.0;
    
    filter2D(src, dst, CV_16S, mask);
    dst.convertTo(dst, CV_8U, 1, 128);
 
    display(dst, "embose");
}
 
void sketch(Mat &src)
{
    Mat gray, edge, dst;
    int threshold = 5;
 
    cvtColor(src, gray, CV_BGR2GRAY);
    blur(gray, gray, Size(3, 3));
    Canny(gray, edge, threshold, threshold * 2, 5, 3);
    
    dst.create(src.size(), src.type());
    dst = Scalar::all(128);
    src.copyTo(dst, edge);
    
    display(dst, "sketch");
}
 
void waterColor(Mat &src)
{
    double dsize = 7, sigma = 32;
    int iterate = 20;
    
    Mat t1 = src.clone(), t2;
    
    for (int i = 0; i < iterate; i++)
    {
        if (i % 2 == 0)
            bilateralFilter(t1, t2, dsize, sigma, sigma);
        else
            bilateralFilter(t2, t1, dsize, sigma, sigma);
    }
    if (iterate % 2 == 0)
        display(t1, "waterColor");
    else
        display(t2, "waterColor");
}
 
void display(Mat &img, String name)
{
    namedWindow(name);
    imshow(name, img);
}
 



Tracking Learning and Detection(TLD)

컴퓨터비전/영상처리 2014. 10. 30. 13:35

Tracking-Learning and Detection(TLD) 논문은 Zdenenk Kalal 이 2010년 작성한 논문이다.

아래 동영상은 Zdenenk kalal 자신이 직접 youtube에 올린 동영상이다.

동영상을 보면 추적 물체가 사라짐, 움직임에도 강건한 추적을 함을 볼 수 있다. 

상당히 인상깊은 동영상이었다.



이 알고리즘은 Predator이라고도 불리며 영화 predator가 스스로 학습하며 성장하는 모습을 

보고 지었는지?도 모르겠다.


해당 알고리즘은 tracker와 detector와 learning의 3단계로 이루어져있다.

    

                     

   [그림 1] Tracking Learning and Detection(TLD) 블럭 흐름도


간략하게 소개하면, tracker는 object를 매 프레임 추적하고 detector는 tracker가 잘 추적할 수 있도록

detect을 수행하며, 물체의 유사 외형에 대한 데이터 셋을 tracker에 추가한다. learning은 detector의 

에러 확률을 측정해 갱신하는 역활을 한다. 논문에서 제안한 P-N laerning 가 detector를 강건하게 만드는

역할을 한다. 















'컴퓨터비전/영상처리' 카테고리의 다른 글

직선 검출 허프 변환  (0) 2015.04.03
이미지 비율 유지 크기 조절  (0) 2015.02.10
random tree(임의 숲)  (0) 2014.10.25
영상처리 색공간 변환  (0) 2014.10.20
특징 기술자, 관심 기술자  (0) 2014.10.18

사람얼굴을 검출해보자 (OpenCV)

사람은 각종 배경 여러 물건이 혼합 된 이미지 속에서 사람 얼굴을 쉽게 찾아낸다. 

주변에는 인공지능 까지는 아니더라도 사용자를 알아보는 사례가 많다. 인터넷 쇼핑 사이트에서 자신이 검색한 물건

위주로 정렬해 다시 접속시 보여주는 기능도 한가지 예가 될것이다. 컴퓨터가 얼굴을 인식하도록 하는 방법은 없을까? 

다행이도 OpenCV 라이브러리에서 SDK형태로 제공해 쉽게 테스트 해볼수 있다.

다음은 OpenCV에서 제공하는 얼굴 인식 코드 이다.


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
#include <cv.h>
#include <highgui.h>
#include <iostream>
 
using namespace std;
using namespace cv;
 
String face_cascade = "C:/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
String eye_cascade = "C:/opencv/sources/data/haarcascades/haarcascade_eye.xml";
String img_name = "face.jpg"// 파일 명
 
CascadeClassifier face;
CascadeClassifier eye;
 
int main()
{
    Mat img = imread(img_name);
 
    if (img.data == NULL) {
        cout << img_name << " 이미지 열기 실패" << endl;
        return -1;
    }
 
    if (!face.load(face_cascade) || !eye.load(eye_cascade)) {
        cout << "Cascade 파일 열기 실패" << endl;
        return -1;
    }
    
    #pragma region 얼굴 검출
    Mat gray;
    cvtColor(img, gray, CV_RGB2GRAY);
 
    vector<Rect> face_pos; // 얼굴 위치 저장
    face.detectMultiScale(gray, face_pos, 1.13| CV_HAAR_SCALE_IMAGE, Size(1010)); // 얼굴 검출
 
    // 얼굴 영역 표시
    for (int i = 0; i < (int)face_pos.size(); i++)    {
        rectangle(img, face_pos[i], Scalar(02550), 2);
    }
    #pragma endregion
 
 
    #pragma region 눈 검출
    for (int i = 0; i < (int)face_pos.size(); i++) {
        vector<Rect> eye_pos; // 눈 위치 저장
        
        Mat roi = gray(face_pos[i]); // 관심영역 설정
        eye.detectMultiScale(roi, eye_pos, 1.13| CV_HAAR_SCALE_IMAGE, Size(1010)); // 눈 검출
 
        // 눈 영역 표시
        for (int j = 0; j < (int)eye_pos.size(); j++) {
            Point center(face_pos[i].x + eye_pos[j].x + (eye_pos[j].width / 2), 
                       face_pos[i].y + eye_pos[j].y + (eye_pos[j].height / 2));
            
            int radius = cvRound((eye_pos[j].width + eye_pos[j].height) * 0.2);
            circle(img, center, radius, Scalar(00255), 2); 
        }
    }
    #pragma endregion
 
    namedWindow("검출");
    imshow("검출", img);
        
    waitKey();
    return 0;
}
cs


                                              [그림 1] 얼굴 검출


       [그림 2] 얼굴 검출 & 눈 검출


결과를 보면 얼굴과 눈 검출이 매우 잘됨을 확인할 수 있었다. 

소스를 보면 매우 간단한 구조이다. 이미지와 cascade파일을 불러오고 검출 함수를 통해 출력하는 구조이다.

함수 하나만으로 저렇게 잘 동작한다니 과연 저 함수는 어떻게 동작하는 것일까?


함수는 어떤 파일을 불러와 동작하는것을 확인할 수 있다. 불러온 파일에 따라 얼굴이 검출되기도 하고,

눈이 검출되기도 했다. 불러온 파일을 보니 haarcascade_xxx 가 붙어 있다. 뒤에 오는것에 따라 

얼굴과 눈이 검출됬다. 과연 haarcascade가 무엇일까? 네이버 사전을 검색해보면 cascade는 

작은폭포? 풍성하게 늘어진것 같은 뜻을 지닌다. 그럼 Haar는 무엇일까?

2001년 두 학자(Viola, Jones)가  "Rapid Object Detection using a Boosted Cascade of Simple Features" 

논문에서 제안한 방법이다. 이들은 유사 하르 특징(Haar like feature)이라는 것에 기반한  알고리즘으로 구성

되어 있었다. 

Haar 특징에 대해 인터넷에 검색해보면 아래와 같은 사진을 볼 수 있다.


                                      [그림 3] Haar like feature


저 사각형은 무엇인가? 뭔지 몰라서 한참을 들여다 봤다. 알고보니 사람의 얼굴에는 뭔가 특별한 패턴이 있는것을

알수 있다. 두 눈은 명암이 어둡고 코는 명암이 밝다. 이런 명암을 이용해 패턴을 구하는 것이다. 

이것들을 Haar like feature라고 한다. 사람의 얼굴 위에 흑백의 사각형을 겹쳐 놓은 다음 밝은 영역에 속한 픽셀

값들의 평균에서 어두운 영역에 속한 픽셀값들의 평균의 차이를 구한다. 그 차이가 문턱값(threshold)를 넘으면

사람 얼굴에 대한 유사 하르 특징이 있는것이었다. 사람의 얼굴은 다양하지만 생김새의 패턴은 비슷하므로 

임의의 얼굴 위에서의 특정 위치, 특정 분포에 따른 명암의 차이는 거의 없을것이라고 판단한 것이다. 


              [그림 4] haarcascade_frontalface.xml 일부


위 그림은 haarcascade_frontalface.xml 파일의 일부이다. 태그 <size> 20 20은 얼굴의 크기이고 그 안의 영역에

[그림 3]의 사각형이 돌아다니면서 명암의 평균 차를 측정하는 것이었다. [그림 4]의 rect값인 3 7 14 4 -1은 순서

대로 x좌표, y좌표, 폭(width), 높이(height), 이득(gain)이다. 그리고 마지막 값은 부호를 나타낸다. -1이면 픽셀값의

평균을 빼라는 뜻이고, 2는 2를 곱한후 더하란 뜻이된다. <threshold>는 임계값(문턱치)가 될것이다.



random tree(임의 숲)

컴퓨터비전/영상처리 2014. 10. 25. 22:12

랜덤 트리(random tree)

랜덤 트리는 서로 독립적이게 설계된다. 이진 트리를 보면 하나의 부모 노드로 부터 좌측 노드, 우측 노드으로 나뉜다.


       

                     [그림 1] 이진 트리


부모 노드는 질문을 갖고 있으며 질문의 결과에 따라 왼쪽, 오른쪽으로 분기 한다. 랜덤 트리는 트리 분류기

(tree classifier)를 사용한다. 트리 분류기의 노드는 질문을 갖고 있다. 이진 트리와 마찬가지로 특징 하나를 임계값과

비교하여 어느 쪽으로 진행할지 결정한다. 잎노드에 도착하면 부류를 결정하고 멈춘다.


     

                                        [그림 2] 트리 분류기 동작 방식 예


트리 분류기를 학습 시키려면 노드의 질문 Q를 만드는 방법과 잎 노드에 부류를 할당하는 방법을 결정해야 

한다. 트리 노드의 질문 Q1은 X를 두 집합Xleft, Xright로 나눈다. 이때 Xleft와 Xright는 순도가 높을수록 좋다.

(같은 부류의 데이터일수록 좋다) 예를 들어 두 부류(w1, w2)인 경우 Xleft에 모두 w1, Xright에 모두 w2로 분기되면

가장 이상적이다. 하지만 현실적으로 이런 상황은 거의 불가능 할 것이다. 따라서 순도를 최대로 하는 최적의 특징

과 분할점(임계값)을 찾아야 한다. 찾는 방법은 결정트리분기 를 참조 하면 된다.


이렇게 만든 질문을 루트 노드에 부여한 후 Xleft와 Xright를 가지고 각각 왼쪽과 오른쪽에서 같은 과정을 재귀적으로 

반복하면 된다. 이 분할을 언제까지 할지를 선택해야 하는데 순도 100%가 될되까지 분할하면 인식률은 좋으나 학습 

집합이 과적합(overfitting)되어 일반화 능력이 떨어지는 결과가 생긴다. 따라서 적절한 조건을 설정해 두고, 조건을 

만족하는 노드에서 멈추게 해야 한다. 멈춘 노드가 잎 노드(left node)가 된다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 트리 분류기 학습
// 입력 : 학습 집합 X = {(x1,t1),(x2,t2), ... ,(xn,tn)}
// 출력 : 트리 분류기 R   // 루트 노드를 가리키는 포인터
 
노드 하나를 생성하고 그것을 R이라 한다.
split(R,X);
 
function split(T,X) {
  if(X가 멈춤 조건을 만족)   
     T를 잎 노드로 설정하고 부류를 결정한다.
  else {
     T에서 최적의 특징과 그것의 최적 분할점을 찾아 질문 Q를 만든다.
     Q로 X를 Xleft와 Xright로 나눈다. 
     새로운 노드 Tleft와 Tright를 생성한다.
     T.leftChild = Tleft;
     T.rightChild = Tright;
     split(Tleft, Xleft);
     split(Tright, Xright);
  } 
}



위 학습 알고리즘을 사용하면 하나의 트리 학습이 완료된다. 숲을 만드려면 각각의 트리의 독립성이 가장 중요하다.

만약 같은 학습집합을 가지고 위 알고리즘을 수행하면 같은 트리가 여러개 만들어 질것이다. 같은 트리를 여러개 구성해

봐야 동일한 결과를 출력하므로 아무 소용이 없다. 따라서 서로 다른 분류기는 나름의 차별성을 가져야 한다.


1
2
3
4
5
6
7
8
9
10
// 임의 숲
// 입력 : 학습 집합 X = {(x1,t1), (x2,t2), ..., (x3,t3)}, 샘플링 비율ρ(0≤ρ≤1), 분류기 개수 K
// 출력 : 분류기 양상블 C = {Ck, 1≤k≤k}
 
C = φ;
for(k=1 to K) {
   X에서 임의로 ρN개의 샘플을 뽑아 X`라 한다. 이때 대치를 허용함
   X`를 학습 집합으로 하고 [트리 분류기 학습] 알고리즘을 이용해 트리 분류기 Ck를 만든다.
   트리 분류기를 C에 추가한다.
}


참조 : 컴퓨터비전, 패턴인식(오일석)

'컴퓨터비전/영상처리' 카테고리의 다른 글

이미지 비율 유지 크기 조절  (0) 2015.02.10
Tracking Learning and Detection(TLD)  (0) 2014.10.30
영상처리 색공간 변환  (0) 2014.10.20
특징 기술자, 관심 기술자  (0) 2014.10.18
모라벡 알고리즘  (0) 2014.10.13

OpenCV 요약 정리(자주쓰는 기능)

IplImage 생성

1
2
3
4
// 생성
IplImage *srcimg_R = NULL;
srcimg_R = cvCreateImage(cvSize(m_width,m_height), 8, 3); // cvSize(640,480) 같은 것도 됨
srcimg_R = cvCreateImage(cvGetSize(src_color), 8,3)// 다른 IplImage 사이즈 받아와서

1
2
3
4
5
// 이 방법도 사용 가능
CvSize img_size; 
img_size.height = ImageHeight;
img_size.width = ImageWidth;
IplImage* BGR_image = cvCreateImage(img_size, IPL_DEPTH_8U, 3)


이미지 복사하기

1
2
src = cvCloneImage(src_img)// src가 비어있어야 함. 데이터가 있으면 메모리 계속 쌓인다.
cvCopy(src_img, src_img2);


컬러 이미지 각 채널별 분리 및 결합

1
2
cvCvtPixToPlane( src_hlsimg, Hue, Intensity, Saturation, NULL )// HLS 이미지 각 속성별로 나눔
cvCvtPlaneToPix( Hue, Intensity, Saturation, NULL, Equ_hls )// 다시 합친다


IplImage 이미지 데이터 0초기화(검정 이미지)

1
cvZero(src_img);


IplImage 해제

1
2
if(srcimg_R)
   cvReleaseImage(&srcimg_R);


IplImageROI 설정하기

1
2
3
4
5
//설정한 ROI 부분을 전체 이미지 인 것처럼 다룬다
//cvSetImageROI( IplImage* img, CvRect rect )
//rect는 순서대로 ROI의 왼쪽 위점의 좌표, ROI의 width, height이다.
 
cvSetImageROI(src, cvRect(CenterX-ROI_width/2, CenterY-ROI_height/2, ROI_width, ROI_height));

설정한 ROI 해제하기

1
cvResetImageROI( IplImage* img );


1
2
3
4
5
6
7
8
//ROI가 설정되어 있다면
if(inputimg->roi != 0x00)
{
    //ROI의 속성 접근하기
    inputimg->roi->width 
    inputimg->roi->height
    ...
}


이미지 불러오기, 저장하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//불러오기 1
Img = cvLoadImage("AndroidImage.bmp"); // Img 구조체에 AndroidImage.bmp를 load
 
//불러오기 2
if ((Img = cvLoadImage("AndroidImage.bmp")) == 0) // load image
{
   printf("%s", "image file read has failed!! \n");
   return 0;
}
 
//저장하기
char file_name[20];
sprintf(file_name,"WriteImage.bmp")//파일이름 만들기
cvSaveImage(file_name,srcimg_R)//srcimg_R 이라는 IplImage를 저장 



이미지 위 아래나 좌우 뒤집기, 반전

1
cvFlip(src, dst, 0)//0은 수직방향 반전, 1은 수평방향 반전, -1은 양방향 다 반전



카메라로 부터 입력받기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IplImage *src; 
 
//capture for cam
CvCapture* capture = cvCaptureFromCAM(0); // 0번 카메라로부터 입력받음
 
//get init scene
src=cvRetrieveFrame(capture); //src에 capture된 이미지 담기
 
....
// 처리
....
 
//해제
cvReleaseCapture( &capture );


동영상으로 부터 입력받기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
CvCapture* input_video = 0;
input_video = cvCreateFileCapture( "movie.avi");
 
//받아온 프레임 사이즈
CvSize frame_size;
int height =(int) cvGetCaptureProperty( input_video, CV_CAP_PROP_FRAME_HEIGHT );
int width =(int) cvGetCaptureProperty( input_video, CV_CAP_PROP_FRAME_WIDTH );
int fps =(int) cvGetCaptureProperty(input_video, CV_CAP_PROP_FPS);
printf("width : %d, height : %d, fps : %d\n", width, height, fps)
 
//연산에 사용될 IplImage 구조체
IplImage *src;
 
if( (src=cvQueryFrame(input_video)) != 0)
{
   printf("file read success!!\n");
}
 
while(1)
{
   src=cvQueryFrame(input_video)
}
 
cvReleaseCapture( &input_video );


윈도우 생성 및 파괴

1
2
3
4
5
6
7
8
9
10
11
12
13
//생성
cvNamedWindow("Right Original", CV_WINDOW_AUTOSIZE);
 
//창 움직이기
cvMoveWindow("source_color",610,0);
 
//보이기
cvShowImage( "Right Original", srcimg_R );
 
//창 닫기
cvDestroyAllWindows();  //모든 OpenCV 윈도우 닫기
 
cvDestroyWindow("Right Original"); //특정 윈도우만 닫기



창으로 부터 키 입력 받기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pressed_key=cvWaitKey(0);
 
if(pressed_key=='q'//q 키가 누르면 빠져나가기
    break;
else if(pressed_key=='c'//캡쳐 키 누르면 캡쳐
{
   timer=time(NULL)//현재시간저장
   t=localtime(&timer)//지역시간
   sprintf(file_name,"img_%4d%02d%02d%02d%02d%2d.bmp",t->tm_year + 1900, 
            t->tm_mon +1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec)//파일이름 만들기
   cvSaveImage(file_name, src_color);
 
   printf("%s file saved is success!!\n",file_name);  //확인메시지 출력
}



이미지 크기 줄이기

1
2
3
4
5
6
7
//생성
pEviMonitor = cvCreateImage(cvSize(m_pImgWidth, m_pImgHeight), IPL_DEPTH_8U, 1);
pEviMonitor2 = cvCreateImage(cvSize(m_pImgWidth/2, m_pImgHeight/2), IPL_DEPTH_8U, 1)
 
// 1/2 크기로 생성
// 크기 줄이기
cvResize(pEviMonitor, pEviMonitor2, CV_INTER_LINEAR)//Resize



화면에 글자 쓰기

1
2
3
4
5
6
7
char s_output_result[50];
CvFont font;
 
sprintf(s_output_result,"sum vector x:%1.3f y:%1.3f",sumvector_x,sumvector_y )//우선 sprintf로 문자열 생성
cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, 0.5, 0.5, 0, 1); // font 초기화
 
cvPutText(src_color, s_output_result ,cvPoint(15,20),&font,cvScalar(0,255,0))//cvPoint로 글자 시작 위치 설정


트랙바 생성

1
2
3
4
int hue_threshold=139; //Hue 값의 피부색 threshold
 
cvNamedWindow( "HLS_image", CV_WINDOW_AUTOSIZE );
cvCreateTrackbar("Hue","HLS_image",&hue_threshold,255, NULL );


마우스 입력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void on_mouse( int event, int x, int y, int flags, void* param );
 
......
cvSetMouseCallback( "LKTracker", on_mouse, NULL );
......
 
void on_mouse( int event, int x, int y, int flags, void* param )
{
   if( !image )
      return;
    
   if( image->origin )
      y = image->height - y;
    
   if( event == CV_EVENT_LBUTTONDOWN )
   {
      pt = cvPoint(x,y);
      add_remove_pt = 1;
   }
}

canny edge detect 사용하기

1
2
3
4
5
6
IplImage *canny_R = NULL;
canny_R = cvCreateImage(cvSize(m_width,m_height), 8, 1);
 
cvCvtColor(srcimg_R, grayimg_R, CV_BGR2GRAY)//원본 컬러이미지를 흑백으로 변환하고
 
cvCanny( grayimg_R, canny_R, 40, 130, 3 )//그 흑백이미지를 캐니로 변환



Harris edge detector 사용하기

1
2
IplImage *harris = cvCreateImage(cvSize(WIDTH,HEIGHT), IPL_DEPTH_32F, 1);
cvCornerHarris(src_gray, harris, 3, 9, 0.07)



Smoothing 연산

1
2
3
4
5
6
7
//Gaussian filtering
cvSmooth(src, Gaussed_image, CV_GAUSSIAN, 3)
 
//DoG(Difference of Gaussian)
cvSmooth(src_gray, GaussImg1, CV_GAUSSIAN, 3);
cvSmooth(src_gray, GaussImg2, CV_GAUSSIAN, 27);
cvSub(GaussImg1, GaussImg2, dst);



HLS 이미지로 변환하기

1
2
3
4
5
6
7
8
9
10
11
//HLS 이미지
IplImage* src_hlsimg = cvCreateImage(cvSize(m_width,m_height), 8, 3)
 
//각 속성들 저장할 곳 선언
IplImage* Hue = cvCreateImage(cvSize(m_width,m_height), 8, 1);
IplImage* Intensity = cvCreateImage(cvSize(m_width,m_height), 8, 1);
IplImage* Saturation = cvCreateImage(cvSize(m_width,m_height), 8, 1);
 
cvCvtColor(srcimg, src_hlsimg, CV_BGR2HLS)//src_hlsimg IplImage 구조체에 HLS 이미지 담긴다
cvCvtPixToPlane( src_hlsimg, Hue, Intensity, Saturation, NULL )//HLS 이미지 각 속성별로 나눔
cvCvtPlaneToPix( Hue, Intensity, Saturation, NULL, hsvVideo2 )//도로 합치기



모폴로지 연산 Morphology

1
2
3
IplConvKernel* kernel;
kernel= cvCreateStructuringElementEx( 3, 3, 0, 0, CV_SHAPE_RECT, NULL );
cvMorphologyEx( temp_eyeimg, temp_eyeimg, NULL,kernel, CV_MOP_CLOSE , 1 );



히스토그램 관련

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
CvHistogram* hist;
float min_value, max_value; //히스토그램 최대값 최소값 저장
int hist_size = 255; //히스토그램 축 개수
 
hist = cvCreateHist( 1, &hist_size, CV_HIST_UNIFORM)//1차원의, 각 속성 크기 hist_size인 히스토그램 생성
cvCalcHist( &Hue, hist, 0, 0)//Hue가 1차원 IplImage -> 실질적 입력
cvGetMinMaxHistValue(hist, &min_value, &max_value, 0, 0)//히스토그램 최소최대값 얻기, 최대최소값의 위치
printf("hist min max:%f %f\n",min_value, max_value);
 
cvZero(Hue_hist);
 
int axis_base = 210; //그릴 때, 아래 축이 어느정도에 위치하는가
CvFont font; //축에 글자 쓰기
cvInitFont(&font, CV_FONT_HERSHEY_SIMPLEX|CV_FONT_ITALIC, 0.3, 0.3, 0, 1);
char text[50];
 
for ( int i = 0; i <255; ++i)
{   
    int hist_value = cvRound(cvQueryHistValue_1D(hist,i)*255/max_value)//히스토그램 값 normalization
    cvLine(Hue_hist, cvPoint(i+20, axis_base), cvPoint(i+20, abs(axis_base - hist_value) ), CV_RGB(255,255,255), 1);
    
    if ((i % 20) == 0 )
    {
       sprintf( text, "%d", i);
       cvPutText( Hue_hist, text, cvPoint(i+20, axis_base+10), &font, CV_RGB(255,255,255))
    }
}
 
if(hist) //히스토그램 제거
   cvReleaseHist(&hist);
 


히스토그램 평활화 (histogram equalization)

1
2
3
IplImage* Src = cvCreateImage(cvSize(width,height), 8, 1)//원본 이미지
IplImage* Equalized = cvCreateImage(cvSize(width,height), 8, 1)//평활화 된 이미지
cvEqualizeHist( Src, Equalized );


이미지 회전시키기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CvPoint2D32f rot_center = cvPoint2D32f(ROI_width/2 , ROI_height/2); //회전중심설정
 
// 회전 매트릭스 만들기
CvMat *rot_mat = cvCreateMat( 2, 3, CV_32FC1);
 
//매트릭스 계산 
cv2DRotationMatrix( 
      rot_center, //Source Image의 센터
      Rotation_Angle, //각도 + 값은 시계 반대 반대 방향을 의미
      1, // 이미지 크기(scale)... 
      rot_mat); // 결과를 저장하는 매트릭스
      
//원본 이미지에 ROI 적용하기
cvSetImageROI(src, cvRect(CenterX-ROI_width/2, CenterY-ROI_height/2, CenterX+ROI_width/2, CenterY+ROI_height/2));
 
//변환하기
cvWarpAffine(src, dst, rot_mat, CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS,cvScalarAll(0)); // 선형보간


OpenCV CvMat InputArray로 변환

CvMat *gray_1 = NULL;

으로 선언한 CvMat 구조체가 2.3 버전부터 inputArray으로 변경되었다.


간단하게 (Cv::Mat)만 붙여주면 해결할 수 있다.