해당 포스트는 "OpenCV로 배우는 영상 처리 및 응용", "C++ API OpenCV 프로그래밍" 책의 내용을 요약한 것이다.



- double kmeans(InputArray data, int K, InputOutputArray bestLabels, TermCriteria criteria, int attempts, int flags, OuputArray centers=noArray())

: data에서 k-평균 군집화를 수행한 후 결과를 bestLabels에 저장한다. data 행렬의 크기는 1행이어야 한다. k는 군집 개수이고 bestLabels에는 각 요소의 군집 번호를 저장한다. criteria는 종료 조건으로 최대 반복횟수(COUNT)와 각 군집의 중심이 허용오차(epsilon) 이내로 움직이면, 즉 더이 상의 이동이 없으면 종료한다. attempts는 알고리즘을 시도하는 횟수로, 서로 다른 시도 횟수 중에서 최적의 결과를 bestLabels에 저장한다. centers는 각 군집의 중심을 각 행에 저장한다. flags는 k개의 군집 중심을 초기화하는 방법을 명시한다. 다음은 kmeans를 사용하는 예제이다.


#include "opencv.hpp" using namespace cv; using namespace std; int main() { Mat srcImage = imread("hand.jpg"); // Mat srcImage = imread("flower.jpg"); if (srcImage.empty()) return -1; int K = 2; // # of clusters , 2, 3, // make color tables for displaying objects Mat colorTable(K, 1, CV_8UC3); Vec3b color; for (int k = 0; k < K; k++) { color[0] = rand() & 180 + 50; color[1] = rand() & 180 + 50; color[2] = rand() & 180 + 50; colorTable.at<Vec3b>(k, 0) = color; } Mat dstImage(srcImage.size(), srcImage.type()); int attempts = 1; int flags = KMEANS_RANDOM_CENTERS; TermCriteria criteria(TermCriteria::COUNT + TermCriteria::EPS, 10, 1.0); //kmeans 함수 매개변수 data는 1행의 입력값이 들어가야 된다.

//그래서 reshape 함수로 1행의 데이터 값으로 바꿔준다. Mat samples = srcImage.reshape(3, srcImage.rows*srcImage.cols); samples.convertTo(samples, CV_32FC3); Mat labels; kmeans(samples, K, labels, criteria, attempts, flags); // display clusters using labels for (int y = 0, k = 0; y < srcImage.rows; y++) for (int x = 0; x < srcImage.cols; x++, k++) { //만약 srcImage 이미지를 1행의 행렬로 바꿔주지 않았을 시 k = y*srcImage.cols + x int idx = labels.at<int>(k, 0); color = colorTable.at<Vec3b>(idx, 0); dstImage.at<Vec3b>(y, x) = color; } imshow("dstImage", dstImage); waitKey(); return 0; }



- void preCornerDetect(InputArray src, OuputArray dst, int ksize, int borderType=BORDER_DEFAULT)

: src에서 코너점 검출을 위한 특징 맵 dst를 Sobel 미분 연산자를 이요아여 계산한다. ksize는 Sobel 연산자의 마스크 크기이다. 코너점은 dst에서 지역극값(local maxima/minima)에서 검출된다. 지역 극값은 침식/팽창 연산을 통해서 구할 수 있다.


- void cornerEigenValsAndVecs(InputArray src, OutputArray dst, int blockSize, int ksize, int borderType = BORDER_DEFAULT) 

: src에서 코너점 거출을 위한 영상 블록의 고유벡터를 dst에 저장한다. ksize는 Sobel 미분 연산자의 마스크 크기이다. blockSize * blockSize의 이웃에 있는 미분값을 이용하여 고유 벡터 (x1,y1), (x2,y2)의 고유값을 계산하여 dst에 저장한다. 두 고유값이 모두 작은 곳은 평평한 영역에 있는 점이고 하나는 크고 다른 하나가 작으면 에지이다. 두 고유값이 모두 크면 코너점이다. 다음의 예제를 보자.

