컨볼루션(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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,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,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,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 |
'OpenCV > OpenCV 강좌' 카테고리의 다른 글
웹캠으로부터 입력된 영상을 일련번호 붙인 이미지 파일로 저장하는 예제 (52) | 2017.06.29 |
---|---|
OpenCV 3.1 예제 - OpenCV를 pthread, mutex와 같이 사용하기 (2) | 2016.11.25 |
영상처리 강좌 2 - 히스토그램 평활화( Histogram Equalization ) (5) | 2016.09.23 |
Hough Line Transform 구현 (15) | 2016.09.21 |
ArUco Marker Detection 구현 및 Pose Estimation (29) | 2016.09.09 |
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!