0#02 将 MSSD 制作成视频流

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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352