├── .gitignore ├── README.md ├── common ├── FPS.py ├── bus_call.py └── is_aarch_64.py ├── configs └── config_infer_triton_yolov7.txt ├── demo.py ├── nvdsinfer_custom_impl_Yolo ├── Makefile ├── README ├── libnvds_infercustomparser.so ├── nvdsinfer_custombboxparser.cpp └── nvdsinfer_customclassifierparser.cpp ├── run_docker.sh ├── triton_yolov7 └── yolov7 │ ├── classes.txt │ └── config.pbtxt └── weights └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | weights/yolov7-tiny.onnx 2 | triton_yolov7/yolov7/1/* 3 | __pycache__ 4 | timing.cache 5 | timing.cache.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yolov7-triton-deepstream 2 | 3 | ## Perpare weight model and run docker 4 | Perpare weight: 5 | ```bash 6 | See README into folder weights to generate onnx 7 | or download onnx converted from link google drive 8 | ``` 9 | Pepare docker: 10 | ```bash 11 | #Setup docker 12 | docker pull thanhlnbka/deepstream-python-app:3.0-triton 13 | #Run docker to inference yolov7 with triton deepstream 14 | bash run_docker.sh 15 | ``` 16 | ##### NOTE: NEXT STEPS WORK INTO DOCKER 17 | ## Using deepstream-triton to convert engine 18 | Install package TensorRT: 19 | ```bash 20 | #install package tensorrt 8.* 21 | apt-get install libnvinfer8 libnvinfer-plugin8 libnvparsers8 libnvonnxparsers8 libnvinfer-bin libnvinfer-dev libnvinfer-plugin-dev libnvparsers-dev libnvonnxparsers-dev libnvinfer-samples libcudnn8-dev libcudnn8 22 | apt-mark hold libnvinfer* libnvparsers* libnvonnxparsers* libcudnn8* tensorrt 23 | ``` 24 | 25 | Export Engine: 26 | ```bash 27 | #access folder weights 28 | cd /opt/nvidia/deepstream/deepstream-6.1/sources/yolov7-triton-deepstream/weights 29 | #export file engine with trtexec 30 | /usr/src/tensorrt/bin/trtexec --onnx=yolov7-tiny.onnx --minShapes=images:1x3x640x640 --optShapes=images:8x3x640x640 --maxShapes=images:8x3x640x640 --fp16 --workspace=4096 --saveEngine=yolov7-fp16-1x8x8.engine --timingCacheFile=timing.cache 31 | #move file engine to deploy triton 32 | mv yolov7-fp16-1x8x8.engine ../triton_yolov7/yolov7/1/model.plan 33 | ``` 34 | ## Custom config parse box 35 | Run cmd: 36 | ```bash 37 | #access folder custom parse box yolo 38 | cd /opt/nvidia/deepstream/deepstream-6.1/sources/yolov7-triton-deepstream/nvdsinfer_custom_impl_Yolo 39 | #generate file .so 40 | CUDA_VER=11.7 make install 41 | ``` 42 | ## Demo 43 | Run demo: 44 | ```bash 45 | #access folder demo 46 | cd /opt/nvidia/deepstream/deepstream-6.1/sources/yolov7-triton-deepstream 47 | #run demo with fake rtsp 48 | python3 demo.py -i rtsp://localhost:128/gst -g nvinferserver -c configs/config_infer_triton_yolov7.txt 49 | ``` 50 | ![Screenshot from 2022-10-03 14-31-10](https://user-images.githubusercontent.com/56015771/193529486-2609b621-12d8-4390-8092-a42f76bd3cd5.png) 51 | 52 | 53 | ## Acknowledgements 54 | 55 |
Expand 56 | 57 | * [https://github.com/WongKinYiu/yolov7](https://github.com/WongKinYiu/yolov7) 58 | * [https://github.com/NVIDIA-AI-IOT/deepstream_python_apps](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps) 59 | 60 |
61 | -------------------------------------------------------------------------------- /common/FPS.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | from threading import Lock 4 | start_time=time.time() 5 | 6 | fps_mutex = Lock() 7 | 8 | class GETFPS: 9 | def __init__(self,stream_id): 10 | global start_time 11 | self.start_time=start_time 12 | self.is_first=True 13 | self.frame_count=0 14 | self.stream_id=stream_id 15 | 16 | def update_fps(self): 17 | end_time = time.time() 18 | if self.is_first: 19 | self.start_time = end_time 20 | self.is_first = False 21 | else: 22 | global fps_mutex 23 | with fps_mutex: 24 | self.frame_count = self.frame_count + 1 25 | 26 | def get_fps(self): 27 | end_time = time.time() 28 | with fps_mutex: 29 | stream_fps = float(self.frame_count/(end_time - self.start_time)) 30 | self.frame_count = 0 31 | self.start_time = end_time 32 | return round(stream_fps, 2) 33 | 34 | def print_data(self): 35 | print('frame_count=',self.frame_count) 36 | print('start_time=',self.start_time) 37 | 38 | class PERF_DATA: 39 | def __init__(self, num_streams=1): 40 | self.perf_dict = {} 41 | self.all_stream_fps = {} 42 | for i in range(num_streams): 43 | self.all_stream_fps["stream{0}".format(i)]=GETFPS(i) 44 | 45 | def perf_print_callback(self): 46 | self.perf_dict = {stream_index:stream.get_fps() for (stream_index, stream) in self.all_stream_fps.items()} 47 | print ("\n**PERF: ", self.perf_dict, "\n") 48 | return True 49 | 50 | def update_fps(self, stream_index): 51 | self.all_stream_fps[stream_index].update_fps() -------------------------------------------------------------------------------- /common/bus_call.py: -------------------------------------------------------------------------------- 1 | 2 | import gi 3 | import sys 4 | gi.require_version('Gst', '1.0') 5 | from gi.repository import Gst 6 | 7 | def bus_call(bus, message, loop): 8 | t = message.type 9 | if t == Gst.MessageType.EOS: 10 | sys.stdout.write("End-of-stream\n") 11 | loop.quit() 12 | elif t==Gst.MessageType.WARNING: 13 | err, debug = message.parse_warning() 14 | sys.stderr.write("Warning: %s: %s\n" % (err, debug)) 15 | elif t == Gst.MessageType.ERROR: 16 | err, debug = message.parse_error() 17 | sys.stderr.write("Error: %s: %s\n" % (err, debug)) 18 | loop.quit() 19 | elif t == Gst.MessageType.ELEMENT: 20 | struct = message.get_structure() 21 | #Check for stream-eos message 22 | if struct is not None and struct.has_name("stream-eos"): 23 | parsed, stream_id = struct.get_uint("stream-id") 24 | if parsed: 25 | #Set eos status of stream to True, to be deleted in delete-sources 26 | print("Got EOS from stream %d" % stream_id) 27 | return True 28 | -------------------------------------------------------------------------------- /common/is_aarch_64.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import sys 3 | 4 | 5 | def is_aarch64(): 6 | return platform.uname()[4] == 'aarch64' 7 | 8 | sys.path.append('/opt/nvidia/deepstream-6.1/deepstream/lib') -------------------------------------------------------------------------------- /configs/config_infer_triton_yolov7.txt: -------------------------------------------------------------------------------- 1 | infer_config { 2 | unique_id: 1 3 | gpu_ids: [0] 4 | max_batch_size: 20 5 | 6 | backend { 7 | triton { 8 | model_name: "yolov7" 9 | version: -1 10 | model_repo { 11 | root: "/opt/nvidia/deepstream/deepstream-6.1/sources/yolov7-triton-deepstream/triton_yolov7" 12 | } 13 | } 14 | } 15 | 16 | preprocess { 17 | network_format: IMAGE_FORMAT_RGB 18 | tensor_order: TENSOR_ORDER_LINEAR 19 | maintain_aspect_ratio: 1 20 | frame_scaling_hw: FRAME_SCALING_HW_DEFAULT 21 | frame_scaling_filter: 1 22 | normalize { 23 | scale_factor: 0.0039215697906911373 24 | } 25 | } 26 | 27 | postprocess { 28 | labelfile_path: "/opt/nvidia/deepstream/deepstream-6.1/sources/yolov7-triton-deepstream/triton_yolov7/yolov7/classes.txt" 29 | detection { 30 | num_detected_classes: 80 31 | custom_parse_bbox_func: "NvDsInferParseCustomEfficientNMS" 32 | nms { 33 | confidence_threshold: 0.5 34 | topk: 300 35 | iou_threshold: 0.45 36 | } 37 | } 38 | } 39 | 40 | extra { 41 | copy_input_to_host_buffers: false 42 | } 43 | custom_lib { 44 | path : "/opt/nvidia/deepstream/deepstream-6.1/lib/libnvds_infercustomparser.so" 45 | } 46 | } 47 | 48 | input_control { 49 | process_mode : PROCESS_MODE_FULL_FRAME 50 | interval : 0 51 | } -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ################################################################################ 4 | # SPDX-FileCopyrightText: Copyright (c) 2019-2022 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); 8 | # you may not use this file except in compliance with the License. 9 | # You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, 15 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | ################################################################################ 19 | 20 | import sys 21 | sys.path.append('../') 22 | from pathlib import Path 23 | import gi 24 | import configparser 25 | import argparse 26 | gi.require_version('Gst', '1.0') 27 | from gi.repository import GLib, Gst 28 | from ctypes import * 29 | import time 30 | import sys 31 | import math 32 | import platform 33 | from common.is_aarch_64 import is_aarch64 34 | from common.bus_call import bus_call 35 | from common.FPS import PERF_DATA 36 | 37 | import pyds 38 | 39 | no_display = False 40 | silent = False 41 | file_loop = False 42 | perf_data = None 43 | 44 | MAX_DISPLAY_LEN=64 45 | MUXER_OUTPUT_WIDTH=1920 46 | MUXER_OUTPUT_HEIGHT=1080 47 | MUXER_BATCH_TIMEOUT_USEC=4000000 48 | TILED_OUTPUT_WIDTH=1280 49 | TILED_OUTPUT_HEIGHT=720 50 | GST_CAPS_FEATURES_NVMM="memory:NVMM" 51 | OSD_PROCESS_MODE= 0 52 | OSD_DISPLAY_TEXT= 1 53 | 54 | 55 | 56 | def cb_newpad(decodebin, decoder_src_pad,data): 57 | print("In cb_newpad\n") 58 | caps=decoder_src_pad.get_current_caps() 59 | if not caps: 60 | caps = decoder_src_pad.query_caps() 61 | gststruct=caps.get_structure(0) 62 | gstname=gststruct.get_name() 63 | source_bin=data 64 | features=caps.get_features(0) 65 | 66 | # Need to check if the pad created by the decodebin is for video and not 67 | # audio. 68 | print("gstname=",gstname) 69 | if(gstname.find("video")!=-1): 70 | # Link the decodebin pad only if decodebin has picked nvidia 71 | # decoder plugin nvdec_*. We do this by checking if the pad caps contain 72 | # NVMM memory features. 73 | print("features=",features) 74 | if features.contains("memory:NVMM"): 75 | # Get the source bin ghost pad 76 | bin_ghost_pad=source_bin.get_static_pad("src") 77 | if not bin_ghost_pad.set_target(decoder_src_pad): 78 | sys.stderr.write("Failed to link decoder src pad to source bin ghost pad\n") 79 | else: 80 | sys.stderr.write(" Error: Decodebin did not pick nvidia decoder plugin.\n") 81 | 82 | def decodebin_child_added(child_proxy,Object,name,user_data): 83 | print("Decodebin child added:", name, "\n") 84 | if(name.find("decodebin") != -1): 85 | Object.connect("child-added",decodebin_child_added,user_data) 86 | 87 | if "source" in name: 88 | source_element = child_proxy.get_by_name("source") 89 | if source_element.find_property('drop-on-latency') != None: 90 | Object.set_property("drop-on-latency", True) 91 | 92 | 93 | 94 | def create_source_bin(index,uri): 95 | print("Creating source bin") 96 | 97 | # Create a source GstBin to abstract this bin's content from the rest of the 98 | # pipeline 99 | bin_name="source-bin-%02d" %index 100 | print(bin_name) 101 | nbin=Gst.Bin.new(bin_name) 102 | if not nbin: 103 | sys.stderr.write(" Unable to create source bin \n") 104 | 105 | # Source element for reading from the uri. 106 | # We will use decodebin and let it figure out the container format of the 107 | # stream and the codec and plug the appropriate demux and decode plugins. 108 | if file_loop: 109 | # use nvurisrcbin to enable file-loop 110 | uri_decode_bin=Gst.ElementFactory.make("nvurisrcbin", "uri-decode-bin") 111 | uri_decode_bin.set_property("file-loop", 1) 112 | else: 113 | uri_decode_bin=Gst.ElementFactory.make("uridecodebin", "uri-decode-bin") 114 | if not uri_decode_bin: 115 | sys.stderr.write(" Unable to create uri decode bin \n") 116 | # We set the input uri to the source element 117 | uri_decode_bin.set_property("uri",uri) 118 | # Connect to the "pad-added" signal of the decodebin which generates a 119 | # callback once a new pad for raw data has beed created by the decodebin 120 | uri_decode_bin.connect("pad-added",cb_newpad,nbin) 121 | uri_decode_bin.connect("child-added",decodebin_child_added,nbin) 122 | 123 | # We need to create a ghost pad for the source bin which will act as a proxy 124 | # for the video decoder src pad. The ghost pad will not have a target right 125 | # now. Once the decode bin creates the video decoder and generates the 126 | # cb_newpad callback, we will set the ghost pad target to the video decoder 127 | # src pad. 128 | Gst.Bin.add(nbin,uri_decode_bin) 129 | bin_pad=nbin.add_pad(Gst.GhostPad.new_no_target("src",Gst.PadDirection.SRC)) 130 | if not bin_pad: 131 | sys.stderr.write(" Failed to add ghost pad in source bin \n") 132 | return None 133 | return nbin 134 | 135 | def main(args, requested_pgie=None, config=None, disable_probe=False): 136 | global perf_data 137 | perf_data = PERF_DATA(len(args)) 138 | 139 | number_sources=len(args) 140 | 141 | # Standard GStreamer initialization 142 | Gst.init(None) 143 | 144 | # Create gstreamer elements */ 145 | # Create Pipeline element that will form a connection of other elements 146 | print("Creating Pipeline \n ") 147 | pipeline = Gst.Pipeline() 148 | is_live = False 149 | 150 | if not pipeline: 151 | sys.stderr.write(" Unable to create Pipeline \n") 152 | print("Creating streamux \n ") 153 | 154 | # Create nvstreammux instance to form batches from one or more sources. 155 | streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer") 156 | if not streammux: 157 | sys.stderr.write(" Unable to create NvStreamMux \n") 158 | 159 | pipeline.add(streammux) 160 | for i in range(number_sources): 161 | print("Creating source_bin ",i," \n ") 162 | uri_name=args[i] 163 | if uri_name.find("rtsp://") == 0 : 164 | is_live = True 165 | source_bin=create_source_bin(i, uri_name) 166 | if not source_bin: 167 | sys.stderr.write("Unable to create source bin \n") 168 | pipeline.add(source_bin) 169 | padname="sink_%u" %i 170 | sinkpad= streammux.get_request_pad(padname) 171 | if not sinkpad: 172 | sys.stderr.write("Unable to create sink pad bin \n") 173 | srcpad=source_bin.get_static_pad("src") 174 | if not srcpad: 175 | sys.stderr.write("Unable to create src pad bin \n") 176 | srcpad.link(sinkpad) 177 | queue1=Gst.ElementFactory.make("queue","queue1") 178 | queue2=Gst.ElementFactory.make("queue","queue2") 179 | queue3=Gst.ElementFactory.make("queue","queue3") 180 | queue4=Gst.ElementFactory.make("queue","queue4") 181 | queue5=Gst.ElementFactory.make("queue","queue5") 182 | pipeline.add(queue1) 183 | pipeline.add(queue2) 184 | pipeline.add(queue3) 185 | pipeline.add(queue4) 186 | pipeline.add(queue5) 187 | 188 | nvdslogger = None 189 | transform = None 190 | 191 | print("Creating Pgie \n ") 192 | if requested_pgie != None and (requested_pgie == 'nvinferserver' or requested_pgie == 'nvinferserver-grpc') : 193 | pgie = Gst.ElementFactory.make("nvinferserver", "primary-inference") 194 | elif requested_pgie != None and requested_pgie == 'nvinfer': 195 | pgie = Gst.ElementFactory.make("nvinfer", "primary-inference") 196 | else: 197 | pgie = Gst.ElementFactory.make("nvinfer", "primary-inference") 198 | 199 | if not pgie: 200 | sys.stderr.write(" Unable to create pgie : %s\n" % requested_pgie) 201 | 202 | if disable_probe: 203 | # Use nvdslogger for perf measurement instead of probe function 204 | print ("Creating nvdslogger \n") 205 | nvdslogger = Gst.ElementFactory.make("nvdslogger", "nvdslogger") 206 | 207 | print("Creating tiler \n ") 208 | tiler=Gst.ElementFactory.make("nvmultistreamtiler", "nvtiler") 209 | if not tiler: 210 | sys.stderr.write(" Unable to create tiler \n") 211 | print("Creating nvvidconv \n ") 212 | nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "convertor") 213 | if not nvvidconv: 214 | sys.stderr.write(" Unable to create nvvidconv \n") 215 | print("Creating nvosd \n ") 216 | nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay") 217 | if not nvosd: 218 | sys.stderr.write(" Unable to create nvosd \n") 219 | nvosd.set_property('process-mode',OSD_PROCESS_MODE) 220 | nvosd.set_property('display-text',OSD_DISPLAY_TEXT) 221 | 222 | 223 | if no_display: 224 | print("Creating Fakesink \n") 225 | sink = Gst.ElementFactory.make("fakesink", "fakesink") 226 | sink.set_property('enable-last-sample', 0) 227 | sink.set_property('sync', 0) 228 | else: 229 | if(is_aarch64()): 230 | print("Creating transform \n ") 231 | transform=Gst.ElementFactory.make("nvegltransform", "nvegl-transform") 232 | if not transform: 233 | sys.stderr.write(" Unable to create transform \n") 234 | print("Creating EGLSink \n") 235 | sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer") 236 | 237 | if not sink: 238 | sys.stderr.write(" Unable to create sink element \n") 239 | 240 | if is_live: 241 | print("At least one of the sources is live") 242 | streammux.set_property('live-source', 1) 243 | 244 | streammux.set_property('width', 1920) 245 | streammux.set_property('height', 1080) 246 | streammux.set_property('batch-size', number_sources) 247 | streammux.set_property('batched-push-timeout', 40) 248 | if requested_pgie == "nvinferserver" and config != None: 249 | pgie.set_property('config-file-path', config) 250 | elif requested_pgie == "nvinferserver-grpc" and config != None: 251 | pgie.set_property('config-file-path', config) 252 | elif requested_pgie == "nvinfer" and config != None: 253 | pgie.set_property('config-file-path', config) 254 | else: 255 | pgie.set_property('config-file-path', "dstest3_pgie_config.txt") 256 | pgie_batch_size=pgie.get_property("batch-size") 257 | if(pgie_batch_size != number_sources): 258 | print("WARNING: Overriding infer-config batch-size",pgie_batch_size," with number of sources ", number_sources," \n") 259 | pgie.set_property("batch-size",number_sources) 260 | tiler_rows=int(math.sqrt(number_sources)) 261 | tiler_columns=int(math.ceil((1.0*number_sources)/tiler_rows)) 262 | tiler.set_property("rows",tiler_rows) 263 | tiler.set_property("columns",tiler_columns) 264 | tiler.set_property("width", TILED_OUTPUT_WIDTH) 265 | tiler.set_property("height", TILED_OUTPUT_HEIGHT) 266 | sink.set_property("qos",0) 267 | sink.set_property('sync', 0) 268 | print("Adding elements to Pipeline \n") 269 | pipeline.add(pgie) 270 | if nvdslogger: 271 | pipeline.add(nvdslogger) 272 | pipeline.add(tiler) 273 | pipeline.add(nvvidconv) 274 | pipeline.add(nvosd) 275 | if transform: 276 | pipeline.add(transform) 277 | pipeline.add(sink) 278 | 279 | print("Linking elements in the Pipeline \n") 280 | streammux.link(queue1) 281 | queue1.link(pgie) 282 | pgie.link(queue2) 283 | if nvdslogger: 284 | queue2.link(nvdslogger) 285 | nvdslogger.link(tiler) 286 | else: 287 | queue2.link(tiler) 288 | tiler.link(queue3) 289 | queue3.link(nvvidconv) 290 | nvvidconv.link(queue4) 291 | queue4.link(nvosd) 292 | if transform: 293 | nvosd.link(queue5) 294 | queue5.link(transform) 295 | transform.link(sink) 296 | else: 297 | nvosd.link(queue5) 298 | queue5.link(sink) 299 | 300 | # create an event loop and feed gstreamer bus mesages to it 301 | loop = GLib.MainLoop() 302 | bus = pipeline.get_bus() 303 | bus.add_signal_watch() 304 | bus.connect ("message", bus_call, loop) 305 | pgie_src_pad=pgie.get_static_pad("src") 306 | 307 | # List the sources 308 | print("Now playing...") 309 | for i, source in enumerate(args): 310 | print(i, ": ", source) 311 | 312 | print("Starting pipeline \n") 313 | # start play back and listed to events 314 | pipeline.set_state(Gst.State.PLAYING) 315 | try: 316 | loop.run() 317 | except: 318 | pass 319 | # cleanup 320 | print("Exiting app\n") 321 | pipeline.set_state(Gst.State.NULL) 322 | 323 | def parse_args(): 324 | 325 | parser = argparse.ArgumentParser(prog="deepstream_test_3", 326 | description="deepstream-test3 multi stream, multi model inference reference app") 327 | parser.add_argument( 328 | "-i", 329 | "--input", 330 | help="Path to input streams", 331 | nargs="+", 332 | metavar="URIs", 333 | default=["a"], 334 | required=True, 335 | ) 336 | parser.add_argument( 337 | "-c", 338 | "--configfile", 339 | metavar="config_location.txt", 340 | default=None, 341 | help="Choose the config-file to be used with specified pgie", 342 | ) 343 | parser.add_argument( 344 | "-g", 345 | "--pgie", 346 | default=None, 347 | help="Choose Primary GPU Inference Engine", 348 | choices=["nvinfer", "nvinferserver", "nvinferserver-grpc"], 349 | ) 350 | parser.add_argument( 351 | "--no-display", 352 | action="store_true", 353 | default=False, 354 | dest='no_display', 355 | help="Disable display of video output", 356 | ) 357 | parser.add_argument( 358 | "--file-loop", 359 | action="store_true", 360 | default=False, 361 | dest='file_loop', 362 | help="Loop the input file sources after EOS", 363 | ) 364 | parser.add_argument( 365 | "--disable-probe", 366 | action="store_true", 367 | default=False, 368 | dest='disable_probe', 369 | help="Disable the probe function and use nvdslogger for FPS", 370 | ) 371 | parser.add_argument( 372 | "-s", 373 | "--silent", 374 | action="store_true", 375 | default=False, 376 | dest='silent', 377 | help="Disable verbose output", 378 | ) 379 | # Check input arguments 380 | if len(sys.argv) == 1: 381 | parser.print_help(sys.stderr) 382 | sys.exit(1) 383 | args = parser.parse_args() 384 | 385 | stream_paths = args.input 386 | pgie = args.pgie 387 | config = args.configfile 388 | disable_probe = args.disable_probe 389 | global no_display 390 | global silent 391 | global file_loop 392 | no_display = args.no_display 393 | silent = args.silent 394 | file_loop = args.file_loop 395 | 396 | if config and not pgie or pgie and not config: 397 | sys.stderr.write ("\nEither pgie or configfile is missing. Please specify both! Exiting...\n\n\n\n") 398 | parser.print_help() 399 | sys.exit(1) 400 | if config: 401 | config_path = Path(config) 402 | if not config_path.is_file(): 403 | sys.stderr.write ("Specified config-file: %s doesn't exist. Exiting...\n\n" % config) 404 | sys.exit(1) 405 | 406 | print(vars(args)) 407 | return stream_paths, pgie, config, disable_probe 408 | 409 | if __name__ == '__main__': 410 | stream_paths, pgie, config, disable_probe = parse_args() 411 | sys.exit(main(stream_paths, pgie, config, disable_probe)) 412 | -------------------------------------------------------------------------------- /nvdsinfer_custom_impl_Yolo/Makefile: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a 5 | # copy of this software and associated documentation files (the "Software"), 6 | # to deal in the Software without restriction, including without limitation 7 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | # and/or sell copies of the Software, and to permit persons to whom the 9 | # Software is furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | ################################################################################ 22 | 23 | CC:= g++ 24 | 25 | CFLAGS:= -Wall -std=c++11 26 | 27 | CFLAGS+= -shared -fPIC 28 | 29 | CFLAGS+= -I../../includes \ 30 | -I /usr/local/cuda-$(CUDA_VER)/include 31 | 32 | LIBS:= -lnvinfer -lnvparsers 33 | LFLAGS:= -Wl,--start-group $(LIBS) -Wl,--end-group 34 | 35 | SRCFILES:= nvdsinfer_custombboxparser.cpp nvdsinfer_customclassifierparser.cpp 36 | TARGET_LIB:= libnvds_infercustomparser.so 37 | 38 | all: $(TARGET_LIB) 39 | 40 | $(TARGET_LIB) : $(SRCFILES) 41 | $(CC) -o $@ $^ $(CFLAGS) $(LFLAGS) 42 | 43 | install: $(TARGET_LIB) 44 | cp $(TARGET_LIB) ../../../lib 45 | 46 | clean: 47 | rm -rf $(TARGET_LIB) 48 | -------------------------------------------------------------------------------- /nvdsinfer_custom_impl_Yolo/README: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Copyright (c) 2018-2021, NVIDIA CORPORATION. All rights reserved. 3 | # 4 | # NVIDIA Corporation and its licensors retain all intellectual property 5 | # and proprietary rights in and to this software, related documentation 6 | # and any modifications thereto. Any use, reproduction, disclosure or 7 | # distribution of this software and related documentation without an express 8 | # license agreement from NVIDIA Corporation is strictly prohibited. 9 | # 10 | ################################################################################ 11 | 12 | Refer to the DeepStream SDK documentation for a description of the library. 13 | 14 | -------------------------------------------------------------------------------- 15 | Compile the library : 16 | 17 | # Export correct CUDA version as per the platform 18 | For Jetson: $ export CUDA_VER=11.4 19 | For x86: $ export CUDA_VER=11.7 20 | 21 | $ sudo -E make install 22 | 23 | -------------------------------------------------------------------------------- 24 | This source has been written to parse the output layers of the resnet10 detector 25 | and the resnet18 vehicle type classifier model provided with the SDK. To use this 26 | library for bounding box / classifier output parsing instead of the inbuilt 27 | parsing function, modify the following parameters in [property] section of 28 | primary/secondary vehicle type infer configuration file (config_infer_primary.txt/ 29 | config_infer_secondary_vehicletypes.txt) provided with the SDK: 30 | 31 | # For resnet10 detector 32 | parse-bbox-func-name=NvDsInferParseCustomResnet 33 | custom-lib-path=/path/to/this/directory/libnvds_infercustomparser.so 34 | 35 | # For resnet18 vehicle type classifier 36 | parse-classifier-func-name=NvDsInferClassiferParseCustomSoftmax 37 | custom-lib-path=/path/to/this/directory/libnvds_infercustomparser.so 38 | 39 | # For Tensorflow/Onnx SSD detector within nvinferserver 40 | infer_config { 41 | postprocess { detection { 42 | custom_parse_bbox_func: "NvDsInferParseCustomTfSSD" 43 | ... 44 | } } 45 | ... 46 | custom_lib { 47 | path: "/path/to/this/directory/libnvds_infercustomparser.so" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /nvdsinfer_custom_impl_Yolo/libnvds_infercustomparser.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thanhlnbka/yolov7-triton-deepstream/3e35c77f0c7c2937746a3dcae7e200b3d043671d/nvdsinfer_custom_impl_Yolo/libnvds_infercustomparser.so -------------------------------------------------------------------------------- /nvdsinfer_custom_impl_Yolo/nvdsinfer_custombboxparser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2020, NVIDIA CORPORATION. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "nvdsinfer_custom_impl.h" 26 | #include 27 | #include 28 | 29 | #define MIN(a,b) ((a) < (b) ? (a) : (b)) 30 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 31 | #define CLIP(a,min,max) (MAX(MIN(a, max), min)) 32 | #define DIVIDE_AND_ROUND_UP(a, b) ((a + b - 1) / b) 33 | 34 | struct MrcnnRawDetection { 35 | float y1, x1, y2, x2, class_id, score; 36 | }; 37 | 38 | /* This is a sample bounding box parsing function for the sample Resnet10 39 | * detector model provided with the SDK. */ 40 | 41 | /* C-linkage to prevent name-mangling */ 42 | extern "C" 43 | bool NvDsInferParseCustomResnet (std::vector const &outputLayersInfo, 44 | NvDsInferNetworkInfo const &networkInfo, 45 | NvDsInferParseDetectionParams const &detectionParams, 46 | std::vector &objectList); 47 | 48 | /* This is a sample bounding box parsing function for the tensorflow SSD models 49 | * detector model provided with the SDK. */ 50 | 51 | /* C-linkage to prevent name-mangling */ 52 | extern "C" 53 | bool NvDsInferParseCustomTfSSD (std::vector const &outputLayersInfo, 54 | NvDsInferNetworkInfo const &networkInfo, 55 | NvDsInferParseDetectionParams const &detectionParams, 56 | std::vector &objectList); 57 | 58 | extern "C" 59 | bool NvDsInferParseCustomNMSTLT ( 60 | std::vector const &outputLayersInfo, 61 | NvDsInferNetworkInfo const &networkInfo, 62 | NvDsInferParseDetectionParams const &detectionParams, 63 | std::vector &objectList); 64 | 65 | extern "C" 66 | bool NvDsInferParseYoloV5CustomBatchedNMSTLT ( 67 | std::vector const &outputLayersInfo, 68 | NvDsInferNetworkInfo const &networkInfo, 69 | NvDsInferParseDetectionParams const &detectionParams, 70 | std::vector &objectList); 71 | 72 | extern "C" 73 | bool NvDsInferParseCustomBatchedNMSTLT ( 74 | std::vector const &outputLayersInfo, 75 | NvDsInferNetworkInfo const &networkInfo, 76 | NvDsInferParseDetectionParams const &detectionParams, 77 | std::vector &objectList); 78 | 79 | extern "C" 80 | bool NvDsInferParseCustomMrcnnTLT (std::vector const &outputLayersInfo, 81 | NvDsInferNetworkInfo const &networkInfo, 82 | NvDsInferParseDetectionParams const &detectionParams, 83 | std::vector &objectList); 84 | 85 | extern "C" 86 | bool NvDsInferParseCustomMrcnnTLTV2 (std::vector const &outputLayersInfo, 87 | NvDsInferNetworkInfo const &networkInfo, 88 | NvDsInferParseDetectionParams const &detectionParams, 89 | std::vector &objectList); 90 | 91 | extern "C" 92 | bool NvDsInferParseCustomEfficientDetTAO ( 93 | std::vector const &outputLayersInfo, 94 | NvDsInferNetworkInfo const &networkInfo, 95 | NvDsInferParseDetectionParams const &detectionParams, 96 | std::vector &objectList); 97 | 98 | extern "C" 99 | bool NvDsInferParseCustomResnet (std::vector const &outputLayersInfo, 100 | NvDsInferNetworkInfo const &networkInfo, 101 | NvDsInferParseDetectionParams const &detectionParams, 102 | std::vector &objectList) 103 | { 104 | static NvDsInferDimsCHW covLayerDims; 105 | static NvDsInferDimsCHW bboxLayerDims; 106 | static int bboxLayerIndex = -1; 107 | static int covLayerIndex = -1; 108 | static bool classMismatchWarn = false; 109 | int numClassesToParse; 110 | 111 | /* Find the bbox layer */ 112 | if (bboxLayerIndex == -1) { 113 | for (unsigned int i = 0; i < outputLayersInfo.size(); i++) { 114 | if (strcmp(outputLayersInfo[i].layerName, "conv2d_bbox") == 0) { 115 | bboxLayerIndex = i; 116 | getDimsCHWFromDims(bboxLayerDims, outputLayersInfo[i].inferDims); 117 | break; 118 | } 119 | } 120 | if (bboxLayerIndex == -1) { 121 | std::cerr << "Could not find bbox layer buffer while parsing" << std::endl; 122 | return false; 123 | } 124 | } 125 | 126 | /* Find the cov layer */ 127 | if (covLayerIndex == -1) { 128 | for (unsigned int i = 0; i < outputLayersInfo.size(); i++) { 129 | if (strcmp(outputLayersInfo[i].layerName, "conv2d_cov/Sigmoid") == 0) { 130 | covLayerIndex = i; 131 | getDimsCHWFromDims(covLayerDims, outputLayersInfo[i].inferDims); 132 | break; 133 | } 134 | } 135 | if (covLayerIndex == -1) { 136 | std::cerr << "Could not find bbox layer buffer while parsing" << std::endl; 137 | return false; 138 | } 139 | } 140 | 141 | /* Warn in case of mismatch in number of classes */ 142 | if (!classMismatchWarn) { 143 | if (covLayerDims.c != detectionParams.numClassesConfigured) { 144 | std::cerr << "WARNING: Num classes mismatch. Configured:" << 145 | detectionParams.numClassesConfigured << ", detected by network: " << 146 | covLayerDims.c << std::endl; 147 | } 148 | classMismatchWarn = true; 149 | } 150 | 151 | /* Calculate the number of classes to parse */ 152 | numClassesToParse = MIN (covLayerDims.c, detectionParams.numClassesConfigured); 153 | 154 | int gridW = covLayerDims.w; 155 | int gridH = covLayerDims.h; 156 | int gridSize = gridW * gridH; 157 | float gcCentersX[gridW]; 158 | float gcCentersY[gridH]; 159 | float bboxNormX = 35.0; 160 | float bboxNormY = 35.0; 161 | float *outputCovBuf = (float *) outputLayersInfo[covLayerIndex].buffer; 162 | float *outputBboxBuf = (float *) outputLayersInfo[bboxLayerIndex].buffer; 163 | int strideX = DIVIDE_AND_ROUND_UP(networkInfo.width, bboxLayerDims.w); 164 | int strideY = DIVIDE_AND_ROUND_UP(networkInfo.height, bboxLayerDims.h); 165 | 166 | for (int i = 0; i < gridW; i++) 167 | { 168 | gcCentersX[i] = (float)(i * strideX + 0.5); 169 | gcCentersX[i] /= (float)bboxNormX; 170 | 171 | } 172 | for (int i = 0; i < gridH; i++) 173 | { 174 | gcCentersY[i] = (float)(i * strideY + 0.5); 175 | gcCentersY[i] /= (float)bboxNormY; 176 | 177 | } 178 | 179 | for (int c = 0; c < numClassesToParse; c++) 180 | { 181 | float *outputX1 = outputBboxBuf + (c * 4 * bboxLayerDims.h * bboxLayerDims.w); 182 | 183 | float *outputY1 = outputX1 + gridSize; 184 | float *outputX2 = outputY1 + gridSize; 185 | float *outputY2 = outputX2 + gridSize; 186 | 187 | float threshold = detectionParams.perClassPreclusterThreshold[c]; 188 | for (int h = 0; h < gridH; h++) 189 | { 190 | for (int w = 0; w < gridW; w++) 191 | { 192 | int i = w + h * gridW; 193 | if (outputCovBuf[c * gridSize + i] >= threshold) 194 | { 195 | NvDsInferObjectDetectionInfo object; 196 | float rectX1f, rectY1f, rectX2f, rectY2f; 197 | 198 | rectX1f = (outputX1[w + h * gridW] - gcCentersX[w]) * -bboxNormX; 199 | rectY1f = (outputY1[w + h * gridW] - gcCentersY[h]) * -bboxNormY; 200 | rectX2f = (outputX2[w + h * gridW] + gcCentersX[w]) * bboxNormX; 201 | rectY2f = (outputY2[w + h * gridW] + gcCentersY[h]) * bboxNormY; 202 | 203 | object.classId = c; 204 | object.detectionConfidence = outputCovBuf[c * gridSize + i]; 205 | 206 | /* Clip object box co-ordinates to network resolution */ 207 | object.left = CLIP(rectX1f, 0, networkInfo.width - 1); 208 | object.top = CLIP(rectY1f, 0, networkInfo.height - 1); 209 | object.width = CLIP(rectX2f, 0, networkInfo.width - 1) - 210 | object.left + 1; 211 | object.height = CLIP(rectY2f, 0, networkInfo.height - 1) - 212 | object.top + 1; 213 | 214 | objectList.push_back(object); 215 | } 216 | } 217 | } 218 | } 219 | return true; 220 | } 221 | 222 | extern "C" 223 | bool NvDsInferParseCustomTfSSD (std::vector const &outputLayersInfo, 224 | NvDsInferNetworkInfo const &networkInfo, 225 | NvDsInferParseDetectionParams const &detectionParams, 226 | std::vector &objectList) 227 | { 228 | auto layerFinder = [&outputLayersInfo](const std::string &name) 229 | -> const NvDsInferLayerInfo *{ 230 | for (auto &layer : outputLayersInfo) { 231 | if (layer.dataType == FLOAT && 232 | (layer.layerName && name == layer.layerName)) { 233 | return &layer; 234 | } 235 | } 236 | return nullptr; 237 | }; 238 | 239 | const NvDsInferLayerInfo *numDetectionLayer = layerFinder("num_detections"); 240 | const NvDsInferLayerInfo *scoreLayer = layerFinder("detection_scores"); 241 | const NvDsInferLayerInfo *classLayer = layerFinder("detection_classes"); 242 | const NvDsInferLayerInfo *boxLayer = layerFinder("detection_boxes"); 243 | if (!scoreLayer || !classLayer || !boxLayer) { 244 | std::cerr << "ERROR: some layers missing or unsupported data types " 245 | << "in output tensors" << std::endl; 246 | return false; 247 | } 248 | 249 | unsigned int numDetections = classLayer->inferDims.d[0]; 250 | if (numDetectionLayer && numDetectionLayer->buffer) { 251 | numDetections = (int)((float*)numDetectionLayer->buffer)[0]; 252 | } 253 | if (numDetections > classLayer->inferDims.d[0]) { 254 | numDetections = classLayer->inferDims.d[0]; 255 | } 256 | numDetections = std::max(0, numDetections); 257 | for (unsigned int i = 0; i < numDetections; ++i) { 258 | NvDsInferObjectDetectionInfo res; 259 | res.detectionConfidence = ((float*)scoreLayer->buffer)[i]; 260 | res.classId = ((float*)classLayer->buffer)[i]; 261 | if (res.classId >= detectionParams.perClassPreclusterThreshold.size() || 262 | res.detectionConfidence < 263 | detectionParams.perClassPreclusterThreshold[res.classId]) { 264 | continue; 265 | } 266 | enum {y1, x1, y2, x2}; 267 | float rectX1f, rectY1f, rectX2f, rectY2f; 268 | rectX1f = ((float*)boxLayer->buffer)[i *4 + x1] * networkInfo.width; 269 | rectY1f = ((float*)boxLayer->buffer)[i *4 + y1] * networkInfo.height; 270 | rectX2f = ((float*)boxLayer->buffer)[i *4 + x2] * networkInfo.width;; 271 | rectY2f = ((float*)boxLayer->buffer)[i *4 + y2] * networkInfo.height; 272 | rectX1f = CLIP(rectX1f, 0.0f, networkInfo.width - 1); 273 | rectX2f = CLIP(rectX2f, 0.0f, networkInfo.width - 1); 274 | rectY1f = CLIP(rectY1f, 0.0f, networkInfo.height - 1); 275 | rectY2f = CLIP(rectY2f, 0.0f, networkInfo.height - 1); 276 | if (rectX2f <= rectX1f || rectY2f <= rectY1f) { 277 | continue; 278 | } 279 | res.left = rectX1f; 280 | res.top = rectY1f; 281 | res.width = rectX2f - rectX1f; 282 | res.height = rectY2f - rectY1f; 283 | if (res.width && res.height) { 284 | objectList.emplace_back(res); 285 | } 286 | } 287 | 288 | return true; 289 | } 290 | 291 | extern "C" 292 | bool NvDsInferParseCustomMrcnnTLT (std::vector const &outputLayersInfo, 293 | NvDsInferNetworkInfo const &networkInfo, 294 | NvDsInferParseDetectionParams const &detectionParams, 295 | std::vector &objectList) { 296 | auto layerFinder = [&outputLayersInfo](const std::string &name) 297 | -> const NvDsInferLayerInfo *{ 298 | for (auto &layer : outputLayersInfo) { 299 | if (layer.dataType == FLOAT && 300 | (layer.layerName && name == layer.layerName)) { 301 | return &layer; 302 | } 303 | } 304 | return nullptr; 305 | }; 306 | 307 | const NvDsInferLayerInfo *detectionLayer = layerFinder("generate_detections"); 308 | const NvDsInferLayerInfo *maskLayer = layerFinder("mask_head/mask_fcn_logits/BiasAdd"); 309 | 310 | if (!detectionLayer || !maskLayer) { 311 | std::cerr << "ERROR: some layers missing or unsupported data types " 312 | << "in output tensors" << std::endl; 313 | return false; 314 | } 315 | 316 | if(maskLayer->inferDims.numDims != 4U) { 317 | std::cerr << "Network output number of dims is : " << 318 | maskLayer->inferDims.numDims << " expect is 4"<< std::endl; 319 | return false; 320 | } 321 | 322 | const unsigned int det_max_instances = maskLayer->inferDims.d[0]; 323 | const unsigned int num_classes = maskLayer->inferDims.d[1]; 324 | if(num_classes != detectionParams.numClassesConfigured) { 325 | std::cerr << "WARNING: Num classes mismatch. Configured:" << 326 | detectionParams.numClassesConfigured << ", detected by network: " << 327 | num_classes << std::endl; 328 | } 329 | const unsigned int mask_instance_height= maskLayer->inferDims.d[2]; 330 | const unsigned int mask_instance_width = maskLayer->inferDims.d[3]; 331 | 332 | auto out_det = reinterpret_cast( detectionLayer->buffer); 333 | auto out_mask = reinterpret_cast(maskLayer->buffer); 335 | 336 | for(auto i = 0U; i < det_max_instances; i++) { 337 | MrcnnRawDetection &rawDec = out_det[i]; 338 | 339 | if(rawDec.score < detectionParams.perClassPreclusterThreshold[0]) 340 | continue; 341 | 342 | NvDsInferInstanceMaskInfo obj; 343 | obj.left = CLIP(rawDec.x1, 0, networkInfo.width - 1); 344 | obj.top = CLIP(rawDec.y1, 0, networkInfo.height - 1); 345 | obj.width = CLIP(rawDec.x2, 0, networkInfo.width - 1) - rawDec.x1; 346 | obj.height = CLIP(rawDec.y2, 0, networkInfo.height - 1) - rawDec.y1; 347 | if(obj.width <= 0 || obj.height <= 0) 348 | continue; 349 | obj.classId = static_cast(rawDec.class_id); 350 | obj.detectionConfidence = rawDec.score; 351 | 352 | obj.mask_size = sizeof(float)*mask_instance_width*mask_instance_height; 353 | obj.mask = new float[mask_instance_width*mask_instance_height]; 354 | obj.mask_width = mask_instance_width; 355 | obj.mask_height = mask_instance_height; 356 | 357 | float *rawMask = reinterpret_cast(out_mask + i 358 | * detectionParams.numClassesConfigured + obj.classId); 359 | memcpy (obj.mask, rawMask, sizeof(float)*mask_instance_width*mask_instance_height); 360 | 361 | objectList.push_back(obj); 362 | } 363 | 364 | return true; 365 | 366 | } 367 | 368 | extern "C" 369 | bool NvDsInferParseCustomNMSTLT (std::vector const &outputLayersInfo, 370 | NvDsInferNetworkInfo const &networkInfo, 371 | NvDsInferParseDetectionParams const &detectionParams, 372 | std::vector &objectList) { 373 | if(outputLayersInfo.size() != 2) 374 | { 375 | std::cerr << "Mismatch in the number of output buffers." 376 | << "Expected 2 output buffers, detected in the network :" 377 | << outputLayersInfo.size() << std::endl; 378 | return false; 379 | } 380 | 381 | // Host memory for "nms" which has 2 output bindings: 382 | // the order is bboxes and keep_count 383 | float* out_nms = (float *) outputLayersInfo[0].buffer; 384 | int * p_keep_count = (int *) outputLayersInfo[1].buffer; 385 | const float threshold = detectionParams.perClassThreshold[0]; 386 | 387 | float* det; 388 | 389 | for (int i = 0; i < p_keep_count[0]; i++) { 390 | det = out_nms + i * 7; 391 | 392 | // Output format for each detection is stored in the below order 393 | // [image_id, label, confidence, xmin, ymin, xmax, ymax] 394 | if ( det[2] < threshold) continue; 395 | assert((unsigned int) det[1] < detectionParams.numClassesConfigured); 396 | 397 | #if 0 398 | std::cout << "id/label/conf/ x/y x/y -- " 399 | << det[0] << " " << det[1] << " " << det[2] << " " 400 | << det[3] << " " << det[4] << " " << det[5] << " " << det[6] << std::endl; 401 | #endif 402 | NvDsInferObjectDetectionInfo object; 403 | object.classId = (int) det[1]; 404 | object.detectionConfidence = det[2]; 405 | 406 | /* Clip object box co-ordinates to network resolution */ 407 | object.left = CLIP(det[3] * networkInfo.width, 0, networkInfo.width - 1); 408 | object.top = CLIP(det[4] * networkInfo.height, 0, networkInfo.height - 1); 409 | object.width = CLIP((det[5] - det[3]) * networkInfo.width, 0, networkInfo.width - 1); 410 | object.height = CLIP((det[6] - det[4]) * networkInfo.height, 0, networkInfo.height - 1); 411 | 412 | objectList.push_back(object); 413 | } 414 | 415 | return true; 416 | } 417 | 418 | extern "C" 419 | bool NvDsInferParseYoloV5CustomBatchedNMSTLT ( 420 | std::vector const &outputLayersInfo, 421 | NvDsInferNetworkInfo const &networkInfo, 422 | NvDsInferParseDetectionParams const &detectionParams, 423 | std::vector &objectList) { 424 | 425 | if(outputLayersInfo.size() != 4) 426 | { 427 | std::cerr << "Mismatch in the number of output buffers." 428 | << "Expected 4 output buffers, detected in the network :" 429 | << outputLayersInfo.size() << std::endl; 430 | return false; 431 | } 432 | 433 | /* Host memory for "BatchedNMS" 434 | BatchedNMS has 4 output bindings, the order is: 435 | keepCount, bboxes, scores, classes 436 | */ 437 | int* p_keep_count = (int *) outputLayersInfo[0].buffer; 438 | float* p_bboxes = (float *) outputLayersInfo[1].buffer; 439 | float* p_scores = (float *) outputLayersInfo[2].buffer; 440 | float* p_classes = (float *) outputLayersInfo[3].buffer; 441 | 442 | const float threshold = detectionParams.perClassThreshold[0]; 443 | 444 | const int keep_top_k = 200; 445 | const char* log_enable = std::getenv("ENABLE_DEBUG"); 446 | 447 | if(log_enable != NULL && std::stoi(log_enable)) { 448 | std::cout <<"keep cout" 449 | <= detectionParams.numClassesConfigured) continue; 463 | if(p_bboxes[4*i+2] < p_bboxes[4*i] || p_bboxes[4*i+3] < p_bboxes[4*i+1]) continue; 464 | 465 | NvDsInferObjectDetectionInfo object; 466 | object.classId = (int) p_classes[i]; 467 | object.detectionConfidence = p_scores[i]; 468 | 469 | object.left = CLIP(p_bboxes[4*i], 0, networkInfo.width - 1); 470 | object.top = CLIP(p_bboxes[4*i+1], 0, networkInfo.height - 1); 471 | object.width = CLIP(p_bboxes[4*i+2], 0, networkInfo.width - 1) - object.left; 472 | object.height = CLIP(p_bboxes[4*i+3], 0, networkInfo.height - 1) - object.top; 473 | 474 | if(object.height < 0 || object.width < 0) 475 | continue; 476 | objectList.push_back(object); 477 | } 478 | return true; 479 | } 480 | 481 | extern "C" 482 | bool NvDsInferParseCustomBatchedNMSTLT ( 483 | std::vector const &outputLayersInfo, 484 | NvDsInferNetworkInfo const &networkInfo, 485 | NvDsInferParseDetectionParams const &detectionParams, 486 | std::vector &objectList) { 487 | 488 | if(outputLayersInfo.size() != 4) 489 | { 490 | std::cerr << "Mismatch in the number of output buffers." 491 | << "Expected 4 output buffers, detected in the network :" 492 | << outputLayersInfo.size() << std::endl; 493 | return false; 494 | } 495 | 496 | /* Host memory for "BatchedNMS" 497 | BatchedNMS has 4 output bindings, the order is: 498 | keepCount, bboxes, scores, classes 499 | */ 500 | int* p_keep_count = (int *) outputLayersInfo[0].buffer; 501 | float* p_bboxes = (float *) outputLayersInfo[1].buffer; 502 | float* p_scores = (float *) outputLayersInfo[2].buffer; 503 | float* p_classes = (float *) outputLayersInfo[3].buffer; 504 | 505 | const float threshold = detectionParams.perClassThreshold[0]; 506 | 507 | const int keep_top_k = 200; 508 | const char* log_enable = std::getenv("ENABLE_DEBUG"); 509 | 510 | if(log_enable != NULL && std::stoi(log_enable)) { 511 | std::cout <<"keep cout" 512 | <= detectionParams.numClassesConfigured) continue; 526 | if(p_bboxes[4*i+2] < p_bboxes[4*i] || p_bboxes[4*i+3] < p_bboxes[4*i+1]) continue; 527 | 528 | NvDsInferObjectDetectionInfo object; 529 | object.classId = (int) p_classes[i]; 530 | object.detectionConfidence = p_scores[i]; 531 | 532 | /* Clip object box co-ordinates to network resolution */ 533 | object.left = CLIP(p_bboxes[4*i] * networkInfo.width, 0, networkInfo.width - 1); 534 | object.top = CLIP(p_bboxes[4*i+1] * networkInfo.height, 0, networkInfo.height - 1); 535 | object.width = CLIP(p_bboxes[4*i+2] * networkInfo.width, 0, networkInfo.width - 1) - object.left; 536 | object.height = CLIP(p_bboxes[4*i+3] * networkInfo.height, 0, networkInfo.height - 1) - object.top; 537 | 538 | if(object.height < 0 || object.width < 0) 539 | continue; 540 | objectList.push_back(object); 541 | } 542 | return true; 543 | } 544 | 545 | extern "C" 546 | bool NvDsInferParseCustomMrcnnTLTV2 (std::vector const &outputLayersInfo, 547 | NvDsInferNetworkInfo const &networkInfo, 548 | NvDsInferParseDetectionParams const &detectionParams, 549 | std::vector &objectList) { 550 | auto layerFinder = [&outputLayersInfo](const std::string &name) 551 | -> const NvDsInferLayerInfo *{ 552 | for (auto &layer : outputLayersInfo) { 553 | if (layer.dataType == FLOAT && 554 | (layer.layerName && name == layer.layerName)) { 555 | return &layer; 556 | } 557 | } 558 | return nullptr; 559 | }; 560 | 561 | const NvDsInferLayerInfo *detectionLayer = layerFinder("generate_detections"); 562 | const NvDsInferLayerInfo *maskLayer = layerFinder("mask_fcn_logits/BiasAdd"); 563 | 564 | if (!detectionLayer || !maskLayer) { 565 | std::cerr << "ERROR: some layers missing or unsupported data types " 566 | << "in output tensors" << std::endl; 567 | return false; 568 | } 569 | 570 | if(maskLayer->inferDims.numDims != 4U) { 571 | std::cerr << "Network output number of dims is : " << 572 | maskLayer->inferDims.numDims << " expect is 4"<< std::endl; 573 | return false; 574 | } 575 | 576 | const unsigned int det_max_instances = maskLayer->inferDims.d[0]; 577 | const unsigned int num_classes = maskLayer->inferDims.d[1]; 578 | if(num_classes != detectionParams.numClassesConfigured) { 579 | std::cerr << "WARNING: Num classes mismatch. Configured:" << 580 | detectionParams.numClassesConfigured << ", detected by network: " << 581 | num_classes << std::endl; 582 | } 583 | const unsigned int mask_instance_height= maskLayer->inferDims.d[2]; 584 | const unsigned int mask_instance_width = maskLayer->inferDims.d[3]; 585 | 586 | auto out_det = reinterpret_cast( detectionLayer->buffer); 587 | auto out_mask = reinterpret_cast(maskLayer->buffer); 589 | 590 | for(auto i = 0U; i < det_max_instances; i++) { 591 | MrcnnRawDetection &rawDec = out_det[i]; 592 | 593 | if(rawDec.score < detectionParams.perClassPreclusterThreshold[0]) 594 | continue; 595 | 596 | NvDsInferInstanceMaskInfo obj; 597 | obj.left = CLIP(rawDec.x1, 0, networkInfo.width - 1); 598 | obj.top = CLIP(rawDec.y1, 0, networkInfo.height - 1); 599 | obj.width = CLIP(rawDec.x2, 0, networkInfo.width - 1) - rawDec.x1; 600 | obj.height = CLIP(rawDec.y2, 0, networkInfo.height - 1) - rawDec.y1; 601 | if(obj.width <= 0 || obj.height <= 0) 602 | continue; 603 | obj.classId = static_cast(rawDec.class_id); 604 | obj.detectionConfidence = rawDec.score; 605 | 606 | obj.mask_size = sizeof(float)*mask_instance_width*mask_instance_height; 607 | obj.mask = new float[mask_instance_width*mask_instance_height]; 608 | obj.mask_width = mask_instance_width; 609 | obj.mask_height = mask_instance_height; 610 | 611 | float *rawMask = reinterpret_cast(out_mask + i 612 | * detectionParams.numClassesConfigured + obj.classId); 613 | memcpy (obj.mask, rawMask, sizeof(float)*mask_instance_width*mask_instance_height); 614 | 615 | objectList.push_back(obj); 616 | } 617 | 618 | return true; 619 | 620 | } 621 | 622 | extern "C" 623 | bool NvDsInferParseCustomEfficientDetTAO (std::vector const &outputLayersInfo, 624 | NvDsInferNetworkInfo const &networkInfo, 625 | NvDsInferParseDetectionParams const &detectionParams, 626 | std::vector &objectList) { 627 | if(outputLayersInfo.size() != 4) 628 | { 629 | std::cerr << "Mismatch in the number of output buffers." 630 | << "Expected 4 output buffers, detected in the network :" 631 | << outputLayersInfo.size() << std::endl; 632 | return false; 633 | } 634 | 635 | int* p_keep_count = (int *) outputLayersInfo[0].buffer; 636 | 637 | float* p_bboxes = (float *) outputLayersInfo[1].buffer; 638 | NvDsInferDims inferDims_p_bboxes = outputLayersInfo[1].inferDims; 639 | int numElements_p_bboxes=inferDims_p_bboxes.numElements; 640 | 641 | float* p_scores = (float *) outputLayersInfo[2].buffer; 642 | float* p_classes = (float *) outputLayersInfo[3].buffer; 643 | 644 | const float threshold = detectionParams.perClassThreshold[0]; 645 | 646 | float max_bbox=0; 647 | for (int i=0; i < numElements_p_bboxes; i++) 648 | { 649 | // std::cout <<"p_bboxes: " 650 | // < 0) 656 | { 657 | assert (!(max_bbox < 2.0)); 658 | for (int i = 0; i < p_keep_count[0]; i++) { 659 | 660 | 661 | if ( p_scores[i] < threshold) continue; 662 | assert((unsigned int) p_classes[i] < detectionParams.numClassesConfigured); 663 | 664 | 665 | // std::cout << "label/conf/ x/y x/y -- " 666 | // << (int)p_classes[i] << " " << p_scores[i] << " " 667 | // << p_bboxes[4*i] << " " << p_bboxes[4*i+1] << " " << p_bboxes[4*i+2] << " "<< p_bboxes[4*i+3] << " " << std::endl; 668 | 669 | if(p_bboxes[4*i+2] < p_bboxes[4*i] || p_bboxes[4*i+3] < p_bboxes[4*i+1]) 670 | continue; 671 | 672 | NvDsInferObjectDetectionInfo object; 673 | object.classId = (int) p_classes[i]; 674 | object.detectionConfidence = p_scores[i]; 675 | 676 | 677 | object.left=p_bboxes[4*i+1]; 678 | object.top=p_bboxes[4*i]; 679 | object.width=( p_bboxes[4*i+3] - object.left); 680 | object.height= ( p_bboxes[4*i+2] - object.top); 681 | 682 | object.left=CLIP(object.left, 0, networkInfo.width - 1); 683 | object.top=CLIP(object.top, 0, networkInfo.height - 1); 684 | object.width=CLIP(object.width, 0, networkInfo.width - 1); 685 | object.height=CLIP(object.height, 0, networkInfo.height - 1); 686 | 687 | objectList.push_back(object); 688 | } 689 | } 690 | return true; 691 | } 692 | 693 | 694 | extern "C" 695 | bool NvDsInferParseCustomEfficientNMS (std::vector const &outputLayersInfo, 696 | NvDsInferNetworkInfo const &networkInfo, 697 | NvDsInferParseDetectionParams const &detectionParams, 698 | std::vector &objectList) { 699 | if(outputLayersInfo.size() != 4) 700 | { 701 | std::cerr << "Mismatch in the number of output buffers." 702 | << "Expected 4 output buffers, detected in the network :" 703 | << outputLayersInfo.size() << std::endl; 704 | return false; 705 | } 706 | 707 | int* p_keep_count = (int *) outputLayersInfo[0].buffer; 708 | 709 | float* p_bboxes = (float *) outputLayersInfo[1].buffer; 710 | NvDsInferDims inferDims_p_bboxes = outputLayersInfo[1].inferDims; 711 | int numElements_p_bboxes=inferDims_p_bboxes.numElements; 712 | 713 | float* p_scores = (float *) outputLayersInfo[2].buffer; 714 | unsigned int* p_classes = (unsigned int *) outputLayersInfo[3].buffer; 715 | 716 | const float threshold = detectionParams.perClassThreshold[0]; 717 | 718 | float max_bbox=0; 719 | for (int i=0; i < numElements_p_bboxes; i++) 720 | { 721 | if ( max_bbox < p_bboxes[i] ) 722 | max_bbox=p_bboxes[i]; 723 | } 724 | 725 | if (p_keep_count[0] > 0) 726 | { 727 | assert (!(max_bbox < 2.0)); 728 | for (int i = 0; i < p_keep_count[0]; i++) { 729 | 730 | if ( p_scores[i] < threshold) continue; 731 | assert((unsigned int) p_classes[i] < detectionParams.numClassesConfigured); 732 | 733 | NvDsInferObjectDetectionInfo object; 734 | object.classId = (int) p_classes[i]; 735 | object.detectionConfidence = p_scores[i]; 736 | 737 | object.left=p_bboxes[4*i]; 738 | object.top=p_bboxes[4*i+1]; 739 | object.width=(p_bboxes[4*i+2] - object.left); 740 | object.height= (p_bboxes[4*i+3] - object.top); 741 | 742 | 743 | object.left=CLIP(object.left, 0, networkInfo.width - 1); 744 | object.top=CLIP(object.top, 0, networkInfo.height - 1); 745 | object.width=CLIP(object.width, 0, networkInfo.width - 1); 746 | object.height=CLIP(object.height, 0, networkInfo.height - 1); 747 | 748 | objectList.push_back(object); 749 | } 750 | } 751 | return true; 752 | } 753 | 754 | 755 | 756 | /* Check that the custom function has been defined correctly */ 757 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomResnet); 758 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomTfSSD); 759 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomNMSTLT); 760 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseYoloV5CustomBatchedNMSTLT); 761 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomBatchedNMSTLT); 762 | CHECK_CUSTOM_INSTANCE_MASK_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomMrcnnTLT); 763 | CHECK_CUSTOM_INSTANCE_MASK_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomMrcnnTLTV2); 764 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomEfficientDetTAO); 765 | CHECK_CUSTOM_PARSE_FUNC_PROTOTYPE(NvDsInferParseCustomEfficientNMS); 766 | -------------------------------------------------------------------------------- /nvdsinfer_custom_impl_Yolo/nvdsinfer_customclassifierparser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the "Software"), 6 | * to deal in the Software without restriction, including without limitation 7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | * and/or sell copies of the Software, and to permit persons to whom the 9 | * Software is furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | * DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include "nvdsinfer_custom_impl.h" 26 | 27 | /* This is a sample classifier output parsing function from softmax layers for 28 | * the vehicle type classifier model provided with the SDK. */ 29 | 30 | /* C-linkage to prevent name-mangling */ 31 | extern "C" 32 | bool NvDsInferClassiferParseCustomSoftmax (std::vector const &outputLayersInfo, 33 | NvDsInferNetworkInfo const &networkInfo, 34 | float classifierThreshold, 35 | std::vector &attrList, 36 | std::string &descString); 37 | 38 | static std::vector < std::vector< std:: string > > labels { { 39 | "coupe1", "largevehicle1", "sedan1", "suv1", "truck1", "van1"} }; 40 | 41 | extern "C" 42 | bool NvDsInferClassiferParseCustomSoftmax (std::vector const &outputLayersInfo, 43 | NvDsInferNetworkInfo const &networkInfo, 44 | float classifierThreshold, 45 | std::vector &attrList, 46 | std::string &descString) 47 | { 48 | /* Get the number of attributes supported by the classifier. */ 49 | unsigned int numAttributes = outputLayersInfo.size(); 50 | 51 | /* Iterate through all the output coverage layers of the classifier. 52 | */ 53 | for (unsigned int l = 0; l < numAttributes; l++) 54 | { 55 | /* outputCoverageBuffer for classifiers is usually a softmax layer. 56 | * The layer is an array of probabilities of the object belonging 57 | * to each class with each probability being in the range [0,1] and 58 | * sum all probabilities will be 1. 59 | */ 60 | NvDsInferDimsCHW dims; 61 | 62 | getDimsCHWFromDims(dims, outputLayersInfo[l].inferDims); 63 | unsigned int numClasses = dims.c; 64 | float *outputCoverageBuffer = (float *)outputLayersInfo[l].buffer; 65 | float maxProbability = 0; 66 | bool attrFound = false; 67 | NvDsInferAttribute attr; 68 | 69 | /* Iterate through all the probabilities that the object belongs to 70 | * each class. Find the maximum probability and the corresponding class 71 | * which meets the minimum threshold. */ 72 | for (unsigned int c = 0; c < numClasses; c++) 73 | { 74 | float probability = outputCoverageBuffer[c]; 75 | if (probability > classifierThreshold 76 | && probability > maxProbability) 77 | { 78 | maxProbability = probability; 79 | attrFound = true; 80 | attr.attributeIndex = l; 81 | attr.attributeValue = c; 82 | attr.attributeConfidence = probability; 83 | } 84 | } 85 | if (attrFound) 86 | { 87 | if (labels.size() > attr.attributeIndex && 88 | attr.attributeValue < labels[attr.attributeIndex].size()) 89 | attr.attributeLabel = 90 | strdup(labels[attr.attributeIndex][attr.attributeValue].c_str()); 91 | else 92 | attr.attributeLabel = nullptr; 93 | attrList.push_back(attr); 94 | if (attr.attributeLabel) 95 | descString.append(attr.attributeLabel).append(" "); 96 | } 97 | } 98 | 99 | return true; 100 | } 101 | 102 | /* Check that the custom function has been defined correctly */ 103 | CHECK_CUSTOM_CLASSIFIER_PARSE_FUNC_PROTOTYPE(NvDsInferClassiferParseCustomSoftmax); 104 | -------------------------------------------------------------------------------- /run_docker.sh: -------------------------------------------------------------------------------- 1 | docker run -it --rm --gpus all --network host \ 2 | -v $PWD:/opt/nvidia/deepstream/deepstream-6.1/sources/yolov7-triton-deepstream \ 3 | -e DISPLAY=$DISPLAY -e XAUTHORITY=$XAUTHORITY -v $XAUTHORITY:$XAUTHORITY -v /tmp/.X11-unix:/tmp/.X11-unix thanhlnbka/deepstream-python-app:3.0-triton -------------------------------------------------------------------------------- /triton_yolov7/yolov7/classes.txt: -------------------------------------------------------------------------------- 1 | person 2 | bicycle 3 | car 4 | motorcycle 5 | airplane 6 | bus 7 | train 8 | truck 9 | boat 10 | traffic light 11 | fire hydrant 12 | stop sign 13 | parking meter 14 | bench 15 | bird 16 | cat 17 | dog 18 | horse 19 | sheep 20 | cow 21 | elephant 22 | bear 23 | zebra 24 | giraffe 25 | backpack 26 | umbrella 27 | handbag 28 | tie 29 | suitcase 30 | frisbee 31 | skis 32 | snowboard 33 | sports ball 34 | kite 35 | baseball bat 36 | baseball glove 37 | skateboard 38 | surfboard 39 | tennis racket 40 | bottle 41 | wine glass 42 | cup 43 | fork 44 | knife 45 | spoon 46 | bowl 47 | banana 48 | apple 49 | sandwich 50 | orange 51 | broccoli 52 | carrot 53 | hot dog 54 | pizza 55 | donut 56 | cake 57 | chair 58 | couch 59 | potted plant 60 | bed 61 | dining table 62 | toilet 63 | tv 64 | laptop 65 | mouse 66 | remote 67 | keyboard 68 | cell phone 69 | microwave 70 | oven 71 | toaster 72 | sink 73 | refrigerator 74 | book 75 | clock 76 | vase 77 | scissors 78 | teddy bear 79 | hair drier 80 | toothbrush 81 | -------------------------------------------------------------------------------- /triton_yolov7/yolov7/config.pbtxt: -------------------------------------------------------------------------------- 1 | name: "yolov7" 2 | platform: "tensorrt_plan" 3 | max_batch_size: 8 4 | input [ 5 | { 6 | name: "images" 7 | data_type: TYPE_FP32 8 | dims: [3,640,640] 9 | } 10 | ] 11 | output [ 12 | { 13 | name: "det_boxes" 14 | data_type: TYPE_FP32 15 | dims: [ 100, 4] 16 | reshape { shape: [100,4] } 17 | }, 18 | { 19 | name: "det_classes" 20 | data_type: TYPE_INT32 21 | dims: [ 100 ] 22 | }, 23 | { 24 | name: "det_scores" 25 | data_type: TYPE_FP32 26 | dims: [ 100 ] 27 | }, 28 | { 29 | name: "num_dets" 30 | data_type: TYPE_INT32 31 | dims: [ 1] 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /weights/README.md: -------------------------------------------------------------------------------- 1 | # Export model pt to onnx with yolov7 2 | See https://github.com/WongKinYiu/yolov7#export for more info.
3 | 4 | * python export.py --weights ./yolov7.pt --grid --end2end --dynamic-batch --simplify --topk-all 100 --iou-thres 0.65 --conf-thres 0.35 --img-size 640 640
5 | 6 | 7 | download yolov7-tiny converted onnx: https://drive.google.com/file/d/1GZbGFUk8BtEO-ZyWL2aoa3g4YS40dKiF/view?usp=sharing
--------------------------------------------------------------------------------