注意:“轴”是指图像坐标,而不是 NumPy 的数组尺寸。
问题在于ndarray 的维度(“N 维数组”)的解释,或在该上下文中坐标系的定义。
对于Pillow,很清楚:
Coordinate System
Python 图像库使用笛卡尔像素坐标系,
(0,0) 在左上角。注意坐标是指
到隐含的像素角;像素的中心地址为 (0,
0) 实际上位于 (0.5, 0.5)。
坐标通常作为 2 元组 (x, y) 传递给库。
矩形表示为 4 元组,左上角
先给出。例如,一个覆盖所有 800x600 像素的矩形
图像写成 (0, 0, 800, 600)。
看起来像这样(图片 -> 公共领域):
您的代码,已修改为创建 2x2 像素图像:
import numpy as np
from PIL import Image # Pillow
w, h, d = 2,2,3
x,y = 0,1
grid = np.zeros((w, h, d), dtype=np.uint8) # NumPyarray for image data
#test = np.zeros(w*h*d, dtype=np.uint8).reshape(w, h, d)
#print(np.array_equal(grid,test)) # => True
# red pixel with NumPy
grid[x, y] = [255, 0, 0]
print(grid[::])
# green pixel with Pillow
img = Image.fromarray(grid, 'RGB')
pixels = img.load()
pixels[x,y] = (0, 255, 0)
# display temporary image file with default application
scale = 100
img.resize((w*scale,h*scale)).show()
显示问题(在 (0,1) 处绘制像素,绿色:Pillow,红色:ndarray):
X 和 Y 确实互换了:
是因为 NumPy 还是 Pillow?
ndarray 打印为
[[[ 0 0 0]
[255 0 0]]
[[ 0 0 0]
[ 0 0 0]]]
很容易重新格式化以在视觉上与图像像素对应
[
[ [ 0 0 0] [255 0 0] ]
[ [ 0 0 0] [ 0 0 0] ]
]
这表明 Pillow 会按预期解释数组。
但为什么 NumPy 的 ndarray 似乎交换了轴?
让我们进一步分解一下
[ # grid
[ # grid[0]
[ 0 0 0] #grid[0,0]
[255 0 0] #grid[0,1]
]
[ #grid[1]
[ 0 0 0] #grid[1,0]
[ 0 0 0] #grid[1,1]
]
]
让我们测试一下(-i 让 Python 在脚本完成后以交互模式运行):
>py -i t.py
[[[ 0 0 0]
[255 0 0]]
[[ 0 0 0]
[ 0 0 0]]]
>>> grid[0,1]
array([255, 0, 0], dtype=uint8)
>>> grid[0]
array([[ 0, 0, 0],
[255, 0, 0]], dtype=uint8)
>>> ^Z
这证实了上述假设的索引。
很明显ndarray 的第一个维度对应于图像线或 Y 轴,第二个维度对应于图像列或 X 轴(第三个维度显然对应于 RGB 像素值)。
所以,要匹配“坐标系”,要么 ...
- ...轴需要“交换”
- ...数据需要“交换”
- ...轴解释需要“交换”
让我们看看:
1.写入ndarray 时只需交换索引变量:
# red pixel with NumPy
grid[y, x] = [255, 0, 0]
预期结果
[[[ 0 0 0]
[ 0 0 0]]
[[255 0 0]
[ 0 0 0]]]
和
当然,包装函数可以做到这一点。
2. Transposing 数组,如 suggested by zch,在 3 维数组上不容易,因为这个函数默认影响所有维度:
grid = np.transpose(grid)
print("transposed\n", grid)
print("shape:", grid.shape)
结果
[[[ 0 0]
[255 0]]
[[ 0 0]
[ 0 0]]
[[ 0 0]
[ 0 0]]]
shape: (3, 2, 2)
由于指定了 Pillow RGB 图像模式,因此引发了异常:
ValueError: not enough image data
但是np.transpose,axes还有一个额外的参数:
...根据给定的值排列坐标轴。
我们只想交换0 和1,而不是2,所以:
grid = np.transpose(grid, (1,0,2))
还有其他类似的功能,例如
grid = np.swapaxes(grid,0,1)
3.改变解释?
可以使用 Pillow 的 PIL.Image.fromarray 来解释带有交换轴的 ndarray 吗?除了mode 之外,它没有任何其他参数用于颜色(真的,请参阅source code)。
使用缓冲区协议从导出数组接口的对象创建图像内存)。
如果 obj 不连续,则调用 tobytes 方法并使用 frombuffer()。
该函数计算出如何调用PIL.Image.frombuffer() (source),它为“解码器”提供了更多选项。
Array interface? Buffer protocol?现在这两者都太低级了......
TL;DR
只需交换索引变量(任一)!
进一步阅读:
-
https://docs.scipy.org/doc/numpy-dev/user/quickstart.html