day04-05:Cmake与Torch c++实现数字识别

  • 环境:
    • window 10
    • visual studio 2019(nmake/cl/link/lib/dumpbin)
    • Qt 5.14.0 (designer/uic/moc)
    • OpenCV 4.2.0
    • Torch C++ 1.5.1
    • cmake
  • 注意事项:

    1. 编译/链接的环境(编译/链接的命令行设置):Makefile/CMake/QMake
      1. include
      2. lib
    2. 运行环境:(设置PATH/或者拷贝到当前路径/或者拷贝到window安装目录下的system32)
      1. bin
    3. 动态库也有依赖环境:
      1. window的运行环境(基本上差不多)
      2. vcrt160.lib运行时(visual studio自带/动态库自带)
  • lib库名.主版本号.副版本号.批次号.so

  • 库名140.dll/库名160.dll

注意

  1. libtorch,opencv, qt的动态库所在路径设置为PATH环境变量

    • 保存编译后的执行文件能调用到动态库;
  2. 头文件目录

    • opencv
      • C:\opencv_new\install\include
    • qt
      • C:\Qt\Qt-5.14.0\include
    • libtorch
      • C:\libtorch\include
  3. lib

    • 目录
      • C:\Qt\Qt-5.14.0\lib
      • C:\opencv_new\install\x64\vc16\lib
      • C:\libtorch\lib
    • lib文件
      • 根据调用的模块

CMake

Qt, OpenCV的例子

  • 使用Qt显示一副OpenCV读取的图像
#include 
#include 
#include 
#include 


int main(int argc, char **argv){
    // 1. 创建Qt应用
    QApplication app(argc, argv);
    // 2. 创建对话框
    QDialog dlg;
    dlg.setWindowTitle("CMake组织工程");
    dlg.resize(600, 400);
    dlg.move(100, 100);
    // 3. 创建标签狂
    QLabel  lbl("图像显示", &dlg);
    lbl.setGeometry(0, 0, dlg.width(), dlg.height());

    // 4. 打开图像
    cv::Mat img = cv::imread("gpu.bmp");
    cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
    // 5. 显示图像
    QImage qt_img(img.data, img.cols, img.rows, QImage::Format_RGB888);
    QPixmap qt_pixmap = QPixmap::fromImage(qt_img);
    lbl.setPixmap(qt_pixmap);
    lbl.setScaledContents(true);

    dlg.show();
    return app.exec();
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

project(main)

# 控制C++编译选项,链接选项
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQIUIRED True)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

# 属于QT专有
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)

# 配置Qt的内置的CMake环境(源代码安装才有cmake目录)
set(CMAKE_PREFIX_PATH  "C:/Qt/Qt-5.14.0/lib/cmake")

# Qt的库,头文件自动查找
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Core REQUIRED)

# 头文件
include_directories(
    C:/Qt/Qt-5.14.0/include
    C:/opencv_new/install/include
)
# 库路径
link_directories(
    C:/opencv_new/install/x64/vc16/lib
    C:/Qt/Qt-5.14.0/lib
)

# 源文件
aux_source_directory(. SOURCES)
add_executable(main ${SOURCES})

# 编译库
target_link_libraries(
    main 
    opencv_core420d.lib
    opencv_imgcodecs420d.lib 
    opencv_imgproc420d.lib 
    Qt5Cored.lib  
    Qt5Widgetsd.lib 
    Qt5Guid.lib
)

  • 注意:

    • 源代码
    • 头文件
    • lib文件
    • 输出的文件名
    • C++的编译选项/链接选项
    • cmake的封装打包
  • Cmake的抽象在于不需要指定具体的编译器

    • 动态侦测,从而实现跨平台安装与部署

Torch C++开发环境与第一个程序

头文件

#include 
#include 

编译环境

- 两种方法,已注释区分
cmake_minimum_required(VERSION 3.16)

# 指定项目名
project(main)

# 执行Torch的cmake配置的位置
set(CMAKE_PREFIX_PATH  "D:/libtorch")
# set(Torch_DIR  "D:/libtorch")

# 直接加载Torch C++提供cmake配置
find_package(Torch REQUIRED)

# 直接使用预先定义的变量(服务于Torch项目的编译链接)
set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FALAG} ${TORCH_CXX_FLAGS}")

# 输出的执行文件
add_executable(main main.cpp)

# 指定编译库
target_link_libraries(main "${TORCH_LIBRARIES}")

# 只对指定的编译目标指定编译器的C++语言标准版本
set_property(TARGET main PROPERTY CXX_STANDARD 14)
# set(CMAKE_CXX_STANDARD 14)

Torch C++编程

Torch C++ 文档

  • https://pytorch.org/cppdocs/api/library_root.html
    • at命名空间Tensor
    • torch命名:数据集/模型/函数/优化器

数据集

MNIST
Dataset

数据集批次处理

DataLoader

Lenet-5 模型

[图片上传失败...(image-66a07a-1593616320465)]

  • 作业:
    • 完成文档
      • 运行我们的程序;
      • 理解我们的程序;
    • 可选:
      • 自己独立完成,并理解;

训练

  • 训练的算法公式

  • 算法步骤

    1. 使用训练样本(data,target),计算模型的预测值pred;
    2. 计算误差
    3. 根据 函数计算在每个训练矩阵的导数grad;
    4. 使用 更新训练矩阵;
    5. 最终是的误差接近0

验证与测试

模型保存

#include 
#include 

// 模型的实现(模型类)
class Lenet5 : public torch::nn::Module{
private:
    // 卷积特征运算
    torch::nn::Conv2d  conv1;
    torch::nn::Conv2d  conv2;
    torch::nn::Conv2d  conv3;
    torch::nn::Linear  fc1;
    torch::nn::Linear  fc2;

public:
    Lenet5():
    conv1(torch::nn::Conv2dOptions(1, 6, 5).stride(1).padding(2)),  // 1 * 28 * 28 -> 6 * 28 * 28 -> 6 * 14 * 14
    conv2(torch::nn::Conv2dOptions(6, 16, 5).stride(1).padding(0)),  // 6 * 14 * 14 -> 16 * 10 * 10 -> 16 * 5 * 5
    conv3(torch::nn::Conv2dOptions(16, 120, 5).stride(1).padding(0)), // 16 * 5 * 5 -> 120 * 1 * 1 (不需要池化)
    fc1(120, 84),  // 120 -> 84
    fc2(84, 10){  // 84 -> 10 (分量最大的小标就是识别的数字)
        // 注册需要学习的矩阵(Kernel Matrix)
        register_module("conv1", conv1);
        register_module("conv2", conv2);
        register_module("conv3", conv3);
        register_module("fc1", fc1);
        register_module("fc2", fc2);
    }

    // override
    torch::Tensor forward(torch::Tensor x){  // {n * 1 * 28 * 28}
        // 1. conv
        x = conv1->forward(x);   // {n * 6 * 28 * 28}
        x = torch::max_pool2d(x, 2);   // {n * 6 * 14 * 14}
        x = torch::relu(x); // 激活函数 // {n * 6 * 14 * 14}
        // 2. conv
        x = conv2->forward(x);   // {n * 16 * 10 * 10}
        x = torch::max_pool2d(x, 2);   // {n * 16 * 5 * 5}
        x = torch::relu(x); // 激活函数 // {n * 16 * 5 * 5}
        // 3. conv
        x = conv3->forward(x);   // {n * 120 * 1 * 1}
        x = torch::relu(x); // 激活函数 // {n * 120 * 1 * 1}
        // 做数据格式转换
        x = x.view({-1, 120});   // {n * 120}
        // 4. fc
        x = fc1->forward(x);
        x = torch::relu(x);
        
        // 5. fc 
        x = fc2->forward(x);
        return  torch::log_softmax(x, 1);   // CrossEntryLoss = log_softmax + nll
    }

};
// 训练好的模型文件:lenet5.pt

模型加载与识别

#include 
#include 

int main(){
    const char * data_filename = ".\\data";
    // 加载模型
    std::shared_ptr model = std::make_shared();
    torch::load(model, "lenet5.pt");


    // 使用测试集中数据识别
    auto imgs = torch::data::datasets::MNIST(data_filename, torch::data::datasets::MNIST::Mode::kTest);
    // 取一张图像
    for(int i = 20; i < 30; i++){
        torch::data::Example<> example = imgs.get(i);
        // std::cout << "识别的数字是:" << example.target.item() << std::endl;  
        // 获取图像
        torch::Tensor  a_img = example.data;
        // 预测
        a_img = a_img.view({-1, 1, 28, 28});  // 我们的模型只接受4为的固定的数据格式(N * C * H * W)(NCHW格式)
        torch::Tensor  y = model->forward(a_img);
        int32_t result = y.argmax(1).item();
        std::cout << "识别的结果是:" << result << "->" << example.target.item() <<  std::endl;
        
        return 0;
    }
    

类图

  • 创建工程目录
    • 代码文件
    • main.cpp
    • CMakeLists.txt

附录

  1. OpenCV

    • https://docs.opencv.org/4.2.0/
  2. Qt

    • https://doc.qt.io/qt-5/
  1. Torch

    • https://pytorch.org/cppdocs/api/library_root.html
  2. cmake

    • https://cmake.org/cmake/help/v3.18/
  3. Makeflie

    • gnu make:http://www.gnu.org/software/make/manual/html_node/index.html
    • microsoft nmake: https://docs.microsoft.com/zh-cn/cpp/build/reference/nmake-reference?view=vs-2019

作业:

  1. 训练出一个模型:模型文件,验证的结果

    • 代码
    • 模型文件
    • 笔记截图,训练的日志文件
  2. 利用模型实现识别

    • 使用测试数据集测试识别;
  3. 可选:

    • 完成一个完整的识别程序;
      1. 手写数字识别
      2. 人脸识别 (人脸采集程序)
    • 验收:
      • 类图
      • 源代码
      • 安装包(编译后的程序)
      • markdown的说明文档(上传到github)

你可能感兴趣的:(day04-05:Cmake与Torch c++实现数字识别)