How to use Python multiprocessing to prepare image

2019-09-01 07:01发布

问题:

I'm making a slideshow app with that oh-so-naught-ies pan and zoom effect. I'm using pygame.

The main display is therefore realtime 30fps+, and I don't want it stuttering when it has to load a new image - this takes it over 1/30th of a second.

So I wanted to use some parallel process to prepare the images and feed the main process with these objects, which are instances of a class.

I've tried with threading and with multiprocess. Threading 'works' but it's still jumpy (I blame python) - the whole thing slows down when the thread is busy! So the code ran but it didn't meet the goal of allowing a continually smooth display.

But multiprocess segfaults (pygame parachute) as soon as I call a method on the received prepared image from the main process. I've tried pipe and queue communications - both result in the same problem. The method runs up until it does a call to

sized = pygame.transform.scale(self.image, newsize )

Then there's the segfault. This class does not have any dependencies from the main process.

Does pygame just not like multiprocessing? Is there another way that is compatible? Is there a way to 'nice' secondary threads that might stop the threading method performing?

Any help greatly appreciated. Happy to post more code, just ask in comments, but didn't want to dump a big listing here unless needed.

Thanks in advance!

EDIT

This is as short as I could make it. You need to provide three paths to jpeg files in the constructor at the bottom.

#!/usr/bin/env python2
import pygame
import sys
import time
import re
import os
import pickle
from random import randrange, shuffle
from multiprocessing import Process, Pipe
import Queue


class Img:
    """The image objects I need to pass around"""
    def __init__(self, filename=None):
        image = pygame.image.load(filename).convert()
        self.image = pygame.transform.scale(image, (640,480))

    def getSurface(self):
        """Get a surface, blit our image onto it in right place."""
        surface = pygame.Surface((640,480))
        # xxx this next command fails
        sized = pygame.transform.scale(self.image, (640,480))
        surface.blit(sized, (0,0))
        return surface

class Floaty:
    """demo"""
    def __init__(self, fileList):
        self.fileList = fileList
        pygame.init()
        self.screen = pygame.display.set_mode((640,480))

        # open the first image to get it going
        self.img = Img(self.fileList.pop())

        # Set up parallel process for opening images
        self.parent_conn, child_conn = Pipe()
        self.feeder = Process(target=asyncPrep, args=(child_conn,))
        self.feeder.start()

    def draw(self):
        """draw the image"""
        # collect image ready-prepared by other process
        if self.parent_conn.poll():
            self.img = self.parent_conn.recv()
            print ("received ", self.img)

        # request new image
        self.parent_conn.send(self.fileList.pop())

        self.screen.blit(self.img.getSurface(), (0, 0))
        pygame.display.flip()

def asyncPrep(conn):
    """load up the files"""

    while True:
        if conn.poll(1):
            filename = conn.recv()
            print ("doing ", filename)
            img = Img(filename)
            conn.send(img)


if __name__ == '__main__':
    fileList = ['/path/to/a.jpg', 'path/to/b.jpg', 'path/to/c.jpg']
    f = Floaty(fileList)
    clock = pygame.time.Clock()
    while 1:
        f.draw()
        clock.tick(4);

When I run this (Python 2.7.6) I get:

('doing ', '/path/to/a.jpg')
('received ', <__main__.Img instance at 0x7f2dbde2ce60>)
('doing ', '/path/to/b.jpg')
Fatal Python error: (pygame parachute) Segmentation Fault
zsh: abort (core dumped)

回答1:

I solved this using multiprocessing by making the Img class load the image into a string buffer, stored in a property, then adding a postReceive method which loads it into a surface stored in the image property.

The postReceive method is called by the parent process after receiving the Img object from the child.

Therefore the object created by the child is not bound to anything pygame-y.

    self.imageBuffer = pygame.image.tostring(
      pygame.transform.scale(image, (640,480)),
      'RGB')

Then, the new method in Img is simply:

def: postReceive(self):
    self.image = pygame.image.frombuffer( self.imagebuffer, 'RGB' )

Add a call to this here:

    # collect image ready-prepared by other process
    if self.parent_conn.poll():
        self.img = self.parent_conn.recv().postReceive()
        print ("received ", self.img)