Please provide complete information as applicable to your setup.
• Hardware Platform (Jetson / GPU): GPU NVIDIA RTX 3050 6GB, Ubuntu 22.04 Docker
• DeepStream Version: 7.1
• JetPack Version (valid for Jetson only)
• TensorRT Version: v100300
• NVIDIA GPU Driver Version (valid for GPU only): 570.133.07
• Issue Type( questions, new requirements, bugs): Question
Hi everyone, I’m working on a license plate detection + recognition pipeline, run with gst-launch-1.0 command, using YOLO for detection model and LPRNet trained from TAO Toolkit for recognition. My problem is in my country, the license plate have both 1-line and 2-line type, depend on user customization. It make the LPR model works well on 1-line license plate, but in 2-line license plate, not so much (sometimes have output on screen, but all it wrong, or no output, only lp). How can I handle this situation?
This is a gst-launch-1.0 command I’m using:
gst-launch-1.0 -v filesrc location=/opt/nvidia/deepstream/deepstream-7.1/lab/recorded_vms_data/converted1.mp4 ! \
qtdemux name=demux ! \
queue ! \
h264parse ! \
nvv4l2decoder ! \
queue ! \
nvvideoconvert ! \
"video/x-raw(memory:NVMM), format=NV12, width=1920, height=1080" ! \
mux.sink_0 nvstreammux name=mux batch-size=1 width=1920 height=1080 live-source=0 ! \
queue ! \
nvinferserver config-file-path=config_infer_primary_person_vehicle_detector.txt ! \
queue ! \
nvinferserver config-file-path=config_infer_secondary_license_plate_detector.txt ! \
queue ! \
nvinferserver config-file-path=config_infer_tertiary_license_plate_recognizer.txt ! \
queue ! \
nvdsosd ! \
nvvideoconvert ! \
"video/x-raw, format=RGBA" ! \
nveglglessink sync=false
config_infer_primary_person_vehicle_detector.txt:
infer_config {
unique_id: 1
gpu_ids: [0]
max_batch_size: 1
backend {
inputs: [{
name: "images"
dims: [3, 640, 640] # Fixed input size
}]
outputs: [{name: "output0"}] # Match the output layer name
triton {
grpc {
url: "localhost:8001" # Địa chỉ Triton Server (gRPC)
enable_cuda_buffer_sharing: false
}
model_name: "person_vehicle_detector"
version: -1
}
}
preprocess {
network_format: IMAGE_FORMAT_RGB
tensor_order: TENSOR_ORDER_LINEAR
tensor_name: "images"
maintain_aspect_ratio: 1
frame_scaling_hw: FRAME_SCALING_HW_DEFAULT
frame_scaling_filter: 2
normalize {
scale_factor: 0.003921569 # 1/255
channel_offsets: [0.0, 0.0, 0.0]
}
}
postprocess {
labelfile_path: "/opt/nvidia/deepstream/deepstream-7.1/lab/triton_repo/person_vehicle_detector/1/labels.txt"
detection {
num_detected_classes: 5
custom_parse_bbox_func: "NvDsInferParseYolo"
nms {
confidence_threshold: 0.3
iou_threshold: 0.5
topk: 300
}
}
}
custom_lib {
path: "/opt/nvidia/deepstream/deepstream-7.1/lib/libnvdsinfer_custom_impl_Yolo.so"
}
extra {
copy_input_to_host_buffers: 0
output_buffer_pool_size: 16
}
}
config_infer_secondary_license_plate_detector.txt:
infer_config {
unique_id: 2
gpu_ids: [0]
max_batch_size: 1
backend {
inputs: [{
name: "images"
dims: [3, 640, 640]
}]
outputs: [{name: "output0"}]
triton {
grpc {
url: "localhost:8001"
enable_cuda_buffer_sharing: false
}
model_name: "license_plate_detector"
version: -1
}
}
preprocess {
network_format: IMAGE_FORMAT_RGB
tensor_order: TENSOR_ORDER_LINEAR
tensor_name: "images"
maintain_aspect_ratio: 0
symmetric_padding: 0
frame_scaling_hw: FRAME_SCALING_HW_DEFAULT
frame_scaling_filter: 2
normalize {
scale_factor: 0.003921569
channel_offsets: [0.0, 0.0, 0.0]
}
}
postprocess {
labelfile_path: "/opt/nvidia/deepstream/deepstream-7.1/lab/triton_repo/license_plate_detector/1/labels.txt"
detection {
num_detected_classes: 1
custom_parse_bbox_func: "NvDsInferParseYolo"
nms {
confidence_threshold: 0.3
iou_threshold: 0.5
topk: 300
}
}
}
custom_lib {
path: "/opt/nvidia/deepstream/deepstream-7.1/lib/libnvdsinfer_custom_impl_Yolo.so"
}
extra {
copy_input_to_host_buffers: 0
output_buffer_pool_size: 16
}
}
input_control {
process_mode: PROCESS_MODE_CLIP_OBJECTS
operate_on_gie_id: 1
operate_on_class_ids: [1, 2, 4]
}
config_infer_tertiary_license_plate_recognizer.txt:
infer_config {
unique_id: 3
gpu_ids: [0]
max_batch_size: 1
backend {
inputs: [{
name: "image_input"
dims: [3, 48, 120]
}]
outputs: [{
name: "tf_op_layer_ArgMax"
}, {
name: "tf_op_layer_Max"
}]
triton {
model_name: "license_plate_recognizer"
version: -1
grpc {
url: "localhost:8001"
enable_cuda_buffer_sharing: false
}
}
}
preprocess {
network_format: IMAGE_FORMAT_RGB
tensor_order: TENSOR_ORDER_LINEAR
tensor_name: "image_input"
maintain_aspect_ratio: 0
symmetric_padding: 0
normalize {
scale_factor: 0.00392156862745098
channel_offsets: [0, 0, 0]
}
}
postprocess {
classification {
threshold: 0.7
custom_parse_classifier_func: "NvDsInferParseCustomNVPlate"
}
}
custom_lib {
path: "/opt/nvidia/deepstream/deepstream-7.1/lib/libnvdsinfer_custom_impl_lpr.so"
}
extra {
copy_input_to_host_buffers: 1
output_buffer_pool_size: 16
}
}
input_control {
process_mode: PROCESS_MODE_CLIP_OBJECTS
operate_on_gie_id: 2
operate_on_class_ids: [0]
}
With YOLO model, I’m using this github repo for custom parser: GitHub - marcoslucianops/DeepStream-Yolo: NVIDIA DeepStream SDK 7.1 / 7.0 / 6.4 / 6.3 / 6.2 / 6.1.1 / 6.1 / 6.0.1 / 6.0 / 5.1 implementation for YOLO models
With LPRNet model, I’m using the custom parser provided by NVIDIA and make some changes:
nvinfer_custom_lpr_parser.cpp
/*
* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <string>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <assert.h>
#include <locale>
#include <codecvt>
#include "nvdsinfer.h"
#include <fstream>
#include <cstdint>
using namespace std;
using std::string;
using std::vector;
static bool dict_ready = false;
std::vector<string> dict_table;
extern "C"
{
bool NvDsInferParseCustomNVPlate(std::vector<NvDsInferLayerInfo> const &outputLayersInfo,
NvDsInferNetworkInfo const &networkInfo, float classifierThreshold,
std::vector<NvDsInferAttribute> &attrList, std::string &attrString)
{
int64_t *outputStrBuffer = NULL;
float *outputConfBuffer = NULL;
NvDsInferAttribute LPR_attr;
int seq_len = 0;
// Get list
vector<int64_t> str_idxes;
int64_t prev = 100;
// For confidence
double bank_softmax_max[16] = {0.0};
unsigned int valid_bank_count = 0;
bool do_softmax = false;
ifstream fdict;
setlocale(LC_CTYPE, "");
if (!dict_ready)
{
fdict.open("/opt/nvidia/deepstream/deepstream-7.1/lab/custom_parser_lpr/dict.txt");
if (!fdict.is_open())
{
cout << "open dictionary file failed." << endl;
return false;
}
while (!fdict.eof())
{
string strLineAnsi;
if (getline(fdict, strLineAnsi))
{
dict_table.push_back(strLineAnsi);
}
}
dict_ready = true;
fdict.close();
}
int layer_size = outputLayersInfo.size();
LPR_attr.attributeConfidence = 1.0;
seq_len = networkInfo.width / 4;
for (int li = 0; li < layer_size; li++)
{
if (!outputLayersInfo[li].isInput)
{
if (outputLayersInfo[li].dataType == 0) // FLOAT (FP32)
{
if (!outputConfBuffer)
outputConfBuffer = static_cast<float *>(outputLayersInfo[li].buffer);
}
else if (outputLayersInfo[li].dataType == 4) // Changed to 4 for INT64
{
if (!outputStrBuffer)
outputStrBuffer = static_cast<int64_t *>(outputLayersInfo[li].buffer);
}
}
}
// Safety check for NULL buffers
if (outputStrBuffer == NULL || outputConfBuffer == NULL)
{
std::cerr << "Error: output buffers not found" << std::endl;
return false;
}
for (int seq_id = 0; seq_id < seq_len; seq_id++)
{
do_softmax = false;
int64_t curr_data = outputStrBuffer[seq_id];
if (curr_data < 0 || curr_data > static_cast<int64_t>(dict_table.size()))
{
continue;
}
if (seq_id == 0)
{
prev = curr_data;
str_idxes.push_back(curr_data);
if (curr_data != static_cast<int64_t>(dict_table.size()))
do_softmax = true;
}
else
{
if (curr_data != prev)
{
str_idxes.push_back(curr_data);
if (static_cast<size_t>(curr_data) != dict_table.size())
do_softmax = true;
}
prev = curr_data;
}
// Do softmax
if (do_softmax)
{
do_softmax = false;
bank_softmax_max[valid_bank_count] = outputConfBuffer[seq_id];
valid_bank_count++;
}
}
attrString = "";
for (unsigned int id = 0; id < str_idxes.size(); id++)
{
if (static_cast<size_t>(str_idxes[id]) < dict_table.size())
{
attrString += dict_table[str_idxes[id]];
}
}
// Ignore the short string, it may be wrong plate string
if (valid_bank_count >= 3)
{
LPR_attr.attributeIndex = 0;
LPR_attr.attributeValue = 1;
LPR_attr.attributeLabel = strdup(attrString.c_str());
for (unsigned int count = 0; count < valid_bank_count; count++)
{
LPR_attr.attributeConfidence *= bank_softmax_max[count];
}
attrList.push_back(LPR_attr);
}
return true;
}
} // end of extern "C"
Makefile:
################################################################################
# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
################################################################################
CC:= g++
CFLAGS:= -Wall -Werror -std=c++11 -shared -fPIC -Wno-error=deprecated-declarations
CFLAGS+= -I/opt/nvidia/deepstream/deepstream/sources/includes
LIBS:= -lnvinfer
LFLAGS:= -Wl,--start-group $(LIBS) -Wl,--end-group
SRCFILES:= nvinfer_custom_lpr_parser.cpp
TARGET_LIB:= libnvdsinfer_custom_impl_lpr.so
all: $(TARGET_LIB)
$(TARGET_LIB) : $(SRCFILES)
$(CC) -o $@ $^ $(CFLAGS) $(LFLAGS)
clean:
rm -rf $(TARGET_LIB)
The compiled .so file, I copied to path /opt/nvidia/deepstream/deepstream-7.1/lib
Some example images about the LPR output:




