Массив NumPy видео изменяется по сравнению с оригиналом после записи в то же видео

У меня есть видео (test.mkv), которое я преобразовал в массив 4D NumPy - (кадр, высота, ширина, канал цвета). Мне даже удалось преобразовать этот массив обратно в то же видео (test_2.mkv), ничего не меняя. Однако после чтения этого нового test_2.mkv обратно в новый массив NumPy массив первого видео отличается от массива второго видео, т. е. их хэши не совпадают, и функция numpy.array_equal() возвращает false. Я пытался использовать как python-ffmpeg, так и scikit-video, но не может сопоставить массивы.

Попытка Python-ffmpeg:

import ffmpeg
import numpy as np
import hashlib

file_name = 'test.mkv'

# Get video dimensions and framerate
probe = ffmpeg.probe(file_name)
video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
width = int(video_stream['width'])
height = int(video_stream['height'])
frame_rate = video_stream['avg_frame_rate']

# Read video into buffer
out, error = (
    ffmpeg
        .input(file_name, threads=120)
        .output("pipe:", format='rawvideo', pix_fmt='rgb24')
        .run(capture_stdout=True)
)

# Convert video buffer to array
video = (
    np
        .frombuffer(out, np.uint8)
        .reshape([-1, height, width, 3])
)

# Convert array to buffer
video_buffer = (
    np.ndarray
        .flatten(video)
        .tobytes()
)

# Write buffer back into a video
process = (
    ffmpeg
        .input('pipe:', format='rawvideo', s='{}x{}'.format(width, height))
        .output("test_2.mkv", r=frame_rate)
        .overwrite_output()
        .run_async(pipe_stdin=True)
)
process.communicate(input=video_buffer)

# Read the newly written video
out_2, error = (
    ffmpeg
        .input("test_2.mkv", threads=40)
        .output("pipe:", format='rawvideo', pix_fmt='rgb24')
        .run(capture_stdout=True)
)

# Convert new video into array
video_2 = (
    np
        .frombuffer(out_2, np.uint8)
        .reshape([-1, height, width, 3])
)

# Video dimesions change
print(f'{video.shape} vs {video_2.shape}') # (844, 1080, 608, 3) vs (2025, 1080, 608, 3)
print(f'{np.array_equal(video, video_2)}') # False

# Hashes don't match
print(hashlib.sha256(bytes(video_2)).digest()) # b'\x88\x00\xc8\x0ed\x84!\x01\x9e\x08 \xd0U\x9a(\x02\x0b-\xeeA\xecU\xf7\xad0xa\x9e\\\xbck\xc3'
print(hashlib.sha256(bytes(video)).digest()) # b'\x9d\xc1\x07xh\x1b\x04I\xed\x906\xe57\xba\xf3\xf1k\x08\xfa\xf1\xfaM\x9a\xcf\xa9\t8\xf0\xc9\t\xa9\xb7'

Попытка Scikit-видео:

import skvideo.io as sk
import numpy as np

video_data = sk.vread('test.mkv')

sk.vwrite('test_2_ski.mkv', video_data)

video_data_2 = sk.vread('test_2_ski.mkv')

# Dimensions match but...
print(video_data.shape) # (844, 1080, 608, 3)
print(video_data_2.shape) # (844, 1080, 608, 3)

# ...array elements don't
print(np.array_equal(video_data, video_data_2)) # False

# Hashes don't match either
print(hashlib.sha256(bytes(video_2)).digest()) # b'\x8b?]\x8epD:\xd9B\x14\xc7\xba\xect\x15G\xfaRP\xde\xad&EC\x15\xc3\x07\n{a[\x80'
print(hashlib.sha256(bytes(video)).digest()) # b'\x9d\xc1\x07xh\x1b\x04I\xed\x906\xe57\xba\xf3\xf1k\x08\xfa\xf1\xfaM\x9a\xcf\xa9\t8\xf0\xc9\t\xa9\xb7'

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


