Encoded object image with nvds_obj_enc_process dimension is wrong by one pixel

Please provide complete information as applicable to your setup.

Hardware Platform: dGPU
**• DeepStream Version: 7.0 **
• TensorRT Version: 8.6.1
• NVIDIA GPU Driver Version: 550
• Issue Type: question

Adding object meta

    @staticmethod
    def add_obj_meta_to_frame(frame_object, batch_meta, frame_meta, unique_id, allow_downstream_infer,visualize = True, parent = None):
        """ Inserts an object into the metadata """
        # this is a good place to insert objects into the metadata.
        # Here's an example of inserting a single object.
        obj_meta = pyds.nvds_acquire_obj_meta_from_pool(batch_meta)
        # Set bbox properties. These are in input resolution.
        rect_params = obj_meta.rect_params
        #print(frame_object)
        rect_params.left =  max(0,int(frame_object["bbox"][0]))
        rect_params.top =  max(0,int(frame_object["bbox"][1]))
        rect_params.width = int(frame_object["bbox"][2] -  frame_object["bbox"][0])
        rect_params.height = int(frame_object["bbox"][3] - frame_object["bbox"][1])

        #print("Object rect param:",rect_params.left, rect_params.top, rect_params.width , rect_params.height)
        if not visualize: 
            rect_params.has_bg_color = 0
            rect_params.border_width = 0
        else: 
            # Semi-transparent yellow backgroud
            rect_params.has_bg_color = 0
            rect_params.bg_color.set(1, 1, 0, 0.4)

            # Red border of width 3
            rect_params.border_width = 3
            rect_params.border_color.set(0, 1, 0, 1)

        # Set object info including class, detection confidence, etc.
        obj_meta.confidence = frame_object["score"]
        obj_meta.class_id = 99
        
        # There is no tracking ID upon detection. The tracker will
        # assign an ID.
        obj_meta.object_id = CONFIG["pipeline"]["untracked_object_id"]
        if allow_downstream_infer:
            obj_meta.unique_component_id = unique_id
        #print(frame_object)
        if "landmark_data" in frame_object: 
            ProbesHandler.modify_mask_data(obj_meta, frame_object["landmark_data"])
       
        pyds.nvds_add_obj_meta_to_frame(frame_meta, obj_meta, parent)
        
        return obj_meta

Encode/get object function:

    def encode_object_or_frame(ctx_handle,gst_buffer,frame_meta,object_meta,file_path,frame_mode = False,scaleImg = CONFIG["saving_format"]["crop_object_scale"],scaledWidth = CONFIG["saving_format"]["crop_object_scale_width"], scaledHeight = CONFIG["saving_format"]["crop_object_scale_height"] ,  saveImg = True, calcEncodeTime = False, attachUsrMeta = False): 
        frame_data = pyds.NvDsObjEncUsrArgs()
        if frame_mode: 
            frame_data.isFrame = 1
        else: 
            frame_data.isFrame = 0
        frame_data.saveImg = saveImg
        if saveImg: 
            frame_data.fileNameImg = file_path
        frame_data.attachUsrMeta = attachUsrMeta
        frame_data.scaleImg = scaleImg
        frame_data.scaledWidth = scaledWidth
        frame_data.scaledHeight = scaledHeight
        frame_data.quality = 95
        frame_data.calcEncodeTime = calcEncodeTime
        #Call nvds_obj_enc_process
        #print("Encoding object:")
        pyds.nvds_obj_enc_process(ctx_handle, frame_data, hash(gst_buffer), object_meta, frame_meta)


    def get_encoded_object_from_meta(obj_meta): 
        output = None
        l_user = obj_meta.obj_user_meta_list
        while l_user is not None:
            user_meta = pyds.NvDsUserMeta.cast(l_user.data)
            if user_meta.base_meta.meta_type == pyds.NvDsMetaType.NVDS_CROP_IMAGE_META:
                out_param = pyds.NvDsObjEncOutParams.cast(user_meta.user_meta_data)
                output = out_param.outBuffer()
                output = output.tobytes()

            try:
                l_user = l_user.next
            except StopIteration:
                break
        return output

