【opencv】凸包算法之寻找凸包convexHull

好久不见. 提交于 2019-12-07 17:14:27

1.概述

凸包(Convex Hull)是一个计算几何(图形学)中的概念,在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。
X的凸包可以用X内所有点(x1, x2….xn)的线性组合来构造。在二维欧几里得空间中,凸包可以想象为一条刚好包着所有点的橡皮圈,用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。常见的有Graham’s Scan法和Jarvis步进法

2.原理

2.1Graham’s Scan法

Graham扫描法通过不断在凸壳中加入新的点和去除影响凸性的点,最后形成凸包。算法的主体由两部分组成,先是排序,然后扫描。

(1)点集排序
为了得到加入新点的顺序,Graham扫描法的第一步是对点集排序,对杂乱的点集进行梳理,这也是这种算法能够得到更高效的根本原因。排序的方法有极角坐标排序(极角序)和直角坐标排序(水平序)两种方法。在实现的时候,直角坐标排序比较方便。
对于极角序,首先选取一个参考点,一般选取横坐标最小的点作为参考点,如果有多个这样的点就从这些点钟选取纵坐标最小的点。如下图:

这样就决定了参考点的性质:点集中任意两点和参考点锁成的倒角为锐角。
极角排序以参考点为极角坐标系原点,根据上述参考点性质,可以设所有点的极角均在(-90,90]之间,排序完成后如下图所示:

(2)栈扫描

Graham扫描用的栈,其核心思想是按照拍好的序一次加入新点得到的边,边的寻找符合左旋判定。如果和上一条边成左转关系就压栈继续,如果右转就出栈直到和栈顶两点的边成左转关系,压栈继续。其栈扫描过程如下图所示:



2.2Jarvis步进法

其算法流程如下:
1.照横坐标最小的点(如有一样则取相同点纵坐标更小的点)
2.从这点开始卷包裹,照最靠近外侧的点(通过叉积比较)
3.遍历所有点,直到重新找到起点,退出。

3.OpenCV API函数

opencv提供了convexHull()函数来查找图像中物体的凸包,起函数定义如下:

void cv::convexHull (   InputArray  points,
                        OutputArray     hull,
                        bool    clockwise = false,
                        bool    returnPoints = true 
)
  • 1
  • 2
  • 3
  • 4
  • 5

参数解释
points:输入的二维点集,Mat类型数据即可
hull:输出参数,用于输出函数调用后找到的凸包
clockwise:操作方向,当标识符为真时,输出凸包为顺时针方向,否则为逆时针方向。
returnPoints:操作标识符,默认值为true,此时返回各凸包的各个点,否则返回凸包各点的指数,当输出数组时std::vector时,此标识被忽略。


代码如下:

#include<opencv2\opencv.hpp>
#include<opencv2\imgproc\imgproc.hpp>
#include<iostream>
using namespace cv;
using namespace std;
void on_ThreshChange(int,void*);
int  ThreshValve = 100;
Mat srcImage,srcImage_gray,srcImage_blur,srcImage_thresh;

int main()
{     
         /***********************求随机点的凸包*****************
	//Mat image(600, 600, CV_8UC3, Scalar::all(255));
	//imshow("image", image);
	//RNG &rng = theRNG();
	//while (1)
	//{
	//	char key;
	//	int count = (unsigned)rng % 100 ;  //产生随机数的个数不大于10
	//	cout << "cout为:" << count << endl;
	//	

	//	vector<Point>points;        //vector容器存放Point坐标
	//	for (int i = 0; i < count; i++)
	//	{
	//		Point point;
	//		point.x = rng.uniform(image.cols / 4, image.rows * 3 / 4);
	//		point.y = rng.uniform(image.cols / 4, image.rows * 3 / 4);
	//		points.push_back(point);      //在vector尾部加入一个点向量
	//		cout<<"vector容器中存放的个点坐标"<<point<<endl;

	//	}
	//	cout << "基于Mat类的vector" << Mat(points) << endl; //以矩阵的形式存储


	//	cout << points.size() << endl;    //产生随机点的个数
	//	vector<int>hull;
	//	convexHull(points,
	//		hull,
	//	    true);           //凸包检测,第三个参数为操作标识符,当为true时,输出凸包为顺时针方向,否则为逆时针方向
	//	cout << hull.size() << endl;      //凸包的边数

	//	image = Scalar::all(0);   //Scale::all(0)表示所以点设为0,即全部为黑色的窗口等价于下面
	//	//Mat image(600, 600, CV_8UC3, Scalar::all(255));
	//	for (int i = 0; i < count; i++)
	//	{
	//		circle(image, points[i], 3,
	//			Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),//用随机数产生随机颜色
	//			2, 8, 0);
	//	}

	//	int hullcount = (int)hull.size();
	//	Point point0 = points[hull[hullcount - 1]];

	//	for (int i = 0; i < hullcount; i++)
	//	{
	//		Point point = points[hull[i]];
	//		line(image, point0, point, Scalar(255, 0, 0), 5, 8, 0);
	//		point0 = point;

	//	}
	//	imshow("凸包检测图", image);
	//	key = (char)waitKey();
	//	if (key == 27 || key == 'q' || key == 'Q')
	//	{
	//		break;

	//	}

	//}


/****************************求图像的凸包************************/
	
	srcImage = imread("C:\\Users\\蓝月亮\\Desktop\\常用\\图片\\111111.jpg");
	resize(srcImage, srcImage, Size(400, 300),0, 0, 1);
	if (!srcImage.data)
	{
		printf("图片读入失败");
		return false;
	}	

	cvtColor(srcImage, srcImage_gray, CV_BGR2GRAY);
	blur(srcImage_gray, srcImage_blur, Size(3, 3), Point(-1, -1), 4);
	namedWindow("阈值窗口", WINDOW_AUTOSIZE);
	createTrackbar("阈值", "阈值窗口",&ThreshValve, 255,on_ThreshChange);     //回调函数名,即调节的参数被哪个参数使用就调用哪个参数
		on_ThreshChange(0, 0);

	imshow("阈值窗口", srcImage);
	waitKey(0);
	return 0;
	}


void on_ThreshChange(int, void*)
{
vector<vector<Point>>contours;    //
vector<Vec4i>hierarchy;           //

threshold(srcImage_gray, srcImage_thresh, ThreshValve, 255, THRESH_BINARY);
Mat drawImage = Mat::zeros(srcImage_thresh.size(), CV_8UC3);           //画轮廓和画凸包的图像格式必须是二值化的



	/***********遍历每个轮廓并寻找其凸包***********/
findContours(srcImage_thresh, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>hull(contours.size());//初始化向量的大小
for (int i = 0; i < contours.size(); i++)
{

	convexHull(contours[i], hull[i], false);       //求凸包
	
	}


/****************画轮廓并画凸包****************/
for (int i = 0; i < hull.size(); i++)
{
	    Point point;  
		drawContours(drawImage, contours, i, Scalar(255, 0, 0), 3, 8, hierarchy);             //画轮廓
		drawContours(drawImage, hull, i, Scalar(255, 0, 255), 1, 8, vector<Vec4i>(), 0, Point());    // 画凸包
		
}
    imshow("阈值图", srcImage_thresh);
	imshow("drawImage", drawImage);

}



from:http://blog.csdn.net/keith_bb/article/details/70194073

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!