如何捕获pygame屏幕?

发布于 2024-11-09 14:49:09 字数 317 浏览 4 评论 0原文

如何捕获并保存 pygame 屏幕的一系列图像或视频?
基本上我想在 youtube 上分享我的游戏视频。另外,想做个教程。

游戏主要以循环方式渲染:

def main():
    while True:
        GetInput()
        Move()
        Shift()
        Draw()

使用 Draw() 函数在执行 pygame.display.flip() 之前执行所有 blit() 等操作

How can I capture and save a sequence of images or a video of a pygame screen?
Basically I want to share my game video on youtube. Also, want to make a tutorial.

The game is rendered mainly in a loop:

def main():
    while True:
        GetInput()
        Move()
        Shift()
        Draw()

With the Draw() function doing all the blit() and stuff before doing the pygame.display.flip()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不一样的天空 2024-11-16 14:49:09

在屏幕表面使用 pygame.image.save :

window = pygame.display.set_mode(...)

...

pygame.image.save(window, "screenshot.jpeg")

请注意,这会极大地减慢您的程序速度。如果它是基于时间的,您可能希望在捕获时伪造帧速率。

Use pygame.image.save on your screen surface:

window = pygame.display.set_mode(...)

...

pygame.image.save(window, "screenshot.jpeg")

Note that this will slow down your program tremendously. If it is time-based, you may wish to fake the framerate when doing a capture.

请叫√我孤独 2024-11-16 14:49:09

接受的答案说您可以使用 pygame.image.save 方法保存当前屏幕

这是一个好主意,但是您可能不希望在游戏运行时将图像保存在硬盘上,问题中也指出。相反,您应该将屏幕保存在程序中,然后在游戏停止运行后处理它们。

这是我的带有注释的代码,仅显示了屏幕录制如何工作的总体思路。它使用 opencv(带有 numpy)和 pygame,但您必须安装 ffmpeg 将图像转换为视频(尝试在终端中使用 ffmpg 进行测试)。不要让程序运行太长时间,因为保存仍然需要相当长的时间,并且大约与记录的帧成正比。为了提高效率,您只能每隔一帧左右录制一次。

from contextlib import contextmanager
import os
import time

import cv2
import numpy as np
import pygame as pg
from pygame import surfarray

def pg_to_cv2(cvarray:np.ndarray)->np.ndarray:
    cvarray = cvarray.swapaxes(0,1) #rotate
    cvarray = cv2.cvtColor(cvarray, cv2.COLOR_RGB2BGR) #RGB to BGR
    return cvarray

