如何从 float32 的 numpy 数组创建 pygame 表面?
How to create a pygame surface from a numpy array of float32?
我有一段代码可以使用
my_surface = pygame.image.load('some_image.png')
这 return 是一个 pygame 表面。我想在其他地方使用相同的代码,而是传入一个 numpy 数组。 (实际上,我将有一个 if 语句来确定我们是否有一个数组或一个图像的路径。在任何一种情况下,该函数都必须 return 相同类型的对象,一个 pygame表面。它已经使用上面的代码工作。现在我必须添加第二种方法来生成相同的对象,如果脚本被不同地使用。)我已经尝试使用
my_surface = pygame.pixelcopy.make_surface(my_array)
但问题是这个函数需要一个 INTEGER 数组。我的数组是 float32。我可以通过像这样传递数组来强制它通过
(my_array*10000).astype(int)
但是当我稍后显示它时,它看起来像垃圾(想象一下我的惊讶)。所以我的问题是,我们如何从 numpy 浮点数组中优雅地创建一个 pygame 表面?
将数据范围转换为范围[0-255],数据大小必须为M x N
或M x N x 3
pygame.init()
display = pygame.display.set_mode((350, 350))
x = np.arange(0, 300)
y = np.arange(0, 300)
X, Y = np.meshgrid(x, y)
Z = X + Y
Z = 255*Z/Z.max()
surf = pygame.surfarray.make_surface(Z)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
display.blit(surf, (0, 0))
pygame.display.update()
pygame.quit()
如果你想要灰度:
import pygame
import numpy as np
def gray(im):
im = 255 * (im / im.max())
w, h = im.shape
ret = np.empty((w, h, 3), dtype=np.uint8)
ret[:, :, 2] = ret[:, :, 1] = ret[:, :, 0] = im
return ret
pygame.init()
display = pygame.display.set_mode((350, 350))
x = np.arange(0, 300)
y = np.arange(0, 300)
X, Y = np.meshgrid(x, y)
Z = X + Y
Z = 255 * Z / Z.max()
Z = gray(Z)
surf = pygame.surfarray.make_surface(Z)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
display.blit(surf, (0, 0))
pygame.display.update()
pygame.quit()
为了简单起见,假设您只有值 [0,1],否则最好将值截断到固定范围内,以便通过一些浮点值,例如0,5 你总是有相同的颜色输出。
我将以简单的水平渐变作为输入示例:
W = 300
H = 200
# generate simple gradient in float
F0 = numpy.linspace(0, 1, num = W)
F = numpy.tile(F0, (H, 1))
现在有几种显示方式。在这种情况下,我可能会在 8 位表面上显示它。在这种情况下,您将需要它以 Pygame 格式定义调色板:
def make_palette (C1, C2):
palR = numpy.linspace(C1[0], C2[0], num = 256, dtype = "uint8")
palG = numpy.linspace(C1[1], C2[1], num = 256, dtype = "uint8")
palB = numpy.linspace(C1[2], C2[2], num = 256, dtype = "uint8")
return zip(palR,palG,palB)
然后将数据从数组复制到表面:
def put_arr(Dest, Src):
buf = Dest.get_buffer()
buf.write(Src.tostring(), 0)
现在,在程序的开头,您初始化与数组大小相同的表面并应用调色板:
I_surf = pygame.Surface((W, H), 0, 8) # Pygame surface
C1 = (0,0,250)
C2 = (250,0,0)
palRGB = make_palette (C1, C2)
I_surf.set_palette(palRGB)
在主循环中你有类似的东西:
I = numpy.rint( F*255 ).astype("uint8")
put_arr(I_surf, I)
...
DISPLAY.blit(I_surf, (100, 100))
注意数组和表面的类型,在这种情况下它们都必须是 8 位。
如果一切正常,您必须在 window 中看到:
这是对 eyllanesc 回答的补充,因为我发现该回答的示例很有帮助,但如果您想更新图像,可以稍微优化它们。
主要优化是使用8位数组,并使用surfarray.blit_array
代替blit
和display.flip
代替display.update
。 (后两者要求显示和数组大小相同。)
然而,差别并不大——下面的代码在没有优化的情况下使我达到了 15 fps,在有优化的情况下达到了 20-21 fps。基于 OpenGL 的方法会快得多,但我不知道一个方便的库。
import pygame
import numpy as np
from pygame import surfarray
from timeit import default_timer as timer
pygame.init()
w = 1000
h = 800
display = pygame.display.set_mode((w, h))
img = np.zeros((w,h,3),dtype=np.uint8)
init_time = timer()
frames_displayed = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# updating the image
for i in range(100):
img[np.random.randint(w),np.random.randint(h)] = np.random.randint(255,size=3,dtype=np.uint8)
surfarray.blit_array(display, img)
pygame.display.flip()
frames_displayed+=1
print("average frame rate:", frames_displayed/(timer()-init_time), "fps")
pygame.quit()
假设您有一个 OpenCV 图像(它是一个 Numpy ndarray)。要使用 pygame 加载它,我必须执行以下操作:
# I suppose that the image source is a camera of something else, so we already have it as a numpy array.
# Convert the numpy image into a pygame.Surface
pygame_img = pygame.image.frombuffer(your_image.tostring(), your_image.shape[1::-1], "RGB")
your_screen.blit(pygame_img, (pos_x, pos_y))
我有一段代码可以使用
my_surface = pygame.image.load('some_image.png')
这 return 是一个 pygame 表面。我想在其他地方使用相同的代码,而是传入一个 numpy 数组。 (实际上,我将有一个 if 语句来确定我们是否有一个数组或一个图像的路径。在任何一种情况下,该函数都必须 return 相同类型的对象,一个 pygame表面。它已经使用上面的代码工作。现在我必须添加第二种方法来生成相同的对象,如果脚本被不同地使用。)我已经尝试使用
my_surface = pygame.pixelcopy.make_surface(my_array)
但问题是这个函数需要一个 INTEGER 数组。我的数组是 float32。我可以通过像这样传递数组来强制它通过
(my_array*10000).astype(int)
但是当我稍后显示它时,它看起来像垃圾(想象一下我的惊讶)。所以我的问题是,我们如何从 numpy 浮点数组中优雅地创建一个 pygame 表面?
将数据范围转换为范围[0-255],数据大小必须为M x N
或M x N x 3
pygame.init()
display = pygame.display.set_mode((350, 350))
x = np.arange(0, 300)
y = np.arange(0, 300)
X, Y = np.meshgrid(x, y)
Z = X + Y
Z = 255*Z/Z.max()
surf = pygame.surfarray.make_surface(Z)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
display.blit(surf, (0, 0))
pygame.display.update()
pygame.quit()
如果你想要灰度:
import pygame
import numpy as np
def gray(im):
im = 255 * (im / im.max())
w, h = im.shape
ret = np.empty((w, h, 3), dtype=np.uint8)
ret[:, :, 2] = ret[:, :, 1] = ret[:, :, 0] = im
return ret
pygame.init()
display = pygame.display.set_mode((350, 350))
x = np.arange(0, 300)
y = np.arange(0, 300)
X, Y = np.meshgrid(x, y)
Z = X + Y
Z = 255 * Z / Z.max()
Z = gray(Z)
surf = pygame.surfarray.make_surface(Z)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
display.blit(surf, (0, 0))
pygame.display.update()
pygame.quit()
为了简单起见,假设您只有值 [0,1],否则最好将值截断到固定范围内,以便通过一些浮点值,例如0,5 你总是有相同的颜色输出。
我将以简单的水平渐变作为输入示例:
W = 300
H = 200
# generate simple gradient in float
F0 = numpy.linspace(0, 1, num = W)
F = numpy.tile(F0, (H, 1))
现在有几种显示方式。在这种情况下,我可能会在 8 位表面上显示它。在这种情况下,您将需要它以 Pygame 格式定义调色板:
def make_palette (C1, C2):
palR = numpy.linspace(C1[0], C2[0], num = 256, dtype = "uint8")
palG = numpy.linspace(C1[1], C2[1], num = 256, dtype = "uint8")
palB = numpy.linspace(C1[2], C2[2], num = 256, dtype = "uint8")
return zip(palR,palG,palB)
然后将数据从数组复制到表面:
def put_arr(Dest, Src):
buf = Dest.get_buffer()
buf.write(Src.tostring(), 0)
现在,在程序的开头,您初始化与数组大小相同的表面并应用调色板:
I_surf = pygame.Surface((W, H), 0, 8) # Pygame surface
C1 = (0,0,250)
C2 = (250,0,0)
palRGB = make_palette (C1, C2)
I_surf.set_palette(palRGB)
在主循环中你有类似的东西:
I = numpy.rint( F*255 ).astype("uint8")
put_arr(I_surf, I)
...
DISPLAY.blit(I_surf, (100, 100))
注意数组和表面的类型,在这种情况下它们都必须是 8 位。
如果一切正常,您必须在 window 中看到:
这是对 eyllanesc 回答的补充,因为我发现该回答的示例很有帮助,但如果您想更新图像,可以稍微优化它们。
主要优化是使用8位数组,并使用surfarray.blit_array
代替blit
和display.flip
代替display.update
。 (后两者要求显示和数组大小相同。)
然而,差别并不大——下面的代码在没有优化的情况下使我达到了 15 fps,在有优化的情况下达到了 20-21 fps。基于 OpenGL 的方法会快得多,但我不知道一个方便的库。
import pygame
import numpy as np
from pygame import surfarray
from timeit import default_timer as timer
pygame.init()
w = 1000
h = 800
display = pygame.display.set_mode((w, h))
img = np.zeros((w,h,3),dtype=np.uint8)
init_time = timer()
frames_displayed = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# updating the image
for i in range(100):
img[np.random.randint(w),np.random.randint(h)] = np.random.randint(255,size=3,dtype=np.uint8)
surfarray.blit_array(display, img)
pygame.display.flip()
frames_displayed+=1
print("average frame rate:", frames_displayed/(timer()-init_time), "fps")
pygame.quit()
假设您有一个 OpenCV 图像(它是一个 Numpy ndarray)。要使用 pygame 加载它,我必须执行以下操作:
# I suppose that the image source is a camera of something else, so we already have it as a numpy array.
# Convert the numpy image into a pygame.Surface
pygame_img = pygame.image.frombuffer(your_image.tostring(), your_image.shape[1::-1], "RGB")
your_screen.blit(pygame_img, (pos_x, pos_y))