Opencv实现简单图形的面积和周长的计算、轮廓提取以及形状识别

问题描述:

关键字VS2015、Opencv、形状、周长、面积、轮廓提取

在很多时候我们需要对一些形状进行识别,其中包括对形状的区分、对图像的提取、对面积和周长的计算等等,这时我们可以利用opencv进行运用从而实现目的。

本篇文章源于有一次同学提出了一个问题:存在一张图片如下,把图中的红色内容提出来,并且识别形状(正三角形,圆形,正方形),计算面积,周长,边长,中心点,接下来我们就用VS2015+Opencv4来实现它!
Opencv实现简单图形的面积和周长的计算、轮廓提取以及形状识别_第1张图片

主要流程:

基于硬核求解原则,咱们用最现成的方法、最简单的思路对问题进行求解。

老规矩先来波预操作:先给咱们的图来滤个波、二值化然后再进行形态学操作,然后我们就能得到一个清晰的二值图了

得到二值图像后咱们就要把他的轮廓提取出来,这里主要是利用finfContours()函数找出图形轮廓,找出各个轮廓后用drawContours()函数绘出轮廓,注意在这里要加入一个轮廓大小判断是否等于窗口面积,否则会将窗口识别成矩形绘出。

接下来先检测出圆形,这里可以利用一个常见的霍夫圆检测函数来检测圆形,利用获得的圆心和半径可以确定圆的位置和大小,从而绘出。

然后对矩形和三角形进行多边拟合操作,利用approxPolyDP()函数
对图像轮廓点进行多边形拟合,对于拟合的结果我们可以进行一个简单的筛选,以便于筛掉过小的拟合矩形。三角形以及其他多边形的拟合操作都与矩形类似。

根据拟合结果我们就能得到各个图形的顶点和边长,从而我们就能利用line()函数将不同的几何形状用不同的颜色描绘出来。
同时我们也可以通过contourArea()函数和arcLength()函数计算出各个图形的面积与周长。

代码实现:

#include
#include
#include
#define pi 3.14
using namespace cv;
using namespace std;

Mat src;
int main(int argc, char** argv)
{
	src = imread("D:\\我的文档\\Visual Studio 2015\\Projects\\images\\demo.jpg");
	if (src.empty())
	{
		printf("图片未找到!!!");
		return -1;
	}
	imshow("input image", src);
	//高斯模糊
	Mat GaussImg;
	//medianBlur(src, GaussImg,5);//中值滤波
	GaussianBlur(src, GaussImg, Size(7, 7), 0, 0);//高斯滤波
	imshow("Gauss Image", GaussImg);
	cvtColor(GaussImg, GaussImg, COLOR_BGR2GRAY);

	//二值化操作
	Mat binary;
	threshold(GaussImg, binary, 128, 255, THRESH_BINARY);
	imshow("binary Image", binary);

	//形态学操作
	Mat morphImg;
	Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
	morphologyEx(binary, morphImg, MORPH_CLOSE, kernel, Point(-1, -1), 1);
	imshow("morph Image", morphImg);

	//阈值分割
	Mat contoursIm;
	threshold(morphImg, contoursIm, 0, 255, THRESH_BINARY + THRESH_OTSU);
	imshow("阈值分割", contoursIm);


	//轮廓发现
	Mat contoursImg = Mat::zeros(src.size(), CV_8UC3);
	vector<vector<Point>>contours;//存储二维浮点型向量,这里面将来会存储找到du的边界的(x,y)坐标
	vector<Vec4i>hierarchy;//定义的层级。这个在找边界findcontours的时候会自动生成,这里只是给它开辟一个空间

	findContours(contoursIm, contours, RETR_LIST, CHAIN_APPROX_NONE);//保存物体边界上所有连续的轮廓点到contours向量内
	int flags = 0;
	for (size_t i = 0; i < contours.size(); i++)
	{
		Rect rect = boundingRect(contours[i]);

		if (rect.height == src.rows)
		{
			flags = 1;
			contours.pop_back();
		}


		if (flags == 0)
		{
			drawContours(contoursImg, contours, static_cast<int>(i),Scalar(0, 0, 255), 2, 8, hierarchy, 0, Point(0, 0));
		}
		flags = 0;
	}

	vector<Point>triangle;
	vector<Point>approx;
	vector<Point>squares;

	vector<Vec3f>circles;//圆
	HoughCircles(GaussImg, circles, HOUGH_GRADIENT, 1, src.rows / 20, 100, 60, 0, 0);//rows/20为检测圆之间距离,100为传递给canny边缘检测算子的高阈值,60(它越小则可以检测到更多跟本不存在的圆,两个0为半径默认值)

	Mat dstImg(morphImg.rows, morphImg.cols, CV_8UC3, Scalar(255, 255, 255));
	for (size_t i = 0; i < circles.size(); i++)//画圆
	{
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int radius = cvRound(circles[i][2]);
		circle(dstImg, center, radius, Scalar(0, 255, 0), 5, 8, 0);
		circle(dstImg, center, 3, Scalar(0, 255, 0), -1);
		cout << "圆形" << i + 1 << "中心" << center << endl;
		cout << "圆" << i + 1 << "的面积为:" << pi*radius*radius << endl;
		cout << "圆" << i + 1 << "的周长为:" << pi * 2 * radius << endl;
	}

	//多边形拟合操作
	for (size_t i = 0; i < contours.size(); i++)
	{
		approxPolyDP(contours[i], approx, arcLength(Mat(contours[i]), true)*0.02, true);                     //主要功能是把一个连续光滑曲线折线化,对图像轮廓点进行多边形拟合。
		if (approx.size() == 4 && fabs(contourArea(Mat(approx))) > 1000 && isContourConvex(Mat(approx)))
		{
			double minDist = 1e10;

			for (int i = 0; i < 4; i++)
			{
				Point side = approx[i] - approx[(i + 1) % 4];
				double squaredSideLength = side.dot(side);
				minDist = min(minDist, squaredSideLength);
			}
			if (minDist<50)
				break;
			for (int i = 0; i<4; i++)
				squares.push_back(Point(approx[i].x, approx[i].y));
		}

		approxPolyDP(contours[i], approx, arcLength(Mat(contours[i]), true)*0.1, true);
		if (approx.size() == 3 && fabs(contourArea(Mat(approx))) > 1000 && isContourConvex(Mat(approx)))
		{
			double minDist = 1e10;

			for (int i = 0; i <3; i++)
			{
				Point side = approx[i] - approx[(i + 1) % 3];
				double squaredSideLength = side.dot(side);
				minDist = min(minDist, squaredSideLength);
			}
			if (minDist<50)
				break;
			for (int i = 0; i<3; i++)
				triangle.push_back(Point(approx[i].x, approx[i].y));
		}
		drawContours(dstImg, contours, i, Scalar(0, 0, 255), 3);
	}

	for (size_t i = 0; i < squares.size(); i += 4)//描边操作
	{
		Point center;
		center.x = (squares[i].x + squares[i + 2].x) / 2;
		center.y = (squares[i].y + squares[i + 2].y) / 2;
		line(dstImg, squares[i], squares[i + 1], Scalar(255, 0, 255), 4);
		line(dstImg, squares[i + 1], squares[i + 2], Scalar(255, 0, 255), 4);
		line(dstImg, squares[i + 2], squares[i + 3], Scalar(255, 0, 255), 4);
		line(dstImg, squares[i + 3], squares[i], Scalar(255, 0, 255), 4);
		cout << "矩形" << (i + 1) % 4 << "中心" << center << endl;
		cout << "矩形" << i + 1 << "的面积为:" << contourArea(squares) << endl;
		cout << "矩形" << i + 1 << "的周长为:" << arcLength(squares, true) << endl;
		circle(dstImg, center, 3, Scalar(255, 0, 255), -1);
	}
	for (size_t i = 0; i < triangle.size(); i += 3)
	{
		Point center;
		center.x = (triangle[i].x + triangle[i + 1].x + triangle[i + 2].x) / 3;
		center.y = (triangle[i].y + triangle[i + 1].y + triangle[i + 2].y) / 3;
		line(dstImg, triangle[i], triangle[i + 1], Scalar(255, 0, 0), 4);
		line(dstImg, triangle[i + 1], triangle[i + 2], Scalar(255, 0, 0), 4);
		line(dstImg, triangle[i], triangle[i + 2], Scalar(255, 0, 0), 4);
		cout << "三角形" << (i + 1) % 3 <<"中心" <<center << endl;
		cout << "三角形" << i + 1 << "的面积为:" << contourArea(triangle) << endl;
		cout << "三角形" << i + 1 << "的周长为:" << arcLength(triangle, 1) << endl;
		circle(dstImg, center, 3, Scalar(255, 0, 0), -1);
	}
	

	//显示提取图像
	Mat extract(src.size(), CV_8UC3, Scalar(255, 255, 255));
	for (size_t i = 0; i < squares.size(); i += 4)//描边操作
	{
		Point center;
		center.x = (squares[i].x + squares[i + 2].x) / 2;
		center.y = (squares[i].y + squares[i + 2].y) / 2;
		line(extract, squares[i], squares[i + 1], Scalar(255, 0, 255), 4);
		line(extract, squares[i + 1], squares[i + 2], Scalar(255, 0, 255), 4);
		line(extract, squares[i + 2], squares[i + 3], Scalar(255, 0, 255), 4);
		line(extract, squares[i + 3], squares[i], Scalar(255, 0, 255), 4);
		circle(extract, center, 3, Scalar(255, 0, 255), -1);
	}

	for (size_t i = 0; i < triangle.size(); i += 3)
	{
		Point center;
		center.x = (triangle[i].x + triangle[i + 1].x + triangle[i + 2].x) / 3;
		center.y = (triangle[i].y + triangle[i + 1].y + triangle[i + 2].y) / 3;
		line(extract, triangle[i], triangle[i + 1], Scalar(255, 0, 0), 4);
		line(extract, triangle[i + 1], triangle[i + 2], Scalar(255, 0, 0), 4);
		line(extract, triangle[i], triangle[i + 2], Scalar(255, 0, 0), 4);
		circle(extract, center, 3, Scalar(255, 0, 0), -1);
	}

	for (size_t i = 0; i < circles.size(); i++)//画圆
	{
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int radius = cvRound(circles[i][2]);
		circle(extract, center, radius, Scalar(0, 255, 0), 5, 8, 0);
		circle(extract, center, 3, Scalar(0, 255, 0), -1);
	}
	imshow("轮廓提取", extract);
	imshow("形状检测", dstImg);
	waitKey(0);
	return 0;

}

运行结果:

滤波图像:
Opencv实现简单图形的面积和周长的计算、轮廓提取以及形状识别_第2张图片
二值图像:
Opencv实现简单图形的面积和周长的计算、轮廓提取以及形状识别_第3张图片
图形提取与区分:
Opencv实现简单图形的面积和周长的计算、轮廓提取以及形状识别_第4张图片
图形中心坐标、图形面积与周长计算结果:
Opencv实现简单图形的面积和周长的计算、轮廓提取以及形状识别_第5张图片

小小总结:

本次所运用的方法存在一定的局限性,识别图形的范围有限,比如难以识别扇形、凹形等等,不过对于比较常规的多边形识别确很有效,同时也可以拓展到调用Video进行实时的规则形状识别,还有地方值得提升拓展,欢迎大家交流。

你可能感兴趣的:(opencv,C++,opencv,图像识别)