【OpenCV】OpenCV基础教程(8)—— 几何形状的检测和拟合

8. 几何形状的检测和拟合

8.1 点集的最小外包

8.1.1 最小外包矩形

OpenCV提供如下函数:

cv::RotatedRect cv::minAreaRect(cv::InputArray points)

points:接收三种点集形式

​ 第一种:N×2的Mat类型,每一行代表一个点的坐标且数据类型只能是 CV_32S 或者 CV_32F;

​ 第二种:vector<Point>或者vector<Point2f>,即多个点组成的向量;

​ 第三种:N×1的双通道Mat类型

返回:输入点集points的最小外包旋转矩形(角度 中心 尺寸)

用法如下:

//点集(以第一种为例)
Mat points = (Mat_<float>(5, 2) << 1, 1, 5, 1, 1, 10, 5, 10, 2, 5);
//点集(以第二种为例)
vector<Point2f> points;
points.push_back(Point2f(1, 1));
points.push_back(Point2f(5, 1));
points.push_back(Point2f(1, 10));
points.push_back(Point2f(5, 10));
points.push_back(Point2f(2, 5));
//点集(以第三种为例)
Mat points = (Mat_<Vec2f>(5, 1) << Vec2f(1, 1), Vec2f(5, 1), Vec2f(1, 10), Vec2f(5, 10), Vec2f(2, 5));
//计算点集的最小外包旋转矩形
RotatedRect rRect = minAreaRect(points);
//打印旋转矩形的信息:
cout << "旋转矩形的中心:" << rRect.center << endl;	//[3,5.5]
cout << "旋转矩形的尺寸:" << rRect.size << endl;	//[9 x 4]
cout << "旋转矩形的角度:" << rRect.angle << endl;	//-90

rRect是RotatedRect类,不能直接输出rRect。可以通过以下方式来定义一个旋转矩阵:

//构造旋转矩形
RotatedRect rRect(Point2f(200,200),Point2f(90,150),-60);
8.1.2 旋转矩形的四个顶点

RotatedRect类只包含了旋转矩阵的中心、尺寸和角度信息,并没有矩形四个顶点的信息。

OpenCV提供以下函数来获取旋转矩阵的四个顶点:

void cv::boxPoints(cv::RotatedRect box, cv::OutputArray points)

box:输入旋转矩阵,为RotatedRect类

points:输出的四个顶点,为一个4行2列的单通道float类型的Mat

使用方法如下:

//构造旋转矩形
RotatedRect rRect(Point2f(200,200),Point2f(90,150),-60);
//计算旋转矩形的4个顶点,存储为一个4行2列的单通道float类型的Mat
Mat vertices;
boxPoints(rRect, vertices);
//打印4个顶点
cout << vertices << endl;

利用line函数绘制旋转矩形的方法:

//在黑色画板上画出该旋转矩形
Mat img=Mat::zeros(Size(400, 400), CV_8UC1);
for (int i = 0; i < 4; i++)
{
    //相邻的点
    Point p1 = vertices.row(i);
    int j = (i + 1) % 4;
    Point p2 = vertices.row(j);
    //画出直线
    line(img, p1, p2, Scalar(255), 3);
}
8.1.3 最小外包圆

点集的最小外包圆的OpenCV实现:

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

points:点集,Mat 或者 vector 类型,和 minAreaRect 一样

center:最小外包圆的圆心(输出)

radius:最小外包圆的半径(输出)

用法如下:

//点集
Mat points = (Mat_<float>(5, 2) << 1, 1, 5, 1, 1, 10, 5, 10, 2, 5);
//计算点集的最小外包圆
Point2f center;	//圆心
float radius;	//半径
minEnclosingCircle(points, center, radius);
//打印最小外包圆的信息:
cout << "圆心:" << center << endl;	//[3,5.5]
cout << "半径:" << radius << endl;	//5.07216
8.1.4 最小外包直立矩形

点集的最小外包直立矩形的OpenCV实现:

cv::Rect cv::boundingRect(cv::InputArray points)

points:接收三种点集形式

​ 第一种:N×2的Mat类型,每一行代表一个点的坐标且数据类型只能是 CV_32S 或者 CV_32F;

​ 第二种:vector<Point>或者vector<Point2f>,即多个点组成的向量;

