Сегодня я расскажу об интересном и довольно простом эксперименте со временем в контексте компьютерного зрения. Мы попытаемся понять, как заставить машину двигаться из будущего в прошлое, и в чем разница между динамичным боевиком и повествовательной драмой.

Когда мы работаем с картинкой, все довольно просто - она ​​имеет высоту и ширину, и мы сразу все в ней видим. Стоит отметить, что мы «читаем» картинку на небольших участках, начиная с областей интереса, а затем продвигаясь к периферии. Сверточная сеть работает точно так же, но она считывает изображение блоками с размерами, равными ядру свертки, начиная с угла изображения и постепенно сдвигаясь на определенное расстояние.

С видео дело обстоит немного сложнее. Глядя на один кадр видео, мы не видим и не знаем, что происходит в следующем. Так бывает с каждым кадром. Это происходит потому, что у нас есть третье измерение - время.

Если бы у нас были трехмерные данные, мы могли бы перейти от наших любимых двумерных сверток к трехмерным. Это можно было бы сделать, но с видео у оси времени есть две проблемы по сравнению с шириной и высотой. Во-первых, размер выборки не является постоянным во времени. Например, фильм с постоянной высотой и шириной 640x480 может иметь разную продолжительность. Даже если разрешение изменится, мы можем изменить его размер, не теряя сюжетной идеи. «Изменение размера» по времени - гораздо более сложная задача.

Во-вторых, временная составляющая может быть очень большой. Например, 1,5-часовой фильм содержит 135 000 кадров (90 минут * 60 секунд * 25 кадров). Одновременно обрабатывать такой объем 3D данных проблематично. Придется разделить видео на части и обработать каждую отдельно. Более того, каждая часть не будет иметь информации о других.

Одна из возможностей - использовать повторяющиеся сети, например, LSTM в сочетании с экстрактором функций. Однако в текущем эксперименте мы заинтересованы в поиске более наглядного представления времени, чтобы лучше понять принципы видеопоследовательности и трехмерной свертки.

В нашем эксперименте мы не будем использовать ни LSTM, ни 3D-свертки, и вообще на время забудем о глубоком обучении. Сегодня мы сами станем сверточной сетью.

Как было сказано выше, при добавлении времени смена объектов в кадрах по этой оси становится неочевидной. Как можно сразу смотреть на время? Давайте просто поменяем время с осью ширины, оставив ось высоты на месте. Таким образом, в одном кадре мы увидим все, что происходило на протяжении всего видео, а движение объекта по сцене по ширине будет отображаться как его трансформация во времени в видеопоследовательности. Возможно, это звучит сложно? Давайте посмотрим на это на практике.

Для этого нам нужно последовательно собрать все кадры. У нас получится прямоугольный параллелепипед с размерами Ширина x Высота x Время. После транспонирования получаем параллелепипед с размерами Время x Высота x Ширина. Время будет расположено по ширине изображения, Ширина будет играть роль Времени.

Изображения выше являются кадрами одного и того же видео. Результат выглядит немного странно, и я объясню это позже. Теперь главное понять принцип трансформации.

Давайте посмотрим, как это реализовать на Python. Обратите внимание: не рекомендуется сразу снимать длинное видео или видео с высоким разрешением (FullHD, 4k). Возможно, вам не хватит места в ОЗУ. Для простой реализации я работаю со всеми кадрами видеопоследовательности, поскольку для этого требуется поменять местами оси. А если вам нужно обработать большой объем данных, а они не помещаются в ОЗУ, то вам нужно будет сохранить все кадры на жестком диске и реализовать построчное чтение.

import cv2
import numpy as np
filename = 'input_video.mp4'
cap = cv2.VideoCapture(filename + '.mp4')
frameCount = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
frameWidth = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
frameHeight = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(frameCount, frameHeight, frameWidth)

В приведенном ниже коде мы создаем буфер и добавляем в него все кадры. Переменная N нужна для того, чтобы в выходной буфер попадали не все кадры, а каждый N-й. Это может быть полезно, если вы хотите прорезать видеопоследовательность. Если N равно 1, мы вычитаем все кадры.

