【问题标题】:Pixel coordinates vs Drawing Coordinates像素坐标与绘图坐标
【发布时间】:2023-04-06 19:05:01
【问题描述】:

在下面的代码 sn-p 中,传递 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()

【问题讨论】:

  • 我不太了解,但我想你可以做到Image.fromarray(np.transpose(grid), 'RGB')

标签: python numpy python-imaging-library pillow


【解决方案1】:

注意:“轴”是指图像坐标,而不是 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. ...轴需要“交换”
  2. ...数据需要“交换”
  3. ...轴解释需要“交换”

让我们看看:

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.transposeaxes还有一个额外的参数:

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

我们只想交换01,而不是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

【讨论】:

  • 非常感谢您的详细解释。看了你的回答,我学到了很多。
  • $ python -i script_name.py 改变了游戏规则!我从来不知道,谢谢你的花絮以及你的完整回答!
猜你喜欢
  • 2021-08-22
  • 2011-12-04
  • 1970-01-01
  • 2013-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
  • 1970-01-01
相关资源
最近更新 更多