​ 第三种:N×1的双通道Mat类型

返回:Rect类的最小外包矩形,形式类似于[5 x 10 from (1, 1)]

用法如下:

//点集
vector<Point2f> points;
points.push_back(Point2f(1, 1));
points.push_back(Point2f(5, 1));
points.push_back(Point2f(1, 10));
points.push_back(Point2f(5, 10));
points.push_back(Point2f(2, 5));
//计算点集的最小外包直立矩形
Rect rect = boundingRect(points);
//打印最小外包直立矩形
cout <<"最小外包矩形:"<< rect << endl;	//[5 x 10 from (1, 1)]
8.1.5 最小凸包

给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中的所有点,如下图所示:

image-20211020213356743

OpenCV定义的函数如下:

void cv::convexHull(cv::InputArray points, cv::OutputArray hull, bool clockwise = false, bool returnPoints = true)

points:输入点集是 vector 或者 Mat 类型

hull:构成凸包的点,类型为vector<Point>vector<Point2f>

clockwise:hull 中的点是按顺时针还是逆时针排列的

returnPoints:值为 true 时,hull 中存储的是坐标点;值为 false 时,存储的是这些坐标点在点集中的索引

用法如下:

//5行2列的单通道Mat
Mat points = (Mat_<float>(5, 2) << 1, 1, 5, 1, 1, 10, 5, 10, 2, 5);
//求点集的凸包
vector<Point2f> hull;
convexHull(points,hull);
//打印得到最外侧的点(凸包)
for (int i = 0; i < hull.size(); i++)
{
	cout << hull[i] <<",";
}
//打印结果
//[5,10],[1,10],[1,1],[5,1],
8.1.6 最小外包三角形
double cv::minEnclosingTriangle(InputArray points, CV_OUT OutputArray triangle)

points 只支持两种点集形式:𝑁 × 1 的双通道 Mat 或者vector<Point>vector<Point2f>不支持 𝑁 × 2 的单通道 Mat

triangle:是计算出的三角形的三个顶点,存储在 vector 向量中

返回的 double 值是最小外包三角形的面积。


可以用reshape函数,将N行2列单通道Mat转成N行1列双通道Mat:

cv::Mat cv::Mat::reshape(int cn, int rows = 0) const

cn:表示通道数(channels)。如果设为0,则表示保持通道数不变,否则则变为设置的通道数。

rows:表示矩阵行数。 如果设为0,则表示保持原有的行数不变,否则则变为设置的行数。


minEnclosingTriangle函数用法如下:

//5行2列的单通道Mat
Mat points = (Mat_<int>(5, 2) << 1, 1, 5, 1, 1, 10, 5, 10, 2, 5);
//转换为5行1列的双通道Mat
points = points.reshape(2, 5);
//存储三角形的三个顶点
vector<Point> triangle;
//点集的最小外包三角形
double area = minEnclosingTriangle(points, triangle);
cout << "三角形的三个顶点:";
for (int i = 0; i < 3; i++)
{
    cout << triangle[i] << ",";
}
cout << "最小外包三角形的面积:"<< area << endl;
//三角形的三个顶点: [9,1],[1,1],[1,19],
//最小外包三角形的面积:72

8.2 霍夫直线检测

直线方程可由以下方式表示(过程略,具体见书P307):
ρ = x cos ⁡ θ + y sin ⁡ θ \rho =x\cos \theta +y\sin \theta ρ=xcosθ+ysinθ
如果知道平面内的一条直线,那么可计算出唯一的 ρ \rho ρ θ \theta θ。换句话讲, x o y xoy xoy平面内的任意一条直线对应参数空间(也称霍夫空间) θ o ρ \theta o \rho θoρ中的一点 ( ρ , θ ) (\rho,\theta) (ρ,θ)

ρ \rho ρ是原点到直线的代数距离, θ \theta θ O N → \overrightarrow{ON} ON x x x轴的正方向(一二象限)或负方向(三四象限)的夹角。

image-20211021190841904

如果已知 x o y xoy xoy 平面内的一个点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0),则对应霍夫空间中的一条曲线 ρ = x 0 cos ⁡ θ + y 0 sin ⁡ θ \rho ={{x}_{0}} \cos \theta + y_0 \sin \theta ρ=x0cosθ+y0sinθ。据此,在 x o y xoy xoy平面内的多个点在霍夫空间内对应多条曲线。如果这几条曲线相交于同一点,那么这几个点在 x o y xoy xoy 平面内就是共线的。


