Массив NumPy видео отличается от оригинала после записи в то же видео
У меня есть видео (
test.mkv
), который я преобразовал в массив 4D NumPy - (рамка, высота, ширина, color_channel). Мне даже удалось преобразовать этот массив обратно в тот же video (), ничего не изменив. Однако, прочитав это новое,
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'
Я не понимаю, в чем я ошибаюсь, и в обеих соответствующих документах не указывается, как выполнить эту конкретную задачу. Любая помощь приветствуется. Спасибо.
1 ответ
Получение одного и того же хеша при записи и чтении видеофайла требует особого внимания.
Прежде чем сравнивать хеш, попробуйте сначала посмотреть видео.
Выполнение вашего кода дало мне следующий результат (первый кадр видео_2):
Когда вход (первый кадр видео):
Предлагаю следующие модификации:
- Используйте контейнер AVI (вместо MKV) для хранения видео в формате сырого видео.
Видеоконтейнер AVI изначально предназначен для хранения необработанного видео.
Может быть способ хранения необработанного RGB-видео или RGB-видео без потерь в контейнере MKV, но мне неизвестен такой вариант. - Установите формат входных пикселей видео.
Добавьте аргумент:pixel_format='rgb24'
.
Примечание: я изменил его наpixel_format='bgr24'
, потому что AVI поддерживает и нет. - Выберите видео кодек без потерь для
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') # Set input pixel format.
.output("test_2.avi", vcodec='rawvideo', r=frame_rate) # 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 (2025, 1080, 608, 3)
print(f'{np.array_equal(video, video_2)}') # False
# Hashes do match
print(hashlib.sha256(bytes(video_2)).digest())
print(hashlib.sha256(bytes(video)).digest())
Результат (тот же хеш):
True
b'R\x11\x14\xe1e\x0bf\xc4\xde\xa5\x99\xb1\xcf\xf7\x03q\xe90k\x86\x07\xa1\x15z\xf4\xa1+\x92Z*F\xd0'