본문 바로가기

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

OpenCV 기초 - 6. 모폴로지 연산 (2) - 엣지, 코너 검출

 

 

모폴로지 연산을 이용한, 간단한 형태의 엣지 및 코너 검출에 대해서 알아보겠습니다.

 

모폴로지 필터를 잘 이용하면 영상 내의 특정 특징을 검출할 수 있습니다. 이번 코드에서는 구현을 위해 MorphoFeatures라는 클래스를 정의하여 사용하게 되어 있습니다.

 

<MorphoFeatures.h>

 

<main.cpp>

 

 

- 엣지

영상의 엣지를 감지하는 방법은 간단히 침식된 영상과 팽창된 영상 간의 차이를 계산하여 구할 수 있으며 이를 위 코드를 통해 확인할 수 있습니다. 

두 변환 영상이 주로 엣지 부분에서 차이가 발생하기 때문에 엣지가 두드러지게 되며 morphologyEx 함수에 인자로 MORPH_GRADIENT를 넣어 엣지를 얻을 수 있게 됩니다. 비슷한 결과를 얻는 방법으로는 (팽창 - 원영상), (침식 - 원영상)이 가능하며 결과는 약간 얇아지게 됩니다.

 

- 코너 

모폴로지 필터를 이용한 코너 감지는 OpenCV에서 따로 제공하지 않기 때문에 사각형 - 다이아몬드 - 십자가 - X 모양의 형태로 정의된 네 가지 필터를 순차적으로 적용하여야 합니다.

십자가 모양의 필터를 통해 팽창을 수행하면 코너를 제외한 엣지가 확장되며 다이아몬드 침식을 통해 닫힘 영상 1개를 얻고, 두 필터의 45도 회전된 모양의 필터인 X와 사각형으로 다시 닫힘 연산을 얻은 후 두 영상의 차이 값으로 코너를 얻게 됩니다.

 

 

- 결과

 

 

 

※ 참고 : 모폴로지 연산을 통해 엣지와 코너를 얻을 수 있지만, OpenCV에서는 이보다 더 좋은 결과를 얻을 수 있는 방법들을 제공하고 있기 때문에 추천하지는 않습니다. 

다만, 다른 함수들을 사용하기 힘든 특별한 상황에서는 위와 같은 방법을 적용하여 영상 내의 특징을 검출할 수 있으므로 필요한 경우에 사용하시면 될 것 같습니다.

 

- 코드 첨부

main.cpp

더보기

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

This file contains material supporting chapter 5 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 <opencv2/core/core.hpp>

#include <opencv2/imgproc/imgproc.hpp>

#include <opencv2/highgui/highgui.hpp>

#include "morphoFeatures.h"

 

int main()

{

// 입력 영상 읽기

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

if (!image.data)

return 0;

 

// 영상 띄워 보기

cv::namedWindow("Image");

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

 

// 모폴로지 특징 인스턴스 생성

MorphoFeatures morpho;

morpho.setThreshold(40);

 

// 에지 가져오기

cv::Mat edges;

edges = morpho.getEdges(image);

 

// 에지 영상 띄워 보기

cv::namedWindow("Edge Image");

cv::imshow("Edge Image", edges);

 

// 코너 가져오기

morpho.setThreshold(-1);

cv::Mat corners;

corners = morpho.getCorners(image);

cv::morphologyEx(corners, corners, cv::MORPH_TOPHAT, cv::Mat());

cv::threshold(corners, corners, 40, 255, cv::THRESH_BINARY_INV);

 

// 코너 영상 띄워 보기

cv::namedWindow("Corner Image");

cv::imshow("Corner Image", corners);

 

// 영상 내 코너가 있는 영상 띄워 보기

morpho.drawOnImage(corners, image);

cv::namedWindow("Corners on Image");

cv::imshow("Corners on Image", image);

 

// 사각형 영상을 읽고 띄워 보기

image = cv::imread("robert/square.bmp", 0);

cv::namedWindow("Square Image");

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

 

// 십자가 모양의 구조 요소 생성

cv::Mat cross(5, 5, CV_8U, cv::Scalar(0));

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

 

cross.at<uchar>(2, i) = 1;

cross.at<uchar>(i, 2) = 1;

}

 

// 십자가로 팽창

cv::Mat result;

cv::dilate(image, result, cross);

 

// 결과 띄워 보기

cv::namedWindow("Dilated square with cross");

cv::imshow("Dilated square with cross", result);

 

// 다이아몬드 모양의 구조요소 생성

cv::Mat diamond(5, 5, CV_8U, cv::Scalar(1));

diamond.at<uchar>(0, 0) = 0;

diamond.at<uchar>(0, 1) = 0;

diamond.at<uchar>(1, 0) = 0;

diamond.at<uchar>(4, 4) = 0;

diamond.at<uchar>(3, 4) = 0;

