【问题标题】:Display stream with FFmpeg, python and opencv使用 FFmpeg、python 和 opencv 显示流
【发布时间】:2021-05-25 16:45:47
【问题描述】:

情况: 我有一个连接到树莓派的 basler 相机,我正在尝试将其与 FFmpg 一起传输到我的 Windows PC 中的 tcp 端口,以监控相机前发生的事情。

有效的方法: 我设法在树莓派上设置了一个 python 脚本,该脚本负责记录帧,将它们馈送到管道并将它们流式传输到 tcp 端口。从那个端口,我可以使用 FFplay 显示流。

我的问题: 如果您前进的方向正确,FFplay 非常适合快速轻松地测试,但我想从流中“读取”每一帧,进行一些处理,然后使用 opencv 显示流。那个,我还做不到。

最少代表,这是我在树莓派方面使用的代码:

command = ['ffmpeg',
           '-y',
           '-i', '-',
           '-an',
           '-c:v', 'mpeg4',
           '-r', '50',
           '-f', 'rtsp',
           '-rtsp_transport',
           'tcp','rtsp://192.168.1.xxxx:5555/live.sdp']

p = subprocess.Popen(command, stdin=subprocess.PIPE) 

while camera.IsGrabbing():  # send images as stream until Ctrl-C
    grabResult = camera.RetrieveResult(100, pylon.TimeoutHandling_ThrowException)
    
    if grabResult.GrabSucceeded():
        image = grabResult.Array
        image = resize_compress(image)
        p.stdin.write(image)
    grabResult.Release() 

在我的 PC 上,如果我在终端上使用以下 FFplay 命令,它可以工作并实时显示流:

ffplay -rtsp_flags listen rtsp://192.168.1.xxxx:5555/live.sdp?tcp

在我的 PC 上,如果我使用以下 python 脚本,则流开始,但它在 cv2.imshow 函数中失败,因为我不确定如何对其进行解码:

import subprocess
import cv2

command = ['C:/ffmpeg/bin/ffmpeg.exe',
           '-rtsp_flags', 'listen',
           '-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?', 
           '-']

p1 = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

while True:
    frame = p1.stdout.read()
    cv2.imshow('image', frame)
    cv2.waitKey(1)

有谁知道我需要在其中任何一个脚本中进行哪些更改才能让 i 工作?

提前感谢您的任何提示。

