模型集成到Deepstream里后为何识别效果变差以及如何从Deepstream Infer Plugin里导出预处理过的用于模型推理的图片数据并使用TensorRT进行推理测试

13 篇文章 3 订阅
9 篇文章 1 订阅

      在将模型集成到Deepstream Infer Plugin过程中可能会碰到这样那样的问题,其中一个困扰人的问题就是一个模型集成到Deepstream Infer Plugin后,模型推理时的效果下降,使用onnx格式导出模型使用TensorRT解析生成engine后推理精度下降这个问题我报过case但是一直没有得到解决,这里说的是另一个方式使用模型时遇到的问题,就是模型使用TensorRT的API实现后集成到Deepstream Infer Plugin里比不使用Deepstream而是直接使用python调用原始模型或者使用C++调用TensorRT API实现的模型的效果要差得不少,两种方式使用同样的视频或图片和同样的模型engine文件,模型在Deepstream Infer Plugin里的表现要差,经过很多次的调查和实验,可以确定这个问题和模型本身的基于TensorRT API实现没有关系,和数据预处理也没有关系,最后发现这个问题的根本原因在Deepstream的gstnvinfer_meta_utils.cpp里处理不正确,这个bug即使在最新版的Deepstream5.1中都仍然存在:

void

attach_metadata_detector (GstNvInfer * nvinfer, GstMiniObject * tensor_out_object,

    GstNvInferFrame & frame, NvDsInferDetectionOutput & detection_output, float segmentationThreshold)

