In this article, I will see how to directly access the Inference result through the Probe function. We will use Python. However, since DeepStream's element plug-ins are developed in C/C++, the interface is achieved through Python Binding. Passing data between Python and element plugins developed in C/C++ through Python Binding is a bit tricky. To use it properly, you need to learn how to use it by referring to NVidia's manual.
Prerequisites
Several blogs have explained how to implement DeepStream using Python in Xavier NX. Since DeepStream can be used in all Jetson series using JetPack, the contents described here can be used in Jetson Nano, TX2, and Xavier. This time, we will cover the necessary contents to apply the DeepStream installed and tested earlier to your application. Please be sure to read the preceding articles first.
- Jetson Xavier NX - JetPack 4.4(production release) headless setup
- Jetson Xavier NX - Python virtual environment and ML platforms(tensorflow, Pytorch) installation
- Xavier NX-DeepStream 5.0 #1 - Installation
- Xavier NX-DeepStream 5.0 #2 - Run Python Samples(test1)
- Xavier NX-DeepStream 5.0 #3 - Run Python Samples (test2)
Python Bindings
You can find a detailed explanation in the DeepStream Python API Reference. Inference results can also be found in the Probe function.
Among the articles below, many parts related to Python binding were taken from Python Sample Apps Source Details.
MetaData
DeepStream MetaData contains inference results and other information used in analytics. The MetaData is attached to the Gst Buffer received by each pipeline component. The metadata format is described in detail in the SDK MetaData documentation and API Guide. The SDK MetaData library is developed in C/C++. Python bindings provide access to the MetaData from Python applications. The pyds.so module is available as part of the DeepStream SDK installation under /lib directory.
Memory Management
Memory for MetaData is shared by the Python and C/C++ code paths. For example, a MetaData item may be added by a probe function written in Python and needs to be accessed by a downstream plugin written in C/C++. The deepstream-test4 app contains such usage. The Python garbage collector does not have visibility into memory references in C/C++, and therefore cannot safely manage the lifetime of such shared memory. Because of this complication, Python access to MetaData memory is typically achieved via references without claiming ownership.
String Access
Some MetaData structures contain string fields. These are accessable in the following manner:
Setting a string field results in the allocation of a string buffer in the underlying C++ code.
obj.type = "Type"
pyds.free_buffer(obj.type)
Tips : NvOSD_TextParams.display_text string now gets freed automatically when a new string is assigned.
Directly reading a string field returns C address of the field in the form of an int, for example:
obj = pyds.NvDsVehicleObject.cast(data); print(obj.type)
This will print an int representing the address of obj.type in C (which is a char*).
To retrieve the string value of this field, use pyds.get_string(), for example:
print(pyds.get_string(obj.type))
Casting
Some MetaData instances are stored in GList form. To access the data in a GList node, the data field needs to be cast to the appropriate structure. This casting is done via cast() member function for the target type:
NvDsBatchMeta.cast
NvDsFrameMeta.cast
NvDsObjectMeta.cast
NvDsUserMeta.cast
NvDsClassifierMeta.cast
NvDsDisplayMeta.cast
NvDsLabelInfo.cast
NvDsEventMsgMeta.cast
NvDsVehicleObject.cast
NvDsPersonObject.cast
In version v0.5, standalone cast functions were provided. Those are now deprecated and superseded by the cast() functions above:
glist_get_nvds_batch_meta glist_get_nvds_frame_meta glist_get_nvds_object_meta glist_get_nvds_user_meta glist_get_nvds_classifier_meta glist_get_nvds_display_meta glist_get_nvds_label_info glist_get_nvds_event_msg_meta glist_get_nvds_vehicle_object glist_get_nvds_person_object
Example:
l_frame = batch_meta.frame_meta_list frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
Callback Function Registration
Custom
MetaData added to NvDsUserMeta require custom copy and release
functions. The MetaData library relies on these custom functions to
perform deep-copy of the custom structure, and free allocated resources.
These functions are registered as callback function pointers in the
NvDsUserMeta structure. Callback functions are registered using these
functions:
pyds.set_user_copyfunc(NvDsUserMeta_instance, copy_function) pyds.set_user_releasefunc(NvDsUserMeta_instance, free_func)
Tips
: Callbacks need to be unregistered with the bindings library before
the application exits. The bindings library currently keeps global
references to the registered functions, and these cannot last beyond
bindings library unload which happens at application exit. Use the
following function to unregister all callbacks:
pyds.unset_callback_funcs()
See the deepstream-test4 sample application for an example of callback registration and unregistration.
Limitation:
The bindings library currently only supports a single set of callback
functions for each application. The last registered function will be
used.
Optimizations and Utilities
Python
interpretation is generally slower than running compiled C/C++ code. To
provide better performance, some operations are implemented in C and
exposed via the bindings interface. This is currently experimental and
will expand over time.
The following optimized functions are available:
pyds.NvOSD_ColorParams.set(double red, double green, double blue, double alpha)
This is a simple function that performs the same operations as the following:
txt_params.text_bg_clr.red = red txt_params.text_bg_clr.green = green txt_params.text_bg_clr.blue = blue txt_params.text_bg_clr.alpha = alpha
These are performed on each object in deepstream_test_4.py, causing the aggregate processing time to slow down the pipeline. Pushing this function into the C layer helps to increase performance.
generate_ts_rfc3339 (buffer, buffer_size)
This function populates the input buffer with a timestamp generated according to RFC3339:
%Y-%m-%dT%H:%M:%S.nnnZ\0
Image Data Access
Decoded images are accessible as NumPy arrays via the get_nvds_buf_surface function. This function is documented in the API Guide.
Please see the deepstream-imagedata-multistream sample application for an example of image data usage.
What is Probe?
A needle-shaped tool made of metal is used as a kind of electrode and connected to a sensor to measure the temperature, vibration, and electrical changes during chemical changes.
<probe>
In DeepStream, we connect a probe function to understand what is happening in the nvdsosd element and update the data if necessary. GStreamer developers seem to have made this because this principle is reminiscent of probes used in measuring equipment such as multimeters and oscilloscopes that monitor electrical and electronic devices.
<probe concept in GStreamer Pipeline>The nvosd element plugin draws bounding boxes, text, and region of interest (RoI) polygons using inference results. Therefore, by examining the nvosd element, information on an object recognized in a frame of a video or camera stream can be found.
Tips : Therefore, the most important function is the probe callback function when you want to make your application using DeepStream.
Changing the output image
<default output screen of deeepstrem_test1.py>
In the picture above, the text on the upper right, which displays the frame information and the number of recognized objects, was added by the Probe function. Like this, the Probe function can change the content displayed by the nvosd element on the screen. The contents that can be changed are as follows.
- Text output
- Box color and color inside the box of classified objects
Text Output
The code related to text output in the Probe function is as follows.
# Acquiring a display meta object. The memory ownership remains in # the C code so downstream plugins can still access it. Otherwise # the garbage collector will claim it when this probe function exits. display_meta=pyds.nvds_acquire_display_meta_from_pool(batch_meta) display_meta.num_labels = 1 py_nvosd_text_params = display_meta.text_params[0] # Setting display text to be shown on screen # Note that the pyds module allocates a buffer for the string, and the # memory will not be claimed by the garbage collector. # Reading the display_text field here will return the C address of the # allocated string. Use pyds.get_string() to get the string content. py_nvosd_text_params.display_text = "Frame Number={} Number of Objects={} Vehicle_count={} Person_count={} FPS={}".format(frame_number, num_rects, obj_counter[PGIE_CLASS_ID_VEHICLE], obj_counter[PGIE_CLASS_ID_PERSON], (1 / (now - start))) # Now set the offsets where the string should appear py_nvosd_text_params.x_offset = 10 py_nvosd_text_params.y_offset = 12 # Font , font-color and font-size py_nvosd_text_params.font_params.font_name = "Serif" py_nvosd_text_params.font_params.font_size = 10 # set(red, green, blue, alpha); set to White py_nvosd_text_params.font_params.font_color.set(1.0, 1.0, 1.0, 1.0) # (red, green, blue , alpha) # Text background color py_nvosd_text_params.set_bg_clr = 1 # set(red, green, blue, alpha); set to Black py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0) # Using pyds.get_string() to get display_text as string if prt: print(pyds.get_string(py_nvosd_text_params.display_text)) pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
# Font , font-color and font-size py_nvosd_text_params.font_params.font_name = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" py_nvosd_text_params.font_params.font_size = 20 # set(red, green, blue, alpha); set to White py_nvosd_text_params.font_params.font_color.set(0.2, 0.2, 1.0, 1) # (red, green, blue , alpha) # Text background color py_nvosd_text_params.set_bg_clr = 1 # set(red, green, blue, alpha); set to Black py_nvosd_text_params.text_bg_clr.set(0.2, 0.2, 0.2, 0.3)
Box color and color inside the box of classified objects
Next, we will change the color of the box that displays the classified objects. In the github example, only the color of the box is specified, but the background color can also be specified. There is also an advantage of applying an alpha channel.
try: # Casting l_obj.data to pyds.NvDsObjectMeta #obj_meta=pyds.glist_get_nvds_object_meta(l_obj.data) obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data) except StopIteration: break obj_meta.rect_params.has_bg_color = 1 obj_meta.rect_params.bg_color.set(0.0, 0.0, 1.0, 0.2) #It seems that only the alpha channel is working. RGB value is reflected. obj_counter[obj_meta.class_id] += 1 obj_meta.rect_params.border_color.set(0.0, 1.0, 1.0, 1.0) #It seems that the alpha channel is not working. (red, green, blue , alpha)
I set the background color to blue and gave it an alpha value of 0.2. However, the background color was not applied and only appeared in yellow green. The alpha value was applied properly. I haven't tested the same code in C/C++ code, so it's not accurate, but it's probably a problem with the DeepStream 5.0 or Python version of DeepStream . Conversely, the box color was painted normally, but the alpha value was not applied. In the case of the box border, the alpha value has no meaning, so it may have been intentionally implemented like this. I couldn't find an explanation for this part in the document.
<Change the box color of the classified object and the color inside the box>
Return value of Probe function
Finally, let's look at the return value of the probe function. This value also has a significant effect on the screen output. There are 4 possible return values. You can find the definition and description of return values at https://lazka.github.io/pgi-docs/Gst-1.0/enums.html#Gst.PadProbeReturn.
- Gst.PadProbeReturn.DROP : drop data in data probes. For push mode this means that the data item is not sent downstream. For pull mode, it means that the data item is not passed upstream. In both cases, no other probes are called for this item and Gst.FlowReturn.OK or True is returned to the caller. It doesn't even draw a frame.
- Gst.PadProbeReturn.OK : normal probe return value. This leaves the probe in place, and defers decisions about dropping or passing data to other probes, if any. If there are no other probes, the default behaviour for the probe type applies (‘block’ for blocking probes, and ‘pass’ for non-blocking probes).
- Gst.PadProbeReturn.REMOVE : remove this probe. No more painting on the frame.
- Gst.PadProbeReturn.HANDLED : pass the data item in the block probe and block on the
next item.Do not paint in this frame.
Get Inference Results from DeepStream
The basic configuration for processing the result of inference in the
probe function is as follows. In the code below, you can process the
Inference result in the part commented out in blue.
def osd_sink_pad_buffer_probe(pad, info, u_data): gst_buffer = info.get_buffer() batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer)) l_frame = batch_meta.frame_meta_list while l_frame is not None: try: frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data) except StopIteration: break frame_number=frame_meta.frame_num l_obj=frame_meta.obj_meta_list while l_obj is not None: try: # Casting l_obj.data to pyds.NvDsObjectMeta obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data) #obj_meta contains Primary, Secondary Detection Results #Do What you want to do Here !!!! except StopIteration: break try: l_obj=l_obj.next except StopIteration: break try: l_frame=l_frame.next except StopIteration: break return Gst.PadProbeReturn.OK
<Basic code for processing inference results>
Primary Detector Inference Results
The following are information values that can be obtained even when only Promary Detector is used. The most important information is class_id, which informs the classified object information, and rect_params, which informs the location and drawing information of the object.
Object | Class Type | Description |
---|---|---|
obj_meta.class_id | int | Index of the object class infered by the primary detector/classifier. VEHICLE=0, BICYCLE=1, PERSON=2, ROADSIGN=3 |
obj_meta.confidence | float | Not implemented in DeepStream 5.0 |
obj_meta.detector_bbox_info | pyds.NvDsObjectMeta.NvDsComp_BboxInfo | Not implemented in DeepStream 5.0 |
obj_meta.obj_label | str | an array to store the string describing the class of the detected object |
obj_meta.object_id | int | Unique ID for tracking the object. @ref UNTRACKED_OBJECT_ID indicates the object has not been tracked |
obj_meta.rect_params | pyds.NvDsObjectMeta.NvOSD_RectParams | Structure containing the positional parameters of the object in the frame. Can also be used to overlay borders / semi-transparent boxes on top of objects. Refer @see pyds.NvOSD_RectParams |
obj_meta.rect_params.has_bg_color | int | Decide whether to paint the box background color or not. If set to 0, obj_meta.rect_params.bg_color is ignored |
obj_meta.rect_params.bg_color | pyds.NvOSD_ColorParams | Specifies the background color of the box. It contains 4 values (red, green, blue, alpha). Each value has a float value between 0 and 1. If the red value 255 corresponds to 1.0. |
obj_meta.rect_params.border_color | pyds.NvOSD_ColorParams | Specifies the color of the box border. It contains 4 values (red, green, blue, alpha). Each value has a float value between 0 and 1. If the red value 255 corresponds to 1.0. |
obj_meta.rect_params.border_width | int | Indicate the width of the box. |
obj_meta.rect_params.border_width | int | Indicate the width of the box. Thickness of Line |
obj_meta.rect_params.left | int | Holds the box's left coordinate in pixels |
obj_meta.rect_params.top | int | Holds the box's top coordinate in pixels |
obj_meta.rect_params.width | int | Holds the box's width coordinate in pixels |
obj_meta.rect_params.height | int | Holds the box's height coordinate in pixels. |
obj_meta.mask_params | pyds.NvDsObjectMeta.NvOSD_MaskParams | Not binded for Python. Don't use this object until the function is implemented |
Then, let's print out the result of the Primary Detector described in the table above. Then, let's print out the result of the Primary Detector described in the table above. The code is modified from deepstream_test2.py. I intentionally omitted the main function. The main function has not been modified. If you uncomment the commented part of analyze_meta function , you can check the values of the remaining members of obj_meta (NvDsObjectMeta class). In the example code, inference results are displayed every 30 frames.
#!/usr/bin/env python3
################################################################################
# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
################################################################################
import sys, time
sys.path.append('../')
import platform
import configparser
import gi
gi.require_version('Gst', '1.0')
from gi.repository import GObject, Gst
from common.is_aarch_64 import is_aarch64
from common.bus_call import bus_call
import pyds, ctypes
import cv2
import numpy as np
PGIE_CLASS_ID_VEHICLE = 0
PGIE_CLASS_ID_BICYCLE = 1
PGIE_CLASS_ID_PERSON = 2
PGIE_CLASS_ID_ROADSIGN = 3
VEHICLE_TYPES = ['Coupe', 'LargeVehicle', 'Sedan', 'suv', 'truck', 'van']
CAR_MAKE = ['acura', 'audi', 'bmw', 'chevrolet', 'chrysler', 'dodge', 'ford', 'gmc', 'honda', 'hyundai', 'infiniti', 'jeep', 'kia', 'lexus', 'mazda', 'mercedes', 'nissan', 'subaru', 'toyota', 'volkswagen']
CAR_COLOR = ['Black', 'Blue', 'brown', 'gold', 'green', 'grey','maroon', 'orange', 'red', 'silver', 'white', 'yellow' ]
PGIE_CLASS = ['VEHICLE', 'BICYCLE', 'PERSON', 'ROADSIGN']
SECONDARY_ELEMENT = ['','Primary Detector', 'CarColor', 'CarMake', 'VehicleTypes']
def analyze_meta( frame_number, obj_meta):
print(' === obj_meta Primary Detection [%d]==='%(frame_number))
print('object ={%s}'%(PGIE_CLASS[obj_meta. class_id]))
print('object_id={}'.format(obj_meta.object_id))
print('object height={}'.format(obj_meta.rect_params.height))
print('object left={}'.format(obj_meta.rect_params.left))
print('object top={}'.format(obj_meta.rect_params.top))
print('object width={}'.format(obj_meta.rect_params.width))
'''
print('class_id={}'.format(obj_meta. class_id))
print('confidence={}'.format(obj_meta.confidence))
#print('mask_params={}'.format(obj_meta.mask_params))
print('obj_label={}'.format(obj_meta.obj_label))
print('object_id={}'.format(obj_meta.object_id))
print('rect_params={}'.format(obj_meta.rect_params))
print(' rect_params bg_color alpha={}'.format(obj_meta.rect_params.bg_color.alpha))
print(' rect_params bg_color blue={}'.format(obj_meta.rect_params.bg_color.blue))
print(' rect_params bg_color green={}'.format(obj_meta.rect_params.bg_color.green))
print(' rect_params bg_color red={}'.format(obj_meta.rect_params.bg_color.red))
print(' rect_params border_color alpha={}'.format(obj_meta.rect_params.border_color.alpha))
print(' rect_params border_color blue={}'.format(obj_meta.rect_params.border_color.blue))
print(' rect_params border_color green={}'.format(obj_meta.rect_params.border_color.green))
print(' rect_params border_color red={}'.format(obj_meta.rect_params.border_color.red))
print(' rect_params border_width={}'.format(obj_meta.rect_params.border_width))
print(' rect_params color_id={}'.format(obj_meta.rect_params.color_id))
print(' rect_params has_bg_color={}'.format(obj_meta.rect_params.has_bg_color))
print(' rect_params has_color_info={}'.format(obj_meta.rect_params.has_color_info))
print(' rect_params height={}'.format(obj_meta.rect_params.height))
print(' rect_params left={}'.format(obj_meta.rect_params.left))
print(' rect_params top={}'.format(obj_meta.rect_params.top))
print(' rect_params width={}'.format(obj_meta.rect_params.width))
print(' rect_params reserved={}'.format(obj_meta.rect_params.reserved))
print('tracker_confidence={}'.format(obj_meta.tracker_confidence))
'''
''' Not binded properly. detector_bbox_info, tracker_bbox_info
print('detector_bbox_info={}'.format(dir(obj_meta.detector_bbox_info))) #NVDsComp_BboxInfo
print('detector_bbox_info org_bbox_coords.left={}'.format(obj_meta.detector_bbox_info.org_bbox_coords.left))
print('tracker_bbox_info={}'.format(dir(obj_meta.tracker_bbox_info))) #NVDsComp_BboxInfo
'''
start = time.time()
prt = True
def osd_sink_pad_buffer_probe(pad,info,u_data):
global start, prt
frame_number=0
#Intiallizing object counter with 0.
obj_counter = {
PGIE_CLASS_ID_VEHICLE:0,
PGIE_CLASS_ID_PERSON:0,
PGIE_CLASS_ID_BICYCLE:0,
PGIE_CLASS_ID_ROADSIGN:0
}
num_rects=0
gst_buffer = info.get_buffer()
if not gst_buffer:
print("Unable to get GstBuffer ")
return
# Retrieve batch metadata from the gst_buffer
# Note that pyds.gst_buffer_get_nvds_batch_meta() expects the
# C address of gst_buffer as input, which is obtained with hash(gst_buffer)
batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
l_frame = batch_meta.frame_meta_list
while l_frame is not None:
now = time.time()
try:
# Note that l_frame.data needs a cast to pyds.NvDsFrameMeta
# The casting is done by pyds.NvDsFrameMeta.cast()
# The casting also keeps ownership of the underlying memory
# in the C code, so the Python garbage collector will leave
# it alone.
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
except StopIteration:
break
frame_number=frame_meta.frame_num
num_rects = frame_meta.num_obj_meta
l_obj=frame_meta.obj_meta_list
while l_obj is not None:
try:
# Casting l_obj.data to pyds.NvDsObjectMeta
obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data)
if (frame_number %30 == 0):
obj_user = obj_meta.obj_user_meta_list
analyze_meta(frame_number, obj_meta)
except StopIteration:
break
obj_counter[obj_meta.class_id] += 1
try:
l_obj=l_obj.next
except StopIteration:
break
# Acquiring a display meta object. The memory ownership remains in
# the C code so downstream plugins can still access it. Otherwise
# the garbage collector will claim it when this probe function exits.
display_meta=pyds.nvds_acquire_display_meta_from_pool(batch_meta)
display_meta.num_labels = 1
py_nvosd_text_params = display_meta.text_params[0]
# Setting display text to be shown on screen
# Note that the pyds module allocates a buffer for the string, and the
# memory will not be claimed by the garbage collector.
# Reading the display_text field here will return the C address of the
# allocated string. Use pyds.get_string() to get the string content.
py_nvosd_text_params.display_text = "Frame Number={} Number of Objects={} Vehicle_count={} Person_count={} FPS={}".format(frame_number, num_rects, obj_counter[PGIE_CLASS_ID_VEHICLE], obj_counter[PGIE_CLASS_ID_PERSON], (1 / (now - start)))
# Now set the offsets where the string should appear
py_nvosd_text_params.x_offset = 10
py_nvosd_text_params.y_offset = 12
# Font , font-color and font-size
py_nvosd_text_params.font_params.font_name = "Serif"
py_nvosd_text_params.font_params.font_size = 10
# set(red, green, blue, alpha); set to White
py_nvosd_text_params.font_params.font_color.set(1.0, 1.0, 1.0, 1.0)
# Text background color
py_nvosd_text_params.set_bg_clr = 1
# set(red, green, blue, alpha); set to Black
py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0)
# Using pyds.get_string() to get display_text as string
if(prt):
print(pyds.get_string(py_nvosd_text_params.display_text))
pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
try:
l_frame=l_frame.next
except StopIteration:
break
start = now
prt = False
return Gst.PadProbeReturn.OK
Now run the code.
(python) spypiggy@XavierNX:~/src/deepstream_python_apps/apps/deepstream-test2$ python3 deepstream_test2_primary.py /opt/nvidia/deepstream/deepstream/samples/streams/sample_720p.h264 ...... ...... === obj_meta Primary Detection [0]=== object ={VEHICLE} object_id=1 object height=45.0 object left=585.0 object top=466.875 object width=60.0 === obj_meta Primary Detection [0]=== object ={VEHICLE} object_id=2 object height=42.1875 object left=540.0 object top=472.5 object width=48.0 === obj_meta Primary Detection [0]=== object ={VEHICLE} object_id=3 object height=81.5625 object left=612.0 object top=486.5625 object width=102.0 === obj_meta Primary Detection [0]=== object ={PERSON} object_id=4 object height=627.1875 object left=3.0 object top=396.5625 object width=198.0 === obj_meta Primary Detection [0]=== object ={PERSON} object_id=5 object height=376.875 object left=294.0 object top=452.8125 object width=159.0 Frame Number=0 Number of Objects=5 Vehicle_count=3 Person_count=2 FPS=0.01487494765818283 ...... ......
In the above frame 0 output screen, it can be seen that the classification of 5 objects (3 cars and 2 people) and the location and size of individual objects are accurately output.
Secondary Detector Inference Results
Example deepstream_test2 goes through a second inference that classifies car color, car type, and manufacturer, using the inference result passed through the Primary Detector. These two processes are connected to each other and must be connected to each other because the result value passed through the primary detector is used. For an explanation of the example deepstream_test2, refer to Xavier NX-DeepStream 5.0 #3-Run Python Samples (test2).
This time, I added the code to get the result of Secondary Detector.
The core of the Secondary Detector result is the NvDsClassifierMeta class.
For one Primary Detection result, 3 Secondary Detectors (car color, car maker, car type) operate. Therefore, up to 3 results can be obtained. Therefore, iteration is repeated to bring the result values of the Secondary Detectors. The number of result values is between 0 and 3. For how to repeat iteration, check directly with the code below.
And when iteration determines that there is a result value, the object of the NvDsLabelInfo class type is obtained using the pyds.glist_get_nvds_label_info function. This object contains the result of the Secondary Detector.
def analyze_meta( frame_number, obj_meta): print(' === obj_meta Primary Detection [%d]==='%(frame_number)) print('object ={%s}'%(PGIE_CLASS[obj_meta. class_id])) print('object_id={}'.format(obj_meta.object_id)) print('object height={}'.format(obj_meta.rect_params.height)) print('object left={}'.format(obj_meta.rect_params.left)) print('object top={}'.format(obj_meta.rect_params.top)) print('object width={}'.format(obj_meta.rect_params.width)) if obj_meta.class_id == PGIE_CLASS_ID_VEHICLE: #Only vehicle supports secondary inference print(' === obj_meta Secondary Detection ===') cls_meta = obj_meta.classifier_meta_list while cls_meta is not None: cls = pyds.NvDsClassifierMeta.cast(cls_meta.data) #unique_component_id : Primary Detector:1, Secondary_CarColor: 2, Secondary_CarMake : 3, Secondary_VehicleTypes:4 #print('Secondary element={}'.format(SECONDARY_ELEMENT[cls.unique_component_id])) #print(' num_labels={}'.format(cls.num_labels)) info = cls.label_info_list # type of pyds.GList while info is not None: label_meta = pyds.glist_get_nvds_label_info(info.data) if cls.unique_component_id == 2: print('\tCarColor ={}'.format(CAR_COLOR[label_meta.result_class_id])) elif cls.unique_component_id == 3: print('\tCarMake ={}'.format(CAR_MAKE[label_meta.result_class_id])) elif cls.unique_component_id == 4: print('\tVehicleTypes ={}'.format(VEHICLE_TYPES[label_meta.result_class_id])) #print('label_meta label_id ={}'.format(label_meta.label_id)) print('\tlabel_meta result_prob ={}'.format(label_meta.result_prob)) try: info=info.next except StopIteration: break try: cls_meta=cls_meta.next except StopIteration: break
<deepstream_test2_secondary.py>
Run the code.
(python) spypiggy@XavierNX:~/src/deepstream_python_apps/apps/deepstream-test2$ python3 deepstream_test2_secondary.py /opt/nvidia/deepstream/deepstream/samples/streams/sample_720p.h264 ...... ...... === obj_meta Primary Detection [1440]=== object ={VEHICLE} object_id=1150 object height=22.5 object left=1176.0 object top=480.9375 object width=57.0 === obj_meta Secondary Detection === === obj_meta Primary Detection [1440]=== object ={VEHICLE} object_id=1143 object height=33.75 object left=588.0 object top=483.75 object width=42.0 === obj_meta Secondary Detection === === obj_meta Primary Detection [1440]=== object ={VEHICLE} object_id=1088 object height=115.3125 object left=636.0 object top=472.5 object width=147.0 === obj_meta Secondary Detection === CarColor =white label_meta result_prob =0.5112398266792297 CarMake =nissan label_meta result_prob =0.9992345571517944 VehicleTypes =suv label_meta result_prob =0.5845340490341187 === obj_meta Primary Detection [1440]=== object ={VEHICLE} object_id=1140 object height=126.5625 object left=834.0 object top=461.25 object width=210.0 === obj_meta Secondary Detection === CarColor =red label_meta result_prob =0.7972736358642578 VehicleTypes =suv label_meta result_prob =0.7728081941604614 === obj_meta Primary Detection [1440]=== object ={VEHICLE} object_id=1152 object height=45.0 object left=1410.0 object top=486.5625 object width=126.0 === obj_meta Secondary Detection === === obj_meta Primary Detection [1440]=== object ={VEHICLE} object_id=1153 object height=67.5 object left=1578.0 object top=483.75 object width=174.0 === obj_meta Secondary Detection === CarColor =white label_meta result_prob =0.9581888318061829 VehicleTypes =truck label_meta result_prob =0.6332785487174988 === obj_meta Primary Detection [1440]=== object ={PERSON} object_id=1142 object height=67.5 object left=441.0 object top=478.125 object width=21.0 === obj_meta Primary Detection [1440]=== object ={PERSON} object_id=1032 object height=67.5 object left=411.0 object top=478.125 object width=24.0
From the recognition result of object_id value of 1088, it can be seen that the car maker, color, and type are all identified. However, in the case of object_id 1152, the Secondary Detector did not recognize anything.
Get frames available in OpenCV from DeepStream
This time, I will see how to check the result value recognized by the probe function as an image. Images delivered through GStreamer's pipeline can be checked directly through the probe function. I will explain how to integrate OpenCV, which we are familiar with.
The function to get the image from the probe function is done in the pyds.get_nvds_buf_surface function.
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data) img = pyds.get_nvds_buf_surface(hash(gst_buffer), frame_meta.batch_id) img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
Tip : DeepStream uses RGBA channels for alpha channel processing. Therefore, change it to the BGR format used for OpenCV.
This time, I will make an example of saving the objects recognized earlier as files. A specific object is cut out from the original image by using the object size obtained from Primary Detection. And, in the case of a car, the result value obtained from Secondary Detection is added to the file name.
def analyze_meta(img, frame_number, obj_meta, save): filename = '/home/spypiggy/src/test_images/result/%d_%d_'%(frame_number, obj_meta.object_id) print(' === obj_meta Primary Detection [%d]==='%(frame_number)) print('object ={%s}'%(PGIE_CLASS[obj_meta. class_id])) print('object_id={}'.format(obj_meta.object_id)) print('object height={}'.format(obj_meta.rect_params.height)) print('object left={}'.format(obj_meta.rect_params.left)) print('object top={}'.format(obj_meta.rect_params.top)) print('object width={}'.format(obj_meta.rect_params.width)) filename += (PGIE_CLASS[obj_meta. class_id]) if obj_meta.class_id == PGIE_CLASS_ID_VEHICLE: #Only vehicle supports secondary inference print(' === obj_meta Secondary Detection ===') cls_meta = obj_meta.classifier_meta_list filename_ex = filename while cls_meta is not None: cls = pyds.NvDsClassifierMeta.cast(cls_meta.data) #unique_component_id : Primary Detector:1, Secondary_CarColor: 2, Secondary_CarMake : 3, Secondary_VehicleTypes:4 #print('Secondary element={}'.format(SECONDARY_ELEMENT[cls.unique_component_id])) #print(' num_labels={}'.format(cls.num_labels)) info = cls.label_info_list # type of pyds.GList while info is not None: label_meta = pyds.glist_get_nvds_label_info(info.data) if cls.unique_component_id == 2: print('\tCarColor ={}'.format(CAR_COLOR[label_meta.result_class_id])) filename_ex += ('_'+CAR_COLOR[label_meta.result_class_id]) elif cls.unique_component_id == 3: print('\tCarMake ={}'.format(CAR_MAKE[label_meta.result_class_id])) filename_ex += ('_'+CAR_MAKE[label_meta.result_class_id]) elif cls.unique_component_id == 4: print('\tVehicleTypes ={}'.format(VEHICLE_TYPES[label_meta.result_class_id])) filename_ex += ('_'+VEHICLE_TYPES[label_meta.result_class_id]) #print('label_meta label_id ={}'.format(label_meta.label_id)) print('\tlabel_meta result_prob ={}'.format(label_meta.result_prob)) filename_ex += ('_%4.2f'%(label_meta.result_prob)) try: info=info.next except StopIteration: break try: cls_meta=cls_meta.next except StopIteration: break filename_ex += '.jpg' if save: crop_img = img[int(obj_meta.rect_params.top):int(obj_meta.rect_params.top)+int(obj_meta.rect_params.height), int(obj_meta.rect_params.left):int(obj_meta.rect_params.left)+int(obj_meta.rect_params.width)] cv2.imwrite(filename_ex, crop_img) else: # save 'BICYCLE', 'PERSON', 'ROADSIGN' filename_ex = filename + '.jpg' if save: crop_img = img[int(obj_meta.rect_params.top):int(obj_meta.rect_params.top)+int(obj_meta.rect_params.height), int(obj_meta.rect_params.left):int(obj_meta.rect_params.left)+int(obj_meta.rect_params.width)] cv2.imwrite(filename_ex, crop_img) start = time.time() prt = True def osd_sink_pad_buffer_probe(pad,info,u_data): global start, prt frame_number=0 #Intiallizing object counter with 0. obj_counter = { PGIE_CLASS_ID_VEHICLE:0, PGIE_CLASS_ID_PERSON:0, PGIE_CLASS_ID_BICYCLE:0, PGIE_CLASS_ID_ROADSIGN:0 } num_rects=0 gst_buffer = info.get_buffer() if not gst_buffer: print("Unable to get GstBuffer ") return # Retrieve batch metadata from the gst_buffer # Note that pyds.gst_buffer_get_nvds_batch_meta() expects the # C address of gst_buffer as input, which is obtained with hash(gst_buffer) batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer)) l_frame = batch_meta.frame_meta_list while l_frame is not None: now = time.time() try: # Note that l_frame.data needs a cast to pyds.NvDsFrameMeta # The casting is done by pyds.NvDsFrameMeta.cast() # The casting also keeps ownership of the underlying memory # in the C code, so the Python garbage collector will leave # it alone. frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data) img = pyds.get_nvds_buf_surface(hash(gst_buffer), frame_meta.batch_id) img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR) except StopIteration: break frame_number=frame_meta.frame_num num_rects = frame_meta.num_obj_meta if (frame_number %30 == 0): # Save Full image cv2.imwrite('/home/spypiggy/src/test_images/result/%d_original.jpg'%frame_number, img) l_obj=frame_meta.obj_meta_list while l_obj is not None: try: # Casting l_obj.data to pyds.NvDsObjectMeta obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data) if (frame_number %30 == 0): obj_user = obj_meta.obj_user_meta_list analyze_meta(img, frame_number, obj_meta, True) except StopIteration: break obj_counter[obj_meta.class_id] += 1 try: l_obj=l_obj.next except StopIteration: break # Acquiring a display meta object. The memory ownership remains in # the C code so downstream plugins can still access it. Otherwise # the garbage collector will claim it when this probe function exits. display_meta=pyds.nvds_acquire_display_meta_from_pool(batch_meta) display_meta.num_labels = 1 py_nvosd_text_params = display_meta.text_params[0] # Setting display text to be shown on screen # Note that the pyds module allocates a buffer for the string, and the # memory will not be claimed by the garbage collector. # Reading the display_text field here will return the C address of the # allocated string. Use pyds.get_string() to get the string content. py_nvosd_text_params.display_text = "Frame Number={} Number of Objects={} Vehicle_count={} Person_count={} FPS={}".format(frame_number, num_rects, obj_counter[PGIE_CLASS_ID_VEHICLE], obj_counter[PGIE_CLASS_ID_PERSON], (1 / (now - start))) # Now set the offsets where the string should appear py_nvosd_text_params.x_offset = 10 py_nvosd_text_params.y_offset = 12 # Font , font-color and font-size py_nvosd_text_params.font_params.font_name = "Serif" py_nvosd_text_params.font_params.font_size = 10 # set(red, green, blue, alpha); set to White py_nvosd_text_params.font_params.font_color.set(1.0, 1.0, 1.0, 1.0) # Text background color py_nvosd_text_params.set_bg_clr = 1 # set(red, green, blue, alpha); set to Black py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0) # Using pyds.get_string() to get display_text as string if(prt): print(pyds.get_string(py_nvosd_text_params.display_text)) pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta) try: l_frame=l_frame.next except StopIteration: break start = now prt = False return Gst.PadProbeReturn.OK
Now when you run the program, you will see a lot of image files in the output directory.
Let's check the resulting images of frame 240.
The following image is a combination of image files storing objects recognized in frame 240 into one image. There are some errors in recognition, but we can see that we have handled the desired results correctly.
<detected ocjects and saved croped files>
Wrapping Up
Due to the lack of examples using Python in DeepStream, it took a few days to create code to find the result of Secondary Detector and save it as an image. In the future, many Python examples will be introduced in DeepStream, so users who develop with Python are expected to be able to easily develop DeepStream-based solutions.
You can download the source code at https://github.com/raspberry-pi-maker/NVIDIA-Jetson .
Bro, This is Brilliant. Even the nvidia dint give such nice explanations.
답글삭제