写入同一视频后,视频的 NumPy 数组与原始视频不同 Python-ffmpeg 尝试:Scikit 视频尝试:

问题描述

我有一个已转换为 4D NumPy 数组的视频 (test.mkv) -(帧、高度、宽度、颜色通道)。我什至设法将该数组转换回相同的视频 (test_2.mkv),而无需更改任何内容。但是,在将这个新的 test_2.mkv 读回新的 NumPy 数组后,第一个视频的数组与第二个视频的数组不同,即它们的哈希不匹配,numpy.array_equal() 函数返回 false .我曾尝试同时使用 python-ffmpegscikit-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:',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:",pix_fmt='rgb24')
        .run(capture_stdout=True)
)

# Convert new video into array
video_2 = (
    np
        .frombuffer(out_2,3])
)

# Video dimesions change
print(f'{video.shape} vs {video_2.shape}') # (844,1080,608,3) vs (2025,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,3)
print(video_data_2.shape) # (844,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'

我不明白我哪里出错了,两个各自的文档都没有强调如何完成这个特定的任务。任何帮助表示赞赏。谢谢。

解决方法

在写入和读取视频文件时需要仔细注意获得相同的哈希值。

在比较hash之前,先试试看视频。

执行你的代码给了我以下输出(video_2的第一帧):
enter image description here

当输入(视频的第一帧)是:
enter image description here

我建议进行以下修改:

  • 使用 AVI 容器(而不是 MKV)以原始视频格式存储 test_2 视频。
    AVI 视频容器最初是为存储原始视频而设计的。
    可能有一种方法可以在 MKV 容器中存储原始或无损 RGB 视频,但我不知道有这样的选项。
  • 设置test_2视频的输入像素格式。
    添加参数:pixel_format='rgb24'.
    注意:我将其修改为 pixel_format='bgr24',因为 AVI 支持 bgr24 而不是 rgb24
  • 选择视频为 test_2 视频的无损编解码器。
    您可以选择 vcodec='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:',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:",pix_fmt='bgr24')
        .run(capture_stdout=True)
)

# Convert new video into array
video_2 = (
    np
        .frombuffer(out_2,3])
)

# Video dimesions change
print(f'{video.shape} vs {video_2.shape}') # (844,1080,608,3) vs (844,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 编码器:

使用 .mkv 的 ffv1 编码器实现相同的散列

  • vcodec='ffv1' 的参数中选择 output()

还有一件事:

  • 将参数 r=frame_rate 从输出参数移动到输入参数。
    这不直观……但是当从帧中创建视频时,应将帧速率定义为输入的参数。

     # Write buffer back into a video
     process = (
         ffmpeg
             .input('pipe:',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'),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,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",pix_fmt='rgb24')
        .run(capture_stdout=True)
)

# Convert new video into array
video_2 = (
    np
        .frombuffer(out_2,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,3)
print(video_data_2.shape) # (844,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())