Skip to content

Instantly share code, notes, and snippets.

@mfoglio
Last active December 13, 2021 19:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mfoglio/5dca2c6cc82fc17c71742d6d3c3aaf92 to your computer and use it in GitHub Desktop.
Save mfoglio/5dca2c6cc82fc17c71742d6d3c3aaf92 to your computer and use it in GitHub Desktop.
Yolo V3 slow fps
# Build
docker build . -t deepstream-custom-slow
# Run
# Add your RTSP video url on line 64 of pipeline.py . You can also try to use a local file.
# You can also change number_of_streams if you want to increase/decrease the number of replicated streams.
docker run \
-it \
--rm \
--net=host \
--runtime nvidia \
-e DISPLAY=$DISPLAY \
-v /tmp/.X11-unix/:/tmp/.X11-unix \
--cap-add=SYS_PTRACE \
--security-opt seccomp=unconfined \
--device /dev/video0 \
--privileged \
--expose 8554 \
deepstream-custom-slow
# Kill
docker kill $(docker ps -a -q --filter ancestor=deepstream-custom-slow)
################################################################################
# Copyright (c) 2019-2021, 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.
################################################################################
# Following properties are mandatory when engine files are not specified:
# int8-calib-file(Only in INT8), model-file-format
# Caffemodel mandatory properties: model-file, proto-file, output-blob-names
# UFF: uff-file, input-dims, uff-input-blob-name, output-blob-names
# ONNX: onnx-file
#
# Mandatory properties for detectors:
# num-detected-classes
#
# Optional properties for detectors:
# cluster-mode(Default=Group Rectangles), interval(Primary mode only, Default=0)
# custom-lib-path
# parse-bbox-func-name
#
# Mandatory properties for classifiers:
# classifier-threshold, is-classifier
#
# Optional properties for classifiers:
# classifier-async-mode(Secondary mode only, Default=false)
#
# Optional properties in secondary mode:
# operate-on-gie-id(Default=0), operate-on-class-ids(Defaults to all classes),
# input-object-min-width, input-object-min-height, input-object-max-width,
# input-object-max-height
#
# Following properties are always recommended:
# batch-size(Default=1)
#
# Other optional properties:
# net-scale-factor(Default=1), network-mode(Default=0 i.e FP32),
# model-color-format(Default=0 i.e. RGB) model-engine-file, labelfile-path,
# mean-file, gie-unique-id(Default=0), offsets, process-mode (Default=1 i.e. primary),
# custom-lib-path, network-mode(Default=0 i.e FP32)
#
# The values in the config file are overridden by values set through GObject
# properties.
[property]
gpu-id=0
net-scale-factor=0.0039215697906911373
#batch-size=54
#0=RGB, 1=BGR
model-color-format=0
custom-network-config=/src/models/yoloV3/yolov3.cfg
model-file=/src/models/yoloV3/yolov3.weights
#model-engine-file=yolov3_b1_gpu0_int8.engine
labelfile-path=/src/labels.txt
int8-calib-file=/src/models/yoloV3/yolov3-calibration.table.trt7.0
## 0=FP32, 1=INT8, 2=FP16 mode
network-mode=1
num-detected-classes=80
gie-unique-id=1
network-type=0
is-classifier=0
## 1=DBSCAN, 2=NMS, 3= DBSCAN+NMS Hybrid, 4 = None(No clustering)
cluster-mode=2
maintain-aspect-ratio=1
parse-bbox-func-name=NvDsInferParseCustomYoloV3
custom-lib-path=/src/models/yoloV3/nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so
engine-create-func-name=NvDsInferYoloCudaEngineGet
#scaling-filter=0
#scaling-compute-hw=0
[class-attrs-all]
nms-iou-threshold=0.3
threshold=0.7
FROM nvcr.io/nvidia/deepstream:6.0-triton
ENV GIT_SSL_NO_VERIFY=1
RUN sh docker_python_setup.sh
RUN update-alternatives --set python3 /usr/bin/python3.8
RUN apt install --fix-broken -y
RUN apt -y install python3-gi python3-gst-1.0 python-gi-dev git python3 python3-pip cmake g++ build-essential \
libglib2.0-dev python3-dev python3.8-dev libglib2.0-dev-bin python-gi-dev libtool m4 autoconf automake
RUN cd /opt/nvidia/deepstream/deepstream-6.0/sources/apps && \
git clone https://github.com/NVIDIA-AI-IOT/deepstream_python_apps.git
RUN cd /opt/nvidia/deepstream/deepstream-6.0/sources/apps/deepstream_python_apps && \
git submodule update --init
RUN cd /opt/nvidia/deepstream/deepstream-6.0/sources/apps/deepstream_python_apps/3rdparty/gst-python/ && \
./autogen.sh && \
make && \
make install
RUN pip3 install --upgrade pip
RUN cd /opt/nvidia/deepstream/deepstream-6.0/sources/apps/deepstream_python_apps/bindings && \
mkdir build && \
cd build && \
cmake -DPYTHON_MAJOR_VERSION=3 -DPYTHON_MINOR_VERSION=8 -DPIP_PLATFORM=linux_x86_64 -DDS_PATH=/opt/nvidia/deepstream/deepstream-6.0 .. && \
make && \
pip3 install pyds-1.1.0-py3-none-linux_x86_64.whl
RUN cd /opt/nvidia/deepstream/deepstream-6.0/sources/apps/deepstream_python_apps && \
mv apps/* ./
RUN pip3 install --upgrade pip
RUN pip3 install numpy opencv-python
# RTSP
RUN apt update && \
apt install -y python3-gi python3-dev python3-gst-1.0
RUN apt update && \
apt install -y libgstrtspserver-1.0-0 gstreamer1.0-rtsp && \
apt install -y libgirepository1.0-dev && \
apt-get install -y gobject-introspection gir1.2-gst-rtsp-server-1.0
# DEVELOPMENT TOOLS
RUN apt install -y ipython3 graphviz
# Yolo V3
ADD ./setup.sh /src/setup.sh
RUN sh /src/setup.sh
## Copying Project.
ADD ./ /src
WORKDIR /src
## Disable previous entrypoint.
ENTRYPOINT []
## Run Project.
CMD ["python3", "pipeline.py"]
person
bicycle
car
motorbike
aeroplane
bus
train
truck
boat
traffic light
fire hydrant
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
backpack
umbrella
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
sofa
pottedplant
bed
diningtable
toilet
tvmonitor
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
book
clock
vase
scissors
teddy bear
hair drier
import sys
sys.path.append("../")
import pyds
import gi
gi.require_version("Gst", "1.0")
gi.require_version("GstRtspServer", "1.0")
from gi.repository import GObject, Gst, GstRtspServer
from collections import defaultdict
import multiprocessing
import threading
import time
# Counter to count FPS
class Counter:
lock = threading.Lock()
count = defaultdict(int)
started = multiprocessing.Value("d", time.time())
@classmethod
def print_fps_and_reset(cls):
with cls.lock:
# Compute fps
tot_fps = round(sum(cls.count.values()) / (time.time() - cls.started.value), 2)
avg_fps = round(tot_fps / 54) if cls.count else 0
streams_up = len(cls.count)
# Reset
cls.count = defaultdict(int)
cls.started.value = time.time()
print(f"TOT FPS {tot_fps} AVG FPS {avg_fps} STREAMS UP {streams_up}")
# Start monitoring process to print FPS
def monitor(Counter):
while True:
Counter.print_fps_and_reset()
time.sleep(5)
monitoring_thread = threading.Thread(target=monitor, daemon=True, args=(Counter,))
monitoring_thread.start()
# Bus call copied from official examples
def bus_call(bus, message, loop):
t = message.type
if t == Gst.MessageType.EOS:
sys.stdout.write("End-of-stream\n")
loop.quit()
elif t==Gst.MessageType.WARNING:
err, debug = message.parse_warning()
sys.stderr.write("Warning: %s: %s\n" % (err, debug))
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
sys.stderr.write("Error: %s: %s\n" % (err, debug))
loop.quit()
return True
# Manager for the Deepstream pipeline
class PipelineManager:
def __init__(self):
# Load cameras configuration
url = "" # INSERT HERE YOUR RTSP VIDEO STREAM URL
number_of_streams = 54 # CHANGE THIS TO TRY WITH A DIFFERENT NUMBER OF STREAMS
self.fps_streams = dict()
for index in range(number_of_streams):
self.fps_streams["stream{0}".format(index)] = url
number_sources = len(self.fps_streams)
print(f"Number of streams {number_sources}")
# Standard GStreamer initialization
GObject.threads_init()
Gst.init(None)
# Create Pipeline element that will form a connection of other elements
print("Creating Pipeline \n")
pipeline = Gst.Pipeline()
if not pipeline:
sys.stderr.write(" Unable to create Pipeline \n")
# Create nvstreammux instance to form batches from one or more sources.
print("Creating nvstreammux \n")
streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
if not streammux:
sys.stderr.write(" Unable to create NvStreamMux \n")
pipeline.add(streammux)
for index in range(number_of_streams):
# sourcebin (source pad) -> (sinkpad) streammux ->
# Source bin
print("Creating source_bin ", index, " \n ")
source_bin = self.create_source_bin(index, url)
if not source_bin:
sys.stderr.write("Unable to create source bin \n")
pipeline.add(source_bin)
# Sink pad
padname = "sink_%u" % index
sinkpad = streammux.get_request_pad(padname)
if not sinkpad:
sys.stderr.write("Unable to create sink pad bin \n")
# Source pad
srcpad = source_bin.get_static_pad("src")
if not srcpad:
sys.stderr.write("Unable to create src pad bin \n")
srcpad.link(sinkpad)
# Primary GPU Inference Engine (pgie)
print("Creating Pgie \n ")
pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
if not pgie:
sys.stderr.write(" Unable to create pgie \n")
# Fakesink
sink = Gst.ElementFactory.make("fakesink", "fakesink")
batch_size = number_sources
streammux.set_property("width", 1920)
streammux.set_property("height", 1080)
streammux.set_property("batch-size", batch_size)
streammux.set_property("batched-push-timeout", 4000000)
streammux.set_property("live-source", 1)
pgie.set_property("config-file-path", "/src/config_infer_primary_yoloV3.txt")
pgie.set_property("batch-size", batch_size)
pipeline.add(pgie)
pipeline.add(sink)
streammux.link(pgie)
pgie.link(sink)
# create an event loop and feed gstreamer bus messages to it
loop = GObject.MainLoop()
bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect("message", bus_call, loop)
# Probe to get metadata
tiler_src_pad = pgie.get_static_pad("src")
if not tiler_src_pad:
sys.stderr.write(" Unable to get src pad \n")
else:
tiler_src_pad.add_probe(Gst.PadProbeType.BUFFER, self.tiler_src_pad_buffer_probe, 0)
# Start play back and listen to events
print("Starting pipeline \n")
pipeline.set_state(Gst.State.PLAYING)
try:
loop.run()
except BaseException:
pass
except Exception as e:
print("Exception", str(e))
# cleanup
pipeline.set_state(Gst.State.NULL)
def tiler_src_pad_buffer_probe(self, pad, info, u_data):
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:
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
# Update FPS counter
with Counter.lock:
Counter.count[frame_meta.pad_index] += 1
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)
except StopIteration:
break
# do what you want with obj_meta
try:
l_obj = l_obj.next
except StopIteration:
break
try:
l_frame = l_frame.next
except StopIteration:
break
return Gst.PadProbeReturn.OK
def cb_newpad(self, decodebin, decoder_src_pad, data):
print("In cb_newpad\n")
caps = decoder_src_pad.get_current_caps()
gststruct = caps.get_structure(0)
gstname = gststruct.get_name()
source_bin = data
features = caps.get_features(0)
# Need to check if the pad created by the decodebin is for video and not
# audio.
print("gstname=", gstname)
if gstname.find("video") != -1:
# Link the decodebin pad only if decodebin has picked nvidia
# decoder plugin nvdec_*. We do this by checking if the pad caps contain
# NVMM memory features.
print("features=", features)
if features.contains("memory:NVMM"):
# Get the source bin ghost pad
bin_ghost_pad = source_bin.get_static_pad("src")
if not bin_ghost_pad.set_target(decoder_src_pad):
sys.stderr.write(
"Failed to link decoder src pad to source bin ghost pad\n"
)
else:
sys.stderr.write(
" Error: Decodebin did not pick nvidia decoder plugin.\n")
def decodebin_child_added(self, child_proxy, Object, name, user_data):
print("Decodebin child added:", name, "\n")
if name.find("decodebin") != -1:
Object.connect("child-added", self.decodebin_child_added, user_data)
def create_source_bin(self, id, uri):
print("Creating source bin")
# Create a source GstBin to abstract this bin's content from the rest of the
# pipeline
bin_name = "source-bin-%02d" % id
print(bin_name)
nbin = Gst.Bin.new(bin_name)
if not nbin:
sys.stderr.write(" Unable to create source bin \n")
# Source element for reading from the uri.
# We will use decodebin and let it figure out the container format of the
# stream and the codec and plug the appropriate demux and decode plugins.
uri_decode_bin = Gst.ElementFactory.make("uridecodebin", "uri-decode-bin")
if not uri_decode_bin:
sys.stderr.write(" Unable to create uri decode bin \n")
# We set the input uri to the source element
uri_decode_bin.set_property("uri", uri)
# Connect to the "pad-added" signal of the decodebin which generates a
# callback once a new pad for raw data has been created by the decodebin
uri_decode_bin.connect("pad-added", self.cb_newpad, nbin)
uri_decode_bin.connect("child-added", self.decodebin_child_added, nbin)
# We need to create a ghost pad for the source bin which will act as a proxy
# for the video decoder src pad. The ghost pad will not have a target right
# now. Once the decode bin creates the video decoder and generates the
# cb_newpad callback, we will set the ghost pad target to the video decoder
# src pad.
Gst.Bin.add(nbin, uri_decode_bin)
bin_pad = nbin.add_pad(
Gst.GhostPad.new_no_target(
"src", Gst.PadDirection.SRC))
if not bin_pad:
sys.stderr.write(" Failed to add ghost pad in source bin \n")
return None
return nbin
if __name__ == "__main__":
PipelineManager()
mkdir -p /src/models/yoloV3
cd /src/models/yoloV3
# Download data
wget https://raw.githubusercontent.com/pjreddie/darknet/master/cfg/yolov3.cfg
wget https://pjreddie.com/media/files/yolov3.weights
# Copy calibration from sample
cp /opt/nvidia/deepstream/deepstream-6.0/sources/objectDetector_Yolo/yolov3-calibration.table.trt7.0 /src/models/yoloV3
# Build plugin
cd /opt/nvidia/deepstream/deepstream-6.0/sources/objectDetector_Yolo/
# wget https://forums.developer.nvidia.com/uploads/short-url/oezjVVUIuYdfJ8BdTJWNmIBVawl.patch -O DS6.0GA_objectDetector_Yolo_perf_regression.patch
#patch -t -p1 < DS6.0GA_objectDetector_Yolo_perf_regression.patch
CUDA_VER=11.4 make -C nvdsinfer_custom_impl_Yolo
mkdir /src/models/yoloV3/nvdsinfer_custom_impl_Yolo
cp /opt/nvidia/deepstream/deepstream-6.0/sources/objectDetector_Yolo/nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so /src/models/yoloV3/nvdsinfer_custom_impl_Yolo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment