Python PIL生成图像像素呈矩形而非正方形的原因与解决方法
问题描述
我是Python Imaging Library (PIL)的初学者,近期使用时发现生成的图像像素为矩形而非正方形,效果参考如下截图:


- 图1:左侧红色像素存在明显显示异常(为96x96像素图像放大后的效果)
- 图2:所有像素均呈现矩形形态(为48x48像素图像放大后的效果)
经测试,尺寸小于20x20的小图像显示基本正常(可能也存在矩形像素问题但肉眼难以辨识),但当图像尺寸大于30x30时,矩形像素的问题会非常明显。
现咨询该问题的产生原因是什么?应当如何修复?是否存在可设置像素宽高比/尺寸的相关方法?
用于复现图2效果的代码如下:
import numpy as np from PIL import Image def random_rgb(): """ Creates tuple of random values between 0 - 255 inclusive. :return: tuple """ r, g, b = np.random.randint(0, 256), np.random.randint(0, 256), np.random.randint(0, 256) rgb = (r, g, b) return rgb def rainbow_bg(n): """ Creates a list of lists of dimensions n x n :return: list """ img = [] for i in range(n): img.append([random_rgb() for i in range(n)]) return img def generate_image(pixels, dim, img_name): """ Generates pixel image from list. :param pixels: list :param dim: tuple :param img_name: string :return: """ arr = np.array(pixels, dtype=np.uint8) new_image = Image.fromarray(arr) new_image = new_image.resize(dim, resample=0) filename = f'{img_name}.png' new_image.save(filename) return bg = rainbow_bg(48) generate_image(pixels=bg, dim=(400, 400), img_name=f'background')
问题原因
该现象不是PIL生成的图像本身像素为非正方形导致,核心问题出在缩放逻辑:
- 通过
Image.fromarray从numpy数组生成的原始图像是n×n正方形,标准栅格图像的像素默认均为1:1正方形比例,本身不存在形状异常。 - 代码中调用
resize时使用了最近邻插值(resample=0对应NEAREST模式,是像素风格图常用的硬边插值方式),但设置的目标尺寸不是原始尺寸的整数倍。以复现图2的代码为例:原始图像尺寸为48×48,目标尺寸为400×400,缩放比例约为8.33,不是整数。 - 非整数倍缩放搭配最近邻插值时,原始图像的单个像素映射到目标图的过程中,占有的像素宽/高会在8~9个像素之间浮动,部分像素块宽高不等,肉眼看起来就是矩形、大小不均的效果。图像尺寸越大,这种浮动带来的视觉差异越明显;小尺寸图总像素少,差异很难被肉眼察觉。
PIL没有提供单独设置像素宽高比的特殊接口,不存在额外调整像素形状的配置项。
修复方案
- 整数倍缩放(最推荐,完美保留硬边像素风格):缩放时直接选择原始尺寸的整数倍作为目标尺寸。比如48×48的原图,放大8倍对应384×384、放大9倍对应432×432,保证每个原始像素对应整数个输出像素,所有像素块大小完全一致,呈现标准正方形。
- 固定尺寸适配:如果必须输出400×400这类非整数倍的固定尺寸,可以先将原图用整数倍放大到大于目标尺寸的大小,再通过居中裁剪得到目标尺寸;也可以放弃最近邻插值,改用双线性、双三次等平滑插值方式,但后者会模糊像素块边缘,丢失像素风格的硬边效果。
修正后的参考代码:
import numpy as np from PIL import Image def random_rgb(): """生成0-255区间的随机RGB三元组""" r, g, b = np.random.randint(0, 256, size=3) return (int(r), int(g), int(b)) def rainbow_bg(n): """生成n×n尺寸的随机彩色像素数组""" img = [] for i in range(n): img.append([random_rgb() for _ in range(n)]) return img def generate_image(pixels, scale, img_name): """ 按整数倍缩放生成像素图 :param pixels: 像素数组 :param scale: 整数缩放倍数 :param img_name: 保存文件名 """ arr = np.array(pixels, dtype=np.uint8) new_image = Image.fromarray(arr) origin_w, origin_h = new_image.size target_size = (origin_w * scale, origin_h * scale) # 使用最近邻插值保留硬边像素效果 new_image = new_image.resize(target_size, resample=Image.Resampling.NEAREST) new_image.save(f'{img_name}.png') bg = rainbow_bg(48) # 放大8倍得到384×384图像,所有像素块均为标准正方形 generate_image(pixels=bg, scale=8, img_name='background')
内容的提问来源于stack exchange,提问作者d789w
相关产品推荐
相关产品推荐

