5.21. 缩放、翻转和裁剪

前面的各小节都是在像素原来的位置上对其进行处理。而变换这一类操作改变了这一点。缩放会把每个输入像素送到一个不同的输出位置,可能一次送到多个输出位置(放大时),也可能送到一个与多个其他输入像素共享的位置(缩小时)。翻转和旋转通过不同的映射做同样的事。裁剪保留输入像素中的一个矩形子集,并丢弃其余部分。

image 模块通过三个方法暴露这一类操作,它们共享大部分参数和大部分行为:

  • copy() —— 生成图像的副本,可能经过缩放、裁剪或重新定向。

  • crop() —— 与 copy 相同的操作,但预期应用将从源图像中选取一个子矩形。

  • scale() —— 同样的操作,但预期应用将调整结果的尺寸。

这三者共享相同的参数和相同的变换机制;区别在于结果默认落在何处。copy() 生成一幅新图像,而 crop()scale() 则就地修改源图像。

5.21.1. 共享的参数

单次调用可以组合应用所要求的缩放、裁剪、定向和通道提取的任意组合:

x_scaley_scale 分别沿水平和垂直轴独立缩放输入。两者默认均为 1.0(不缩放)。两者取不同的值会产生非均匀缩放——例如,一帧被拉伸到宽是高的两倍。

roi 将输入限制为源图像的一个矩形,只让该矩形内的像素进入后续变换。这就是操作中的“裁剪”部分:传入一个 roi 即可提取子区域。

hint 是一个标志位字段,用于选择插值方法和任何定向翻转。多个标志通过按位或组合(hint=image.BILINEAR | image.HMIRROR)。这些标志分为两组——插值类和定向类——二者互不相关,但共用同一个位字段。

rgb_channel 选择 RGB565 源图像的单个通道。0 表示红色,1 表示绿色,2 表示蓝色;结果以仅包含该通道的灰度图像形式输出。例如,可用于仅对红色通道进行阈值处理。

color_palettealpha_palette 在输出时通过查找表重新映射像素值,方式与转换方法 to_rainbow()to_ironbow() 相同。

copy=Truecopy_to_fb=True 遵循其他所有产生结果的方法所用的相同约定——默认就地处理,copy=True 分配一个单独的结果,copy_to_fb=True 将结果放入帧缓冲区以供 IDE 预览。

5.21.2. 插值:AREA、BILINEAR、BICUBIC

当缩放将每个输出像素送到一个与任何单个输入像素都不对齐的位置时,该方法必须决定写入什么值。三个标志控制其方式:

image.BILINEAR 在最近的四个输入像素之间进行插值,按它们到输出位置的距离加权。结果比最近邻法更平滑,对角线上没有可见的锯齿,但额外的运算开销约为最近邻遍历的四倍。它是大多数放大工作以及任何非整数缩放因子的恰当选择。

image.BICUBIC 使用三次曲线在最近的十六个输入像素之间进行插值,结果更加平滑,但运算量也随之进一步增加。对于需要的、对成本敏感的应用而言这是最佳质量;但对于 IDE 仅用于显示的实时画面而言,额外的计算很少值得。

image.AREA 对落在输出像素覆盖范围内的每个输入像素取平均——这是缩小的恰当算法。双线性和双三次都是插值器:它们估计源像素之间的值,这正是放大所需要的,但在缩小时每个输出像素覆盖许多源像素,而插值器只读取其中最近的几个——它跳过的细节会以混叠的形式回来。image.AREA 则会把每个被覆盖的像素都折算进平均值。

不带任何 hint 的默认缩放算法是最近邻法,当源图像已经处于目标的像素分辨率时,它最便宜,也是正确答案。

5.21.3. 定向:翻转和旋转

定向标志是一小组布尔变换,它们彼此之间以及与插值标志之间都可以自由组合:

  • image.VFLIP 将图像垂直翻转(顶部变为底部)。

  • image.HMIRROR 将其水平镜像(左侧变为右侧)。

  • image.TRANSPOSE 交换 x 轴和 y 轴(行变为列)。

大多数旋转由组合这三者得到。该模块还暴露了命名的快捷方式:

  • image.ROTATE_90(= VFLIP | TRANSPOSE

  • image.ROTATE_180(= HMIRROR | VFLIP

  • image.ROTATE_270(= HMIRROR | TRANSPOSE

代码示例:

img.copy(hint=image.ROTATE_90, copy_to_fb=True)

5.21.4. 宽高比处理

当源图像的宽高比与要绘制到的矩形不匹配时,三个标志决定如何处理这种不匹配:

image.SCALE_ASPECT_KEEP 保持源图像的宽高比并对结果进行信箱化处理——源图像被缩放到刚好能放入目标内,目标的其余部分用空(零)像素填充。当保持源图像不变形比填满整个输出更重要时,这是恰当的选择。

image.SCALE_ASPECT_EXPAND 保持源图像的宽高比并对其进行裁剪——源图像被缩放到填满目标,超出目标的部分被裁去。当填满整个输出比看到源图像的每一部分更重要时,这是恰当的选择。

image.SCALE_ASPECT_IGNORE 忽略宽高比,将源图像拉伸以填满目标,接受由此引入的任何变形。当应用已经考虑到这种变形时,这是恰当的选择——例如,当目标的尺寸实际上并非同一场景的等比矩形时。

默认(未设置宽高比标志)与 SCALE_ASPECT_IGNORE 相同:拉伸以填满。关心宽高比的应用应明确指定这三者之一。

5.21.5. 何时使用哪一个

大多数尺寸调整使用 scale(),配合一对 x_scale / y_scale 和一个插值 hint:

img.scale(x_scale=0.5, y_scale=0.5, hint=image.AREA)

大多数旋转使用同样的调用,配合 hint=image.ROTATE_90 或类似参数。

裁剪使用 crop(),配合一个非默认的 roi

img.crop(roi=(40, 30, 200, 150))

当源图像必须在操作后保留时——例如捕获参考帧,或为一帧即将被破坏性处理的图像拍摄缩略图——copy() 会将结果生成为一幅新图像,并保持源图像不受影响:

thumbnail = img.copy(x_scale=0.25, y_scale=0.25, hint=image.AREA)

这个默认行为正是三个名称背后的真正区别:scalecrop 就地变换,copy 进行分配。结果放置关键字弥合了这一差异:在 scalecrop 上使用 copy=True 会将结果分配为一个单独的堆缓冲区,而非覆盖源图像;在这三者中任意一个上使用 copy_to_fb=True 都会将结果放入帧缓冲区以供 IDE 预览。