본문 바로가기

영상처리/OpenCV 및 영상처리 이론

OpenCV 기초 - 9. 영상 필터링 (2) - 엣지 검출

 

방향성 필터를 이용한 엣지 검출에 대해서 알아보겠습니다.

 

 

영상처리에서는 영상을 이해하고 영상 내의 정보를 이용하여 여러가지 작업을 수행합니다. 이때 영상 내의 정보에는 여러가지 정보들이 있을 수 있습니다. 이중 특별히 feature라고 명칭하는, 영상이 가지는 특별한 정보들이 있습니다. blob, corner, edge가 이러한 feature(특징 or 특징점)에 해당하는 기본적인 요소들입니다.

 

- 코드

더보기

/*------------------------------------------------------------------------------------------*\

This file contains material supporting chapter 6 of the cookbook:

Computer Vision Programming using the OpenCV Library.

by Robert Laganiere, Packt Publishing, 2011.

 

This program is free software; permission is hereby granted to use, copy, modify,

and distribute this source code, or portions thereof, for any purpose, without fee,

subject to the restriction that the copyright notice may not be removed

or altered from any source or altered source distribution.

The software is released on an as-is basis and without any warranties of any kind.

In particular, the software is not guaranteed to be fault-tolerant or free from failure.

The author disclaims all warranties with regard to this software, any use,

and any consequent failure, is purely the responsibility of the user.

 

Copyright (C) 2010-2011 Robert Laganiere, www.laganiere.name

\*------------------------------------------------------------------------------------------*/

 

#include <iostream>

#include <iomanip>

#include <opencv2/core/core.hpp>

#include <opencv2/imgproc/imgproc.hpp>

#include <opencv2/highgui/highgui.hpp>

#include "laplacianZC.h"

 

int main()

{

// 입력 영상 읽기

cv::Mat image = cv::imread("robert/boldt.jpg", 0);

if (!image.data)

return 0;

 

// 영상 띄워 보기

cv::namedWindow("Original Image");

cv::imshow("Original Image", image);

 

// 소벨 X 미분 계산

cv::Mat sobelX;

cv::Sobel(image, sobelX, CV_8U, 1, 0, 3, 0.4, 128);

 

// 영상 띄워 보기

cv::namedWindow("Sobel X Image");

cv::imshow("Sobel X Image", sobelX);

 

// 소벨 Y 미분 계산

cv::Mat sobelY;

cv::Sobel(image, sobelY, CV_8U, 0, 1, 3, 0.4, 128);

 

// 영상 띄워 보기

cv::namedWindow("Sobel Y Image");

cv::imshow("Sobel Y Image", sobelY);

 

// 소벨 놈 계산

cv::Sobel(image, sobelX, CV_16S, 1, 0);

cv::Sobel(image, sobelY, CV_16S, 0, 1);

cv::Mat sobel;

// L1 놈 계산

sobel = abs(sobelX) + abs(sobelY);

 

double sobmin, sobmax;

cv::minMaxLoc(sobel, &sobmin, &sobmax);

std::cout << "sobel value range: " << sobmin << "  " << sobmax << std::endl;

 

// 화소값을 창에 출력

for (int i = 0; i<12; i++) {

for (int j = 0; j<12; j++)

std::cout << std::setw(5) << static_cast<int>(sobel.at<short>(i + 135, j + 362)) << " ";

std::cout << std::endl;

}

std::cout << std::endl;

std::cout << std::endl;

std::cout << std::endl;

 

// 8-비트 영상으로 변환

// sobelImage = -alpha*sobel + 255

cv::Mat sobelImage;

sobel.convertTo(sobelImage, CV_8U, -255. / sobmax, 255);

 

// 영상 띄워 보기

cv::namedWindow("Sobel Image");

cv::imshow("Sobel Image", sobelImage);

 

// 소벨 놈에 대한 경계값 적용(낮은 경계값)

cv::Mat sobelThresholded;

cv::threshold(sobelImage, sobelThresholded, 225, 255, cv::THRESH_BINARY);

 

// 영상 띄워 보기

cv::namedWindow("Binary Sobel Image (low)");

cv::imshow("Binary Sobel Image (low)", sobelThresholded);

 

// 소벨 놈에 대한 경계값 적용(높은 경계값)

cv::threshold(sobelImage, sobelThresholded, 190, 255, cv::THRESH_BINARY);

 

// 영상 띄워 보기

cv::namedWindow("Binary Sobel Image (high)");

cv::imshow("Binary Sobel Image (high)", sobelThresholded);

 

// 3x3 라플라시안 계산

cv::Mat laplace;

cv::Laplacian(image, laplace, CV_8U, 1, 1, 128);

 

// 영상 띄워 보기

cv::namedWindow("Laplacian Image");

cv::imshow("Laplacian Image", laplace);

 

// Print window pixel values

for (int i = 0; i<12; i++) {

for (int j = 0; j<12; j++)

std::cout << std::setw(5) << static_cast<int>(laplace.at<uchar>(i + 135, j + 362)) - 128 << " ";

std::cout << std::endl;

}

std::cout << std::endl;

std::cout << std::endl;

std::cout << std::endl;

 

// 7x7 라플라시안 계산

cv::Laplacian(image, laplace, CV_8U, 7, 0.01, 128);

 

// 영상 띄워 보기

cv::namedWindow("Laplacian Image");

cv::imshow("Laplacian Image", laplace);

 

// 화소값을 창에 출력

for (int i = 0; i<12; i++) {

for (int j = 0; j<12; j++)

std::cout << std::setw(5) << static_cast<int>(laplace.at<uchar>(i + 135, j + 362)) - 128 << " ";

std::cout << std::endl;

}

 

// 작은 창 추출

cv::Mat window(image, cv::Rect(362, 135, 12, 12));

cv::namedWindow("Image window");

cv::imshow("Image window", window);

cv::imwrite("window.bmp", window);

 

// LaplacianZC 클래스를 이용한 라플라시안 계산

LaplacianZC laplacian;

laplacian.setAperture(7);

cv::Mat flap = laplacian.computeLaplacian(image);

double lapmin, lapmax;

cv::minMaxLoc(flap, &lapmin, &lapmax);

std::cout << "Laplacian value range=[" << lapmin << "," << lapmax << "]\n";

laplace = laplacian.getLaplacianImage();

cv::namedWindow("Laplacian Image (7x7)");

cv::imshow("Laplacian Image (7x7)", laplace);

 

// 라플라시안 값 출력

std::cout << std::endl;

for (int i = 0; i<12; i++) {

for (int j = 0; j<12; j++)

std::cout << std::setw(5) << static_cast<int>(flap.at<float>(i + 135, j + 362) / 100) << " ";

std::cout << std::endl;

}

std::cout << std::endl;

 

// 영교차점 계산 및 띄워 보기

cv::Mat zeros;

zeros = laplacian.getZeroCrossings(lapmax);

cv::namedWindow("Zero-crossings");

cv::imshow("Zero-crossings", zeros);

 

// 영교차점 계산 및 띄워 보기(소벨 버전)

zeros = laplacian.getZeroCrossings();

zeros = laplacian.getZeroCrossingsWithSobel(50);

cv::namedWindow("Zero-crossings (2)");

cv::imshow("Zero-crossings (2)", zeros);

 

// 화소값을 창에 출력

for (int i = 0; i<12; i++) {

for (int j = 0; j<12; j++)

std::cout << std::setw(2) << static_cast<int>(zeros.at<uchar>(i + 135, j + 362)) << " ";

std::cout << std::endl;

}

 

// 영상을 창에 띄워 보기

cv::rectangle(image, cv::Point(362, 135), cv::Point(374, 147), cv::Scalar(255, 255, 255));

cv::namedWindow("Original Image with window");

cv::imshow("Original Image with window", image);

 

cv::waitKey();

return 0;

}

 

