deepstream-covid-19-cpp.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. # Import required libraries
  2. import argparse
  3. import os
  4. import sys
  5. sys.path.append('../')
  6. sys.path.append('../source_code')
  7. sys.path.append('../source_code/distancing/')
  8. import gi
  9. import configparser
  10. gi.require_version('Gst', '1.0')
  11. from gi.repository import GObject, Gst
  12. from gi.repository import GLib
  13. from ctypes import *
  14. import time
  15. import math
  16. import platform
  17. from common.bus_call import bus_call
  18. from common.FPS import GETFPS
  19. import pyds
  20. import distancing
  21. PGIE_CLASS_ID_VEHICLE = 0
  22. PGIE_CLASS_ID_BICYCLE = 1
  23. PGIE_CLASS_ID_PERSON = 2
  24. PGIE_CLASS_ID_ROADSIGN = 3
  25. g_args=None
  26. # Define variables to be used later
  27. fps_streams={}
  28. pgie_classes_str= ["Vehicle", "TwoWheeler", "Person","RoadSign"]
  29. def parse_args():
  30. parser = argparse.ArgumentParser()
  31. parser.add_argument("--num-sources", type=int, default=1, help="Number of sources, it replicates inputs if its is greater than length of inputs")
  32. parser.add_argument("--prof", type=bool, default=False, help="Profiling Mode , Profiles with a shorter video clip.")
  33. args = parser.parse_args()
  34. return args
  35. def cb_newpad(decodebin, decoder_src_pad,data):
  36. print("In cb_newpad\n")
  37. caps=decoder_src_pad.get_current_caps()
  38. gststruct=caps.get_structure(0)
  39. gstname=gststruct.get_name()
  40. source_bin=data
  41. features=caps.get_features(0)
  42. # Need to check if the pad created by the decodebin is for video and not
  43. # audio.
  44. print("gstname=",gstname)
  45. if(gstname.find("video")!=-1):
  46. # Link the decodebin pad only if decodebin has picked nvidia
  47. # decoder plugin nvdec_*. We do this by checking if the pad caps contain
  48. # NVMM memory features.
  49. print("features=",features)
  50. if features.contains("memory:NVMM"):
  51. # Get the source bin ghost pad
  52. bin_ghost_pad=source_bin.get_static_pad("src")
  53. if not bin_ghost_pad.set_target(decoder_src_pad):
  54. sys.stderr.write("Failed to link decoder src pad to source bin ghost pad\n")
  55. else:
  56. sys.stderr.write(" Error: Decodebin did not pick nvidia decoder plugin.\n")
  57. def decodebin_child_added(child_proxy,Object,name,user_data):
  58. print("Decodebin child added:", name, "\n")
  59. if(name.find("decodebin") != -1):
  60. Object.connect("child-added",decodebin_child_added,user_data)
  61. def create_source_bin(args, index,uri):
  62. print("Creating source bin")
  63. # Create a source GstBin to abstract this bin's content from the rest of the
  64. # pipeline
  65. bin_name="source-bin-%02d" %index
  66. print(bin_name)
  67. nbin=Gst.Bin.new(bin_name)
  68. if not nbin:
  69. sys.stderr.write(" Unable to create source bin \n")
  70. # Source element for reading from the uri.
  71. # We will use decodebin and let it figure out the container format of the
  72. # stream and the codec and plug the appropriate demux and decode plugins.
  73. uri_decode_bin=Gst.ElementFactory.make("uridecodebin", "uri-decode-bin")
  74. if not uri_decode_bin:
  75. sys.stderr.write(" Unable to create uri decode bin \n")
  76. # We set the input uri to the source element
  77. uri_decode_bin.set_property("uri",uri)
  78. # Connect to the "pad-added" signal of the decodebin which generates a
  79. # callback once a new pad for raw data has beed created by the decodebin
  80. uri_decode_bin.connect("pad-added",cb_newpad,nbin)
  81. uri_decode_bin.connect("child-added",decodebin_child_added,nbin)
  82. # We need to create a ghost pad for the source bin which will act as a proxy
  83. # for the video decoder src pad. The ghost pad will not have a target right
  84. # now. Once the decode bin creates the video decoder and generates the
  85. # cb_newpad callback, we will set the ghost pad target to the video decoder
  86. # src pad.
  87. Gst.Bin.add(nbin,uri_decode_bin)
  88. bin_pad=nbin.add_pad(Gst.GhostPad.new_no_target("src",Gst.PadDirection.SRC))
  89. if not bin_pad:
  90. sys.stderr.write(" Failed to add ghost pad in source bin \n")
  91. return None
  92. return nbin
  93. ## Make Element or Print Error and any other detail
  94. def make_elm_or_print_err(factoryname, name, printedname, detail=""):
  95. print("Creating", printedname)
  96. elm = Gst.ElementFactory.make(factoryname, name)
  97. if not elm:
  98. sys.stderr.write("Unable to create " + printedname + " \n")
  99. if detail:
  100. sys.stderr.write(detail)
  101. return elm
  102. ############# Define Computation required for our pipeline #################
  103. def visualize(objs):
  104. violations = 0
  105. dist_threshold = 160 # Distance in cms
  106. for obj in objs:
  107. min_dist = obj["min_dist"]
  108. redness_factor = 1.5
  109. r_channel = max(255 * (dist_threshold - min_dist) / dist_threshold, 0) * redness_factor
  110. g_channel = 255 - r_channel
  111. b_channel = 0
  112. obj_meta = obj["obj_meta"]
  113. obj_meta.rect_params.border_color.red = r_channel
  114. obj_meta.rect_params.border_color.green = g_channel
  115. obj_meta.rect_params.border_color.blue = b_channel
  116. obj["violated"] = (min_dist < dist_threshold)
  117. violations = violations + int(min_dist < dist_threshold)
  118. return violations
  119. def get_centroid(rect):
  120. xmin = rect.left
  121. xmax = rect.left + rect.width
  122. ymin = rect.top
  123. ymax = rect.top + rect.height
  124. centroid_x = (xmin + xmax) / 2
  125. centroid_y = (ymin + ymax) / 2
  126. return (centroid_x, centroid_y, rect.height)
  127. def compute_min_distances_cpp(objs):
  128. centroids = [o["centroid"] for o in objs]
  129. min_distances = distancing.get_min_distances(centroids)
  130. for o in range(len(objs)):
  131. objs[o]["min_dist"] = min_distances[o]
  132. ############## Working with the Metadata ################
  133. def src_pad_buffer_probe(pad,info,u_data):
  134. # Set frame_number & rectangles to draw as 0
  135. frame_number=0
  136. num_rects=0
  137. gst_buffer = info.get_buffer()
  138. if not gst_buffer:
  139. print("Unable to get GstBuffer ")
  140. return
  141. # Retrieve batch metadata from the gst_buffer
  142. # Note that pyds.gst_buffer_get_nvds_batch_meta() expects the
  143. # C address of gst_buffer as input, which is obtained with hash(gst_buffer)
  144. batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
  145. l_frame = batch_meta.frame_meta_list
  146. while l_frame is not None:
  147. try:
  148. # Note that l_frame.data needs a cast to pyds.NvDsFrameMeta
  149. frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
  150. except StopIteration:
  151. break
  152. # Get frame number , number of rectables to draw and object metadata
  153. frame_number=frame_meta.frame_num
  154. num_rects = frame_meta.num_obj_meta
  155. l_obj=frame_meta.obj_meta_list
  156. objects=[]
  157. #Intiallizing object counter with 0.
  158. obj_counter = {
  159. PGIE_CLASS_ID_VEHICLE:0,
  160. PGIE_CLASS_ID_PERSON:0,
  161. PGIE_CLASS_ID_BICYCLE:0,
  162. PGIE_CLASS_ID_ROADSIGN:0
  163. }
  164. while l_obj is not None:
  165. try:
  166. # Casting l_obj.data to pyds.NvDsObjectMeta
  167. obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data)
  168. except StopIteration:
  169. break
  170. # Increment Object class by 1 and Set Box border to Red color
  171. obj_counter[obj_meta.class_id] +=1
  172. obj_meta.rect_params.border_color.set(0.0, 0.0, 1.0, 0.0)
  173. if (obj_meta.class_id == PGIE_CLASS_ID_PERSON):
  174. obj = {}
  175. obj["tracker_id"] = obj_meta.object_id
  176. obj["unique_id"] = obj_meta.unique_component_id
  177. obj["centroid"] = get_centroid(obj_meta.rect_params)
  178. obj["obj_meta"] = obj_meta
  179. objects.append(obj)
  180. else:
  181. obj_meta.rect_params.border_width = 0
  182. try:
  183. l_obj=l_obj.next
  184. except StopIteration:
  185. break
  186. compute_min_distances_cpp(objects)
  187. violations = visualize(objects)
  188. print("Frame Number={} Number of Objects={} Vehicle_count={} Person_count={} Violations={}".format(frame_number, num_rects, obj_counter[PGIE_CLASS_ID_VEHICLE], obj_counter[PGIE_CLASS_ID_PERSON],violations))
  189. ############################################################################
  190. # Get frame rate through this probe
  191. fps_streams["stream{0}".format(frame_meta.pad_index)].get_fps()
  192. try:
  193. l_frame=l_frame.next
  194. except StopIteration:
  195. break
  196. return Gst.PadProbeReturn.OK
  197. def main():
  198. args = parse_args()
  199. global g_args
  200. g_args = args
  201. num_sources = args.num_sources
  202. path = os.path.abspath(os.getcwd())
  203. if (args.prof):
  204. INPUT_VIDEO = 'file://' + path +'/../source_code/dataset/wt_prof.mp4'
  205. else :
  206. INPUT_VIDEO = 'file://' + path +'/../source_code/dataset/wt.mp4'
  207. print("Creating pipeline with "+str(num_sources)+" streams")
  208. # Initialise FPS
  209. for i in range(0,num_sources):
  210. fps_streams["stream{0}".format(i)]=GETFPS(i)
  211. # Standard GStreamer initialization
  212. Gst.init(None)
  213. # Create gstreamer elements */
  214. # Create Pipeline element that will form a connection of other elements
  215. print("Creating Pipeline \n ")
  216. pipeline = Gst.Pipeline()
  217. if not pipeline:
  218. sys.stderr.write(" Unable to create Pipeline \n")
  219. ########### Create Elements required for the Pipeline ###########
  220. # Create nvstreammux instance to form batches from one or more sources.
  221. streammux = make_elm_or_print_err("nvstreammux", "Stream-muxer","Stream-muxer")
  222. pipeline.add(streammux)
  223. for i in range(num_sources):
  224. print("Creating source_bin ",i," \n ")
  225. uri_name=INPUT_VIDEO
  226. if uri_name.find("rtsp://") == 0 :
  227. is_live = True
  228. source_bin=create_source_bin(args, i, uri_name)
  229. if not source_bin:
  230. sys.stderr.write("Unable to create source bin \n")
  231. pipeline.add(source_bin)
  232. padname="sink_%u" %i
  233. sinkpad = streammux.get_request_pad(padname)
  234. if not sinkpad:
  235. sys.stderr.write("Unable to create sink pad bin \n")
  236. srcpad = source_bin.get_static_pad("src")
  237. if not srcpad:
  238. sys.stderr.write("Unable to create src pad bin \n")
  239. srcpad.link(sinkpad)
  240. # Use nvinfer to run inferencing on decoder's output, behaviour of inferencing is set through config file
  241. pgie = make_elm_or_print_err("nvinfer", "primary-inference" ,"pgie")
  242. # Create Sink for storing the output
  243. fakesink = make_elm_or_print_err("fakesink", "fakesink", "Sink")
  244. # Queues to enable buffering
  245. queue1=make_elm_or_print_err("queue","queue1","queue1")
  246. queue2=make_elm_or_print_err("queue","queue2","queue2")
  247. queue3=make_elm_or_print_err("queue","queue3","queue3")
  248. ############ Set properties for the Elements ############
  249. # Set Input Width , Height and Batch Size
  250. streammux.set_property('width', 1920)
  251. streammux.set_property('height', 1080)
  252. streammux.set_property('batch-size', num_sources)
  253. # Timeout in microseconds to wait after the first buffer is available
  254. # to push the batch even if a complete batch is not formed.
  255. streammux.set_property('batched-push-timeout', 4000000)
  256. # Set configuration file for nvinfer
  257. pgie.set_property('config-file-path', "../source_code/N3/dstest1_pgie_config.txt")
  258. # Setting batch_size for pgie
  259. pgie_batch_size=pgie.get_property("batch-size")
  260. if(pgie_batch_size != num_sources):
  261. print("WARNING: Overriding infer-config batch-size",pgie_batch_size," with number of sources ", num_sources," \n")
  262. pgie.set_property("batch-size",num_sources)
  263. # Fake sink properties
  264. fakesink.set_property("sync", 0)
  265. fakesink.set_property("async", 0)
  266. ########## Add and Link ELements in the Pipeline ##########
  267. print("Adding elements to Pipeline \n")
  268. pipeline.add(queue1)
  269. pipeline.add(pgie)
  270. pipeline.add(queue2)
  271. pipeline.add(queue3)
  272. pipeline.add(fakesink)
  273. print("Linking elements in the Pipeline \n")
  274. streammux.link(queue1)
  275. queue1.link(pgie)
  276. pgie.link(queue2)
  277. queue2.link(queue3)
  278. queue3.link(fakesink)
  279. # create an event loop and feed gstreamer bus mesages to it
  280. loop = GLib.MainLoop()
  281. bus = pipeline.get_bus()
  282. bus.add_signal_watch()
  283. bus.connect ("message", bus_call, loop)
  284. print("Added and Linked elements to pipeline")
  285. src_pad=queue3.get_static_pad("src")
  286. if not src_pad:
  287. sys.stderr.write(" Unable to get src pad \n")
  288. else:
  289. src_pad.add_probe(Gst.PadProbeType.BUFFER, src_pad_buffer_probe, 0)
  290. # List the sources
  291. print("Now playing...")
  292. print("Starting pipeline \n")
  293. # start play back and listed to events
  294. pipeline.set_state(Gst.State.PLAYING)
  295. start_time = time.time()
  296. try:
  297. loop.run()
  298. except:
  299. pass
  300. # cleanup
  301. print("Exiting app\n")
  302. pipeline.set_state(Gst.State.NULL)
  303. print("--- %s seconds ---" % (time.time() - start_time))
  304. if __name__ == '__main__':
  305. sys.exit(main())