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 
// 使用 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 boxes;
    // 设置使用的线宽(line_width)
    int line_width=raw_w*0.005;
    // logout: 检测到目标个数
    printf("detect result num: %d \n",num);
    for (int i=0;i=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<

对于函数的修改已经完成了,接下来对主函数加入 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 <= 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: [email protected]
 */

#include 
#include 
#include 
#include 
#include 
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "tengine_c_api.h"
#include 
#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 boxes;
    // 设置使用的线宽(line_width)
    int line_width=raw_w*0.005;
    // logout: 检测到目标个数
    printf("detect result num: %d \n",num);
    for (int i=0;i=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<> 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 <= 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;
}

你可能感兴趣的:(0#02 将 MSSD 制作成视频流)