dll报错 paddle_工业党福利:使用PaddleX高效实现指针型表计读取(二)

【飞桨开发者说】李康宇,PPDE飞桨开发者技术专家,工作于机械科学研究总院,视觉研发工程师

ada3b28288d8aaf33f6f5e281a0ac7fa.png

1190000037510670

上一篇文章《使用PaddleX高效实现指针型表计读取(一)》中介绍了非常好用的深度学习开发工具PaddleX,以压力表分割为例,阐述了PaddleX 图形化开发界面的使用方法。

=

本文介绍如何将飞桨的C++预测代码生成为Visual Studio下的解决方案,以及最关键的,如何将C++预测代码生成为可调用的动态链接库dll,打通真正可以工业实战的开发流程。

本章目录:使用CMake编译PaddleX C++预测代码生成本地化工程文件。

将工程文件转化成具备输入输出接口的DLL文件。

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

使用CMake编译PaddleX 预测代码生成本地化工程文件

1. 准备工作

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

下载PaddleX develop分支的预测代码:

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

预编译 Windows 预测库下载地址:

c48430080d880e3ca83b285d3a45821b.png

1190000037510670

将上述下载的预测库fluid_inference.zip,与OpenCV和PaddleX三个文件夹放在同一个路径下,方便操作。

f51098de5f38f1872d0610834e88b0a9.png

1190000037510670

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

2c0b224f74b91a9def0760ba4ac21dc4.png

1190000037510670

2. CMake编译

打开PaddleX-develop/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)

c7cbc54999be225520aaae67ccf913a3.png

1190000037510670

打开CMake:source code源码路径选为PaddleX-develop/ deploy/cpp所在目录;

在PaddleX-develop/ deploy/cpp下新建文件夹build_out,用于存储编译后的文件;

选择好路径后,点击Configure。

7efbd4c6239f9993f8c20ad2c0fff139.png

1190000037510670

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

76e1d74aa303b6e5b3188982eb971714.png

1190000037510670

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

dfd4030566b07bd68565126e9cfd27e4.png

1190000037510670

按照下图:将CUDA_LIB、OPENCV_DIR和PADDLE_DIR的路径添加进去;

点击Configure;

点击Generate。

215c192c787081e7f543815735942286.png

1190000037510670

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

2. 将工程文件转化成具备输入输出接口的DLL文件

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

f4d4e54bc702b24448cd292d9a1783db.png

1190000037510670

右键segmenter,查看其属性。将配置类型改为动态库;

指定DLL的输出目录;

确认配置为Release,平台为x64。

71ce49f6a5c846fdfe98f921cd445183.png

1190000037510670

配置好后,接下来是修改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:UsersAdminDesktopinference_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);

}

1190000037510670

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

7ce9d97fdf7ed8aae6b209c952514698.png

1190000037510670

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

a82c17931b01f56d9427f7fd56810ae6.png

1190000037510670

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

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

ec6d881e226fd963c333a5f324866aca.png

1190000037510670

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

198f393dbedf53562e3280975c347f5a.png

1190000037510670

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

b12e4075e5b7e3d2432ba6a7d6ad38ed.png

1190000037510670

在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 

{

Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes);

ptr += bmpData.Stride;

}

bmp.UnlockBits(bmpData);

return rgbValues;

}

}

}

1190000037510670

问题一:为了解决该问题,我们可以在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 

{

Marshal.Copy(ptr, rgbValues, i * rowBytes, rowBytes);

ptr += bmpData.Stride;

}

bmp.UnlockBits(bmpData);

return rgbValues;

}

1190000037510670

通过上述代码,即可将指定路径下的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类型图像

1190000037510670

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

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

3061b9c27ada18c1e293b35ab5830c33.png

1190000037510670

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

09da9b867b50b341c3ce31361b2cdcaf.png

1190000037510670

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

be7fb9d24edb3e134a5fbdc78715a811.png

1190000037510670

都看到这里了,还不点个赞,关注一下?谢谢大家!最后,再一次欢迎大家给这款好用的工具点个star!

PaddleX Github链接:

a7dbc13147ac81733bf1a6546eaaa7aa.png

1190000037510670

如在使用过程中有问题,可加入飞桨官方QQ群进行交流:1108045677。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值