#include "opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
	Mat srcImage = imread("CornerTest.jpg", IMREAD_GRAYSCALE);
	if (srcImage.empty())
		return -1;

	int blockSize = 5;
	int ksize = 3;
	Mat cornerMap;
	cornerEigenValsAndVecs(srcImage, cornerMap, blockSize, ksize);

	Mat dstImage(srcImage.size(), CV_8UC3);
	cvtColor(srcImage, dstImage, COLOR_GRAY2BGR);

	Vec6f  element;
	for (int y = 0; y < cornerMap.rows; y++)
	for (int x = 0; x < cornerMap.cols; x++)
	{
		element = cornerMap.at<Vec6f>(y, x);

		if (element[0] > 0.2 && element[1] > 0.2) // corner points
		{
			circle(dstImage, Point(x, y), 5, Scalar(0, 0, 255), 2);
			cout << "eval(" << x << ", " << y << ")= "
				<< element[0] << ", " << element[1] << endl;
		}
		/*
		if(element[0] > 0.2 ) // edges
		{
		circle(dstImage, Point(x,y), 1, Scalar(255,0,0), 1);
		}
		*/
	}
	imshow("dstImage", dstImage);
	waitKey();

	return 0;
}


- void cornerMinEigenVal(InputArray src, OutputArray dst, int blockSize, int ksize=3, int borderType=BORDER_DEFAULT)

: 위 함수 cornerEigenValsAndVecs랑 똑같다. 다만 dst에 저장되는 값이 고유값 2개 중에서 작은 값만 저장된다. 두 고유값 중에서 작은 고유값이 주어진 임계값보다 크다면 큰 고유값은 더 크기 때문에 작은 고유값이 임계값보다 큰 화소가 코너점이 된다.


- void cornerHarris(InputArray src, OutputArray dst, int blockSize, int ksize, double k, int borderType= BORDER_DEFAULT)

: src에 대해 해리스 코너 검출로 계산된 값들을 dst에 저장한다. k는 해리스 코너 검출 상수이다. dst에서 지역 극대값이 코너점이 된다. 참고로 검출된 코너점에 대해서 cornerSubPix 함수를 적용하면 더 정확한 코너점을 얻을 수 있다.


- void goodFeaturesToTrack(InputArray image, OutputArray corners, int maxCorners, double qualityLevel, double mindistance, InputArray mask = noArray(), int blockSize=3, bool useHarrisDetector=false, double k = 0.04)

: 위의 다른 함수에서는 코너점을 검출할 때 지역 극대값을 따로 구해야 됬으나 이 함수를 사용하면 바로 코너점을 구할 수 있다. 위 함수는 image에 대해서 최대 maxCorners개의 corners 점들을 계산한다. mindistance는 코너점들 사이의 최소 거리이고 qualityLevel은 최소 코너점의 질을 결정하는 값이다. cornerMinEigenVal 함수에 의한 최소 고유값에 곱해지거나 cornerHarris 함수에 의한 Harris 코너 검출 반응값에 곱해진다. mask는 코너점이 검출된 영역을 지정하며 noArray()이면 영상 전체에서 코너점을 계산한다. useHarrisDetector = false라면 cornerMinEigenVal 함수를 사용하고 true이면 cornerHarris 함수를 사용한다.



※ 윤곽선 관련 특징 및 매칭 함수

- double arcLength(InputArray curve, bool closed)

: 곡선 또는 윤곽선의 길이를 계산한다. closed가 true이면 닫힌 곡선이다.


- double coutourArray(InputArray contour, bool oriented=false)
: 윤곽선 내부의 면적을 계산한다.


- Rect boundingRect(InputArray points)

: points에 주어진 좌표점들의 바운딩 직사각형을 계산해 반환한다.


- RotatedRect minAreaRect(InputArray points)
: boundingRect 함수와 동일하나 boundingRect는 회전이 없는 사각형인 반면, minAreaRect는 회전을 고려한 최소 사각형이다.


- void minEnclosingCircle(InputArray points, Point2f& center, float& radius)

: points에 주어진 좌표들을 둘러싸는 면적이 최소인 원을 중심점 center, 반지름 radius로 반환한다.


- void fitLine(InputArray points, OutputArray line, int distType, double param, double reps, double aeps)

: points로 주어진 이미지에 대해서 직선 근사시켜 line에 저장한다. 2D인 경우 line은 (vx, vy, x0, y0)의 직선 요소를 저장한다. (vx,vy)는 직선의 정규화된 방향 벡터이고 (x0,y0)는 직선 위의 한 점이다. param은 0이면 최적의 값을 계산한다. reps와 aeps는 반지름 각도의 충분한 정확도로 0.01을 넣는다.


- void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed)

: curve에 주어진 다각형, 윤곽선, 경계선을 epsilon에 주어진 정확도로 근사시켜 좌표의 개수를 줄여 curve와 같은 자료형의 approxCurve에 저장한다. epsilon은 다각형의 직선과의 허용거리로 epsilon이 크면 aproxCurve에 저장되는 좌표점의 개수가 적어진다. closed=true이면 curve는 닫힌 곡선, 다각형으로 취급한다.


