Pipeline freezes on osd_sink_pad_buffer_probe()

• Jetson AGX Xavier
• Deepstream 6.0
• JetPack 4.6
• TensorRT 8.0.1
• NVIDIA GPU Driver 32.6.1

Hello, I getting videostream from rtsp cam and use it on this pipeline:

import argparse
import os

import sys
sys.path.append('../')

import dbf
import gi
gi.require_version('Gst', '1.0')

from gi.repository import GObject, Gst
from common.is_aarch_64 import is_aarch64
from common.bus_call import bus_call
from common.FPS import GETFPS
import configparser
import numpy as np
import cv2

import pyds

PGIE_CLASS_ID_BOTTOM = 0
PGIE_CLASS_ID_HOOK = 1
PGIE_CLASS_ID_PRESS_B = 2

COLORS = [[255, 255, 255], [0, 0, 128], [0, 128, 128], [128, 0, 0],
          [128, 0, 128], [128, 128, 0], [0, 128, 0], [0, 0, 64],
          [0, 0, 192], [0, 128, 64], [0, 128, 192], [128, 0, 64],
          [128, 0, 192], [0, 0, 0]]

total_bottoms = 0
bottom_absence = 0
bottom_counter = 0
press_counter = 0
hook_counter = 0
all_confirms = {1: 0, 2: 0, 3: 0}

def map_mask_as_display_bgr(mask):
    """ Assigning multiple colors as image output using the information
        contained in mask. (BGR is opencv standard.)
    """
    # getting a list of available classes
    shp = mask.shape
    bgr = np.zeros((shp[0], shp[1]))
    bgr[mask == 0] = 255
    return bgr

fps_stream=0

def cb_newpad(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(child_proxy,Object,name,user_data):
    print("Decodebin child added:", name, "\n")
    if(name.find("decodebin") != -1):
        Object.connect("child-added",decodebin_child_added,user_data)

def create_source_bin(index,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" %index
    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 beed created by the decodebin
    uri_decode_bin.connect("pad-added",cb_newpad,nbin)
    uri_decode_bin.connect("child-added",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

def find_number_of_clusters(mask):
  global all_confirms
  start_point = (0, 0)
  bottoms_counts = []
  thresh = cv2.threshold(mask, 100, 255, cv2.THRESH_BINARY)[1]
  erosion_kernel = np.ones((2,2),np.uint8)
  erosion = cv2.erode(thresh,erosion_kernel, iterations = 1)
  # Make 4 parts and check every
  for half in range(8):
    half_mask = erosion[:, half*64:(half+1) * 64]
    
    # Threshhold to binar
    contours = cv2.findContours(half_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]

    index = 1
    isolated_count = 0
    cluster_count = 0
    for cntr in contours:
      area = cv2.contourArea(cntr)
      convex_hull = cv2.convexHull(cntr)
      convex_hull_area = cv2.contourArea(convex_hull)

      if(convex_hull_area == 0):
        convex_hull_area = 1
      if(area == 0):
        area = 1
      name = "img_num_" + str(half) + ".jpg"
      cv2.imwrite(name, half_mask)
      # Find a box around the area and compare to area
      ratio = area / convex_hull_area
      # If area less then box it is not a noize
      if ratio < 0.90:
        cluster_count = cluster_count + 1
      # And if area close to box area it mostly noize
      else:
          isolated_count = isolated_count + 1

      index = index + 1

    # Append cluster area
    bottoms_counts.append(cluster_count)

  #bottoms_counts example - 1x1x1x2+2x1x1x2 - means that we got 1 bottom
  #bottoms_counts exampls2 - 1x1x2+2+2x1x1x1 - meants that we got 2 bottom
  #If we got more than 2 + it means we got 2 bottoms (or 3 if + connects 3s)
  confirms = []
  print(bottoms_counts)
  for part in range(1, len(bottoms_counts) - 1):
    this = bottoms_counts[part]
    last = bottoms_counts[part-1]

    if(this != last) and (this > 1) and (last > 1):
      confirms.append(min(this, last))
  if(bottoms_counts[0] > 1):
    confirms.append(bottoms_counts[0])
  if(bottoms_counts[7] > 1):
    confirms.append(bottoms_counts[7])
  if len(confirms) > 1:
    if(max(confirms) % 2 == 0):
      all_confirms[2] += 1
    else:
      all_confirms[3] += 1
  else:
    all_confirms[1] += 1

def osd_sink_pad_buffer_probe(pad,info,u_data):
    global total_bottoms
    global bottom_absence
    global bottom_counter
    global press_counter
    global hook_counter
    global all_confirms
    global table
    # print("Pad: " ,dir(pad))
    # print("Info: ", dir(info))
    obj_counter = {
        PGIE_CLASS_ID_BOTTOM:0,
        PGIE_CLASS_ID_HOOK:0,
        PGIE_CLASS_ID_PRESS_B:0
    }
    num_rects=0

    gst_buffer = info.get_buffer()
    #print("Buffer: ", dir(gst_buffer))
    if not gst_buffer:
        print("Unable to get GstBuffer ")
        return
    batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
    l_frame = batch_meta.frame_meta_list
    while l_frame is not None:
        try:
            frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
            # print("Base_meta: ", dir(frame_meta.base_meta))
            # print("Buf_pts: ", frame_meta.buf_pts)
        except StopIteration:
            break

        num_rects = frame_meta.num_obj_meta
        l_user = frame_meta.frame_user_meta_list
        l_obj=frame_meta.obj_meta_list
        while l_obj is not None:
            try:
                obj_meta=pyds.NvDsObjectMeta.cast(l_obj.data)
            except StopIteration:
                break
            obj_counter[obj_meta.class_id] += 1
            obj_meta.rect_params.border_color.set(0.0, 0.0, 1.0, 0.0)
            try: 
                l_obj=l_obj.next
                #print(dir(batch_meta))
                frame = pyds.get_nvds_buf_surface(hash(gst_buffer), frame_meta.batch_id)
            except StopIteration:
                break
        while l_user is not None:
            try:
                # Note that l_user.data needs a cast to pyds.NvDsUserMeta
                # The casting is done by pyds.NvDsUserMeta.cast()
                # The casting also keeps ownership of the underlying memory
                # in the C code, so the Python garbage collector will leave
                # it alone.
                seg_user_meta = pyds.NvDsUserMeta.cast(l_user.data)
            except StopIteration:
                break
            if seg_user_meta and seg_user_meta.base_meta.meta_type == \
                    pyds.NVDSINFER_SEGMENTATION_META:
                try:
                    # Note that seg_user_meta.user_meta_data needs a cast to
                    # pyds.NvDsInferSegmentationMeta
                    # The casting is done by pyds.NvDsInferSegmentationMeta.cast()
                    # The casting also keeps ownership of the underlying memory
                    # in the C code, so the Python garbage collector will leave
                    # it alone.
                    segmeta = pyds.NvDsInferSegmentationMeta.cast(seg_user_meta.user_meta_data)
                except StopIteration:
                    break
                # Retrieve mask data in the numpy format from segmeta
                # Note that pyds.get_segmentation_masks() expects object of
                # type NvDsInferSegmentationMeta
                masks = pyds.get_segmentation_masks(segmeta)
                masks = np.array(masks, copy=True, order='C')
                # map the obtained masks to colors of 2 classes.
                frame_image = map_mask_as_display_bgr(masks).astype(np.uint8)
                # gray_image = cv2.cvtColor(frame_image, cv2.COLOR_BGR2GRAY).astype(np.uint8)
                find_number_of_clusters(frame_image)
            try:
                l_user = l_user.next
            except StopIteration:
                break
        print(all_confirms)
        if(obj_counter[PGIE_CLASS_ID_BOTTOM] == 0):
            bottom_absence += 1
        else:
            if(bottom_absence > 300) and (press_counter > 300):
                total_bottoms += max(all_confirms, key=all_confirms.get)
                table.open(mode=dbf.READ_WRITE)
                table.append(dbf.Date(1979, 9,13), total_bottoms)
                print ('records added:')
                for record in table:
                    print (record)
                    print ('-----')
                table.close
                all_confirms = {1: 0, 2: 0, 3:0}
                press_counter = 0
                bottom_absence = 0
            if bottom_counter > 100:
                bottom_absence = 0
            bottom_counter += 1
        
        if(obj_counter[PGIE_CLASS_ID_PRESS_B] > 0):
            press_counter += 1
        
        

        

        display_meta=pyds.nvds_acquire_display_meta_from_pool(batch_meta)
        display_meta.num_labels = 1
        py_nvosd_text_params = display_meta.text_params[0]

        fps = fps_stream.get_fps()

        py_nvosd_text_params.display_text = "FPS={} Bottoms_at_press={} Total_Bottoms={} Press={}".format(fps, max(all_confirms, key=all_confirms.get), total_bottoms, obj_counter[PGIE_CLASS_ID_PRESS_B])

        py_nvosd_text_params.x_offset = 10
        py_nvosd_text_params.y_offset = 12

        py_nvosd_text_params.font_params.font_name = "Serif"
        py_nvosd_text_params.font_params.font_size = 10

        py_nvosd_text_params.font_params.font_color.set(1.0, 1.0, 1.0, 1.0)

        py_nvosd_text_params.set_bg_clr = 1

        py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0)
        pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
        try:
            l_frame=l_frame.next
        except StopIteration:
            break
			
    return Gst.PadProbeReturn.OK	


def main(opt):
    global fps_stream
    global table

    table = dbf.Table('Отчёт Hermes.dbf', 'Date D; Bottoms N(3,0);')

    print('db definition created with field names:', table.field_names)


    # Check input arguments
    fps_stream=GETFPS(0)
    # Standard GStreamer initialization
    GObject.threads_init()
    Gst.init(None)

    # Create gstreamer elements
    print("Creating Pipeline \n ")
    pipeline = Gst.Pipeline()

    if not pipeline:
        sys.stderr.write(" Unable to create Pipeline \n")

    streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
    if not streammux:
        sys.stderr.write(" Unable to create NvStreamMux \n")

    pipeline.add(streammux)
    
    
    uri_name = "rtsp://" + opt.log + ":" + opt.pwd + "@" + opt.ip + ":" + opt.port + "/snl/live/main"
    if uri_name.find("file://") == 0 :
        is_live = True
    source_bin=create_source_bin(0, uri_name)
    if not source_bin:
        sys.stderr.write("Unable to create source bin \n")
    pipeline.add(source_bin)
    padname="sink_%u" %0
    sinkpad= streammux.get_request_pad(padname) 
    if not sinkpad:
        sys.stderr.write("Unable to create sink pad bin \n")
    srcpad=source_bin.get_static_pad("src")
    if not srcpad:
        sys.stderr.write("Unable to create src pad bin \n")
    srcpad.link(sinkpad)
    # Countinue init elements
    pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
    if not pgie:
        sys.stderr.write(" Unable to create pgie \n")

    dsexample = Gst.ElementFactory.make("dsexample", "cropper")
    if not dsexample:
        sys.stderr.write(" Unable to create dsexample \n")

    sgie = Gst.ElementFactory.make("nvinfer", "secondary1-nvinference-engine")
    if not sgie:
        sys.stderr.write(" Unable to make sgie1 \n")

    nvsegvisual = Gst.ElementFactory.make("nvsegvisual", "nvsegvisual")
    if not nvsegvisual:
        sys.stderr.write("Unable to create nvsegvisual\n")

    nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "convertor")
    if not nvvidconv:
        sys.stderr.write(" Unable to create nvvidconv \n")

    nvvidconv2 = Gst.ElementFactory.make("nvvideoconvert", "convertor2")
    if not nvvidconv2:
        sys.stderr.write(" Unable to create nvvidconv2 \n")

    nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay")

    if not nvosd:
        sys.stderr.write(" Unable to create nvosd \n")

    if is_aarch64():
        transform = Gst.ElementFactory.make("nvegltransform", "nvegl-transform")

    print("Creating EGLSink \n")
    sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
    if not sink:
        sys.stderr.write(" Unable to create egl sink \n")

    print("Playing cam %s ", uri_name)

    # Setting options on elements
    streammux.set_property('width', 1920)
    streammux.set_property('height', 1080)
    streammux.set_property('batch-size', 1)
    streammux.set_property('batched-push-timeout', 4000000)
    nvvidconv2.set_property('nvbuf-memory-type', 0)
    pgie.set_property('config-file-path', "config_yolo.txt")
    dsexample.set_property('full-frame', 0)
    dsexample.set_property('blur_objects', True)
    sgie.set_property('config-file-path', 'config_unet.txt')
    nvsegvisual.set_property('width', 512)
    nvsegvisual.set_property('height', 512)
    sink.set_property('sync', 0)
    # Adding elements
    print("Adding elements to Pipeline \n")

    

    pipeline.add(pgie)
    pipeline.add(sgie)
    pipeline.add(nvvidconv2)
    pipeline.add(nvsegvisual)
    pipeline.add(dsexample)
    pipeline.add(nvvidconv)
    pipeline.add(nvosd)
    pipeline.add(sink)
    if is_aarch64():
        pipeline.add(transform)

    # Linking elements
    print("Linking elements in the Pipeline \n")
    

    streammux.link(pgie)
    pgie.link(nvvidconv2)
    nvvidconv2.link(dsexample)
    dsexample.link(sgie)
    sgie.link(nvsegvisual)
    nvsegvisual.link(nvvidconv)
    nvvidconv.link(nvosd)
    if is_aarch64():
        nvosd.link(transform)
        transform.link(sink)
    else:
        nvosd.link(sink)

    # create an event loop and feed gstreamer bus mesages to it
    loop = GObject.MainLoop()
    bus = pipeline.get_bus()
    bus.add_signal_watch()
    bus.connect ("message", bus_call, loop)

    
    osdsinkpad = nvosd.get_static_pad("sink")
    if not osdsinkpad:
        sys.stderr.write(" Unable to get sink pad of nvosd \n")
    
    osdsinkpad.add_probe(Gst.PadProbeType.BUFFER, osd_sink_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:
        pass
    # cleanup
    pipeline.set_state(Gst.State.NULL)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('--log', type = str, default = 'admin')
    parser.add_argument('--pwd', type = str, default = '-')
    parser.add_argument('--ip', type = str, default = '-')
    parser.add_argument('--port', type = str, default = '-')
  

    opt = parser.parse_args()
    print(opt)
    main(opt)

I use dsexample script to crop the detected object and push it to segmentation.
After 10000-11000 frames it executes osd_sink_pad_buffer_probe() 6 times and freezes on ±30 minutes.
I understood it because i wrote some prints in dsexample and some in osd_sink_pad_buffer_probe() and I seen that before freeze only osd_sink_pad_buffer_probe printed something out.
Before freeze jtop shows that all CPU got 0%

Where is the problem? Please give me an advice.

Please debug by yourself or provide enough information to show the problem is caused by deepstream.

Well, I left program working on night and seen that no memory left. System monitor showed that script took 29GB.

Here is par that I changed in dsexample.

static GstFlowReturn
blur_objects (GstDsExample * dsexample, gint idx,
    NvOSD_RectParams * crop_rect_params, cv::Mat in_mat)
{
  cv::Rect crop_rect;

  if ((crop_rect_params->width == 0) || (crop_rect_params->height == 0)) {
    GST_ELEMENT_ERROR (dsexample, STREAM, FAILED,
        ("%s:crop_rect_params dimensions are zero",__func__), (NULL));
    return GST_FLOW_ERROR;
  }
  /* rectangle for cropped objects */
    int longest;
    int width;
    int height;
    int left = (crop_rect_params->width / 2) + crop_rect_params->left;  //Now it's center of bbox
    int top = (crop_rect_params->height / 2) + crop_rect_params->top; //Now it's center of bbox

    std::cout<<"+_+_+_+_+_+_+_+_+"<<std::endl;

    longest = (crop_rect_params->height > crop_rect_params->width) ? crop_rect_params->height : crop_rect_params->width;
    
    left = (left - (longest / 2) > 0) ? left - (longest / 2) : 0;
    top = (top -  (longest / 2) > 0) ? top - (longest / 2) : 0;
    width = (left + longest < in_mat.size().width) ? longest : in_mat.size().width - left;
    height = (top +  longest < in_mat.size().height) ? longest : in_mat.size().height - top;

    crop_rect = cv::Rect (left, top, width, height);

    cv::Mat bbox = in_mat.clone();
    bbox = bbox(crop_rect);
    cv::resize(bbox, bbox, cv::Size(in_mat.size().width, in_mat.size().height), 0, 0, cv::INTER_CUBIC);
    for (int i = in_mat.size().height - 1; i >= 0; i--){
      for (int j = in_mat.size().width - 1; j >= 0; j--){
        in_mat.at<float>(i, j) = bbox.at<float>(i, j);
      }
  }
    
    
  
  return GST_FLOW_OK;
}

Maybe memory leaks somewhere? I know that code isn’t good

I left debug more debug prints. And seen a strange thing: so in the first strings of osd_sink_pad_buffer_probe I left

print("start")

in the end

print("end");

In the blur_objects as you see I left som couts like

std::cout<<"+_+_+_+_+_+_+_+_+"<<std::endl;

So for the first time in printed out this:

+_+_+_+_+_+_+_+_+
1395, 1029
1920
918, 552, 955, 528
start
[1, 1, 1, 1, 1, 1, 1, 1]
{1: 3619, 2: 0, 3: 0}
end
+_+_+_+_+_+_+_+_+
706, 392
1920
463, 149, 486, 486
start
[1, 1, 1, 1, 1, 1, 1, 1]
{1: 3620, 2: 0, 3: 0}
end
+_+_+_+_+_+_+_+_+
706, 392
1920
464, 150, 485, 485
start
[1, 0, 0, 0, 0, 0, 1, 1]
{1: 3621, 2: 0, 3: 0}
end

And it’s how it must work

But after some time it printed:

start
[1, 1, 1, 1, 1, 1, 1, 1]
{1: 3625, 2: 0, 3: 0}
end
start //Why is it started before dsexample worked?
+_+_+_+_+_+_+_+_+
707, 392
1920
465, 150, 485, 485
[1, 1, 1, 1, 1, 1, 1, 1]
{1: 3626, 2: 0, 3: 0}
end
+_+_+_+_+_+_+_+_+
707, 392
1920
465, 150, 485, 485
+_+_+_+_+_+_+_+_+ //Why dsexample worked 2 times?
707, 392
1920
465, 150, 485, 485
start
[1, 1, 1, 1, 1, 1, 1, 1]
{1: 3627, 2: 0, 3: 0}
end
start //One more time osd_sink_pad_buffer_probe() worked before dsexample worked
+_+_+_+_+_+_+_+_+
707, 392
1920
465, 150, 485, 485
[1, 1, 1, 1, 1, 1, 1, 1]
{1: 3628, 2: 0, 3: 0}
end

So after 10000-11000 frame it prints

[1, 1, 1, 1, 1, 1, 1, 0]
{1: 11357, 2: 0, 3: 0}
[1, 1, 1, 1, 1, 1, 1, 0]
{1: 11358, 2: 0, 3: 0}
[1, 1, 1, 1, 1, 1, 1, 0]
{1: 11359, 2: 0, 3: 0}

And freezed on night. And memory was full (29/32)GB

Well, I changed powermodel to MAXN and freezes disappeared

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.