Ubuntu20.04环境下编译MNN
编译环境准备
- cmake(建议使用3.10或以上版本)
- protobuf(使用3.0或以上版本)
- gcc(使用4.9或以上版本)
1.gcc安装
sudo apt update
sudo apt install build-essential
2.cmake安装
- 从cmake官网下载cmake,选择linux版本进行下载
下载地址: cmake - 在cmake源码所在文件夹中打开命令终端,解压文件
tar -zxv -f cmake-3.23.0-rc3.tar.gz
- 进入解压后的文件夹中执行
./bootstrap
如果出现下面的错误,则需安装libssl-dev,安装完后再重新执行./bootstrap
sudo apt-get install libssl-dev
- 编译构建cmake
make
- 安装cmake
sudo make install
- 安装完后输入
cmake --version
验证是否安装完成
3.安装protobuf
- 安装依赖
sudo apt-get install autoconf automake libtool curl make g++ unzip libffi-dev -y
- 下载protobuf源码
下载地址:protobuf - 解压压缩包
tar -zxv -f protobuf-cpp-3.20.0-rc-1.tar.gz
- 进入解压后的文件夹,生成配置文件
cd protobuf-3.20.0-rc-1/
./autogen.sh
- 配置环境
./configure
- 编译源码
make
- 安装
sudo make install
- 刷新动态库
sudo ldconfig
- 输入
protoc --version
查看是否安装成功
编译MNN
- 下载 MNN源码并解压
下载地址:MNN - 解压
unzip MNN-master.zip
- 进入解压后的文件夹中并执行
cd MNN
./schema/generate.sh
- 本地编译,编译完成后本地出现MNN的动态库
mkdir build && cd build && cmake … && make -j8
Android编译
- 在https://developer.android.com/ndk/downloads/下载安装NDK,建议使用最新稳定版本
- 在 .bashrc 或者 .bash_profile 中设置 NDK 环境变量,例如:export ANDROID_NDK=/Users/username/path/to/android-ndk-r14b
- cd /path/to/MNN
- ./schema/generate.sh
- ./tools/script/get_model.sh(可选,模型仅demo工程需要)。注意get_model.sh需要事先编译好模型转换工具,参见这里。
- cd project/android
- 编译armv7动态库:mkdir build_32 && cd build_32 && …/build_32.sh
- 编译armv8动态库:mkdir build_64 && cd build_64 && …/build_64.sh
部署mnist
安装opencv库
部署mnist 还需要opencv库,所以还需要安装一下opencv
可以根据这篇文章通过克隆OpenCV源代码进行安装
博客
根据博客安装完成后,编写一个简单的opencv程序验证是否可用
创建main.cpp文件,并使用gcc编译并运行
g++ main.cpp -o output `pkg-config --cflags --libs opencv4`
./output
#include <opencv2/highgui.hpp>
#include "opencv2/imgcodecs/legacy/constants_c.h"
#include "opencv2/imgproc/types_c.h"
#include <iostream>
int main( int argc, char** argv ) {
cv::Mat image;
image = cv::imread("test.jpg" , CV_LOAD_IMAGE_COLOR);
if(! image.data ) {
std::cout << "Could not open or find the image" << std::endl ;
return -1;
}
std::cout << "image wide: "<< image.cols << ",image high: " << image.rows << ",image channels: "<< image.channels() << std::endl;
/* display image
cv::namedWindow( "Display window", cv::WINDOW_AUTOSIZE );
cv::imshow( "Display window", image );
cv::waitKey(0);
*/
size_t y,x;// y is row, x is col
int c; // c is channel
y = x = 250;
c = 2;
// row_ptr is the head point of y row
unsigned char *row_ptr = image.ptr<unsigned char>(y);
// data_ptr points to pixel data
unsigned char *data_ptr = &row_ptr[x * image.channels()];
unsigned char data = data_ptr[c];
// use cv::Mat::at() to get the pixel value
// unsigned char is not printable
// std::cout << std::isprint(data)<<std::isprint(image.at<cv::Vec3b>(y,x)[c]) << std::endl;
std::cout << "pixel value at y, x ,c"<<static_cast<unsigned>(image.at<cv::Vec3b>(y,x)[c]) << std::endl;
return 0;
}
运行如果出现下面情况,说明库的路径没有配置正确
解决办法:
sudo vi /etc/ld.so.conf.d/opencv.conf
在/etc/ld.so.conf.d/目录下sudo vim /etc/ld.so.conf.d/opencv.conf
写入两行:
/usr/local/lib
~/opencv_build/opencv/build/lib(这里指的是你安装的opencv路径下的lib)
保存退出,运行sudo ldconfig
,问题解决
再运行./output
能够输出正确信息说明opencv安装成功
部署mnist
创建c++文件,按照MNN官方文档推理过程编写程序,mnn模型下载地址
#include "Backend.hpp"
#include "Interpreter.hpp"
#include "MNNDefine.h"
#include "Interpreter.hpp"
#include "Tensor.hpp"
#include <math.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <stdio.h>
using namespace MNN;
using namespace cv;
int main(void)
{
// 填写自己的测试图像和mnn模型文件路径
std::string image_name = "test.jpg";
const char* model_name = "mnist.mnn";
// 一些任务调度中的配置参数
int forward = MNN_FORWARD_CPU;
// int forward = MNN_FORWARD_OPENCL;
int precision = 2;
int power = 0;
int memory = 0;
int threads = 1;
int INPUT_SIZE = 28;
cv::Mat raw_image = cv::imread(image_name.c_str());
//imshow("image", raw_image);
int raw_image_height = raw_image.rows;
int raw_image_width = raw_image.cols;
cv::Mat image;
cv::resize(raw_image, image, cv::Size(INPUT_SIZE, INPUT_SIZE));
// 1. 创建Interpreter, 通过磁盘文件创建: static Interpreter* createFromFile(const char* file);
std::shared_ptr<Interpreter> net(Interpreter::createFromFile(model_name));
MNN::ScheduleConfig config;
// 2. 调度配置,
// numThread决定并发数的多少,但具体线程数和并发效率,不完全取决于numThread
// 推理时,主选后端由type指定,默认为CPU。在主选后端不支持模型中的算子时,启用由backupType指定的备选后端。
config.numThread = threads;
config.type = static_cast<MNNForwardType>(forward);
MNN::BackendConfig backendConfig;
// 3. 后端配置
// memory、power、precision分别为内存、功耗和精度偏好
backendConfig.precision = (MNN::BackendConfig::PrecisionMode)precision;
backendConfig.power = (MNN::BackendConfig::PowerMode) power;
backendConfig.memory = (MNN::BackendConfig::MemoryMode) memory;
config.backendConfig = &backendConfig;
// 4. 创建session
auto session = net->createSession(config);
net->releaseModel();
clock_t start = clock();
// preprocessing
image.convertTo(image, CV_32FC3);
image = image / 255.0f;
// 5. 输入数据
// wrapping input tensor, convert nhwc to nchw
std::vector<int> dims{1, INPUT_SIZE, INPUT_SIZE, 3};
auto nhwc_Tensor = MNN::Tensor::create<float>(dims, NULL, MNN::Tensor::TENSORFLOW);
auto nhwc_data = nhwc_Tensor->host<float>();
auto nhwc_size = nhwc_Tensor->size();
::memcpy(nhwc_data, image.data, nhwc_size);
std::string input_tensor = "data";
// 获取输入tensor
// 拷贝数据, 通过这类拷贝数据的方式,用户只需要关注自己创建的tensor的数据布局,
// copyFromHostTensor会负责处理数据布局上的转换(如需)和后端间的数据拷贝(如需)。
auto inputTensor = net->getSessionInput(session, nullptr);
inputTensor->copyFromHostTensor(nhwc_Tensor);
// 6. 运行会话
net->runSession(session);
// 7. 获取输出
std::string output_tensor_name0 = "dense1_fwd";
// 获取输出tensor
MNN::Tensor *tensor_scores = net->getSessionOutput(session, output_tensor_name0.c_str());
MNN::Tensor tensor_scores_host(tensor_scores, tensor_scores->getDimensionType());
// 拷贝数据
tensor_scores->copyToHostTensor(&tensor_scores_host);
// post processing steps
auto scores_dataPtr = tensor_scores_host.host<float>();
// softmax
float exp_sum = 0.0f;
for (int i = 0; i < 10; ++i)
{
float val = scores_dataPtr[i];
exp_sum += val;
}
// get result idx
int idx = 0;
float max_prob = -10.0f;
for (int i = 0; i < 10; ++i)
{
float val = scores_dataPtr[i];
float prob = val / exp_sum;
if (prob > max_prob)
{
max_prob = prob;
idx = i;
}
}
printf("the result is %d\n", idx);
return 0;
}
编写CMakeLists.txt,注意要将里面MNN的地址换成自己编译的MNN地址
cmake_minimum_required(VERSION 3.10)
project(mnist)
set(CMAKE_CXX_STANDARD 11)
find_package(OpenCV REQUIRED)
set(MNN_DIR /home/chen/MNN)
include_directories(${MNN_DIR}/include)
include_directories(${MNN_DIR}/include/MNN)
include_directories(${MNN_DIR}/tools)
include_directories(${MNN_DIR}/tools/cpp)
include_directories(${MNN_DIR}/source)
include_directories(${MNN_DIR}/source/backend)
include_directories(${MNN_DIR}/source/core)
LINK_DIRECTORIES(${MNN_DIR}/build)
add_executable(mnist main.cpp)
target_link_libraries(mnist -lMNN ${OpenCV_LIBS})
编译
cmake .
make
编译完成后出现mnist可执行文件,输入./mnist
运行,可以看到已经可以成功预测出手写数字