def timer_wrapper(func):
    def inner(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        #print("Finished:" ,func.__name__ ,end-start)
        return end - start
    return inner

@contextmanager
def video_writer(*args,**kwargs):
    video = cv2.VideoWriter(*args,**kwargs)
    try:
        yield video
    finally:
        video.release()

@timer_wrapper
def save_frames(frames: list, average_dt: float|list, file_type: str = "mp4", name: str = "screen_recording"):
    if type(average_dt) is list: average_dt = sum(average_dt)/len(average_dt) # force average_dt to be a float
    size = frames[0].get_size()
    codec_dict={
        "avi":'DIVX',
        "mp4":'MP4V'
    }
    codec = cv2.VideoWriter_fourcc(*codec_dict[file_type])
    with video_writer(name+"."+file_type, codec, 1000/average_dt, size) as video: # file_name, codec, average_fps, dimensions
        for frame in frames:
            try:
                pg_frame = surfarray.pixels3d(frame) # convert the surface to a np array. Only works with depth 24 or 32, not less
            except:
                pg_frame = surfarray.array3d(frame) # convert the surface to a np array. Works with any depth
            cv_frame = pg_to_cv2(pg_frame)  # then convert the np array so it is compatible with opencv
            video.write(cv_frame)   #write the frame to the video using opencv

def draw_fps(s:pg.Surface,clock:time.Clock): 
    fps = clock.get_fps()
    sysfont.render_to(s,(100,100),str(fps),fgcolor=nice_green)

# initializing globals (colors, fonts, window, etc.)
pg.init()
sysfont = pg.freetype.SysFont(None,40)
BLACK = (0,)*3
nice_green = pg.Color("chartreuse2")
size=(1000, 600)
pg.display.set_caption("Screen Recording")
window = pg.display.set_mode(size)
# this is to save the frames
frames = []
dts = []
clock = pg.time.Clock()

running=True
try:
    while running:
        dt = clock.tick(60) # aim for ... fps
        for event in pg.event.get():
            if event.type == pg.QUIT:
                running=False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    running=False
        window.fill(BLACK)
        draw_fps(window,clock)
        window_copy = window.copy() # if we don't copy the window then we will have the same image in all frames at the end
        frames.append(window_copy) # We save the current frame together with the time passed between the frames
        dts.append(dt)
        pg.display.flip()
        #if len(frames) >= 100: running = False # uncomment this to stop the game after ... frames for similar results in every run"
finally:
    pg.quit()
    # At this stage, the game ended and the cleanup process can start. For this we convert the frames to opencv images
    # Only then we will write the video to the hard drive (That is what makes the save so slow).
    # General information about the recording
    frame_num = len(frames)
    dt_sum = sum(dts)
    average_dt = dt_sum/frame_num
    # This is only an approximation: 
    # for each frame we have width * height many pixels -> frame_num * width * height
    # A Surface needs get_bytesize() many bytes per pixel (In this case 4 bytes, because we set the depth of the display to 32 bits)
    memory_usage_approx = frame_num * size[0] * size[1] * frames[0].get_bytesize()  #https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_bytesize
    print("Total time:" , dt_sum/1000,"s")
    print("Average time per frame:" , average_dt,"ms")
    print("Number of frames:", frame_num)
    print("Memory usage approximation" , memory_usage_approx/1000, "KB")
    args = (frames,dts,"avi","screen_recording")
    time_for_save = save_frames(*args)
    file_name = args[3]+"."+args[2]
    video_memory_usage = os.path.getsize(file_name)
    print("Video memory usage:" , video_memory_usage/1000, "KB")
    with open("test.txt", "a") as f:
        print("Total time:" , dt_sum/1000,"s\nNumber of frames:", frame_num,"\nSize:",size,"\nTime for save:",time_for_save,"s\nSaved in file:",file_name,file=f)
        print("_"*100,file=f)

或者您只需使用 Pygame Recorder 之类的库。

The accepted answer said you could save the current screen with the pygame.image.save method

This is a good idea, however you might not want to save the images on the hard drive while the game is running, also pointed out by the question. Instead, you should save the screens in the program and then process them after the game has stopped running.

Here is my code with comments, that only shows the general idea of how a screen recording might work. It uses opencv (with numpy) and pygame but you have to install ffmpeg to convert the images to a video (try ffmpg in the terminal to test). Don't let the program run too long, because the saving still takes quite a while and is about proportional to the recorded frames. For more efficiency, you could only record every second frame or so.

from contextlib import contextmanager
import os
import time

import cv2
import numpy as np
import pygame as pg
from pygame import surfarray

def pg_to_cv2(cvarray:np.ndarray)->np.ndarray:
    cvarray = cvarray.swapaxes(0,1) #rotate
    cvarray = cv2.cvtColor(cvarray, cv2.COLOR_RGB2BGR) #RGB to BGR
    return cvarray

def timer_wrapper(func):
    def inner(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        #print("Finished:" ,func.__name__ ,end-start)
        return end - start
    return inner

@contextmanager
def video_writer(*args,**kwargs):
    video = cv2.VideoWriter(*args,**kwargs)
    try:
        yield video
    finally:
        video.release()

@timer_wrapper
def save_frames(frames: list, average_dt: float|list, file_type: str = "mp4", name: str = "screen_recording"):
    if type(average_dt) is list: average_dt = sum(average_dt)/len(average_dt) # force average_dt to be a float
    size = frames[0].get_size()
    codec_dict={
        "avi":'DIVX',
        "mp4":'MP4V'
    }
    codec = cv2.VideoWriter_fourcc(*codec_dict[file_type])
    with video_writer(name+"."+file_type, codec, 1000/average_dt, size) as video: # file_name, codec, average_fps, dimensions
        for frame in frames:
            try:
                pg_frame = surfarray.pixels3d(frame) # convert the surface to a np array. Only works with depth 24 or 32, not less
            except:
                pg_frame = surfarray.array3d(frame) # convert the surface to a np array. Works with any depth
            cv_frame = pg_to_cv2(pg_frame)  # then convert the np array so it is compatible with opencv
            video.write(cv_frame)   #write the frame to the video using opencv

def draw_fps(s:pg.Surface,clock:time.Clock): 
    fps = clock.get_fps()
    sysfont.render_to(s,(100,100),str(fps),fgcolor=nice_green)

# initializing globals (colors, fonts, window, etc.)
pg.init()
sysfont = pg.freetype.SysFont(None,40)
BLACK = (0,)*3
nice_green = pg.Color("chartreuse2")
size=(1000, 600)
pg.display.set_caption("Screen Recording")
window = pg.display.set_mode(size)
# this is to save the frames
frames = []
dts = []
clock = pg.time.Clock()

running=True
try:
    while running:
        dt = clock.tick(60) # aim for ... fps
        for event in pg.event.get():
            if event.type == pg.QUIT:
                running=False
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    running=False
        window.fill(BLACK)
        draw_fps(window,clock)
        window_copy = window.copy() # if we don't copy the window then we will have the same image in all frames at the end
        frames.append(window_copy) # We save the current frame together with the time passed between the frames
        dts.append(dt)
        pg.display.flip()
        #if len(frames) >= 100: running = False # uncomment this to stop the game after ... frames for similar results in every run"
finally:
    pg.quit()
    # At this stage, the game ended and the cleanup process can start. For this we convert the frames to opencv images
    # Only then we will write the video to the hard drive (That is what makes the save so slow).
    # General information about the recording
    frame_num = len(frames)
    dt_sum = sum(dts)
    average_dt = dt_sum/frame_num
    # This is only an approximation: 
    # for each frame we have width * height many pixels -> frame_num * width * height
    # A Surface needs get_bytesize() many bytes per pixel (In this case 4 bytes, because we set the depth of the display to 32 bits)
    memory_usage_approx = frame_num * size[0] * size[1] * frames[0].get_bytesize()  #https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_bytesize
    print("Total time:" , dt_sum/1000,"s")
    print("Average time per frame:" , average_dt,"ms")
    print("Number of frames:", frame_num)
    print("Memory usage approximation" , memory_usage_approx/1000, "KB")
    args = (frames,dts,"avi","screen_recording")
    time_for_save = save_frames(*args)
    file_name = args[3]+"."+args[2]
    video_memory_usage = os.path.getsize(file_name)
    print("Video memory usage:" , video_memory_usage/1000, "KB")
    with open("test.txt", "a") as f:
        print("Total time:" , dt_sum/1000,"s\nNumber of frames:", frame_num,"\nSize:",size,"\nTime for save:",time_for_save,"s\nSaved in file:",file_name,file=f)
        print("_"*100,file=f)

Or you just use a lib like Pygame Recorder.

执手闯天涯 2024-11-16 14:49:09
x3 = pygame.surfarray.pixels3d(screen)
x3 = x3[:,:,::-1] 
x3 = pygame.surfarray.pixels3d(screen)
x3 = x3[:,:,::-1] 
朦胧时间 2024-11-16 14:49:09

我找到了一个很酷的方法;
您可以使用它

x3 = pygame.surfarray.pixels3d(window)

来获取任何表面或屏幕上的所有像素(窗口变量)!
您可以在 NumPy 之类的东西中使用它,您可以使用代码

array = numpy.uint8(x3)

将表面图像作为 NumPy 数组获取,然后

im = PIL.Image.fromarray(array)

根据需要将其设为 Pillow 图像。然后您可以使用简单的 im.show() 来显示它,或者只是用它做任何事情。

I found a cool way;
you can use

x3 = pygame.surfarray.pixels3d(window)

to get all the pixels on any surface or the screen (the window variable)!
You can use this in things like NumPy where you can use the code

array = numpy.uint8(x3)

to get the image of the surface as a NumPy array and then

im = PIL.Image.fromarray(array)

to make it a Pillow image, if you want. Then you can show it with a simple im.show() or just do whatever with it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文