基于OpenVINO C++ 接口部署飞桨表计识别模型

基于OpenVINO C++ 接口部署飞桨表计识别模型

1 项目说明

在该项目中,主要向大家介绍如何基于基于  OpenVINO C++  接口来实现对指针型表计读数。

在电力能源厂区需要定期监测表计读数,以保证设备正常运行及厂区安全。但厂区分布分散,人工巡检耗时长,无法实时监测表计,且部分工作环境危险导致人工巡检无法触达。针对上述问题,希望通过摄像头拍照->智能读数的方式高效地完成此任务。

基于OpenVINO C++ 接口部署飞桨表计识别模型_第1张图片

为实现智能读数,我们采取目标检测->语义分割->读数后处理的方案:

· 第一步,使用目标检测模型定位出图像中的表计;

· 第二步,使用语义分割模型将各表计的指针和刻度分割出来;

· 第三步,根据指针的相对位置和预知的量程计算出各表计的读数。

整个方案的流程如下所示:

基于OpenVINO C++ 接口部署飞桨表计识别模型_第2张图片

 2 环境准备 (Ubuntu)

由于本次任务将用到 OpenCV 和 OpenVINO 的相关组件,所以需要在进行代码开发之前,完成相关 runtime 依赖的安装。这边以 Ubuntu 系统作为示例,具体方法可以参考:

1.OpenVINO: https://docs.openvino.ai/latest/openvino_docs_install_guides_installing_openvino_linux.html#install-openvino

2.OpenCV: https://docs.opencv.org/4.x/d7/d9f/tutorial_linux_install.html

注:由于该实例中提供的 CMakeList 使用 OpenCV 的默认路径,因此需要在完成 OpenCV 的编译后,执行 make install 命令。

3 数据准备

3.1 测试数据下载

本案例开放了表计检测数据集,使用该数据集可以测试本次 OpenVINO 部署的模型精度和识别性能。

·表计测试图片:

https://bj.bcebos.com/paddlex/examples/meter_reader/datasets/meter_test.tar.gz

解压后的表计测试图片的文件夹内容如下:

一共有58张测试图片。

meter_test/
|-- 20190822_105.jpg
|-- 20190822_142.jpg
|-- ... ...

 由于本次任务主要是完成推理阶段的部署,所以我们只需要从这58张测试图片中随机选取测试用例即可。

3.2 预训练模型下载

该实例代码将演示如何在通过 OpenVINO 完成 Paddle 模型在 Intel 平台上部署。我们可以使用训练好的 PPYOLO 和 DeepLabV3P 模型对测试用的圆形表计图片进行识别,实现表面缺陷的识别。预训练模型下载地址:

表计检测预训练模型:

https://bj.bcebos.com/paddlex/examples2/meter_reader/meter_det_model.tar.gz

刻度和指针分割预训练模型:

https://bj.bcebos.com/paddlex/examples2/meter_reader/meter_seg_model.tar.gz

3.3 模型转换

目前 OpenVINO 2022.1的 runtime 可以直接支持对 Paddle 静态模型的读取和加载,但为了追求更好的性能,这里我们还是展示了如果通过 OpenVINO 的 Model Optimizer 工具对下载后的 Paddle 模型进行转换。

$ mo --input_model meter_det_model/model.pdmodel
$ mo --input_model meter_seg_model/model.pdmodel

 转换成功以后会在当前目录下分别生成以下三个模型文件:

meter_det_model/
|-- model.xml
|-- model.bin
|-- model.mapping

其中.xml文件用来描述模型的拓扑结构,.bin存储模型的权重信息,.mapping则是用来记录转换前后的2个模型的算子映射关系。实际推理过程中只需要用到.xml及.bin两个文件即可。

4 代码编译

4.1 代码下载

下载仓库中该任务的源码包:

meter_reader_openvino_cpp-main.zip 或者也可以通过

git clone https://github.com/OpenVINO-dev-contest/meter_reader_openvino_cpp.git

 下载到本地电脑,本进行解压。

4.2 修改CMakeLists.txt

将 CMakeLists.txt 其中的 OpenVINO 相关环境的路径换成你本地路径。

cmake_minimum_required(VERSION 3.10)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenCV REQUIRED)
#find_package(OpenVINO REQUIRED)

set(openvino_LIBRARIES "/home/ethan/intel/openvino_2022.1.0.643/runtime/lib/intel64/libopenvino.so")

include_directories(
    ./
    /home/ethan/intel/openvino_2022.1.0.643/runtime/include
    /home/ethan/intel/openvino_2022.1.0.643/runtime/include/ie
    /home/ethan/intel/openvino_2022.1.0.643/runtime/include/ngraph
    /home/ethan/intel/openvino_2022.1.0.643/runtime/include/openvino
    ${OpenCV_INCLUDE_DIR}
)
link_directories("/home/ethan/intel/openvino_2022.1.0.643/runtime/lib")
aux_source_directory(src SRC)
add_executable(meter_reader main.cpp ${SRC})
target_link_libraries(meter_reader PRIVATE ${openvino_LIBRARIES} ${OpenCV_LIBS})

4.3 编译

运行下列指令,完成后将在build目录下生成meter_reader可执行文件。

$ cd ~/meter_reader_openvino_cpp
 $ mkdir build && cd build
 $ cmake ..
 $ make

 5 代码模块说明

