射线法通过从点向右引水平射线并统计与多边形边的交点数判断位置:奇数次相交在内部,偶数次在外部;需严格处理顶点穿越(仅当下端点y严格小于点y且上端点y大于点y时计数)和水平边(忽略)。

射线法(Ray Casting)是最实用的判断方式
绝大多数实际项目里,用射线法判断点是否在多边形内部既快又稳,不需要多边形凸性前提,也不依赖浮点高精度运算。核心思路是:从目标点向右水平引一条射线,统计它与多边形各边的交点数;奇数次相交 → 点在内部,偶数次(含 0)→ 在外部。
注意边界情况:射线恰好穿过顶点或与边重合时容易误判。标准做法是只将「严格跨过上端点、不包含下端点」的边计入交点——即对每条边 p[i]→p[i+1],仅当 y 坐标满足 min(p[i].y, p[i+1].y) 且交点横坐标 > <code>px 时才计数。
示例关键逻辑片段(C++):
bool pointInPolygon(const vector<Point>& poly, const Point& p) {
int n = poly.size();
bool inside = false;
for (int i = 0; i < n; ++i) {
int j = (i + 1) % n;
double y1 = poly[i].y, y2 = poly[j].y;
double x1 = poly[i].x, x2 = poly[j].x;
if (((y1 > p.y) != (y2 > p.y)) &&
(p.x < (x2 - x1) * (p.y - y1) / (y2 - y1) + x1))
inside = !inside;
}
return inside;
}使用 std::complex 做绕数法(Winding Number)要小心精度
绕数法理论上更鲁棒,尤其对自相交多边形或点紧贴边界的场景,但它依赖角度累加,浮点误差会随顶点数增长而放大。C++ 标准库没提供现成绕数函数,有人会用 std::complex<double> 表示向量并调用 arg() 算局部转角,但 arg() 在接近 π 或 -π 处有跳变风险,直接累加会导致绕数错误。
立即学习“C++免费学习笔记(深入)”;
真正可靠的绕数实现应避免 arg(),改用叉积符号判断转向,并用整数计数器累加 ±1。例如:
- 对每条边
p[i]→p[i+1],计算向量v1 = p[i] - p和v2 = p[i+1] - p - 若
v1 × v2 > 0且v1 ⋅ v2 < 0(逆时针跨过正 x 轴),绕数 +1 - 若
v1 × v2 < 0且v1 ⋅ v2 < 0(顺时针跨过),绕数 -1
最终绕数 ≠ 0 表示点在内部(含边界穿透情形)。但这套逻辑比射线法多 3–4 倍运算,一般只在必须区分「内/外/边界」且多边形结构复杂时启用。
OpenCV 的 pointPolygonTest() 可直接用,但要注意返回值含义
如果你项目已依赖 OpenCV,cv::pointPolygonTest() 是最省事的选择。它返回三类值:positive 表示点在内部(值为到边界的带符号距离),0.0 表示在边上,negative 表示外部。
注意两个易错点:
- 输入多边形点序列必须是
std::vector<cv::Point2f>或cv::Mat,不能是int坐标——否则可能因隐式转换导致精度丢失和误判 - 函数默认第三个参数设为
false时只返回 -1/0/1;设为true才返回精确距离,但计算开销明显上升 - OpenCV 内部用的是优化过的射线法,对退化边(零长度)、共线三点等做了过滤,比手写版本更耐糙
点在边界上的判定取决于需求,别默认当作「内部」
几乎所有通用算法(包括上面提到的三种)默认把边界视为「外部」或单独分类。比如射线法中,点恰好落在某条边上时,交点统计可能因浮点误差漏掉一次,结果返回 false;绕数法可能返回非零但极小的绕数值;pointPolygonTest() 明确返回 0.0。
如果你的业务要求「边界也算内部」,不能简单用 >= 0 判断,而应:
- 先跑一次主判断(如射线法)
- 再对每条边调用点到线段距离函数(如
distToSegment(p, poly[i], poly[j]) <= eps) - 任一距离 ≤ ε 就认为在边界上,按需合并结果
ε 选多少很关键:太小(如 1e-9)在大坐标系下会失效;太大(如 1.0)可能吞掉本该算外部的点。建议设为多边形包围盒对角线长度的 1e-6 倍,并在预处理时统一做坐标归一化。


















