要理解凸包(凸壳)检测,首无要知道什么是凸包(凸壳),凸包(凸壳)是指如果在集合A内连接任意两个点的直线段都在A的内部,则称集合A是凸形的。简单点理解,就是一个多边型,没有凹的地方。凸包(凸壳)能包含点集中所有的点,凸包检测常应用在物体识别、手势识别及边界检测等领域。
OpenCV中提供了用于对物体轮廓凸包进行检测的函数convexHull(),对形状的凸包缺陷分析的函数convexityDefects(),下面分别介绍这两个函数。
函数convexHull()原型如下:
void convexHull( InputArray points,
OutputArray hull,
bool clockwise = false,
bool returnPoints = true );
官方文档对参数意义的说明如下:
points – Input 2D point set, stored in std::vector or Mat.
hull – Output convex hull. It is either an integer vector of indices or vector of points. In the first case, the hull elements are 0-based indices of the convex hull points in the original array (since the set of convex hull points is a subset of the original point set). In the second case, hull elements are the convex hull points themselves.
clockwise – Orientation flag. If it is true, the output convex hull is oriented clockwise. Otherwise, it is oriented counter-clockwise. The usual screen coordinate system is assumed so that the origin is at the top-left corner, x axis is oriented to the right, and y axis is oriented downwards.
orientation – Convex hull orientation parameter in the old API, CV_CLOCKWISE or CV_COUNTERCLOCKWISE.
returnPoints – Operation flag. In case of a matrix, when the flag is true, the function returns convex hull points. Otherwise, it returns indices of the convex hull points. When the output array is std::vector, the flag is ignored, and the output depends on the type of the vector: std::vector<int> implies returnPoints=true, std::vector<Point> implies returnPoints=false.
翻译如下:
points:输入二维点集(一般为轮廓点集),这些点集被存储在容器vector或Mat中,在下面的源码中,我是强制转化为了Mat类型。
hull:凸包点集输出。它的数据类型要么为整型向量,要么为点集向量。如果是整型向量,那么存储的只是索引,索引的对象是你输入的二维点集,之所以可以这样做,是因为函数检测到的凸包是输入点集的子集。如果是点集向量,那么这些点集就是凸包本身的点。
clockwise:凸包方向的标志位。如果是true,那么是基于顺时针方向,如果是false,那么是基于反时针方向。我测试了下两种结果下输出凸包,结果是没有任何区别。如下面两幅图所示:
returnPoints:函数输出类型,如果OutputArray是一个矩阵变量(MAT),那么returnPoints==true时,输出点坐标,否则,输出坐标索引,索引的对象是输入的二维点集。当然,如果你的OutputArray本来就是一个向量了,那这个参数的值会被无视掉,因为向量在初始化的时候是需要规定好类型的,要么为int型,要么为point型。
下面是使用函数convexHull()进行凸包检测的示例代码:
代码中用到的图片下载链接:https://pan.baidu.com/s/1uAClJ3xklpSGE1iA-7py5g?pwd=h237
//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理需求也可联系博主
//图像处理技术交流QQ群 271891601
//OpenCV版本:3.0
//VS版本:2013
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcGary = imread("F:/material/images/P0044-hand-02.jpg",0);
imshow("srcGary", srcGary);
// 阈值化操作
Mat threMat;
int thresh = 128;
threshold(srcGary, threMat, thresh, 255, THRESH_BINARY);
// 轮廓检测
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(threMat, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
// 绘制轮廓
Mat contours_img(srcGary.size(), CV_8U, Scalar(0));
drawContours(contours_img, contours, -1, Scalar(255), 1);
imshow("contours_img", contours_img);
//凸包检测
vector<vector<Point> > pointHull(contours.size());
vector<vector<int> > intHull(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
// 输出结果为Point类型的凸包检测
convexHull(Mat(contours[i]), pointHull[i], false);
// 输出结果为int类型的凸包检测
convexHull(Mat(contours[i]), intHull[i], false);
}
//绘制凸包
Mat convex_hull_img = Mat::zeros(threMat.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(0, 0, 255);
drawContours(convex_hull_img, pointHull, i, color, 1, 8, vector<Vec4i>(), 0, Point());
}
imshow("convex_hull_img", convex_hull_img);
waitKey();
return 0;
}
运行结果如下:
原图如下:
原图的轮廓如下:
轮廓的凸包如下:
从上面的运行结果中我们也对凸包的概念有了直观的认识。
下面讲解函数convexHull():
在解释这个函数各参数的意义前,先要知道什么叫凸包(凸壳)的缺陷检测?要说明这个问题,配图说明是最好的方式,看了下面这幅图你就知道是怎么回事了!
上图中黑色双箭头部分所指就是红色线绘制的凸包的缺陷,大家可以结合图形仔细体会一下什么叫凸包缺陷。从图中我们可以看出,凸包缺陷在运算上就像是凸包减去轮廓的图形。
函数convexHull()原型如下
void convexityDefects( InputArray contour, InputArray convexhull, OutputArray convexityDefects );
三个参数说明如下:
contour---生成凸包的轮廓点集,一般就是轮廓检测函数findContours()的输出。
convexhull----函数convexHull()的输出,里面存储的是凸包信息,在这里只能是int类型(即vector<vector<int>>类型),而不能是vector<vector<Point>>类型。
convexityDefects ----类型为vector<vector<Vec4i>>类型,每一个凸包缺陷由若干个Vec4i来描述,每一个Vec4i由四个整型数据构成,这四个整型数据的名称分别为:start_index, end_index, farthest_pt_index, fixpt_depth。具体意义我用下面这张图来说明。
上面的黄色数字标的①②③④⑤⑥六个部分实际上就描述了手的轮廓凸包的六个凸包缺陷。这里以⑥为例说明start_index, end_index, farthest_pt_index, fixpt_depth的意义。
start_index:凸包缺陷的起点,上图⑥中的A或B就代表起点,A或B都位于轮廓上。
end_index: 凸包缺陷的终点,上图⑥中的A或B就代表终点,A或B都位于轮廓上。
farthest_pt_index:凸包缺陷中轮廓与凸包相距的最远点,上图⑥中的C点就是凸包缺陷中轮廓与凸包相距的最远点,C点也位于轮廓上。
fixpt_depth:上图中C点距离凸包的距离。它以8位定点小数来近似表示,所以如果要换成浮点数,应该除以256,即the floating-point value=fixpt_depth/256.0。为什么有这个换算式?可参看下面这篇博文:
https://blog.csdn.net/niaolianjiulin/article/details/82764511
明白了上面的参数意义后,接下来我们把上面的函数convexHull()的示例程序完善,得到函数convexHull()的示例代码如下:
代码中用到的图片下载链接:https://pan.baidu.com/s/1uAClJ3xklpSGE1iA-7py5g?pwd=h237
//博主微信/QQ 2487872782
//有问题可以联系博主交流
//有图像处理需求也可联系博主
//图像处理技术交流QQ群 271891601
//OpenCV版本:3.0
//VS版本:2013
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat srcGary = imread("F:/material/images/P0044-hand-02.jpg",0);
imshow("srcGary", srcGary);
// 阈值化操作
Mat threMat;
int thresh = 128;
threshold(srcGary, threMat, thresh, 255, THRESH_BINARY);
// 轮廓检测
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(threMat, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
// 绘制轮廓
Mat contours_img(srcGary.size(), CV_8U, Scalar(0));
drawContours(contours_img, contours, -1, Scalar(255), 1);
imshow("contours_img", contours_img);
//凸包检测和凸包缺陷检测
vector<vector<Point> > pointHull(contours.size());
vector<vector<int> > intHull(contours.size());
vector<vector<Vec4i> > hullDefect(contours.size());
for (size_t i = 0; i < contours.size(); i++)
{
// 输出结果为Point类型的凸包检测
convexHull(Mat(contours[i]), pointHull[i], false);
// 输出结果为int类型的凸包检测
convexHull(Mat(contours[i]), intHull[i], false);
//凸包缺陷检测
convexityDefects(Mat(contours[i]), intHull[i], hullDefect[i]);
}
//绘制凸包及凸包缺陷
Mat convex_hull_img = contours_img.clone();
cvtColor(convex_hull_img, convex_hull_img, COLOR_GRAY2BGR);
for (size_t i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(0, 0, 255);
drawContours(convex_hull_img, pointHull, i, color, 1, 8, vector<Vec4i>(), 0, Point());
// 绘制缺陷
size_t count = contours[i].size();
if (count < 300) //小于300个点的轮廓略去
continue;
// 凸包缺陷迭代器设置
vector<Vec4i>::iterator iterDefects = hullDefect[i].begin();
// 遍历得到凸包缺陷的4个特征量并进行绘制
while (iterDefects != hullDefect[i].end())
{
Vec4i& v = (*iterDefects);
// 起始位置
int startidx = v[0];
Point ptStart(contours[i][startidx]);
// 终止位置
int endidx = v[1];
Point ptEnd(contours[i][endidx]);
// 内凸壳的最远的点缺陷
int faridx = v[2];
Point ptFar(contours[i][faridx]);
// 凸包之间的最远点
int depth = v[3] / 256;
if (depth > 10 && depth < 100)
{
line(convex_hull_img, ptStart, ptFar, CV_RGB(0, 255, 0), 2);
line(convex_hull_img, ptEnd, ptFar, CV_RGB(0, 255, 0), 2);
circle(convex_hull_img, ptStart, 4, Scalar(255, 0, 0), 2);//ptStart用蓝色
circle(convex_hull_img, ptEnd, 4, Scalar(255, 0, 128), 2);//ptEnd用紫色
circle(convex_hull_img, ptFar, 4, Scalar(128, 0, 255), 2);//ptFar用粉红色
}
iterDefects++;
}
}
imshow("convex_hull_img", convex_hull_img);
cv::waitKey();
return 0;
}
运行结果如下: