OpenVINO2021.4+YOLOX目标检测模型部署测试
内容来源|OpenCV学堂
本文基于YOLOX的ONNX模型分别测试了YOLOX-Small与YOLOX-Tiny版本的模型。硬件配置与软件版本:
Win10 64位
CPU CORE i7 8th
VS2017
OpenVINO2021.4
型号说明
两个模型的输入与输出格式分别如下:以YOLOX为例,解释输出的内容是什么,看模型的输出图标如下:
有三个输出层,分别是8倍、16倍、32倍的降采样,输出的8400计算方法为:
80x80+40x40+20x20 = 6400+1600+400=8400
分别6408倍、16倍、32倍的降幅大小。85的前四个是cx、cy、w、h大小,第5个是对象预测评分,80个是COCO类别。
看到这里就知道它跟YOLOv5的解析差不多。然后它对它的预测要求如下:
输入通道顺序:RGB、类型浮点数0~1之间
输入的均值:0.485f, 0.456f, 0.406f
输入的归一化方差:0.229f, 0.224f, 0.225f
代码实现部分
首先需要加载对应模型,从github上下载好的模型ONNX格式文件之后,首先通过IECore来加载YOLOX,模型代码如下:
std :: cout << "YOLOX 演示" << std :: endl ;
核心即;
std :: vector < std :: string >availableDevices = ie.GetAvailableDevice*******r>for ( int i = 0 ; i < availableDevices.size(); i++) {
printf ( "支持的设备名称: %s \n" , availableDevices[i].c_str());
}
// 加载检测模型
auto network = ie.ReadNetwork( "D:/yolox.onnx" );
设置模型的输入与输出,这里需要注意,输入设置为FP32,读取输入与输出层名称,代码如下:
// 请求网络输入与输出信息
InferenceEngine::InputsDataMap input_info(network.getInputsInfo());
InferenceEngine::OutputsDataMap output_info(network.getOutputsInfo());
// 设置输入格式
std::string input_name = "" ;
for (auto &item : input_info) {
auto input_data = item.second;
input_name = item.first;
input_data->setPrecision(Precision::FP32);
input_data->setLayout(Layout::NCHW);
}
printf( "得到它\n" );
// 设置输出格式
std::string output_name = "" ;
for (auto &item : output_info) {
auto output_data = item.second;
output_name = item.first;
std::cout << "输出名称:" << item.first << std::endl;
output_data->setPrecision(Precision::FP32);
}
下面就是每个输出层的网格,每个网格上的每个点的坐标信息,可以解析数据的时候需要根据索引来获取每个网格的数据
// 生成三个输出层的网格与锚信息
std :: vector < int > strides = { 8 , 16 , 32 };
std :: vector <GridAndStride> grid_stride*****r>generate_grids_and_stride(IMG_W, strides, grid_strides);
用generate_grids_and_stride省是我例子了官方的代码,这部分我觉得是可以去的,可以从index中直接计算的,也许这样会更快点,暂时我就借了,该方法的代码如下:
常量 浮点 IMG_W = 640.0f;
结构 GridAndStride
{
int gh;
国际 **;
INT 大步;
};
void generate_grids_and_stride ( int target_size, std :: vector < int >& strides , std :: vector <GridAndStride>& grid_stride****r> {
for ( auto stride : stride******r> {
int num_grid = target_size / stride*****r> for ( int g1 = 0; g1 < num_grid; g1++)
{
for ( int g0 = 0 ; g0 < num_grid; g0++)
{
GridAndStride g*****r> gs.gh = g0;
gs.gw = g1;
gs.stride = 步幅;
grid_strides.push_back(g******r> }
}
}
}
下面就很容易啦,创建推理请求,开始执行推理,推理的部分,代码如下:
// 开始推理处理 - 支持视频与视频
cv::Mat image = cv::imread( "D:/zidane.jpg" );
inferAndOutput(image, grid_strides, input_name, output_name, infer_request);
推断和输出是我的推理与解析输出结果的方法,该方法首先得到输出,然后根据索引从网格_步幅里面查询对应网格的对应位置信息,原来官方的方法比较啰嗦,代码简洁,我很少修改了一下,借助OpenVINO中OpenCV自带的NMS功能功能,重新整理一下,改成现在的方法,发现可以降低量代码,提升神话性,该方法的代码如下:
void inferAndOutput (cv::Mat &image, std :: vector <GridAndStride> &grid_strides, std :: string &input_name, std :: string &output_name, InferRequest &infer_request) {
int64 start = cv::getTickCount();
Blob::Ptr imgBlob = infer_request.GetBlob(input_name);
float sx = static_cast < float >(image.cols) / IMG_W;
float sy = static_cast < float >(image.rows) / IMG_W;
//推理
blobFromImage(image, imgBlob);
infer_request.Infer();
const Blob::Ptr output_blob = infer_request.GetBlob(output_name);
const float * outblob = static_cast <PrecisionTrait<Precision::FP32>::value_type*>(output_blob->buffer());
const SizeVector outputDims = output_blob->getTensorDesc().getDim*******r> const int num_anchors = grid_strides.size();
const int num_class = 80 ;
// 处理解析输出结果
std :: vector <cv::Rect> boxe*****r> std :: vector < int > classId*****r> std :: vector < float > 置信度;
为了 ( int anchor_idx = 0 ; anchor_idx < num_anchors; anchor_idx++)
{
const int grid0 = grid_strides[anchor_idx].gh; // H
const int grid1 = grid_strides[anchor_idx].gw; // W
const int stride = grid_strides[anchor_idx].stride; // 步幅
const int basic_pos = anchor_idx * 85 ;
float x_center = (outblob[basic_pos + 0 ] + grid0) * stride * sx;
float y_center = (outblob[basic_pos + 1 ] + grid1) * stride * sy;
浮动 w = exp(outblob[basic_pos + 2 ]) * 步幅 * sx;
float h = exp (outblob[basic_pos + 3 ]) * stride * sy;
浮动 x0 = x_center - w * 0.5f ;
浮动 y0 = y_center - h * 0.5f;
float box_objectness = outblob[basic_pos + 4 ];
for ( int class_idx = 0 ; class_idx < num_class; class_idx++)
{
float box_cls_score = outblob[basic_pos + 5 + class_idx];
浮动 box_prob = box_objectnes*****ox_cls_score;
如果 (box_prob > 0.25 )
{
cv::Rect 矩形;
rect.x = x0;
rect.y = y0;
rect.width = w;
rect.height = h;
classIds.push_back(class_idx);
置信度.push_back ((浮动)box_prob);
box.push_back(rect);
}
} // 类循环
}
std :: vector < int > 索引;
cv::dnn::NMSBoxe***oxes,confidences, 0.25 , 0.5 , 指数);
对于 ( size_t 我 = 0 ; i < 索引.大小(); ++i)
{
int idx = 索引[i];
cv::Rect box = box[idx];
矩形(图像,框,CV ::标量(140, 199, 0), 4, 8, 0);
}
浮子 FPS = CV :: getTickFrequency()/(CV ::的GetTickCount() -开始);
浮动 时间 = (cv::getTickCount() - 开始) / cv::getTickFrequency();
std :: ostringstream s*****r> ss << "FPS:" << fps << " 检测时间:" << 时间 * 1000 << "毫秒" ;
cv::putText(image, ss.str(), cv::Point( 20 , 50 ), 0 , 1.0 , cv::Scalar( 0 , 0 , 255 ), 2 );
cv::imshow( "OpenVINO2021.4+YOLOX Demo@JiaZhiGang" , image);
}
运行与测试
首先用YOLOv5的一张测试图象测试一下,基于YOLOX的samll版本模型运行结果如下:
跟YOLOV5 **all版本测试结果完成一致,毫无违和感!
视频测试(YOLOX Small版本模型)运行结果如下:
感觉没有YOLOv5的**all版本推理速度快(在我的机器上)!还需进一步优化输出解析代码。
视频测试(YOLOX Tiny版本模型)运行结果如下:
CPU果然可以30+ FPS的。