OpenCV识别草莓轮廓、质心、生长方向

机器人视觉系统 作业

识别下图中草莓轮廓,计算质心、生长方向。

OpenCV识别草莓轮廓、质心、生长方向_第1张图片

OpenCV识别草莓轮廓、质心、生长方向

  • 0 调试环境
  • 1 提取草莓区域
    • 1.1 HSI颜色空间处理(方法1)
      • 1.1.1 RGB转HSI
      • 1.1.2 二值化
    • 1.2 RGB颜色空间处理(方法2)
      • 1.2.1 RGB归一化
      • 1.2.2 灰度化
      • 1.2.3 二值化
  • 2 填充空洞
  • 3 分离草莓
  • 4 计算质心、获取草莓生长方向
    • 4.1 下极值点获取生长方向(方法1)
    • 4.2 轮廓拟合椭圆获取生长方向(方法2)
  • 5 参考文献
  • 附录1 主函数程序
  • 附录2 子函数程序

0 调试环境

① OpenCV 4.2.0
② Visual Studio 2017

1 提取草莓区域

获取图像中草莓部分的二值图。

1.1 HSI颜色空间处理(方法1)

1.1.1 RGB转HSI

  读取源图像的RGB值,归一化后按下述公式将其转到HSI颜色空间,得到的HSI取值范围分别为: H [ 0 , 2 π ] H[0,2\pi] H[0,2π] S [ 0 , 1 ] S[0,1] S[0,1] I [ 0 , 1 ] I[0,1] I[0,1],将H归一化( H = H / 2 π H = H/2\pi H=H/2π)后显示。
H = { θ , B ≤ G π − θ , B > G , θ = arccos ⁡ { ( R − G ) + ( R − B ) 2 ( R − G ) 2 + ( R − B ) ( G − B ) } H=\begin{cases} \theta, B \le G \\ \pi-\theta, B>G \end{cases},\theta=\arccos\left\{ \frac{(R-G)+(R-B)}{2\sqrt{ (R-G)^2+(R-B)(G-B)}} \right\} H={ θ,BGπθ,B>Gθ=arccos{ 2(RG)2+(RB)(GB) (RG)+(RB)}
S = 1 − 3 R + G + B min ⁡ ( R , G , B ) S=1-\large\frac{3}{R+G+B}\min(R,G,B) S=1R+G+B3min(R,G,B)
I = 1 − R + G + B 3 I=1-\large\frac{R+G+B}{3} I=13R+G+B
  得到草莓HSI颜色空间的图像如图1-1所示。

OpenCV识别草莓轮廓、质心、生长方向_第2张图片(a) HSI
OpenCV识别草莓轮廓、质心、生长方向_第3张图片(b) H通道
OpenCV识别草莓轮廓、质心、生长方向_第4张图片(a) S通道
OpenCV识别草莓轮廓、质心、生长方向_第5张图片(b) I通道
图1-1 HSI

1.1.2 二值化

  为了获取草莓区域的二值图,使用OpenCV的threshold()函数对草莓HSI图像的S通道进行阈值分割。由于上一步得到的数据是归一化状态的,在处理数据时需要乘以255将其扩展到 [ 0 , 255 ] [0,255] [0,255]

Mat binary;
threshold(S * 255, binary, 85, 255, THRESH_BINARY_INV);

  只含草莓部分的二值图如图1-2所示。

OpenCV识别草莓轮廓、质心、生长方向_第6张图片
图1-2 binary

1.2 RGB颜色空间处理(方法2)

1.2.1 RGB归一化

  在RGB模型下,为削弱光照情况对图像像素值的干扰,利用下述公式对源图像进行归一化处理,获得r、g、b通道归一化像素值。[1]
{ r = R R + G + B g = G R + G + B b = B R + G + B \left\{\begin{aligned} r=\frac{R}{R+G+B}\\ g=\frac{G}{R+G+B}\\ b=\frac{B}{R+G+B} \end{aligned}\right. r=R+G+BRg=R+G+BGb=R+G+BB

  草莓图像进行RGB归一化后的结果如图1-3所示。

OpenCV识别草莓轮廓、质心、生长方向_第7张图片
图1-3 normalize

1.2.2 灰度化

  利用下述公式对归一化后的图像进行灰度化处理,计算得到灰度图像中各点像素值。[1]
V g r a y = { ( r − g ) × 255 , ( r > g ) 0 ,           ( r ≤ g ) V_{gray}=\left\{\begin{aligned} (r-g)&\times255,(r>g)\\ &0,\ \ \ \ \ \ \ \ \ (r \le g) \end{aligned}\right. Vgray={ (rg)×255,(r>g)0,         (rg)

  得到草莓区域的灰度图像如图1-4所示。

OpenCV识别草莓轮廓、质心、生长方向_第8张图片
图1-4 gray

1.2.3 二值化

  为进一步削弱光照影响,采用OTSU算法计算得到自适应分割阈值 T f T_f Tf。同时为尽量多的保留草莓区域信息,利用补偿系数k对自适应阈值 T f T_f Tf进行修正。此外,为滤除部分处于成熟渐变期的偏粉色草萄及其他浅红色系干扰物,设定固定阈值统计干扰物阈值分布 T 0 T_0 T0。利用下述式子对草莓灰度图进行二值化处理。[1]
V b i n a r y = { 255 , ( V g r a y ≥ T f × k ) 0 ,      ( V g r a y < T f × k    o r    V g r a y < T 0 ) V_{binary}=\left\{\begin{aligned} &255,(V_{gray}\geq T_f\times k)\\ &0,\ \ \ \ (V_{gray} < T_f\times k\ \ or\ \ V_{gray}Vbinary={ 255,(VgrayTf×k)0,    (Vgray<Tf×k  or  Vgray<T0)

  得到草莓区域的二值图像如图1-5所示。

OpenCV识别草莓轮廓、质心、生长方向_第9张图片
图1-5 binary

2 填充空洞

  上述方法得到的草莓二值图包含许多空洞,为了方便识别,需要尽可能地将空洞补全。补空洞的步骤为:
(1)用闭操作连通区域;
(2)使用findContours()函数,选择RETR_CCOMP模式,找出草莓轮廓中的内轮廓(即空洞的轮廓);
(3)判断每个轮廓的大小,只有当其小于90像素时才进行填充。
  填充后的草莓二值图如图2-1所示。

OpenCV识别草莓轮廓、质心、生长方向_第10张图片(a) 方法1的二值图填充
OpenCV识别草莓轮廓、质心、生长方向_第11张图片(b) 方法2的二值图填充
图2-1 填充空洞

3 分离草莓

  由于RGB空间处理得到的草莓二值图(方法2)不方便用分水岭分割(主要是watershed()的第一个参数不好取),这里只对HSI空间得到的二值图(方法1)做分水岭分割,步骤如下:[2]
(1)对填充空洞后的图像使用distanceTransform()函数作距离变换,并归一化;
(2)对归一化后的距离图使用的threshold()函数作阈值处理,得到种子区域;(这种方法其实很难确定种子区域的阈值范围)
(3)使用watershed()函数执行分水岭分割;
(4)对每个草莓区域上色。

  得到结果如图3-1所示。

OpenCV识别草莓轮廓、质心、生长方向_第12张图片
OpenCV识别草莓轮廓、质心、生长方向_第13张图片
图3-1 分离草莓

4 计算质心、获取草莓生长方向

以下均对图3-2进行处理。

  由草莓轮廓区域的一阶矩可以获得草莓的中心点,作为质心。

4.1 下极值点获取生长方向(方法1)

(1)遍历各草莓的轮廓数据,找出y值最小值点作为草莓的下极值点;
(2)连接下极值点和中心点(质心),得到草莓的生长方向(姿态)。

OpenCV识别草莓轮廓、质心、生长方向_第14张图片
图4-1 质心和生长方向(方法1)

4.2 轮廓拟合椭圆获取生长方向(方法2)

(1)将草莓轮廓拟合为椭圆;
(2)作椭圆的外接矩形;
(3)求矩形偏移角,转换为直线斜率;
(4)绘制生长方向。

OpenCV识别草莓轮廓、质心、生长方向_第15张图片
图4-2 质心和生长方向(方法2)

5 参考文献

[1]纪超. 温室果蔬采摘机器人视觉信息获取方法及样机系统研究[D].中国农业大学,2014.
[2]OpenCV官方文档示例

附录1 主函数程序

(1)HSI颜色空间处理

#include 
#include 
using namespace std;
using namespace cv;
int main()
{
     
	Mat src;
	src = imread("C:/Users/12421/Desktop/草莓1.jpg");
	Mat S;
	rgb2hsi(src, S);
	Mat binary(src.rows, src.cols, CV_32FC1);
	threshold(S * 255, binary, 85, 255, THRESH_BINARY_INV);
	imshow("binary", binary);	
	binary.convertTo(binary, CV_8UC1);
	Mat element(5, 5, CV_8U, Scalar(1));
	morphologyEx(binary, binary, MORPH_CLOSE, element);
	Mat binary_filled;
	fillcavity(binary, binary_filled);//填充小区域
	//erode(binary_filled, binary_filled, element);
	//erode(binary_filled, binary_filled, element);
	Mat strawberry;
	Mat S_C3(src.rows, src.cols, CV_8UC3);
	for (int i = 0; i < src.rows; i++)
	{
     
		for (int j = 0; j < src.cols; j++)
		{
     
			S_C3.at<Vec3b>(i, j) = S.at<float>(i, j) * 255;
		}
	}
	findstrawberry(binary_filled, strawberry, S_C3);
	orientation(src, strawberry);
	//orientation_ell(src, strawberry);
	imshow("orientation", src);
	Mat result;
	addWeighted(src, 0.5, strawberry, 0.5, 0, result);
	//imshow("Result", result);
	return 0;
}

(2)RGB颜色空间处理:

#include 
#include 
using namespace std;
using namespace cv;
int main()
{
     
	Mat src;
	src = imread("C:/Users/12421/Desktop/草莓1.jpg");
	Mat nor(src.rows, src.cols, CV_32FC3);
	mynormalize(src, nor);//削弱光照情况对图像像素值干扰
	Mat gray(src.rows, src.cols, CV_8UC1);
	rgb2gray(nor, gray);//进一步削弱光照影响	
	int otsuThreshold;
	OTSU(gray, otsuThreshold);
	Mat binary(src.rows, src.cols, CV_8UC1);
	gray2binary(gray, binary, otsuThreshold);
	imshow("binary", binary);
	Mat element(5, 5, CV_8U, Scalar(1));
	morphologyEx(binary, binary, MORPH_CLOSE, element);
	Mat binary_filled;
	fillcavity(binary, binary_filled);//填充小区域
	erode(binary_filled, binary_filled, element);
	erode(binary_filled, binary_filled, element);
	imshow("binary_filled", binary_filled);
	waitKey(0);
	return 0;
}

附录2 子函数程序

(1)RGB转HSI:

/**
* @brief RGB转HSI
* @param InputArray 输入RGB图像
* @param OutputArray 输出HSI的S通道图像
* @return 0 成功
*/
int rgb2hsi(Mat InputArray, Mat& OutputArray)
{
     
	Mat H(InputArray.rows, InputArray.cols, CV_32FC1);
	Mat S(InputArray.rows, InputArray.cols, CV_32FC1);
	Mat I(InputArray.rows, InputArray.cols, CV_32FC1);
	Mat HSI(InputArray.rows, InputArray.cols, CV_32FC3);
	double r = 0, g = 0, b = 0;
	for (int ii = 0; ii < InputArray.rows; ii++)
	{
     
		for (int j = 0; j < InputArray.cols; j++)
		{
     
			//读取rgb值
			b = InputArray.at<Vec3b>(ii, j)[0];
			g = InputArray.at<Vec3b>(ii, j)[1];
			r = InputArray.at<Vec3b>(ii, j)[2];
			//rgb值归一化
			r = r / (r + g + b);
			g = g / (r + g + b);
			b = b / (r + g + b);
			double minvalue = 0, W = 0;
			double h = 0, s = 0, i = 0;
			//计算I值
			i = (r + g + b) / 3.0;
			minvalue = __min(r, g);
			minvalue = __min(minvalue, b);
			//计算S值
			s = 1 - 3 * minvalue / (r + g + b);
			if (s < 0.00001)
			{
     
				s = 0;
			}
			else if (s > 0.99999)
			{
     
				s = 1;
			}
			//计算H值
			if (s != 0)
			{
     
				W = acos((r - g + r - b) / (2 * sqrt((r - g)*(r - g) + (r - b)*(g - b))));
				if (g >= b)
				{
     
					h = W;
				}
				else
				{
     
					h = 2 * CV_PI - W;
				}
			}
			H.at<float>(ii, j) = h / (2 * CV_PI);
			HSI.at<Vec3f>(ii, j)[0] = h / (2 * CV_PI);
			S.at<float>(ii, j) = s;
			HSI.at<Vec3f>(ii, j)[1] = s;
			I.at<float>(ii, j) = i;
			HSI.at<Vec3f>(ii, j)[2] = i;
		}
	}	
	//imshow("HSI", HSI);
	//imshow("H", H);
	//imshow("S", S);
	//imshow("I", I);
	OutputArray = S;
	return 0;
}

(2)RGB归一化:

/**
* @brief 对RGB归一化处理
* @param InputArray 输入原始图像
* @param OutputArray 输出归一化图像
* @return 0 成功
*/
int mynormalize(Mat InputArray, Mat& OutputArray)
{
     
	Mat Output(InputArray.rows, InputArray.cols, CV_32FC3);
	for (int i = 0; i < InputArray.rows; i++)
	{
     
		for (int j = 0; j < InputArray.cols; j++)
		{
     
			//读取rgb值
			double epslon = 0.000001;//防止除以0
			int b = InputArray.at<Vec3b>(i, j)[0];
			int g = InputArray.at<Vec3b>(i, j)[1];
			int r = InputArray.at<Vec3b>(i, j)[2];
			double sum = b + g + r + epslon;
			//rgb值归一化
			Output.at<Vec3f>(i, j)[2] = r / sum;
			Output.at<Vec3f>(i, j)[1] = g / sum;
			Output.at<Vec3f>(i, j)[0] = b / sum;
		}
	}
	OutputArray = Output;
	return 0;
}

(3)灰度化:

/**
* @brief 识别草莓自定义灰度化函数
* @param InputArray 输入RGB归一化图像
* @param OutputArray 输出灰度图像
* @return 0 成功
*/
int rgb2gray(Mat InputputArray, Mat& OutputArray)
{
     
	for (int i = 0; i < InputputArray.rows; i++)
	{
     
		for (int j = 0; j < InputputArray.cols; j++)
		{
     
			//读取rg值
			double g = InputputArray.at<Vec3f>(i, j)[1];
			double r = InputputArray.at<Vec3f>(i, j)[2];
			if (r > g)
			{
     
				OutputArray.at<uchar>(i, j) = (r - g) * 255;
			}
			else
			{
     
				OutputArray.at<uchar>(i, j) = 0;
			}
		}
	}
	return 0;
}

(4)OTSU算法获取自适应阈值:

/**
* @brief Otsu算法获取自适应分割阈值Tf
* @param InputArray 输入原始图像
* @param threshold 输出自适应阈值
* @return 0 成功
*/
int OTSU(Mat InputArray, int& threshold)
{
     
	int nCols = InputArray.cols;
	int nRows = InputArray.rows;
	//init the parameters
	int nSumPix[256];
	float nProDis[256];
	for (int i = 0; i < 256; i++)
	{
     
		nSumPix[i] = 0;
		nProDis[i] = 0;
	}
	//统计灰度集中每个像素在整幅图像中的个数
	for (int i = 0; i < nRows; i++)
	{
     
		for (int j = 0; j < nCols; j++)
		{
     
			nSumPix[(int)InputArray.at<uchar>(i, j)]++;
		}
	}
	//计算每个灰度级占图像中的概率分布
	for (int i = 0; i < 256; i++)
	{
     
		nProDis[i] = (float)nSumPix[i] / (nCols*nRows);
	}
	//遍历灰度级[0,255],计算出最大类间方差下的阈值
	float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
	double delta_max = 0.0;
	for (int i = 0; i < 256; i++)
	{
     
		//初始化相关参数
		w0 = w1 = u0 = u1 = u0_temp = u1_temp = delta_temp = 0;
		for (int j = 0; j < 256; j++)
		{
     
			//背景部分
			if (j <= i)
			{
     
				w0 += nProDis[j];
				u0_temp += j * nProDis[j];
			}
			//前景部分
			else
			{
     
				w1 += nProDis[j];
				u1_temp += j * nProDis[j];
			}
		}
		//计算两个分类的平均灰度
		u0 = u0_temp / w0;
		u1 = u1_temp / w1;
		//依次找到最大类间方差下的阈值
		delta_temp = (float)(w0*w1*pow((u0 - u1), 2)); //前景与背景之间的方差(类间方差)
		if (delta_temp > delta_max)
		{
     
			delta_max = delta_temp;
			threshold = i;
		}
	}
	return 0;
}

(5)二值化:

/**
* @brief 识别草莓自定义二值化函数
* @param InputArray 输入灰度化图像
* @param OutputArray 输出二值图像
* @param otsuThreshold 输入自适应阈值
* @return 0 成功
*/
int gray2binary(Mat InputArray, Mat& OutputArray, int otsuThreshold)
{
     
	double k = 0.7;
	double T0 = 35;
	for (int i = 0; i < InputArray.rows; i++)
	{
     
		for (int j = 0; j < InputArray.cols; j++)
		{
     
			int graydata = InputArray.at<uchar>(i, j);
			if (graydata >= otsuThreshold * k)
			{
     
				OutputArray.at<uchar>(i, j) = 255;
			}
			else if (graydata < T0 || graydata < otsuThreshold * k)
			{
     
				OutputArray.at<uchar>(i, j) = 0;
			}
		}
	}
	return 0;
}

(6)填充空洞

int fillcavity(Mat InputArray, Mat& OutputArray)
{
     
	Mat src = InputArray.clone();
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	findContours(src, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
	for (size_t i = 0; i < contours.size(); i++)
	{
     
		if (hierarchy[i][3] >= 0 && contours[i].size() < 90) //小空洞
		{
     
			drawContours(src, contours, i, Scalar(255, 255, 255), FILLED);
		}
	}
	OutputArray = src;
	return 0;
}

(7)分离草莓:

int findstrawberry(Mat InputArray, Mat& OutputArray, Mat strawberry_only)
{
     
	//距离变换
	Mat dist;
	distanceTransform(InputArray, dist, DIST_L2, 3);
	//距离图归一化
	normalize(dist, dist, 0, 1.0, NORM_MINMAX);
	//namedWindow("Distance", WINDOW_AUTOSIZE);
	//imshow("Distance", dist);

	threshold(dist, dist, 0.5, 1.0, THRESH_BINARY);
	//腐蚀
	Mat kernel1 = Mat::ones(3, 3, CV_8U);
	imshow("Peaks", dist);
	//imwrite("C:/Users/12421/Desktop/Peaks.jpg", dist);
	Mat dist_8u;
	dist.convertTo(dist_8u, CV_8U);
	//查找标记
	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	findContours(dist_8u, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	//创建标记
	Mat markers = Mat::zeros(dist.size(), CV_32S);
	//绘制前景标记
	for (size_t i = 0; i < contours.size(); i++)
	{
     
		drawContours(markers, contours, static_cast<int>(i), Scalar(static_cast<int>(i) + 1), 2, 8, hierarchy, 0, Point(0, 0));
	}
	//绘制背景标记
	circle(markers, Point(5, 5), 3, Scalar(255), -1);
	//更改类型显示标记(扩大10000倍)
	markers.convertTo(markers, CV_8U);
	//namedWindow("Markers", WINDOW_AUTOSIZE);
	//imshow("Markers", markers * 10000);
	//改回类型进行分水岭操作
	markers.convertTo(markers, CV_32S);
	//执行分水岭分割
	watershed(strawberry_only, markers);
	//分割后的图形
	Mat mark;
	markers.convertTo(mark, CV_8U);
	//imshow("Markers_v1", mark);
	//取反,即将背景置为黑色
	bitwise_not(mark, mark);
	//namedWindow("Watershed", WINDOW_AUTOSIZE);
	//imshow("Watershed", mark);
	//生成随机颜色
	vector<Vec3b> colors;
	for (size_t i = 0; i < contours.size(); i++)
	{
     
		int b = theRNG().uniform(0, 256);
		int g = theRNG().uniform(0, 256);
		int r = theRNG().uniform(0, 256);
		colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
	}
	//为轮廓填充颜色
	Mat dst = Mat::zeros(markers.size(), CV_8UC3);
	for (int i = 0; i < markers.rows; i++)
	{
     
		for (int j = 0; j < markers.cols; j++)
		{
     
			int index = markers.at<int>(i, j);
			if (index > 0 && index <= static_cast<int>(contours.size()))
			{
     
				dst.at<Vec3b>(i, j) = colors[index - 1];
			}
		}
	}
	OutputArray = dst;
	return 0;
}

(8)下极值点法绘制草莓质心、生长方向:

/**
* @brief 绘制草莓重心;下极值点法绘制草莓生长方向
* @param InputOutputArray 输入初始图像,输出位姿图像
* @param InputArray 草莓区域
* @return 0 成功
*/
int orientation(Mat& InputOutputArray, Mat InputArray)
{
     
	/*灰度化*/
	Mat gray;
	cvtColor(InputArray, gray, COLOR_BGR2GRAY);
	//namedWindow("gray", WINDOW_AUTOSIZE);
	//imshow("gray", gray);
	/*腐蚀*/
	Mat element1 = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat er;
	erode(gray, er, element1);
	//namedWindow("erode", WINDOW_AUTOSIZE);
	//imshow("erode", er);
	/*寻找草莓分开后的轮廓*/
	vector<vector<Point>> contours_strawberry;
	vector<Vec4i> hierarchy_strawberry;
	findContours(er, contours_strawberry, hierarchy_strawberry, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
	/*草莓重心+生长方向*/
	for (int i = 0; i < contours_strawberry.size(); i++)
	{
     
		drawContours(InputOutputArray, contours_strawberry, i, Scalar(0, 255, 255), 2, 8, hierarchy_strawberry, 0, Point());//在原图上绘制草莓轮廓
		Mat tmp(contours_strawberry.at(i));
		Moments moment = moments(tmp, false);
		if (moment.m00 != 0)//除数不能为0
		{
     
			/*寻找重心坐标并绘制*/
			int x = cvRound(moment.m10 / moment.m00);//计算重心横坐标
			int y = cvRound(moment.m01 / moment.m00);//计算重心纵坐标
			circle(InputOutputArray, Point(x, y), 5, Scalar(235, 191, 0), -1);//绘制实心圆
			cout << "-> 第" << i + 1 << "个草莓" << endl;
			cout << "重心: " << Point(x, y) << endl;
			/*寻找生长方向*/
			int minyx = contours_strawberry[i][0].x;//当前轮廓上极值点横坐标赋初值
			int minyy = contours_strawberry[i][0].y;//当前轮廓上极值点纵坐标赋初值
			int maxyx = contours_strawberry[i][0].x;//当前轮廓下极值点横坐标赋初值
			int maxyy = contours_strawberry[i][0].y;//当前轮廓下极值点纵坐标赋初值
			for (int j = 0; j < contours_strawberry[i].size(); j++)//遍历轮廓数据
			{
     
				if (minyy > contours_strawberry[i][j].y)//如果上极值点纵坐标小于当前纵坐标
				{
     
					minyy = contours_strawberry[i][j].y;//将当前纵坐标赋值给上极值点纵坐标
					minyx = contours_strawberry[i][j].x;//将当前横坐标赋值给上极值点横坐标
				}
				if (maxyy < contours_strawberry[i][j].y)//如果下极值点纵坐标大于当前纵坐标
				{
     
					maxyy = contours_strawberry[i][j].y;//将当前纵坐标赋值给下极值点纵坐标
					maxyx = contours_strawberry[i][j].x;//将当前横坐标赋值给下极值点横坐标
				}
			}
			//cout << "上极值点: " << Point(minyx, minyy) << endl;
			//cout << "下极值点: " << Point(maxyx, maxyy) << endl;
			circle(InputOutputArray, Point(maxyx, maxyy), 5, Scalar(0, 255, 0), -1);//绘制当前轮廓下极值点
			//circle(src, Point(minyx, minyy), 5, Scalar(255, 255, 0), -1);
			/*延长生长方向线段*/
			if (maxyx != x)//斜率不为∞时
			{
     
				double k = (maxyy - y) / (maxyx - x);//由当前轮廓下极值点和重心计算斜率
				double b = y - k * x;//计算直线纵向偏移
				double x1 = (minyy - 30 - b) / k;//计算当前轮廓上极值点纵坐标对应于直线上的横坐标
				arrowedLine(InputOutputArray, Point(maxyx, maxyy),
					Point(x1, minyy - 30), Scalar(255, 0, 0), 2, LINE_AA);//绘制生长方向线段(带箭头)
			}
			else//斜率为∞时
			{
     
				arrowedLine(InputOutputArray, Point(maxyx, maxyy),
					Point(x, minyy - 30), Scalar(255, 0, 0), 2, LINE_AA);//绘制生长方向线段(带箭头)
			}
		}
	}
	return 0;
}

(9)拟合椭圆绘制草莓质心、生长方向:

int orientation_ell(Mat& InputOutputArray, Mat InputArray)
{
     
	/*灰度化*/
	Mat gray;
	cvtColor(InputArray, gray, COLOR_BGR2GRAY);
	//namedWindow("gray", WINDOW_AUTOSIZE);
	//imshow("gray", gray);
	/*腐蚀*/
	Mat element1 = getStructuringElement(MORPH_RECT, Size(3, 3));
	Mat er;
	erode(gray, er, element1);
	//namedWindow("erode", WINDOW_AUTOSIZE);
	//imshow("erode", er);
	/*寻找草莓分开后的轮廓*/
	vector<vector<Point>> contours_strawberry;
	vector<Vec4i> hierarchy_strawberry;
	findContours(er, contours_strawberry, hierarchy_strawberry, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
	unsigned int cnt = 0;//计数用
	Mat cimage = Mat::zeros(InputOutputArray.size(), CV_8UC3);
	/*草莓重心+生长方向*/
	for (int i = 0; i < contours_strawberry.size(); i++)
	{
     
		drawContours(InputOutputArray, contours_strawberry, i, Scalar(0, 255, 255), 2, 8, hierarchy_strawberry, 0, Point());//在原图上绘制草莓轮廓
		Mat tmp(contours_strawberry.at(i));
		Moments moment = moments(tmp, false);
		if (moment.m00 != 0)//除数不能为0
		{
     
			cnt++;
			double k, b;//用于直线方程计算
			/*寻找重心坐标并绘制*/
			int x = cvRound(moment.m10 / moment.m00);//计算重心横坐标
			int y = cvRound(moment.m01 / moment.m00);//计算重心纵坐标
			circle(InputOutputArray, Point(x, y), 5, Scalar(235, 191, 0), -1);//绘制实心圆
			cout << "-> 第" << i + 1 << "个草莓" << endl;
			cout << "重心: " << Point(x, y) << endl;
			/*寻找生长方向*/
			//作轮廓的椭圆拟合并作椭圆外接矩形
			RotatedRect r = fitEllipse(contours_strawberry.at(i));
			//获取矩形中心点
			Point2f ct = r.center;
			//输出重心坐标
			cout << "c" << cnt << "=" << ct << endl;
			//获取矩形偏移角度
			double orientation = r.angle;
			//偏移角度转为弧度制
			double orientation_rads = orientation * CV_PI / 180;
			//输出偏移角度值
			cout << "angle" << cnt << "=" << orientation << endl;
			cout << "angle" << cnt << "=" << orientation_rads << endl;
			//画拟合的椭圆
			ellipse(cimage, r, Scalar(0, 0, 255), 1, LINE_AA);
			//获取矩形端点
			Point2f vertices[4];
			r.points(vertices);
			//画椭圆的外接矩形
			for (int j = 0; j < 4; j++)
			{
     
				line(cimage, vertices[j], vertices[(j + 1) % 4], Scalar(0, 255, 0));
			}
			drawContours(cimage, contours_strawberry, (int)i, Scalar::all(255), 1, 8);

			//由偏移角求斜率(均为弧度制)
			k = tan(CV_PI / 2 + orientation_rads);
			//k = tan(theta*CV_PI / 180);(Hu矩方法求斜率)
			//求直线与y轴的交点
			b = y - k * x;
			//取直线上的两点
			Point p1((x - 20), k*(x - 20) + b);
			Point p2((x + 30), k*(x + 30) + b);
			//画出该直线(即主轴)
			line(InputOutputArray, p1, p2, Scalar(0, 255, 0), 2, LINE_AA);
			line(cimage, p1, p2, Scalar(0, 255, 0), 2, LINE_AA);
		}
	}
	return 0;
}

你可能感兴趣的:(OpenCV,opencv)