- 数据增强
- 计算机视觉
- 图像处理
- 机器学习
【免费下载链接】imgaug
Image augmentation for machine learning experiments.
本篇文章解析 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)源码注释明确解释了设计考量:
- 并非技术必需:多边形增强此时已经完成,图像与多边形此前使用的是同一批随机样本;恢复器即使推进了 RNG 状态,下一次
augment()调用仍会对图像与多边形使用相同的(已被推进的)RNG 状态,不会破坏对齐; - 复制只是为了「整洁」:将恢复过程对 RNG 状态的推进与主增强流程隔离开,从语义上更清晰,因此「无论如何都复制一份」;
- 对用户零影响:无论是复制的副本还是原 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)依次执行:
- 快速路径:用新外圈构造
Polygon实例,若is_valid为真则立即返回,不做任何修复; - 准备 RNG:
create_if_not_rng_()将输入归一化为 RNG,再duplicate(3)得到三份引用(rss[0]、rss[1]、rss[2]),分别服务于后续三个随机环节; _remove_consecutive_duplicate_points:基于threshold_duplicate_points=1e-4的距离阈值,剔除相邻重复点,并处理首尾点重合的情况;_fix_polygon_is_line:当所有点近似共线时,使用noise_strength=1e-4的均匀噪声抖动顶点(rss[0]),每次不成功噪声强度放大 10 倍,直到多边形不再共线;_jitter_duplicate_points:通过空间哈希快速查找重复点(rss[1]),对重复点施加同量级噪声抖动;_generate_intersection_points:以decimals=4的精度计算边与边之间的交点,坐标绝对值超过limit_coords_values_for_inter_search=50000时停止计算,以避免底层浮点库因键精度问题抛异常;_oversample_intersection_points:按oversampling=0.01的比例对交点附近进行过采样,总数上限由oversample_up_to_n_points_max=75控制;_insert_intersection_points:把新生成的点插入外圈;_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 处理变更是一例典型的「性能与整洁度」内部优化,核心结论可归纳为三点:
- 外层复制、内层去复制:
_apply_to_polygons_as_keypoints在调用恢复器前用random_state.copy()隔离 RNG 状态推进;_ConcavePolygonRecoverer.recover_from内部则以random_state.duplicate(3)替代昂贵的 RNG 派生/复制,兼顾了语义清晰与运行性能; - 对用户完全透明:RNG 副本与原 RNG 产生的随机序列一致,恢复过程额外推进的状态不会影响后续批次的图像-多边形对齐,
augment_polygons的公开行为没有任何变化; - 对齐性有测试兜底:test_meta.py 中故意推进 RNG 的
_DummyRecoverer与两组对齐断言,确保此类内部改造永远不破坏 imgaug 最核心的「同一随机流」契约。
对使用者而言,本变更无需任何迁移动作;对想深入 imgaug 内部机制的读者,则可以从 polys.py 的_ConcavePolygonRecoverer出发,沿着recover_psois_→recover_from的调用链,完整理解一条多边形从「增强后损坏」到「恢复为合法凹多边形」的修复路径。
- 数据增强
- 计算机视觉
- 图像处理
- 机器学习
【免费下载链接】imgaug
Image augmentation for machine learning experiments.
相关推荐
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考