前言:
笔者目前在校本科大二,有志于进行计算机视觉、计算机图形学方向的研究,准备系统性地、扎实的学习一遍OpenCV的内容,故记录学习笔记,同时,由于笔者同时学习数据结构、机器学习等知识,会尽量根据自己的理解,指出OpenCV的应用,并在加上自己理解的前提下进行叙述。
若有不当之处,希望各位批评、指正。
本篇学习内容:
1.寻找凸包
2.使用多边形将轮廓包围
1.寻找凸包
1.1 凸包
摘自博客:https://www.cnblogs.com/aiguona/p/7232243.html
凸包(Convex Hull)是一个计算几何(图形学)中的概念。
在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。
X的凸包可以用X内所有点(X1,…Xn)的线性组合来构造.
很多数学学科中都会提到类似的概念,例如“凸区域”、“闭包”等。在仅考虑二维点集的情况下,这个概念可以很直观:给一个点集S,尽可能少地在S中选k个点,将这k个点连起来,所围成的多边形为一凸多边形,并且所有S中的点都在这个凸多边形内。
我这样叙述的定义可能不太严谨,各位也可以想象点集S为二维平面上的一个个钉子,凸包就是将一个橡皮筋撑开,然后放手,橡皮筋围成的凸多边形的顶点集合就是凸包了。
另外,博客https://www.cnblogs.com/aiguona/p/7232243.html中还介绍了一种计算凸包的方法,Graham扫描法,非常清晰易懂。
1.2 寻找凸包
用convexHull()寻找凸包
OpenCV使用Sklansky算法来寻找凸包。
void cv::convexHull (
InputArray points,
OutputArray hull,
bool clockwise = false,
bool returnPoints = true
)
对参数进行介绍:
points: 输入的点集,可以是Mat形式,也可以是Point型的vector
hull: 输出的凸包,可以输出int型的vector,或者Point型的vector。对于第一种情况,由于凸包是输入集的子集,所以输出的vector保存的是输入集的下标指引;对于第二种情况,输出的是凸包的所有点。
clockwise: 如果为true,输出的凸包是顺时针的,如果是false,输出的凸包是逆时针的。前提是假定坐标轴的X轴指向右,Y轴指向上。
returnPoints: 如果hull那边输出到一个int型的vector或者Point型的vector,那不用管这个参数。如果hull那边输出到一个Mat,则这个参数设为true时,返回的Mat储存的是凸包的点;设为false时,返回的是输入集的下标指引。
下面给出一个例子,这个例子的写法借鉴于OpenCV的一个例子:https://docs.opencv.org/4.x/d7/d1d/tutorial_hull.html
Mat src = imread("E:/program/image/k.jpg");
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
blur(src_gray, src_gray, Size(5, 5));
Mat img;
Canny(src_gray, img, 30, 60);
vector<vector<Point>> contours;
findContours(img, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
vector<vector<Point>> hull(contours.size());
for (int i = 0; i < contours.size(); i++) {
convexHull(contours[i], hull[i]);
}
Mat dst = Mat::zeros(img.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++) {
drawContours(dst, contours, i, Scalar(255, 255, 0));
drawContours(dst, hull, i, Scalar(255, 255, 0));
}
imshow("dst", dst);
waitKey();
2.使用多边形将轮廓包围
OpenCV提供了多种类似的函数,如:
boundingRect()计算灰度图像的点集或非零像素的上-右(up-right)边界矩形
minAreaRect()返回可旋转的最小面积的包围矩形
minEnclosingCircle()返回面积最小的包围圆形
approxPolyDP()返回以指定的精度近似多边形曲线(用另一个顶点较少的曲线/多边形逼近一条曲线或一个多边形,使它们之间的距离小于或等于指定的精度)
由于参数较少,我把函数声明一起放在下面:
Rect cv::boundingRect (InputArray array)
RotatedRect cv::minAreaRect (InputArray points)
void cv::minEnclosingCircle (
InputArray points,
Point2f & center,
float & radius
)
void cv::approxPolyDP (
InputArray curve,
OutputArray approxCurve,
double epsilon,
bool closed
)
然后给出一个例子,来介绍怎么使用这些函数,以及怎么利用这些函数的返回值在输入图像上进行绘画。另外,approxPolyDP可以检测一个图像中存在的一些多边形轮廓,这里没有举相关的例子,读者可以参考《OpenCV3编程入门》。
RNG& rng = theRNG();
vector<Point> points;
Mat img = Mat::zeros(Size(600, 600), CV_8UC3);
for (int i = 0; i < 20; i++) {
Point p;
p.x = rng.uniform(img.cols / 4, img.cols * 3 / 4);
p.y = rng.uniform(img.rows / 4, img.rows * 3 / 4);
circle(img, p, 2, Scalar(255, 255, 255), 1, LINE_8, 0);
points.push_back(p);
}
RotatedRect box = minAreaRect(points);
Rect r = boundingRect(points);
Point2f center;
float radius;
minEnclosingCircle(points, center, radius);
Point2f v[4];
box.points(v);
for (int i = 0; i < 4; i++) {
line(img, v[i], v[(i + 1) % 4], Scalar(0, 0, 255), 1, LINE_AA);
}
rectangle(img, r, Scalar(0, 255, 0), 1, LINE_8, 0);
circle(img, center, radius, Scalar(255, 255, 0), 1, LINE_8, 0);
vector<Point> poly;
approxPolyDP(points, poly, 3, true);
Rect r1 = boundingRect(poly);
rectangle(img, r1, Scalar(0, 255, 255), 1, LINE_8, 0);
imshow("img", img);
waitKey();
注意,在寻找好多边形轮廓后,我又用boundingRect()来寻找轮廓的上-右边界矩形,这样画出来的矩形正好会和直接对点集进行boundingRect()后绘制的矩形重合。
参考文献:
- OpenCV官方文档:https://docs.opencv.org/4.x/
- 《OpenCV3编程入门》毛星云、冷雪飞等编著
- https://www.cnblogs.com/aiguona/p/7232243.html