The plugin adds one ROI metadata object to every buffer. The ROI contains a
label and bounding box. Extra fields, such as confidence and frame count, are
stored in a Gst.Structure parameter attached to the ROI.
#!/usr/bin/env python3importgigi.require_version("Gst","1.0")gi.require_version("GstBase","1.0")gi.require_version("GstVideo","1.0")fromgi.repositoryimportGst,GstBase,GstVideo,GObjectGst.init(None)classAppSinkMetaFilter(GstBase.BaseTransform):__gstmetadata__=("AppSinkMetaFilter","Filter/Analyzer/Video","Attach ROI metadata to buffers for appsink","example",)__gsttemplates__=(Gst.PadTemplate.new("sink",Gst.PadDirection.SINK,Gst.PadPresence.ALWAYS,Gst.Caps.from_string("video/x-raw"),),Gst.PadTemplate.new("src",Gst.PadDirection.SRC,Gst.PadPresence.ALWAYS,Gst.Caps.from_string("video/x-raw"),),)def__init__(self):super().__init__()self.frame_count=0defdo_transform_ip(self,buffer):self.frame_count+=1roi=GstVideo.buffer_add_video_region_of_interest_meta(buffer,"person",120,40,80,180,)# add params to roi object params=Gst.Structure.new_empty("detection")params.set_value("frame-count",self.frame_count)params.set_value("confidence",0.91)roi.add_param(params)returnGst.FlowReturn.OKGObject.type_register(AppSinkMetaFilter)__gstelementfactory__=("appsinkmetafilter",Gst.Rank.NONE,AppSinkMetaFilter)
roi.add_param(params) attaches extra fields to the ROI.
The buffer continues downstream to appsink.
Read the metadata from appsink
GST_PLUGIN_PATH
Don't forget using / export GST_PLUGIN_PATH environment variable
Pull style
The application pulls samples from appsink, gets the sample buffer, and reads the ROI metadata using buffer_get_video_region_of_interest_meta_id from that buffer.
#!/usr/bin/env python3importgigi.require_version("Gst","1.0")gi.require_version("GstApp","1.0")gi.require_version("GstVideo","1.0")fromgi.repositoryimportGLib,Gst,GstApp,GstVideoGst.init(None)pipeline=Gst.parse_launch("videotestsrc num-buffers=5 ! ""videoconvert ! ""appsinkmetafilter ! ""appsink name=sink emit-signals=false sync=false")appsink=pipeline.get_by_name("sink")pipeline.set_state(Gst.State.PLAYING)whileTrue:sample=appsink.try_pull_sample(Gst.SECOND)ifsampleisNone:breakbuffer=sample.get_buffer()roi=GstVideo.buffer_get_video_region_of_interest_meta_id(buffer,0)ifnotroi:print("sample has no ROI metadata")continuelabel=GLib.quark_to_string(roi.roi_type)params=roi.get_param("detection")print("label:",label,"box:",(roi.x,roi.y,roi.w,roi.h),"frame:",params.get_value("frame-count")ifparamselseNone,"confidence:",params.get_value("confidence")ifparamselseNone,"pts:",int(buffer.pts),)pipeline.set_state(Gst.State.NULL)
Callback style
You can also let appsink emit a new-sample signal and read each sample from
a callback. This is useful when the application already uses a GLib.MainLoop.
The metadata is still buffer metadata, so the callback still gets the sample
from appsink and then reads the Gst.Buffer.
#!/usr/bin/env python3importgigi.require_version("Gst","1.0")gi.require_version("GstVideo","1.0")fromgi.repositoryimportGLib,Gst,GstVideoGst.init(None)defprint_roi_from_sample(sample):buffer=sample.get_buffer()roi=GstVideo.buffer_get_video_region_of_interest_meta_id(buffer,0)ifnotroi:print("sample has no ROI metadata")returnlabel=GLib.quark_to_string(roi.roi_type)params=roi.get_param("detection")print("label:",label,"box:",(roi.x,roi.y,roi.w,roi.h),"frame:",params.get_value("frame-count")ifparamselseNone,"confidence:",params.get_value("confidence")ifparamselseNone,"pts:",int(buffer.pts),)defon_new_sample(appsink):sample=appsink.emit("pull-sample")ifsampleisNone:returnGst.FlowReturn.ERRORprint_roi_from_sample(sample)returnGst.FlowReturn.OKdefon_bus_message(bus,message,loop):ifmessage.type==Gst.MessageType.EOS:loop.quit()elifmessage.type==Gst.MessageType.ERROR:error,debug=message.parse_error()print("error:",error.message)ifdebug:print("debug:",debug)loop.quit()pipeline=Gst.parse_launch("videotestsrc num-buffers=5 ! ""videoconvert ! ""appsinkmetafilter ! ""appsink name=sink emit-signals=true sync=false")loop=GLib.MainLoop()appsink=pipeline.get_by_name("sink")appsink.connect("new-sample",on_new_sample)bus=pipeline.get_bus()bus.add_signal_watch()bus.connect("message",on_bus_message,loop)pipeline.set_state(Gst.State.PLAYING)loop.run()pipeline.set_state(Gst.State.NULL)
This is a callback example, not a Gst.Event example. A Gst.Event is normally
used for pipeline control information such as EOS, seek, flush, or custom
upstream/downstream events. Per-frame ROI data should stay on the Gst.Buffer
as metadata.
videotestsrc ! fakedetector ! roiprinter ! appsink
| | |
| | +-> application reads ROI metadata
| +-> reads ROI metadata from the buffer
+-> attaches ROI metadata to the buffer
fakedetector creates the detection. roiprinter reads the detection from the
same buffer and passes the buffer downstream unchanged. appsink can still read
the same metadata after both plugins.
Detector plugin
The detector attaches one fake object box to the buffer.
The next plugin reads the bounding box from the same buffer. This example only
prints it. A real drawing plugin would use the box values to draw on the frame.