1.시스템 업데이트 및 필수 패키지 설치

sudo apt update
sudo apt upgrade
sudo apt install build-essential cmake git pkg-config
sudo apt install libjpeg-dev libtiff-dev libpng-dev
sudo apt install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
sudo apt install libxvidcore-dev libx264-dev
sudo apt install libgtk-3-dev
sudo apt install libatlas-base-dev gfortran
sudo apt install python3-dev

 

2.OpenCV 및 OpenCV Contrib 소스 코드 다운로드

git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git

 

3.OpenCV 빌드 디렉토리 생성

cd ~/opencv
mkdir build
cd build

cd ~/opencv는 git  다운 받은 경로로  생각하면 된다.

 

4.CMake를 사용하여 OpenCV 설정

cmake -D CMAKE_BUILD_TYPE=Release \
      -D CMAKE_INSTALL_PREFIX=/usr/local \
      -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
      -D BUILD_EXAMPLES=ON ..

~/opencv_contrib/modules도 git  다운 받은 경로로  변경한다.

 

5.OpenCV 컴파일 및 설치

make -j4
sudo make install
sudo ldconfig

 

 

 

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,

OpenCV IplImage를 사용해서 Labeling 함수 구현


1. Labeling이란?

  우선 1-channel의 gray scale 영상이 필요합니다. 이진화된 아래의 이미지처럼 0 or 255(1-channel)의 값을 갖을 경우 인접한 영역끼리 그룹을 짓는 것을 Labeling(레이블링)이라고 합니다.

 

[그림 1]


아래의 그림처럼 인접 영역끼리 label Number를 매겨 그룹화를 하게됩니다.

 

[그림 2]


 여기서 인접한 pixel을 탐색할 때 8-neighbor 방식을 사용하였습니다. neighbor는 '이웃'이라는 뜻처럼 8-neighbor는 현재 pixel을 기준으로 몇 개의 이웃 pixel들을 탐색하는지를 뜻합니다. 아래의 왼쪽 그림은 현재 pixel을 기준으로 위, 아래, 왼쪽, 오른쪽 총 4방향을 탐색하는 4-neighbor 탐색입니다. 오른쪽 그림은 4-neighbor에 4개의 대각방향을 더 탐색하는 8-neighbor 탐색입니다. 저는 8-neighbor를 사용하였기 때문에 [그림 2]에서 label number가 '4'인 대각 픽셀이 그룹된 것을 확인할 수 있습니다. 이렇게 인접한 영역들을 Grouping한 것이 Labeling입니다.

[그림 3] 4-neighbor & 8-neighbor


2. Labeling 구현 전략

1) 이미지에서 화소 값(255)이 있는 지점까지 탐색

2) 이미 Labeling된 화소인지 확인

3) 아니라면 stack에 현재 x, y 좌표를 저장

4) 8-neighbor 탐색하면서 픽셀화소가 255이면서 Labeling이 안된 지역을     발견하면 stack에 모두 넣는다. (8-neighbor 모두 탐색한다.)

5) stack의 맨 위 좌표를 받아 4)번 과정을 반복한다.

6) 더이상 grouping할 행렬이 없을 때(stack이 비었을 때)까지 수행

7) 이미지의 다음 화소 값이 있는 곳까지 탐색하는 1) 과정부터 다시 시작


3. Labeling 구현 설명

제일 첫 좌표를 (0, 0)이라고 할 때 이미지에서 화소 값(255)이 있는 지점인 (3, 3)까지 오게된다. 이 점을 stack에 push한다.

[그림 4]


[그림 5] stack


  (3, 3)을 기준으로 8-neighbor 탐색을 한다. 해당 pixel에 값이 있고 Labeling이 안된 영역을 stack에 push 한다. [그림 6]을 보면 (3, 3)을 기준으로 3칸이 그림영역이고 현재 Labeling이 안되었기 때문에 [그림 7]처럼 stack에 push 한다.

[그림 6]


  stack의 top인 (4, 3)를 기준으로 다시 8-neighbor 탐색을 하면서 Label Number를 매기면 된다. 

[그림 7]


4. Iplimage로 구현한 Labeling 함수

stl stack을 사용하기 위해 #include <stack> 헤더파일을 선언한다. 
- 함수 호출방법: Labeling(src, dst);
- 함수 인자설명: src(원본이미지), dst(출력이미지)
(labelNumber 값이 출력이미지로 바로 출력되어 잘 안보일 수 있다. 눈에 보이게 하고 싶으면 labelNumber = 100; 이렇게 초기 값을 설정하면 잘 보일 것이다.)

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
#include <stack>
 
