【问题标题】:NumPy array of a video changes from the original after writing into the same video写入同一视频后,视频的 NumPy 数组从原始数组发生变化
【发布时间】:2021-06-17 04:55:06
【问题描述】:

我有一个视频 (test.mkv) 已转换为 4D NumPy 数组 - (帧、高度、宽度、颜色通道)。我什至设法将该数组转换回相同的视频 (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:', 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'

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

【问题讨论】:

    标签: python ffmpeg video-processing scikits ffmpeg-python


    【解决方案1】:

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

    在比较哈希之前,请先尝试查看视频。

    执行您的代码给了我以下输出(video_2 的第一帧):

    当输入(视频第一帧)为:

    我建议进行以下修改:

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

    使用 .mkv 的 ffv1 编码器实现相同的哈希

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

    还有一件事:

    • 将参数 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。
    如果不使用skvideo.io.FFmpegWriter,我找不到选择ffv1 编解码器的方法。
    该实现使用 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())
    

    【讨论】:

    • 使用 .mkv 的 ffv1 编码器为帧 here 实现相同的哈希值。但是即使在阅读了 ffmpeg-python 的文档之后,我也无法弄清楚如何指定它,这在我看来非常令人费解。您可能知道如何指定吗?谢谢你的回答。我会玩一下,然后再回复你。
    • 你说得对,'ffv1' 编解码器效果很好。在output()的参数中选择vcodec='ffv1'
    • 我发现了问题(存在帧率转换问题 - 您得到的是 1013 帧而不是 844 帧)。解决方案:将参数r=frame_rate 从输出参数移动到输入参数。更新后,我使用您的输入文件得到相同的哈希值。我更新了我的帖子。
    • 我从未使用过 Scikit-Video。你知道如何选择ffv1编解码器吗? Scikit-Video 支持吗?
    • 我添加了一个使用 Scikit-Video 的解决方案,但它使用了FFmpegWriter,并且它逐帧写入视频。
    猜你喜欢
    • 2020-11-02
    • 2013-02-03
    • 2020-08-24
    • 2022-08-12
    • 2021-07-19
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多