Encode object image:

                ProbesHandler.encode_object_or_frame(u_data.obj_ctx_handle, gst_buffer,current_frame_meta, parent_meta,"nvds_object.jpg", 
                                                     frame_mode = False,saveImg=True,calcEncodeTime=False,attachUsrMeta=True)
                pyds.nvds_obj_enc_finish(u_data.obj_ctx_handle)

                object_encoded_image = ProbesHandler.get_encode`Preformatted text`d_object_from_meta(parent_meta)
                decoded_image = decode_jpeg_buffer(object_encoded_image)
                h, w = decoded_image.shape[:2]
                original_height, original_width = parent_meta.rect_params.height, parent_meta.rect_params.width
                print(f"original object: {original_width}x{original_height}")
                print(f"encoded object:{w}x{h}")

Some object image size:



original object: 90.0x50.0
encoded object:90x50

original object: 96.0x49.0
encoded object:96x50

original object: 97.0x50.0
encoded object:98x50

original object: 65.0x40.0
encoded object:66x40

As you can see, the image size of the encoded image is not consistant. I also crop image for inference in nvdspreprocess as seen below:

        NvBufSurfaceParams *surf_params = &surface->surfaceList[frame_meta->batch_id];
        int frame_height = static_cast<int>(surf_params->height);
        int frame_width = static_cast<int>(surf_params->width);
        auto metaEnd = std::chrono::high_resolution_clock::now();
        meta_access_time += std::chrono::duration<double, std::milli>(metaEnd - metaStart).count();

        // 2. Create cv::Mat and Color Conversion
        auto colorStart = std::chrono::high_resolution_clock::now();
        cv::Mat input_frame(surf_params->height, surf_params->width, CV_8UC4, surf_params->dataPtr, surf_params->pitch);

        cv::Mat rgb_frame;
        cv::cvtColor(input_frame, rgb_frame, cv::COLOR_RGBA2RGB);
        auto colorEnd = std::chrono::high_resolution_clock::now();
        color_convert_time += std::chrono::duration<double, std::milli>(colorEnd - colorStart).count();

        NvDsObjectMeta *obj_meta = (NvDsObjectMeta *)batch->units[i].roi_meta.object_meta;
        float x = obj_meta->rect_params.left;
        float y = obj_meta->rect_params.top;
        float object_width = obj_meta->rect_params.width;
        float object_height = obj_meta->rect_params.height;
        // std::cout << "object top left: " << x << "x" << y<< std::endl;
        // std::cout << "object: " << object_width << "x" << object_height<< std::endl;
        int x_start = static_cast<int>(std::round(x));
        int y_start = static_cast<int>(std::round(y));
        int x_end = std::min(rgb_frame.cols, static_cast<int>(std::round(x + obj_meta->rect_params.width)));
        int y_end = std::min(rgb_frame.rows, static_cast<int>(std::round(y + obj_meta->rect_params.height)));
        int crop_w = x_end - x_start;
        int crop_h = y_end - y_start;

         // Crop the image
        cv::Mat cropped_image;
        if (crop_w > 0 && crop_h > 0) {
            cv::Rect roi(x_start, y_start, crop_w, crop_h);
            rgb_frame(roi).copyTo(cropped_image);
        }

The image i crop with this code doesnt align with the image cropped by nvds_obj_enc_proces.And since I save frame with nvds_obj_enc_process for re-inference later but there is a miss match in object height width of just one pixel, it can produce a very diffrent result. How can i avoid this.

The hardware encoder does not support odd width or height. The width and height should be even. You can change the crop width and height to even values before sending to your “re-inference”

Thank you, never thought of it. Im gonna test it out now.

Does object top left also need to be even?

No. “top” and “left” can be any value inside the image. You need to make sure the “right” and “bottom” are also inside the image too.