{

   ...

    obj.left /= frame.scale_ratio_x;

    obj.top /= frame.scale_ratio_y;

    obj.width /= frame.scale_ratio_x;

    obj.height /= frame.scale_ratio_y;

   ...

 

   if (obj.top + obj.height >

        (frame.input_surf_params->height - filter_params.roiBottomOffset))

   continue;

   当把bbox缩放到原始图片下的坐标后检测到bbox坐标越界时,它不是做安全转换而是静默地把这个bbox丢掉了!这样处理是明显不正确的,按照一般做法应该是对越界的bbox坐标做安全转换,直接丢掉bbox显然会降低召回率,造成bbox触及边界的物体识别非常不稳定!造成的直接效果就是在Nano之类的板子上使用Deepstream Infer Plugin集成模型后进行视频识别的效果比单独使用python或者C++直接调用TensorRT API来使用模型对同一视频进行识别的效果要差不少。

   把上述代码改成这样就可以解决问题:

if (obj.top + obj.height >

        (frame.input_surf_params->height - filter_params.roiBottomOffset))

    obj.height = frame.input_surf_params->height - filter_params.roiBottomOffset - obj.top;

如果不想修改Deepstream的代码造成需要自己维护Deepstream特定版本,也可以在定制bbox解析接口函数里增加这个检查和转换,不过得根据缩放前后的图片的大小把代码写死,因为解析bbox的定制接口函数的入参里没有scale_ratio_x和scale_ratio_y,所以没法做到灵活,当然缩放后的尺寸大小参数是有的(networkInfo.height,networkInfo.width),缩放前的原始帧图片大小就只能使用配置文件方式读入了,即便这样显然还是不灵活,视频的图像尺寸有变化时都得记得修改这里,不然解析bbox的定制接口函数这里的检查和转换没起到对放大后坐标的提前检查把关作用的话,又会出现Deepstream把触及边界的bbox丢掉造成识别效果下降。

     这种问题大致可以分成三个方面排查。

     一是模型网络的实现本身是否有问题,例如用于推理的cuda stream要保持自始至终使用同一个,并且最好自己创建而不是使用default cuda stream,使用python或C++代码调用TensorRT API测试模型的效果确认模型的输出和官方原始版本的测试结果一样,这部分工作和你使用或实现的模型网络有关,所以也不好说太具体的步骤细节。

     排除了模型网络实现本身的问题之后,就是进行第二个方面的调查,就是比较Deepstream Infer Plugin里的图片数据预处理结果和你不使用DeepSstream而是直接使用python或C++调用TensorRT API测试模型时的图片数据预处理结果一样,可以把两者预处理后数据进行还原成图片用肉眼观察进行比较,这是个办法但不是很精确,比较好的办法就是把Deepstream Infer Plugin预处理过的用于推理的raw data导出成二进制文件,然后在你的不使用Deepstream直接调用TensorRT API的代码里把这个raw data从导出文件里直接以二进制方式读入并拷贝到用于推理的GPU buffer里去,然后做推理,如果得到的结果比Deepstream Infer Plugin里推理得到的结果好,那就已经说明了问题在Deepstream里某个地方或者更下层的TensorRT里,那是第三个方面的排查了,需要nvidia工程师去协助进行,因为context->enqueue()这个实现代码是TensorRT的核心部分,是不开源的。

     那么怎么导出Deepstream Infer Plugin预处理过的用于推理的图片数据呢?

在/opt/nvidia/deepstream/deepstream/sources/libs/nvdsinfer/nvdsinfer_context_impl.cpp里

NvDsInferStatus InferPreprocessor::transform(
    NvDsInferContextBatchInput& batchInput, void* devBuf,
    CudaStream& mainStream, CudaEvent* waitingEvent)
这个函数是完成最后的预处理(做通道转换和1/255.0归一化)的地方,可以在这个函数里靠最后地方加上下面的代码将已处理的数据还原成彩色图片:

    

        float* outPtr = (float*)devBuf;
        float* tf = new float[m_NetworkInputLayer.inferDims.numElements];
        cudaMemcpyAsync(tf, outPtr, m_NetworkInputLayer.inferDims.numElements*sizeof(float),cudaMemcpyDeviceToHost,*m_PreProcessStream);
        for (int p=0; p<m_NetworkInputLayer.inferDims.numElements; p++){
            tf[p] *= 255;
            if (tf[p] > 255) tf[p] = 255;
            if (tf[p] < 0) tf[p] = 0;
        }

        cv::Mat img(m_NetworkInfo.height, m_NetworkInfo.width,CV_8UC3,tf,m_NetworkInfo.width*3 );
        img.convertTo(img,CV_RGB2BGR);
        static int g_idx=0;
        cv::imwrite("input"+std::to_string(g_idx++)+".jpg",img);

代码也可以加在NvDsInferContextImpl::queueInputBatch(NvDsInferContextBatchInput &batchInput)里面在transform()被调用之后:

    ...
    assert(m_BackendContext && backendBuffer);
    assert(m_InferStream && m_InputConsumedEvent && m_InferCompleteEvent);
    
    float* outPtr = (float*)m_BindingBuffers[INPUT_LAYER_INDEX];
    int size = 3*m_NetworkInfo.height*m_NetworkInfo.width;
    float* tf = new float[size];
    cudaMemcpyAsync(tf, outPtr, size*sizeof(float), cudaMemcpyDeviceToHost,*m_InferStream);  
    for (int p=0; p< size; p++){
        tf[p] *= 255;
        if (tf[p] > 255) tf[p] = 255;
        if (tf[p] < 0) tf[p] = 0;
    }
     cv::Mat img(m_NetworkInfo.height, m_NetworkInfo.width, CV_8UC3, tf, m_NetworkInfo.width*3 );
     static g_idx = 0;
     cv::imwrite("input"+std::to_string(g_idx++)+".jpg",img);

使用下面的代码以导出二进制raw data到文件:

        float* outPtr = (float*)m_BindingBuffers[INPUT_LAYER_INDEX];

        int size = 3*m_NetworkInfo.height*m_NetworkInfo.width;

        float* tf2 = new float[size];

        cudaMemcpyAsync(tf2, outPtr, size*sizeof(float),cudaMemcpyDeviceToHost,*m_InferStream);

        std::ofstream of("input_raw.bin", std::ios::binary);

        of.write(reinterpret_cast<char *>(tf2), size*sizeof(float));

        of.close();

        delete tf2;

直接导入预处理过的二进制数据进行推理的主要代码:

    void* buffers[2];
    static float prob[BATCH_SIZE * OUTPUT_SIZE];
    ...
    const int inputIndex = engine->getBindingIndex(INPUT_BLOB_NAME);
    const int outputIndex = engine->getBindingIndex(OUTPUT_BLOB_NAME);
    cudaMalloc(&buffers[inputIndex], BATCH_SIZE * 3 * INPUT_H * INPUT_W * sizeof(float));
    cudaMalloc(&buffers[outputIndex], BATCH_SIZE * OUTPUT_SIZE * sizeof(float)));
    cudaStream_t stream;
    CUDA_CHECK(cudaStreamCreate(&stream));
    
    std::string raw_file = std::string("input_raw.bin");
    std::ifstream file(raw_file, std::ios::binary);
    if (!file.good()) {
          printf("read raw data file error!\n");
    }else {
          char *rawStream = nullptr;
          size_t size = 0;
          file.seekg(0, file.end);
          size = file.tellg();
          file.seekg(0, file.beg);
          rawStream = new char[size];
          assert(rawStream);
          file.read(rawStream, size);
          file.close();
          doInference(*context, stream, buffers,reinterpret_cast<float *>(rawStream),prob,BATCH_SIZE );
          ...
      }
    

     如果需要验证Deepstream Infer Plugin里的数据预处理和模型推理部分没问题,问题应该出现在后处理上,可在InferPostprocessor::copyBuffersToHostMemory()里导出模型推理后的output buffer里的数据,便于单独使用其他程序进行解析出bbox,然后和Deepstream在display plugin里展示的后处理效果进行比较:

NvDsInferStatus
InferPostprocessor::copyBuffersToHostMemory(NvDsInferBatch& batch, const std::shared_ptr<InferBatchBuffer>& buffer, CudaStream& mainStream)
{
    assert(m_AllLayerInfo.size());
    /* Queue the copy of output contents from device to host memory after the
     * infer completion event. */
    for (size_t i = 0; i < m_AllLayerInfo.size(); i++)
    {
        NvDsInferLayerInfo& info = m_AllLayerInfo[i];
        assert(info.inferDims.numElements > 0);

        if (!info.isInput)
        {
           
            memset(batch.m_HostBuffers[info.bindingIndex]->ptr(),0, getElementSize(info.dataType) * info.inferDims.numElements *
                        batch.m_BatchSize);
            RETURN_CUDA_ERR(
                cudaMemcpyAsync(batch.m_HostBuffers[info.bindingIndex]->ptr(),
                    batch.m_DeviceBuffers[info.bindingIndex],
                    getElementSize(info.dataType) * info.inferDims.numElements *
                        batch.m_BatchSize,
                    cudaMemcpyDeviceToHost, mainStream),
                "postprocessing cudaMemcpyAsync for output buffers failed");
            
            if(g_idx == 22 ){ //找出识别效果不好的帧对应的位置,修改全局变量g_idx的值
               cudaStreamSynchronize(mainStream);

               std::string filename =
                       "gie-" + std::to_string(m_UniqueID) +
                       "output-layer-index-" + std::to_string(i)+"-frame"+std::to_string(g_idx)+".bin";
               std::ofstream dump_file(filename, std::ios::binary);
               dump_file.write((char *)batch.m_HostBuffers[info.bindingIndex]->ptr(),
                       getElementSize(info.dataType) * info.inferDims.numElements *
                       batch.m_BatchSize);
           }
           
          ...

如果想把交由GPU进行数据预处理的部分改成由CPU处理,可以这样改:

    

    


    std::shared_ptr<CudaEvent> preprocWaitEvent = m_InputConsumedEvent;
    assert(m_Preprocessor && m_InputConsumedEvent);

    #define PREPRO_NOGPU
    #ifdef PREPRO_NOGPU
    CudaEvent* waitingEvent = preprocWaitEvent.get();
    if (waitingEvent)
    {
        
        RETURN_CUDA_ERR(
            cudaStreamWaitEvent(*(m_Preprocessor->m_PreProcessStream), *waitingEvent, 0),
            "Failed to make stream wait on event");
    }

    unsigned char* inputPtr = (unsigned char*)batchInput.inputFrames[0];
    int buf_size = m_NetworkInfo.height * m_NetworkInfo.width * 4;
    unsigned char* tf = new unsigned char[buf_size];
    cudaMemcpyAsync(tf, inputPtr, buf_size*sizeof(unsigned char), cudaMemcpyDeviceToHost, *m_InferStream); //*(m_Preprocessor->m_PreProcessStream));
    cv::Mat imgc4 = cv::Mat(m_NetworkInfo.height, m_NetworkInfo.width, CV_8UC4,
                   tf, m_NetworkInfo.width*4);

    cv::Mat img(m_NetworkInfo.height, m_NetworkInfo.width, CV_8UC3);
    #if (CV_MAJOR_VERSION >= 4)
    cv::cvtColor(imgc4, img, cv::COLOR_RGBA2RGB);
    #else
    cv::cvtColor(imgc4, img, CV_RGBA2RGB);
    #endif

    #if 0
    cv::Mat o_img(m_NetworkInfo.height, m_NetworkInfo.width, CV_8UC3);
    cv::cvtColor(img, o_img, CV_RGB2BGR);
    cv::imwrite("input.jpg",o_img);
    #endif

    int step = m_NetworkInfo.height * m_NetworkInfo.width;
    int size = 3 * step;
    float* data = new float[size];
    int i = 0;
    for (int row = 0; row < m_NetworkInfo.height; ++row) {
       uchar* pixels = img.data + row * img.step[0];
       for (int col = 0; col < m_NetworkInfo.width; ++col) {
           data[i] = pixels[0] / 255.0;
           data[i + step] = pixels[1] / 255.0;
           data[i + 2 * step] = pixels[2] / 255.0;
           pixels += 3;
           ++i;
       }
    }

    cudaMemcpyAsync(m_BindingBuffers[INPUT_LAYER_INDEX], data,
       size * sizeof(float), cudaMemcpyHostToDevice, *m_InferStream); //*(m_Preprocessor->m_PreProcessStream));
    delete data;
    delete tf;
    if (batchInput.returnInputFunc)
    {
        RETURN_CUDA_ERR(
            cudaStreamAddCallback(*(m_Preprocessor->m_PreProcessStream), returnInputCudaCallback,
                new NvDsInferReturnInputPair(
                    batchInput.returnInputFunc, batchInput.returnFuncData),
                0),
            "Failed to add cudaStream callback for returning input buffers");
    }
   
    
    #else
    RETURN_NVINFER_ERROR(m_Preprocessor->transform(batchInput,
                             m_BindingBuffers[INPUT_LAYER_INDEX],
                             *m_InferStream, preprocWaitEvent.get()),
        "Preprocessor transform input data failed.");
    #endif

deepstream是一种实时流式数据处理架,可以用来处理和保存图片。在deepstream中,保存图片通常涉及到使用合适的插件和配置来实现。 首先,需要确保已经安装了deepstream,并且配置了用于处理图像的相关插件。然后,在配置文件中,需要设置要保存图片的路径和格式。可以使用deepstream的插件来实现将图像保存到本地或者远程服务器。在配置文件中,可以指定保存图片的触发条件,比如在检测到特定对象时保存图片,或者定时保存图片。还可以设置保存图片的规则,比如保存最新的图片,或者保存满足一定条件的图片。 在运行deepstream时,当满足保存图片的条件时,deepstream会调用相关的插件来实现图片的保存。保存的图片会按照之前在配置文件中设置的路径和格式保存到指定的位置。 除了保存图片deepstream还可以处理实时的视频流,并且可以对视频流进行分析和检测。通过深度学习和机器学习算法,deepstream可以实现对象检测、人脸识别等功能。保存图片是其中的一部分功能,但是它对于监控、安防等应用场景非常重要。 总之,deepstream保存图片需要通过配置文件设置保存路径和格式,使用相关的插件实现保存功能。通过深度学习和机器学习算法,deepstream可以实现更多的图像和视频处理功能,为各种应用场景提供了强大的支持。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Arnold-FY-Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值