本示例的推理部分模块大致可以分成三个部分:

检测任务模块

分割任务模块

后处理模块

关于 OpenVINO C++ 接口的部署流程大家可以参考这个文档:Integrate OpenVINO™ with Your Application:

https://docs.openvino.ai/latest/openvino_docs_OV_UG_Integrate_OV_with_your_application.html


bool Segmenter::init(string model_path)
{
    _model_path = model_path;
    ov::Core core;
    shared_ptr model = core.read_model(_model_path);
    map name_to_shape;
    model->reshape({{-1, 3, 512, 512}});
    ov::CompiledModel segment_model = core.compile_model(model, "CPU");
    segment_infer_request = segment_model.create_infer_request();
    return true;
}

bool Segmenter::process_frame(vector &inframes, vector &masks)
{
    static map color_table = {
        {0, Vec3b(0, 0, 0)},
        {1, Vec3b(20, 59, 255)},
        {2, Vec3b(120, 59, 200)},
    };
    float mean[3] = {0.5, 0.5, 0.5};
    float std[3] = {0.5, 0.5, 0.5};

    int batch_size = inframes.size();
    ov::Tensor input_tensor0 = segment_infer_request.get_input_tensor(0);
    input_tensor0.set_shape({batch_size, 3, 512, 512});
    auto data0 = input_tensor0.data();
    // nhwc -> nchw
    for (int batch = 0; batch < batch_size; batch++)
    {
        resize(inframes[batch], inframes[batch], Size(512, 512));
        for (int h = 0; h < 512; h++)
        {
            for (int w = 0; w < 512; w++)
            {
                for (int c = 0; c < 3; c++)
                {
                    int out_index = batch * 3 * 512 * 512 + c * 512 * 512 + h * 512 + w;
                    data0[out_index] = float(((float(inframes[batch].at(h, w)[c]) / 255.0f) - mean[c]) / std[c]);
                }
            }
        }
    }

    //start inference
    segment_infer_request.infer();

    //extract the output data
    auto output = segment_infer_request.get_output_tensor(0);
    const float *result = output.data();
    // nchw -> nhwc
    for (int batch = 0; batch < batch_size; batch++)
    {
        Mat mask = Mat::zeros(512, 512, CV_8UC1);
        for (int h = 0; h < 512; h++)
        {
            for (int w = 0; w < 512; w++)
            {
                int argmax_id;
                float max_conf = numeric_limits::min();
                for (int c = 0; c < 3; c++)
                {
                    int out_index = batch * 3 * 512 * 512 + c * 512 * 512 + h * 512 + w;
                    float out_value = result[out_index];
                    if (out_value > max_conf)
                    {
                        argmax_id = c;
                        max_conf = out_value;
                    }
                }
                mask.at(h, w) = argmax_id;
            }
        }
        masks.push_back(mask);
    }

    return true;
}

5.3 后处理模块

这里的后处理模块其实是复用了PaddleX中提供的参考示例,整体逻辑大家可以参考开篇的那张图片,关于具体的功能模块我们可以直接看其中的头文件。这里我们额外定义了一个Visualize函数,用来将检测模型与表计读数的结果以bounding box和读数的形式标注在原始输入图片上,并保存在本地。

  1. Erode 腐蚀分割结果,分离一些“粘连”的的临近刻度;

  2. CircleToRectangle 将分割模型的输出的表计原型mask转化为长方形;

  3. RectangleToLine 将方形的表计mask中关于指针和刻度的像素点数据以一维vector进行表示;

  4. MeanBinarization 二值化操作,刻度中心点置1,非中心点置0;

  5. LocateScale 及 LocatePointer 定位每个刻度和指针的具体位置;

  6. GetRelativeLocation 找到刻度和指针的相对位置;

  7. GetMeterReading 根据表计的量程以及单位刻度的数值,计算实际指针所指向的刻度值。

 


bool Erode(const int32_t &kernel_size,
           const vector &seg_results,
           vector> *seg_label_maps);

bool CircleToRectangle(
    const vector &seg_label_map,
    vector *rectangle_meter);

bool RectangleToLine(const vector &rectangle_meter,
                     vector *line_scale,
                     vector *line_pointer);

bool MeanBinarization(const vector &data,
                      vector *binaried_data);

bool LocateScale(const vector &scale,
                 vector *scale_location);

bool LocatePointer(const vector &pointer,
                   float *pointer_location);

bool GetRelativeLocation(
    const vector &scale_location,
    const float &pointer_location,
    MeterResult *result);

bool CalculateReading(const MeterResult &result,
                      float *reading);

bool PrintMeterReading(const vector &readings);

bool Visualize(Mat &img,
               vector &detected_objects,
               const vector &readings);

bool GetMeterReading(
    const vector> &seg_label_maps,
    vector *readings);

6 测试结果

在终端上运行 meter_reader 可执行文件,其中第一个参数代表检测模型的路径,第二个参数代表分割模型的路径,第三个参数代表测试图片的路径。

执行结束后会在本地保存本次推理的结果图片,具体示例如下:

基于OpenVINO C++ 接口部署飞桨表计识别模型_第3张图片

你可能感兴趣的:(OpenVINO,openvino,paddlepaddle,人工智能)