본문 바로가기

3학년/OpenCV

[OpenCV] 9장 에지 검출과 응용

 

9.1 에지 검출

 

9.1.1 미분과 그래디언트

 

 

에지(edge): 한쪽 방향으로 픽셀 값이 급격하게 바뀌는 부분. 즉, 어두운 영역에서 갑자기 밝아지거나, 또는 밝은 영역에서 급격하게 어두워지는 부분을 에지라고 한다. 

 

 

 

- 2차원 영상에서 에지를 찾는 기본적인 방법은 그래디언트 크기가 특정 값보다 큰 위치를 찾는 것. 

에지 여부를 판단하기 위해 기준이 되는 값을 임계값(threshold)

 

 

 

9.1.2 마스크 기반 에지 검출

 

가장 널리 사용되고 있는 미분 마스크는 소벨 필터 마스크이다. 

 

소벨 필터 마스크

 

 

void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize =3, 
			double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT);

 

  • src: 입력 영상
  • dst: 출력 영상. src와 같은 크기, 같은 채널 수를 가진다
  • ddepth: 출력 영상의 깊이
  • dx: x 방향 미분 차수
  • dy: y 방향 미분 차수
  • ksize: 소벨 커널의 크기
  • scale: 필터링 연산 후 추가적으로 곱할 값
  • delta: 필터링 연산 후 추가적으로 더할 값
  • borderType: 가장자리 픽셀 확장 방식

 

 

 

샤르 필터 마스크는 3x3 소벨 마스크보다 정확한 미분 계산을 수행한다

 

void Scahrr(InputArray src, OutputArray dst, int ddepth, 
	int dx, int dy, double scale = 1, double delta = 0, int borderType = BORDER_DEFAULT);
  • src: 입력 영상
  • dst: 출력 영상. src와 같은 크기, 같은 채널 수를 갖는다
  • ddepth: 출력 영상의 깊이
  • dx: x 방향 미분 차수
  • dy: y 방향 미분 차수 
  • scale: 필터링 연산 후 추가적으로 곱할 값
  • delta: 필터링 연산 후 추가적으로 더할 값
  • borderType: 가장자리 픽셀 확장 방식

 

 

void magnitude(InputArray x, InputArray y, OutputArray magnitude);
  • x: 벡터의 x 좌표를 나타내는 실수 행렬 또는 벡터
  • y: 벡터의 y 좌표를 나타내는 실수 행렬 또는 벡터. x와 크기와 타입이 같아야 한다,
  • magnitude: 벡터의 크기를 나타내는 실수 행렬 또는 벡터. x와 같은 크기& 같은 타입을 갖는다.

 

void phase(InputArray x, InputArray y, OutputArray angle, bool angleInDegrees = false);
  • x: 벡터의 x 좌표를 나타내는 실수 행렬 또는 벡터
  • y: 벡터의 y 좌표를 나타내는 실수 행렬 또는 벡터. x와 크기와 타입이 같아야 한다.
  • angle: 벡터의 방향을 나타내는 실수 행렬 또는 벡터. x와 같은 크기, 같은 타입을 갖는다.
  • angleInDegrees: 이 값이 true면 각도(degree)단위를 사용하고, false이면 라디안 (radian)단위를 사용한다

 

 

void sobel_edge()
{
	Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

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

	Mat dx, dy;
	Sobel(src, dx, CV_32FC1, 1, 0);
	Sobel(src, dy, CV_32FC1, 0, 1);

	Mat fmag, mag;
	magnitude(dx, dy, fmag);
	fmag.convertTo(mag, CV_8UC1);

	Mat edge = mag > 150;

	imshow("src", src);
	imshow("mag", mag);
	imshow("edge", edge);

	waitKey();
	destroyAllWindows();
}
  • 10~12행: x축 방향으로 1차 편미분, y축 방향으로 1차 편미분을 각각 구하여 dx와 dy 행렬에 저장. dx와 dy 행렬은 float 자료형을 사용하도록 설정
  • 15행: dx와 dy 행렬로부터 그래디언트 크기를 계산하여 fmag에 저장 dx와 dy가 모두 float 자료형을 사용하므로 fmag도 float 자료형을 사용하는 행렬로 생성
  • 16행: 실수형 행렬 fmag를 그레이스케일 형식으로 변환하여 mag에 저장
  • 18행: 에지 판별을 위한 그래이언트 크기 임계값을 150으로 설정하여 에지를 판별. 행렬 edge 의 원수 값은 mag 행렬 원소 값이 150보다 크면 255로, 작으면 0으로 설정

 

 

 

 

