Есть слишком много аспектов, чтобы сделать вещи быстрее, я сосредоточусь только на многопроцессорной части.
Поскольку вы не хотите читать все видео за раз, мы должны прочитать видео кадр за кадром.
Я буду использовать 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 с .
Теперь мы можем использовать многопроцессорный подход. Идея может быть обобщена на следующем рисунке.
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. Определенно, есть некоторые узкие места, которых можно избежать, чтобы он работал быстрее.
Единственная проблема с моей реализацией заключается в том, что в регионах, где видео было сегментировано, отсутствует 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.