캐니엣지검출기

전종원·2022년 11월 2일
0

1. 캐니 엣지 검출 알고리즘

1.1. 좋은 엣지 검출기의 조건

  • 정확한 검출: 엣지가 아닌 점을 엣지로 찾거나 또는 엣지를 검출하지 못하는 확률을 최소화
  • 정확한 위치: 실제 엣지의 중심을 검출
  • 단일 엣지: 하나의 엣지는 하나의 점으로 표현

1.2. 캐니엣지검출법

  • 가우시안 필터링(잡음제거목적)

  • 그래디언트 계산(크기&방향)

    • sobel mask 사용
  • non maximum suppression

    • 하나의 엣지가 여러개의 픽셀로 표현되는 현상을 없애기 위해 gradient 크기가 local maximum인 픽셀만 엣지픽셀로 설정
    • gradient 방향에 위치한 두개의 픽셀과 국지적 최대를 검사
  • 이중임계값을 이용한 히스테리시스 엣지 트래킹

    • 두개의 임계값을 사용: T(low), T(high)
    • 강한 엣지: 촤종 엣지로 선정
    • 약한 엣지: 강한 엣지와 연결된 픽셀만 최종 엣지로 선정

1.3. OpenCV 캐니엣지검출기

void Canny(InputArray image, OutputArray deges, double threshold1, double threshold2, 
		   int apertureSize = 3, bool L2gradient - false);
  • image: 입력영상
  • edges: 엣지영상
  • threshold1: 하단 임계값
  • threshold2: 상단 임계값
  • apertureSize: 소벨 연산을 위한 커널 크기
  • L2gradient: L2norm 사용여부.

2. 캐니 엣지 검출기 구현

#include <iostream>
#include "opencv2/opencv.hpp"
#include "mycanny.h"

using namespace std;
using namespace cv;

