2D几何库#
Visionflow 在 visionflow::geometry 命名空间下提供了功能完善的2D几何库,涵盖了基本几何模型、空间关系判断、几何计算和几何变换等功能。开发者可以使用这些功能快速实现几何相关的开发任务,不需要自己从底层实现数学细节。
Note
详细的接口定义和相关功能细节请参考 2D几何库接口文档。
几何模型#
Visionflow 提供的几何模型既包含 OGC Simple Feature Access 标准中定义的常用类型——以点坐标表示的点、线串、多边形及其集合,也包含直线、线段、矩形、圆、椭圆等基于集合或属性描述的几何模型。在某些场景下,后者相比纯坐标表示更高效且使用更便捷。
两类模型可以通过 visionflow::geometry::convert() 或相应的成员函数相互转换。例如可以将矩形转换为多边形的边界环,也可以将多边形转换为其外接矩形。
#include "visionflow/geometry/geometry.hpp"
namespace vflow = visionflow;
vflow::geometry::Rect2f rect{{0, 0}, {10, 5}};
auto ring = rect.to_ring();
auto rect_2 = ring.bounding_box();
import visionflow as vflow
import vflow.geometry as geo
rect = geo.Rect2f(geo.Point2f(0, 0), geo.Size2f(10, 5))
ring = rect.to_ring()
rect_2 = ring.bounding_box()
using visionflow.geometry;
var rect = new Rect2f(new Point2f(0, 0), new Size2f(10, 5));
var ring = rect.to_ring();
var rect_2 = ring.bounding_box();
Note
visionflow::geometry::Ring2f 是一个特殊的几何模型,表示一个闭合的线串。它作为 visionflow::geometry::Polygon2f 的基本组成部分,定义了多边形的外边界和内边界(洞)。
对于无法直接转换的模型关系,可以通过一些算法得到近似的表示。例如基于属性描述的圆和椭圆可以通过采样 visionflow::geometry::sampling 得到近似的多边形表示,反之则通过 visionflow::geometry::bounding_circle() 和 visionflow::geometry::min_area_rect() 得到一个近似的圆或矩形表示。
vflow::geometry::Circle2f circle{{0, 0}, 10};
auto ring = sampling(circle, 360);
auto circle_2 = bounding_circle(ring);
circle = geo.Circle2f(geo.Point2f(0, 0), 10)
ring = geo.sampling(circle, 360)
circle_2 = geo.bounding_circle(ring)
var circle = new Circle2f(new Point2f(0, 0), 10);
var ring = sampling(circle, 360);
var circle_2 = bounding_circle(ring);
浮点型和整型#
几何库通过 2f 和 2i 来区分使用单精度浮点数还是整数来表示坐标和相关数据。受限于整形计算的不封闭性,部分算法(如缓冲区 visionflow::geometry::buffer() )仅支持浮点数类型的几何模型。浮点类型的模型可以通过 visionflow::geometry::convert_to_int 转换为整数类型的模型,反之则通过 visionflow::geometry::convert() 转换为浮点类型的模型。
vflow::geometry::Polygon2f polygon2f{{{0, 0}, {5, 0}, {5, 5}, {0, 5}},
{{1, 1}, {1, 4}, {4, 4}, {4, 1}}};
auto polygon2i = convert_to_int(polygon2f);
auto polygon2f_2 = convert(polygon2i);
polygon2f = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(0, 0), geo.Point2f(5, 0),
geo.Point2f(5, 5), geo.Point2f(0, 5)
]),
geo.Ring2f([
geo.Point2f(1, 1), geo.Point2f(1, 4),
geo.Point2f(4, 4), geo.Point2f(4, 1)
])
])
polygon2i = geo.convert_to_int(polygon2f)
polygon2f_2 = geo.convert(polygon2i)
var polygon2f = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(0, 0), new Point2f(5, 0),
new Point2f(5, 5), new Point2f(0, 5)
}),
new Ring2f( new std.VectorPoint2f {
new Point2f(1, 1), new Point2f(1, 4),
new Point2f(4, 4), new Point2f(4, 1)
})
});
var polygon2i = convert_to_int(polygon2f);
var polygon2f_2 = convert(polygon2i);
笛卡尔坐标系#
几何库的坐标系采用笛卡尔坐标系,坐标轴方向和单位可以由用户定义。例如在图像处理场景中,通常以图像左上角为原点,x 轴向右,y 轴向下,单位为像素;在其他场景中可能以某个地理位置为原点,x 轴向右,y 轴向上,单位为任意长度单位。几何库的算法对坐标系没有特定要求,只要输入的几何对象在 同一坐标系 下即可正确工作。
正方向#
几何库约定几何对象的正方向为 x 正半轴向 y 正半轴的方向。在某些算法中(例如 visionflow::geometry::area),几何对象的正方向会影响结果的符号,甚至导致结果完全不正确,因此建议用户保持几何对象的正方向一致。
对于多边形 visionflow::geometry::Polygon2f 而言,内边界和外边界方向必须相反。例如在 图像坐标系 下,应该按如下顺序描述坐标点:
空间关系判断#
判断几何对象之间的空间关系(如相交、包含、接触或完全分离)是空间查询、候选裁剪和拓扑验证等操作的基础。Visionflow 提供一组语义清晰的空间谓词(例如 visionflow::geometry::intersects() 、 visionflow::geometry::within() 等),这些谓词能接受不同类型的几何对象并返回布尔结果,用于表示两个对象在空间上的布尔关系。
Note
定义来自 OGC Simple Feature Access 和 PostGIS 。
例如,相交但不包含可以这么表示:
if (intersects(a, b) && !within(a, b)) {
//
}
if geo.intersects(a, b) and not geo.within(a, b):
pass
if (intersects(a, b) && !within(a, b)) {
//
}
DE-9IM#
Dimensionally Extended Nine-Intersection Model (DE-9IM) 为这些空间关系提供了统一的拓扑语义描述,用户在需要精确拓扑分类时可以参考该模型。涉及到空间关系的几何库算法接口注释中会提及该概念。
对于一个几何对象 a,定义 I(a) , B(a) , E(a) 表示对象的内部,边界和外部。定义 dim(x) 表示 x 中几何对象的最大维度( \(dim(x) \in \{ -1, 0, 1, 2 \}, -1 = \varnothing\) )。那么两个几何对象的关系可以组成一个矩阵:
Interior |
Boundary |
Exterior |
|
|---|---|---|---|
Interior |
\(dim(I(a) \cap I(b))\) |
\(dim(I(a) \cap B(b))\) |
\(dim(I(a) \cap E(b))\) |
Boundary |
\(dim(B(a) \cap I(b))\) |
\(dim(B(a) \cap B(b))\) |
\(dim(B(a) \cap E(b))\) |
Exterior |
\(dim(E(a) \cap I(b))\) |
\(dim(E(a) \cap B(b))\) |
\(dim(E(a) \cap E(b))\) |
例如一个多边形相交的结果:
Interior |
Boundary |
Exterior |
|
|---|---|---|---|
Interior |
2 |
1 |
2 |
Boundary |
1 |
0 |
1 |
Exterior |
2 |
1 |
2 |
几何计算#
几何计算类算法包含:
评估几何特性的度量计算。例如面积
visionflow::geometry::area和周长visionflow::geometry::perimeter。对几何对象执行某种构造或优化操作并返回新几何对象的函数。例如凸包
visionflow::geometry::convex_hull和采样visionflow::geometry::sampling。几何对象的特性评估和检查。例如是否是有效对象
visionflow::geometry::is_valid()和visionflow::geometry::return_is_valid()。集合运算。
这些工具在预处理或后处理阶段非常有用。
度量计算#
度量计算返回数值结果,用于评估几何对象的量化特性。这类函数广泛应用于几何过滤(例如”只保留面积大于阈值的区域”)和质量检查(例如”圆度判断”)。
常见的度量有两类:
全局度量(作用于整个几何):面积、周长、长度、圆度等。
点对度量(计算两点间关系):欧氏距离、最大/最小距离等。
下面是一个过滤判断和检查:
vflow::geometry::Polygon2f polygon{{{0, 0}, {5, 0}, {5, 5}, {0, 5}},
{{1, 1}, {1, 4}, {4, 4}, {4, 1}}};
auto area = area(polygon);
if (area < 5) {
return;
}
auto circularity = circularity(polygon);
if (circularity > 0.5) {
//
}
polygon = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(0, 0), geo.Point2f(5, 0),
geo.Point2f(5, 5), geo.Point2f(0, 5)
]),
geo.Ring2f([
geo.Point2f(1, 1), geo.Point2f(1, 4),
geo.Point2f(4, 4), geo.Point2f(4, 1)
])
])
area = geo.area(polygon)
if area < 5:
return
circularity = geo.circularity(polygon)
if circularity > 0.5:
pass
var polygon = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(0, 0), new Point2f(5, 0),
new Point2f(5, 5), new Point2f(0, 5)
}),
new Ring2f( new std.VectorPoint2f {
new Point2f(1, 1), new Point2f(1, 4),
new Point2f(4, 4), new Point2f(4, 1)
})
});
var area = area(polygon);
if (area < 5) {
return;
}
var circularity = circularity(polygon);
if (circularity > 0.5) {
//
}
构造和优化#
构造和优化用于生成新的几何对象或对既有几何进行优化处理。常见的操作包括:
缓冲区
visionflow::geometry::buffer():沿着几何边界扩展或收缩一定距离,用于扩大数据范围、填补空隙或增强图形结构。凸包
visionflow::geometry::convex_hull:求包含所有点的最小凸多边形,用于快速的粗粒度包含判断或者碰撞判断。简化
visionflow::geometry::simplify():通过减少顶点数量来简化线性几何,保留关键特征点,降低数据体积。采样
visionflow::geometry::sampling:在曲线上均匀或按特定间距取点,便于离散化表示或数据转换。线性插值
visionflow::geometry::line_interpolate():在线段上从起点开始按指定距离插值取点。
几何对象的特性评估和检查#
几何库提供了多种检查几何特性的辅助函数,例如判断多边形是否是矩形 visionflow::geometry::is_rect() 、是否为空 visionflow::geometry::is_empty() 等。其中最关键的是几何有效性的检查。
这是因为库中的所有算法都假设输入几何是有效的——即正方向一致、无自相交等。如果输入不满足这些条件,可能导致算法结果不可预料。为了确保计算的正确性,建议先使用 visionflow::geometry::is_valid() 检查几何的有效性,然后根据需要使用 visionflow::geometry::correct() 修复它们:
vflow::geometry::Polygon2f polygon{{{0, 0}, {5, 0}, {5, 5}, {5, 5}, {0, 5}}};
if (!is_valid(polygon)) {
correct(polygon);
}
polygon = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(0, 0), geo.Point2f(5, 0),
geo.Point2f(5, 5), geo.Point2f(5, 5),
geo.Point2f(0, 5)
])
])
if not geo.is_valid(polygon):
geo.correct(polygon)
var polygon = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(0, 0), new Point2f(5, 0),
new Point2f(5, 5), new Point2f(5, 5),
new Point2f(0, 5)
})
});
if (!is_valid(polygon)) {
correct(polygon);
}
Note
visionflow::geometry::correct() 的修复能力有限,只能处理常见的几何错误。对于更复杂的无效情况,可以参考 visionflow::geometry::GeoIsSampleFailType 枚举类型,它详细列举了各种无效几何的错误类型,便于精确诊断和自行处理。
集合运算#
集合运算是指对几何对象执行布尔逻辑操作,通过并、交、差、对称差等运算来合并或分解几何区域。这些操作广泛应用于:
区域融合 :合并相邻或重叠的多个区域(如多个检测框的合并)。
区域提取 :提取两个区域的公共部分(如两个 ROI 的交集)。
区域过滤 :从一个大区域中排除指定部分(如背景去除)。
精确覆盖计算 :计算不同效果区域的重叠关系。
支持的集合运算包括:
visionflow::geometry::union_areal:求两个区域的并集,合并分离或重叠的多边形。visionflow::geometry::intersection:求两个区域的交集,提取公共部分。visionflow::geometry::difference:求差集,从第一个区域中移除第二个区域。visionflow::geometry::sym_difference:求对称差,保留两个区域中不重叠的部分。
vflow::geometry::Polygon2f polygon_a{{{0, 0}, {10, 0}, {10, 10}, {0, 10}}};
vflow::geometry::Polygon2f polygon_b{{{5, 5}, {15, 5}, {15, 15}, {5, 15}}};
auto union_result = union_areal(polygon_a, polygon_b);
auto intersection_result = intersection(polygon_a, polygon_b);
auto difference_result = difference(polygon_a, polygon_b);
auto sym_diff_result = sym_difference(polygon_a, polygon_b);
polygon_a = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(0, 0), geo.Point2f(10, 0),
geo.Point2f(10, 10), geo.Point2f(0, 10)
])
])
polygon_b = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(5, 5), geo.Point2f(15, 5),
geo.Point2f(15, 15), geo.Point2f(5, 15)
])
])
union_result = geo.union_areal(polygon_a, polygon_b)
intersection_result = geo.intersection(polygon_a, polygon_b)
difference_result = geo.difference(polygon_a, polygon_b)
sym_diff_result = geo.sym_difference(polygon_a, polygon_b)
var polygon_a = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(0, 0), new Point2f(10, 0),
new Point2f(10, 10), new Point2f(0, 10)
})
});
var polygon_b = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(5, 5), new Point2f(15, 5),
new Point2f(15, 15), new Point2f(5, 15)
})
});
var union_result = union_areal(polygon_a, polygon_b);
var intersection_result = intersection(polygon_a, polygon_b);
var difference_result = difference(polygon_a, polygon_b);
var sym_diff_result = sym_difference(polygon_a, polygon_b);
Note
对于批量多边形的并集和交集计算,几何库提供了专门优化的批量接口,可直接一次性处理多个多边形集合,相比逐对计算更加高效。例如 visionflow::geometry::intersection 可接受 std::vector<visionflow::geometry::MultiPolygon2f> 参数直接对多个多边形集合进行交集运算。
对于自交的几何对象,集合运算对于所有区域采用固定策略的选取方式。下图红色部分是相交结果:
vflow::geometry::Polygon2f polygon_a{{{100, 50}, {9.5, 79.4}, {65.5, 2.4}, {65.5, 97.6}, {9.5, 20.7}}};
vflow::geometry::Polygon2f polygon_b{{{20, 20}, {80, 20}, {80, 80}, {20, 80}}};
auto intersection_result = intersection(polygon_a, polygon_b);
polygon_a = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(100, 50), geo.Point2f(9.5, 79.4),
geo.Point2f(65.5, 2.4), geo.Point2f(65.5, 97.6),
geo.Point2f(9.5, 20.7)
])
])
polygon_b = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(20, 20), geo.Point2f(80, 20),
geo.Point2f(80, 80), geo.Point2f(20, 80)
])
])
intersection_result = geo.intersection(polygon_a, polygon_b)
var polygon_a = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(100, 50), new Point2f(9.5, 79.4),
new Point2f(65.5, 2.4), new Point2f(65.5, 97.6),
new Point2f(9.5, 20.7)
})
});
var polygon_b = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(20, 20), new Point2f(80, 20),
new Point2f(80, 80), new Point2f(20, 80)
})
});
var intersection_result = intersection(polygon_a, polygon_b);
几何变换#
几何变换包括平移、旋转、缩放等仿射操作,通常直接通过相应函数就能完成。但是当需要对变换进行更细粒度的控制、与第三方库协作、或在一批几何上复用同一组合时,矩阵 visionflow::geometry::Matrix3f 接口就非常有用。几何库允许你构造并获取变换矩阵,随后可以用 visionflow::geometry::transform 等通用接口将其应用到任意几何对象上。visionflow::geometry::get_affine_transform() 和visionflow::geometry::get_perspective_transform() 分别生成仿射矩阵和透视矩阵,方便用户按需拼接或修改。
例如将一个多边形平移后绕原点旋转后,再缩放:
vflow::geometry::Polygon2f polygon{{{0, 0}, {10, 0}, {10, 10}, {0, 10}}};
polygon = transform_translate(polygon, 5, 5);
polygon = transform_rotate(polygon, vflow::geometry::Radian::FromDegree(45));
polygon = transform_scale(polygon, 2, 2);
polygon = geo.Polygon2f([
geo.Ring2f([
geo.Point2f(0, 0), geo.Point2f(10, 0),
geo.Point2f(10, 10), geo.Point2f(0, 10)
])
])
polygon = geo.transform_translate(polygon, 5, 5);
polygon = geo.transform_rotate(polygon, geo.Radian.FromDegree(45));
polygon = geo.transform_scale(polygon, 2, 2);
var polygon = new Polygon2f( new std.VectorRing2f {
new Ring2f( new std.VectorPoint2f {
new Point2f(0, 0), new Point2f(10, 0),
new Point2f(10, 10), new Point2f(0, 10)
})
});
polygon = transform_translate(polygon, 5, 5);
polygon = transform_rotate(polygon, Radian.FromDegree(45));
polygon = transform_scale(polygon, 2, 2);