大伙肯定都会用到这个的~  

golang python c++ 版本都已经弄到~ 有感兴趣的可以找我

golang 和py的是自己计算的,  c++ 是用的opencv自带的方法

py应该也有只是还没去实验, go就不研究关于cv的了, 个人感觉没价值, 谁用go做 不喜勿喷, 之前还有想用js统治全世界的呢 , 极端份子恕不接待

好了说说++里的  pointPolygonTest

方法说明

C++: double pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

说明

contour – 输入findContour提取到的边缘.

pt – 需要检测的点.

measureDist – 为真,则计算检测点到边缘的距离,为负值在外部,0在边上,正值在内部。为假,则返回-1(在contour外部)、0(在contour上)、1(在contour内部。

核心算法是 pnpoly方法~~ 发现我们公司也是用这个做的

int pnpoly(int nvert, float *vertx, float *verty, float testx, float testy)
{
  int i, j, c = 0;
  for (i = 0, j = nvert-1; i < nvert; j = i++) {
    if ( ((verty[i]>testy) != (verty[j]>testy)) &&
     (testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
       c = !c;
  }
  return c;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

点与多边形关系_解决方案

 

首先,参数nvert 代表多边形有几个点。浮点数testx, testy代表待测试点的横坐标和纵坐标,*vertx,*verty分别指向储存多边形横纵坐标数组的首地址。

我们注意到,每次计算都涉及到相邻的两个点和待测试点,然后考虑两个问题:

1. 被测试点的纵坐标testy是否在本次循环所测试的两个相邻点纵坐标范围之内?即

verty[i] <testy < verty[j]

或者

verty[j] <testy < verty[i]

2. 待测点test是否在i,j两点之间的连线之下?看不懂后半短if statement的朋友请自行在纸上写下i,j两点间的斜率公式,要用到一点初中解析几何和不等式的知识范畴,对广大码农来说小菜一碟。

然后每次这两个条件同时满足的时候我们把返回的布尔量取反。

这个表达式的意思是说,随便画个多边形,随便定一个点,然后通过这个点水平划一条线,先数数看这条横线和多边形的边相交几次,(或者说先排除那些不相交的边,第一个判断条件),然后再数这条横线穿越多边形的次数是否为奇数,如果是奇数,那么该点在多边形内,如果是偶数,则在多边形外。详细的数学证明这里就不做了,不过读者可以自行画多边形进行验证。

这个表达式的意思是说,随便画个多边形,随便定一个点,然后通过这个点水平划一条线,先数数看这条横线和多边形的边相交几次,(或者说先排除那些不相交的边,第一个判断条件),然后再数这条横线穿越多边形的次数是否为奇数,如果是奇数,那么该点在多边形内,如果是偶数,则在多边形外。

 

点与多边形关系_人工智能_02

为了方便阐述,以某个图为例,以点为中心分成四个象限:

该算法假定输入的d多边形是连续的所有点,判断条件

1)verty[j]和verty[i]不能同时全部大于或者全部小于该店的纵坐标,这条保证了verty[j]或者verty[i]与testy相等或者小于testy,并且另一个比testy大(即一条边穿过x轴只计算一次相交)。

2)用点斜式保证交点X轴在测试点向右的射线上。testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i])。这条的理解可如下:

知道两点verty[j]和verty[i],坐标(x1,y1),(x2,y2), 斜率k = (y2 - y1)/(x2 - x1),b = (y1x2-y2x1)/(x2-x1)得到多边形与x轴相交的直线为 y=k∗x+b,要满足x<(x2-x1)*(y-y1)/(y2-y1) + x1

如下图,多边形与x轴相交有如下6种情况,我们希望原点在直线的“左边”(如下图4,5,6),如果交于负半轴,需要判断的点在该直线“右边”,则不满足条件

点与多边形关系_解决方案_03

考虑到vertx[j]和vertx[i]相等情况,但是条件一已经保证verty[j]和verty[i]不等,所以将x-y轴交换转换成x = 1/k + b的式子

x=(y−b)/k

x=(y−y1x2−y2x1x2−x1)/(x2−x1y2−y1)

x=(y(x2−x1)−(y1x2−y2x1))/(y2−y1)

x=(y(x2−x1)−y1∗x2+y2∗x1+x1∗y1−x1∗y1)/(y2−y1)

x=(y(x2−x1)−y1∗x2+x1∗y1)/(y2−y1)+x1

x=(x2−x1)(y−y1)/(y2−y1)+x1

y¯=(x2−x1)(x¯−y1)/(y2−y1)+x1

转换完后,判断更为简单,我们只需要满足t原点在直线下边。如有直线y = kx + b,只需要满足y < kx+b,即 x<(x2−x1)(y−y1)/(y2−y1)+x1则为条件二的判断 ,当多边形的边穿过x负半轴不能满足该条件!

OpenCV源码不复杂,思想也是上面的思想。只是判断的写法略有不同,没用两点式而是用的向量叉乘

opencv路径opencv/opencv/modules/imgproc/src/geometry.cpp Line95:

double cv::pointPolygonTest( InputArray _contour, Point2f pt, bool measureDist )

1.在或者Vi−1或者Vi在x轴的时候,且另一个端点( 或者Vi或者Vi−1 )在第四象限,用叉乘(v−v0)¯×(ip−v0)¯判断与(Vi−Vi−1)与(V0−Vi−1) 是否共线且在正半轴(注意源码有个判断v.y < v0.y 的操作),如果共线,横线穿越多边形的次数count不变,否则+1

2.其他情况直接判断点是否在多边形边上

点与多边形关系_解决方案_04

点与多边形关系_斜率_05

 

相交正半轴情况