OpenCV提供的霍夫直线检测函数如下:

void cv::HoughLines(cv::InputArray image, cv::OutputArray lines, double rho, double theta, int threshold, double srn = (0.0), double stn = (0.0), double min_theta = (0.0), double max_theta = (3.141592653589793116))

image:输入边缘图片,必须是单通道的Mat格式的二值图像

lines:vector<Vec2f>格式,包含 ρ \rho ρ θ \theta θ信息,不包含直线上的点

rho:以像素为单位的距离 r 的精度。一般情况下,使用的精度是 1

theta:角度 θ 的精度。一般情况下,使用的精度是CV_PI / 180,表示要搜索所有可能的角度

threshold:阈值。阈值越大 -> 检测越精准 + 速度越快 + 直线越少

使用方法如下:

Mat srcImage = imread("0.jpg");
//边缘检测
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//定义矢量结构存放检测出来的直线
vector<Vec2f> lines;
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
//lines是包含rho和theta的,而不包括直线上的点
//所以下面需要根据得到的rho和theta来建立一条直线
for (size_t i = 0; i < lines.size(); i++)
{
    float rho = lines[i][0]; 	//就是圆的半径r
    float theta = lines[i][1]; 	//就是直线的角度  
    double a = cos(theta), b = sin(theta);
    double x0 = a*rho, y0 = b*rho;
    Point pt1, pt2;
    pt1.x = cvRound(x0 + 1000 * (-b));	//cvRound:四舍五入
    pt1.y = cvRound(y0 + 1000 * (a));
	pt2.x = cvRound(x0 - 1000 * (-b));
    pt2.y = cvRound(y0 - 1000 * (a));
    line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, CV_AA);
    imshow("边缘检测后的图", midImage);
    imshow("最终效果图", dstImage);
}

标准的霍夫直线检测内存消耗比较大,执行时间比较长,基于这一点,概率霍夫直线检测随机地从边缘二值图中选择前景像素点,确定检测直线的两个参数,其本质上还是标准的霍夫直线检测。OpenCV 提供的函数:

void cv::HoughLinesP(cv::InputArray image, cv::OutputArray lines, double rho, double theta, int threshold, double minLineLength = (0.0), double maxLineGap = (0.0))

一般在直线检测中使用HoughLinesP而不是HoughLines

image:输入边缘图片,必须是单通道的Mat格式的二值图像

lines:vector<Vec4i>格式,包含直线上的点坐标

rho:以像素为单位的距离 r 的精度。一般情况下,使用的精度是 1

theta:角度 θ 的精度。一般情况下,使用的精度是CV_PI / 180,表示要搜索所有可能的角度

threshold:阈值。阈值越大 -> 检测越精准 + 速度越快 + 直线越少

minLineLength:线段的最小长度

maxLineGap:线段之间的最小距离

HoughLines不同的是,HoughLinesP得到lines的是含有直线上点的坐标的,所以下面进行划线时就不再需要自己求出两个点来确定唯一的直线了。

Mat srcImage = imread("2.jpg");
//边缘检测
Mat midImage, dstImage;
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//定义矢量结构存放检测出来的直线
vector<Vec4i> lines;
HoughLinesP(midImage, lines, 1, CV_PI / 180, 80, 50, 10);
//依次画出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
    Vec4i l = lines[i];
    line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, CV_AA);
    imshow("边缘检测后的图", midImage);
    imshow("最终效果图", dstImage);
}

8.3 霍夫圆检测

OpenCV函数如下:

void cv::HoughCircles(cv::InputArray image, cv::OutputArray circles, int method, double dp, double minDist, double param1 = (100.0), double param2 = (100.0), int minRadius = 0, int maxRadius = 0)

image:输入图像矩阵,需要是单通道灰度图片

circles:返回圆的信息,类型为vector<Vec3f>,每一个 Vec3f 都代表 (𝑥, 𝑦, radius),即圆心的横坐标、纵坐标、半径

method:现在只有CV_HOUGH_GRADIENT一个参数

