二维凸包convex hull之C++及OpenCV实现

来源:互联网 发布:如何将数据导入matlab 编辑:程序博客网 时间:2024/05/18 00:03

原文地址:http://blog.csdn.net/xizhibei/article/details/7430385

 

打算接下来好好研究下算法(很明显,算法才是王道啊),然后尽量用直观的方式输出,于是用OpenCV画图成了不二首选,各位看官接下来看到一堆“XXX之C++及OpenCV实现”之类的标题就别见怪了~


另外还有个打算,看到自己写的东西被别人拿去占为己有,不爽,开始贴版权了^_^。

本文出处:http://blog.csdn.net/xizhibei

============================================================

今天就是二维凸包,算法导论中文版584页说的就是凸包,现在,让我们来实现它。


话说,凸包在很多地方有着重要的作用,如手势识别,需要识别出手的轮廓的凸包,二维或者三维区域的边界等等。

而对于凸包算法,其中最有名的莫过于Graham扫描算法,它的复杂度为nlog(n),过程很优美,相信你看过运行过程你就会同样觉得了。

简单来说,这个算法的过程就是这样:

1.计算求得输入点x坐标最小(如果x相等,则比较y是不是最小)的点,作为第一个点

2.其它的点按照极角按顺时针排列,如果有共线的,取最近的那一个

3.依次将0,1,2三个坐标压入栈

4.对于剩余的点,i依次从3到最后,看次栈顶,栈顶,以及当前的i对应的点,如果是非左转动,那么弹栈

5.将i对应的点压栈,转到3

好了,现在开始实现:

下面是一些头文件/定义以及简单函数实现

其中值得一提的便是叉积,二维向量A和B的叉积就是A.x * B.y - A.y *B.x ,结果为正就表明A经过顺时针到达B(当然,小于180),反之则逆时针,凭借这种算法,就可以按极角排序了。

[cpp] view plaincopyprint?
  1. #include <cv.h>  
  2. #include <cxcore.h>  
  3. #include <highgui.h>  
  4.   
  5. #define POINT_NUM 100  
  6. #define WIDTH 800  
  7. #define HEIGHT 800  
  8. #define zero 1e-12  
  9.   
  10. #define DIS(a,b) sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y))  
  11. #define SGN(x) (fabs(x)<zero?0:(x>0?1:-1))  
  12. #define CROSS(a,b,c) ((b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x))//叉积,用来判断旋转方向  
  13. #define CMP(a,b) (a.x < b.x || SGN(a.x - b.x)==0 && a.y < b.y)//坐标的比较  
  14. #define RAND (rand() % 100000 / 100000.0)//产生0-1之间的浮点数  
  15.   
  16. CvPoint p[POINT_NUM];  
  17. CvPoint* hull_p = new CvPoint[POINT_NUM];//用来储存凸包上的点  
  18. int hull_size = 0;  
  19. IplImage* img;  
  20. //下面简单实现了栈操作  
  21. inline void push(CvPoint* S,CvPoint pt)  
  22. {  
  23.     S[hull_size++] = pt;  
  24. }  
  25. inline CvPoint pop(CvPoint* S)  
  26. {  
  27.     return S[--hull_size];  
  28. }  
  29.   
  30. inline void swap(int x,int y)  
  31. {  
  32.     CvPoint pt = p[x];  
  33.     p[x] = p[y];  
  34.     p[y] = pt;  
  35. }  
  36.   
  37. inline bool compare(CvPoint a,CvPoint b,CvPoint c)  
  38. {  
  39.     int tmp = SGN(CROSS(a,b,c));  
  40.     if(tmp != 0)  
  41.         return tmp > 0;  
  42.     else//如果两点共线的话,就需要比较远近了  
  43.         return DIS(a,b) < DIS(a,b);  
  44. }  
  45. //快排,极角的排序  
  46. void sort(int l,int r)  
  47. {  
  48.     CvPoint tmp = p[(l + r) / 2];  
  49.     int i = l;  
  50.     int j = r;  
  51.     do  
  52.     {  
  53.         while(compare(p[0],p[i],tmp))i++;  
  54.         while(compare(p[0],tmp,p[j]))j--;  
  55.         if(i <= j)  
  56.         {  
  57.             swap(i,j);  
  58.             i++;  
  59.             j--;  
  60.         }  
  61.     }while(i <=j);  
  62.     if(i < r)sort(i,r);  
  63.     if(j > l)sort(l,j);  
  64. }  