diamond.at<uchar>(4, 3) = 0;

diamond.at<uchar>(4, 0) = 0;

diamond.at<uchar>(4, 1) = 0;

diamond.at<uchar>(3, 0) = 0;

diamond.at<uchar>(0, 4) = 0;

diamond.at<uchar>(0, 3) = 0;

diamond.at<uchar>(1, 4) = 0;

 

// 다이아몬드로 팽창

cv::Mat result2;

cv::erode(result, result2, diamond);

 

// 결과 띄워 보기

cv::namedWindow("Eroded square with diamond");

cv::imshow("Eroded square with diamond", result2);

 

// 영상을 하나로 조합하기

cv::Mat final(100, 300, CV_8U);

cv::Mat window = final(cv::Rect(0, 0, 100, 100));

image.copyTo(window);

window = final(cv::Rect(100, 0, 100, 100));

result.copyTo(window);

window = final(cv::Rect(200, 0, 100, 100));

result2.copyTo(window);

 

// 조합 결과를 띄워 보기

cv::namedWindow("Combined");

cv::imshow("Combined", final);

 

// 조합 결과 저장

cv::imwrite("squares.bmp", final);

 

cv::waitKey();

 

return 0;

 

}

 

morphoFeatures.h

더보기

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

This file contains material supporting chapter 5 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

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

 

#if !defined MORPHOF

#define MORPHOF

 

#include <opencv2/core/core.hpp>

#include <opencv2/imgproc/imgproc.hpp>

 

class MorphoFeatures {

 

private:

 

// 이진 영상을 만들기 위한 경계값 

int threshold;

// 코너 감지에 사용하는 구조 요소

cv::Mat cross;

cv::Mat diamond;

cv::Mat square;

cv::Mat x;

 

void applyThreshold(cv::Mat& result) {

 

// 결과에 경계값 적용

if (threshold>0)

cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY_INV);

}

 

public:

 

MorphoFeatures() : threshold(-1), cross(5, 5, CV_8U, cv::Scalar(0)),

diamond(5, 5, CV_8U, cv::Scalar(1)),

square(5, 5, CV_8U, cv::Scalar(1)),

x(5, 5, CV_8U, cv::Scalar(0)) {

 

// 십자가 모양을 갖는 구조 요소 생성

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

 

cross.at<uchar>(2, i) = 1;

cross.at<uchar>(i, 2) = 1;

}

 

// 다이아몬드 모양을 갖는 구조 요소 생성

diamond.at<uchar>(0, 0) = 0;

diamond.at<uchar>(0, 1) = 0;

diamond.at<uchar>(1, 0) = 0;

diamond.at<uchar>(4, 4) = 0;

diamond.at<uchar>(3, 4) = 0;

diamond.at<uchar>(4, 3) = 0;

diamond.at<uchar>(4, 0) = 0;

diamond.at<uchar>(4, 1) = 0;

diamond.at<uchar>(3, 0) = 0;

diamond.at<uchar>(0, 4) = 0;

diamond.at<uchar>(0, 3) = 0;

diamond.at<uchar>(1, 4) = 0;

 

// Creating the x-shaped structuring element

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

 

x.at<uchar>(i, i) = 1;

x.at<uchar>(4 - i, i) = 1;

}

}

 

void setThreshold(int t) {

 

threshold = t;

}

 

int getThreshold() const {

 

return threshold;

}

 

cv::Mat getEdges(const cv::Mat &image) {

 

// 그레디언트 영상 가져오기

cv::Mat result;

cv::morphologyEx(image, result, cv::MORPH_GRADIENT, cv::Mat());

 

// 이진 영상을 얻기 위한 경계값 적용

applyThreshold(result);

 

return result;

}

 

cv::Mat getCorners(const cv::Mat &image) {

 

cv::Mat result;

 

// 십자가로 팽창

cv::dilate(image, result, cross);

 

// 다이아몬드로 침식

cv::erode(result, result, diamond);

 

cv::Mat result2;

// X로 팽창

cv::dilate(image, result2, x);

 

// 사각형으로 침식

cv::erode(result2, result2, square);

 

// 두 닫힘 영상 간의 차이값으로 코너 얻기

cv::absdiff(result2, result, result);

 

// 이진 영상을 얻기 위한 경계값 적용

applyThreshold(result);

 

return result;

}

 

void drawOnImage(const cv::Mat& binary, cv::Mat& image) {

 

cv::Mat_<uchar>::const_iterator it = binary.begin<uchar>();

cv::Mat_<uchar>::const_iterator itend = binary.end<uchar>();

 

// 각 화소별로 

for (int i = 0; it != itend; ++it, ++i) {

if (!*it)

cv::circle(image, cv::Point(i%image.step, i / image.step), 5, cv::Scalar(255, 0, 0));

}

}

};

 

 

 

#endif