凸包:
凸包(Convex Hull)是一个计算几何(图形学)中常见的概念。简单来说,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它是能包含点集中所有点的。理解物体形状或轮廓的一种比较有用的方法便是计算一个物体的凸包,然后计算其凸缺陷(convexity defects)。
判断:如果在集合A内连接任意两个点的直线段都在A的内部,则称集合A是凸形的。
直观的理解,就是一个多边型,没有凹的地方,如图所示:
名词解释:
如上图所示,黑色的轮廓线为convexity hull(凸包), 而convexity hull与手掌之间的部分为convexity defects(凸包缺陷). 每个convexity defect区域有四个特征量:起始点(startPoint),结束点(endPoint),距离convexity hull最远点(farPoint),最远点到convexity hull的距离(depth)。
凸包检测原理:
思路:Graham扫描的思想和Jarris步进法类似,也是先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点。
操作步骤大概可分为两步
第一步:排序
第二步:扫描
步骤:
1.把所有点放在二维自然直角坐标系中(注意与图片像素坐标系区分),则纵坐标最小的点一定是凸包上的
点,如图中的P0。
2.那么以P0作为坐标参考点,如上图所示:
计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前
面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由定义可以知道,结果中第一个点 P1
和最后一个点 P8 一定是凸包上的点。
3.以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面,并令P2为当前点.
4.根据栈顶的前两个点作出有向直线,记PX-1->PX ,看当前点是在有向直线 L 的右边还是左边。
如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。
5.栈顶的那个元素不是凸包上的点,把栈顶元素出栈,当前点不变,执行步骤4。
6.当前点是凸包上的点,把它压入栈,执行步骤7。
7.检查当前的点是不是步骤3那个结果的最后一个元素:当前点是最后一个元素的话就结束,如果不是的话就将下一个点作为新的当前点,返回步骤4。
动图过程如下:
静态求解过程如下:
API:
凸包检测:
凸包缺陷:
代码示例:
1.程序会首先根据滑动块位置生成坐标值随机的彩色点,然后利用convexHull,对由这些点链接起来的图形求凸包。
#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int pointCount = 50;
void ConvexHull_demo(int pos, void* userdata);
int main()
{
namedWindow("Convex", WINDOW_AUTOSIZE);
createTrackbar("pointCount", "Convex", &pointCount, 200, ConvexHull_demo);
ConvexHull_demo(pointCount, 0);
imshow("Convex", img);
waitKey(0);
}
void ConvexHull_demo(int pos, void* userdata)
{
Mat img=Mat::zeros(500,500,CV_8UC3);
RNG rng(getTickCount());
//随机生成目标点并绘制
vector<Point> points;
for (int i = 0; i < pos; i++)
{
Point point;
point.x = rng.uniform(cvRound(img.cols / 4), cvRound(img.cols * 3 / 4));
point.y = rng.uniform(cvRound(img.rows / 4), cvRound(img.rows * 3 / 4));
circle(img, point, 2, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA);
points.push_back(point);
}
//凸包检测
vector<Point> hull;
convexHull(points, hull, false);
//凸包绘制
for (int i = 0; i < hull.size(); i++)
{
line(img, hull[i], hull[(i + 1)%hull.size()], Scalar(255, 0, 0), 2, LINE_AA);
}
imshow("Convex", img);
}
效果展示:
2.首先对博主的手部图像进行轮廓检测,然后以轮廓中的点作为点集,找到其凸包。
#include "pch.h"
#include <iostream>
#include "opencv2/opencv.hpp"
using namespace std;
using namespace cv;
int thresholdval = 127;
int thresholdmax = 255;
int mode = 3;
void Demo_Contours(int pos, void* userdata);
Mat blursrc;
int main()
{
//待检测图像
Mat src = imread("F:\\visual studio\\Image\\hand4.jpg");
if (src.empty())
{
cout << "Can't load the image" << endl;
return -1;
}
resize(src, src, Size(), 0.5, 0.5);
imshow("src", src);
//转化为灰度图
Mat graysrc;
cvtColor(src, graysrc, COLOR_BGR2GRAY);
//高斯模糊
GaussianBlur(graysrc, blursrc, Size(5, 5), 3, 3);
namedWindow("dst", WINDOW_AUTOSIZE);
createTrackbar("Threshold", "dst", &thresholdval, thresholdmax, Demo_Contours);
createTrackbar("mode", "dst", &mode, 3, Demo_Contours);
Demo_Contours(0, 0);
waitKey(0);
}
void Demo_Contours(int pos, void* userdata)
{
//阈值化
Mat bin;
threshold(~blursrc, bin, thresholdval, thresholdmax, CV_THRESH_BINARY);
imshow("bin", bin);
//查找轮廓
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(bin, contours, hierarchy, mode, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
RNG rng(getTickCount());
Mat dst = Mat::zeros(blursrc.size(), CV_8UC3);
vector<vector<Point>> hull(contours.size());
//遍历每个轮廓,寻找其凸包并绘制
for (int i = 0; i < contours.size(); i++)
{
convexHull(contours[i], hull[i], false);
Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, i, color, 2, LINE_AA, hierarchy);
color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, hull, i, color, 2, LINE_AA);
}
imshow("dst", dst);
}
效果如下: