一,将deploy.prototxt改写为deploy_plugin.prototxt
1,convolution层的param{}全部去掉,convolution_param中的weight_filter{}去掉,bias_filter{}去掉
2,将自定义层的名字改写为IPlugin,自定义层的参数写在新写的class里面
3,ssd的detection_out层的detection_output_param去掉,然后新加一个top:detection_out2
因为在tensorRT中,默认的输出是两个,如果只有一个top,那么程序会报错。
“Plugin layer output count is not equal to caffe output count”.
二,完成自定义层的代码
1,先实现PluginFactory的class,继承IPluginFactory,class中定义需要使用的自定义层
2,实现 bool PluginFactory::isPlugin(const char* name)
3,实现
nvinfer1::IPlugin* PluginFactory::createPlugin(const char* layerName, const nvinfer1::Weights* weights, int nbWeights)
通过if语句判断是属于哪一层,然后在条件分支中实现新的层的参数传递,return mXXXX_layer.get()返回指针
三,tensorRT的使用步骤
1,caffeToTRTModel
void TensorNet::caffeToTRTModel(const std::string& deployFile, const std::string& modelFile, const std::vector<std::string>& outputs,
unsigned int maxBatchSize)
{
IBuilder* builder = createInferBuilder(gLogger);
INetworkDefinition* network = builder->createNetwork();
ICaffeParser* parser = createCaffeParser();
parser->setPluginFactory(&pluginFactory);
bool useFp16 = builder->platformHasFastFp16();
useFp16 = false;
DataType modelDataType = useFp16 ? DataType::kHALF : DataType::kFLOAT;
std::cout << deployFile.c_str() <<std::endl;
std::cout << modelFile.c_str() <<std::endl;
//std::cout << (*network) <
std::cout << "Here : 1"<<std::endl;
const IBlobNameToTensor* blobNameToTensor = parser->parse(deployFile.c_str(),modelFile.c_str(),*network, DataType::kFLOAT);
std::cout << "Here : 2" <<std::endl;
assert(blobNameToTensor != nullptr);
std::cout << "Here : 3" <<std::endl;
for (auto& s : outputs) network->markOutput(*blobNameToTensor->find(s.c_str()));
builder->setMaxBatchSize(maxBatchSize);
builder->setMaxWorkspaceSize(10 << 20);
std::cout << "Here : 4"<< std::endl;
ICudaEngine* engine = builder->buildCudaEngine( *network );
std::cout << "Here : 5"<<std::endl;
assert(engine);
network->destroy();
parser->destroy();
gieModelStream = engine->serialize();
engine->destroy();
builder->destroy();
pluginFactory.destroyPlugin();
shutdownProtobufLibrary();
}
如果这个函数执行完成没有问题,那么整个网络结构修改的没有问题。
问题1:
我目前卡在了const IBlobNameToTensor* blobNameToTensor = parser->parse(),QtCreator提示The program has unexpectedly finished.估计是访问了非法内存还是咋地,调试也不好使。
2018,10,10 这个问题真是自己傻逼了,prototxt改写plugin的时候改错了,有一层没有输入,检查也没有发现这个问题,tensorRT运行的时候直接segmentation fault。
总结一下就是,如果遇到一个问题,百度谷歌全部都找不到相关信息,那么这个问题只有两种可能,一是这个问题很牛逼,别人都还没有发现;二是这个问题很傻逼,别人发现了不想写出来。对于我来说,遇到了牛逼问题的可能性很小,所以一般都是自己傻逼了。找这种bug的时候,把自己当成一个傻逼,往自己觉得自己不可能犯错的地方取查
问题2:
ICudaEngine* engine = builder->buildCudaEngine( *network );
assert(engine);
assertion error,创建engine失败
最后事实证明,这也是一个弱智问题,问题还是存在于prototxt中,修改类别之后的相应的num_output不匹配导致的问题,修改之后就可以了。
问题3:
virtual void nvinfer1::plugin::DetectionOutput::configure(const nvinfer1::Dims*, int, const nvinfer1::Dims*, int, int): Assertion `numPriorsnumLocClasses4 == inputDims[param.inputOrder[0]].d[0]’ failed.
感觉这个问题会很棘手,在github上有人正在讨论。
https://github.com/dusty-nv/jetson-inference/issues/171
https://github.com/chenzhi1992/TensorRT-SSD/issues/26
我尝试了上面所说的方法,将我的tensorRT升级到tensorRT4.0 然后在detection_out层的参数中加入inputOrder = {0,1,2},然后就正常了。
如果使用tensorRT3.0的话,该怎么办呢?我觉得应该是有办法的,先留个坑,全部调通之后在研究tensorRT3.0的API
(2018_10_12)找到原因了,在tensorRT3.0中,在reshape类中的getOutputDimensions的函数返回值,调整一下顺序就可以了。 如果使用tensorRT4.0,只是在detection_out中增加了一个inputOrder的参数选项,其实两者的效果是一样的。
Dims getOutputDimensions(int index, const Dims* inputs, int nbInputDims) override
{
assert(nbInputDims == 1);
assert(index == 0);
assert(inputs[index].nbDims == 3);
assert((inputs[0].d[0])*(inputs[0].d[1]) % OutC == 0);
//输出的顺序
//return DimsCHW(OutC, inputs[0].d[0] * inputs[0].d[1] / OutC, inputs[0].d[2]);
return DimsCHW( inputs[0].d[0] * inputs[0].d[1] / OutC, OutC, inputs[0].d[2]);
}
分别打印出detection_out层的输入 mbox_conf_softmax,mbox_loc和mbox_priorbox的尺寸
我这里分类是5类(4 + background)
分别 C H W
mbox_conf_softmax ------> 5 1917 1
mbox_loc ---------> 7668 1 1
mbox_priorbox ---------> 2 7668 1
可以通过如下代码来打印想观察的层的输出维度
const char* OUTPUT1 = "mbox_conf_softmax";
const char* OUTPUT2 = "mbox_loc";
const char* OUTPUT3 = "mbox_priorbox";
const char* OUTPUT4 = "mbox_conf_flatten";
const char* OUTPUT_BLOB_NAME = "detection_out";
tensorNet.caffeToTRTModel( model, weight, std::vector<std::string>{ OUTPUT_BLOB_NAME }, BATCH_SIZE);
DimsCHW dimsOut = tensorNet.getTensorDims(OUTPUT_BLOB_NAME);
DimsCHW dimsOut1 = tensorNet.getTensorDims(OUTPUT1);
DimsCHW dimsOut2 = tensorNet.getTensorDims(OUTPUT2);
DimsCHW dimsOut3 = tensorNet.getTensorDims(OUTPUT3);
DimsCHW dimsOut4 = tensorNet.getTensorDims(OUTPUT4);
cout << "INPUT Tensor Shape is: C: " <<dimsData.c()<< " H: "<<dimsData.h()<<" W: "<<dimsData.w()<<endl;
cout << "mbox_conf_softmax Tensor Shape is: C: "<<dimsOut1.c()<<" H: "<<dimsOut1.h()<<" W: "<<dimsOut1.w()<<endl;
cout << "mbox_loc Tensor Shape is: C: "<<dimsOut2.c()<<" H: "<<dimsOut2.h()<<" W: "<<dimsOut2.w()<<endl;
cout << "mbox_priorbox Tensor Shape is: C: "<<dimsOut3.c()<<" H: "<<dimsOut3.h()<<" W: "<<dimsOut3.w()<<endl;
cout << "mbox_conf_flatten Tensor Shape is: C: "<<dimsOut4.c()<<" H: "<<dimsOut4.h()<<" W: "<<dimsOut4.w()<<endl;
cout << "OUTPUT Tensor Shape is: C: "<<dimsOut.c()<<" H: "<<dimsOut.h()<<" W: "<<dimsOut.w()<<endl;
cout << "conv11 Tensor Shape is: C: "<<dimsOutConv11.c()<<" H: "<<dimsOutConv11.h()<<" W: "<<dimsOutConv11.w()<<endl;
这样就可以判断自己写的层的输出维度是不是对的,跟caffe的打印的层的维度是否一样。如果维度是一样的,网络结构就应该没有问题了
2,tensorNet.createInference()
问题4
输出结果不对
在推理过程中没有报错了,但是出来的结果不对。
detection_out层输出的结果,7个值分别是 0 -1 0 0 0 0 0 这样的结果肯定是不对的
猜测有几个原因:1,输入图片的均值和归一化的值不对;2,取结果的方式不对;3,推理过程中有的层的参数不对。
2018-10-17
找到原因了:图片输入的方式不对,例如训练VOC-SSD的时候,直接是减去三个通道的均值,并没有进行scale操作,但是训练mobileNet的时候有进行scale操作。所以如果tensorRT在进行图片输入的时候,不进行scale操作,那么输入的数据是有问题的。在util/loadImage.cpp中进行相应地修改。
问题5
输出结果依然不对
输出的7个值分别类似于
0 3 0.95 0.243 0.25 0.244 0.25
可以明显地看出来class和confidence是正确的,就是bbox的值不对,这一点可以通过换测试图片来验证。
既然class和confidence都是正确的,那么可以证明 softmax和mbox_conf_flatten是正确的,那么也就是priorbox的输出不正确。
然后对着原始的deploy.prototxt文件中的priorbox层的param参数来一一比对。
例如这一层
layer {
name: "conv11_mbox_priorbox"
type: "PriorBox"
bottom: "conv11"
bottom: "data"
top: "conv11_mbox_priorbox"
prior_box_param {
min_size: 60.0
aspect_ratio: 2.0
flip: true
clip: false
variance: 0.1
variance: 0.1
variance: 0.2
variance: 0.2
offset: 0.5
}
}
对应tensorRT中的mConv11_mbox_priorbox_layer中的形式应该是
else if (!strcmp(layerName, "conv11_mbox_priorbox"))
{
assert(mConv11_mbox_priorbox_layer.get() == nullptr);
//参数按照原来的prototxt中的prior_box_param设置
PriorBoxParameters params;
float minsize[1] = {60},aspect_ratio[2] = {1.0,2.0};
params.minSize = minsize;
params.aspectRatios = aspect_ratio;
params.numMinSize = 1;
params.numAspectRatios = 2;
params.maxSize = nullptr;
params.numMaxSize = 0;
params.flip = true;
params.clip = false;
params.variance[0] = 0.1;
params.variance[1] = 0.1;
params.variance[2] = 0.2;
params.variance[3] = 0.2;
params.imgH = 0;
params.imgW = 0;
params.stepH = 0;
params.stepW = 0;
params.offset = 0.5;
mConv11_mbox_priorbox_layer = std::unique_ptr<INvPlugin, decltype(nvPluginDeleter)>
(createSSDPriorBoxPlugin(params), nvPluginDeleter);
return mConv11_mbox_priorbox_layer.get();
}
我这里犯错的原因是,由于prototxt中的参数有几个是缺省值,没有写进去。例如conv11_mbox_priorbox层中 prior_box_param就没有maxSize,没有step值等等。所以我在tensorRT中以为也是默认值,所以没有写。
所以才导致最后出现的框中的坐标值是错误的。正确的做法因该是prototxt中没有出现的值默认应该以0补充,如果是指针形式,以nullptr补充。
还有几点不是太明白
1,priorbox,caffe和tensorRT的输出shape不一样
tensorRT的priorbox的输出shape,C通道一直都是2
caffe的priorbox的输出shape,C 通道是W的2倍
在github上的issue区讨论的,别人都说如果遇到问题,就一层一层的和caffe输出对比。但是这里输出维度都不一样,比个锤子啊????
(更新~~)关于上面说的每次输出结果都不一样的问题,是由于一个变量在定义的时候未初始化导致的,下面的github的repo更新了,解决了这个问题,一直忘记了来更新博文,所以我不记得是哪个变量了··· 大家见谅,具体得到代码中去找了。
代码链接
https://github.com/Ghustwb/MobileNet-SSD-TensorRT/blob/master/main.cpp