机器人视觉系统 作业
识别下图中草莓轮廓,计算质心、生长方向。
|
① OpenCV 4.2.0
② Visual Studio 2017
获取图像中草莓部分的二值图。
读取源图像的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={ θ,B≤Gπ−θ,B>G,θ=arccos{ 2(R−G)2+(R−B)(G−B)(R−G)+(R−B)}
S = 1 − 3 R + G + B min ( R , G , B ) S=1-\large\frac{3}{R+G+B}\min(R,G,B) S=1−R+G+B3min(R,G,B)
I = 1 − R + G + B 3 I=1-\large\frac{R+G+B}{3} I=1−3R+G+B
得到草莓HSI颜色空间的图像如图1-1所示。
|
|
|
|
为了获取草莓区域的二值图,使用OpenCV的threshold()函数对草莓HSI图像的S通道进行阈值分割。由于上一步得到的数据是归一化状态的,在处理数据时需要乘以255将其扩展到 [ 0 , 255 ] [0,255] [0,255]。
Mat binary;
threshold(S * 255, binary, 85, 255, THRESH_BINARY_INV);
只含草莓部分的二值图如图1-2所示。
|
在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所示。
|
利用下述公式对归一化后的图像进行灰度化处理,计算得到灰度图像中各点像素值。[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={ (r−g)×255,(r>g)0, (r≤g)
得到草莓区域的灰度图像如图1-4所示。
|
为进一步削弱光照影响,采用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,(Vgray≥Tf×k)0, (Vgray<Tf×k or Vgray<T0)
得到草莓区域的二值图像如图1-5所示。
|
上述方法得到的草莓二值图包含许多空洞,为了方便识别,需要尽可能地将空洞补全。补空洞的步骤为:
(1)用闭操作连通区域;
(2)使用findContours()函数,选择RETR_CCOMP模式,找出草莓轮廓中的内轮廓(即空洞的轮廓);
(3)判断每个轮廓的大小,只有当其小于90像素时才进行填充。
填充后的草莓二值图如图2-1所示。
|
|
由于RGB空间处理得到的草莓二值图(方法2)不方便用分水岭分割(主要是watershed()的第一个参数不好取),这里只对HSI空间得到的二值图(方法1)做分水岭分割,步骤如下:[2]
(1)对填充空洞后的图像使用distanceTransform()函数作距离变换,并归一化;
(2)对归一化后的距离图使用的threshold()函数作阈值处理,得到种子区域;(这种方法其实很难确定种子区域的阈值范围)
(3)使用watershed()函数执行分水岭分割;
(4)对每个草莓区域上色。
得到结果如图3-1所示。
|
|
以下均对图3-2进行处理。
由草莓轮廓区域的一阶矩可以获得草莓的中心点,作为质心。
(1)遍历各草莓的轮廓数据,找出y值最小值点作为草莓的下极值点;
(2)连接下极值点和中心点(质心),得到草莓的生长方向(姿态)。
|
(1)将草莓轮廓拟合为椭圆;
(2)作椭圆的外接矩形;
(3)求矩形偏移角,转换为直线斜率;
(4)绘制生长方向。
|
[1]纪超. 温室果蔬采摘机器人视觉信息获取方法及样机系统研究[D].中国农业大学,2014.
[2]OpenCV官方文档示例
(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;
}
(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;
}