初始化模型
std::shared_ptr<nvinfer1::IExecutionContext> Instance::Init_Instance(const char* model_path, const string class_name_path)
{
//注册防止反序列化报错
nvinfer1::ILogger* gLogger = NULL;
initLibNvInferPlugins(gLogger, "");
TRTLogger logger;
ifstream fs(model_path, std::ios::binary);
std::vector<unsigned char> buffer(std::istreambuf_iterator<char>(fs), {});
fs.close();
std::shared_ptr<nvinfer1::IRuntime> runtime = make_nvshared(nvinfer1::createInferRuntime(logger));
//nvinfer1::IRuntime *runtime = nvinfer1::createInferRuntime(logger);
_engine = make_nvshared(runtime->deserializeCudaEngine((void*)buffer.data(), buffer.size()));
//_engine = runtime->deserializeCudaEngine((void*)buffer.data(), buffer.size());
Malloc_data();
class_names = get_name(class_name_path);
//初始化cuda流,不需要了,在类中已经作为成员变量初始化了
//cudaStream_t stream = nullptr;
checkRuntime(cudaStreamCreate(&stream));
//创建执行的上下文
auto execution_context = make_nvshared(_engine->createExecutionContext());
return execution_context;
}
这里可以看到我用了智能指针来分别定义_engine, _runtime, _context。并且我在头文件中定义了一个类来封装我的推理代码,包括初始化模型的步骤:
类封装
class Instance {
public:
Instance(float scale_):scale(scale_)
{
cout << "调用了构造函数" << endl;
}
~Instance() {
cout << "调用了析构函数" << endl;
};
float scale;
std::shared_ptr < nvinfer1::IExecutionContext > _context = nullptr;
//nvinfer1::IExecutionContext* _context = nullptr;
vector<string> class_names;
string total_output;
std::shared_ptr<nvinfer1::IExecutionContext> Init_Instance(const char* model_path, const string class_name_path);
//nvinfer1::IExecutionContext* Init_Instance(const char* model_path, const string class_name_path);
void Run_Instance(cv::Mat input);
void check_ptr();
void Free_Memory();
vector<string> get_name(string class_name_path);
void destory_engine();
private:
void Malloc_data();
cv::cuda::GpuMat ProcessImage(cv::cuda::GpuMat pre_input);
void PostcessImage(cv::Mat nums, cv::Mat boxes, cv::Mat scores, cv::Mat classes, cv::Mat masks);
//void Init_parameters();
int INPUT_SIZE = 1344;
int MIN_SIZE = 800;
int MAX_SIZE = 1333;
int MASK_SIZE = 28;
float CONFIDENCE = 0;
float MASK_THRESHOLD = 0.5;
int input_numel = 1;
int output_nums_numel = 1;
int output_boxes_numel = 1;
int output_scores_numel = 1;
int output_classes_numel = 1;
int output_masks_numel = 1;
cv::Mat INPUT;
cudaStream_t stream = nullptr;
std::shared_ptr<nvinfer1::ICudaEngine> _engine = nullptr;
std::shared_ptr<nvinfer1::IRuntime> _runtime = nullptr;
//nvinfer1::ICudaEngine* _engine = nullptr;
//nvinfer1::IRuntime* _runtime = nullptr;
//初始化输入和输出指针
//static float* input_host_data = nullptr;
float* input_device_data = nullptr;
Output_ptr output_host = { nullptr, nullptr, nullptr, nullptr, nullptr };
Output_ptr output_device = { nullptr, nullptr, nullptr, nullptr, nullptr };
};
在这里可以看到,我已经将_engine, _runtime, _context都定义在了类的成员变量当中,并且都用的智能指针shared_ptr的方式。(并且我也在类中封装了Malloc_data()和Free_malloc()函数,作用分别是为tensorRT推理时在host和device上为输入输出的指针分配存储空间,和在执行推理完毕后,将host和device上分配的指针指向的内存空间手动释放掉)。
主函数
第一次封装是将我的推理代码封装在类中,为了方便c#软件调用部署导出的dll,我们将进行二次封装,因此主函数的接口使用的是二次封装的:
#include "port.h"
int main()
{
cv::String folder = ;
std::vector<cv::String> paths;
cv::glob(folder, paths);
const char* model_path = ;
const string class_path = ;
Init_model(model_path, class_path);
for (int i = 0; i < paths.size(); i++)
{
char* y_output = NULL;
cv::Mat input = cv::imread(paths[i]);
cv::Mat bgr[3];
cv::split(input, bgr);
uchar* b_ptr = bgr[0].data;
uchar* g_ptr = bgr[1].data;
uchar* r_ptr = bgr[2].data;
auto time_start = std::chrono::system_clock::now();
Run_model(b_ptr, g_ptr, r_ptr, 800, 800, 3, y_output);
auto time_end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = time_end - time_start;
cout << y_output;
cout << "deep learning cost time : " << diff.count() * 1000 << "ms" << endl << endl;
}
Free_memory();
check_Ptr();
destory_trt();
system("pause");
return 0;
}
可以看到在上述代码经过一个for循环遍历完所有要检测的图像之后,在循环外部,程序即将结束之前,我们调用了Free_memory()来手动释放host和device指针指向的内存以及释放推理时创建的cuda流,并且通过Check_ptr()方法来判断指针是否被释放完成。最后我们通过destory_trt()接口来释放推理时创建的_engine, _runtime, _context。最后跑出的推理结果十分正确,代码跑的也很流畅没有bug,并且通过一个写了一个while死循环来跑也没有发现内存泄露。到这里我以为我已经大功告成了。
这是终端跑出来的结果,可以看到执行完了Free_memory()并且通过check_ptr()方法,我们可以判断指针已经释放完毕了。但是当我们经过system(“pause”)继续往下执行推出程序时,问题就来了:
是NvInferRuntime.h所报出的问题,显示我们可管理的指针将被执行两次释放,说人话就是我们的指针已经释放完了,但是现在又要执行那个指针的释放,电脑也不知道咋办了,只能抛出错误。于是乎,我便开始从头开始查询我的代码,看看到底哪里多释放了一次,后来突然发现,由于我使用了智能指针,智能指针在其完成调用之后,根据引用计数可以自动释放,而我的这些_engine, _context, _runtime,也都已经在类的成员变量中定义过了,在类中如果不是new出来的内存,其他的都会随着程序的执行结束而自动销毁(new出来的内存需要手动delete)。到这里我才恍然大悟,既然我用类来封装代码了,因此也不需要通过智能指针的方式来定义推理的变量了,因此我将这三个智能指针换成了普通指针,最后运行,没有问题!