尝试从gstreamer appsink获取numpy映像导致缓冲区太小错误



我正试图从gstreamer appsink缓冲区中获取一个numpy数组。但是缓冲区太小,numpy无法将其放入数组中。我从这里学到了一点代码:从GStreamer 实时接收Numpy数组

我使用的视频测试源应该返回一些240x320大小的3个频道。但缓冲区大小为115200。巧合的是,这是240x320x3(230400(尺寸的一半。我尝试了其他不同分辨率的视频源,缓冲区总是大小的一半。我的猜测是,appsink过早地发出了on_new_sample函数的信号。

import time
import gi
import numpy as np
gi.require_version("Gst", "1.0")
gi.require_version("GstApp", "1.0")
from gi.repository import Gst, GstApp, GLib
_ = GstApp
def on_new_sample(app_sink):
sample = app_sink.pull_sample()
caps = sample.get_caps()
# Extract the width and height info from the sample's caps
height = caps.get_structure(0).get_value("height")
width = caps.get_structure(0).get_value("width")
# Get the actual data
buffer = sample.get_buffer()
print(caps,"buffer size ",buffer.get_size())
# Get read access to the buffer data
success, map_info = buffer.map(Gst.MapFlags.READ)
if not success:
raise RuntimeError("Could not map buffer data!")
numpy_frame = np.ndarray(
shape=(height, width, 3),
dtype=np.uint8,
buffer=map_info.data)
buffer.unmap(map_info)
Gst.init(None)
main_loop = GLib.MainLoop()
pipeline = Gst.parse_launch("""videotestsrc ! video/x-raw, width=320, height=240 ! queue  ! appsink sync=true     max-buffers=1 drop=true name=sink emit-signals=true""")
appsink = pipeline.get_by_name("sink")
pipeline.set_state(Gst.State.PLAYING)
handler_id = appsink.connect("new-sample", on_new_sample)
time.sleep(30)
pipeline.set_state(Gst.State.NULL)
main_loop.quit()

我得到的错误如下:

Traceback (most recent call last):
File "/home/rolf/scripts/tryAlgos/gst_record2.py", line 31, in on_new_sample
numpy_frame = np.ndarray(
TypeError: buffer is too small for requested array

从appsink缓冲区到numpy数组的propper方法是什么?我尝试了其他一些例子,但总是返回一个太小的缓冲区。

缓冲区大小意外减半是因为缓冲区代表YUV(I420(数据,而不是RGB数据。这就是为什么将videoconvert ! video/x-raw,format=RGB添加到管道中解决了这个问题,因为它在将YUV数据馈送到appsink之前将其转换为RGB数据。

我们怎么能说这就是问题所在呢?尝试:

gst-launch-1.0 -v videotestsrc num-buffers=10 ! fakesink silent=false

这将运行videotestsrc,并将fakesink接收到的内容输出到控制台(因为它不是静默的(。我们得到以下行作为输出的一部分:

/GstPipeline:pipeline0/GstVideoTestSrc:videotestsrc0.GstPad:src: caps = video/x-raw, format=(string)I420, width=(int)320, height=(int)240, framerate=(fraction)30/1, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive

正如我们所看到的,format=(string)I420,这意味着我们正在接收I420 YUV格式的数据。此格式定义如下:https://www.fourcc.org/pixel-format/yuv-i420/

正如您从该链接中看到的,Y通道是每像素一个字节,但U和V通道是每4个像素组一个字节。因此,总缓冲区大小预计为320*240 (Y) + 320*240/4 (U) + 320*240/4 (V) = 76800 + 19200 + 19200 = 115200

应该这样做。

buf = sample.get_buffer()
caps = sample.get_caps()
H, W, C = caps.get_structure(0).get_value('height'), caps.get_structure(0).get_value('width'), 3
arr = np.ndarray(
(H, W, C),
buffer=buf.extract_dup(0, buf.get_size()),
dtype=np.uint8
)

您的实现是正确的。

但是,您需要设置正确的上限来克服大小不匹配的问题。

例如,以下管道最终产生的缓冲区长度是height * width * n_channels(在我的例子中是480 * 640 * 3/2(所需长度的一半:

uridecodebin uri=rtsp://localhost:8000/test ! decodebin ! videoconvert ! appsink emit-signals=True

然而,有了正确的上限,你会得到正确的大小(在我的例子中是480 * 640 * 3(:

uridecodebin uri=rtsp://localhost:8000/test ! decodebin ! videoconvert ! video/x-raw, format=RGB ! appsink emit-signals=True

最新更新