dp:计数器的分辨率,一般设为1即可

minDist:圆心之间的最小距离,如果距离太小,则会产生很多相交的圆;如果距离太大,则会漏掉正确的圆

param1:Canny 边缘检测的双阈值中的高阈值,低阈值默认是它的一半

param2:最小投票数。它越小的话,就可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了

minRadius:需要检测圆的最小半径

maxRadius:需要检测圆的最大半径

用法如下:

Mat Image = imread(img_path);
Mat midImage, dstImage;
//转化边缘检测后的图为灰度图
cvtColor(Image, midImage, COLOR_BGR2GRAY);
//高斯滤波
GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);
//定义矢量结构存放检测出来的圆
vector<Vec3f> circles;
HoughCircles(midImage, circles, HOUGH_GRADIENT, 1, midImage.rows/5, 150, 100, 0, 0);
//依次画出每个圆
for (size_t i = 0; i < circles.size(); i++)
{
    Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));	//cvRound:四舍五入
    int radius = cvRound(circles[i][2]);
    //绘制圆心
    circle(Image, center, 3, Scalar(0, 255, 0), -1, 8, 0);
    //绘制圆轮廓
    circle(Image, center, radius, Scalar(255, 50, 0), 3, 8, 0);
}

8.4 轮廓

8.4.1 查找、绘制轮廓
1. 查找轮廓
void cv::findContours(cv::InputOutputArray image, cv::OutputArrayOfArrays contours, cv::OutputArray hierarchy, int mode, int method, cv::Point offset = cv::Point())

image:输入图像,必须为8位单通道二值图像。如果mode = RETR_CCOMP或RETR_FLOODFILL,输入图像可以是32位整型图像(CV_32SC1)

contours:检测到的轮廓,每个轮廓都是以点向量的形式进行存储即使用vector<Point>表示,对多个轮廓的描述用vector<vector<Point>>

hierarchy:可选的输出向量。每个轮廓contours[i]对应hierarchy中hierarchy[i] [0]~hierarchy[i] [3],分别表示后一个轮廓,前一个轮廓,父轮廓,内嵌轮廓的索引,如果没有对应项,则相应的hierarchy[i]设置为负数。

mode:轮廓检索模式

  • RETR_EXTERNAL:表示只检测最外层轮廓,对所有轮廓设置hierarchy[i] [2]=hierarchy[i] [3]=-1
  • RETR_LIST:提取所有轮廓,并放置在list中,检测的轮廓不建立等级关系
  • RETR_CCOMP:提取所有轮廓,并将轮廓组织成双层结构(two-level hierarchy),顶层为连通域的外围边界,次层为内层边界
  • RETR_TREE:提取所有轮廓并重新建立网状轮廓结构
  • RETR_FLOODFILL:官网没有介绍,应该是洪水填充法

method:轮廓近似方法

  • CHAIN_APPROX_NONE:获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1
  • CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息
  • CHAIN_APPROX_TC89_L1:使用Teh-Chinl链逼近算法中的一种
  • CHAIN_APPROX_TC89_KCOS:使用Teh-Chinl链逼近算法中的一种

offset:轮廓点可选偏移量,有默认值Point()

OpenCV中提供的另一种定义形式如下:

void cv::findContours (InputOutputArray image, OutputArrayOfArrays contours, int mode, int method, Point offset = Point())   
2. 绘制轮廓
void cv::drawContours(cv::InputOutputArray image, cv::InputArrayOfArrays contours, int contourIdx, const cv::Scalar &color, int thickness = 1, int lineType = 8, cv::InputArray hierarchy = noArray(), int maxLevel = 2147483647, cv::Point offset = cv::Point())

image:输入输出图像,Mat类型即可

contours:使用findContours检测到的轮廓数据,每个轮廓以点向量的形式存储,point类型的vector

contourIdx:绘制轮廓的索引,如果为负值则绘制所有输入轮廓

color:轮廓颜色

thickness:绘制轮廓所用线条粗细度,如果值为负值,则在轮廓内部绘制

lineTpye:线条类型(默认值为LINE_8)

  • LINE_4:4-邻域直线
  • LINE_8:8-邻域直线
  • LINE_AA:antialiased(抗锯齿)线条,效果好但是绘图慢

hierarchy:可选层次结构信息

maxLevel:用于绘制轮廓的最大等级

offset:可选轮廓偏移参数,用制定偏移量offset=(dx, dy)给出绘制轮廓的偏移量


findContoursdrawContours函数的使用方法如下:

//读取图像
Mat srcImage, grayImage, dstImage;
srcImage = imread("HappyFish.jpg");
//转换为灰度图
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
//定义矢量结构存放检测出来的轮廓及层次
vector<vector<Point>>contours;
vector<Vec4i>hierarchy;
//二值化
grayImage = grayImage > 100;
//检测轮廓
findContours(grayImage, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
//绘制轮廓
dstImage = Mat::zeros(grayImage.size(), CV_8UC3);
for (int i = 0; i < hierarchy.size(); i++)
{
    Scalar color = Scalar(rand() % 255, rand() % 255, rand() % 255);
    drawContours(dstImage, contours, i, color, CV_FILLED, 8, hierarchy);
}
8.4.2 图像目标的定位问题

对于图像目标的定位问题,一般的处理步骤如下:

第一步:对图像边缘检测或者阈值分割得到二值图,有时也需要对这些二值图进行形态学处理。

第二步:利用函数findContours寻找二值图中的多个轮廓。

第三步:对于通过第二步得到的多个轮廓,其中每一个轮廓都可以作为函数convexHullminAreaRect等的输入参数,然后就可以拟合出包含这个轮廓的最小凸包、最小旋转矩形等。


具体实现如下(以边缘检测为例):

//输入图像
Mat img = imread(argv[1], IMREAD_GRAYSCALE);
//第一步:边缘检测,得到边缘二值图(也可以是阈值分割等函数)
GaussianBlur(img, img, Size(3, 3), 0.5);
Mat binaryImg;
Canny(img, binaryImg, 50, 200);
imshow("显示边缘", binaryImg);
//第二步:边缘的轮廓
vector<vector<Point>> contours;
findContours(binaryImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE)
;
//第三步:对每一个轮廓进行拟合,这里用旋转矩形
int num = contours.size();	//轮廓的数量
for (int i = 0; i < num; i++)
{
    Rect rect = boundingRect(contours[i]);
    //筛选出面积大于10000的矩形
    if (rect.area() > 10000)
    {
        //在原图中画出外包矩形
        rectangle(img, rect, Scalar(255));
    } 
}
8.4.3 拟合函数
1. 多边形拟合

OpenCV提供的多边形拟合函数如下:

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

curve:输入曲线,数据类型可以为vector<Point>

approxCurve:输出折线,数据类型可以为vector<Point>

epsilon:判断点到相对应的折线段的距离的阈值。(距离大于此阈值则舍弃,小于此阈值则保留,epsilon越小,折线的形状越“接近”曲线。)

closed:曲线是否闭合的标志位。

使用方法如下:

//输入图片
Mat srcImg = imread("01.jpg");
imshow("src", srcImg);
//定义纯黑目标图像
Mat dstImg(srcImg.size(), CV_8UC3, Scalar::all(0));
//阈值分隔
cvtColor(srcImg, srcImg, CV_BGR2GRAY);
threshold(srcImg, srcImg, 200, 255, CV_THRESH_BINARY_INV);
//定义矢量结构存放检测出来的轮廓及层次
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
//检测轮廓
findContours(srcImg, contours, hierarcy, 0, CV_CHAIN_APPROX_NONE);
//定义矢量结构存放拟合出来的折线点集
vector<vector<Point>> contours_poly(contours.size());
//多边形拟合并在目标图像中绘制出来
for (int i = 0; i<contours.size(); i++)
{
    approxPolyDP(Mat(contours[i]), contours_poly[i], 15, true);
    drawContours(dstImg, contours_poly, i, Scalar(0, 255, 255), 2, 8);
}
2. 直线拟合

OpenCV提供的直线拟合函数如下:

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

points:用于拟合直线的输入点集,可以是二维点的Mat数组,也可以是二维点的vector。

line:输出的直线,对于二维直线而言类型为Vec4f,对于三维直线类型则是Vec6f,输出参数的前半部分给出的是直线的方向,而后半部分给出的是直线上的一点(即通常所说的点斜式直线)。

distType:距离类型,拟合直线时,要使输入点到拟合直线的距离的和最小化,可供选的距离类型如下所示:

  • DIST_L2
  • DIST_L1
  • DIST_L12
  • DIST_FAIR
  • DIST_WELSCH
  • DIST_HUBER

param:距离参数,跟所选的距离类型有关,值可以设置为0,fitLine函数本身会自动选择最优化的值

reps:用于表示拟合直线所需要的径向精度,通常情况下设定为1e-2。

aeps:用于表示拟合直线所需要的角度精度,通常情况下设定为1e-2。

用法如下:

//创建一个用于绘制图像的空白图
cv::Mat image = cv::Mat::zeros(480, 640, CV_8UC3);
//输入拟合点
std::vector<cv::Point> points;
points.push_back(cv::Point(48, 58));
points.push_back(cv::Point(105, 98));
points.push_back(cv::Point(155, 160));
points.push_back(cv::Point(212, 220));
points.push_back(cv::Point(248, 260));
points.push_back(cv::Point(320, 300));
points.push_back(cv::Point(350, 360));
points.push_back(cv::Point(412, 400));
//将拟合点绘制到空白图上
for (int i = 0; i < points.size(); i++)
{
    cv::circle(image, points[i], 5, cv::Scalar(0, 0, 255), 2, 8, 0);
}
//直线检测
cv::Vec4f line_para; 
cv::fitLine(points, line_para, cv::DIST_L2, 0, 1e-2, 1e-2);
std::cout << "line_para = " << line_para << std::endl;
//获取点斜式的点和斜率
cv::Point point0;
point0.x = line_para[2];
point0.y = line_para[3];
double k = line_para[1] / line_para[0];
//计算直线的端点(y = k * (x - x0) + y0)
cv::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;
//绘制直线
cv::line(image, point1, point2, cv::Scalar(0, 255, 0), 2, 8, 0);

另外,还可以考虑使用RANSAC算法拟合直线,思路如下:

  1. 随机在数据集中选出小的子集(对于直线,一般选2)
  2. 计算得到符合这个子集合的最好模型
  3. 找到接近符合这个模型的数据集
  4. 迭代一定次数,选出最好的模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aj6VgTdk-1636463028092)(https://gitee.com/xyn_hit/picgo_xyn/raw/master/img/20140602185854718)]

3. 椭圆拟合

OpenCV提供的椭圆拟合函数如下:

cv::RotatedRect cv::fitEllipse(cv::InputArray points)

points:为二维点集(至少六个点)

返回值:RotatedRect类型,包含中心点坐标,以及矩形的长度和宽度还有矩形的偏转角度

用法如下:

Mat src=imread("/home/zdg/图片/OIP (3).jpeg");
Mat src_gray,src_canny,dst;
imshow("原图",src);
//图像预处理
cvtColor(src,src_gray,COLOR_RGB2GRAY);
GaussianBlur(src_gray,dst,Size(5,5),10,10);
threshold(dst,src_canny,248,100,4);
imshow("canny检测",src_canny);
//检测轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(src_canny, contours, hierarcy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
//检测最小包围椭圆
vector<RotatedRect> box(contours.size());
for(int i = 0; i < contours.size(); i++)
{
    box[i] = fitEllipse(contours[i]);
    //绘制椭圆
    ellipse(src, box[i], Scalar(0, 0, 255), 2);
}

另外,可以取fitEllipse返回的RotatedRect类型数据的宽度和高度的平均值来得到圆的直径的近似值,这样就可以实现圆的拟合

8.4.4 轮廓的周长和面积

计算点集所围区域的周长:

double cv::arcLength(cv::InputArray curve, bool closed)

curve:输入点集。一般有三种形式:vector<Point>、𝑛 × 2 的单通道 Mat(一行代表一个坐标点)、𝑛 × 1 的双通道 Mat

closed:点集是否首尾相接

计算点集所谓区域的面积:

double cv::contourArea(cv::InputArray contour, bool oriented = false)

contour:输入点集。一般有三种形式:vector<Point>、𝑛 × 2 的单通道 Mat(一行代表一个坐标点)、𝑛 × 1 的双通道 Mat

oriented:面向区域标识符(默认值 false)。若为 true,该函数返回一个带符号的面积值,正负取决于轮廓的方向(顺时针还是逆时针)。若为 false,表示以绝对值返回。

用法如下:

//点集的构造方式1
vector<Point> points;
points.push_back(Point2f(0, 0));
points.push_back(Point2f(50, 30));
points.push_back(Point2f(100, 0));
points.push_back(Point2f(100, 100));
//点集的构造方式2
Mat points =(Mat_<float >(4, 2) << 0, 0, 50, 30, 100, 0, 100,100);
//点集的构造方式3
Mat points = (Mat_<Vec2f >(4, 1) << Vec2f(0, 0), Vec2f(50, 30), Vec2f(100, 0),
Vec2f(100, 100));
//计算点集所围区域的周长和面积
double length1 = arcLength(points, false);	//首尾不相连
double length2 = arcLength(points, true);	//首尾相连
double area = contourArea(points);
8.4.5 点和轮廓的位置关系

判断点和点集的关系的函数:

double cv::pointPolygonTest(cv::InputArray contour, cv::Point2f pt, bool measureDist)

contour:输入的点集

pt:坐标点

measureDist:是否计算坐标点到轮廓的距离

返回值:+1 代表 pt 在点集围成的轮廓内;0 代表 pt 在点集围成的轮廓上;-1 代表 pt 在点集围成的轮廓外

使用方法如下:

//点集围成的轮廓
vector<Point> contour;
contour.push_back(Point(0, 0));
contour.push_back(Point(50, 30));
contour.push_back(Point(100, 100));
contour.push_back(Point(100, 0));
//画出点集围成的轮廓
Mat img = Mat::zeros(Size(130, 130), CV_8UC1);
int num = contour.size();//点的数量
for (int i = 0; i < num-1; i++)
{
	//用直线依次连接轮廓中相邻的点
	line(img, contour[i], contour[i + 1], Scalar(255), 1);
}
//首尾相连
line(img, contour[0], contour[num - 1], Scalar(255), 1);
//标注点的位置
circle(img, Point(80, 40), 3, Scalar(255),CV_FILLED );
circle(img, Point(50, 0), 3, Scalar(255), CV_FILLED);
circle(img, Point(40, 80), 3, Scalar(255), CV_FILLED);
//点在轮廓内
double dist1 = pointPolygonTest(contour, Point2f(80, 40), true);
cout << "dist1:" << dist1 << endl;
//点在轮廓上
double dist2 = pointPolygonTest(contour, Point2f(50, 0), true);
cout << "dist2:" << dist2 << endl;
//点在轮廓外
double dist3 = pointPolygonTest(contour, Point2f(40, 80), true);
cout << "dist3:" << dist3 << endl;
8.4.6 轮廓的凸包缺陷

对凸包的缺陷检测在判断物体形状等方面发挥着很重要的作用,与凸包缺陷类似的还有如矩形度、椭圆度、圆度等,它们均是衡量目标物体形态的度量。

image-20211022201055493

OpenCV提供的检测凸包缺陷的函数如下:

void cv::convexityDefects(cv::InputArray contour, cv::InputArray convexhull, cv::OutputArray convexityDefects)

contour:轮廓(有序的点集),形式为vector<Point>

convexhull:函数convexHull的输出值,形式为vector<int>,代表轮廓 contour 中哪些点构成了轮廓的凸包

convexityDefects:返回的凸包缺陷的信息,形式为vector<Vec4i>,每一个Vec4i代表一个缺陷,它的四个元素依次代表:缺陷的起点、终点、最远点的索引及最远点到凸包的距离。

使用方法如下:

//轮廓
vector<Point> contour;
contour.push_back(Point(20, 20));
contour.push_back(Point(50, 70));
contour.push_back(Point(20, 120));
contour.push_back(Point(120, 120));
contour.push_back(Point(100, 70));
contour.push_back(Point(120, 20));
//计算轮廓的凸包
vector<int> hull;
convexHull(contour, hull, false, false);
//计算凸包缺陷
vector<Vec4i> defects;
convexityDefects(contour, hull, defects);
//打印凸包缺陷
for (int i = 0; i < defects.size(); i++)
{
	cout << defects[i] << endl;
}
/*
[3,5,4,5120]
[0,2,1,7680]
*/
  • 11
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Quentin_HIT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值