본문 바로가기

3학년/OpenCV

[OpenCV] 13장 객체 검출

 

 

13.1 템플릿 매칭

 

템플릿 매칭(template matching): 입력 영상에서 작은 크기의 부분 영상 위치를 찾아내고 싶은 경우에 사용

템플릿: 찾고자 하는 대상이 되는 작은 크기의 영상

 

 

void matchTemplate(InputArray image, InputArray templ, OutputArray result, 
	int method, InputArray mask = noArray());
  • image: 입력 영상. 8비트 또는 32비트 실수형
  • templ: 템플릿 영상. 입력 영상 image보다 같거나 작아야 하며, image와 타입이 같아야 한다
  • result: (출력) 비교 결과를 저장할 행렬. CV_32FC1 타입
  • method: 템플릿 매칭 비교 방법. TemplateMatchModes 열거형 상수 중 하나를 지정
  • mask: 찾고자하는 템플릿의 마스크 영상. mask는 templ과 같은 크기, 같은 타입이어야 함. TM_SQDIFF와 TM_CCORR_NORMED 방법에서만 지원

 

 

void template_matching()
{
	Mat img = imread("circuit.bmp", IMREAD_COLOR);
	Mat templ = imread("crystal.bmp", IMREAD_COLOR);

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

	img = img + Scalar(50, 50, 50);

	Mat noise(img.size(), CV_32SC3);
	randn(noise, 0, 10);
	add(img, noise, img, Mat(), CV_8UC3);

	Mat res, res_norm;
	matchTemplate(img, templ, res, TM_CCOEFF_NORMED);
	normalize(res, res_norm, 0, 255, NORM_MINMAX, CV_8U);

	double maxv;
	Point maxloc;
	minMaxLoc(res, 0, &maxv, 0, &maxloc);
	cout << "maxv: " << maxv << endl;

	rectangle(img, Rect(maxloc.x, maxloc.y, templ.cols, templ.rows), Scalar(0, 0, 255), 2);

	imshow("templ", templ);
	imshow("res_norm", res_norm);
	imshow("img", img);

	waitKey(0);
	destroyAllWindows();
}
  • 3행: circuit.bmp 파일을 입력 영상 img로 사용
  • 4행: crystal.bmp 파일을 템플릿 영상 templ로 사용
  • 11행: 입력 영상 밝기를 50만큼 증가
  • 13~15행: 입력 영상에 표준 편차가 10인 가우시안 잡음을 추가
  • 18행: 정규화된 상관계수 매칭 방법을 사용하여 템플릿 매칭을 수행
  • 19행: 템플릿 매칭 결과 행렬 res의 모든 원소 값을 0~255 사이로 정규화하고, 타입을 CV_8UC1로 변환하여 res_norm 영상에 저장
  • 21~23행: res 행렬에서 최댓값 위치를 찾아 maxloc에 저장. 이 위치에서의 최댓값 maxv는 템플릿 매칭이 잘 되었는지를 가늠하는 척도로 사용
  • 24행: res 행렬의 최댓값을 콘솔 창에 출력
  • 26행: img 영상에 템플릿 매칭으로 찾은 위치를 빨간색 사각형으로 표시

 

 

 

 

 

13.2  캐스케이드 분류기와 얼굴 검출

 

비올라와 존스가 개발한 객체 검출 알고리즘: 특히 얼굴 검출에 적용되어 속도와 정확도를 인정받음.

 

 

비올라-존스 얼굴 검출 알고리즘: 영상을 24x24 크기로 정규화한 후, 유사-하르필터 집합으로부터 특정 정보를 추출하여 얼굴 여부를 판별

 

유사-하르 필터: 흑백 사각형이 서로 붙어 있는 형태로 구성된 필터. 유사-하르 필터 형태에서 흰색 영역 픽셀 값은 모두 더하고, 검은색 영역 픽셀 값은 모두 빼서 하나의 특징 값을 얻을 수 있다. 사람의 정면 얼굴 형태가 전형적으로 밝은 영역(이마, 미간, 볼 등)과 어두운 영역(눈썹, 입술 등)이 정해져있기 때문에 유사-하르 필터로 구한 특징 값은 얼굴을 판별하는 용도로 사용할 수 있음.

 

 

 

class CascadeClassifier
{
public:
	CascadeClassifier();
    CascadeClassifier(const String& filename);
    ~CascadeClassifier();
    
    bool load(const String& filename);
    bool empty() const;
    
    void detectMultiScale(InputArray image,
    					  std::vector<Rect>& objects,
                          double scaleFactor = 1.1,
                          int minNeighbors = 3, int flags = 0,
                          Size minSize = Size();
                          Size maxSize = Size() );
                          
};
  • 4~6행: CascadeClassifier 클래스의 생성자와 소멸자
  • 8행: CascadeClassifier::load() 멤버 함수는 분류기 XML 파일을 불러옴
  • 9행: CascadeClassifier::empty() 멤버 함수는 분류기가 정상적으로 불려왔는지를 검사
  • 11~16행: CascadeClassifier::detectMultiScale() 함수는 영상에서 객체를 검출

 

