如何从一个numpy数组的float32创建一个pygame表面?

时间:2016-12-15 16:02:04

标签: python pygame pygame-surface

我有一段使用

工作的代码
my_surface = pygame.image.load('some_image.png')

这会返回一个pygame表面。我想在其他地方使用相同的代码,而是传入一个numpy数组。 (实际上,我将有一个if语句来确定我们是否有一个数组或一个图像的路径。在任何一种情况下,该函数必须返回相同类型的对象,一个pygame表面。它已经使用上面的工作了如果脚本的使用方式不同,我现在必须添加第二种生成相同对象的方法。)我尝试过使用

my_surface = pygame.pixelcopy.make_surface(my_array)

但问题是这个函数需要一个INTEGER数组。我的数组是float32。我可以通过像这样传递数组来强制它通过

(my_array*10000).astype(int)

但是当我稍后显示它看起来像垃圾(想象我的意外)。所以我的问题是,我们如何在一系列花车中优雅地创建一个pygame表面?

4 个答案:

答案 0 :(得分:8)

将数据范围转换为[0-255]范围,数据大小必须为mxnmxnx3

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()

enter image description here

如果你想要灰度:

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()

enter image description here

答案 1 :(得分:0)

假设为简单起见,您只有值[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位 如果一切正常,您必须在窗口中看到:

enter image description here

答案 2 :(得分:0)

这是对eyllanesc答案的补充,因为我发现该答案的示例很有用,但是如果您要更新图像,可以对它们进行一些优化。

主要优化是使用8位数组,并使用surfarray.blit_array代替blitdisplay.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()

答案 3 :(得分:0)

假设您有一个 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))