本系列代码托管于:https://github.com/chintsan-code/opencv4-tutorials

本篇使用的项目为:appro_fit_contours

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

using namespace cv;
using namespace std;

void approx_polys_demo(Mat &image);
void fit_circle_demo(Mat &image);
void fit_line_demo(Mat &image);

int main(int argc, const char** argv) {
	Mat src1 = imread("../sample/contours.png");
	Mat src2 = imread("../sample/stuff.png");
	Mat src3 = imread("../sample/fitline.png");
	if (src1.empty() || src2.empty()) {
		cout << "could not load image..." << endl;
		return -1;
	}
	approx_polys_demo(src1);
	fit_circle_demo(src2);
	fit_line_demo(src3);

	waitKey(0);
	destroyAllWindows();
	return 0;
}

void approx_polys_demo(Mat &image) {
	// 二值化
	GaussianBlur(image, image, Size(3, 3), 0);
	Mat gray, binary;
	cvtColor(image, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);

	// 轮廓发现
	imshow("poly binary", binary);
	vector<vector<Point>> contours;
	vector<Vec4i> hirearchy;
	findContours(binary, contours, hirearchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());

	// 多边形逼近演示程序
	for (size_t t = 0; t < contours.size(); t++) {
		Moments mm = moments(contours[t]);
		double cx = mm.m10 / mm.m00;
		double cy = mm.m01 / mm.m00;
		drawMarker(image, Point(cx, cy), Scalar(255, 0, 0), MARKER_TILTED_CROSS, 10, 2, 8);

		double area = contourArea(contours[t]);
		double clen = arcLength(contours[t], true);

		Mat result;
		approxPolyDP(contours[t], result, 4, true);
		printf("corners : %d , contour area : %.2f, contour length : %.2f \n", result.rows, area, clen);
		if (result.rows == 6) {
			putText(image, "hexagon", Point(cx, cy - 10), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 0, 255), 1, 8);
		}
		if (result.rows == 4) {
			putText(image, "rectangle", Point(cx, cy - 10), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 255, 255), 1, 8);
		}
		if (result.rows == 3) {
			putText(image, "triangle", Point(cx, cy - 10), FONT_HERSHEY_PLAIN, 1.0, Scalar(255, 0, 255), 1, 8);
		}
		if (result.rows  > 10) {
			putText(image, "circle", Point(cx, cy - 10), FONT_HERSHEY_PLAIN, 1.0, Scalar(255, 255, 0), 1, 8);
		}

	}
	imshow("find contours demo", image);
}

void fit_circle_demo(Mat &image) {
	// 二值化
	Mat gray, binary;
	cvtColor(image, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);

	// 轮廓发现
	imshow("circle binary", binary);
	vector<vector<Point>> contours;
	vector<Vec4i> hirearchy;
	findContours(binary, contours, hirearchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());

	// 拟合圆或者椭圆
	for (size_t t = 0; t < contours.size(); t++) {
		// drawContours(image, contours, t, Scalar(0, 0, 255), 2, 8);
		RotatedRect rrt = fitEllipse(contours[t]);
		float w = rrt.size.width;
		float h = rrt.size.height;
		Point center = rrt.center;
		drawMarker(image, center, Scalar(255, 0, 0), MARKER_TILTED_CROSS, 10, 2, 8);
		ellipse(image, rrt, Scalar(0, 255, 0), 2, 8);
	}
	imshow("fit circle result", image);
}

void fit_line_demo(Mat &image) {
	// 二值化
	Mat gray, binary;
	cvtColor(image, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);

	// 轮廓发现
	imshow("line binary", binary);
	vector<vector<Point>> contours;
	vector<Vec4i> hirearchy;
	findContours(binary, contours, hirearchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());

	for (size_t t = 0; t < contours.size(); t++) {
		Vec4f line_param;
		fitLine(contours[t], line_param, DIST_L2, 0, 0.01, 0.01);
		//获取点斜式的点和斜率
		Point point0;
		point0.x = line_param[2];
		point0.y = line_param[3];

		double k = line_param[1] / line_param[0];

		//计算直线的端点(y = k(x - x0) + y0)
		Point point1, point2;
		point1.x = 0;
		point1.y = k * (0 - point0.x) + point0.y;
		point2.x = 640;
		point2.y = k * (640 - point0.x) + point0.y;

		line(image, point1, point2, cv::Scalar(0, 255, 0), 2, 8, 0);
	}
	imshow("fit line result", image);
}

轮廓逼近,本质是减少编码点。但减少不是无限制的,轮廓逼近得越厉害,反而编码点会变多。

approxPolyDP:多边形逼近

void approxPolyDP( InputArray curve, OutputArray approxCurve, double epsilon, bool closed );
  • curve:输入,使用std::vector容器存储2D点集,或Mat对象
  • approxCurve:输出,逼近结果,type与curve一致
  • epsilon:指定逼近的精度
  • closed:当为true,逼近结果是闭合的,否则,不是闭合的

fitEllipse:拟合椭圆

RotatedRect fitEllipse( InputArray points );
  • points:输入,使用std::vector容器存储2D点集,或Mat对象
  • @return:返回拟合椭圆的内切旋转矩形

fitLine:拟合直线

void fitLine( InputArray points, OutputArray line, int distType, double param, double reps, double aeps );
  • points:输入,使用std::vector容器存储2D/3D点集,或Mat对象
  • line:输出拟合直线的参数。例如2D直线拟合,line应该为4个元素的集合(可以使用Vec4f存储):(vx, vy, x0, y0),其中通过vx, vy可以计算出直线的方向, x0, y0为拟合直线上的一点;如果为3D直线拟合,line应该为6个元素的集合(可以使用Vec6f存储):(vx, vy, vz, x0, y0, z0) ,其中通过vx, vy, vz可以计算出直线的方向, x0, y0, z0为拟合直线上的一点。即直线的点截式。
  • distType:距离类型,拟合直线时,要使输入点到拟合直线的距离和最小化
    • DIST_L2:最简单的最小二乘法
  • param:距离参数,跟所选的距离类型有关,如果设置为0,会自动选择最优化的值
  • reps:半径精度,通常设为0.01
  • aeps:角度精度,通常设为0.01