void CascadeClassifier::load(const String& filename);
  • filename: 불러올 분류기 XML 파일 이름

 

 

 

bool CascadeClassifier::empty()const
  • 반환값: 분류기 파일을 정상적으로 불러왔으면 false, 그렇지 않으면 true를 반환

 

 

 

void CascadeClassifier::detectMultiScale(InputArray image, vector<Rect>& objects, 
	double scaleFactor = 1.1, int minNeighbors = 3, int flags = 0, Size minSize = Size(), 
    Size maxSize = Size());
  • image: 입력 영상. CV_8U 깊이의 행렬
  • objects: (출력) 검출된 객체의 사각형 좌표 정보
  • scaleFactor: 검색 윈도우 확대 비율. 1보다 커야함
  • minNeighbors: 검출 영역으로 선택하기 위한 최소 검출 횟수
  • flags; 현재 사용되지 않음
  • minSize: 검출할 객체의 최소 크기
  • maxSize: 검출할 객체의 최대 크기

 

 

void detect_face()
{
	Mat src = imread("kids.png");

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

	CascadeClassifier classifier("haarcascade_frontalface_default.xml");

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

	vector<Rect> faces;
	classifier.detectMultiScale(src, faces);

	for (Rect rc : faces) {
		rectangle(src, rc, Scalar(255, 0, 255), 2);
	}

	imshow("src", src);

	waitKey(0);
	destroyAllWindows();
}
  • 10행: CascadeClassifier 객체를 생성함과 동시에 haarcascade_frontalface_default.xml 파일을 불러옴
  • 12~15행: 분류기를 정상적으로 불러왔는지를 확인. 분류기를 정상적으로 불러오지 못했으면 에러 메시지를 출력하고 함수를 종료
  • 17~18행: src 영상에서 얼굴을 검출하여 검출된 사각형 정보를 faces에 저장
  • 20~22행: 검출된 얼굴 영역 사각형을 src 영상에 보라색으로 그림

 

 

 

void detect_eyes()
{
	Mat src = imread("kids.png");

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

	CascadeClassifier face_classifier("haarcascade_frontalface_default.xml");
	CascadeClassifier eye_classifier("haarcascade_eye.xml");

	if (face_classifier.empty() || eye_classifier.empty()) {
		cerr << "XML load failed!" << endl;
		return;
	}

	vector<Rect> faces;
	face_classifier.detectMultiScale(src, faces);

	for (Rect face : faces) {
		rectangle(src, face, Scalar(255, 0, 255), 2);

		Mat faceROI = src(face);
		vector<Rect> eyes;
		eye_classifier.detectMultiScale(faceROI, eyes);

		for (Rect eye : eyes) {
			Point center(eye.x + eye.width / 2, eye.y + eye.height / 2);
			circle(faceROI, center, eye.width / 2, Scalar(255, 0, 0), 2, LINE_AA);
		}
	}

	imshow("src", src);

	waitKey(0);
	destroyAllWindows();
}
  • 11행: 눈 검출을 위해 haarcascade_eye.xml 파일을 사용하는 CascadeClassifier 객체를 생성
  • 24행: 입력 영상에서 검출한 사각형 얼굴 영역의 부분 영상을 추출하여 faceROI에 저장
  • 25~26행: faceROI 영상에서 눈을 검출
  • 28~31행: 검출한 눈의 중앙에 파란색 원을 그림.faceROI 영상은 src 영상의 부분 영상을 참조하므로 faceROI에 원을 그리면 src 영상에도 원이 그려짐

 

 

 

 

13.3 HOG 알고리즘과 보행자 검출

 

 

HOG: 그래디언트 방향 히스토그램.

 

 

 

static std::vector<float> HOGDescriptor::getDefaultPeopleDetector();
  • 반환값: 보행자 검출을 위해 훈련된 분류기 계수

 

virtual void HOGDescripot::setSVMDetector(InputArray svmdetector);
  • svmdetector: 선형 SVM 분류기를 위한 계수

 

virtual void HOGDescriptor::detectMultiScale(InputArray img, std::vector<Rect>& foundLocations, 
	std::vector<double>& foundWeights, double hitThreshold = 0, Size winStride = Size(), 
    	Size padding = Size(), double scale = 1.05, double finalThreshold = 2.0, 
        bool useMeanshifhtGrouping = false) const;

virtual void HOGDescriptor::detectMultiScale(InputArray img, std::vector<Rect>& foundLoactions, 
	double hitThreshold = 0, Size winStride = Size(), Size padding() = Size(),
    	double scale = 1.05, double finalThreshold = 2.0, bool useMeanshiftGrouping = false) const;
  • img: 입력 영상. CV_8UC1 또는 CV_8UC3
  • foundLocations: (출력) 검출된 사각형 영역 정보
  • foundWeighs: (출력) 검출된 사각형 영역에 대한 신뢰도
  • hitThreshold: 특징 벡터와 SVM 분류 평면까지의 거리에 대한 임계값
  • winStride: 셀 윈도우 이동 크기. Size() 지정 시 셀 크기와 같게 설정
  • padding: 패딩 크기
  • scale: 검색 윈도우 크기 확대 비율
  • finalThreshold: 검출 결정을 위한 임계값
  • useMeanshiftGrouping: 겹쳐진 검색 윈도우를 합치는 방법 지정 플래그

 

 

 

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

