오늘은 영상 속의 특징점(Keypoint)를 검출할 수 있는 Harris Corner에 대해 알아보겠습니다.

 

1998년도에 발표된 Harris Corner는 1980년도에 발표된 Moravec's Corner의 방법을 보안한 알고리즘으로서,

영상에서 코너의 특징점을 찾는 것입니다. 간략하게 말하자면 아래 그림과 같이 작은 영역을 이동시키며,

이동된 영역과 비교하여 얼마나 변화했는지 찾는 것입니다.

 

 

출처 : https://darkpgmr.tistory.com/131

 

아래 그림과 같이 이동되기 전과 이동된 영역의 변화의 차이인 R이 0보다 작으면 엣지(Edge), 둘다 0에 가까우면

플렛(Flat), 0보다 크면 코너점이 됩니다.

 

출처 : https://darkpgmr.tistory.com/131

 

 * 위 설명은 수학적 과정에 대한 언급 없이 표면적으로 간단하게 설명한 것입니다. 수학적인 전체의 과정은 추후에
충분히 이해한 뒤 내용을 추가해 작성하도록 하겠습니다.

 

 

 

아래 코드는 Opencv에서 제공하는 라이브러리 속 함수인 cornerHarris()를 사용한 예시입니다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

// Harris Corner thresh
int thresh = 120;

int main()
{
	Mat img = imread("D:\\sea.jpg");
	Mat gray;

	// Image resize
	resize(img, img, Size(500, 500), 0, 0, INTER_LANCZOS4);

	Mat img_copy = img.clone(); // 원본 이미지 복사
	Mat show_img = img.clone(); // 원본 이미지 복사

	// Grayscale
	cvtColor(img, gray, CV_BGR2GRAY);

	Mat harris_c;
	Mat harris_norm;
	Mat cs_abs;

	harris_norm = Mat::zeros(gray.size(), CV_32FC1);
	
	// Harris Corner
	// cornerHarris(입력 이미지(Grayscale), 출력 이미지, 인접 픽셀 크기(Blocksize), Sobel ksize, Harris parameter, 픽셀 보간법)
	cornerHarris(gray, harris_c, 2, 3, 0.05, BORDER_DEFAULT);

	// 정규화(Normalizing)
	// normalize(입력 이미지, 출력 이미지, normalize range(low), normalize range(high), 픽셀 보간법) 
	normalize(harris_c, harris_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat());

	// 미분한 결과에 절대값을 적용하여 8bit의 unsigned int형으로 바꾸어 표현
	convertScaleAbs(harris_norm, cs_abs);

	// Drawing a circle around corners
	for (int j = 0; j < harris_norm.rows; j += 1)
		for (int i = 0; i < harris_norm.cols; i += 1)
			if ((int)harris_norm.at<float>(j, i) > thresh)
				circle(img_copy, Point(i, j), 7, Scalar(255, 0, 255), 0, 4, 0);

	imshow("Original Image", show_img);
	imshow("convertScaleAbs", cs_abs);
	imshow("Harris Corner Detection", img_copy);

	waitKey(0);
}

Original Image

 

cornerHarris() to convertScaleAbs()

 

Harris Corner Detection (코너점을 마젠타색 원으로 표현)

 

Harris Corner Detection은 영상의 평행이동, 회전변화에는 불변이고, affine 변화, 조명 변화에도 어느정도 강인한

특성을 가지고 있지만 영상의 크기 변화에는 민감함을 보입니다.

 

 

 

 

< 참고 및 인용 자료 >

 

1) https://darkpgmr.tistory.com/131

 

영상 특징점(keypoint) 추출방법

영상 특징점이 무엇이고 어떻게 뽑는지, 그리고 대표적인 방법에는 어떤 것들이 있는지 정리해 봅니다. 1. 영상 특징점이란? 2. Harris corner [1988] 3. Shi & Tomasi [1994] 4. SIFT - DoG [2004] 5. FAST [2006..

darkpgmr.tistory.com

2) http://egloos.zum.com/eyes33/v/6097465

 

Corner 검출 #1- Harris corner detector

이미지에서 특정 물체를 인식하거나, 추적할때 물체를 특징짖을 수 있는 주요 특징점을 매칭 시키면 쉽게 인식하거나, 추적할 수 있다. Harris Corner는 1980년 Moravec 의 아이디어를 수정 보완한것이다. Moravec은 작은 윈도우를 수직, 수평, 좌대각선, 우대각선 4개 방향으로 1 픽셀씩 이동시켰을 때의 변화하는 intensity 의

egloos.zum.com

3) https://leechamin.tistory.com/293

 

[OpenCV] 05-2. Harris Corner Detection

< Harris Corner Detection > " style="clear: both; font-size: 2.2em; margin: 0px 0px 1em; color: rgb(34, 34, 34); font-family: "Roboto Condensed", Tauri, "Hiragino Sans GB", "Microsoft YaHei",..

leechamin.tistory.com

 

 

어떠한 프로젝트를 진행하면서 픽셀의 값을 이용한 알고리즘으로 접근할 때, 이미지 전체가 아닌 특정 영역의 픽셀 값의

정보를 알고 싶은 경우가 있었습니다. 하지만 제가 아는 것은 이미지 전체의 픽셀에 접근하여 정보를 추출하거나 캡쳐

 

도구를 통해 수작업으로 이미지를 잘라서 처리하는 방법 뿐이여서 불편함을 많이 느꼈습니다.

 

오늘은 제가 느꼈던 불편함을 해소할 수 있는 방법으로 마우스 이벤트인 setMouseCallback 함수를 이용하여,

 

이미지 속에서 직접 지정한 특정 영역의 이미지와 픽셀 값의 정보를 추출하는 방법에 대해 소개하려합니다.

 

 

 

마우스 이벤트는 사용자가 함수를 설계하여 처리할 수 있습니다.

 

여기서 중요한 부분은 인터럽트처럼 사용자가 설계한 함수에서의 마우스 이벤트 발생했을 때

 

바로 처리가 가능하도록 콜백 함수(Event Handler)를 만들어 함수를 setMouseCallback()에 등록하는 겁니다.

 

 

setMouseCallback 함수에 대해 알아보겠습니다.

setMouseCallback 함수 설명

 

 

코드를 통해 살펴보도록 하겠습니다.

 

아래 코드는 입력된 이미지 창에서 마우스 이벤트인 setMouseCallback 함수를 이용하여 마우스를 통해 박스를 그리고

그려진 박스 영역의 이미지를 추출해 Grayscale하여 저장한다.

(마우스는 왼쪽 버튼을 누르면 박스가 그려지는 중이 되며 뗐을 때 완전한 박스가 그려진다.

 

또한, Grayscale이 적용된 이미지에서 픽셀 정보를 읽어 생성된 텍스트에 입력시켜 저장한다. 추가적인 옵션으로 이미지

창에서 키보드의 SPACE를 누를 때 그려진 박스들이 사라지고 EXC를 누르면 프로그램에 종료된다.

 

* 본 코드는 Opencv 3.1.0 Version에서 사용하였습니다.

#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

void onMouse(int x, int y, int flags, int, void *param);      // 마우스 콜백 함수
void img_save(Mat img); // 이미지, 텍스트 저장

int x_1, x_2, y_1, y_2; // 그린 박스의 네 꼭짓점 좌표
int draw_start; 
int draw_save;
int index = 1;

int main()
{
	Mat image = imread("D:\\test.jpg");
	Mat image_copy;
	Mat crop_img;
	Mat img_backup;

	int pre_num = 0;
	int r, g, b; //RGB 색상

	resize(image, image, Size(500, 500), 0, 0, CV_INTER_LANCZOS4);
	img_backup = image.clone();

	while (1){
		// 원복 복사(박스가 자연스럽게 그려지는 것을 보여주기 위함)
		image_copy = image.clone();

		//박스 색상(그리는 중)
		r = 0;
		b = 255;
		g = 0;

		// 박스 좌측 상단에서 우측 하단으로 그리기
		if (x_1 < 0)
			x_1 = 0;
		if (x_2 > image_copy.cols)
			x_2 = image_copy.cols;
		if (y_1 < 0)
			y_1 = 0;
		if (y_2 > image_copy.rows)
			y_2 = image_copy.rows;

		// 박스 그리기
		if (draw_start == 1 && draw_save == 0)
			rectangle(image_copy, Point(x_1, y_1), Point(x_2, y_2), Scalar(b, g, r));
		else if(draw_start == 1 && draw_save == 1){	

				// 박스를 자연스럽게 그리기 위함
				if (x_1 > x_2) {
					pre_num = x_2;
					x_2 = x_1;
					x_1 = pre_num;
				}
				if (y_1 > y_2) {
					pre_num = y_2;
					y_2 = y_1;
					y_1 = pre_num;
				}

				// 이미지 저장 함수
				img_save(img_backup);

				// 초기화
				draw_start = 0;
				draw_save = 0;

				index++;

				// 박스 색상(그려진)
				r = 255;
				g = 0;
				b = 0;

				rectangle(image, Point(x_1, y_1), Point(x_2, y_2), Scalar(b, g, r));

				// 이미지 추출
				crop_img = img_backup(Range(y_1, y_2), Range(x_1, x_2));
				cvtColor(crop_img, crop_img, CV_RGB2GRAY);

				imshow("선택 영역", crop_img);
		}
		
		// 창에 텍스트 세기기
		putText(image, "Space : Delete all box", Point(10, 15), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 2);
		putText(image, "  ESC : End of Program", Point(10, 35), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 2);
		 

		imshow("Original", image_copy);
		setMouseCallback("Original", onMouse, 0);

		if (waitKey(10) == 32)
			image = img_backup.clone(); // 스페이스를 누를 시 그려진 박스 없앰

		if (waitKey(5) == 27) // ESC를 누를 시 종료
			exit(0);
	}
	//waitKey(0);
}

void img_save(Mat img)
{
	FILE *pixel;

	Mat save;

	char buf[256]; // 이미지 저장 경로 버퍼
	char buf2[256]; // 이미지 저장 경로 버퍼
	int pixel_info = 0;

	save = img(Range(y_1, y_2), Range(x_1, x_2));
	cvtColor(save, save, CV_RGB2GRAY);
	sprintf(buf, "D:\\%d.jpg", index);
	imwrite(buf, save);

	//픽셀 값 텍스트에 저장
	sprintf(buf2, "D:\\%d.txt", index);
	pixel = fopen(buf2, "w");

	fprintf(pixel, "이미지 사이즈(가로,세로)\n\n", save.cols, save.rows);
	fprintf(pixel, "%dx%d\n\n", save.cols, save.rows);

	for (int y = 0; y < save.rows; y++){
		for (int x = 0; x < save.cols; x++){
			pixel_info = save.at<uchar>(y, x);
			fprintf(pixel, "%03d ", pixel_info);
		}
		fprintf(pixel, "\n");
	}
	fclose(pixel);
}
void onMouse(int event, int x, int y, int flags, void * param)
{
	switch (event) //switch문으로 event에 따라 버튼 종류를 구분
	{
	case EVENT_MOUSEMOVE: // 마우스 이동
		x_2 = x;
		y_2 = y;
		break;

	case EVENT_LBUTTONDOWN:   // 마우스 왼쪽 버튼 누를 때
		x_1 = x;
		y_1 = y;
		draw_start = 1;
		break;

	case EVENT_LBUTTONUP:    // 마우스 왼쪽 버튼 뗄 때
		draw_save = 1;
		break;
	}
}

원본 이미지
영역을 지정하는 중 (파란색 박스)
지정된 영역(빨간색)
추출된 지정 이미지와 픽셀 정보(GrayScale이 적용됨)

 

 

 

추가적으로 본 코드의 onMouse 함수를 보게 되면 여러 인자가 들어가게 된다.

 

인자 값은 setMouseCallback를 통해서 정보를 가져온 것들이며, 자세한 내용은 아래와 같다.

 

 

 

마우스 이벤트 옵션는 아래와 같으니 필요 상황에 맞춰 사용하시면 된다.

 

 

 

위 표의 정보는 아래의 참고 문헌을 인용하여 작성하였습니다.

 

 

< 참고 문헌 >

 

정성환,배종욱 지음. 2017년. OpenCV로 배우는 영상 처리 및 응용. 생능출판사

 

 

+ Recent posts