【问题标题】:Issue with configuration of cv2.VideoWriter and GStreamercv2.VideoWriter 和 GStreamer 的配置问题
【发布时间】:2021-05-07 20:33:36
【问题描述】:

我在设置 GStreamer 管道以通过 OpenCV 通过 UDP 转发视频流时遇到问题。我有一台笔记本电脑和一台连接到同一网络的 AGX Xavier。这个想法是将网络摄像头视频馈送转发到 AGX,它将在 GPU(在 Python 中)上进行一些 OpenCV 光流估计,在原始图像上绘制流向量并将其发送回我的笔记本电脑。到目前为止,我可以配置两个管道。举个简单的例子,我制作了两个 bash 脚本和一个 Python 脚本,它们在理想情况下可以作为 OpenCV 的 VideoCapture 和 VideoWriter 对象的传递。

servevideo.bash:

#!/bin/bash

gst-launch-1.0 v4l2src device=[device-fd] \
    ! video/x-raw, width=800, height=600, framerate=24/1 \
    ! jpegenc ! rtpjpegpay ! rtpstreampay \
    ! udpsink host=[destination-ip] port=12345

receivevideo.bash:

#!/bin/bash

gst-launch-1.0 -e udpsrc port=12344 \
    ! application/x-rtp-stream,encoding-name=JPEG \
    ! rtpstreamdepay ! rtpjpegdepay ! jpegdec \
    ! autovideosink

如果我在同一台计算机或网络上的两台不同计算机上运行这两个脚本,它工作正常。当我将 Python 脚本(如下所列)混在一起时,我开始遇到问题。理想情况下,我会在我的笔记本电脑上运行 bash 脚本,同时在我的 Jetson 上运行 Python 脚本时考虑到预期的设置。然后,在绕着 Jetson 绕了一圈后,我希望在我的笔记本电脑上看到网络摄像头视频源。

webcam_passthrough.py:

#!/usr/bin/python3.6

import cv2

video_in = cv2.VideoCapture("udpsrc port=12345 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay ! rtpjpegdepay ! jpegdec ! videoconvert ! appsink", cv2.CAP_GSTREAMER)
video_out = cv2.VideoWriter("appsrc ! videoconvert ! jpegenc ! rtpjpegpay ! rtpstreampay ! udpsink host=[destination-ip] port=12344", cv2.CAP_GSTREAMER, 0, 24, (800, 600), True)

while True:
    ret, frame = video_in.read()

    if not ret: break

    video_out.write(frame)
    cv2.imshow('Original', frame)

    key = cv2.waitKey(1) & 0xff
    if key == 27: break

cv2.destroyAllWindows()
video_out.release()
video_in.release()

使用以下 Python 脚本,我可以通过cv2.imshow 可视化从servevideo.bash 脚本设置的管道接收到的帧。所以我认为我的问题与我在 OpenCV 中设置 VideoWriter video_out 的方式有关。当我在创建的这两个管道之间中继网络摄像头视频源时,我已经验证了我的两个 bash 脚本正在工作,并且我已经验证了 cv2.VideoCapture 接收帧。我不是这里的专家,我的 GStreamer 知识几乎不存在,所以在我的最小示例中可能存在一些误解。如果你们中的一些人能指出我在这里遗漏的内容,将不胜感激。

如果有不清楚或遗漏的地方,我也很乐意提供更多信息。

编辑: 因此,我的最小示例的意图似乎没有明确传达。

作为最小示例提供的三个脚本用于将我的网络摄像头视频馈送从我的笔记本电脑中继到 Jetson AGX Xavier,Jetson AGX Xavier 然后将视频馈送回笔记本电脑。 servevideo.bash 在笔记本电脑上创建一个 GStreamer 管道,该管道使用 v4l2 从摄像头抓取帧并将其中继到 UDP 套接字。 webcam_passthrough.py 在 Jetson 上运行,它“连接”到笔记本电脑上运行的管道创建的 UDP 套接字。 Python 脚本提供了一个透传功能,理想情况下将在另一个端口上打开一个新的 UDP 套接字并将帧中继回笔记本电脑。 receivevideo.bash 在笔记本电脑上创建另一个管道,用于接收通过 Jetson 的 Python 脚本传递的帧。笔记本电脑上的第二条管道仅用于可视化目的。理想情况下,这个最小示例显示来自连接到笔记本电脑的摄像头的“原始”视频。