void myCanny(const Mat& src, Mat& dst, double threshold1, double threshold2)
{
	// 1. Gaussian filter
	Mat gauss;
	GaussianBlur(src, gauss, Size(), 0.5);

	// 2. Gradient by Sobel operator
	Mat dx, dy;
	Sobel(gauss, dx, CV_32F, 1, 0);
	Sobel(gauss, dy, CV_32F, 0, 1);

	Mat mag = Mat::zeros(src.rows, src.cols, CV_32F);
	Mat ang = Mat::zeros(src.rows, src.cols, CV_32F);
	
	for (int j = 0; j < src.rows; j++) {
		float* pDx = dx.ptr<float>(j);
		float* pDy = dy.ptr<float>(j);
		float* pMag = mag.ptr<float>(j);
		float* pAng = ang.ptr<float>(j);

		for (int i = 0; i < src.cols; i++) {
			// magŽÂ ±×·¡µðŸðÆ®ÀÇ Å©±â
			pMag[i] = sqrt(pDx[i] * pDx[i] + pDy[i] * pDy[i]);

			// angŽÂ ±×·¡µðŸðÆ®ÀÇ ¹æÇâ (°ªÀÌ °¡Àå ±Þ°ÝÇÏ°Ô º¯ÇÏŽÂ ¹æÇâ)
			if (pDx[i] == 0)
				pAng[i] = 90.f;
			else
				pAng[i] = float(atan(pDy[i] / pDx[i]) * 180 / CV_PI);
		}
	}

	// 3. Non-maximum suppression
	enum DISTRICT { AREA0 = 0, AREA45, AREA90, AREA135, NOAREA };
	const int ang_array[] = { AREA0, AREA45, AREA45, AREA90, AREA90, AREA135, AREA135, AREA0 };

	const uchar STRONG_EDGE = 255;
	const uchar WEAK_EDGE = 128;

	vector<Point> strong_edges;
	dst = Mat::zeros(src.rows, src.cols, CV_8U);

	for (int j = 1; j < src.rows - 1; j++) {
		for (int i = 1; i < src.cols - 1; i++) {
			// ±×·¡µðŸðÆ® Å©±â°¡ th_lowºžŽÙ Å« ÇÈŒ¿¿¡ ŽëÇØŒ­žž ±¹ÁöÀû ÃÖŽë °Ë»ç.
			// ±¹ÁöÀû ÃÖŽëÀÎ ÇÈŒ¿¿¡ ŽëÇØŒ­žž °­ÇÑ ¿§Áö ¶ÇŽÂ ŸàÇÑ ¿§Áö·Î Œ³Á€.
			float mag_value = mag.at<float>(j, i);
			if (mag_value > threshold1) {
				// ±×·¡µðŸðÆ® ¹æÇâ¿¡ 90µµžŠ ŽõÇÏ¿© ¿§ÁöÀÇ ¹æÇâÀ» °è»ê (4°³ ±ž¿ª)
				int ang_idx = cvFloor((ang.at<float>(j, i) + 90) / 22.5f);

				// ±¹ÁöÀû ÃÖŽë °Ë»ç
				bool local_max = false;
				switch (ang_array[ang_idx]) {
				case AREA0:
					if ((mag_value >= mag.at<float>(j - 1, i)) && (mag_value >= mag.at<float>(j + 1, i))) {
						local_max = true;
					}
					break;
				case AREA45:
					if ((mag_value >= mag.at<float>(j - 1, i + 1)) && (mag_value >= mag.at<float>(j + 1, i - 1))) {
						local_max = true;
					}
					break;
				case AREA90:
					if ((mag_value >= mag.at<float>(j, i - 1)) && (mag_value >= mag.at<float>(j, i + 1))) {
						local_max = true;
					}
					break;
				case AREA135:
				default:
					if ((mag_value >= mag.at<float>(j - 1, i - 1)) && (mag_value >= mag.at<float>(j + 1, i + 1))) {
						local_max = true;
					}
					break;구현
				}

				// °­ÇÑ ¿§Áö¿Í ŸàÇÑ ¿§Áö ±žºÐ.
				if (local_max) {
					if (mag_value > threshold2) {
						dst.at<uchar>(j, i) = STRONG_EDGE;
						strong_edges.push_back(Point(i, j));
					} else {
						dst.at<uchar>(j, i) = WEAK_EDGE;
					}
				}
			}
		}
	}

#define CHECK_WEAK_EDGE(x, y) \
	if (dst.at<uchar>(y, x) == WEAK_EDGE) { \
		dst.at<uchar>(y, x) = STRONG_EDGE; \
		strong_edges.push_back(Point(x, y)); \
	}

	// 4. Hysterisis edge tracking
	while (!strong_edges.empty()) {
		Point p = strong_edges.back();
		strong_edges.pop_back();

		// °­ÇÑ ¿§Áö ÁÖº¯ÀÇ ŸàÇÑ ¿§ÁöŽÂ ÃÖÁŸ ¿§Áö(°­ÇÑ ¿§Áö)·Î Œ³Á€
		CHECK_WEAK_EDGE(p.x + 1, p.y)
		CHECK_WEAK_EDGE(p.x + 1, p.y + 1)
		CHECK_WEAK_EDGE(p.x, p.y + 1)
		CHECK_WEAK_EDGE(p.x - 1, p.y + 1)
		CHECK_WEAK_EDGE(p.x - 1, p.y)
		CHECK_WEAK_EDGE(p.x - 1, p.y - 1)
		CHECK_WEAK_EDGE(p.x, p.y - 1)
		CHECK_WEAK_EDGE(p.x + 1, p.y - 1)
	}

	// ³¡±îÁö ŸàÇÑ ¿§Áö·Î ³²ŸÆÀÖŽÂ ÇÈŒ¿Àº žðµÎ ¿§Áö°¡ ŸÆŽÑ °ÍÀž·Î ÆÇŽÜ.
	for (int j = 0; j < src.rows; j++) {
		for (int i = 0; i < src.cols; i++) {
			if (dst.at<uchar>(j, i) == WEAK_EDGE)
				dst.at<uchar>(j, i) = 0;
		}
	}
}
#include <iostream>
#include "opencv2/opencv.hpp"

#include "mycanny.h"

using namespace std;
using namespace cv;

int main()
{
	Mat src = imread("circuit.bmp", IMREAD_GRAYSCALE);

	if (src.empty()) {
		cerr << "Image load failed!" << endl;
		return -1;
	}

	Mat dst1, dst2;
	Canny(src, dst1, 50, 150);
	myCanny(src, dst2, 50, 150);

	imshow("src", src);
	imshow("OpenCV Canny", dst1);
	imshow("My Canny", dst2);

	waitKey();
}

0개의 댓글