반응형


컨볼루션(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




반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. 신당당 2020.06.13 18:08

    안녕하세요. 제가 매트랩에서 kernel convolution을 하려고 하는데 256x256사이즈 이미지인데 내장함수 사용안하고 하니까 100분이나 걸리는데 원래 그런건가요??? 컨볼루션 내장함수 사용하지 않고 계산량 줄이는 방법이 없을까요????

    • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2020.06.13 18:12 신고

      내장 함수를 사용하지 않고 직접 픽셀에 접근하면 속도가 느릴 수 밖에 없습니다.

      계산량을 줄이려면 내장 함수가 어떻게 구현했는지 봐야 합니다.

      물론 내장 함수의 코드가 공개된 경우만 가능하지만요.

+ Recent posts