- RotatedRect fitEllipse(InputArray points)

: points들을 둘러싸는 타원에 맞는 RotatedRect 자료형의 회전이 고려된 사각형을 반환한다. 반환값을 통해 ellipse  하무로 타원을 표시한다.


- double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

: 좌표 pt가 contour에 의해 주어지는 다각형 안에 있는지 검사한다. measureDist가 false이면 좌표 pt가 다각형 내부에 있으면 1, 외부에 있으면 -1, 다각형 위에 있으면 0을 반환한다. measureDist가 true이면 좌표에서 contour의 가장 가까운 다각형의 에지까지의 부호를 갖는 거리를 반환한다.



※ 블록 껍질

-void convexHull(InputAray points, OutputArray hull, bool clockwise=false, bool returnPoints=true)
: points에 주어진 좌표점들의 블록껍질을 계산해 hull에 반환한다. hull은 points의 인덱스를 갖거나 블록껍질을 이루는 좌표들의 좌표를 가진다. clockwise가 true이면 hull의 좌표들이 시계방향으로 출력된다. returnPoints가 true이면 hull이 블록껍질의 좌표를 갖고 그렇지 않으면 points의 인덱스를 가진다.

- bool isContourConvex(InputArray contour)

: contour 행렬이 볼록한지 검사한다.


- void convexityDefects(InputArray contour, InputAray convexhull, OutputAray convexityDefects)

: contour에서 블록결함을 검출한다. convexhull은 입력으로 주어지는 convexHull 함수로 계산한 블록껍질로 주의할 것은 인덱스를 가지는 벡터이어야 한다. convexityDefects는 블록결함을 저장하기 위한 vector<Vec4i> 자료형의 벡터이다. 각각의 블록결함은 (start_index, end_index, farthest_pt_index, fixpt_depth)로 표현된다. start_index와 end_index는 블록 결함의 시작과 끝을 나타내는 contour에서 첨자이다. farthest_pt_index는 블록 결함에서 가장 멀리 떨어진 좌표의 contour에서 첨자이다. 다음의 예를 보자