void    Labeling    (const IplImage *src, IplImage *dst)
{
    // Only 1-Channel
    if( src->nChannels != 1 )
        return;
 
    // image size load
    int height    = src->height;
    int width    = src->width;
 
    // input image, result image를 image size만큼 동적할당
    unsigned char*    inputImage    = new unsigned char    [height * width];
    int*            resultImage    = new int            [height * width];    
 
    // before labeling prcess, initializing 
    forint y = 0; y < height; y++ ){
        forint x = 0; x < width; x++ ){
            // image copy
            inputImage[width * y + x] = src->imageData[width * y + x];
 
            // initialize result image
            resultImage[width * y + x] = 0;
        }
    }
 
    //// 8-neighbor labeling
    // Labeling 과정에서 stack overflow 방지를 위한 stl <stack>사용 
    stack<Point> st;
    int labelNumber = 0;
    forint y = 1; y < height - 1; y++ ){
        forint x = 1; x < width - 1; x++ ){
            // source image가 255일 경우 + Labeling 수행되지 않은 픽셀에서만 labeling process 시작
            if( inputImage[width * y + x] != 255 || resultImage[width * y + x] != 0 ) continue;
            
            labelNumber++;
            
            // 새로운 label seed를 stack에 push
            st.push(Point(x, y));
 
            // 해당 label seed가 labeling될 때(stack이 빌 때) 까지 수행
            while( !st.empty() ){
                // stack top의 label point를 받고 pop
                int ky = st.top().y;
                int kx = st.top().x;
                st.pop();
 
                // label seed의 label number를 result image에 저장
                resultImage[width * ky + kx] = labelNumber;
 
                // search 8-neighbor
                forint ny = ky - 1; ny <= ky + 1; ny++ ){
                    // y축 범위를 벗어나는 점 제외
                    if( ny < 0 || ny >= height ) continue;
                    forint nx = kx - 1; nx <= kx + 1; nx++ ){
                        // x축 범위를 벗어나는 점 제외
                        if( nx < 0 || nx >= width ) continue;
                        
                        // source image가 값이 있고 labeling이 안된 좌표를 stack에 push
                        if( inputImage[width * ny + nx] != 255 || resultImage[width * ny + nx] != 0 ) continue;
                        st.push(Point(nx, ny));
                        
                        // 탐색한 픽셀이니 labeling
                        resultImage[width * ny + nx] = labelNumber;
                    }
                }
            }        
        }
    }
 
    // dst image에 복사
    forint y = 0; y < height; y ++ ){
        forint x = 0; x < width; x++ ){
            dst->imageData[width * y + x] = resultImage[width * y + x];
        }
    }
 
    // 메모리 해제
    delete[] inputImage;
    delete[] resultImage;
}



출처: http://devmonster.tistory.com/22 [Dev.Monster]

출처: http://devmonster.tistory.com/22 [Dev.Monster]

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,


컨볼루션(Convolution)

입력 영상을 스캔하면서 현재 위치의 픽셀과 마스크 크기 내에 포함되는 주변 픽셀을 마스크 배열과 컨볼루션하여 결과 영상의 현재 위치값으로 결정합니다.  

마스크에서 정의한 가중치에 따라 이미지를 흐리기(blurring) 만들거나 선명하게(sharpening) 만들 수 있습니다. 또는 이미지 상에 있는 에지를 검출하는데 사용할 수 있습니다. 마스크는 3,5,7,9,11.. 처럼 홀수크기를 갖습니다.

 


입력 이미지 상의  위치 (1,1)에 3 x 3 크기의 마스크를  컨볼루션하는 예를 들어 보겠습니다.  이해하기 쉽게 현재 위치 (1,1)에 마스크 중앙 (1,1)이 오도록 겹처놓으면,  마스크 배열 항목에 대응되는 이미지 상의 위치들이 있습니다.  컨볼루션 계산은 마스크와 이미지 상에 대응되는 값끼리 곱하여 모두 더하여 구합니다.  결과값을 결과 영상의 현재 위치에 기록하면 됩니다. 

img_output(1,1) = img_input(0,0) x mask(0,0) + img_input(0,1) x mask(0,1) 

                      + img_input(0,2) x mask(0,2) + img_input(1,0) x mask(1,0) 

                      + img_input(1,1) x mask(1,1) + img_input(1,2) x mask(1,2)

                      + img_input(2,0) x mask(2,0) + img_input(2,1) x mask(2,1) 

                      + img_input(2,2) x mask(2,2) 