person Rashiq    schedule 19.03.2021    source источник


Ответы (1)


Получение одного и того же хэша при записи и чтении видеофайла требует особого внимания.

Прежде чем сравнивать хэш, попробуйте сначала посмотреть видео.

Выполнение вашего кода дало мне следующий результат (первый кадр видео_2):
введите здесь описание изображения

Когда вход (первый кадр видео):
введите здесь описание изображения

Предлагаю следующие модификации:

  • Используйте контейнер AVI (вместо MKV) для хранения test_2 видео в необработанном видеоформате.
    Видеоконтейнер AVI изначально предназначен для хранения необработанного видео.
    Может существовать способ хранения необработанного или RGB-видео без потерь в Контейнер MKV, но я не знаю о таком варианте.
  • Установите формат входных пикселей видео test_2.
    Добавьте аргумент: pixel_format='rgb24'.
    Примечание. Я изменил его на pixel_format='bgr24', потому что AVI поддерживает bgr24, а не rgb24.
  • Выберите видео кодек без потерь для test_2 видео.
    Вы можете выбрать vcodec='rawvideo' (кодек rawvideo поддерживается AVI, но не поддерживается MKV).

Примечание.
Чтобы получить равный хэш, вам нужно найти видеокодек без потерь, который поддерживает формат пикселей rgb24 (или bgr24).
Большинство кодеков без потерь преобразует формат пикселей из RGB в YUV.
Преобразование RGB в YUV имеет ошибки округления, которые предотвращают равенство хэшей.
(Я полагаю, что есть способы обойти это, но это немного сложно).


Вот ваш полный код с небольшими изменениями:

import ffmpeg
import numpy as np
import hashlib

file_name = 'test.mkv'

# Get video dimensions and framerate
probe = ffmpeg.probe(file_name)
video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
width = int(video_stream['width'])
height = int(video_stream['height'])
frame_rate = video_stream['avg_frame_rate']

# Read video into buffer
out, error = (
    ffmpeg
        .input(file_name, threads=120)
        .output("pipe:", format='rawvideo', pix_fmt='bgr24')  # Select bgr24 instead of rgb24 (becasue raw AVI requires bgr24).
        .run(capture_stdout=True)
)

# Convert video buffer to array
video = (
    np
        .frombuffer(out, np.uint8)
        .reshape([-1, height, width, 3])
)

# Convert array to buffer
video_buffer = (
    np.ndarray
        .flatten(video)
        .tobytes()
)

# Write buffer back into a video
process = (
    ffmpeg
        .input('pipe:', format='rawvideo', s='{}x{}'.format(width, height), pixel_format='bgr24', r=frame_rate)  # Set input pixel format.
        .output("test_2.avi", vcodec='rawvideo')  # Select video code "rawvideo"
        .overwrite_output()
        .run_async(pipe_stdin=True)
)
process.communicate(input=video_buffer)

# Read the newly written video
out_2, error = (
    ffmpeg
        .input("test_2.avi", threads=40)
        .output("pipe:", format='rawvideo', pix_fmt='bgr24')
        .run(capture_stdout=True)
)

# Convert new video into array
video_2 = (
    np
        .frombuffer(out_2, np.uint8)
        .reshape([-1, height, width, 3])
)

# Video dimesions change
print(f'{video.shape} vs {video_2.shape}') # (844, 1080, 608, 3) vs (844, 1080, 608, 3)
print(f'{np.array_equal(video, video_2)}') # True

# Hashes do match
print(hashlib.sha256(bytes(video_2)).digest())
print(hashlib.sha256(bytes(video)).digest())

Результат (тот же хэш):

True

b"\xd1yy\x97\x8e\xce\x13\xbcI#\xd2PMP\x80(i+5\xe1\xcd\xab\xf3f\xbe\xcd\xd5'\xbaq\xdd\x9b"

