cv2.VideoWriter 和 GStreamer 的配置问题

Issue with configuration of cv2.VideoWriter and GStreamer

我在设置 GStreamer 管道以通过 OpenCV 通过 UDP 转发视频流时遇到问题。我有一台笔记本电脑和一个连接到同一网络的 AGX Xavier。这个想法是将网络摄像头视频转发到 AGX,AGX 将在 GPU 上进行一些 OpenCV 光流估计(在 Python 中),在原始图像上绘制流矢量并将其发送回我的笔记本电脑。到目前为止,我可以配置两个管道。作为一个最小的例子,我制作了两个 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.imshowservevideo.bash 脚本设置的管道接收到的帧进行可视化。所以我认为我的问题与我在 OpenCV 中设置 VideoWriter video_out 的方式有关。当我在创建的这两个管道之间中继网络摄像头视频源时,我已经验证了我的两个 bash 脚本正在工作,并且我已经验证 cv2.VideoCapture 接收到帧。我不是这里的专家,我的 GStreamer 知识几乎不存在,所以我的最小示例中可能存在一些误解。如果你们中的一些人能指出我在这里遗漏的内容,我们将不胜感激。

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

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

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

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

Python 脚本中的 cv2.VideoCapture 配置似乎也有效,因为我可以看到通过 [=19= 提供的 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 VideoCaptureVideoWriter 我没有给这些警告增加太多价值。作为示例,我在下面包含了一个这样的警告:

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 时,我没有收到与以前相同的任何内容。我也很难理解管道的差异化是如何变化的,正如我所期望的那样,它们在不同的环境中已经 运行 了。不过,关于这个假设我可能是错的。

注意:由于信誉度低,我不能写评论。

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

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

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

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

下面的说法我不清楚。

When I throw my Python script (listed below) in the mix, I start to experience issues.

下面的配置怎么样?

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 分成两个单独的进程。您可能需要调整一些详细参数才能在您的配置中使用这些脚本。

祝你好运。

您非常接近解决方案。问题在于您自己注意到的警告 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()