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 <QtWidgets/QApplication>
#include <QtWidgets/QDialog>
#include <QtWidgets/QLabel>
#include <opencv2/opencv.hpp>


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 <torch/torch.h>
#include <iostream>

编译环境

- 两种方法,已注释区分
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)]

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

训练

  • 训练的算法公式

    • w = w - \eta \ast \text{grad}
  • 算法步骤

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

验证与测试

模型保存

#include <opencv2/opencv.hpp>
#include <torch/torch.h>

// 模型的实现(模型类)
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 <opencv2/opencv.hpp>
#include <torch/torch.h>

int main(){
    const char * data_filename = ".\\data";
    // 加载模型
    std::shared_ptr<Lenet5> model = std::make_shared<Lenet5>();
    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<int32_t>() << 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<int32_t>();
        std::cout << "识别的结果是:" << result << "->" << example.target.item<int32_t>() <<  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)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355