【问题标题】:Is there a way to calculate the output frame dimensions when compressing a video and extracting its frames with ffmpeg有没有办法在压缩视频并使用 ffmpeg 提取其帧时计算输出帧尺寸
【发布时间】:2020-01-29 11:22:36
【问题描述】:

我使用以下代码压缩视频并提取其帧。请注意,我不想保存生成的视频。

        output_args = {
            "vcodec": "libx265",
            "crf": 24,
        }
        out, err = (
            ffmpeg
            .input(in_filename)
            .output('pipe:', format='rawvideo', pix_fmt='rgb24',**output_args)
            .run(capture_stdout=True)
        )
        frames = np.frombuffer(out, np.uint8).reshape(-1,width,height,3)

当我尝试将输出缓冲区重塑为原始视频尺寸时,我收到以下错误: cannot reshape array of size 436567 into shape (1920,1080,3) 这是意料之中的,因为生成的视频具有较小的尺寸。有没有办法计算压缩视频的帧数、宽度和高度,以便从缓冲区重塑帧?

另外,如果我保存压缩视频,而不是加载它的帧,然后我从压缩视频中加载视频帧,这些将具有与原始视频相同的尺寸。我怀疑引擎盖下发生了某种插值。有没有办法在不保存视频的情况下应用它?

【问题讨论】:

    标签: python ffmpeg


    【解决方案1】:

    我找到了使用ffmpeg-python 的解决方案。

    假设:

    • out 将整个 h265 编码流保存在内存缓冲区中。
    • 您不想将流写入文件。

    解决方案应用以下内容:

    • 在子进程中执行FFmpegsdtin 作为输入pipestdout 作为输出pipe
      输入将是视频流(内存缓冲区)。
      输出格式是 BGR 像素格式的原始视频帧。
    • 将流内容写入pipe(至stdin)。
    • 读取解码后的视频(逐帧),并显示每一帧(使用cv2.imshow

    为了测试解决方案,我创建了一个示例视频文件,并将其读入内存缓冲区(编码为 H.265)。
    我使用内存缓冲区作为上述代码的输入(您的out 缓冲区)。

    这里是完整的代码,包括测试代码:

    import ffmpeg
    import numpy as np
    import cv2
    import io
    
    in_filename = 'in.mp4'
    
    # Build synthetic video, for testing begins:
    ###############################################
    # ffmpeg -y -r 10 -f lavfi -i testsrc=size=192x108:rate=1 -c:v libx265 -crf 24 -t 5 in.mp4
    
    width, height = 192, 108
    
    (
        ffmpeg
        .input('testsrc=size={}x{}:rate=1'.format(width, height), r=10, f='lavfi')
        .output(in_filename, vcodec='libx265', crf=24, t=5)
        .overwrite_output()
        .run()
    )
    ###############################################
    
    
    # Use ffprobe to get video frames resolution
    ###############################################
    p = ffmpeg.probe(in_filename, select_streams='v');
    width = p['streams'][0]['width']
    height = p['streams'][0]['height']
    n_frames = int(p['streams'][0]['nb_frames'])
    ###############################################
    
    
    # Stream the entire video as one large array of bytes
    ###############################################
    # https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md
    in_bytes, _ = (
        ffmpeg
        .input(in_filename)
        .video # Video only (no audio).
        .output('pipe:', format='hevc', crf=24)
        .run(capture_stdout=True) # Run asynchronous, and stream to stdout
    )
    ###############################################
    
    
    # Open In-memory binary streams
    stream = io.BytesIO(in_bytes)
    
    # Execute FFmpeg in a subprocess with sdtin as input pipe and stdout as output pipe
    # The input is going to be the video stream (memory buffer)
    # The output format is raw video frames in BGR pixel format.
    # https://github.com/kkroening/ffmpeg-python/blob/master/examples/README.md
    # https://github.com/kkroening/ffmpeg-python/issues/156
    # http://zulko.github.io/blog/2013/09/27/read-and-write-video-frames-in-python-using-ffmpeg/
    process = (
        ffmpeg
        .input('pipe:', format='hevc')
        .video
        .output('pipe:', format='rawvideo', pix_fmt='bgr24')
        .run_async(pipe_stdin=True, pipe_stdout=True)
    )
    
    
    # https://stackoverflow.com/questions/20321116/can-i-pipe-a-io-bytesio-stream-to-subprocess-popen-in-python
    # https://gist.github.com/waylan/2353749
    process.stdin.write(stream.getvalue())  # Write stream content to the pipe
    process.stdin.close()  # close stdin (flush and send EOF)
    
    
    # Read decoded video (frame by frame), and display each frame (using cv2.imshow)
    while(True):
        # Read raw video frame from stdout as bytes array.
        in_bytes = process.stdout.read(width * height * 3)
    
        if not in_bytes:
            break
    
        # transform the byte read into a numpy array
        in_frame = (
            np
            .frombuffer(in_bytes, np.uint8)
            .reshape([height, width, 3])
        )
    
        # Display the frame
        cv2.imshow('in_frame', in_frame)
    
        if cv2.waitKey(100) & 0xFF == ord('q'):
            break
    
    process.wait()
    cv2.destroyAllWindows()
    

    注意:我使用 stdinstdout 代替名称管道,因为我希望代码在 Windows 和 Linux 中都可以工作。

    【讨论】:

    • 感谢您的回答。但是,我发布问题的原因是我想先压缩视频,然后将帧加载到 RAM 上。所以我需要使用像vcodeccrf这样的压缩参数。您是说我无法使用这些参数获得原始 RGB?
    • 我不知道这是否可能。如果可能,这很复杂:您需要构建一个具有两个输出流的过滤器图:保存到磁盘的压缩流和进入管道的未压缩流。使用两个FFmpeg 命令要简单得多。
    • 如果我误解了你,并且你想在 RAM 中压缩序列,你不能将它重新整形为帧,因为你需要先解码它(为了解码,你可以使用 FFmpeg [复杂],或者使用 OpenCV 捕获)。我不知道它是否适用于 RAM 中的所有(压缩)视频流。
    • 是的,我想要 RAM 中的压缩序列。如果我先将压缩视频保存在磁盘中,那么我可以使用原始视频的形状在 RAM 中提取压缩帧。但我的限制是不想使用磁盘。我可能会搜索是否有办法按照您的建议先对其进行解码。
    • 我发布了一个在 RAM 中使用压缩序列的新示例。代码示例没有具体解决您的问题。稍作修改,它应该可以解决您的问题。
    猜你喜欢
    • 1970-01-01
    • 2012-04-14
    • 1970-01-01
    • 2017-03-02
    • 1970-01-01
    • 2023-01-09
    • 2016-04-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多