b"\xd1yy\x97\x8e\xce\x13\xbcI#\xd2PMP\x80(i+5\xe1\xcd\xab\xf3f\xbe\xcd\xd5'\xbaq\xdd\x9b"


Обновлять:

Использование кодировщика ffv1:

Те же хэши получаются с помощью кодировщика ffv1 для .mkv.

  • Выберите vcodec='ffv1' в аргументах output().

Еще кое-что:

  • Переместите аргумент r=frame_rate из выходных аргументов в входные аргументы.
    Это не интуитивно... но при создании видео вне кадров частота кадров должна определяться как аргумент входных .

     # Write buffer back into a video
     process = (
         ffmpeg
             .input('pipe:', format='rawvideo', s='{}x{}'.format(width, height), pixel_format='rgb24', r=frame_rate)  # Set input pixel format.
             .output("test_2.mkv", vcodec='ffv1')  # Select video code "rawvideo"
             .overwrite_output()
             .run_async(pipe_stdin=True)
     )
    

Вот полный пример кода:

import ffmpeg
import numpy as np
import hashlib

file_name = 'test.mkv'

# Get video dimensions and framerate
probe = ffmpeg.probe(file_name)
video_stream = next((stream for stream in probe['streams'] if stream['codec_type'] == 'video'), None)
width = int(video_stream['width'])
height = int(video_stream['height'])
frame_rate = video_stream['avg_frame_rate']

# Read video into buffer
out, error = (
    ffmpeg
        .input(file_name, threads=120)
        .output("pipe:", format='rawvideo', pix_fmt='rgb24')  # Select rgb24 instead of rgb24 (becasue raw AVI requires rgb24).
        .run(capture_stdout=True)
)

# Convert video buffer to array
video = (
    np
        .frombuffer(out, np.uint8)
        .reshape([-1, height, width, 3])
)

# Convert array to buffer
video_buffer = (
    np.ndarray
        .flatten(video)
        .tobytes()
)

# Write buffer back into a video
process = (
    ffmpeg
        .input('pipe:', format='rawvideo', s='{}x{}'.format(width, height), pixel_format='rgb24', r=frame_rate)  # Set input pixel format.
        .output("test_2.mkv", vcodec='ffv1')  # Select video code "rawvideo"
        .overwrite_output()
        .run_async(pipe_stdin=True)
)
process.communicate(input=video_buffer)

# Read the newly written video
out_2, error = (
    ffmpeg
        .input("test_2.mkv", threads=40)
        .output("pipe:", format='rawvideo', pix_fmt='rgb24')
        .run(capture_stdout=True)
)

# Convert new video into array
video_2 = (
    np
        .frombuffer(out_2, np.uint8)
        .reshape([-1, height, width, 3])
)

# Video dimesions change
print(f'{video.shape} vs {video_2.shape}') # (844, 1080, 608, 3) vs (844, 1080, 608, 3)
print(f'{np.array_equal(video, video_2)}') # True

# Hashes do match
print(hashlib.sha256(bytes(video_2)).digest())
print(hashlib.sha256(bytes(video)).digest())

Результат (тот же хеш, используя ваш входной файл):

True

b'\x9d\xc1\x07xh\x1b\x04I\xed\x906\xe57\xba\xf3\xf1k\x08\xfa\xf1\xfaM\x9a\xcf\xa9\t8\xf0\xc9\t\xa9\xb7'

b'\x9d\xc1\x07xh\x1b\x04I\xed\x906\xe57\xba\xf3\xf1k\x08\xfa\xf1\xfaM\x9a\xcf\xa9\t8\xf0\xc9\t\xa9\xb7'


Обновлять:

Использование Scikit-Video:

В следующем примере кода используется Scikit-Video.
Я не смог найти способ выбрать кодек ffv1 без использования skvideo.io.FFmpegWriter.
В реализации используется цикл for для покадровой записи видео.