然后就是重点了:

这里也跟上篇文章一样,为了看清运行过程,加上了画线函数,还有100微秒的延时,这样就能看清了

[cpp] view plaincopyprint?
  1. void draw_hull()  
  2. {  
  3.     int min = -1;  
  4.     for(int j = 0;j < POINT_NUM;j++)//找出x坐标最小的,作为起始点  
  5.     {  
  6.         if(min == -1 || CMP(p[j],p[min]))  
  7.             min = j;  
  8.     }  
  9.     if(min != 0)  
  10.         swap(0,min);  
  11.   
  12.     sort(1,POINT_NUM - 1);//其他点排序  
  13.   
  14.     push(hull_p,p[0]);  
  15.     push(hull_p,p[1]);  
  16.     push(hull_p,p[2]);  
  17.     for(int i = 3;i < POINT_NUM;i++)  
  18.     {  
  19.         while(CROSS(hull_p[hull_size - 2],hull_p[hull_size - 1],p[i]) < 0)//非左转  
  20.         {  
  21.             pop(hull_p);  
  22.             cvLine(img,hull_p[hull_size - 1],p[i],cvScalar(255,0,255));//为了看清运行过程而加的  
  23.             cvShowImage("Image",img);  
  24.             cvWaitKey(100);  
  25.         }  
  26.         cvLine(img,hull_p[hull_size - 1],p[i],cvScalar(255,0,255));  
  27.         push(hull_p,p[i]);  
  28.     }  
  29.   
  30.     cvPolyLine(img,&hull_p,&hull_size,1,1,cvScalar(0,0,255),2);//最终画出凸包  
  31. }  

显示图片:

[cpp] view plaincopyprint?
  1. void show_outcome()  
  2. {  
  3.     cvSet(img,cvScalar(255,255,255));  
  4.     CvScalar color = cvScalar(0,0,0);  
  5.     for(int i = 0;i < POINT_NUM;i++)//画出每个点,十字  
  6.     {  
  7.         int x = p[i].x;  
  8.         int y = p[i].y;  
  9.         cvLine(img,cvPoint(x - 5,y),cvPoint(x + 5,y),color,2);  
  10.         cvLine(img,cvPoint(x,y - 5),cvPoint(x,y + 5),color,2);  
  11.     }  
  12.     draw_hull();  
  13.     cvShowImage("Image",img);  
  14.     cvWaitKey(0);  
  15. }  

然后是主函数,这次的随机点生成换了个方式,点随机分布在左边的椭圆以及右边的圆上,不再象上次那样随机产生在矩形区域内了:

[cpp] view plaincopyprint?
  1. int main()  
  2. {  
  3.     img = cvCreateImage(cvSize(WIDTH,HEIGHT),IPL_DEPTH_8U,3);  
  4.       
  5.     srand((unsigned)time(NULL));  
  6.     double phase = RAND * CV_PI * 2.0;  
  7.     for (int i = 0; i < POINT_NUM / 2; i++) {  
  8.         double r =  RAND * WIDTH / 4.0;  
  9.         double theta = RAND * 1.5 * CV_PI + phase;  
  10.         p[i] = cvPoint( WIDTH /4 + r * cos(theta), HEIGHT / 2 + 2 * r * sin(theta) );//椭圆  
  11.     }  
  12.     phase = RAND * CV_PI * 2.0;  
  13.     for (int i = 0; i < POINT_NUM / 2; i++) {  
  14.         double r =  RAND * WIDTH / 4.0;  
  15.         double theta = RAND * 1.5 * CV_PI + phase;  
  16.         p[i + POINT_NUM / 2] = cvPoint(WIDTH / 4 * 3 +  r * cos(theta), HEIGHT / 2 +  r * sin(theta));//圆  
  17.     }  
  18.   
  19.     show_outcome();  
  20.     delete [] hull_p;  
  21.     return 0;  
  22. }  

接下来看看效果:如果想看动态过程的画就去运行程序看吧~


参考:

http://www.cnblogs.com/Booble/archive/2011/03/10/1980089.html

0 0
原创粉丝点击