2D几何库#

Visionflow 在 visionflow::geometry 命名空间下提供了功能完善的2D几何库,涵盖了基本几何模型、空间关系判断、几何计算和几何变换等功能。开发者可以使用这些功能快速实现几何相关的开发任务,不需要自己从底层实现数学细节。

Note

详细的接口定义和相关功能细节请参考 2D几何库接口文档

几何模型#

Visionflow 提供的几何模型既包含 OGC Simple Feature Access 标准中定义的常用类型——以点坐标表示的点、线串、多边形及其集合,也包含直线、线段、矩形、圆、椭圆等基于集合或属性描述的几何模型。在某些场景下,后者相比纯坐标表示更高效且使用更便捷。

../_images/geometry-model.png

两类模型可以通过 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);

浮点型和整型#

几何库通过 2f2i 来区分使用单精度浮点数还是整数来表示坐标和相关数据。受限于整形计算的不封闭性,部分算法(如缓冲区 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 而言,内边界和外边界方向必须相反。例如在 图像坐标系 下,应该按如下顺序描述坐标点:

../_images/geometry-positive_direction.png

空间关系判断#

判断几何对象之间的空间关系(如相交、包含、接触或完全分离)是空间查询、候选裁剪和拓扑验证等操作的基础。Visionflow 提供一组语义清晰的空间谓词(例如 visionflow::geometry::intersects()visionflow::geometry::within() 等),这些谓词能接受不同类型的几何对象并返回布尔结果,用于表示两个对象在空间上的布尔关系。

Note

定义来自 OGC Simple Feature AccessPostGIS

例如,相交但不包含可以这么表示:

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))\)

例如一个多边形相交的结果:

../_images/geometry-de_9im.png

Interior

Boundary

Exterior

Interior

2

1

2

Boundary

1

0

1

Exterior

2

1

2

几何计算#

几何计算类算法包含:

  1. 评估几何特性的度量计算。例如面积 visionflow::geometry::area 和周长 visionflow::geometry::perimeter

  2. 对几何对象执行某种构造或优化操作并返回新几何对象的函数。例如凸包 visionflow::geometry::convex_hull 和采样 visionflow::geometry::sampling

  3. 几何对象的特性评估和检查。例如是否是有效对象 visionflow::geometry::is_valid()visionflow::geometry::return_is_valid()

  4. 集合运算。

这些工具在预处理或后处理阶段非常有用。

度量计算#

度量计算返回数值结果,用于评估几何对象的量化特性。这类函数广泛应用于几何过滤(例如”只保留面积大于阈值的区域”)和质量检查(例如”圆度判断”)。

常见的度量有两类:

  1. 全局度量(作用于整个几何):面积、周长、长度、圆度等。

  2. 点对度量(计算两点间关系):欧氏距离、最大/最小距离等。

下面是一个过滤判断和检查:

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);
../_images/geometry-intersection.png

几何变换#

几何变换包括平移、旋转、缩放等仿射操作,通常直接通过相应函数就能完成。但是当需要对变换进行更细粒度的控制、与第三方库协作、或在一批几何上复用同一组合时,矩阵 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);