컨볼루션시 주의할 점은 이미지 테두리에서는 컨볼루션 계산을 할 수 없습니다. 마스크 배열의 일부 항목이 마스크와 대응되지 않기 때문입니다. 


이 문제를 해결하는 여러가지 방법이 있는데 그 중에 한가지 방법이 마스크 크기 / 2 만큼 입력 영상의 테두리 영역을 무시하고 컨볼루션을 계산하는 것입니다.  3x3 마스크의 경우에는 3/2=1.5이므로 1픽셀 크기만큼 입력 영상의 테두리 영역을 무시하게 됩니다.  입력 영상의 테두리 영역에 해당되는 결과 영상의 위치에는 모두 0으로 채우거나 입력 영상의 테두리 영역에 있는 값을 복사해줍니다.


다른 방법은 범위를 벗어난 경우에는 해당 위치의 값을 0으로 처리하는 것입니다. 마스크 크기 3 x 3의 경우 이미지 가장자리에 크기 1인 0으로 채워진 가상의 테두리가 있는 것으로 보는 것입니다. 


또다른 방법은 이미지 테두리에 있는 값을 범위 벗어난 경우에 사용하는 것입니다. 



스무딩(Smoothing)

영상을 흐리게(Blurring)하거나 영상에 존재하는 노이즈를 제거하기 위해 사용됩니다. 


스무딩은 현재 위치의 픽셀 값과 이웃 픽셀 값들의 평균으로 결과 영상의 현재 위치 값을 결정하는 방법입니다. 영상에 적용하면 선명도가 떨어지는 대신 노이즈를 제거하거나 끊어진 에지를 연결하게 됩니다. 


스무딩을 위한 mask로는  모든 픽셀에 똑같은 가중치를 부여하는 mean mask와 


마스크 중앙으로 갈수록 높은 가중치를 부여하는 gaussian mask가 있습니다. 


영상에 mean mask를 적용한 결과입니다.  마스크의 크기를 크게 할 수록  Blurring 효과가 커지며 계산양은 많아 지기 때문에 결과를 얻기까지 시간이 더 걸립니다. 


입력 영상


3 x 3 mean 마스크를 사용한 경우


5 x 5 mean 마스크를 사용한 경우


25 x 25 mean 마스크를 사용한 경우


구현한 소스 코드입니다. 

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
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray, img_result;
 
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input5.png", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray;
        }
    }
 
 
    //mean mask
    int mask1[3][3= { {1,1,1},
                        { 1,1,1 },
                        { 1,1,1 }};
    int mask2[5][5= { { 1,1,1,1,1 },
                        { 1,1,1,1,1 },
                        { 1,1,1,1,1 },
                        { 1,1,1,1,1 },
                        { 1,1,1,1,1 } };
    int mask3[25][25= { 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
 
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 },
    { 1,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,11,1,1,1,1 } };
 
 
    long int sum;
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int masksize = 3;
        
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            sum = 0;
            for (int i = -1 * masksize / 2; i <= masksize / 2; i++)
            {
                for (int j = -1 * masksize / 2; j <= masksize / 2; j++)
                {
                    
 
                    //영상 범위를 벗어난 경우 테두리 값을 사용 
                    int new_y = y + i;
                    int new_x = x + j;
 
                    if (new_y < 0) new_y = 0;
                    else if (new_y > img_input.rows - 1) new_y = img_input.rows - 1;
 
                    if (new_x < 0) new_x = 0;
                    else if (new_x > img_input.cols - 1) new_x = img_input.cols - 1;
 
 
                    //선택한 마스크 크기 따라 컨볼루션 계산
                    if ( masksize == 3 )
                        sum += img_gray.at<uchar>( new_y, new_x) * mask1[masksize / 2 +i][masksize / 2 +j];
                    else if ( masksize == 5 )
                        sum += img_gray.at<uchar>(new_y, new_x) * mask2[masksize / 2 + i][masksize / 2 + j];
                    else if ( masksize == 25 )
                        sum += img_gray.at<uchar>(new_y, new_x) * mask3[masksize / 2 + i][masksize / 2 + j];
                }
            }
            
            //마스크 크기로 나누어 주어야 한다. 
            sum = sum / (double)(masksize*masksize);
 
            //0~255 사이값으로 조정
            if (sum > 255) sum = 255;
            if (sum < 0) sum = 0;
 
            img_result.at<uchar>(y, x) = sum;
        }
    }
    
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 영상", img_result);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
}
cs



