简体   繁体   English

使用来自 pi 相机的 python 解码并显示 H.264 卡盘视频序列

[英]decode and show H.264 chucked video sequence with python from pi camera

I would like to decode the H.264 video sequences and show them on the screen.我想解码 H.264 视频序列并将它们显示在屏幕上。 The video sequences are from the pi camera and I capture with the following code视频序列来自 pi 相机,我使用以下代码捕获

import io
import picamera

stream = io.BytesIO()
while True:
    with picamera.PiCamera() as camera:
        camera.resolution = (640, 480)
        camera.start_recording(stream, format='h264', quality=23)
        camera.wait_recording(15)
        camera.stop_recording()

Is there any way to decode the sequence of 'stream' data and show them with the opencv or other python libraries?有什么方法可以解码“流”数据的序列并用 opencv 或其他 python 库显示它们?

I found a solution using ffmpeg-python .我找到了一个使用ffmpeg-python的解决方案。
I can't verify the solution in raspberry-pi, so I am not sure if it's going to work for you.我无法在 raspberry-pi 中验证解决方案,所以我不确定它是否适合你。

Assumptions:假设:

  • stream holds the entire captured h264 stream in memory buffer. stream将整个捕获的 h264 流保存在内存缓冲区中。
  • You don't want to write the stream into a file.您不想将流写入文件。

The solution applies the following:该解决方案适用于以下内容:

  • Execute FFmpeg in a sub-process with sdtin as input pipe and stdout as output pipe .在子进程中执行FFmpeg ,以sdtin作为输入pipestdout作为输出pipe
    The input is going to be the video stream (memory buffer).输入将是视频流(内存缓冲区)。
    The output format is raw video frames in BGR pixel format.输出格式是 BGR 像素格式的原始视频帧。
  • Write stream content to the pipe (to stdin ).将流内容写入pipe (到stdin )。
  • Read decoded video (frame by frame), and display each frame (using cv2.imshow )读取解码后的视频(逐帧),并显示每一帧(使用cv2.imshow

Here is the code:这是代码:

import ffmpeg
import numpy as np
import cv2
import io

width, height = 640, 480


# Seek to stream beginning
stream.seek(0)

# 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:')
    .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()

Note: I used sdtin and stdout as pipes (instead of using named-pipes), because I wanted the code to work in Windows too.注意:我使用sdtinstdout作为管道(而不是使用命名管道),因为我希望代码也能在 Windows 中工作。


For testing the solution, I created a sample video file, and read it into memory buffer (encoded as H.264).为了测试解决方案,我创建了一个示例视频文件,并将其读入内存缓冲区(编码为 H.264)。
I used the memory buffer as input to the above code (replacing your stream ).我使用内存缓冲区作为上述代码的输入(替换您的stream )。

Here is the complete code, include the testing code:这是完整的代码,包括测试代码:

import ffmpeg
import numpy as np
import cv2
import io

in_filename = 'in.avi'

# Build synthetic video, for testing begins:
###############################################
# ffmpeg -y -r 10 -f lavfi -i testsrc=size=160x120:rate=1 -c:v libx264 -t 5 in.mp4
width, height = 160, 120

(
    ffmpeg
    .input('testsrc=size={}x{}:rate=1'.format(width, height), r=10, f='lavfi')
    .output(in_filename, vcodec='libx264', crf=23, 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='h264', crf=23)
    .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 ouptut 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:')
    .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()

I don't think OpenCV knows how to decode H264, so you would have to rely on other libraries to convert it to RGB or BGR.我认为 OpenCV 不知道如何解码 H264,因此您将不得不依赖其他库将其转换为 RGB 或 BGR。

On the other hand, you can use format='bgr' in picamera and make your life easier:另一方面,您可以在picamera 中使用format='bgr'并让您的生活更轻松:

I dont know what you exactly want to do but another way to do it without FFMPEG is this:我不知道你到底想做什么,但没有 FFMPEG 的另一种方法是:

If you read the picam docs you will see that the video port has splitters which you can access using the splitter_port=x (1<=x<=3) kwarg for camera.如果您阅读 picam 文档,您将看到视频端口具有分离器,您可以使用splitter_port=x (1<=x<=3) kwarg for camera 访问这些分离器 start_recorder() : start_recorder() :
https://picamera.readthedocs.io/en/release-1.13/api_camera.html#picamera.PiCamera.start_recording https://picamera.readthedocs.io/en/release-1.13/api_camera.html#picamera.PiCamera.start_recording

Basically this means that you can split the recorded stream into 2 substreams, one you encode to h264 for saving or whatever, and one where you enconde it to a OPENCV compatible format.基本上这意味着您可以将录制的流拆分为 2 个子流,一个您编码为 h264 以进行保存或其他任何内容,另一个您将其编码为 OPENCV 兼容格式。 https://picamera.readthedocs.io/en/release-1.13/recipes2.html?highlight=splitter#capturing-to-an-opencv-object https://picamera.readthedocs.io/en/release-1.13/recipes2.html?highlight=splitter#capturing-to-an-opencv-object

This all happens mainly in the GPU so it is pretty fast (see the picamera docs for more info)这一切主要发生在 GPU 中,因此速度非常快(有关更多信息,请参阅 picamera 文档)

It is the same as they do here if you need an example: https://picamera.readthedocs.io/en/release-1.13/recipes2.html?highlight=splitter#recording-at-multiple-resolutions but then with an opencv object and a h264 stream如果您需要一个示例,它与他们在此处所做的相同: https ://picamera.readthedocs.io/en/release-1.13/recipes2.html?highlight = splitter#recording-at-multiple-resolutions 但随后使用 opencv对象和一个 h264 流

@Rotem 's answer is correct but it does not work with large video chunks. @Rotem 的答案是正确的,但它不适用于大型视频块。

To work with the larger video, we need to replace process.stdin.write the process.communicate .为了处理更大的视频,我们需要替换process.stdin.write process.communicate Updating the following lines更新以下几行

...
# process.stdin.write(stream.getvalue())  # Write stream content to the pipe
outs, errs = process.communicate(input=stream.getvalue())
# process.stdin.close()  # close stdin (flush and send EOF)
# Read decoded video (frame by frame), and display each frame (using cv2.imshow)

position = 0
ct = time.time()
while(True):
    # Read raw video frame from stdout as bytes array.
    in_bytes = outs[position: position + width * height * 3]
    position += width * height * 3
...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM