工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)

最近在做一个工业巡检的项目,主要涉及的内容是指针型表计的读取。本系列文章主要介绍实现表计读取的全流程开发(立个FLAG,想想真是肝...留下了不争气的眼泪),其中主要使用的工具为百度开发的PaddleX和Visual studio 2019。

如有问题,欢迎联系,作者微信:yikeAI14539

一般来讲,在工业领域使用深度学习技术来实施的项目主要为工业质检和工业巡检两部分,实现这两部分的流程均为:

v2-209fd6b2ebff7977b375ff48e88bb249_b.jpg

本系列文章的内容包含了上述流程的全部内容,其目录如下:

  1. 高效语义分割:基于PaddleX零代码快速实现表计分割
  2. 工业场景适配:Windows下PaddleX的C++编译、C#界面开发及dll调用
  3. 表计读取实战:表计读取算法开发方案

李是Lyapunov的李:工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(1)

李是Lyapunov的李:工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)

李是Lyapunov的李:工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(3)

正文开始:

二、工业场景适配:Windows下PaddleX的C++编译并生成dll

本节目录

  1. 使用CMake编译PaddleX C++文件生成本地化工程文件
  2. 生成开放输入输出接口的DLL文件
  3. 使用C#编写界面,调用DLL实现压力表分割

1. 使用CMake编译PaddleX C++文件生成本地化工程文件

1.1 准备工作

安装CMake 3.16.5,Visual Studio 2019,OpenCV 3.4.6三个软件。

下载develop分支下的预测代码:github.com/PaddlePaddle

根据自己的CUDA和cuDNN版本,下载相应的Paddle官方提供的Windows预测库,我所测试的版本为cuda10.0_cudnn7_avx_mkl,其他版本未测试。

安装与编译 Windows 预测库

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第1张图片

将上述下载的OpenCV、fluid_inference_install_dir.zip、PaddleX-develop三个文件放在同一个路径下,方便操作。

v2-4fa433aa0072ed9bad06c1bbe9635a74_b.png

将Opencv的bin文件路径添加至系统变量Path中:

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第2张图片

1.2 CMake编译

打开deploy/cpp路径下的CMakeLists.txt,将其中的:add_executable(segmenter demo/segmenter.cpp src/transforms.cpp src/paddlex.cpp src/visualize.cpp)改为:ADD_library(segmenter SHARED demo/segmenter.cpp src/transforms.cpp src/paddlex.cpp src/visualize.cpp)

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第3张图片

打开CMake:①source code源码路径选为PaddleX-develop中cpp所在目录;②在当前目录下新建文件夹build_out,用于存储编译后的文件;③选择好路径后,点击Configure。

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第4张图片

将生成器指定为Visual Studio 2019,x64:

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第5张图片

点击Finish,此时会出现报错,这是因为没有设置CUDA_LIB、OPENCV_DIRPADDLE_DIR:

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第6张图片

按照下图:①将CUDA_LIB、OPENCV_DIR和PADDLE_DIR的路径添加进去;②点击Configure;③点击Generate。

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第7张图片

在Configuring done和Generating done后,点击Open Project,即会自动用Visual Studio 2019打开本地化工程文件。

2. 生成开放输入输出接口的DLL文件

接下来打开PaddleX中编译的本地化工程文件,因为我要做的是分割任务,涉及到其中的segmenter部分。

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第8张图片

右键segmenter,查看其属性。①将配置类型改为动态库;②指定DLL的输出目录;③确认配置为Release,平台为x64

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第9张图片

配置好后,接下来是修改segmenter.cpp代码(这里先不讲为什么这么修改,下一小节会说明):

#include 
#include 

#include 
#include   // NOLINT
#include 
#include 
#include 
#include 
#include 
#include "include/paddlex/paddlex.h"
#include "include/paddlex/visualize.h"

extern "C" __declspec(dllexport) cv::Mat* LoadModel(char *input, int width, int height);
__declspec(dllexport) cv::Mat* LoadModel(char* input, int width, int height) {
  std::string model_dir = "C:\\Users\\Admin\\Desktop\\inference_model";
  std::string key = "";
  int gpu_id = 0;
  bool use_trt = 0;
  bool use_gpu = 0;

  PaddleX::SegResult result;
  cv::Mat im(height, width, CV_8UC3, input);
  //加载模型及创建分割
  PaddleX::Model model;
  model.Init(model_dir, use_gpu, use_trt, gpu_id, key);
  model.predict(im, &result);
  //结果返回
  cv::Mat vis_img = PaddleX::Visualize(im, result, model.labels);
  return new cv::Mat(vis_img);
}

修改好上述内容后,右键 ==> 仅用于项目 ==> 仅重新生成segmenter

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第10张图片

生成成功后,就可以看到之前指定的输出目录中看到生成的DLL文件了

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第11张图片

3. 使用C#编写界面,调用DLL实现压力表分割

工业上一般使用C#来开发用户界面,因此需要将上述工程文件生成为在从C#中可调用的。不管是做目标检测还是语义分割,我们都需要将图像输入至模型中,然后将检测或分割的结果输出。在本节中,我以压力表的语义分割为例,介绍如何生成具有输入和输出接口的DLL文件(在本例中,输入和输出均为图像)。

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第12张图片

打开Visual studio 2019,创建一个Windows窗体应用

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第13张图片

在窗体界面,设置一个Button控件和两个Picturebox控件

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第14张图片