코너나 엣지같은 특징들은 자신을 경계로하여 주변의 밝기값이 급격하게 변화하는 현상을 보이기 때문에 고주파 성분에 해당하게 됩니다. 따라서 이러한 고주파 성분을 찾아내기 위해 영상 전반에 미분을 수행하고 미분값이 큰, 즉 기울기가 크게 얻어지는 지점을 엣지로 검출하게 됩니다. 코너의 경우 엣지중에서 특별한 부분에 해당하기 때문에 부수적인 연산을 통해 엣지 중에서도 코너성분인지 아닌지를 파악하여 결정하게 됩니다.

 

 

<소벨 연산자>

 

마스크를 통해 각 축방향으로의 디지털 형태의 미분을 구현하게 됩니다. 이때 주방향으로 1이 아닌 2의 가중치를 부여하여 마스크를 구성하고 있습니다. 

 

소벨 연산은 마스크의 특성상 이미지의 x방향과 y방향으로 총 2번 수행하여야합니다. 이렇게 소벨 연산을 통해 얻어지는 기울기 자체를 그래디언트(Gradient)라고 볼 수 있지만, 실제로는 많은 사람들이 magnitude of gradient에 해당하는 

를 간략화한 G = |Gx| + |Gy|의 형태로 매그니튜드를 표현하며 이 값을 그래디언트라고 부릅니다.

 


 

2차 미분의 경우 영상처리에서 아래와 같이 정의하며 이를 라플라시안(Laplacian)이라고 부릅니다.

 

.

<라플라시안 연산자>

 

 

이 라플라시안 연산자는 디지털 2차미분의 수식적 형태를 마스크로 가져온 것이며 

 

 

에서 픽셀 사이의 거리에 해당하는 h를 1로 설정하면 위와 같은 마스크의 형태가 얻어지는 것을 확인할 수 있습니다.

 

2차 미분에 해당하는 연산은 1차 미분에 비하여 영상 내에 블랍이나, 섬세한 부분을 더 잘 검출하는 경향이 있다고 합니다. 이러한 특징 때문에 2차 미분은 주로 영상개선에 사용하며 1차 미분은 특징 검출에 사용한다고 합니다.



-결과