| Extensions | Event based Extensions | References |
Extensions are a simple way to add functionality to a Intel(R) Deep Learning Streamer (Intel(R) DL Streamer) Pipeline Server pipeline using its python bindings and GVAPython element. By extending pipelines using gvapython
a developer can access frames and analyze and/or update metadata such as detected objects or JSON messages. The Intel(R) DL Streamer gvapython samples provide more examples of the full breadth of capabilities.
An extension is a GVA Python script that is called during pipeline execution. The script is given a frame and any Pipeline Server parameters defined by the pipeline request. The extension must be added after the last element that generates data it requires. Below are some examples that cover how extensions can be used to process inference results.
Note: Make sure to either build the Pipeline Server container with
docker/build.sh
after editing extension code and/or pipeline per Build script reference or use --dev mode during run as outlined in Running Intel(R) DL Streamer Pipeline Server
This section will outline an example to show how inference results of object detection can be accessed and used to generate alerts.
process_frame
is the default function invoked by GVA Python.
Note: The
process_frame
function needs toreturn True
in order for the rest of the pipeline to function. Withoutreturn True
or withreturn False
, the extension runs but drops frames. This could be useful to short circuit processing i.e intentionally drop certain frames.
In the example below, in process_frame
, the number of objects in the frame is obtained by counting the number of detected regions. A statement is printed if the number of objects exceeds a threshold value.
class ObjectCounter:
def __init__(self):
self._threshold = 0
def process_frame(self, frame):
num_objects = len(list(frame.regions()))
if num_objects > self._threshold:
print("Object count {} exceeded threshold {}".format(
num_objects, self._threshold))
return True
The extension must be added after the last element that generates data it requires. In this case it is placed after gvadetect
. The template of the object_detection/person_vehicle_bike pipeline is modified by adding a line for the GVA python extension as shown below.
class
, module
and name
are gvapython
parameters. See gvapython
documentation for a full list of parameters.
<snip>
"template": ["uridecodebin name=source",
" ! gvadetect model={models[object_detection][person_vehicle_bike][network]} name=detection",
" ! gvapython class=ObjectCounter module=/home/pipeline-server/extensions/object_counter.py name=object-counter",
" ! gvametaconvert name=metaconvert ! gvametapublish name=destination",
" ! appsink name=appsink"
]
<snip>
The pipeline can be run with Pipeline Client as follows:
./client/pipeline_client.sh run object_detection/person_vehicle_bike https://github.com/intel-iot-devkit/sample-videos/blob/master/person-bicycle-car-detection.mp4?raw=true
As process_frame runs once per frame, the Pipeline Server output would resemble
{"levelname": "INFO", "asctime": "2021-08-31 23:12:44,838", "message": "Setting Pipeline 1 State to RUNNING", "module": "gstreamer_pipeline"}
Object count 1 exceeded threshold 0
Object count 1 exceeded threshold 0
<snip>
The same object_detection/person_vehicle_bike pipeline will be used to illustrate setting parameters. Instead of hard-coding the value for object count threshold in the previous example, it's possible to set it at request time.
Note: The
kwarg
parameter is a JSON object and requires the pipeline parameter format to be set tojson
as in theobject-counter
example below. The default value for the object needs to be specified at the parameter level and not in the individual properties due to how default values are applied.
The extension can take in the defined parameters via the constructor and use them. In this case, a count_threshold
parameter is passed in and in process_frame
, the value is used to check if number of detected regions exceeds the threshold.
class ObjectCounter:
def __init__(self, count_threshold):
self._threshold = count_threshold
def process_frame(self, frame):
num_objects = len(list(frame.regions()))
if num_objects > self._threshold:
print("Object count {} exceeded threshold {}".format(
num_objects, self._threshold))
return True
{
"type": "GStreamer",
"template": [
"uridecodebin name=source",
" ! gvadetect model={models[object_detection][person_vehicle_bike][network]} name=detection",
" ! gvapython class=ObjectCounter module=/home/pipeline-server/extensions/object_counter.py name=object-counter",
" ! gvametaconvert name=metaconvert ! gvametapublish name=destination",
" ! appsink name=appsink"
],
"description": "Person Vehicle Bike Detection based on person-vehicle-bike-detection-crossroad-0078",
"parameters": {
"type": "object",
"properties": {
"detection-device": {
"element": {
"name": "detection",
"property": "device"
},
"type": "string"
},
"detection-model-instance-id": {
"element": {
"name": "detection",
"property": "model-instance-id"
},
"type": "string"
},
"inference-interval": {
"element": "detection",
"type": "integer"
},
"threshold": {
"element": "detection",
"type": "number"
},
"object-counter": {
"element": {
"name": "object-counter",
"property": "kwarg",
"format": "json"
},
"type": "object",
"properties": {
"count_threshold": {
"type": "integer"
}
},
"default": {
"count_threshold": 0
}
}
}
}
}
- Running Pipeline Client as shown (parameter-file is optional for extension parameters if defaults are set in pipeline JSON)
Output reflects the default count_threshold i.e 0
./client/pipeline_client.sh run object_detection/person_vehicle_bike https://github.com/intel-iot-devkit/sample-videos/blob/master/person-bicycle-car-detection.mp4?raw=true
<snip> {"levelname": "INFO", "asctime": "2021-08-31 18:49:19,360", "message": "Setting Pipeline 1 State to RUNNING", "module": "gstreamer_pipeline"} Object count 1 exceeded threshold 0 Object count 1 exceeded threshold 0 <snip>
- Running Pipeline Client with the following parameter file
{ "parameters": { "object-counter": { "count_threshold": 1 } } }
The Pipeline Server output shows count_threshold is set to 1 per parameter file./client/pipeline_client.sh run object_detection/person_vehicle_bike https://github.com/intel-iot-devkit/sample-videos/blob/master/person-bicycle-car-detection.mp4?raw=true --parameter-file /tmp/sample_parameters.json
<snip> {"levelname": "INFO", "asctime": "2021-09-01 16:28:30,037", "message": "Setting Pipeline 1 State to RUNNING", "module": "gstreamer_pipeline"} Object count 2 exceeded threshold 1 Object count 2 exceeded threshold 1 <snip>
The following sections build on the previous extension example that detects when the number of objects in a frame exceeds a specific threshold and additionally demonstrates how to publish a corresponding event using the gva_event_meta
module along with gva_event_convert
, gvametaconvert
and gvametapublish
.
Note: Events are a preview feature and related aspects like their schema are subject to change.
Events are a type of metadata that can be added and read from a frame using methods from the gva_event_meta
module. They illustrate how to add and publish additional information using the underlying Intel(R) DL Streamer python bindings.
Events are also used to publish results of the new set of Pipeline Server spatial analytics extensions: object_line_crossing and object_zone_count.
Events are added to a frame and stored as a JSON message containing a list of event objects. The only required key is event-type
which is the type of event. It is defined by the extension. An optional field is related-objects which is an array of indices to the list of detected objects. This allows a many-to-many relationship between events and the objects that create them (e.g. a social distancing algorithm would have one violate event and a number of objects that were too close to each other).
Here is an example of how events are added to existing metadata showing an event type object-count-exceeded
with two related objects.
"objects": [
{"detection":{"bounding_box":{"x_max":0.4753129482269287,"x_min":0.306820273399353,"y_max":0.9987226724624634,"y_min":0.5475195646286011},"confidence":0.8045234084129333,"label":"person","label_id":1},"h":195,"roi_type":"person","w":129,"x":236,"y":237},
{"detection":{"bounding_box":{"x_max":0.4753129482269287,"x_min":0.306820273399353,"y_max":0.9987226724624634,"y_min":0.5475195646286011},"confidence":0.8045234084129333,"label":"person","label_id":1},"h":195,"roi_type":"person","w":129,"x":200,"y":237}
],
"events": [
{
"event-type": "object-count-exceeded",
"related-objects": [
0,
1
]
}
]
The following example generates an event type object-count-exceeded
if more than a pre-defined number of objects are detected in a frame.
Few things to note:
- The
count_threshold
pipeline parameter is picked up in the constructor as it was defined in the parameters section of the pipeline. - Use of the
gva_event_meta
module's functionadd_event()
to populate an event object. attributes
dictionary is meant for storing event related information. It needs to be set to a non-empty value.
import gva_event_meta
class ObjectCounter:
def __init__(self, count_threshold):
self._threshold = count_threshold
def process_frame(self, frame):
num_objects = len(list(frame.regions()))
if num_objects > self._threshold:
attributes = {'num_objects': num_objects}
gva_event_meta.add_event(frame,
event_type="object_count_exceeded",
attributes=attributes)
return True
The extension must be added after the last element that generates data it requires. Following the event based extension example, as events are not part of the Intel(R) DL Streamer message schema we also add an extension called gva_event_convert
after gvametaconvert
and before gvametapublish
. This reusable extension adds an event list to the published result.
A pipeline using a sample object counting extension would look like this:
and the pipeline JSON would look like this:
{
"type": "GStreamer",
"template": [
"uridecodebin name=source",
" ! gvadetect model={models[object_detection][person_vehicle_bike][network]} name=detection",
" ! gvapython class=ObjectCounter module=/home/pipeline-server/extensions/object_counter.py name=object-counter",
" ! gvametaconvert name=metaconvert",
" ! gvapython module=/home/pipeline-server/extensions/gva_event_meta/gva_event_convert.py",
" ! gvametapublish name=destination",
" ! appsink name=appsink"
],
"description": "Person Vehicle Bike Detection based on person-vehicle-bike-detection-crossroad-0078",
"parameters": {
"type": "object",
"properties": {
"detection-device": {
"element": {
"name": "detection",
"property": "device"
},
"type": "string"
},
"detection-model-instance-id": {
"element": {
"name": "detection",
"property": "model-instance-id"
},
"type": "string"
},
"inference-interval": {
"element": "detection",
"type": "integer"
},
"threshold": {
"element": "detection",
"type": "number"
},
"object-counter": {
"element": {
"name": "object-counter",
"property": "kwarg",
"format": "json"
},
"type": "object",
"properties": {
"count_threshold": {
"type": "integer"
}
},
"default": {
"count_threshold": 1
}
}
}
}
}
event_type
is a required field set during add_event in the extension above.
num_objects
is a custom optional field set as part of attributes
in the extension above.
{
"events": [
{
"event-type": "object_count_exceeded",
"num_objects": 2
}
]
}
Another optional field (unused here) is related_objects
as shown in line crossing and zone counting.
Pipeline Client can be launched as follows, as no parameter-file is given, the default count_threshold is picked up i.e 1.
./client/pipeline_client.sh run object_detection/person_vehicle_bike https://github.com/intel-iot-devkit/sample-videos/blob/master/person-bicycle-car-detection.mp4?raw=true
Output snippet is shown, events are fired for object count > 1:
<snip>
Timestamp 43666666666
- person (0.80) [0.25, 0.03, 0.28, 0.21]
Timestamp 43750000000
- person (0.84) [0.26, 0.02, 0.29, 0.21]
Timestamp 43833333333
- person (0.98) [0.27, 0.02, 0.31, 0.19]
Timestamp 43916666666
- person (0.97) [0.27, 0.02, 0.31, 0.20]
- vehicle (0.76) [0.10, 0.85, 0.30, 1.00]
Event: event-type: object_count_exceeded, num_objects: 2
<snip>
-
For details on frame inference data classes i.e video frame, regions of interest, tensors see Intel(R) DL Streamer gstgva Python classes.
-
For details on more advanced extensions, see the line crossing and zone counting. These include more complex parameters, guidance on how to break down algorithmic implementation to simplify event generation and how to use watermarking for visualizing output.
-
Note also how the pipeline definition is used to validate parameter schemas.
-
See object_line_crossing.md and object_zone_count.md for more information and a format for extension documentation.