85368
やりたいこととしては「メイン処理を行いながら、動画の読み込みをサイズ上限を超えないように行う」という処理を行いたいです。

試したことは以下の通りです。

  * Python標準の「futures.ThreadPoolExecutor」を使ってみたところFPSが極端に落ちてしまう  
（動画読み込み処理が完了するとFPSが元に戻るので、スレッドだとメイン処理と読み込み処理が交互に動いてると見て間違いなさそうです）

  * Python標準の「futures.ProcessPoolExecutor」を使うとグローバル変数がメイン処理と読み込み処理で共有できない

この場合、一番効率的な並行処理はどういったものがあるのか、ご助力願いたいです。

以下、サンプルソースを記載します。

```

    ## スレッドのテスト
    import cv2
    import os
    import sys
    import numpy as np
    from concurrent import futures
    import pygame
    from pygame.locals import *
    import traceback
    
    MOVIE_PATH = os.path.join(os.path.dirname(__file__), "../img/movie")
    
    test_mp4 = cv2.VideoCapture(MOVIE_PATH+"/test.mp4")
    mp4_fps = test_mp4.get(cv2.CAP_PROP_FPS)
    frame_list = []
    is_game_end = False
    
    def mp4_reset():
        global test_mp4, frame_list
        test_mp4.set(cv2.CAP_PROP_POS_FRAMES, 0)
        frame_list = []
    
    def mp4_read():
        global test_mp4, frame_list, is_game_end
        while(1):
            if is_game_end:
                break
            if sys.getsizeof(frame_list) > 1000:
                continue
            ret, frame = test_mp4.read()
            if ret:
                frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                frame = np.rot90(frame)
                frame = np.flipud(frame)
                frame_list.append(frame)
            print(sys.getsizeof(frame_list))
        print("mp4_read end")
    
    
    def main():
        global frame_list, is_game_end
        pygame.init()
        screen = pygame.display.set_mode((1280,720))
        clock = pygame.time.Clock()
        font = pygame.font.SysFont(None, 50)
        try :
            while(1):
                screen.fill((100,100,100))
    
                for event in pygame.event.get():
                    if event.type == QUIT:
                        is_game_end = True
                        pygame.quit()
                        sys.exit()
                    elif event.type == KEYDOWN:
                        if event.key == K_SPACE:
                            mp4_reset()
                
                if len(frame_list) > 0:
                    frame = frame_list.pop(0)
                    frame = pygame.surfarray.make_surface(frame)
                    screen.blit(frame, (0,0))
                
                text = font.render(f"frame_size:{len(frame_list)}, fps:{clock.get_fps()}", True, (255,255,255))
                screen.blit(text, (0,0))
    
                pygame.display.update()
                clock.tick(60)
        except Exception as e:
            is_game_end = True
            print(e)
            print(traceback.format_exc())
    
    if __name__ == "__main__":
        future_list = []
        ## ▼マルチプロセスで動かそうとしたもの
        with futures.ProcessPoolExecutor(max_workers=2) as executor:
            future_list.append(executor.submit(mp4_read))
            future_list.append(executor.submit(main))
            _ = futures.as_completed(fs=future_list)
        ## ▼スレッドを使用した時のコード
        # with futures.ThreadPoolExecutor(max_workers=2) as executor:
        #     future_list.append(executor.submit(mp4_read))
        #     future_list.append(executor.submit(main))
        #     _ = futures.as_completed(fs=future_list)
        print("end")
    
```

環境は以下の通りです

  * python : 3.9.7
  * opencv-python : 4.5.4.60
  * numpy : 1.21.4
  * pygame : 2.1.0

▼PCスペック（参考）

  * OS : Windows10 pro
  * CPU : Ryzen 9 5950X
  * RAM : 64GB
  * GPU : GTX 1050ti
CC BY-SA 4.0
2021-12-26T08:22:00.813
85337
2021-12-28T08:07:44.953
2021-12-26T11:52:16.150
3060
50657
question
python python3 pygame
pygameでメイン処理と動画読み込み処理を並行して行いたい
自己解決できたかもしれないので共有します

▼参考  
<https://eqseqs.hatenablog.com/entry/2020/09/16/100000>

まず上記の記事を参考にアレンジしてビデオ読み込みスレッド用のクラスを作ります

▼ video.py

```

    import threading
    import queue
    import os
    import cv2
    import numpy as np
    import time
    
    MOVIE_PATH = os.path.join(os.path.dirname(__file__), "../img/movie")
    
    class ThreadingVideoCapture:
        def __init__(self, src, max_queue_size=256):
            self.video = cv2.VideoCapture(MOVIE_PATH+"/"+src)
            self.q = queue.Queue(maxsize=max_queue_size)
            self.stopped = False
            print("video init")
    
        def start(self):
            thread = threading.Thread(target=self.update, daemon=True)
            thread.start()
            return self
    
        def update(self):
            while True:
    
                if self.stopped:
                    return
    
                if not self.q.full():
    
                    ok, frame = self.video.read()
    
                    if not ok:
                        self.stop()
                        return
                    
                    frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    frame = np.rot90(frame)
                    frame = np.flipud(frame)
                    self.q.put((ok, frame))
                else:
                    time.sleep(1)
    
        def read(self):
            return self.q.get() if not self.q.empty() else None
    
        def stop(self):
            self.stopped = True
    
        def release(self):
            self.stopped = True
            self.video.release()
    
        def isOpened(self):
            return self.video.isOpened()
    
        def get(self, i):
            return self.video.get(i)
    
        def q_size(self):
            return self.q.qsize()
    
```

このクラスを活用してpygameに描画します

▼ main.py

```

    ## スレッドのテスト
    import cv2
    import pygame
    from pygame.locals import *
    from common import *
    import traceback
    
    import video
    
    test_mp4 = video.ThreadingVideoCapture("test.mp4",100)
    if not test_mp4.isOpened():
        raise Exception
    mp4_fps = test_mp4.get(cv2.CAP_PROP_FPS)
    test_mp4.start()
    is_game_end = False
    
    def mp4_reset():
        global test_mp4
        test_mp4.release()
        test_mp4 = video.ThreadingVideoCapture("test.mp4",100)
        test_mp4.start()
    
    def main():
        global test_mp4, is_game_end
        pygame.init()
        screen = pygame.display.set_mode((SCREEN_W,SCREEN_H))
        clock = pygame.time.Clock()
        font = pygame.font.SysFont(None, 50)
        try :
            while(not is_game_end):
                screen.fill((100,100,100))
    
                for event in pygame.event.get():
                    if event.type == QUIT:
                        is_game_end = True
                        pygame.quit()
                    elif event.type == KEYDOWN:
                        if event.key == K_SPACE:
                            mp4_reset()
                
                if is_game_end :
                    break            
                
                read_data = test_mp4.read()
                if read_data != None and read_data[0]:
                    frame = pygame.surfarray.make_surface(read_data[1])
                    screen.blit(frame, (0,0))
                
                text = font.render(f"frame_size:{test_mp4.q_size()}, fps:{clock.get_fps()}", True, COLOR_WHITE)
                screen.blit(text, (0,0))
    
                pygame.display.update()
                clock.tick(60)
        except Exception as e:
            is_game_end = True
            print(e)
            print(traceback.format_exc())
    
    if __name__ == "__main__":
        main()
        print("end")
    
```

特に追加で外部ライブラリも必要なく要件を満たせました  
他にいい方法があれば回答お待ちしています
CC BY-SA 4.0
2021-12-28T04:33:05.353
85368
2021-12-28T08:07:44.953
2021-12-28T08:07:44.953
50657
50657
85337
answer