N = 1
buf = np.empty((int(frameCount/N), frameHeight, frameWidth, 3), np.dtype('uint8'))
fc = 0
bc = 0
ret = True
while (fc < frameCount and ret):
    if (fc % N) == N-1:
        ret, buf[bc] = cap.read()
        bc += 1
    else:
        ret, _ = cap.read()
    fc += 1
cap.release()

Для удобства работы с python и cv2 я использую обратное положение осей Time x Height x Width. Мы выполняем преобразование вокруг оси высоты, меняя местами время и ширину, как в приведенном ниже коде:

buf = np.transpose(buf, (2, 1, 0, 3))

Однако, если вы хотите поменять местами время и высоту, используется следующее преобразование:

buf = np.transpose(buf, (1, 0, 2, 3))

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

Затем вы можете просмотреть его в средстве визуализации видео:

i = 0
while(True):
    frame = cv2.resize(buf[i], dsize=(frameWidth*3, frameHeight), interpolation=cv2.INTER_CUBIC)
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    
    i += 1
    if (i >= buf.shape[0]):
        i = 0
cv2.destroyAllWindows()

Или сохраните как файл:

writer = cv2.VideoWriter(filename + '_result.mp4', cv2.VideoWriter_fourcc(*'MP4V'), 25, (frameWidth*3, frameHeight))
for i in range(buf.shape[0]):
    frame = cv2.resize(buf[i], dsize=(frameWidth*3, frameHeight), interpolation=cv2.INTER_CUBIC)
    writer.write(frame)
writer.release()

Обратите внимание, что ширина выходного видео будет равна количеству кадров в исходном видео. Поэтому для удобства просмотра я масштабирую выходное видео под разрешение входа.

Посмотрим на результаты.

На видео видно, как две машины быстро проезжают мимо идущих людей. Все, кто стоит на видео, не видны в выходном видео. Такой способ проецирования времени показывает все, но мы воспринимаем только движущиеся объекты. Более того, чем быстрее движется объект, тем короче он в выходном видео. Чем медленнее он движется, тем шире. Если объект остановится, он будет размазан по всей ширине изображения.

Это очень хорошо видно из следующих нескольких видеороликов. Обратите внимание на два поезда, идущие снизу, по сравнению с машинами.

В этом видео уже проехало больше машин, и мы видим их все сразу уже в первом кадре.

Что интересно, мы сканируем слева направо. Следовательно, все, кто движется в одном направлении в выходном видео, будут двигаться сзади вперед. Это хорошо видно здесь:

Также в этом случае у нас будет прошлое слева, а будущее - справа. Это связано с тем же методом сканирования слева направо. Некоторые объекты могут казаться движущимися из будущего в прошлое. Это просто их местонахождение в указанное время.

А здесь вы можете увидеть, как картинка из окна иллюминатора разворачивается в панораму города с эффектом параллакса.

А если развернуть не часть города, а всю Землю.

Вот еще один интересный пример:

Как вы думаете, что мы получим в результате?

Также интересно посмотреть, какой длины потоки выглядят в этом случае. Я взял для эксперимента два трейлера, потому что они длиннее, чем видео выше, примерно на 2–3 минуты. Более того, они очень разные по содержанию.

Первый - «Искупление Шоушенка» (1994). На видео показаны переходы между сценами. Это очень интересный эффект. Таким образом, вы можете сразу разделить ролик на кадры в первом кадре. Я увеличил ширину кадра выходного видео в 3 раза, чтобы было лучше видно.

Второй - фильм «Железный человек» (2008). Сразу видно, что кадров в действии намного больше. Ощущение такое, что кадры меняются каждые пару секунд. Это современный экшн.

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

В нашем блоге Машинное обучение Deelvin есть еще много интересных статей. Надеюсь, вам будет интересно их прочитать. Также не забудьте посетить наш сайт deelvin.com.

Видео взяты с www.pexels.com