/*
*ip 需要判断是否在多边形内部的点
*v0, v 是多边形的连续的顶点
*cnt是contour,不是计数
*在边上 return  0
*在内部 return  1
*在外部 return -1
*/
if( !is_float && !measureDist && ip.x == pt.x && ip.y == pt.y )
    {
        // the fastest "purely integer" branch
        Point v0, v = cnt[total-1];

        for( i = 0; i < total; i++ )
        {
            v0 = v;
            v = cnt[i];

            if( (v0.y <= ip.y && v.y <= ip.y) ||
               (v0.y > ip.y && v.y > ip.y) ||
               (v0.x < ip.x && v.x < ip.x) )
            {
                if( ip.y == v.y && (ip.x == v.x || (ip.y == v0.y &&
                    ((v0.x <= ip.x && ip.x <= v.x) || (v.x <= ip.x && ip.x <= v0.x)))) )
                    return 0;
                continue;
            }

            int64 dist = static_cast<int64>(ip.y - v0.y)*(v.x - v0.x)
                       - static_cast<int64>(ip.x - v0.x)*(v.y - v0.y);
            if( dist == 0 )
                return 0;
            /*
             *看着是解决多边形与x轴交与该点左边
             */
            if( v.y < v0.y )
                dist = -dist;
            counter += dist > 0;
        }

        result = counter % 2 == 0 ? -1 : 1;
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.

这里在细说一下

判断一个点是否在多边形内部 - 射线法思路

比如说,我就随便涂了一个多边形和一个点,现在我要给出一种通用的方法来判断这个点是不是在多边形内部(别告诉我用肉眼观察……)。

点与多边形关系_测试点_06

首先想到的一个解法是从这个点做一条射线,计算它跟多边形边界的交点个数,如果交点个数为奇数,那么点在多边形内部,否则点在多边形外。

点与多边形关系_人工智能_07

这个结论很简单,那它是怎么来的?下面就简单讲解一下。

首先,对于平面内任意闭合曲线,我们都可以直观地认为,曲线把平面分割成了内、外两部分,其中“内”就是我们所谓的多边形区域。

点与多边形关系_人工智能_08

基于这一认识,对于平面内任意一条直线,我们可以得出下面这些结论:

直线穿越多边形边界时,有且只有两种情况:进入多边形或穿出多边形。 

在不考虑非欧空间的情况下,直线不可能从内部再次进入多边形,或从外部再次穿出多边形,即连续两次穿越边界的情况必然成对。 

直线可以无限延伸,而闭合曲线包围的区域是有限的,因此最后一次穿越多边形边界,一定是穿出多边形,到达外部。 

点与多边形关系_人工智能_09

现在回到我们最初的题目。假如我们从一个给定的点做射线,还可以得出下面两条结论:

如果点在多边形内部,射线第一次穿越边界一定是穿出多边形。 

如果点在多边形外部,射线第一次穿越边界一定是进入多边形。 

点与多边形关系_解决方案_10

把上面这些结论综合起来,我们可以归纳出:

当射线穿越多边形边界的次数为偶数时,所有第偶数次(包括最后一次)穿越都是穿出,因此所有第奇数次(包括第一次)穿越为穿入,由此可推断点在多边形外部。 

点与多边形关系_人工智能_11

 

当射线穿越多边形边界的次数为奇数时,所有第奇数次(包括第一次和最后一次)穿越都是穿出,由此可推断点在多边形内部。 

点与多边形关系_人工智能_12

 

到这里,我们已经了解了这个解法的思路,大家可以试着自己写一个实现出来。


不知道大家思考得怎么样,有没有遇到一些不好处理的特殊情况。今天就来讲讲射线法在实际应用中的一些问题和解决方案。

1点在多边形的边上

前面我们讲到,射线法的主要思路就是计算射线穿越多边形边界的次数。那么对于点在多边形的边上这种特殊情况,射线出发的这一次,是否应该算作穿越呢?

点与多边形关系_测试点_13

看了上面的图就会发现,不管算不算穿越,都会陷入两难的境地——同样落在多边形边上的点,可能会得到相反的结果。这显然是不正确的,因此对这种特殊情况需要特殊处理。

2点和多边形的顶点重合

点与多边形关系_测试点_14

这其实是第一种情况的一个特例。

3射线经过多边形顶点

射线刚好经过多边形顶点的时候,应该算一次还是两次穿越?这种情况比前两种复杂,也是实现中的难点,后面会讲解它的解决方案。

点与多边形关系_人工智能_15

4射线刚好经过多边形的一条边

这是上一种情况的特例,也就是说,射线连续经过了多边形的两个相邻顶点。

点与多边形关系_解决方案_16

解决方案:

1判断点是否在线上的方法有很多,比较简单直接的就是计算点与两个多边形顶点的连线斜率是否相等,中学数学都学过。

2点和多边形顶点重合的情况更简单,直接比较点的坐标就行了。

3顶点穿越看似棘手,其实我们换一个角度,思路会大不相同。先来回答一个问题,射线穿越一条线段需要什么前提条件?没错,就是线段两个端点分别在射线两侧。只要想通这一点,顶点穿越就迎刃而解了。这样一来,我们只需要规定被射线穿越的点都算作其中一侧。

点与多边形关系_测试点_17

如上图,假如我们规定射线经过的点都属于射线以上的一侧,显然点D和发生顶点穿越的点C都位于射线Y的同一侧,所以射线Y其实并没有穿越CD这条边。而点C和点B则分别位于射线Y的两侧,所以射线Y和BC发生了穿越,由此我们可以断定点Y在多边形内。同理,射线X分别与AD和CD都发生了穿越,因此点X在多边形外,而射线Z没有和多边形发生穿越,点Z位于多边形外。

解决了第三点,这一点就毫无难度了。根据上面的假设,射线连续经过的两个顶点显然都位于射线以上的一侧,因此这种情况看作没有发生穿越就可以了。由于第三点的解决方案实际上已经覆盖到这种特例,因此不需要再做特别的处理。