这两个 bash 脚本独立运行,既在笔记本电脑上本地运行,又在另一台计算机上远程运行 receivevideo.bash

Python 脚本中的cv2.VideoCapture 配置似乎也可以工作,因为我可以可视化通过servevideo.bash 脚本提供的UDP 套接字接收的帧(带有cv2.imshow)。这也适用于本地和远程。让我头疼的部分(我相信)是cv2.VideoWriter的配置;理想情况下,这应该打开一个 UDP 套接字,我可以通过我的 receivevideo.bash 脚本“连接”到它。我已经在本地和远程对此进行了测试,但无济于事。

当我运行 receivevideo.bash 连接到 Python 脚本提供的 UDP 套接字时,我得到以下输出:

Setting pipeline to PAUSED ...
Pipeline is live and does not need PREROLL ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock

这对我来说似乎没有错,我尝试使用 GST_DEBUG=3 运行不同的脚本,这给出了一些警告,但由于 bash 脚本和 cv2 VideoCapture 和 @ 中的管道配置基本相同987654340@我没有为这些警告增加太多价值。例如,我在下面包含了一个这样的警告:

0:00:06.595120595  8962      0x25b8cf0 WARN              rtpjpegpay gstrtpjpegpay.c:596:gst_rtp_jpeg_pay_read_sof:<rtpjpegpay0> warning: Invalid component

使用GST_DEBUG=3 连续运行 Python 脚本会打印此警告。以相同的调试级别运行 receivevideo.bash 给出:

Setting pipeline to PAUSED ...
Pipeline is live and does not need PREROLL ...
0:00:00.013911480  9078 0x55be0899de80 FIXME           videodecoder gstvideodecoder.c:933:gst_video_decoder_drain_out:<jpegdec0> Sub-class should implement drain()
Setting pipeline to PLAYING ...
New clock: GstSystemClock

我希望我的意图现在更清楚,正如我已经指出的那样,我认为我在 Python 脚本中的 cv2.VideoWriter 有问题,但我不是专家,而且 GStreamer 远非我每天都使用的东西。因此,我可能误解了一些东西。

编辑 2: 所以现在我尝试按照@abysslover 的建议将两个管道分成两个单独的进程。我仍然看到相同的结果,但我仍然不知道为什么会这样。下面列出了我当前的 Python 脚本实现。

webcam_passthrough.py:

#!/usr/bin/python3.6

import signal, cv2
from multiprocessing import Process, Pipe

is_running = True

def signal_handler(sig, frame):
    global is_running
    print("Program was interrupted - terminating ...")
    is_running = False

def produce(pipe):
    global is_running
    video_in = cv2.VideoCapture("udpsrc port=12345 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay ! rtpjpegdepay ! jpegdec ! videoconvert ! appsink", cv2.CAP_GSTREAMER)

    while is_running:
        ret, frame = video_in.read()
        if not ret: break
        print("Receiving frame ...")

        pipe.send(frame)

    video_in.release()

if __name__ == "__main__":
    consumer_pipe, producer_pipe = Pipe()

    signal.signal(signal.SIGINT, signal_handler)
    producer = Process(target=produce, args=(producer_pipe,))

    video_out = cv2.VideoWriter("appsrc ! videoconvert ! jpegenc ! rtpjpegpay ! rtpstreampay ! udpsink host=[destination-ip] port=12344", cv2.CAP_GSTREAMER, 0, 24, (800, 600), True)
    producer.start()

    while is_running:
        frame = consumer_pipe.recv()
        video_out.write(frame)
        print("Sending frame ...")

    video_out.release()
    producer.join()

我在两个进程之间创建的管道按预期提供了一个新框架。当我尝试使用netcat 监听 UDP 端口 12344 时,我没有收到任何与以前相同的内容。我也很难理解管道的差异化是如何变化的,因为我希望它们已经在不同的环境中运行。不过,关于这个假设,我可能是错的。