샤프닝(Sharpening)

영상을 선명하게 하거나 에지를 검출하는데 사용됩니다. 


라플라시안(Laplacian) 마스크입니다.  마스크 배열 항목의 합이 0이 됨을 알 수 있습니다.  영상에 적용시키면 픽셀값이 일정한 영역에서는 0이 되고 픽셀값이 크게 변하는 에지가 있는 영역에서는 값이 커지는 게 됩니다. 


라플라시안 마스크를 영상에 적용시켜보면 에지가 검출됩니다.


입력영상


결과 영상


영상을 선명하게 만드는 효과를 얻기 위해서는 픽셀값이 일정한 영역이 보존되어야 합니다. 그렇게 하기 위해서 마스크 배열의 항목들의 합이 1이 되도록 라플라시안 마스크의 중앙값을 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
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
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray, img_result;
 
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input6.png", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray;
        }
    }
 
 
    //라플라시안 마스크
    int mask1[3][3= { {-1,-1,-1},  //에지 검출
                        { -1,8,-1 },
                        { -1,-1,-1 }};
 
    int mask2[3][3= { { -1,-1,-1 }, //영상 선명하게
                        { -1,9,-1 },
                        { -1,-1,-1 } };
 
 
    long int sum;
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int masksize = 3;
        
    for (int y = 0 + masksize / 2; y < img_input.rows - masksize / 2; y++)
    {
        for (int x = 0 + masksize / 2; x < img_input.cols - masksize / 2; x++)
        {
            sum = 0;
            for (int i = -1 * masksize / 2; i <= masksize / 2; i++)
            {
                for (int j = -1 * masksize / 2; j <= masksize / 2; j++)
                {
                    //sum += img_gray.at<uchar>(y + i, x + j) * mask1[masksize / 2 + i][masksize / 2 + j];
                    sum += img_gray.at<uchar>(y + i, x + j) * mask2[masksize / 2 + i][masksize / 2 + j];
                }
            }
            
            //0~255 사이값으로 조정
            if (sum > 255) sum = 255;
            if (sum < 0) sum = 0;
 
            img_result.at<uchar>(y, x) = sum;
        }
    }
    
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 영상", img_result);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
}
cs

출처 : http://webnautes.tistory.com/1044?category=710857

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,


히스토그램 평활화는 히스토그램을 이용하여 이미지의 명암 대비를 개선시키는 방법입니다.  


그레이스케일 영상의 경우 픽셀이 가질 수 있는 값의 범위는 0 ~ 255 사이의 값입니다.  이미지 상에서 픽셀값이 0인 갯수, 픽셀값이 1인 갯수, ... , 픽셀값이 255인 갯수를 세어서 배열에 저장한 것이 히스토그램입니다. 왼쪽 이미지에 대해 히스토그램을 구하여 그래프로 그려보면 중앙의 좁은 범위에 픽셀들이 몰려있는 것을 볼 수 있습니다.  그래프에서 x축은 0~255사이의 픽셀값 범위이며 y축은 픽셀 갯수입니다. 

 



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

히스토그램 평활화를 적용시키면 이미지의 픽셀값이 0~255 범위내에 골고루 분산되어 이미지의 명암대비가 개선됩니다.  



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


구현 과정

1. 입력 영상에 대한 히스토그램과 누적 히스토그램을 계산합니다. 누적 히스토그램은 현재 픽셀값까지의 히스토그램을 더해주는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    //입력 그레이스케일 영상의 히스토그램 계산
    int histogram[256= { 0, };
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            int value = img_gray.at<uchar>(y, x);
            histogram[value] += 1;
        }
    }
 
    //입력 그레이스케일 영상의 누적 히스토그램 계산
    int cumulative_histogram[256= { 0, };
    int sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram[i];
        cumulative_histogram[i] = sum;
    }
cs


2. 정규화된 누적 히스토그램을 구하여 입력 그레이스케일 영상에 히스토그램 평활화를 적용합니다. 

P_new = N[ P_old ] * 255

N : 정규화된 누적 히스토그램

P_old : 입력 영상의 픽셀값

P_new: 결과 영상의 픽셀값

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
    //입력 그레이스케일 영상의 정규화된 누적 히스토그램 계산
    float normalized_cumulative_histogram[256= { 0.0, };
    int image_size = img_input.rows * img_input.cols;
 
    for (int i = 0; i < 256; i++)
    {
        normalized_cumulative_histogram[i] = cumulative_histogram[i] / (float)image_size;
    }
 
 
    //히스토그램 평활화 적용 및 결과 영상의 히스토그램 계산
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int histogram2[256= { 0, };
    for (int y = 0; y<img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            img_result.at<uchar>(y, x) = normalized_cumulative_histogram[img_gray.at<uchar>(y, x)] * 255;
            histogram2[img_result.at<uchar>(y, x)] += 1;
        }
    }
 
    
    //결과 영상의 누적 히스토그램 계산
    int cumulative_histogram2[256= { 0, };
    sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram2[i];
        cumulative_histogram2[i] = sum;
    }
cs


입력 영상의 경우 픽셀들이 중앙에 몰려 있었는데 


이미지 평활화 적용 후, 0~255사이에 넓게 분포하게 되었습니다.  누적 히스토그램도 점차적으로 증가하게 되었습니다.



전체 소스코드입니다.

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
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray, img_result, img_histogram, img_histogram2;
 
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input4.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    //입력영상을 그레이스케일 영상으로 변환
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray;
        }
    }
 
 
    //입력 그레이스케일 영상의 히스토그램 계산
    int histogram[256= { 0, };
 
    for (int y = 0; y < img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            int value = img_gray.at<uchar>(y, x);
            histogram[value] += 1;
        }
    }
 
    //입력 그레이스케일 영상의 누적 히스토그램 계산
    int cumulative_histogram[256= { 0, };
    int sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram[i];
        cumulative_histogram[i] = sum;
    }
 
    //입력 그레이스케일 영상의 정규화된 누적 히스토그램 계산
    float normalized_cumulative_histogram[256= { 0.0, };
    int image_size = img_input.rows * img_input.cols;
 
    for (int i = 0; i < 256; i++)
    {
        normalized_cumulative_histogram[i] = cumulative_histogram[i] / (float)image_size;
    }
 
 
    //히스토그램 평활화 적용 및 결과 영상의 히스토그램 계산
    img_result = Mat(img_input.rows, img_input.cols, CV_8UC1);
    int histogram2[256= { 0, };
    for (int y = 0; y<img_input.rows; y++)
    {
        for (int x = 0; x < img_input.cols; x++)
        {
            img_result.at<uchar>(y, x) = normalized_cumulative_histogram[img_gray.at<uchar>(y, x)] * 255;
            histogram2[img_result.at<uchar>(y, x)] += 1;
        }
    }
 
    
    //결과 영상의 누적 히스토그램 계산
    int cumulative_histogram2[256= { 0, };
    sum = 0;
 
    for (int i = 1; i < 256; i++)
    {
        sum += histogram2[i];
        cumulative_histogram2[i] = sum;
    }
 
 
    //히스토그램 그리기
    img_histogram = Mat( 300600, CV_8UC1, Scalar(0));
    img_histogram2 = Mat(300600, CV_8UC1, Scalar(0));
    
    int max = -1;
    for (int i = 0; i < 256; i++)
        if (max < histogram[i]) max = histogram[i];
 
    int max2 = -1;
    for (int i = 0; i < 256; i++)
        if (max2 < histogram2[i]) max2 = histogram2[i];
 
    for (int i = 0; i<256; i++)
    {
        int histo = 300 * histogram[i] / (float)max;
        int cumulative_histo = 300 * cumulative_histogram[i] / (float)cumulative_histogram[255];
 
        line(img_histogram, cvPoint(i + 10300), cvPoint(i + 10300 - histo), Scalar(255,255,255));
        line(img_histogram, cvPoint(i + 300300), cvPoint(i + 300300 - cumulative_histo), Scalar(255255255));
 
 
        int histo2 = 300 * histogram2[i] / (float)max2;
        int cumulative_histo2 = 300 * cumulative_histogram2[i] / (float)cumulative_histogram2[255];
 
        line(img_histogram2, cvPoint(i + 10300), cvPoint(i + 10300 - histo2), Scalar(255255255));
        line(img_histogram2, cvPoint(i + 300300), cvPoint(i + 300300 - cumulative_histo2), Scalar(255255255));
    }
 
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("입력 그레이스케일 영상", img_gray);
    imshow("결과 그레이스케일 영상", img_result);
    imshow("입력 영상의 히스토그램", img_histogram);
    imshow("평활화 후 히스토그램", img_histogram2);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
    imwrite("img_result.jpg", img_result);
    imwrite("img_histogram.jpg", img_histogram);
    imwrite("img_histogram2.jpg", img_histogram2);
}
cs

출처 : http://webnautes.tistory.com/1043?category=710857

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,


영상처리 관련 개념 및 구현을  틈나는대로 정리하여 블로그에 올릴 계획입니다. 편의상 opencv 라이브러리를 사용하여  이미지를 불러와서 Mat 객체에 저장,  이미지를 화면에 보여주기,  파일로 저장 등의 처리를 했습니다. opencv 설치는 아래 글들을 참고하세요. 


[그래픽스&컴퓨터비전/개발환경] - Visual Studio 2015에서 OpenCV 3.1 연동하기

[그래픽스&컴퓨터비전/개발환경] - OpenCV 3.1을 Ubuntu 16.04에 설치

[그래픽스&컴퓨터비전/개발환경] - OpenCV 3.1을 Ubuntu 14.04에 설치

 


첫번째 내용은 컬러영상을 그레이스케일 영상으로 변환하는 것입니다.  OpenCV에서 보통 이미지를 불러올 때, BGR888 포맷을 사용합니다. 한 픽셀당 Blue 8비트, Green 8비트, Red 8비트 정보가 포함되어 있습니다. RGB라 하지 않고 BGR이라 하는 이유는 메모리상에 저장되는 순서가 Blue, Green, Red 이기 때문입니다. 4개의 픽셀을 예로 들면 다음처럼 저장됩니다.

(0,0)                 (0,1)                (0,2)                 (0,3)

Blue Green Red  Blue Green Red  Blue Green Red  Blue Green Red 


화면에 픽셀이 표시될 때 Blue, Green, Red 값에 따라 다른 색으로 표현됩니다. 각 성분의 값은 0~255까지 가능합니다.  각 성분의 크기가 8비트이기 때문에 2의 8승인 256가지(0~255)를 표현할 수 있기 때문입니다.

Blue 

Green 

Red 

픽셀 색 

 255

255 

255 

흰색 

 0

0

검정색 

 128

128

128 

회색

 255

파란색 

 0

255 

녹색 

 0

255 

빨간색 

 0

255 

255 

노란색 


컬러 영상의 모든 픽셀에 대해 blue, green, red를 더하고서  3으로 나누면 그레이 스케일 영상으로 변환이 됩니다. 

실제로 구현한 코드입니다. 컬러 영상을 저장하고 있는 img_input에서 픽셀 접근하는 부분과 그레이 스케일 영상을 저장하는 img_gray에서 픽셀 접근하는 부분은 자주 사용하게 되니 기억해두면 유용합니다. 

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
#include <opencv2/highgui.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray;
        
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input2.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    // 1채널, uchar로 생성
    //opencv에선 unsigned char을 uchar로  재정의해서 사용한다.
    img_gray = Mat(img_input.rows, img_input.cols, CV_8UC1);
 
    for(int y=0; y<img_input.rows; y++)
    { 
        for (int x = 0; x < img_input.cols; x++)
        {
            //img_input으로부터 현재 위치 (y,x) 픽셀의
            //blue, green, red 값을 읽어온다. 
            uchar blue = img_input.at<Vec3b>(y, x)[0];
            uchar green = img_input.at<Vec3b>(y, x)[1];
            uchar red = img_input.at<Vec3b>(y, x)[2];
 
            //blue, green, red를 더한 후, 3으로 나누면 그레이스케일이 된다.
            uchar gray = (blue + green + red) / 3.0;    
 
            //Mat타입 변수 img_gray에 저장한다. 
            img_gray.at<uchar>(y, x) = gray; 
        }
    }
 
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("그레이스케일 영상", img_gray);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
}
cs


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
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
 
using namespace std;
using namespace cv;
 
 
int main()
{
    Mat img_input, img_gray;
        
    //이미지 파일을 읽어와서 img_input에 저장
    img_input = imread("input2.jpg", IMREAD_COLOR);
    if (img_input.empty())
    {
        cout << "파일을 읽어올수 없습니다." << endl;
        exit(1);
    }
 
 
    cvtColor(img_input, img_gray, COLOR_BGR2GRAY);
 
 
    //화면에 결과 이미지를 보여준다.
    imshow("입력 영상", img_input);
    imshow("그레이스케일 영상", img_gray);
 
    //아무키를 누르기 전까지 대기
    while (cvWaitKey(0== 0);
 
    //결과를 파일로 저장
    imwrite("img_gray.jpg", img_gray);
}

 

 

출처 :  http://webnautes.tistory.com/1042?category=710857

블로그 이미지

맨오브파워

한계를 뛰어 넘어서..........

,