ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

imgaug 多边形增强的 RNG 处理优化:凹多边形恢复器的随机状态管理解析

imgaug 多边形增强的 RNG 处理优化:凹多边形恢复器的随机状态管理解析
  • 数据增强
  • 计算机视觉
  • 图像处理
  • 机器学习

【免费下载链接】imgaug

Image augmentation for machine learning experiments.

项目地址:https://gitcode.com/gh_mirrors/im/imgaug
点击查看免费下载

本篇文章解析 imgaug 0.4.0 版本中关于多边形增强(augment_polygons)随机数生成器(RNG)处理的一项关键变更(对应 原始变更记录):Augmenter.augment_polygons()在凹多边形恢复开始前复制 RNG,同时移除_ConcavePolygonRecoverer内部的 RNG 复制以提升性能。读完本文,你将理解 imgaug 如何通过 RNG 对齐保证「图像与多边形使用同一套随机变换」,掌握RNG.copy()与RNG.duplicate()的语义差异,并了解凹多边形恢复器recover_from()的完整修复流程与对应的测试验证方式。

背景:多边形增强为什么需要 RNG 对齐

在 imgaug 中,多边形(Polygon)的增强并不是直接对多边形做几何变换,而是将其顶点当作关键点(Keypoint)参与增强:_augment_polygons_as_keypoints()通过functools.partial把_augment_keypoints绑定为回调,再经由_apply_to_polygons_as_keypoints施加到多边形顶点上(见 meta.py)。这意味着图像、多边形顶点、边界框等所有数据在每一次增强过程中,都必须从同一个 RNG 流中采样随机参数,否则就会出现「图像被旋转 30°、多边形却被旋转 -10°」的错位现象。

官方用法要求先调用to_deterministic()固定增强器的随机状态,再分别调用augment_images()与augment_polygons();或者直接使用augment(),由它自动处理随机状态对齐(见 meta.py 中 augment_polygons 的文档)。正因为「图像采样序列 = 多边形采样序列」是对齐的核心,任何额外消耗随机数的环节都必须被仔细控制——这正是本次变更的切入点。

变更内容:两处 RNG 处理的此消彼长

本次变更(GitHub issue #447)包含两个方向相反的动作,均以 0.4.0 变更记录的形式固化在 20190929_rngs_polygon_recoverer.md 中:

变更点动作目的
Augmenter.augment_polygons()在开始凹多边形恢复(concave polygon recovery)之前复制augmenter 的 RNG代码整洁(cleanliness),对用户无任何可感知影响
_ConcavePolygonRecoverer内部移除RNG 复制提升性能

两处改动方向相反却互不冲突:外层(增强器调用恢复器之前)新增一次 RNG 复制,是为了隔离「恢复过程对随机状态的推进」;内层(恢复器自身)删掉重复的复制开销,则是为了减少无谓的 RNG 派生成本。

外层新增:调用恢复器前复制 RNG

在 meta.py 的_apply_to_polygons_as_keypoints中,恢复器调用前的关键代码为:

# Its not really necessary to create an RNG copy for the recoverer # here, as the augmentation of the polygons is already finished and # used the same samples as the image augmentation. The recoverer might # advance the RNG state, but the next call to e.g. augment() will then # still use the same (advanced) RNG state for images and polygons. # We copy here anyways as it seems cleaner. random_state_recoverer = (random_state.copy() if random_state is not None else None) psois = recover_psois_(psois, psois_orig, recoverer, random_state_recoverer)

源码注释明确解释了设计考量:

  1. 并非技术必需:多边形增强此时已经完成,图像与多边形此前使用的是同一批随机样本;恢复器即使推进了 RNG 状态,下一次augment()调用仍会对图像与多边形使用相同的(已被推进的)RNG 状态,不会破坏对齐;
  2. 复制只是为了「整洁」:将恢复过程对 RNG 状态的推进与主增强流程隔离开,从语义上更清晰,因此「无论如何都复制一份」;
  3. 对用户零影响:无论是复制的副本还是原 RNG,产生的随机数序列完全一致,因此这是纯内部行为。

RNG.copy()的实现位于 random.py,其语义是「创建副本,且副本将产生与原来相同的随机样本」:

def copy(self): """Create a copy of this RNG. Returns ------- RNG Copy of this RNG. The copy will produce the same random samples. """ return RNG(copy_generator(self.generator))

副本(copy())与派生(derive_rngs_())的关键区别在于:副本复制的是同一状态快照,后续推进互不影响;而派生会从母 RNG 中消耗熵以创建独立的子 RNG。本次变更正是把「派生子 RNG」替换为「复制同一实例」,从而省去派生过程中的熵消耗与开销。

内层精简:恢复器内部不再派生/复制 RNG

_ConcavePolygonRecoverer.recover_from()位于 polys.py。在旧版本中,该方法的随机数准备可能涉及多次 RNG 派生(derive_rngs_());本次变更后改为直接对同一个 RNG 实例做三次「重复引用」:

random_state = iarandom.RNG.create_if_not_rng_(random_state) rss = random_state.duplicate(3)

RNG.duplicate(n)在 random.py 中定义为「返回包含 n 次该 RNG 的列表(同一实例,非副本)」:

def duplicate(self, n): """Create a list containing `n` times this RNG. ... Returns ------- list of RNG List containing `n` times this RNG (same instances, no copies). """ return [self for _ in sm.xrange(n)]

该方法的注释揭示了一个重要的性能背景:它「主要作为此前derive_rngs_()调用的替代品」——因为derive_rngs_()在 numpy 1.17+ 中被证实非常慢,故除「绝对必须创建子 RNG」的情形外,一律改用简单的重复引用。重复引用虽然没有为各随机任务真正隔离 RNG,但保留了代码中的「此处适合创建子 RNG」标记,便于未来派生机制提速后再次替换。这正是本次变更「移除 RNG 复制以提升性能」在源码层面的落点。

凹多边形恢复的完整流程

理解本次变更的意义,需要先看清recover_from()到底做了什么。当多边形在增强后产生自交或退化(如顶点共线、出现重复点)时,_ConcavePolygonRecoverer负责将其修复为合法的凹多边形。整个修复管线(见 polys.py)依次执行:

  1. 快速路径:用新外圈构造Polygon实例,若is_valid为真则立即返回,不做任何修复;
  2. 准备 RNG:create_if_not_rng_()将输入归一化为 RNG,再duplicate(3)得到三份引用(rss[0]、rss[1]、rss[2]),分别服务于后续三个随机环节;
  3. _remove_consecutive_duplicate_points:基于threshold_duplicate_points=1e-4的距离阈值,剔除相邻重复点,并处理首尾点重合的情况;
  4. _fix_polygon_is_line:当所有点近似共线时,使用noise_strength=1e-4的均匀噪声抖动顶点(rss[0]),每次不成功噪声强度放大 10 倍,直到多边形不再共线;
  5. _jitter_duplicate_points:通过空间哈希快速查找重复点(rss[1]),对重复点施加同量级噪声抖动;
  6. _generate_intersection_points:以decimals=4的精度计算边与边之间的交点,坐标绝对值超过limit_coords_values_for_inter_search=50000时停止计算,以避免底层浮点库因键精度问题抛异常;
  7. _oversample_intersection_points:按oversampling=0.01的比例对交点附近进行过采样,总数上限由oversample_up_to_n_points_max=75控制;
  8. _insert_intersection_points:把新生成的点插入外圈;
  9. _fit_best_valid_polygon:以凸包多边形为起点迭代拟合最佳有效凹多边形(rss[2]),受fit_n_changes_max=100、fit_n_iters_max=3、候选边数量上限fit_n_candidates_before_sort_max=100等参数约束。

上述threshold_duplicate_points、noise_strength、oversampling、max_segment_difference均为_ConcavePolygonRecoverer.__init__的可配置参数(见 polys.py)。从源码结构看,第 2 步的duplicate(3)取代了旧版中对每个随机环节的 RNG 复制/派生——三个环节共享同一 RNG 实例,虽然随机独立性略降,但换来的是每帧多边形恢复时显著减少的 RNG 构造开销,这正是变更记录中「移除 RNG 复制以提升性能」的直接体现。

恢复器整体由 recover_psois_ 驱动:它遍历psois中的每个多边形,调用recoverer.recover_from(polygon.exterior, psois_orig[i].polygons[j], random_state)并以返回结果原地更新polygon.exterior。

对齐性如何被测试锁定

本次变更属于「对用户无影响」的内部改造,其承诺由测试用例严格守护。在 test_meta.py 中定义了一个特殊的_DummyRecoverer:

# This is the same as _ConcavePolygonRecoverer, but we make sure that we # always sample random values. This is to advance the state of random_state # and ensure that this breaks not alignment. class _DummyRecoverer(_ConcavePolygonRecoverer): def recover_from(self, new_exterior, old_polygon, random_state=0): # sample lots of values to ensure that the RNG is advanced _ = random_state.integers(0, 2**30, 100) return super(_DummyRecoverer, self).recover_from( new_exterior, old_polygon, random_state=random_state)

它故意在恢复前一次性采样 100 个随机整数以大幅推进 RNG 状态,目的就是「制造最坏情况,验证恢复器的 RNG 消耗不会破坏对齐」。配套的_DummyAugmenterWithRecoverer同时记录图像增强与关键点增强时各自采样的随机数(random_samples_images、random_samples_kps)。

随后两个对齐性测试(见 test_meta.py)分别验证:

  • test_polygon_aligned_without_recoverer:不启用恢复器时,连续 4 批增强后,图像采样序列必须等于关键点采样序列(assert aug.random_samples_images == aug.random_samples_kps),且多边形会按设计交替出现「有效/无效」状态;
  • test_polygon_aligned_with_recoverer:启用恢复器后,每一批的多边形都必须被修复为有效(assert psois_augN[0].polygons[0].is_valid),同时图像与多边形采样序列依旧完全一致。

这两组断言把「RNG 对齐」从实现细节上升为可回归验证的契约:无论恢复器内部如何推进 RNG,只要外层在调用前复制 RNG(本次变更新增的random_state.copy()),图像与多边形的随机变换就永远不会错位。此外,Test_ConcavePolygonRecoverer测试类(见 test_polys.py)从单元层面覆盖了recover_from()对少于 3 个点、共线、重复点、无效多边形及随机多边形等边界场景的行为,保障了恢复器自身逻辑的稳定性。

总结

本次针对augment_polygons的 RNG 处理变更是一例典型的「性能与整洁度」内部优化,核心结论可归纳为三点:

  1. 外层复制、内层去复制:_apply_to_polygons_as_keypoints在调用恢复器前用random_state.copy()隔离 RNG 状态推进;_ConcavePolygonRecoverer.recover_from内部则以random_state.duplicate(3)替代昂贵的 RNG 派生/复制,兼顾了语义清晰与运行性能;
  2. 对用户完全透明:RNG 副本与原 RNG 产生的随机序列一致,恢复过程额外推进的状态不会影响后续批次的图像-多边形对齐,augment_polygons的公开行为没有任何变化;
  3. 对齐性有测试兜底:test_meta.py 中故意推进 RNG 的_DummyRecoverer与两组对齐断言,确保此类内部改造永远不破坏 imgaug 最核心的「同一随机流」契约。

对使用者而言,本变更无需任何迁移动作;对想深入 imgaug 内部机制的读者,则可以从 polys.py 的_ConcavePolygonRecoverer出发,沿着recover_psois_→recover_from的调用链,完整理解一条多边形从「增强后损坏」到「恢复为合法凹多边形」的修复路径。

  • 数据增强
  • 计算机视觉
  • 图像处理
  • 机器学习

【免费下载链接】imgaug

Image augmentation for machine learning experiments.

项目地址:https://gitcode.com/gh_mirrors/im/imgaug
点击查看免费下载
上一篇:Buzz 本地音频转录完整指南:Whisper 离线转录与翻译教程
下一篇:wger API文档自动化:集成Swagger到健身平台CI流程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进