Многопроцессорная обработка видео - PullRequest
1 голос
/ 30 апреля 2020

Я бы хотел обработать видео на соседних кадрах. Более конкретно, c, я хотел бы вычислить среднеквадратичную ошибку между соседними кадрами:

mean_squared_error(prev_frame,frame)

Я знаю, как вычислить это линейным прямым способом: я использую imutils пакет для использования очереди для разделения загрузки кадров и их обработки. Храня их в очереди, мне не нужно ждать их, прежде чем я смогу их обработать. ... но я хочу быть еще быстрее ...

# import the necessary packages to read the video
import imutils
from imutils.video import FileVideoStream
# package to compute mean squared errror
from skimage.metrics import mean_squared_error

if __name__ == '__main__':

    # SPECIFY PATH TO VIDEO FILE
    file = "VIDEO_PATH.mp4" 

    # START IMUTILS VIDEO STREAM
    print("[INFO] starting video file thread...")
    fvs = FileVideoStream(path_video, transform=transform_image).start()

    # INITALIZE LIST to store the results
    mean_square_error_list = []

    # READ PREVIOUS FRAME
    prev_frame = fvs.read()

    # LOOP over frames from the video file stream
    while fvs.more():

        # GRAP THE NEXT FRAME from the threaded video file stream
        frame = fvs.read()

        # COMPUTE the metric
        metric_val = mean_squared_error(prev_frame,frame)
        mean_square_error_list.append(1-metric_val) # Append to list

        # UPDATE previous frame variable 
        prev_frame = frame

Теперь мой вопрос: как я могу выполнить многократную обработку вычисления метри c, чтобы увеличить скорость и время сохранения?

Моя операционная система Windows 10, и я использую python 3.8.0

1 Ответ

3 голосов
/ 01 мая 2020

Есть слишком много аспектов, чтобы сделать вещи быстрее, я сосредоточусь только на многопроцессорной части.

Поскольку вы не хотите читать все видео за раз, мы должны прочитать видео кадр за кадром.

Я буду использовать opencv (cv2), numpy для чтения кадров, вычисления mse и сохранения mse на диск.

Во-первых, мы можем начать без какой-либо многопроцессорной обработки, чтобы мы могли сравнить наши результаты. Я использую видео 1920 с разрешением 1080 , 60 FPS, продолжительность: 1: 29 , размер: 100 МБ.

import cv2
import sys
import time

import numpy as np
import subprocess as sp
import multiprocessing as mp

filename = '2.mp4'

def process_video():    
    cap = cv2.VideoCapture(filename)

    proc_frames = 0

    mse = []
    prev_frame = None
    ret = True
    while ret:
        ret, frame = cap.read() # reading frames sequentially
        if ret == False:
            break

        if not (prev_frame is None):
            c_mse = np.mean(np.square(prev_frame-frame))
            mse.append(c_mse)

        prev_frame = frame

        proc_frames += 1

    np.save('data/' + 'sp' + '.npy', np.array(mse))

    cap.release()
    return


if __name__ == "__main__":

    t1 = time.time()

    process_video()

    t2 = time.time()

    print(t2-t1)

В моей системе она работает в течение 142 с .

Теперь мы можем использовать многопроцессорный подход. Идея может быть обобщена на следующем рисунке.


opencv multiprocessing frames

GIF кредит: Google


Мы делаем несколько сегментов (основываясь на том, сколько у нас процессорных ядер) и параллельно обрабатываем эти сегментированные кадры.

import cv2
import sys
import time

import numpy as np
import subprocess as sp
import multiprocessing as mp

filename = '2.mp4'

def process_video(group_number):    
    cap = cv2.VideoCapture(filename)
    num_processes = mp.cpu_count()
    frame_jump_unit = cap.get(cv2.CAP_PROP_FRAME_COUNT) // num_processes
    cap.set(cv2.CAP_PROP_POS_FRAMES, frame_jump_unit * group_number)
    proc_frames = 0

    mse = []
    prev_frame = None
    while proc_frames < frame_jump_unit:
        ret, frame = cap.read()
        if ret == False:
            break

        if not (prev_frame is None):
            c_mse = np.mean(np.square(prev_frame-frame))
            mse.append(c_mse)

        prev_frame = frame

        proc_frames += 1

    np.save('data/' + str(group_number) + '.npy', np.array(mse))

    cap.release()
    return


if __name__ == "__main__":

    t1 = time.time()

    num_processes =  mp.cpu_count()
    print(f'CPU: {num_processes}')

    # only meta-data
    cap = cv2.VideoCapture(filename)

    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fps = cap.get(cv2.CAP_PROP_FPS)
    frame_jump_unit = cap.get(cv2.CAP_PROP_FRAME_COUNT) // num_processes
    cap.release()

    p = mp.Pool(num_processes)
    p.map(process_video, range(num_processes))

    # merging



    # the missing mse will be 

    final_mse = []
    for i in range(num_processes):
        na = np.load(f'data/{i}.npy')
        final_mse.extend(na)


        try:
            cap = cv2.VideoCapture(filename) # you could also take it outside the loop to reduce some overhead
            frame_no = (frame_jump_unit) * (i+1) - 1
            print(frame_no)
            cap.set(1, frame_no)
            _, frame1 = cap.read()
            #cap.set(1, ((frame_jump_unit) * (i+1)))
            _, frame2 = cap.read()
            c_mse = np.mean(np.square(frame1-frame2))
            final_mse.append(c_mse)
            cap.release()
        except:
            print('failed in 1 case')
            # in the last few frames, nothing left
            pass




    t2 = time.time()

    print(t2-t1)

    np.save(f'data/final_mse.npy', np.array(final_mse))


Я использую numpy save, чтобы сохранить частичные результаты, вы можете попробуйте что-нибудь получше.

Этот работает на 49,56 с с моим cpu_count = 12. Определенно, есть некоторые узкие места, которых можно избежать, чтобы он работал быстрее.

enter image description here

Единственная проблема с моей реализацией заключается в том, что в регионах, где видео было сегментировано, отсутствует mse, его довольно легко добавить. Поскольку мы можем индексировать отдельные кадры в любом месте с помощью OpenCV в O (1), мы можем просто go к этим местоположениям и рассчитать mse отдельно и объединиться в окончательное решение. [Проверьте обновленный код, который исправляет объединяющуюся часть]

Вы можете написать простую проверку работоспособности, чтобы убедиться, что оба предоставляют одинаковый результат.

import numpy as np

a = np.load('data/sp.npy')

b = np.load('data/final_mse.npy')

print(a.shape)

print(b.shape)

print(a[:10])

print(b[:10])

for i in range(len(a)):
    if a[i] != b[i]:
        print(i)

Теперь, некоторые дополнительные ускорения могут быть получены при использовании скомпилированного CUDA opencv, ffmpeg, добавления механизма очередей и многопроцессорной обработки и т. д. c.

...