import skvideo.io as sk
import numpy as np
import hashlib

video_data = sk.vread('test.mkv')

# Create FFmpeg vidoe writer
writer = sk.FFmpegWriter('test_2_ski.mkv', outputdict={'-vcodec': 'ffv1' })

#sk.vwrite('test_2_ski.mkv', video_data)

# Write frame by frame in a loop
for i in range(video_data.shape[0]):
    writer.writeFrame(video_data[i, :, :, :])

writer.close()  # Close video writer.

video_data_2 = sk.vread('test_2_ski.mkv')

# Dimensions match
print(video_data.shape) # (844, 1080, 608, 3)
print(video_data_2.shape) # (844, 1080, 608, 3)

# Array elements match
print(np.array_equal(video_data, video_data_2))

# Hashes match
print(hashlib.sha256(bytes(video_data_2)).digest())
print(hashlib.sha256(bytes(video_data)).digest())
person Rotem    schedule 20.03.2021
comment
Те же хэши достигаются для кадров здесь с использованием кодировщика ffv1 для .mkv. Но я не мог понять, как указать это, даже после прочтения документации ffmpeg-python, которая, на мой взгляд, очень запутана. Вы, наверное, знаете, как это указать? Спасибо за ответ. Я немного поиграю с этим, а потом вернусь к вам. - person Rashiq; 25.03.2021
comment
Вы правы, кодек 'ffv1' работает хорошо. Выберите vcodec='ffv1' в аргументах output(). - person Rotem; 25.03.2021
comment
Я попытался использовать решение, которое вы предоставили слово в слово, но я не могу заставить его работать. Массивы NumPy отличаются, а затем и хэши. Формы массива: (844, 1080, 608, 3) vs (1013, 1080, 608, 3); Исходный хэш: b'\xdb\x9bn\xe2\xde\xd2\xdd\x0c\xd8\xad\x86\x81\x06\xbd\x0c\x87\xeb\x0c}c\x1a%I\xcd\x16% \x9d(Y\xba\x901'; Выходной хеш: b\xd1yy\x97\x8e\xce\x13\xbcI#\xd2PMP\x80(i+5\xe1\xcd\xab\xf3f\xbe\xcd\xd5' \xbaq\xdd\x9b. Я использовал файл здесь. Я не уверен, что я делаю неправильно. - person Rashiq; 29.03.2021
comment
Я нашел проблему (есть проблема с преобразованием частоты кадров - вы получаете 1013 кадров вместо 844). Решение: переместите аргумент r=frame_rate из выходных аргументов во входные аргументы. После обновления я получаю тот же хэш, используя ваш входной файл. Я обновил свой пост. - person Rotem; 29.03.2021
comment
Могу я узнать ваше мнение о моей попытке использовать Scikit-Video для этой задачи? Я не думаю, что понимаю, что мешает этому работать правильно. Большое спасибо за быстрый ответ. - person Rashiq; 29.03.2021
comment
Я никогда не использовал Scikit-Video. Вы знаете, как выбрать кодек ffv1? Поддерживается ли он Scikit-Video? - person Rotem; 29.03.2021
comment
Я добавил решение с помощью Scikit-Video, но оно использует FFmpegWriter и записывает видео кадр за кадром. - person Rotem; 30.03.2021
comment
Я как раз собирался сказать, что понятия не имею, что отправить в качестве ключа к методу outputdict для метода FFmpegWriter. Я попробовал encoder, как в документации ffmpeg, vcodec, как в вашем примере, и c:v. Постоянно получаю ошибку Broken Pipe. Как вы вычислили -vcodec, если его даже нет в документах? Пожалуйста, научи меня своим путям. Я хочу учиться. - person Rashiq; 30.03.2021
comment
Простой поиск в Google... Я нашел пример здесь. - person Rotem; 30.03.2021
comment
Давайте продолжим обсуждение в чате. - person Rashiq; 30.03.2021