【问题讨论】:

    标签: python opencv ffmpeg subprocess video-streaming


    【解决方案1】:

    您可以从p1.stdout 读取解码后的帧,将其转换为 NumPy 数组,然后对其进行整形。

    • 更改command 以获取rawvideo 格式和BGR 像素格式的解码帧:

       command = ['C:/ffmpeg/bin/ffmpeg.exe',
                  '-rtsp_flags', 'listen',
                  '-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?',
                  '-f', 'image2pipe',    # Use image2pipe demuxer
                  '-pix_fmt', 'bgr24',   # Set BGR pixel format
                  '-vcodec', 'rawvideo', # Get rawvideo output format.
                  '-']
      
    • p1.stdout读取原始视频帧:

       raw_frame = p1.stdout.read(width*height*3)
      
    • 将读取的字节转换为 NumPy 数组,并将其重塑为视频帧尺寸:

       frame = np.fromstring(raw_frame, np.uint8)
       frame = frame.reshape((height, width, 3))
      

    现在您可以显示调用cv2.imshow('image', frame) 的框架。

    该解决方案假设您事先知道视频帧大小(widthheight)。

    下面的代码示例包括使用cv2.VideoCapture 读取widthheight 的部分,但我不确定它是否适用于您的情况(由于'-rtsp_flags', 'listen'。(如果它确实有效) ,您可以尝试使用 OpenCV 而不是 FFmpeg 进行捕获)。

    以下代码是使用公共 RTSP 流进行测试的完整“工作示例”:

    import cv2
    import numpy as np
    import subprocess
    
    # Use public RTSP Stream for testing
    in_stream = 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
    
    if False:
        # Read video width, height and framerate using OpenCV (use it if you don't know the size of the video frames).
    
        # Use public RTSP Streaming for testing:
        cap = cv2.VideoCapture(in_stream)
    
        framerate = cap.get(5) #frame rate
    
        # Get resolution of input video
        width  = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    
        # Release VideoCapture - it was used just for getting video resolution
        cap.release()
    else:
        # Set the size here, if video frame size is known
        width = 240
        height = 160
    
    
    command = ['C:/ffmpeg/bin/ffmpeg.exe',
               #'-rtsp_flags', 'listen',  # The "listening" feature is not working (probably because the stream is from the web)
               '-rtsp_transport', 'tcp',  # Force TCP (for testing)
               '-max_delay', '30000000',  # 30 seconds (sometimes needed because the stream is from the web).
               '-i', in_stream,
               '-f', 'image2pipe',
               '-pix_fmt', 'bgr24',
               '-vcodec', 'rawvideo', '-an', '-']
    
    # Open sub-process that gets in_stream as input and uses stdout as an output PIPE.
    p1 = subprocess.Popen(command, stdout=subprocess.PIPE)
    
    while True:
        # read width*height*3 bytes from stdout (1 frame)
        raw_frame = p1.stdout.read(width*height*3)
    
        if len(raw_frame) != (width*height*3):
            print('Error reading frame!!!')  # Break the loop in case of an error (too few bytes were read).
            break
    
        # Convert the bytes read into a NumPy array, and reshape it to video frame dimensions
        frame = np.fromstring(raw_frame, np.uint8)
        frame = frame.reshape((height, width, 3))
    
        # Show video frame
        cv2.imshow('image', frame)
        cv2.waitKey(1)
      
    # Wait one more second and terminate the sub-process
    try:
        p1.wait(1)
    except (sp.TimeoutExpired):
        p1.terminate()
    
    cv2.destroyAllWindows()
    

    示例框架(仅供娱乐):


    更新:

    使用FFprobe读取宽度和高度:

    当您事先不知道视频分辨率时,您可以使用 FFprobe 获取信息。

    这是使用 FFprobe 读取 widthheight 的代码示例:

    import subprocess
    import json
    
    # Use public RTSP Stream for testing
    in_stream = 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
    
    probe_command = ['C:/ffmpeg/bin/ffprobe.exe',
                     '-loglevel', 'error',
                     '-rtsp_transport', 'tcp',  # Force TCP (for testing)]
                     '-select_streams', 'v:0',  # Select only video stream 0.
                     '-show_entries', 'stream=width,height', # Select only width and height entries
                     '-of', 'json', # Get output in JSON format
                     in_stream]
    
    # Read video width, height using FFprobe:
    p0 = subprocess.Popen(probe_command, stdout=subprocess.PIPE)
    probe_str = p0.communicate()[0] # Reading content of p0.stdout (output of FFprobe) as string
    p0.wait()
    probe_dct = json.loads(probe_str) # Convert string from JSON format to dictonary.
    
    # Get width and height from the dictonary
    width = probe_dct['streams'][0]['width']
    height = probe_dct['streams'][0]['height']
    

    【讨论】:

    • 非常感谢Rotem,您的回答非常有帮助,感谢它,我设法解决了我的问题,并了解了我面临的问题。只是为了上下文和像我一样使用 Balser 相机的其他人(我的是 daA1280-54uc),使用 ``` cv2.VideoCapture(index) ``` 可能有点问题,并且像我一样手动获取每一帧在我的第一个```代码块```中可能是唯一的方法。我实际上知道我的图像的大小,因为我在流之前调整了它们的大小,但如果其他人不知道,他可能应该知道,只是为了确定图像的尺寸
    • 我编辑了帖子:使用 FFprobe 读取 widthheight 的示例。你能告诉我它是否适用于你的情况吗?我知道你不需要它,但它可能对其他人有帮助。
    • 感谢您的更新,不幸的是它似乎不适用于我的特殊情况。
    猜你喜欢
    • 1970-01-01
    • 2016-07-05
    • 2021-10-03
    • 1970-01-01
    • 2019-03-30
    • 2022-01-25
    • 2017-09-30
    • 1970-01-01
    • 2011-02-05
    相关资源
    最近更新 更多