9.1.3 캐니 에지 검출기

 

 

좋은 에지 검출기의 조건 3가지

 

1. 정확한 검출: 에지를 검출하지 못하거나 또는 에지가 아닌데 에지로 검출하는 확률을 최소화해야한다.

2. 정확한 위치: 실제 에지의 중심을 찾아야 한다.

3. 단일 에지: 하나의 에지는 하나의 점으로 표현되어야 한다.

 

 

 

 

 

가우시안 필터링

7.2.2 절에 나옴.  캐니 에지 검출기의 첫 단계에 가우시안 필터링을 적용하는 이유는 영상에 포함된 잡음을 제거하기 위함. 영상에 포함된 잡음이 심하지 않다면 가우시안 필터링은 생략할 수 있다.

 

그래디언트 계산

보통 3x3 소벨 마스크를 사용. 가로 방향과 세로 방향으로 각각 소벨 마스크 필터링을 수행한 후, 그래디언트 크기와 방향을 모두 계산해야함. 

 

비최대 억제

에지가 두껍게 표현되는 현상을 방지하기 위해 캐니 에지 검출기에서는 비최대 억제 과정을 사용. 이는 그래디언트 크기가 국지적 최대인 픽셀만을 에지 픽셀로 설정하는 기법임. 

 

이중 임계값을 이용한 히스테리시스 에지 트래킹

하나의 임계값을 사용할 경우 이분법으로 결과가 판단되기 때문에 환경 변화에 민감해질 수 있음. 이러한 문제를 보완하기 위해 캐니 에지 검출기는 두개의 임계값을 사용한다. 

 

 

 

void Canny(InputArray image, OutputArray edges, 
		double threshold1, double threhold2, int apertureSize = 3, bool L2gradient = false);
void Canny(InputArray dx, InputArray dy, OutputArray edges, double threshold1, 
		double threshold2, bool L2gradient = false);
  • dx: 입력 영상의 x 방향 미분 영상. CV_16SC1 또는 CV_16SC3 
  • dy: 입력 영상의 y 방향 미분 영상.  CV_16SC1 또는 CV_16SC3 
  • edges: 출력 에지 영상. 입력 영상과 크기가 같은 8비트 단일 채널 영상
  • threshold1: 히스테리시스 에지 검출을 위한 첫번째 임계값
  • threshold2: 히스테리시스 에지 검출을 위한 두번째 임계값
  • apertureSize: 그래디언트 계산을 위한 소벨 마스크 크기
  • L2gradient : 그래디언트 크기 계산 시 L2 노름을 사용하려면 true를 지정. 이 값이 false 이면 L1 노름을 사용

 

 

 

void canny_edge()
{
	Mat src = imread("lenna.bmp", IMREAD_GRAYSCALE);

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

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

	imshow("src", src);
	imshow("dst1", dst1);
	imshow("dst2", dst2);

	waitKey();
	destroyAllWindows();
}

 

  • 11행: 낮은 임계값을 50, 높은 임계값을 100으로 설정하여 캐니 에지 검출을 수행하고 그 결과를 dst1에 저장
  • 12행: 낮은 임계값을 50, 높은 임계값을 150으로 설정하여 캐니 에지 검출을 수행하고 그 결과를 dst2에 저장

 

 

 

9.2 직선 검출과 원 검출

 

9.2.1 허프 변환 직선 검출

 

 

허프 변환 기법: 2차원 xy 좌표에서 직선의 방정식을 파라미터 공간으로 변환하여 직선을 찾는 알고리즘. 영상에서 직선을 찾기 위한 용도로 널리 사용됨.

 

 

void HoughLines(InputArray image, OutputArray lines, double rho, double theta, 
	int threshold, double srn = 0, double stn = 0, double min_theta = 0, double max_theta = CV_PI);
  • image: 8비트 단일 채널 입력 영상. 주로 에지 영상을 지정
  • lines: 직선 정보 (rho, theta)를 저장할 출력 벡터
  • rho: 축적 배열에서 p 값의 해상도 (픽셀 단위)
    theta: 축적 배열에서 값의 해당소 (라디안 단위)
  • threshold: 축적 배열에서 직선으로 판단할 임계값
  • srn: 멀티스케일 허프 변환에서 rho 해상도를 나누는 값. srn에 양의 실수를 지정하면 rho 해상도와 rho/srn 해상도를 각각 이용하여 멀티스케일 허프 변환을 수행. srn과 stn이 모두 0이면 일반 허프 변환을 수행
  • stn: 멀티스케일 허프 변환에서 theta 해상도를 나누는 값
  • min_theta: 검출할 직선의 최소 theta 값
  • max_theta: 검출할 직선의 최대 theta 값

 

void hough_lines()
{
	Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

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

	Mat edge;
	Canny(src, edge, 50, 150);

	vector<Vec2f> lines;
	HoughLines(edge, lines, 1, CV_PI / 180, 250);

	Mat dst;
	cvtColor(edge, dst, COLOR_GRAY2BGR);

	for (size_t i = 0; i < lines.size(); i++) {
		float rho = lines[i][0], theta = lines[i][1];
		float cos_t = cos(theta), sin_t = sin(theta);
		float x0 = rho * cos_t, y0 = rho * sin_t;
		float alpha = 1000;

		Point pt1(cvRound(x0 - alpha * sin_t), cvRound(y0 + alpha * cos_t));
		Point pt2(cvRound(x0 + alpha * sin_t), cvRound(y0 - alpha * cos_t));
		line(dst, pt1, pt2, Scalar(0, 0, 255), 2, LINE_AA);
	}

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);
	destroyAllWindows();
}
  • 3행: building.jpg 영상을 그레이스케일 형식으로 불러와 src에 저장
  • 10~11행: 캐니 에지 검출기를 이용하여 구한 에지 영상을 edge에 저장
  • 13~14행: HoughLines() 함수를 이용하여 직선의 파라미터 p와  정보를 lines에 저장. 축적 배열에서 p 간격은 1 픽셀 단위로, 단위는 1도 단위로 처리
  • 17행: 그레이스케일 에지 영상 edge를 BGR 3챈러 컬러 영상으로 변환하여 dst에 저장
  • 19행: HoughLines() 함수에 의해 구해진 직선의 개수 만큼 for 반복문을 수행
  • 20~26행: 직선의 방정식 파라미터 중에서 p를 변수 r에, 를 변수 t에 저장한다. x0와 y0는 원점에서 직선에 수선을 내렸을  때 만나는 점의 좌표. pt1과 pt2 에는 (x0, y0)에서 충분히 멀리 떨어져있는 직선 상의 두 점 좌표가 저장됨
  • 27행: 검출된 직선을 두계가 2인 빨간색 실선으로 그린다

 

 

 

void HoughLinesP(InputArray image, OutputArray lines, douebl rho,
	 double theta, int threshold, double minLineLength = 0, double maxLineGap = 0);
  • image: 8비트 단일 채널 입력 영상. 주로 에지 영상을 지정 
  • lines: 선분의 시작점과 끝점의 정보 (x1, y1, x2 ,y2)를 저장할 출력 벡터 
  • rho: c축적 배열에서 p값의 해상도(픽셀 단위)
  • theta: 축적 배열에서 값의 해상도(라디안 단위)
  • threshold: 축적 배열에서 직선으로 판단할 임계값
  • minLineLength: 검출할 선분의 최소 길이
  • maxLineGap: 직선으로 간주할 최대 에지 점 간격

 

 

void hough_line_segments()
{
	Mat src = imread("building.jpg", IMREAD_GRAYSCALE);

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

	Mat edge;
	Canny(src, edge, 50, 150);

	vector<Vec4i> lines;
	HoughLinesP(edge, lines, 1, CV_PI / 180, 160, 50, 5);

	Mat dst;
	cvtColor(edge, dst, COLOR_GRAY2BGR);

	for (Vec4i l : lines) {
		line(dst, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(0, 0, 255), 2, LINE_AA);
	}

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);
	destroyAllWindows();
}
  • 3행: building.jpg 영상을 그레이스케일 형식으로 불러와 src에 저장
  • 11행: 캐니 에지 검출기를 이용하여 구한 에지 영상을 edge에 저장
  • 13~14행: HoughLines() 함수를 잉요하여 모든 직선 성분의 시작점과 끝점 좌표를 구한다
  • 17행: 그레이스케일 에지 영상 edge를 BGR 3채널 컬러 영상으로 변환하여 dst에 저장
  • 19행: HoughLinesP()함수에 의해 구해진 모든 직선 성분을 dst 영상 위에 빨간색 직선으로 그림

 

 

 

 

9.2.2 허프 변환 원 검출

 

 

void HoughCircles(InputArray image, OutputArray circles, int method, doubel dp, 
	double minDist, double param1 = 100, double param2 = 100, int minRadius = 0, int maxRadius = 0);
  • image: 입력 영상. 에지 영상이 아닌 원본 그레이스캐일 영상을 지정
  • circles: 검출된 원 정보를 저장할 출력 벡터
  • method: HOUGH_GRADIENT만 지정 가능
  • dp: 입력 영상과 축적 배열의 크기 비율
  • minDist: 인접한 원 중심의 최소 거리
  • param1: Canny 에지 검출기의 높은 임계값
  • param2: 축적 배열에서 원 검출을 위한 임계값
  • minRadius: 검출할 원의 최소 반지름
  • maxRadius: 검출할 원의 최대 반지름 

 

void hough_circles()
{
	Mat src = imread("coins.png", IMREAD_GRAYSCALE);

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

	Mat blurred;
	blur(src, blurred, Size(3, 3));

	vector<Vec3f> circles;
	HoughCircles(blurred, circles, HOUGH_GRADIENT, 1, 50, 150, 30);

	Mat dst;
	cvtColor(src, dst, COLOR_GRAY2BGR);

	for (Vec3f c : circles) {
		Point center(cvRound(c[0]), cvRound(c[1]));
		int radius = cvRound(c[2]);
		circle(dst, center, radius, Scalar(0, 0, 255), 2, LINE_AA);
	}

	imshow("src", src);
	imshow("dst", dst);

	waitKey(0);
	destroyAllWindows();
}
  • 3행: coin.png 동전 영상을 불러와 src에 저장
  • 11행: 입력 영상 src의 잡음을 제거하는 용도로 blur() 함수를 적용
  • 14행: HoughCircle() 함수를 이용하여 원을 검출. 축적 배열 크기는 입력 영상과 같은 크기로 사용하고, 두 원의 중심점 거리가 50픽셀보다 작으면 검출하지 않음. 캐니 에지 검출기의 높은 임계값은 150으로 지정하고, 축적 배열 원소 값이 30보다 크면 원의 중심점으로 선택. 검출된 원의 중심 좌표와 반지금 정보는 circles 변수에 저장
  • 16~17행: 입력 영상 src를 3채널 컬러 영상으로 변환하여 dst에 저장
  • 19~23행: dst 영상 위에 검출된 원을 빨간색으로 그림