#include "opencv2\opencv.hpp"
using namespace cv;
using namespace std;
int main()
{
	Mat srcImage = imread("hand.jpg");
	Mat dstImage = srcImage.clone();
	GaussianBlur(srcImage, srcImage, Size(3, 3), 0.0);

	Mat hsvImage;
	cvtColor(srcImage, hsvImage, COLOR_BGR2HSV);
	imshow("hsvImage", hsvImage);

	Mat bImage;
	Scalar lowerb(0, 40, 0);
	Scalar upperb(20, 180, 255);
	inRange(hsvImage, lowerb, upperb, bImage);
	// HSV 컬러영상에서 이진영상으로 바꾸어 손 영역을 검출한다.

	erode(bImage, bImage, Mat());
	dilate(bImage, bImage, cv::Mat(), Point(-1, -1), 2);
	imshow("bImage", bImage);

	vector<vector<Point> > contours;
	findContours(bImage, contours, noArray(),
		RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	cout << "contours.size()=" << contours.size() << endl;
	if (contours.size()<1)
		return 0;

	int maxK = 0;
	double maxArea = contourArea(contours[0]);
	for (int k = 1; k<contours.size(); k++)
	{
		double area = contourArea(contours[k]);
		if (area > maxArea)
		{
			maxK = k;
			maxArea = area;
		}
	}
	vector<Point> handContour = contours[maxK];

	vector<int> hull;
	convexHull(handContour, hull);
	cout << " hull.size()=" << hull.size() << endl;

	vector<Point> ptsHull;
	for (int k = 0; k<hull.size(); k++)
	{
		int i = hull[k];
		ptsHull.push_back(handContour[i]);
	}
	drawContours(dstImage, vector<vector<Point>>(1, ptsHull), 0,
		Scalar(255, 0, 0), 2);

	vector<Vec4i> defects;
	convexityDefects(handContour, hull, defects);
	for (int k = 0; k<defects.size(); k++)
	{
		Vec4i v = defects[k];
		Point ptStart = handContour[v[0]];
		Point ptEnd = handContour[v[1]];
		Point ptFar = handContour[v[2]];
		float depth = v[3] / 256.0;
		if (depth > 10)
		{
			line(dstImage, ptStart, ptFar, Scalar(0, 255, 0), 2);
			line(dstImage, ptEnd, ptFar, Scalar(0, 255, 0), 2);
			circle(dstImage, ptStart, 6, Scalar(0, 0, 255), 2);
			circle(dstImage, ptEnd, 6, Scalar(0, 0, 255), 2);
			circle(dstImage, ptFar, 6, Scalar(255, 0, 255), 2);
		}
	}
	cout << " defects.size()=" << defects.size() << endl;
	imshow("dstImage", dstImage);
	waitKey(0);
	return 0;
}

손 사이의 circle이 ptFar이다


- double matchShapes(InputArray contour1, InputArray contour2, int method, double parameter)

: contour1, contour2의 매칭을 수행한다. 리턴값이 0에 가까울수록 매칭이 잘 이루어진 것이다. contour1과 contour2는 윤곽선이거나 그레이스케일 영상이다. 다음의 예를 보자.

#include "opencv2\opencv.hpp"
using namespace cv;
using namespace std;
vector<vector<Point> > myFindContours(Mat &srcImage);
void onMouse(int event, int x, int y, int flags, void* param);
vector<Point> g_points;
int main()
{
	Mat refImage = imread("refShapes.jpg", IMREAD_GRAYSCALE);
	if (refImage.empty())
		return -1;
	Scalar  colorTable[] = { Scalar(0, 0, 255),
		Scalar(0, 255, 0),
		Scalar(255, 0, 0) };

	Mat dstImage(512, 512, CV_8UC3, Scalar::all(255));
	imshow("dstImage", dstImage);
	setMouseCallback("dstImage", onMouse, (void *)&dstImage);

	vector<vector<Point> > refContours = myFindContours(refImage);
	if (refContours.size()<1)
		return 0;

	Mat refRGB;
	cvtColor(refImage, refRGB, COLOR_GRAY2BGR);
	for (int k = 0; k<refContours.size(); k++)
		drawContours(refRGB, refContours, k, colorTable[k], 2);
	imshow("refRGB", refRGB);

	int npts;
	const Point *pts;
	vector<Point> approxCurve;

	int method = 3;// CV_CONTOURS_MATCH_I3 in “types_c.h”
	int    minK;
	double minD;
	bool bEscKey = false;
	while (!bEscKey)
	{
		int nKey = waitKey(0);
		switch (nKey)
		{
		case 27:
			bEscKey = true;
			break;
		case 'r':
			g_points.clear();
			dstImage = Scalar::all(255);
			break;
		case ' ':
			if (g_points.size() < 1)
				break;
			//			cout << " g_points.size()=" << g_points.size() << endl;

			approxCurve = g_points;
			//			approxPolyDP(g_points, approxCurve, 10, true);

			// matches
			minD = matchShapes(refContours[0], approxCurve, method, 0);
			minK = 0;
			for (int k = 1; k<refContours.size(); k++)
			{
				double d = matchShapes(refContours[k], approxCurve, method, 0);
				if (d < minD)
				{
					minD = d;
					minK = k;
				}
			}
			pts = (const Point*)approxCurve.data();
			npts = approxCurve.size();
			polylines(dstImage, &pts, &npts, 1, true, colorTable[minK], 4);
			//			cout << "minK=" << minK << endl;
			break;
		}
		imshow("dstImage", dstImage);
	}
	waitKey(0);
	return 0;
}
vector<vector<Point> > myFindContours(Mat &srcImage)
{
	GaussianBlur(srcImage, srcImage, Size(3, 3), 0.0);
	Mat bImage;
	threshold(srcImage, bImage, 128, 255,
		THRESH_BINARY_INV + THRESH_OTSU);
	erode(bImage, bImage, Mat(), Point(-1, -1), 1);
	dilate(bImage, bImage, cv::Mat(), Point(-1, -1), 2);

	vector<vector<Point> > contours;
	findContours(bImage, contours, noArray(),
		RETR_EXTERNAL, CHAIN_APPROX_NONE);
	return contours;
}
void onMouse(int event, int x, int y, int flags, void* param)
{
	Mat *data = (Mat *)param;
	Mat dstImage = *data;
	switch (event)
	{
	case EVENT_MOUSEMOVE:
		if (flags & EVENT_FLAG_LBUTTON)
		{
			circle(dstImage, Point(x, y), 5, Scalar::all(0), -1);
			g_points.push_back(Point(x, y));
		}
		break;
	}
	imshow("dstImage", dstImage);
}


+ Recent posts