像素坐标与绘图坐标

Lau*_*ent 2 python numpy python-imaging-library pillow

在下面的代码片段中,传递 x 和 y 值会将点放在 (y,x) 坐标中,而绘图是在 (x,y) 中完成的。设置绘图缓冲区以便在同一坐标系中放置像素和绘图的正确方法是什么?

from PIL import Image, ImageDraw

def visual_test(x, y):
    grid = np.zeros((100, 100, 3), dtype=np.uint8)
    grid[:] = [0, 0, 0]
    grid[x, y] = [255, 0, 0]
    img = Image.fromarray(grid, 'RGB')
    draw = ImageDraw.Draw(img)
    draw.line((x, y, x, y-5), fill=(255,255,255), width=1)
    img.show()
Run Code Online (Sandbox Code Playgroud)

han*_*dle 10

注意:“轴”是指图像坐标,而不是 NumPy 的数组维度。

问题在于ndarray的维数(“N 维数组”)的解释,或该上下文中坐标系的定义。

对于Pillow,很明显:

坐标系

Python 成像库使用笛卡尔像素坐标系,左上角为 (0,0)。请注意,坐标是指隐含的像素角;寻址为 (0, 0) 的像素的中心实际上位于 (0.5, 0.5)。

坐标通常作为 2 元组 (x, y) 传递给库。矩形表示为 4 元组,首先给出左上角。例如,一个覆盖所有 800x600 像素图像的矩形被写为 (0, 0, 800, 600)。

看起来像这样(图像 -> 公共领域):

枕头的XY坐标系

您的代码经过修改以创建 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()
Run Code Online (Sandbox Code Playgroud)

显示问题(在 (0,1) 处绘制像素,绿色:枕头,红色:ndarray):

生成的图像

X 和 Y 确实交换了:

ndarrays YX 轴

是因为 NumPy 还是 Pillow?

ndarray打印为

[[[  0   0   0]
  [255   0   0]]

 [[  0   0   0]
  [  0   0   0]]]
Run Code Online (Sandbox Code Playgroud)

很容易重新格式化以在视觉上对应于图像像素

[
 [ [  0   0   0] [255   0   0] ]
 [ [  0   0   0] [  0   0   0] ]
]
Run Code Online (Sandbox Code Playgroud)

这表明 Pillow 将数组解释为人们所期望的。

但是为什么 NumPyndarray似乎交换了轴?

让我们把它拆开一点

[ # 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]
 ]
]
Run Code Online (Sandbox Code Playgroud)

让我们测试一下(-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
Run Code Online (Sandbox Code Playgroud)

这证实了上面假设的索引。

很明显 的第一个维度如何ndarray对应于图像行或 Y 轴,第二个对应于图像列或 X 轴(第三个显然对应于 RGB 像素值)。

因此,要匹配“坐标系”,要么......

  1. ...轴需要“交换”
  2. ...数据需要“交换”
  3. ...轴解释需要“交换”

让我们来看看:

1.写入时只需交换索引变量ndarray

# red pixel with NumPy
grid[y, x] = [255, 0, 0]
Run Code Online (Sandbox Code Playgroud)

预期结果

[[[  0   0   0]
  [  0   0   0]]

 [[255   0   0]
  [  0   0   0]]]
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明

当然,包装函数可以做到这一点。

2. 按照zch 的建议,置数组在 3 维数组上并不那么容易,因为此函数默认影响所有维度:

grid = np.transpose(grid)
print("transposed\n", grid)
print("shape:", grid.shape)
Run Code Online (Sandbox Code Playgroud)

结果是

[[[  0   0]
  [255   0]]

 [[  0   0]
  [  0   0]]

 [[  0   0]
  [  0   0]]]
shape: (3, 2, 2)
Run Code Online (Sandbox Code Playgroud)

并且由于RGB指定了枕头图像模式,因此抛出异常:

ValueError: not enough image data
Run Code Online (Sandbox Code Playgroud)

但有一个额外的参数np.transposeaxes

...根据给定的值排列轴。

我们只想交换0and 1,而不是2,所以:

grid = np.transpose(grid, (1,0,2))
Run Code Online (Sandbox Code Playgroud)

还有其他功能类似的操作,例如

grid = np.swapaxes(grid,0,1)
Run Code Online (Sandbox Code Playgroud)

3. 改变解释?

可以用枕头PIL.Image.fromarray来解释ndarray交换轴吗?除了mode颜色之外,它没有任何其他参数(实际上,请参阅源代码)。

从使用缓冲协议导出数组接口的对象创建图像存储器)。如果 obj 不连续,则调用 tobytes 方法并使用 frombuffer()。

该函数计算出如何调用PIL.Image.frombuffer()( source ),它为“解码器”提供了更多选项。

阵列接口? 缓冲协议? 现在这两个水平都太低了......

TL;DR
只需交换索引变量(要么)!


进一步阅读: - https://docs.scipy.org/doc/numpy-dev/user/quickstart.html