【问题讨论】:

    标签: python bash opencv gstreamer


    【解决方案1】:

    您非常接近解决方案。问题在于您自己注意到的警告warning: Invalid component。问题是 rtp jpeg payloader 由于不支持它所获取的视频格式而被卡住。检查this

    但是我是盲目的,错过了你写的内容,并进入了完整的调试模式。

    所以让我们为其他人或类似问题保留调试方法:

    1,第一个调试步骤 - 使用 wireshark 检查接收机器是否在端口 12344 上收到 udp 数据包。不,它没有。

    2,如果没有 opencv 的东西,这可以工作吗?让我们检查一下用一些随机处理替换 opencv 逻辑 - 比如视频的旋转。还要去掉 appsrc/appsink 来简化。

    然后我用这个:

    GST_DEBUG=3 gst-launch-1.0 udpsrc port=12345 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay ! rtpjpegdepay ! jpegdec ! videoconvert ! rotate angle=0.45 ! videoconvert ! jpegenc ! rtpjpegpay ! rtpstreampay ! queue ! udpsink host=[my ip] port=12344

    嗯,现在我收到奇怪的警告,例如:

    0:00:00.174424533 90722 0x55cb38841060 WARN              rtpjpegpay gstrtpjpegpay.c:596:gst_rtp_jpeg_pay_read_sof:<rtpjpegpay0> warning: Invalid component
    WARNING: from element /GstPipeline:pipeline0/GstRtpJPEGPay:rtpjpegpay0: Invalid component
    

    3、快速搜索产生上述GStreamer论坛页面。

    4,当我在 videoconvert 之后添加 video/x-raw,format=I420 时,它开始工作,我的第二台机器开始接收 udp 数据包。

    5,因此解决您的问题的方法是将 jpegenc 限制为后续 rtp payloader 可以处理的特定视频格式:

    #!/usr/bin/python3
    
    import signal, cv2
    from multiprocessing import Process, Pipe
    
    is_running = True
    
    def signal_handler(sig, frame):
        global is_running
        print("Program was interrupted - terminating ...")
        is_running = False
    
    def produce(pipe):
        global is_running
        video_in = cv2.VideoCapture("udpsrc port=12345 ! application/x-rtp-stream,encoding-name=JPEG ! rtpstreamdepay ! rtpjpegdepay ! jpegdec ! videoconvert ! appsink", cv2.CAP_GSTREAMER)
    
        while is_running:
            ret, frame = video_in.read()
            if not ret: break
            print("Receiving frame ...")
    
            pipe.send(frame)
    
        video_in.release()
    
    if __name__ == "__main__":
        consumer_pipe, producer_pipe = Pipe()
    
        signal.signal(signal.SIGINT, signal_handler)
        producer = Process(target=produce, args=(producer_pipe,))
    
        # the only edit is here, added video/x-raw capsfilter:     <-------
        video_out = cv2.VideoWriter("appsrc ! videoconvert ! video/x-raw,format=I420 ! jpegenc ! rtpjpegpay ! rtpstreampay ! udpsink host=[receiver ip] port=12344", cv2.CAP_GSTREAMER, 0, 24, (800, 600), True)
        producer.start()
    
        while is_running:
            frame = consumer_pipe.recv()
            rr = video_out.write(frame)
            print("Sending frame ...")
            print(rr)
    
        video_out.release()
        producer.join()
    

    【讨论】:

    • 感谢您花时间研究此问题并向我解释问题所在。我已经对此进行了测试,它现在运行良好。我什至尝试将所有内容都放在webcam_passtrhough.py 中的相同进程中,就像我最初的尝试一样,它仍然有效。
    【解决方案2】:

    注意:由于声誉低,我无法发表评论。

    根据你的问题描述,很难理解你的问题是什么。

    简单地说,您将在笔记本电脑上运行两个 bash 脚本(servevideo.bashreceivevideo.bash),它们可以从笔记本电脑(?)接收和发送网络摄像头帧,同时运行一个 Python 脚本(webcam_passthrough.py)在 Jetson AGX Xavier 上。

    你的 bash 脚本可以工作,所以我猜你的 Python 脚本有一些问题。根据您的解释,您已经从 bash 脚本中的 gst-launch 中获得了框架并可视化了这些框架。

    那么,你真正的问题是什么?你想用 Python 脚本解决什么问题?

    以下陈述我不清楚。

    当我将 Python 脚本(如下所列)混在一起时,我开始遇到问题。

    下面的配置怎么样?

    servevideo.bash:

    #!/bin/bash
    gst-launch-1.0 videotestsrc device=[device-fd] \
        ! video/x-raw, width=800, height=600, framerate=20/1 \
        ! videoscale
        ! videoconvert
        ! x264enc tune=zerolatency bitrate=500 speed-preset=superfast
        ! rtph264pay
        ! udpsink host=[destination-ip] port=12345
    

    receivevideo.bash

    #!/bin/bash
    gst-launch-1.0 -v udpsrc port=12345 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264, payload=(int)96" \
         ! rtph264depay \
         ! decodebin \
         ! videoconvert \
         ! autovideosink
    

    Python 脚本:

    import numpy as np
    import cv2
    from multiprocessing import Process
    
    def send_process():
        video_in = cv2.VideoCapture("videotestsrc ! video/x-raw,framerate=20/1 ! videoscale ! videoconvert ! appsink", cv2.CAP_GSTREAMER)
        video_out = cv2.VideoWriter("appsrc ! videoconvert ! x264enc tune=zerolatency bitrate=500 speed-preset=superfast ! rtph264pay ! udpsink host=[destination_ip] port=12345", cv2.CAP_GSTREAMER, 0, 24, (800,600), True)
    
        if not video_in.isOpened() or not video_out.isOpened():
            print("VideoCapture or VideoWriter not opened")
            exit(0)
    
        while True:
            ret,frame = video_in.read()
    
            if not ret: break
    
            video_out.write(frame)
    
            cv2.imshow("send_process", frame)
            if cv2.waitKey(1)&0xFF == ord("q"):
                break
    
        video_in.release()
        video_out.release()
    
    def receive_process():
        cap_receive = cv2.VideoCapture('udpsrc port=12345 caps = "application/x-rtp, media=(string)video, clock-rate=(int)90000, encoding-name=(string)H264, payload=(int)96" ! rtph264depay ! decodebin ! videoconvert ! appsink', cv2.CAP_GSTREAMER)
    
        if not cap_receive.isOpened():
            print("VideoCapture not opened")
            exit(0)
    
        while True:
            ret,frame = cap_receive.read()
    
            if not ret: break
    
            cv2.imshow('receive_process', frame)
            if cv2.waitKey(1)&0xFF == ord('q'):
                break
    
        cap_receive.release()
    
    if __name__ == '__main__':
        s = Process(target=send_process)
        r = Process(target=receive_process)
        s.start()
        r.start()
        s.join()
        r.join()
    
        cv2.destroyAllWindows()
    

    我无法使用代码进行测试,因为我没有您的配置。我认为接收者和发送者需要使用 Python 中的 multiprocessing.Process 分成两个独立的进程。您可能需要调整一些详细参数才能在您的配置中使用这些脚本。

    祝你好运。

    【讨论】:

    • 确实我相信Python脚本是这里的问题,更准确地说是cv2.VideoWriter的配置。 Python 脚本的目的基本上是将视频帧转发回我的笔记本电脑,这样我就可以在通过 Jetson 往返之后在我的笔记本电脑上看到视频源。
    • 检查我的编辑以获得澄清,我希望这次我能获得更好的清晰度。
    • 感谢您的回答,即使我不完全理解为什么,多处理也可能是问题所在?可能是两个 GStreamer 管道在使用网络时在同一个线程中表现不佳。无论如何,这里有点晚了,所以我明天会再次讨论这个问题并返回一些反馈。另一个问题是改变编码方案的原因。我会获得更好的性能还是有其他与我的问题相关的原因?
    • 检查我的第二次编辑,你能解释为什么需要分成两个进程,以及编码方案如何。您是否测试了自己的实现?顺便说一句,我的实现可以很容易地在一台机器上进行测试。
    猜你喜欢
    • 1970-01-01
    • 2020-10-10
    • 2019-05-10
    • 1970-01-01
    • 2019-04-17
    • 2021-12-04
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多