(Я поставлю 500 рецензий на этот вопрос, как только он будет допущен - если вопрос не будет закрыт.)
Проблема в одном предложении
Чтение кадров с VideoCapture
продвигает видео гораздо дальше, чем предполагалось.
Объяснение
Мне нужно читать и анализировать кадры со скоростью 100 кадров в секунду (в соответствии с видеоформатом cv2
и VLC media player) между определенными интервалами времени. В следующем минимальном примере я пытаюсь прочитать все кадры за первые десять секунд трехминутного видео.
Я создаю объект cv2.VideoCapture
, из которого я читаю фреймы, пока не будет достигнута желаемая позиция в миллисекундах. В моем фактическом коде каждый кадр анализируется, но этот факт не имеет значения, чтобы продемонстрировать ошибку.
Проверка текущего кадра и миллисекунды позиции VideoCapture
после считывания кадров дает правильные значения, поэтому VideoCapture
думает находится в правильном положении, но это не так. Сохранение изображения последнего кадра чтения показывает, что моя итерация сильно превысила время назначения более чем на две минуты.
Что еще более странно, если я вручную установил миллисекундную позицию захвата с VideoCapture.set
до 10 секунд (то же самое значение VideoCapture.get
вернется после прочтения кадров) и сохранит изображение, видео находится в (почти ) правильное положение!
Демо-видео файл
Если вы хотите запустить MCVE, вам нужен видеофайл demo.avi. Вы можете скачать его ЗДЕСЬ.
MCVE
Этот MCVE тщательно обработан и прокомментирован. Пожалуйста, оставьте комментарий в вопросе, если что-то останется неясным.
Если вы используете OpenCV 3, вам нужно заменить все экземпляры cv2.cv.CV_
на cv2.
. (Проблема возникает в обеих версиях для меня.)
import cv2
# set up capture and print properties
print 'cv2 version = {}'.format(cv2.__version__)
cap = cv2.VideoCapture('demo.avi')
fps = cap.get(cv2.cv.CV_CAP_PROP_FPS)
pos_msec = cap.get(cv2.cv.CV_CAP_PROP_POS_MSEC)
pos_frames = cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES)
print ('initial attributes: fps = {}, pos_msec = {}, pos_frames = {}'
.format(fps, pos_msec, pos_frames))
# get first frame and save as picture
_, frame = cap.read()
cv2.imwrite('first_frame.png', frame)
# advance 10 seconds, that 100*10 = 1000 frames at 100 fps
for _ in range(1000):
_, frame = cap.read()
# in the actual code, the frame is now analyzed
# save a picture of the current frame
cv2.imwrite('after_iteration.png', frame)
# print properties after iteration
pos_msec = cap.get(cv2.cv.CV_CAP_PROP_POS_MSEC)
pos_frames = cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES)
print ('attributes after iteration: pos_msec = {}, pos_frames = {}'
.format(pos_msec, pos_frames))
# assert that the capture (thinks it) is where it is supposed to be
# (assertions succeed)
assert pos_frames == 1000 + 1 # (+1: iteration started with second frame)
assert pos_msec == 10000 + 10
# manually set the capture to msec position 10010
# note that this should change absolutely nothing in theory
cap.set(cv2.cv.CV_CAP_PROP_POS_MSEC, 10010)
# print properties again to be extra sure
pos_msec = cap.get(cv2.cv.CV_CAP_PROP_POS_MSEC)
pos_frames = cap.get(cv2.cv.CV_CAP_PROP_POS_FRAMES)
print ('attributes after setting msec pos manually: pos_msec = {}, pos_frames = {}'
.format(pos_msec, pos_frames))
# save a picture of the next frame, should show the same clock as
# previously taken image - but does not
_, frame = cap.read()
cv2.imwrite('after_setting.png', frame)
Выход MCVE
Операторы print
производят следующий вывод.
cv2 version = 2.4.9.1
начальные атрибуты: fps = 100.0, pos_msec = 0.0, pos_frames = 0.0
атрибуты после чтения: pos_msec = 10010.0, pos_frames = 1001.0
атрибуты после установки msec pos вручную: pos_msec = 10010.0, pos_frames = 1001.0
Как вы можете видеть, все свойства имеют ожидаемые значения.
imwrite
сохраняет следующие снимки.
Во второй картинке вы можете увидеть эту проблему. Цель 9:26:15 (часы реального времени на снимке) пропущена более двух минут. Установка целевого времени вручную (третье изображение) устанавливает видео в (почти) правильное положение.
Что я делаю неправильно и как его исправить?
Пробовал до сих пор
cv2 2.4.9.1 @Ubuntu 16.04
cv2 2.4.13 @Scientific Linux 7.3 (три компьютера)
cv2 3.1.0 @Scientific Linux 7.3 (три компьютера)
Создание захвата с помощью
cap = cv2.VideoCapture('demo.avi', apiPreference=cv2.CAP_FFMPEG)
и
cap = cv2.VideoCapture('demo.avi', apiPreference=cv2.CAP_GSTREAMER)
в OpenCV 3 (версия 2 не имеет аргумента apiPreference
).
Использование cv2.CAP_GSTREAMER
занимает очень много времени (около 2-3 минут для запуска MCVE), но оба api-предпочтения создают одинаковые неправильные изображения.
При использовании ffmpeg
непосредственно для чтения фреймов (кредит этого учебника) создаются правильные выходные изображения.
import numpy as np
import subprocess as sp
import pylab
# video properties
path = './demo.avi'
resolution = (593, 792)
framesize = resolution[0]*resolution[1]*3
# set up pipe
FFMPEG_BIN = "ffmpeg"
command = [FFMPEG_BIN,
'-i', path,
'-f', 'image2pipe',
'-pix_fmt', 'rgb24',
'-vcodec', 'rawvideo', '-']
pipe = sp.Popen(command, stdout = sp.PIPE, bufsize=10**8)
# read first frame and save as image
raw_image = pipe.stdout.read(framesize)
image = np.fromstring(raw_image, dtype='uint8')
image = image.reshape(resolution[0], resolution[1], 3)
pylab.imshow(image)
pylab.savefig('first_frame_ffmpeg_only.png')
pipe.stdout.flush()
# forward 1000 frames
for _ in range(1000):
raw_image = pipe.stdout.read(framesize)
pipe.stdout.flush()
# save frame 1001
image = np.fromstring(raw_image, dtype='uint8')
image = image.reshape(resolution[0], resolution[1], 3)
pylab.imshow(image)
pylab.savefig('frame_1001_ffmpeg_only.png')
pipe.terminate()
Это дает правильный результат! (Правильная временная метка 9:26:15)
Дополнительная информация
В комментариях меня попросил мой файл cvconfig.h
. Кажется, у меня есть этот файл для cv2 версии 3.1.0 под /opt/opencv/3.1.0/include/opencv2/cvconfig.h
.
ЗДЕСЬ является вставкой этого файла.
В случае, если это помогает, мне удалось извлечь следующую видеоинформацию с помощью VideoCapture.get
.
яркость 0.0
контраст 0.0
convert_rgb 0.0
экспозиция 0.0
формат 0.0
fourcc 1684633187.0
fps 100.0
frame_count 18000.0
frame_height 593.0
frame_width 792.0
прибыль 0.0
hue 0.0
режим 0.0
openni_baseline 0.0
openni_focal_length 0.0
openni_frame_max_depth 0.0
openni_output_mode 0.0
openni_registration 0.0
pos_avi_ratio 0.01
pos_frames 0.0
pos_msec 0.0
ректификация 0.0
насыщенность 0.0 -