在C#中,我们使用Bitmap类将对图像进行操作,主要为加载指定路径下的图像。但是Bitmap类并不适用于C++中。所以首先需要解决的问题是正确地从C#中传递图像数据到C++端,然后再将c++中分割后的结果传回C#中。

因此需要解决的问题有两个:

  • 问题一:如何将C#中图像数据传递至C++;
  • 问题二:如何在C++中接收图像数据,并将分割结果返回至C++。

这里先将C#的代码列出,再一一说明两个问题:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using OpenCvSharp;

namespace PaddleX_dll_test
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        [DllImport("segmenter.dll", EntryPoint = "LoadModel", SetLastError = true, CharSet = CharSet.Ansi)]
        static extern IntPtr LoadModel(byte[] input, int height, int width);  //out IntPtr seg_res

        private void Button1_Click(object sender, EventArgs e)
        {
            string image_path = "C:/Users/Admin/Desktop/yalibiao_126.JPG";         
            Bitmap bmp = new Bitmap(image_path);
            pictureBox1.Image = bmp;
            int stride;
            byte[] source = GetBGRValues(bmp, out stride);
            IntPtr seg_img = LoadModel(source, bmp.Width, bmp.Height);  //out seg_img
            Mat img = new Mat(seg_img);
            Bitmap seg_show = new Bitmap(img.Cols, img.Rows, (int)img.Step(), System.Drawing.Imaging.PixelFormat.Format24bppRgb, img.Data);

            pictureBox2.Image = seg_show;
        }
        // 将Btimap类转换为byte[]类函数
        public static byte[] GetBGRValues(Bitmap bmp, out int stride)
        {
            var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
            stride = bmpData.Stride;
            var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
            var imgBytes = bmp.Height * rowBytes;
            byte[] rgbValues = new byte[imgBytes];
            IntPtr ptr = bmpData.Scan0;
            for (var i = 0; i < bmp.Height; i++)
            {
                Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes); 
                ptr += bmpData.Stride;
            }
            bmp.UnlockBits(bmpData);
            return rgbValues;
        }
    }
}

问题一:为了解决该问题,我们可以首先在C#中将Bitmap类转换为byte[]类,再传递给C++去处理。涉及到这一部分的代码为:

// C# 代码
//也可设置为可选路径,我这里就直接指定了 
string image_path = "C:/Users/Admin/Desktop/yalibiao_126.JPG";      
Bitmap bmp = new Bitmap(image_path);   
int stride;
byte[] source = GetBGRValues(bmp, out stride);  // 类型转换  bitmap ==> byte[]
...

// 将Btimap类转换为byte[]类
public static byte[] GetBGRValues(Bitmap bmp, out int stride)
        {
            var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            var bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
            stride = bmpData.Stride;
            var rowBytes = bmpData.Width * Image.GetPixelFormatSize(bmp.PixelFormat) / 8;
            var imgBytes = bmp.Height * rowBytes;
            byte[] rgbValues = new byte[imgBytes];
            IntPtr ptr = bmpData.Scan0;
            for (var i = 0; i < bmp.Height; i++)
            {
                Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes); 
                ptr += bmpData.Stride;
            }
            bmp.UnlockBits(bmpData);
            return rgbValues;
        }

通过上述代码,即可将指定路径下的bitmap类图像转为byte[]字节数组的类型。

问题二:在C++中,我们需要将接收到的byte[]类型数据转换成易操作的OpenCV Mat类型。为了还原图像,需要用到图像的byte[]数据、长、宽和通道数。由于我所用的图像通道数已知,就只把byte[]数据、长、宽三个数据传到LoadModel中。然后通过指针的方式将分割后的图像返回至C#中。涉及到这一部分的代码为:

//C#代码
static extern IntPtr LoadModel(byte[] input, int height, int width); // LoadModel的类型为IntPtr
...
IntPtr seg_img = LoadModel(source, bmp.Width, bmp.Height);// 传递图像数据:byte[]数组、长、宽,并接收返回值
...


//C++代码
extern "C" __declspec(dllexport) cv::Mat* LoadModel(char *input, int width, int height);//声明为C编译、连接方式的外部函数
__declspec(dllexport) cv::Mat* LoadModel(char* input, int width, int height) // 通过地址返回Mat类型的分割图像结果
...
cv::Mat im(height, width, CV_8UC3, input);  // 由byte[]数组、长、宽和通道数生成Mat类型图像

至此,已经用C#写好窗体应用程序。

在运行前,需要将segmenter.dll目录下的全部文件及其lib文件复制到C#项目的运行目录bin/Debug目录下

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第15张图片

其中有几个文件只有dll,没有对应的lib文件,这个时候,我们需要在Paddle预测库文件中找到如下的lib文件,这里推荐直接使用everything搜索。

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第16张图片

复制完全部文件后,点击启动进行测试。可以看到,界面左边是输入的原始图片,右边是经过C++代码分割后返回的图片。这说明我们成功的生成了具有输入和输出接口的DLL文件。

工业党福利:使用PaddleX高效实现指针型仪表读取系列文章(2)_第17张图片

欢迎转发。如需转载,或有相关问题,欢迎联系。作者微信:yikeAI14539

v2-ea8854df905c6fbb9a14f7f9b822b22c_b.jpg

部分内容参考好友写的文章:

富土康一号质检员:PaddleX的C++部署的方式的几种使用方式 富土康一号质检员:系列文章 如何使用PaddleDetection做一个完整项目(三)

你可能感兴趣的:(c#,c++,python,java,opencv)