1.预备知识
本章还是不讨论 SSD 是什么,只要能够理解 SSD 的目的是目标检测。
前提基础知识:解析mobile_ssd样例代码
上一章中具体的讲解了 "mobilenet_SSD"(下称MSSD)运行过程,为示例代码添上了注释。但是也很明显只是对一张图片进行检测。为了更近一步,我把 这个样例代码略做修剪,对摄像头的视频流图片进行检测。
但是如何使用视频流呢?
这里给出一段简单的 OpenCV 程序,该程序是 OpenCV 的样例程序,位于 "opencv-3.4.2/samples/cpp/example_cmake",同样使用 CLion 打开。
#include "opencv2/core.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/videoio.hpp"
#include <iostream>
// 使用 opencv 的命名空间
using namespace cv;
using namespace std;
void drawText(Mat & image);
int main()
{
cout << "Built with OpenCV " << CV_VERSION << endl;
Mat image;
// 设置使用摄像头(0)
VideoCapture capture;
capture.open(0);
// 如果摄像头存在
if(capture.isOpened())
{
cout << "Capture is opened" << endl;
for(;;)
{
capture >> image;
if(image.empty())
break;
drawText(image);
// img 图像大小 480,640
imshow("Sample", image);
// 注意 imshow 后面必须跟一个 waitKey(),否则无法显示图片
if(waitKey(10) >= 0)
break;
}
}
// 如果摄像头不存在(可以省略)
else
{
cout << "No capture" << endl;
image = Mat::zeros(480, 640, CV_8UC1);
drawText(image);
imshow("Sample", image);
waitKey(0);
}
return 0;
}
void drawText(Mat & image)
{
putText(image, "Hello OpenCV",
Point(20, 50),
FONT_HERSHEY_COMPLEX, 1, // font face and scale
Scalar(255, 255, 255), // white
1, LINE_AA); // line thickness and type
}
该程序是一个简单的视频流程序,所以一个简单的想法,将该程序与 MSSD 进行结合。由 视频流 提供 图片(image),由 MSSD 对图片进行检测。所以我们需要
TODO:1.知道在 MSSD中那部分是实际的对图像进行处理的地方。
TODO:2.知道如何在 MSSD 中使用视频流。
TODO:3.将 MSSD 的图片地址,改为视频流的图片(CV::Mat)。
2.执行任务
1.知道在 MSSD中那部分是实际的对图像进行处理的地方。
比较容易找到的线索是,样例中 MSSD 使用的是一个图片路径,以图片路径为线索进行查找。
TODO: 1. 调用 init_tengine_library 函数初始化, 只能被调用一次
TODO: 2. 调用 load_model 载入训练好的模型
TODO: 3. 调用 create_runtime_graph 函数创建图(类似 pytorch 的 net)
TODO: 3.1 需要调用 check_graph_valid 来确定返回的句柄
TODO: 4.1 调用 get_graph_input_tensor 获取输入Tensor
TODO: 4.2 并用 check_tensor_valid 确认返回值是否合法
TODO: 4.3 并用 set_tensor_shape 设置输入Tensor的shape
TODO: 5. 调用 prerun_graph 函数预启动图(类似 malloc,申请资源)
TODO: 7.1.向 input_data 写入输入的数据,
TODO: 7.2.并调用 set_tensor_buffer 把数据转移到输入Tensor上
TODO: 8. 调用 run_graph 运行图(做一次前向传播)
TODO: 9.1.调用 get_graph_output_tensor 获取输出Tensor
TODO: 9.2.并用 get_tensor_shape 取得 tensor 的shape
TODO: 9.3.并用 get_tensor_buffer 取得缓冲区上的数据
TODO: 10. 最后在退出程序前依次释放各个申请的动态空间
TODO: 9.4 释放 out_tensor 所占空间
TODO: 4.4 释放 input_tensor 所占空间
TODO: 3.2 调用 destroy_runtime_graph() 来释放资源
TODO: 2.1 请调用 remove_model() to 释放导入的 model
参考具体代码发现
开始使用
TODO: 7.1.向 input_data 写入输入的数据,
get_input_data_ssd(image_file, input_data, img_h, img_w);
......(省略)
结束使用
TODO: 9.3.并用 get_tensor_buffer 取得缓冲区上的数据
中使用到
// 通过 outdata 对 image 进行绘制边框和label信息,并保存
post_process_ssd(image_file,show_threshold, outdata, num,save_name);
所以摄像头图片检索的for(;;)要加在这两行之间。
2.知道如何在 MSSD 中使用视频流。
在MSSD中使用摄像头,我们也要为OpenCV的样例代码做一些微调。比如
1.去掉前面的提示信息
2.去掉摄像头不存在的情况
3.去掉 "void drawText(Mat & image)" 部分
int main()
{
.....(MSSD 代码)
Mat image;
// 设置使用摄像头(0)
VideoCapture capture;
capture.open(0);
// 如果摄像头存在
if(capture.isOpened())
{
for(;;)
{
capture >> image;
if(image.empty())
break;
......(将图片交给 MSSD 处理)
// img 图像大小 480,640
imshow("Sample", image);、
if(waitKey(10) >= 0)
break;
}
}
......(MSSD 代码)
return 0;
}
3.将 MSSD 的图片地址,改为视频流的图片(CV::Mat)。
实际上使用到 image_file 只有两处,并且都是在函数中。
以下两个函数
void get_input_data_ssd(std::string& image_file, float* input_data, int img_h, int img_w)
void post_process_ssd(std::string& image_file,float threshold,float* outdata,int num,std::string& save_name)
所以我们要将函数稍作修改,对 "void get_input_data_ssd" 的修改。(实际只修改了两个地方)
//void get_input_data_ssd(std::string& image_file, float* input_data, int img_h, int img_w)(修改第一处)
void get_input_data_ssd(cv::Mat image, float* input_data, int img_h, int img_w)
{
// 读取数据, img 为图形的内容
//cv::Mat img = cv::imread(image_file);(修改第二处)
cv::Mat img = image.clone();
// 如果 img 为空
if (img.empty())
{
std::cerr << "Failed to read image file " << image << ".\n";
return;
}
// 将 img 进行 reshape
cv::resize(img, img, cv::Size(img_h, img_w));
// 转化数据类型 CV_32FC3:
img.convertTo(img, CV_32FC3);
float *img_data = (float *)img.data;
int hw = img_h * img_w;
/*类似数据归一化
* 127.5 = 255/2
* 0.007843 =1/127.5
*/
float mean[3]={127.5,127.5,127.5};
for (int h = 0; h < img_h; h++)
{
for (int w = 0; w < img_w; w++)
{
for (int c = 0; c < 3; c++)
{
input_data[c * hw + h * img_w + w] = 0.007843* (*img_data - mean[c]);
img_data++;
}
}
}
}
对 "post_process_ssd" 的修改
//(修改第一处)
//void post_process_ssd(std::string& image_file,float threshold,float* outdata,int num,std::string& save_name)
cv::Mat post_process_ssd(cv::Mat image,float threshold,float* outdata,int num,std::string& save_name)
{
//检测目标的类别
const char* class_names[] = {
"background", //背景
"aeroplane", //飞机
"bicycle", //自行车
"bird", //鸟
"boat", //船
"bottle", //瓶子
"bus", //公交车
"car", //私家车
"cat", //猫
"chair", //椅子
"cow", //奶牛
"diningtable", //餐桌
"dog", //狗
"horse", //马
"motorbike", //摩托车
"person", //人
"pottedplant", //盆栽
"sheep", //羊
"sofa", //沙发
"train", //火车
"tvmonitor"}; //电视机
// 图片路径转为矩阵
//(修改第二处)
//cv::Mat img = cv::imread(image_file);
cv::Mat img = image.clone();
// 图片的信息(height,width)
int raw_h = img.size().height;
int raw_w = img.size().width;
// boxes 为检测输出的信息
std::vector<Box> boxes;
// 设置使用的线宽(line_width)
int line_width=raw_w*0.005;
// logout: 检测到目标个数
printf("detect result num: %d \n",num);
for (int i=0;i<num;i++)
{
if(outdata[1]>=threshold)
{
//Box 为 输出内容的信息
Box box;
box.class_idx=outdata[0];
box.score=outdata[1];
box.x0=outdata[2]*raw_w;
box.y0=outdata[3]*raw_h;
box.x1=outdata[4]*raw_w;
box.y1=outdata[5]*raw_h;
// boxes 作为 栈(vector)
boxes.push_back(box);
// logout:输出output信息
printf("%s\t:%.0f%%\n", class_names[box.class_idx], box.score * 100);
printf("BOX:( %g , %g ),( %g , %g )\n",box.x0,box.y0,box.x1,box.y1);
}
outdata+=6;
}
/* output返回的信息并不是都有用
* 经过阈值过滤之后 数量 可能会 降低
*/
// 对过滤后的 正确目标点 进行绘制 矩阵框
for(int i=0;i<(int)boxes.size();i++)
{
Box box=boxes[i];
/*!
* img: 使用的img(Mat 格式)
* Rect rec:矩形结构
* 左上角点(x,y),w宽,h高
* const Scalar& color: 使用的颜色(scalar:BGR)
* thickness: 线宽(默认:1)
* lineType: 线形(默认:LINE_8,实线)
* shift: 坐标的小数点位数(默认为0,整形)
*/
cv::rectangle(img, cv::Rect(box.x0, box.y0,(box.x1-box.x0),(box.y1-box.y0)),
cv::Scalar(255, 255, 0),
line_width);
std::ostringstream score_str;
score_str<<box.score;
// 设置框的标签(名称+分数)
std::string label = std::string(class_names[box.class_idx]) + ": " + score_str.str();
int baseLine = 0;
/*!
* 计算文本的所占大小的尺寸
* label:设置的文本
* fontFace:使用的字体
* fontScale:字体的大小
* thickness:线宽
* baseLine:
*/
cv::Size label_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
// 绘制文本边框矩阵 CV_FILLED 使用填充
cv::rectangle(img,
cv::Rect(cv::Point(box.x0,box.y0- label_size.height),
cv::Size(label_size.width, label_size.height + baseLine)),
cv::Scalar(255, 255, 0),
CV_FILLED);
/*!
* img:输入图片
* text:输入文本
* org:左下角点的坐标
* fontFace:使用字体
* fontScale:字的尺寸
* color:使用的颜色
* thickness:绘制text的线宽
* lineType:线形
*/
cv::putText(img, label, cv::Point(box.x0, box.y0),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
}
// 图形保存
/*!
* filename:文件路径
* img:图形矩阵
* params:
*/
cv::imwrite(save_name,img);
// logout: 输出信息
std::cout<<"======================================\n";
std::cout<<"[DETECTED IMAGE SAVED]:\t"<< save_name<<"\n";
std::cout<<"======================================\n";
return img;
}
对于函数的修改已经完成了,接下来对主函数加入 for循环
struct timeval t0, t1;
float mytime=0;
// 使用opencv
cv::VideoCapture capture;
capture.open(0);
if(capture.isOpened())
{
cv::Mat image;
std::cout<<"Capture is opened" << std::endl;
for(;;)
{
capture >> image;
if(image.empty())
break;
//TODO: 7.1 向 input_data 写入输入的数据,
get_input_data_ssd(image, input_data, img_h, img_w);
// 获取当前值,并赋值给 t0(开始值)
gettimeofday(&t0, NULL);
//TODO: 7.2 并调用 set_tensor_buffer 把数据转移到输入Tensor上
set_tensor_buffer(input_tensor, input_data, img_size * 4);
//TODO: 8. 调用 run_graph 运行图(做一次前向传播)
run_graph(graph, 1);
//获取当前时间,并赋值给 t1(结束时间)
gettimeofday(&t1, NULL);
mytime = (float)((t1.tv_sec * 1000000 + t1.tv_usec) - (t0.tv_sec * 1000000 + t0.tv_usec)) / 1000;
std::cout << "--------------------------------------\n";
std::cout << " times, avg time per run is " << mytime << " ms\n";
//TODO: 9.1.调用 get_graph_output_tensor 获取输出Tensor
tensor_t out_tensor = get_graph_output_tensor(graph, 0,0);//"detection_out");
//TODO: 9.2.并用 get_tensor_shape 取得 tensor 的shape
/*
* [0]:批次:1张图
* [1]:检测到目标个数:3个目标
* [2]:outdata 的 Box 6 个信息:
* 0. 属于的类别(下标)
* 1. 属于该类别的score
* 2. 左上角点(x)相对于宽的百分比
* 3. 左上角点(y)相对于高的百分比
* 4. 右上角点(x)相对于宽的百分比
* 5. 右上角点(y)相对于高的百分比
* [3]:1 一行
*/
int out_dim[4];
get_tensor_shape( out_tensor, out_dim, 4);
//std::cout<<"out_dim" << *out_dim <<std::endl;
//TODO: 9.3.并用 get_tensor_buffer 取得缓冲区上的数据
float *outdata = (float *)get_tensor_buffer(out_tensor);
//std::cout<< "outdata" <<*outdata<<std::endl;
//获取 outdata 的 检测到目标个数(num)
int num=out_dim[1];
//设置阈值,是否为检测目标
float show_threshold=0.5;
// 通过 outdata 对 image 进行绘制边框和label信息,并保存
image = post_process_ssd(image,show_threshold, outdata, num,save_name);
//img 图像大小 480,640
cv::imshow("Sample", image);
if(cv::waitKey(10) >= 0)
break;
//TODO: 9.4 释放 out_tensor 所占空间
put_graph_tensor(out_tensor);
}
}
mssd.cpp 完整代码见文末。
3.进行编译
检测的图片.png 成功检测到杯子和显示屏。
任务完成。
附 MSSD.cpp完整代码
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* License); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright (c) 2018, Open AI Lab
* Author: chunyinglv@openailab.com
*/
#include <unistd.h>
#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "tengine_c_api.h"
#include <sys/time.h>
#include "common.hpp"
//宏定义 prototxt 文件
#define DEF_PROTO "models/MobileNetSSD_deploy.prototxt"
//宏定义 caffemodel 文件
#define DEF_MODEL "models/MobileNetSSD_deploy.caffemodel"
//宏定义 默认图片文件
#define DEF_IMAGE "tests/images/myimages.jpg"
//预测的内容(6个)
struct Box
{
// 左上角点
float x0;
float y0;
// 右下角点
float x1;
float y1;
// 预测的类别
int class_idx;
// 该类别的 正确率
float score;
};
/*!
* @brief 将 图片 的路径名转化为 图片内容的矩阵,并赋值给 input_data
* @param image_file: 输入的图片路径
* @param input_data: 输出的图片的矩阵
* @param img_h: 输出图形的height
* @param img_w: 输出图形的weight
*/
//void get_input_data_ssd(std::string& image_file, float* input_data, int img_h, int img_w)(修改第一处)
void get_input_data_ssd(cv::Mat image, float* input_data, int img_h, int img_w)
{
// 读取数据, img 为图形的内容
//cv::Mat img = cv::imread(image_file);(修改第二处)
cv::Mat img = image.clone();
// 如果 img 为空
if (img.empty())
{
std::cerr << "Failed to read image file " << image << ".\n";
return;
}
// 将 img 进行 reshape
cv::resize(img, img, cv::Size(img_h, img_w));
// 转化数据类型 CV_32FC3:
img.convertTo(img, CV_32FC3);
float *img_data = (float *)img.data;
int hw = img_h * img_w;
/*类似数据归一化
* 127.5 = 255/2
* 0.007843 =1/127.5
*/
float mean[3]={127.5,127.5,127.5};
for (int h = 0; h < img_h; h++)
{
for (int w = 0; w < img_w; w++)
{
for (int c = 0; c < 3; c++)
{
input_data[c * hw + h * img_w + w] = 0.007843* (*img_data - mean[c]);
img_data++;
}
}
}
}
/*!
* @brief
*
* @param image_file: 图片的路径
* @param threshold:是否为检测目标的阈值
* @param outdata:输出数据
* @param num:检测到目标的个数
* @param save_name:
*
*/
//(修改第一处)
//void post_process_ssd(std::string& image_file,float threshold,float* outdata,int num,std::string& save_name)
cv::Mat post_process_ssd(cv::Mat image,float threshold,float* outdata,int num,std::string& save_name)
{
//检测目标的类别
const char* class_names[] = {
"background", //背景
"aeroplane", //飞机
"bicycle", //自行车
"bird", //鸟
"boat", //船
"bottle", //瓶子
"bus", //公交车
"car", //私家车
"cat", //猫
"chair", //椅子
"cow", //奶牛
"diningtable", //餐桌
"dog", //狗
"horse", //马
"motorbike", //摩托车
"person", //人
"pottedplant", //盆栽
"sheep", //羊
"sofa", //沙发
"train", //火车
"tvmonitor"}; //电视机
// 图片路径转为矩阵
//(修改第二处)
//cv::Mat img = cv::imread(image_file);
cv::Mat img = image.clone();
// 图片的信息(height,width)
int raw_h = img.size().height;
int raw_w = img.size().width;
// boxes 为检测输出的信息
std::vector<Box> boxes;
// 设置使用的线宽(line_width)
int line_width=raw_w*0.005;
// logout: 检测到目标个数
printf("detect result num: %d \n",num);
for (int i=0;i<num;i++)
{
if(outdata[1]>=threshold)
{
//Box 为 输出内容的信息
Box box;
box.class_idx=outdata[0];
box.score=outdata[1];
box.x0=outdata[2]*raw_w;
box.y0=outdata[3]*raw_h;
box.x1=outdata[4]*raw_w;
box.y1=outdata[5]*raw_h;
// boxes 作为 栈(vector)
boxes.push_back(box);
// logout:输出output信息
printf("%s\t:%.0f%%\n", class_names[box.class_idx], box.score * 100);
printf("BOX:( %g , %g ),( %g , %g )\n",box.x0,box.y0,box.x1,box.y1);
}
outdata+=6;
}
/* output返回的信息并不是都有用
* 经过阈值过滤之后 数量 可能会 降低
*/
// 对过滤后的 正确目标点 进行绘制 矩阵框
for(int i=0;i<(int)boxes.size();i++)
{
Box box=boxes[i];
/*!
* img: 使用的img(Mat 格式)
* Rect rec:矩形结构
* 左上角点(x,y),w宽,h高
* const Scalar& color: 使用的颜色(scalar:BGR)
* thickness: 线宽(默认:1)
* lineType: 线形(默认:LINE_8,实线)
* shift: 坐标的小数点位数(默认为0,整形)
*/
cv::rectangle(img, cv::Rect(box.x0, box.y0,(box.x1-box.x0),(box.y1-box.y0)),
cv::Scalar(255, 255, 0),
line_width);
std::ostringstream score_str;
score_str<<box.score;
// 设置框的标签(名称+分数)
std::string label = std::string(class_names[box.class_idx]) + ": " + score_str.str();
int baseLine = 0;
/*!
* 计算文本的所占大小的尺寸
* label:设置的文本
* fontFace:使用的字体
* fontScale:字体的大小
* thickness:线宽
* baseLine:
*/
cv::Size label_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
// 绘制文本边框矩阵 CV_FILLED 使用填充
cv::rectangle(img,
cv::Rect(cv::Point(box.x0,box.y0- label_size.height),
cv::Size(label_size.width, label_size.height + baseLine)),
cv::Scalar(255, 255, 0),
CV_FILLED);
/*!
* img:输入图片
* text:输入文本
* org:左下角点的坐标
* fontFace:使用字体
* fontScale:字的尺寸
* color:使用的颜色
* thickness:绘制text的线宽
* lineType:线形
*/
cv::putText(img, label, cv::Point(box.x0, box.y0),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));
}
// 图形保存
/*!
* filename:文件路径
* img:图形矩阵
* params:
*/
cv::imwrite(save_name,img);
// logout: 输出信息
std::cout<<"======================================\n";
std::cout<<"[DETECTED IMAGE SAVED]:\t"<< save_name<<"\n";
std::cout<<"======================================\n";
return img;
}
int main(int argc, char *argv[])
{
// root_path 为 Tengine 的文件路径
const std::string root_path = get_root_path();
// proto_file 为我们设置的 prototxt 文件路径
std::string proto_file;
// model_file 为我们设置的 model 文件路径
std::string model_file;
// 设置保存的 生成图片 路径名称
std::string save_name="save.jpg";
// device 使用设备默认为空(用于graph)
const char * device=nullptr;
int res;
// 与命令行输入相关, 指定可输入参数(-p -m -i -hd)
/*
* -p prototxt 文件路径 默认:"models/MobileNetSSD_deploy.prototxt"
* -m model 文件路径 默认:"models/MobileNetSSD_deploy.caffemodel"
* -i image 文件路径 默认:"tests/images/ssd_dog.jpg"
* -h help 信息
* -d device 设置 默认:为空
*/
while( ( res=getopt(argc,argv,"p:m:i:hd:"))!= -1)
{
switch(res)
{
case 'p':
proto_file=optarg;
break;
case 'm':
model_file=optarg;
break;
case 'i':
// image_file=optarg;
break;
case 'd':
device=optarg;
break;
case 'h':
std::cout << "[Usage]: " << argv[0] << " [-h]\n"
<< " [-p proto_file] [-m model_file] [-i image_file]\n";
return 0;
default:
break;
}
}
const char *model_name = "mssd_300";
// 设置 proto_file 默认值
if(proto_file.empty())
{
proto_file = root_path + DEF_PROTO;
std::cout<< "proto file not specified,using "<<proto_file<< " by default\n";
}
// 设置 model_file 默认值
if(model_file.empty())
{
model_file = root_path + DEF_MODEL;
std::cout<< "model file not specified,using "<<model_file<< " by default\n";
}
/*
* Tengine 使用流程:
* 1. 调用 init_tengine_library 函数初始化
* 2. 调用 load_model 载入训练好的模型
* 这里需要指定框架的模型: tensorflow,caffe,mxnet,onnx
* 设置时需要修改 Tengine_root/makefile.config
* # Enable other serializers
* CONFIG_CAFFE_SERIALIZER=y
* # CONFIG_MXNET_SERIALIZER=y
* # CONFIG_ONNX_SERIALIZER=y
* # CONFIG_TF_SERIALIZER=y
* CONFIG_TENGINE_SERIALIZER=y
* 将要使用的 model 去掉注释(默认使用caffe)
* 3. 调用 create_runtime_graph 函数创建图(类似 pytorch 的 net)
* 4. 调用 get_graph_input_tensor 获取输入Tensor并用 set_tensor_shape 设置输入Tensor的shape
* 类似 net(input)
* 5. 调用 prerun_graph 函数预启动图(类似 malloc,申请资源)
* 6. 调用 get_graph_output_tensor 获取输出Tensor并用 get_tensor_buffer_size 获取输出的shape
* 7. 向 input_data 写入输入的数据,并调用 set_tensor_buffer 把数据转移到输入Tensor上
* 8. 调用 run_graph 运行图(做一次前向传播)
* 9. 调用 get_graph_output_tensor 获取输出Tensor并用 get_tensor_buffer 取得缓冲区上的数据
* 10. 最后在退出程序前依次释放各个申请的动态空间
*/
//TODO: 1. 调用 init_tengine_library 函数初始化, 只能被调用一次
init_tengine_library();
// 检查库文件是否高于 0.1
if (request_tengine_version("0.1") < 0)
return 1;
//TODO: 2. 调用 load_model 载入训练好的模型
/*
* @param model_name 给导入的模型命名,便于调用
* @param model_format 模型的文件格式: caffe/onnx/tensorflow/mxnet/tengine
* @param fname 文件路径(可以有多个,char)
* @note 0. 保存的模型可能含有多个文件(比如caffe)
* 1. 请调用 remove_model() to 释放导入的 model
*/
if (load_model(model_name, "caffe", proto_file.c_str(), model_file.c_str()) < 0)
return 1;
std::cout << "load model done!\n";
//TODO: 3. 调用 create_runtime_graph 函数创建图(类似 pytorch 的 net)
/*
* @note 1. 需要调用 check_graph_valid 来确定返回的句柄
* 2. 调用 destroy_runtime_graph() 来释放资源
*/
graph_t graph = create_runtime_graph("graph", model_name, NULL);
//TODO: 3.1 需要调用 check_graph_valid 来确定返回的句柄
if (!check_graph_valid(graph))
{
std::cout << "create graph0 failed\n";
return 1;
}
if(device!=nullptr)
{
//绑定运行 graph 的设备
set_graph_device(graph,device);
}
// 输入图片信息
// img 的高(height)
int img_h = 300;
// img 的宽(width)
int img_w = 300;
// img 的大小(size)
int img_size = img_h * img_w * 3;
// 为输入图片申请空间,有mallloc,所以需要释放(free)
float *input_data = (float *)malloc(sizeof(float) * img_size);
int node_idx=0;
int tensor_idx=0;
//TODO: 4.1 调用 get_graph_input_tensor 获取输入Tensor
tensor_t input_tensor = get_graph_input_tensor(graph, node_idx, tensor_idx);
//TODO:4.2 并用 check_tensor_valid 确认返回值是否合法
if(!check_tensor_valid(input_tensor))
{
printf("Get input node failed : node_idx: %d, tensor_idx: %d\n",node_idx,tensor_idx);
return 1;
}
//TODO:4.3 并用 set_tensor_shape 设置输入 Tensor 的shape
// 输入的信息(1:1张图,3:3通道,img_h:高,img_w:宽)
int dims[] = {1, 3, img_h, img_w};
set_tensor_shape(input_tensor, dims, 4);
//TODO: 5. 调用 prerun_graph 函数预启动图(类似 malloc,申请资源)
prerun_graph(graph);
// //TODO: 7.1.向 input_data 写入输入的数据,
// // 将 图片 的路径名转化为 图片内容的矩阵,并赋值给 input_data
// get_input_data_ssd(image_file, input_data, img_h, img_w);
// //TODO: 7.2.并调用 set_tensor_buffer 把数据转移到输入Tensor上
// set_tensor_buffer(input_tensor, input_data, img_size * 4);
// //TODO: 8. 调用 run_graph 运行图(做一次前向传播)
// run_graph(graph, 1);
/* t0 开始时间
* t1 结束时间
* total_time = (t1-t0)*repeat_count 为使用时间
*/
struct timeval t0, t1;
float mytime=0;
// 使用opencv
cv::VideoCapture capture;
capture.open(0);
if(capture.isOpened())
{
cv::Mat image;
std::cout<<"Capture is opened" << std::endl;
for(;;)
{
capture >> image;
if(image.empty())
break;
//TODO: 7.1 向 input_data 写入输入的数据,
get_input_data_ssd(image, input_data, img_h, img_w);
// 获取当前值,并赋值给 t0(开始值)
gettimeofday(&t0, NULL);
//TODO: 7.2 并调用 set_tensor_buffer 把数据转移到输入Tensor上
set_tensor_buffer(input_tensor, input_data, img_size * 4);
//TODO: 8. 调用 run_graph 运行图(做一次前向传播)
run_graph(graph, 1);
//获取当前时间,并赋值给 t1(结束时间)
gettimeofday(&t1, NULL);
mytime = (float)((t1.tv_sec * 1000000 + t1.tv_usec) - (t0.tv_sec * 1000000 + t0.tv_usec)) / 1000;
std::cout << "--------------------------------------\n";
std::cout << " times, avg time per run is " << mytime << " ms\n";
//TODO: 9.1.调用 get_graph_output_tensor 获取输出Tensor
tensor_t out_tensor = get_graph_output_tensor(graph, 0,0);//"detection_out");
//TODO: 9.2.并用 get_tensor_shape 取得 tensor 的shape
/*
* [0]:批次:1张图
* [1]:检测到目标个数:3个目标
* [2]:outdata 的 Box 6 个信息:
* 0. 属于的类别(下标)
* 1. 属于该类别的score
* 2. 左上角点(x)相对于宽的百分比
* 3. 左上角点(y)相对于高的百分比
* 4. 右上角点(x)相对于宽的百分比
* 5. 右上角点(y)相对于高的百分比
* [3]:1 一行
*/
int out_dim[4];
get_tensor_shape( out_tensor, out_dim, 4);
//std::cout<<"out_dim" << *out_dim <<std::endl;
//TODO: 9.3.并用 get_tensor_buffer 取得缓冲区上的数据
float *outdata = (float *)get_tensor_buffer(out_tensor);
//std::cout<< "outdata" <<*outdata<<std::endl;
//获取 outdata 的 检测到目标个数(num)
int num=out_dim[1];
//设置阈值,是否为检测目标
float show_threshold=0.5;
// 通过 outdata 对 image 进行绘制边框和label信息,并保存
image = post_process_ssd(image,show_threshold, outdata, num,save_name);
//img 图像大小 480,640
cv::imshow("Sample", image);
if(cv::waitKey(10) >= 0)
break;
//TODO: 9.4 释放 out_tensor 所占空间
put_graph_tensor(out_tensor);
}
}
//TODO: 10. 最后在退出程序前依次释放各个申请的动态空间
// //TODO: 9.4 释放 out_tensor 所占空间
// //put_graph_tensor(out_tensor);
//TODO: 4.4 释放 input_tensor 所占空间
put_graph_tensor(input_tensor);
free(input_data);
//TODO: 3.2 调用 destroy_runtime_graph() 来释放资源
// 释放 graph 执行所占用的资源
postrun_graph(graph);
// 释放 graph 所占空间
destroy_runtime_graph(graph);
//TODO: 2.1 请调用 remove_model() to 释放导入的 model
remove_model(model_name);
return 0;
}
网友评论