using namespace cv;
using namespace std;

int main()
{
	VideoCapture cap("vtest.avi");

	if (!cap.isOpened()) {
		cerr << "Video open failed!" << endl;
		return -1;
	}

	HOGDescriptor hog;
	hog.setSVMDetector(HOGDescriptor::getDefaultPeopleDetector());

	Mat frame;
	while (true) {
		cap >> frame;
		if (frame.empty()) 
			break;

		vector<Rect> detected;
		hog.detectMultiScale(frame, detected);

		for (Rect r : detected) {
			Scalar c = Scalar(rand() % 256, rand() % 256, rand() % 256);
			rectangle(frame, r, c, 3);
		}

		imshow("frame", frame);

		if (waitKey(10) == 27)
			break;
	}

	return 0;
}
  • 9행: 현재 폴더에서 vtest.avi 파일을 불러옴.
  • 16행: HOGDescriptor 객체 hog를 선언
  • 17행: 보행자 검출을 위한 용도로 훈련된 SVM 분류기 계수를 등록
  • 25~26행: 동영상 매 프레임마다 보행자 검출을 수행. 검출된 사각형 정보는 detected 변수에 저장
  • 28~31행: 검출된 사각형 정보를 이용하여 임의의 색상으로 3픽셀 두께의 사각형을 그림

 

 

 

 

 

 

13.4 QR 코드 검출

 

QR 코드: 흑백 격자 무늬 모양의 2차원 바코드 일종으로 숫자, 영문자, 8비트 문자, 한자 등의 정보를 저장할 수 있음

 

QR 코드 인식을 위해서는 먼저 QR 코드 세 모서리에 포함된 흑백 정사각형 패턴을 찾아 QR 코드 전체 영역 위치를 알아내야 함. 그 다음 검출된 QR 코드를 정사각형 형태로 투시 변환한 후, QR 코드 내부에 포함된 흑백 격자 무늬를 해석하여 문자열을 추출해야한다.

 

 

bool QRCodeDetector::detect(InputArray img, OutputArray points) const;
  • img: 입력 영상. CV_8U 또는 CV_8UC3
  • points: (출력)QR 코드를 감싸는 사각형의 네 꼭지점 좌표
  • 반환값: QR 코드를 검출하면 true, 검출하지 못하면 false를 반환

 

std::string QRCodeDetector::decode(InputArray img, InputArray points,
	 OutputArray straight_qrcode = noArray());
  • img: 입력 영상
  • points: (출력)QR 코드를 감싸는 사각형의 네 꼭지점 좌표
  • straight_qrcode: (출력) 정사각형 QR 코드 영상. CV_8UC1
  • 반환값: QR 코드에 포함된 문자열

 

 

std::string QRCodeDetector::detectAndDecode(InputArray img, OutputArray points = noArray(), 
	OutputArray straight_qrcode = noArray());
  • img: 입력 영상. CV_8U
  • points: (출력)QR 코드를 감싸는 사각형의 네 꼭지점 좌표
  • straight_qrcode: (출력) 정사각형 QR 코드 영상. CV_8UC1
  • 반환값: QR 코드에 포함된 문자열

 

 

 

 

 

 

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

using namespace cv;
using namespace std;

void decode_qrcode();

int main(void)
{
	decode_qrcode();

	return 0;
}

void decode_qrcode()
{
	VideoCapture cap(0);

	if (!cap.isOpened()) {
		cerr << "Camera open failed!" << endl;
		return;
	}

	QRCodeDetector detector;

	Mat frame;
	while (true) {
		cap >> frame;

		if (frame.empty()) {
			cerr << "Frame load failed!" << endl;
			break;
		}

		vector<Point> points;
		String info = detector.detectAndDecode(frame, points);

		if (!info.empty()) {
			polylines(frame, points, true, Scalar(0, 0, 255), 2);
			putText(frame, info, Point(10, 30), FONT_HERSHEY_DUPLEX, 1, Scalar(0, 0, 255));
		}

		imshow("frame", frame);
		if (waitKey(1) == 27)
			break;
	}
}

 

  • 3행: 컴퓨터에 연결된 기본 카메라를 이용하여 VideoCapture 객체 cap을 생성
  • 10행: QRCodeDetector 객체 detector 변수를 선언
  • 21~22행:  카메라 매 프레임 마다 QR 코드 검출 및 해석을 수행
  • 24~27행: 만약 QR 코드를 검출하고 QR 코드 문자열이 제대로 info 변수에 저장되었다면 QR 코드에 빨간색 사각형을 그리고, 해석된 문자열을 화면 좌측 상단에 빨간색 글자로 출력
  • 31~32행: esc 키를 누르면 while 반복문을 빠져나오고 프로그램이 종료됨