Linux调用pb模型并编译为so文件


写在前面

本文主要使用g++进行编译,介绍相关指令。

编译so文件

需要预先编译安装OpenCV。
pch.h

#ifndef PCH_H
#define PCH_H

#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/dnn.hpp>

using namespace std;
using namespace cv;
using namespace dnn;

// 使用C++时必须添加extern "C",可通过__cplusplus判断编译环境
#ifdef __cplusplus
extern "C"
{
#endif
   const char* predict(const char* filepath);
#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
extern "C"
{
#endif
   int add(int num1, int num2);
#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
extern "C"
{
#endif
   string cv_predict(const char* filepath);
#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
extern "C"
{
#endif
   const char* stringToCharP(std::string str);
#ifdef __cplusplus
}
#endif

#endif

pch.cpp

#include "pch.h"

String modelWeights = "model.pb";
Net model = readNetFromTensorflow(modelWeights);
clock_t start_time, end_time;

char letter[36] = {
        '0','1','2','3','4','5','6','7','8','9','a','b',
        'c','d','e','f','g','h','i','j','k','l','m','n',
        'o','p','q','r','s','t','u','v','w','x','y','z'
};

int region[4][2] = { {0,16},{14,31},{30,46},{44,60} };

string cv_predict(const char* filepath) {
        Mat img = imread(filepath);
        Mat img_split, blob, out;
        string result = "";
        int max_location = 0;
        float max_value = 0.0;
        cvtColor(img, img, CV_BGR2GRAY);
        for (int r = 0; r < 4; r++) {
                max_location = 0;
                max_value = 0.0;
                img_split = img(Rect(region[r][0], 0, region[r][1] - region[r][0], 25));
                resize(img_split, img_split, Size(15, 25));
                blob = blobFromImage(img_split, 1.0, Size(15, 25), Scalar(0, 0, 0), true, false);
                model.setInput(blob);
                out = model.forward();
                for (int i = 0; i < 36; i++) {
                        if (out.at<float>(0, i) > max_value) {
                                max_value = out.at<float>(0, i);
                                max_location = i;
                        }
                }
                result += letter[max_location];
        }
        return result;
}

// 将string转换为char*,直接返回c_str()会乱码
const char* stringToCharP(std::string str) {
        char* result = new char[str.length() + 1];
        strcpy(result, str.c_str());
        return (const char*)result;
}

const char* predict(const char* filepath)
{
        model.setPreferableBackend(DNN_BACKEND_OPENCV);
        model.setPreferableTarget(DNN_TARGET_CPU);

        string result = cv_predict(filepath);
        const char* c_char_p_result = stringToCharP(result);
        return c_char_p_result;
}

// 测试用的函数
int add(int num1,int num2)
{
        return num1 + num2;
}

编译指令
必须和OpenCV的库文件关联起来。

g++ pch.cpp -L/usr/local/lib/ -lopencv_imgproc -lopencv_highgui -lopencv_dnn -lopencv_imgcodecs -fPIC -shared -o libmodel.so

另附一种cmake编译的方法:
CMakeLists.txt

# cmake needs this line
cmake_minimum_required(VERSION 3.1)

# Define project name
project(model)

# Find OpenCV, you may need to set OpenCV_DIR variable
# to the absolute path to the directory containing OpenCVConfig.cmake file
# via the command line or GUI
find_package(OpenCV REQUIRED)

# If the package has been found, several variables will
# be set, you can find the full list with descriptions
# in the OpenCVConfig.cmake file.
# Print some message showing some of them
message(STATUS "OpenCV library status:")
message(STATUS "    config: ${OpenCV_DIR}")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

# Declare the executable target built from your sources
add_library(model SHARED pch.cpp pch.h)

# Link your application with OpenCV libraries
target_link_libraries(model PRIVATE ${OpenCV_LIBS})

cmake指令:
将CMakeLists.txt和pch.h、pch.cpp放到同一目录。

cmake .
make

编译完成后得到libmodel.so。
查看动态库依赖关系:
ldd -r libmodel.so
如果没有无法解析的符号(引用下VS2019的报错),说明编译成功。

调用库文件

pch.h

#include <iostream>

using namespace std;

extern "C"
{
        const char* predict(const char* filepath);
}

extern "C"
{
        int add(int num1, int num2);
}

test.cpp

#include <pch.h>

int main() {
        int a = add(123,123);
        printf("%d\n",a);
        const char* b = predict("captcha.png");
        printf("%s\n",b);
}

编译指令

g++ test.cpp -L. -lmodel -o test

编译完成后得到一个二进制文件,类似于Windows的exe,运行指令(记得放一张测试用的验证码,名字修改为captcha.png):
./test