[C++ openCV] 소벨 마스크를 사용한 엣지 검출
소벨 마스크를 알아보기 전, ‘컨볼루션이(Convolution)’라는 개념을 알아야 한다. 컨볼루션은 한국 말로는 합성곱을 나타내며, 하나의 함수와 또 다른 함수를 반전 이동한 값을 곱한 다음, 구간에 대해 적분하여 새로운 함수를 구하는 수학 연산자이다(출처: 위키백과).
소벨 마스크
‘소벨 마스크’는 영상에서 경계선을 검출하는데에 있어서 사용되는 마스크 중 하나이다. 영상 이미지를 3X3에 해당하는 소벨 마스크를 사용하여 한 번 쭉 훑으면서 컨볼루션을 하고, 값이 급격히 변하는 곳을 찾아 그 부분만을 밝게 나타내는 것이다.
컨볼루션은 다음과 같이 실행한다.
이미지에서 마스크 크기만큼 짤라서 각 픽셀값과 마스크의 각 값을 곱한 후 더한 값으로 다시 이미지를 만든다. 다시 만든 이미지는 원본 영상보다 작아지므로 제로패딩(Zeropadding)을 해주어야 한다.
우선, x방향과 y방향의 3x3 소벨 마스크 필터를 통해 컨볼루션을 해주어야 한다. 각 방향의 소벨 마스크는 다음과 같다.
Code
main.cpp는 다음과 같다.
#include "iostream"
#include "opencv2/opencv.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "header.h"
#define IMAGE_PATH "opencv_test_image.jpg"
int main() {
//image 받아오기(gray로..)
cv::Mat input = cv::imread(IMAGE_PATH, cv::IMREAD_GRAYSCALE);
//sobelMask 사용하여 얻은 x, y의 image를 합치기 위한 matrix
cv::Mat sobel_sum;
//zeropadding 함수 사용 - 영상 줄어듦 방지
cv::Mat zeropd = zeropadding(input);
//자연스럽게 하기위해 zeropadding한 부분에 original image의 테두리 부분 값으로 채워줌
cv::Mat samevlpd = samevlpadding(input);
//구현한 sobelMask 함수 사용
cv::Mat sobel_x = sobelMask_x(samevlpd);
cv::Mat sobel_y = sobelMask_y(samevlpd);
//x, y 합
add(sobel_x, sobel_y, sobel_sum);
//image 보기 및 닫기
cv::imshow("origin", input);
cv::imshow("zero", zeropd);
cv::imshow("same", samevlpd);
cv::imshow("sobel_x", sobel_x);
cv::imshow("sobel_y", sobel_y);
cv::imshow("sobel_sum", sobel_sum);
cv::waitKey(0);
cv::destroyAllWindows;
}
header.cpp는 다음과 같다.
#include "iostream"
#include "opencv2/opencv.hpp"
//x방향 sobelMask
cv::Mat sobelMask_x(cv::Mat input) {
cv::Mat sobel_image = cv::Mat::zeros(input.rows, input.cols, input.type());
float sobeldata_x[] = { 1, 2, 1, 0, 0, 0, -1, -2, -1 };
//convolution
for (int row = 1; row < input.rows - 1; row++) {
for (int col = 1; col < input.cols - 1; col++) {
int sum_x = 0;
int idx = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
sum_x += input.at<uchar>(row + i, col + j) * sobeldata_x[idx];
idx++;
}
}
//0보다 작은 값은 0으로, 255보다 큰 값은 255로 채워줌
if (sum_x < 0) { sum_x = 0; }
else if (sum_x > 255) { sum_x = 255; }
sobel_image.at<uchar>(row - 1, col - 1) = (uchar)sum_x;
}
}
return sobel_image;
}
//y방향 sobelMask
cv::Mat sobelMask_y(cv::Mat input) {
cv::Mat sobel_image = cv::Mat::zeros(input.rows, input.cols, input.type());
float sobeldata_y[] = { -1, 0, 1, -2, 0 ,2, -1, 0, 1 };
//convolution
for (int row = 1; row < input.rows - 1; row++) {
for (int col = 1; col < input.cols - 1; col++) {
int sum_y = 0;
int idx = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
sum_y += input.at<uchar>(row + i, col + j) * sobeldata_y[idx];
idx++;
}
}
//0보다 작은 값은 0으로, 255보다 큰 값은 255로 채워줌
if (sum_y <= 0) { sum_y = 0; }
else if (sum_y >= 255) { sum_y = 255; }
sobel_image.at<uchar>(row - 1, col - 1) = (uchar)sum_y;
}
}
return sobel_image;
}
//Zeropadding
cv::Mat zeropadding(cv::Mat input) {
cv::Mat zeropd = cv::Mat::zeros(input.rows + 2, input.cols + 2, input.type());
for (int row = 0; row < input.rows; row++) {
for (int col = 0; col < input.cols; col++) {
zeropd.at<uchar>(row + 1, col + 1) = input.at<uchar>(row, col);
}
}
return zeropd;
}
//자연스럽게 패딩하기
cv::Mat samevlpadding(cv::Mat input) {
//zeropadding
cv::Mat samevlpd = cv::Mat::zeros(input.rows + 2, input.cols + 2, input.type());
for (int row = 0; row < input.rows; row++) {
for (int col = 0; col < input.cols; col++) {
samevlpd.at<uchar>(row + 1, col + 1) = input.at<uchar>(row, col);
}
}
//zeropadding 부분을 original image의 테두리 값으로 넣어줌
//python에서는 -1이 배열의 마지막 index를 나타내는데 c++에서는 사용할 수 없음
samevlpd.at<uchar>(0, 0) = input.at<uchar>(0, 0);
samevlpd.at<uchar>(input.rows + 1, 0) = input.at<uchar>(input.rows - 1, 0);
samevlpd.at<uchar>(0, input.cols + 1) = input.at<uchar>(0, input.cols - 1);
samevlpd.at<uchar>(input.rows + 1, input.cols + 1) = input.at<uchar>(input.rows - 1, input.cols - 1);
for (int row = 0; row < input.rows; row++) {
samevlpd.at<uchar>(row + 1, 0) = input.at<uchar>(row, 0);
samevlpd.at<uchar>(row + 1, input.cols + 1) = input.at<uchar>(row, input.cols - 1);
}
for (int col = 0; col < input.cols; col++) {
samevlpd.at<uchar>(0, col + 1) = input.at<uchar>(0, col);
samevlpd.at<uchar>(input.rows + 1, col + 1) = input.at<uchar>(input.rows - 1, col);
}
return samevlpd;
}
header.h는 다음과 같다.
#ifndef HEADER
#define HEADER
cv::Mat sobelMask_x(cv::Mat input);
cv::Mat sobelMask_y(cv::Mat input);
cv::Mat zeropadding(cv::Mat input);
cv::Mat samevlpadding(cv::Mat input);
#endif // !HEADER
Result
original - gray색으로 받아온 원본 이미지
zero - 원본 이미지를 Zeropadding한 이미지
same - 원본 이미지를 Zeropadding 해주고 원본 이미지의 테두리 값을 넣어준 이미지
sobel_x - x방향 소벨 마스크 적용한 이미지
sobel_y - y방향 소벨 마스크 적용한 이미지
sobel_sum - x방향 소벨 마스크 적용한 이미지 와 y방향 소벨 마스크 적용한 이미지를 합친 이미지