目录
①导出模型出错:AttributeError: Can't get attribute 'SPPF' on
②链接出错:yolov5.obj : error LNK2001: unresolved external symbol cudaMalloc
③报错:error : cannot define dllimport entity
⑤程序中止抛出异常:Unhandled exception at 0x00007FF872941208 (ucrtbase.dll)
前言
opencv dnn部署yolov5成功后,终究还是得对TensorRT下手。没办法,它的诱惑太大了,是英伟达为自家GPU出的推理引擎,论在英伟达产品上的推理速度,他说第一,应该没人说第二。不过我也没试过,之前用tensorrtx跑起来过一次,只是推理图片,没有视频来做比较。此次部署准备对比一下,顺便深入了解一下tensorrt(或者tensorrtx)。
前置环境
opencv安装(opencv_contrib版):博客地址
cuda,VS安装:博客地址前半部分
yolov5正常使用的环境:博客地址前半部分
前置环境很重要,若还没有配好,还希望配好再跟着走,下面的步骤都假设前置环境已经配好了情况下进行的。在windows上我采用的是VS配好环境后,直接用VS编译运行,而不是用cmake的方式编译项目。
一,TensorRT下载安装
官方下载地址:https://developer.nvidia.com/nvidia-tensorrt-download
在下载时会要求注册一下英伟达,随便注册一下就行。然后选择合适的版本下载,下载时注意对应自己的cuda版本,建议下载GA稳定版。博主下载的时候,没有cuda11.6的TensorRT的版本,所以只能下载TensorRT 8.4EA尝鲜版。
下载完之后,其实安装的部分已做了大半,剩下的就是将bin,include,lib目录添加进环境变量就行了,不过我建议和cudnn一样,将lib,bin,include等文件夹和cuda合并最好,就cuda的目录添加进环境变量就行。
后续再移动一下,将TensorRT的lib移动到cuda的lib\x64中,TensorRT就安装成功了,不放心可以搜一下,运行sample例子的教程,这里就不再赘述。
二,tensorrtx正常windows部署,图片推理
王鑫宇大佬的tensorrtx地址
tensorrtx-master版本——yolov5-6.0、6.1,tensorrtx-yolov5-5.0——yolov5-5.0,目前还没有出tensorrtx的yolov5-6.0的稳定版,但是,我试了一下,当前的master版本是可以用来部署yolov5-6.0版本的,此次我用来部署的版本是yolov5-6.1。
2.1 VS配置(这一步骤二和三通用)
①新建VS项目,选择空项目,点击next
②输入项目名称,并选择保存项目的文件夹路径,然后点击create创建VS项目
③将下载的tensorrttx中的yolov5文件夹中所有文件,复制到项目当中 ④添加.c文件和.h文件到VS项目中 ,鼠标右击source file选择添加已有项目。ctrl+鼠标右键可一次选择多个进行添加。
④-1,添加完后的目录结构
⑤修改属性,其实正常来说,添加了环境变量,项目对在环境变量中的库,应该不会报错,但有时不知道为什么链接不上。这时需要在VS项目中添加,引用库报错的库。运行tensorrtx主要需要的是cuda,cudnn,TensorRT,opencv的include,lib,bin文件目录。缺什么,就在属性中添加什么,bin文件好像是自动调用的,暂时还没遇到添加bin文件目录的问题。
⑤-1,我在环境变量中加的路径都失效了,需要将cuda,cudnn,TensorRT,opencv的全部加入进去,但我将cuda,cudnn,TensorRT融合到cuda当中。所以只导入了cuda和opencv的,我添加的路径如下,供大家参考寻找自己的路径。(我的opencv是和opencv_contrib一译过的,build是编译之后存放编译结果的文件夹,没编译过的话,没有install)
⑥添加lib到链接器,这个不是很懂,为什么导入了lib文件夹,但是运行的时候,调用不到lib,需要再连接一下。obj报错的往往都是没有链接到lib的问题,我将常报错的lib文件放在下面,(opencv后缀要写自己的版本,我的版本是4.5.5,所以是455)。
这里我试了一下,我需要链接的就只有opencv的lib和nvinfer.lib就能正常运行
⑦添加预处理,但不添加的话,就运行不起来,是cmakelist的一个语句,运行到此处VS的配置就完成了
add_definitions(-DAPI_EXPORTS)
2.2 导出wts模型
①将tensorrttx的yolov5文件夹中的gen_wts.py复制到自己yolov5训练使用的环境中,。
②,终端进入环境,运行gen_wts.py文件,生成wts模型。注意记得导自己到出的是n/s/m/l/x/n6/s6/m6/l6/x6中的那一种,后面运行需要指定是哪一种模型。
#python gen_wts.py文件路径 -w pt权重文件路径
python gen_wts.py -w yolov5n.pt
③,将生成的wts模型,移入自己VS项目中
2.3生成engine引擎
①修改yololayer.h,改变为自己的类数,80是初始权重的分类数,所以我没有改
②修改logging.h文件,这个localtime在windows上会被警告不安全,需要换一下。74行将原来的语句注释一下,添加修改为红框中的内容。
③屏蔽<dirent.h>文件,util.h文件中调用的<dirent.h>,windows没有这个库,需要进行添加,但我尝试过导入,但报了一大堆其他的错,由于我开始主要是想用它来进行视频流推理,所以我放弃了(后续有时间再更新解决)。util.h文件修改,将#include <dirent.h>,以及read_files_in_dir中内容注释掉。
④,添加命令参数,模拟终端输入参数运行程序,参考Ubuntu运行命令,根据自己的情况添加参数(如果是yolov5-5.0版的,没有n)。
sudo ./yolov5 -s [.wts] [.engine] [n/s/m/l/x/s6/m6/l6/x6 or c/c6 gd gw] // serialize model to plan file
sudo ./yolov5 -d [.engine] [image folder] // deserialize and run inference, the images in [image folder] will be processed.
⑤选择Release模型运行,生成engine文件
2.4使用engine进行推理
②可以参考dirent.h windows安装_WAI_f的博客-CSDN博客_dirent.h windows,进行下载导入一下,如果可以,那就运行推理。后续有时间我打算替换一种windows能用API来读取目录。
三,tensorrtx魔改版部署,视频流推理
首次上传github,需要学一下,再更新。先上个main源码(yolov5-5.0版)
int main(int argc, char** argv) {
cudaSetDevice(DEVICE);
std::string wts_name = "";
std::string engine_name = "";
bool is_p6 = false;
float gd = 0.0f, gw = 0.0f;
std::string img_dir;
std::string parse[10];
parse[0] = "./yolov5"; //无用
parse[1] = "-s"; // -s:构建engine -d run engine
parse[2] = "yolov5s.wts"; //wts文件路径
parse[3] = "yolov5s.engine"; //engine文件路径
parse[4] = "s"; //模型类型
if (!parse_args(parse, wts_name, engine_name, is_p6, gd, gw)) {
std::cerr << "arguments not right!" << std::endl;
return -1;
}
// create a model using the API directly and serialize it to a stream
if (!wts_name.empty()) {
//构建engine
IHostMemory* modelStream{ nullptr };
APIToModel(BATCH_SIZE, &modelStream, is_p6, gd, gw, wts_name);
assert(modelStream != nullptr);
std::ofstream p(engine_name, std::ios::binary);
if (!p) {
std::cerr << "could not open plan output file" << std::endl;
return -1;
}
p.write(reinterpret_cast<const char*>(modelStream->data()), modelStream->size());
modelStream->destroy();
return 0;
}
//推理阶段
// deserialize the .engine and run inference
std::ifstream file(engine_name, std::ios::binary);
if (!file.good()) {
std::cerr << "read " << engine_name << " error!" << std::endl;
return -1;
}
char *trtModelStream = nullptr;
size_t size = 0;
file.seekg(0, file.end);
size = file.tellg();
file.seekg(0, file.beg);
trtModelStream = new char[size];
assert(trtModelStream);
file.read(trtModelStream, size);
file.close();
//准备输入流
// prepare input data ---------------------------
static float data[BATCH_SIZE * 3 * INPUT_H * INPUT_W];
static float prob[BATCH_SIZE * OUTPUT_SIZE];
IRuntime* runtime = createInferRuntime(gLogger);
assert(runtime != nullptr);
ICudaEngine* engine = runtime->deserializeCudaEngine(trtModelStream, size);
assert(engine != nullptr);
IExecutionContext* context = engine->createExecutionContext();
assert(context != nullptr);
delete[] trtModelStream;
assert(engine->getNbBindings() == 2);
void* buffers[2];
// In order to bind the buffers, we need to know the names of the input and output tensors.
// Note that indices are guaranteed to be less than IEngine::getNbBindings()
const int inputIndex = engine->getBindingIndex(INPUT_BLOB_NAME);
const int outputIndex = engine->getBindingIndex(OUTPUT_BLOB_NAME);
assert(inputIndex == 0);
assert(outputIndex == 1);
// Create GPU buffers on device
CUDA_CHECK(cudaMalloc(&buffers[inputIndex], BATCH_SIZE * 3 * INPUT_H * INPUT_W * sizeof(float)));
CUDA_CHECK(cudaMalloc(&buffers[outputIndex], BATCH_SIZE * OUTPUT_SIZE * sizeof(float)));
// Create stream
cudaStream_t stream;
CUDA_CHECK(cudaStreamCreate(&stream));
cv::VideoCapture capture("sample.mp4");
cv::Mat frame;
while (cv::waitKey(1) != 27)
{
// time_begin = clock();
// Create stream
capture >> frame;
cv::Mat img = frame;
if (img.empty())
{
std::cout << "No Video input\n" << std::endl;
return - 1;
}
cv::Mat pr_img = preprocess_img(img, INPUT_W, INPUT_H); // letterbox BGR to RGB
int i = 0;
for (int row = 0; row < INPUT_H; ++row) {
uchar* uc_pixel = pr_img.data + row * pr_img.step;
for (int col = 0; col < INPUT_W; ++col) {
data[i] = (float)uc_pixel[2] / 255.0;
data[i + INPUT_H * INPUT_W] = (float)uc_pixel[1] / 255.0;
data[i + 2 * INPUT_H * INPUT_W] = (float)uc_pixel[0] / 255.0;
uc_pixel += 3;
++i;
}
}
// Run inference
auto start = std::chrono::system_clock::now();
doInference(*context, stream, buffers, data, prob, BATCH_SIZE);
auto end = std::chrono::system_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
std::vector<std::vector<Yolo::Detection>> batch_res(1);
//for (int b = 0; b < fcount; b++) {
auto& res = batch_res[0];
nms(res, &prob[0], CONF_THRESH, NMS_THRESH);
std::cout << res.size() << std::endl; //输出检测到的目标数
if(res.size()>0) // 有目标才进行框选
for (int j = 0; j < 1; j++) {
cv::Rect r = get_rect(img, res[j].bbox);
cv::rectangle(img, r, cv::Scalar(0x27, 0xC1, 0x36), 2);
cv::putText(img, std::to_string((int)res[j].class_id), cv::Point(r.x, r.y - 1), cv::FONT_HERSHEY_PLAIN, 1.2, cv::Scalar(0xFF, 0xFF, 0xFF), 2);
cv::putText(img, std::to_string(1000/std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()), cv::Point(img.cols * 0.02, img.rows * 0.05), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 255, 0), 2,8);
}
cv::imshow("inference", img);
}
// Release stream and buffers
cudaStreamDestroy(stream);
CUDA_CHECK(cudaFree(buffers[inputIndex]));
CUDA_CHECK(cudaFree(buffers[outputIndex]));
// Destroy the engine
context->destroy();
engine->destroy();
runtime->destroy();
return 0;
}
四,总结
之前一直以为yolov5的网络结构只和它的yolo.py有关,所以只有yolo.py有版本之分,初始权重.pt都是一样的。这次通过修改tensorrtx源码实现摄像头,视频流推理,意识到权重也有版本之分。也是首次在github上分享一些自己的东西——虽然是修改大佬的成果,希望能帮到一些,和我一样想用tensorrtx不只是做图片推理的朋友。
个人感受记录,可忽略:泪崩,最开始用tensorrtx-yolov5-5.0的版使用,经过一番挫折最终改编,并且成功加载了以前训练过的yolov5-5.0的模型。想着做教程,当然得紧追潮流,用最新的yolov5-6.1和yolov5n进行部署。最新的tensorrtx-master是支持的yolov5-6.0的,同代版本差别应该不大,于是我就使用yolov5-6.1(主要还是因为已经用过了)。在调试过程中由于配置好后,在运行过程中抛出异常——std::endl还有异常,敢信?。所以我想着是不是模型之间也存在差异,训练过的权重和没训练权重导出的wts模型是不是有区别,由于没训练过,导致出了问题。然后我换回之前yolov5-5.0训练过的模型——此时我没有考虑yolo版本问题(其实用Netron看过,太粗心了一拉到底,下面都一样,但是yolov5-5.0是focous开头而不是conv,还有中间是SPP而不是SPPF)。结果很显然,肯定也不行,这就很折磨人了,抛出异常而不是报错。网上搜异常信息,出来的结果都很难解决遇到的问题。最终我又回到tensorrtx-yolov5-5.0,想着最新的教程做不了,做个旧的也行吧,yolov5n运行不了,运行yolov5s总可以吧,结果不行!!!!。难道经过训练的权重会尊贵点?非常郁闷。既然旧的也不行,还是弄的新的吧,毕竟都写了一大半教程,就差运行成功截图了。这卡了我几天,最后报着试试的想法,将localtime_s的方式再改一改,tm结构体不再是指针结构体,变为普通结构体,然后用引用传送给localtime_s(为什么要改x+++++++++++++--这个,因为之前它报过一次错,这个异常和时间有关,所以就试试了)。异常解决了,但新异常又出现了。所以我没有认为我解决了这个问题,反而在想新异常是不是我改了才冒出来的。因为yolov5之前没改,也运行成功了。之后又是一番搜索苦想,新异常和模型结构有关,突然意识到初始权重会不会也有版本。于是我换回yolov5n.pt,程序成功加载模型并生成enjine文件。。。。。。
问题记录
①导出模型出错:AttributeError: Can't get attribute 'SPPF' on <module 'models.common'
解决办法:添加SPFPF类到commonpy文件:具体解决办法链接
②链接出错:yolov5.obj : error LNK2001: unresolved external symbol cudaMalloc
·· 解决办法:添加lib到链接器,缺什么添什么,可以从报错中看出,缺的是cuda相关的lib,需要去对应文件夹中找,和尝试。此报错需要链接cudart.lib,其他类似的报错,可以参考文中添加的lib文件。
③报错:error : cannot define dllimport entity
解决办法:添加预处理API_EXPORTS,具体参考本文 2.1第⑦步
④报错:yolov5.obj : error LNK2019: unresolved external symbol "public: virtual __cdecl nvinfer1::YoloLayerPlugin::
解决办法:修改cu文件的编译方式为cuda,参考博客
⑤程序中止抛出异常:Unhandled exception at 0x00007FF872941208 (ucrtbase.dll)
原因很多,多注意指针的使用,以及内存访问越界等问题