用 pytorch 训练端对端验证码识别神经网络并进行 C++ 移植

news2024/12/23 16:16:44

文章目录

  • 前言
  • 安装
    • 安装 pytorch
    • 安装 libtorch
    • 安装 opencv(C++)
  • 准备
  • 数据集
    • 获取训练数据
      • 下载
      • 标定
    • 编码
      • 预分析
    • 数据集封装格式
  • 神经网络搭建
  • 神经网络训练
  • 神经网络测试
  • 神经网络预测
  • C++ 移植
    • 模型转换
      • 通过跟踪转换为 Torch Script
      • 通过注解转换为 Torch Script
    • 编写 C++ 代码
    • 编译环境搭建
      • C++ 库管理
      • 方法一:手动配置 visual studio 环境
      • 方法二:cmake 配置环境
    • python 调用 C++ 程序

前言

训练和测试验证码来自中南大学教务系统登录验证码:http://csujwc.its.csu.edu.cn/

部分代码参考自该 github 项目:dee1024/pytorch-captcha-recognition

标记好的训练数据
链接:https://pan.baidu.com/s/1xGmvY8FuKm9jP2HW48wI3A
提取码:fana

训练好的神经网络模型( TrainNetwork.py 得到的模型)
链接:https://pan.baidu.com/s/1-cZCLNuYs-1MOu1NI42UOw
提取码:4wsv

环境

  • 操作系统:Windows 10
  • CPU:Intel i5-9300
  • GPU:GTX 1650
  • 深度学习框架:pytorch 1.6,libtorch 1.6
  • CUDA 11
  • python 3.8
  • opencv 4.4.0(C++)
  • visual studio 2019

安装

安装 pytorch

pytorch 是 torch 的 python 版本,也是我们常用的深度学习框架。

首先进入官网:https://pytorch.org/

在安装栏根据自己的环境配置选择相应的 pytorch 版本,获得安装指令。

在这里插入图片描述

例如 Windows 的 pip 安装:在命令行输入

pip install torch===1.6.0 torchvision===0.7.0 -f https://download.pytorch.org/whl/torch_stable.html

后回车等待一段时间即可完成安装。

如何查看 CUDA 版本?

如果是英伟达的显卡,打开英伟达显卡的控制面板。点击 系统信息 -> 组件,即可看到 CUDA 版本。

例如我的电脑的 CUDA 版本为 11.0.228,但是安装低版本 CUDA (10.1) 的 pytorch 也同样可以使用。

在这里插入图片描述

安装 libtorch

libtorch 是 torch 的 C++ 版本。libtorch 的版本必须与 pytorch 一致

同样是 pytorch 的安装栏,Package 选择 libtorchLangugae 选择 C++/Java

获得如下的下载链接:

在这里插入图片描述

我选择的是 Release version,点击相应链接下载完毕后解压在合适的目录下。

安装 opencv(C++)

opencv 主要用来进行 C++ 程序的图像处理。

和 libtorch 安装类似,在下载页面 https://opencv.org/releases/ 选择相应的操作系统。例如选择 Windows 系统,跟随安装程序选择合适的安装目录即可完成安装。

准备

该项目使用到的需要额外安装的 python 库:

  • numpy
  • requests
  • matplotlib:绘图
  • skimage:负责图像处理

新建一个目录,在目录下新建:

  • TrainImages 目录:存放训练数据
  • TestImages 目录:存放检验数据
  • Network.py:神经网络定义
  • code.py:编码规则
  • Dataset.py:数据集定义
  • TrainNetwork.py:神经网络训练
  • TestNetwork.py:神经网络测试

数据集

获取训练数据

下载

采用 python 脚本进行批量下载,下载的图片用时间戳命名。

提供一个下载脚本,将其复制到项目的主目录再运行:

import requests
import time

CaptchaUrl = 'http://csujwc.its.csu.edu.cn/verifycode.servlet'
root = '.\\TrainImages\\' #图片保存目录
DownloadNumber = 0

def GetOneCaptcha(time_label):
    global DownloadNumber
    try:
        r = requests.get(CaptchaUrl)
        r.raise_for_status()
        with open(root + '_' + time_label + '.png','wb') as f:
            f.write(r.content)
        DownloadNumber = DownloadNumber + 1
    except:
        pass

def Download(CaptchaNumber):
    while(DownloadNumber < CaptchaNumber):
        GetOneCaptcha(str(int(time.time()*1000)))
        print("已下载 {0:>4} 个".format(DownloadNumber),end = '\r')
    print("\n下载完毕")
    
if __name__ == "__main__":   
	Download(100) #下载100张

标定

人工标记(自己标定了600多张,眼睛都要花了),标记的效果大概这样:

在这里插入图片描述

编码

预分析

经过大量验证码数据分析可以得出中南大学教务系统验证码具有如下规律:

  • 包含四个字符
  • 字符分为小写英文字母和数字
  • 数字包括 1,2,3
  • 英文字母包括 b,c,m,n,v,x,z

所以神经网络需要对一个字符作十分类,总共四个字符,输出包含四十个元素的一维特征向量。

在这里插入图片描述

端对端神经网络输入图片直接输出结果,输出的每10个元素中最大的(代表概率最大)为神经网络的预测字符。

code.py 文件内容如下,负责 array 类型和字符串类型的转换。

import numpy as np

char_table = ['1', '2', '3', 'b', 'c', 'm', 'n', 'v', 'x', 'z']


def encode(raw_string):  # 编码
    out_code = np.zeros(40)
    for i, c in enumerate(raw_string):
        out_code[i * 10 + char_table.index(c)] = 1
    return out_code


def decode(raw_code):  # 解码
    out_string = ''
    raw_code = raw_code.reshape(4, 10)
    raw_code = np.argmax(raw_code, 1)
    for i in raw_code:
        out_string += char_table[i]
    return out_string

例程

string = 'mn3z'
code = encode(string)
string = decode(code)
print(code)
print(string)

运行结果

[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 1. 0.
 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
mn3z

数据集封装格式

Dataset.py 文件内容如下,自己定义的数据集需要继承 pytorch 的 torch.utils.data.Dataset 类,而且必须重写 __len____getitem__ 方法

  • __init__folder 参数为数据集所在的目录名称,transform 参数为数据的变换方式,这里为转换Tensor 类型。

    • train_image_file_paths 为含有目录内所有图片路径的列表
  • __len__获取数据集长度。根据前面的获取的train_image_file_paths 即可知道数据集长度。

  • __getitem__根据索引获取数据,类似于列表。返回包含 Tensor 类型数据的元组

    • skimage.io.imread()skimage 库的图像读取函数as_grey 参数表示是否转换为灰度图(即图片三通道转单通道)。

    • skimage.transform.resize()skimage 库的图像变形函数,在这里图像被变形为 45x45 大小。

      • 注意:如果使用其他图像处理库(例如 Pillow)的图像变形函数得出的图像可能不一样,最好在训练,测试,预测时统一使用一个图像处理库。
    • label 为图片的标签编码,例如图片 1b13_1589636262331.png 通过 split('_')[0] 即可得到标签,在经过前面提到的 encode() 函数编码。

    • 输出的 Tensor 类型数据通过 .float() 方法全部转为浮点型

  • torch.utils.data.Dataloader 为 pytorch 的数据集装载方法,数据集必须经过装载才能输入进神经网络。batch_size 参数为小批量训练每批的大小(pytorch 采用小批量训练),shuffle 参数表示是否打乱数据集。

  • get_train_data_loader()get_test_data_loader() 函数用来获取训练集和测试集,通过 batch_size 参数定义批量大小。

import os
import torch
from torch.utils.data import DataLoader,Dataset
import torchvision.transforms as transforms
from skimage import io,transform
import code

class mydataset(Dataset):

    def __init__(self, folder, transform=None):
        self.train_image_file_paths = [os.path.join(folder, image_file) for image_file in os.listdir(folder)] #获取含有目录内所有图片路径的列表
        self.transform = transform

    def __len__(self):
        return len(self.train_image_file_paths)

    def __getitem__(self, idx):
        image_root = self.train_image_file_paths[idx] #图片路径
        image_name = image_root.split(os.path.sep)[-1] #图片名称
        image = io.imread(image_root,as_gray = True) #读取图片并转为灰度图
        image = transform.resize(image,(45,45)) #将图片转为 45x45 大小
        if self.transform is not None:
            image = self.transform(image) #转换图片
        label = code.encode(image_name.split('_')[0]) #编码
        return image.float(), torch.from_numpy(label).float()

Transform = transforms.Compose([
    transforms.ToTensor() #转换为 Tensor 类型
])

def get_train_data_loader(batch_size = 16): #获取训练集
    dataset = mydataset('.\\TrainImages', transform = Transform)
    return DataLoader(dataset, batch_size, shuffle = True)

def get_test_data_loader(batch_size = 1): #获取测试集
    dataset = mydataset('.\\TestImages', transform = Transform)
    return DataLoader(dataset, batch_size, shuffle = True)

神经网络搭建

神经网络的总体结构如图,为典型的 CNN 网络。

该 CNN 网络部分结构与 AlexNet 相似,输入N x 45 x 45(N 为输入图片个数)的灰度图像,前向环节按照从左到右顺序分别是卷积层,池化层,卷积层,池化层,全连接层。其中,图像经过卷积层后还要经过批归一化,经过池化层后采用 ReLU 作为激活函数。在训练时加入了 Dropout 环节以增强神经网络的泛化能力。

输出 N x 40(N 为输入图片个数)one - hot 标签,每 10 个元素为一组代表一个字符,这 10 个元素代表了每个类别的概率,后续需要经过处理来输出最终的结果。

在这里插入图片描述

在这里插入图片描述

Network.py 文件内容如下,自己定义的神经网络需要继承 pytorch 的 torch.nn.Module 类。而且必须重写 __init__forward 方法

  • __init__:负责神经网络的初始化,num_classnum_char 分别为字符种类数和字符个数,由上分析可知分别为 10 和 4。下面的一些方法涉及到深度学习的知识,最好有相关的基础。

    • torch.nn.Sequential():pytorch 的一个封装神经网络环节的方法。

    • torch.nn.Conv2d():卷积层。

    • torch.nn.BatchNorm2d():批归一化层。

      请参考该博客:https://blog.csdn.net/vict_wang/article/details/88075861

    • torch.nn.Dropout(0.5)

      在 Dropout 环节中,神经网络将每个隐藏神经元的输出设置为零(概率为 0.5)。 以这种方式“脱落”的神经元不会对前向传播做出贡献,也不会参与反向传播。

      因此,每次出现输入时,神经网络都会对不同的体系结构进行采样,但是所有这些体系结构都会共享权重。由于神经元不能依靠特定其他神经元的存在,因此该技术减少了神经元的复杂共适应。 因此,它被迫学习更强大的功能,这些功能可与其他神经元的许多不同随机子集结合使用。

    • torch.nn.MaxPool2d()

      池化函数使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出,最大池化函数给出相邻矩形区域内的最大值。不管采用什么样的池化函数,当输入作出少量平移时,池化能够帮助输入的表示近似不变。对于平移的不变性是指当我们对输入进行少量平移时,经过池化函数后的大多数输出并不会发生改变。

    • torch.nn.RELU()

      在神经网络中,线性整流作为神经元的激活函数,定义了该神经元在线性变换之后的非线性输出结果。换言之,对于进入神经元的来自上一层神经网络的输入向量,使用线性整流激活函数的神经元会输出
      m a x ( 0 , w T x + b ) max(0,w^Tx+b) max(0,wTx+b)
      至下一层神经元或作为整个神经网络的输出(取决现神经元在网络结构中所处位置)。

    • torch.nn.Linear

      全连接层在整个卷积神经网络中起到 “分类器”的作用。全连接层连接前面卷积池化后得到的所有特征,将输出值赋予分类器。

  • forward:神经网络的前向传播,正是通过该方法神经网络把输入进的图片数据转换为输出向量(即识别结果)。pytorch 要求输入的数据维度为 N x C x H x W,N:输入的图片个数,C:图片的通道数。

#神经网络定义

import torch
import torch.nn as nn

class Net(nn.Module):
    
    def __init__(self,num_class = 10,num_char = 4):
        super(Net,self).__init__()
        self.num_class = num_class #标签个数
        self.num_char = num_char #字符个数
        self.conv1 = nn.Sequential(nn.Conv2d(1,16,6),
                            nn.BatchNorm2d(16),
                            nn.Dropout(0.5),
                            nn.MaxPool2d(2,2),
                            nn.ReLU()
        )
        self.conv2 = nn.Sequential(nn.Conv2d(16,8,3),
                            nn.BatchNorm2d(8),
                            nn.Dropout(0.5),
                            nn.MaxPool2d(2,2),
                            nn.ReLU()
        )
        self.fc = nn.Linear(8*9*9,self.num_class*self.num_char)

    def forward(self,x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = x.view(x.size(0),-1)
        x = self.fc(x)
        return x

神经网络训练

TrainNetwork.py 文件内容如下,负责神经网络的训练。

  • num_epochs:训练轮数,即重复输入训练集的次数。

  • learning_rate:学习率,梯度下降法里的重要参数,越小梯度下降越慢,太大会导致神经网络发散。

  • torch.device:pytorch 的设备设置,表示数据是在 CPU 上计算还是在 GPU 上计算。

    通过 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') 获取电脑的可用设备。

    通过 .to(device) 方法将数据或神经网络模型移动到设定好的设备上,例如移动到 GPU 上就可以加快神经网络的训练速度。

  • 训练时要将神经网络置于训练模式:.train(),此时神经网络的 Dropout 环节会开启。

  • torch.nn.MultiLabelSoftMarginLoss:多标签分类损失函数,参数为神经网络的预测标签和图片实际标签。通过预测出的数据和实际数据的差异计算出损失值,损失值代表了神经网络预测的准确性。越小神经网络在数据集上的准确性表现得越好。

    数学形式如下
    l o s s ( x , y ) = − ∑ i y [ i ] ∗ log ⁡ ( ( 1 + e x p ( − x [ i ] ) ) − 1 ) + ( 1 − y [ i ] ) ∗ log ⁡ ( e x p ( − x [ i ] ) 1 + e x p ( − x [ i ] ) ) ) loss(x,y)=-\sum_i{y[i]*\log{((1+exp(-x[i]))^{-1})}}+(1-y[i])*\log{}(\frac{exp(-x[i])}{1+exp(-x[i])})) loss(x,y)=iy[i]log((1+exp(x[i]))1)+(1y[i])log(1+exp(x[i])exp(x[i])))

  • torch.optim.Adam:Adam 优化算法,参数为神经网络的参数 cnn.parameters() 和学习率 learning_rate

    pytorch 可以自动进行梯度计算。神经网络每进行一次损失值计算,优化器清空保存的梯度然后执行后向传播得到新的梯度,并基于此执行优化算法。

    请参考该博客:https://www.cnblogs.com/yifdu25/p/8183587.html

  • torch.save(cnn.state_dict(), ".\\model.pt"):保存神经网络的部分数据

import torch
import torch.nn as nn
from torch.autograd import Variable
import Dataset
import code
from Network import Net

#训练参数
num_epochs = 30 #训练轮数
learning_rate = 0.001 #学习率

def main():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    cnn = Net() #初始化神经网络
    cnn.to(device) 
    cnn.train() #训练模式
    print('初始化神经网络')
    criterion = nn.MultiLabelSoftMarginLoss() #损失函数
    optimizer = torch.optim.Adam(cnn.parameters(), lr=learning_rate) #优化器

    #训练神经网络
    train_dataloader = Dataset.get_train_data_loader(batch_size=16)
    for epoch in range(num_epochs):
        for i, (images, labels) in enumerate(train_dataloader):
            images = Variable(images).to(device)
            labels = Variable(labels.float()).to(device)
            predict_labels = cnn(images)
            loss = criterion(predict_labels, labels)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            if (i+1) % 10 == 0:
                print("当前轮数:", epoch, "当前步数:", i, "损失值:", loss.item())
            if (i+1) % 20 == 0:
                torch.save(cnn.state_dict(), ".\\model.pt")
                print("保存模型")
    torch.save(cnn.state_dict(), ".\\model.pt")
    print("保存最后的模型")

if __name__ == '__main__':
    main()

运行结果

初始化神经网络
当前轮数: 0 当前步数: 9 损失值: 0.3719463050365448
当前轮数: 0 当前步数: 19 损失值: 0.3350425958633423
保存模型
当前轮数: 0 当前步数: 29 损失值: 0.30613207817077637
………………
当前轮数: 29 当前步数: 9 损失值: 0.006565847899764776
当前轮数: 29 当前步数: 19 损失值: 0.002701024990528822
保存模型
当前轮数: 29 当前步数: 29 损失值: 0.005611632019281387
保存最后的模型

神经网络测试

TestNetwork.py 文件内容如下,负责神经网络训练后的测试。

将神经网络的输出标签和图片实际标签比较得出神经网络的识别是否正确,在比较时需要通过 code.decode(predict_label.data.numpy()) 将神经网络的输出结果(Tensor 类型)转换字符串形式。

测试时要将神经网络置于计算模式:.eval(),此时神经网络的 Dropout 环节会关闭。

import torch
import Dataset
from Network import Net
import code

def test_network(dataloader):
    net = Net() #初始化神经网络
    net.load_state_dict(torch.load('.\\model.pt')) #加载模型
    net.eval() #计算模式
    output_list = [] #存放预测结果
    for i, (image, label) in enumerate(dataloader):
        predict_label = net(image)
        label = code.decode(label.data.numpy())
        predict_label = code.decode(predict_label.data.numpy())
        if predict_label == label:
            output_list.append(1)
        else:
            output_list.append(0)
        if (i+1)%10 == 0:
            acc = sum(output_list) / len(output_list)
            print("{} 张正确率:{} %".format(i+1,acc*100))
    acc = sum(output_list) / len(output_list)
    print("总正确率:{} %".format(acc*100))
    
if __name__ == '__main__':
    test_network(Dataset.get_test_data_loader(batch_size=1))#获取测试集

运行结果

10 张正确率:100.0 %
20 张正确率:100.0 %
30 张正确率:100.0 %
………………
170 张正确率:98.82352941176471 %
180 张正确率:98.88888888888889 %
190 张正确率:98.94736842105263 %
200 张正确率:99.0 %
总正确率:99.0 %

可以看到正确率已经非常高了。

神经网络预测

由于有现成的验证码 URL,所以采用在线预测。提供一个预测脚本:

# pytorch 预测

import torch
from skimage import io,transform,color
from Network import Net
import matplotlib.pyplot as plt
import code

def predict(img_path):   
    net = Net()
    net.load_state_dict(torch.load('.\\model.pt'))
    net.eval()
    for i in range(25):
        img = io.imread(img_path)
        plt.subplot(5,5,i+1)
        plt.imshow(img)
        img = color.rgb2gray(img)
        img = transform.resize(img,(45,45))
        img = torch.from_numpy(img).float().view(1,1,45,45)
        out = net(img).view(1,-1).data.numpy()
        output = code.decode(out)
        plt.title(output)
        plt.axis('off')
    plt.show()

if __name__ == '__main__':        
    predict('http://csujwc.its.csu.edu.cn/verifycode.servlet')

神经网络输入单个图片时同样也要进行图像处理转换为 1 x 1 x 45 x 45 的维度。

这里调用了 matplotlib 库来同时显示图片和预测结果。

预测结果如下

在这里插入图片描述

C++ 移植

pytorch 进行神经网络的搭建和训练固然很方便,但是当部署到实际中来时就显得效率一般。例如网络爬虫需要识别验证码以进行登录,运行 python 程序的话光是加载 torch 库就要花一段时间,这对于不需要一次进行大量图片识别的网络爬虫是一种拖累。所以需要编写 C++ 程序来加载神经网路模型以提高程序运行的效率。

部分来自官方教程:https://pytorch.org/tutorials/advanced/cpp_export.html

模型转换

pytorch 提供了一种统一的模型描述语言 Torch Script 供其他编程语言程序加载,下面介绍两种将我们训练出来的模型转换为 Torch Script 的方法,也可以参考这篇博客:https://blog.csdn.net/xxradon/article/details/86504906

通过跟踪转换为 Torch Script

将模型的实例以及示例输入传递给 torch.jit.trace 函数。

这个方法适用于对任意类型输入有固定格式输出的神经网络。

import torch
from Network import Net

net = Net()
net.load_state_dict(torch.load('.\\model.pt'))
example = torch.rand(1, 1, 45, 45)
scrpit_net = torch.jit.trace(net,example)
script_net.save('.\\model_script.pt')

跟踪器有可能生成警告,因为有就地赋值(torch.rand())。

通过注解转换为 Torch Script

这个方法适用于对不同类型输入有不同格式输出的神经网络。

import torch
from Network import Net

net = Net()
net.load_state_dict(torch.load('.\\model.pt'))
scrpit_net = torch.jit.script(net)
script_net.save('.\\model_script.pt')

编写 C++ 代码

源.cpp 文件内容如下,调用了 opencv 库和 libtorch 库。

  • decode() 解码函数,类似于前面 python 的解码函数。

  • C++ 程序通过主函数的入口参数 argc(参数个数) 和 argv[](参数集) 获取传入程序的参数,例如在windows 命令行中输入:

    program.exe arg1 arg2
    

    即可向程序传入两个参数:arg1arg2,在程序中读取 argv[0]argv[1] 即可知道这两个参数的值。

    在本程序中传入的参数为前面保存的神经网络模型的路径和图片的路径

  • torch::jit::script::Module module = torch::jit::load(argv[1]) 通过模型路径读取模型并创建神经网络,十分简便。

  • cv::imread() 为 opencv 的图像读取函数,输入图片路径即可返回 Mat 类型的图像数据矩阵。

  • cv::resize() 为 opencv 的图像变形函数,在这里使图片变形为 45 x 45。

  • cv::cvtColor() 为 opencv 的图像色域改变函数,opencv 读取的图像通道和我们常见的 RGB 通道不同,它是 BGR,在这里我们只需要将它转变为单通道(即灰度图)。

  • image.convertTo(image, CV_32FC1);Mat 类型的数据类型转换方法,在这里转换为32为浮点型单通道。因为在前面的 pytorch 节我们知道输入给神经网络的 Tensor 类型的数据都经过 .float() 方法转换为了32位浮点型。

  • cv::vconcat() 为 opencv 的图像拼接函数,在这里是在图像个数维上进行拼接。

  • 使用 write 标记 images 变量是否已赋值,因为我们要将所有的输入图片拼接成一个整体再转换为 Tensor 传给神经网络以加快程序运行速度。

  • torch::from_blob() 为 libtorch 提供的 Mat 类型转 Tensor 类型的函数接口,我们可以看到最终输入给神经网络的数据维度为 N x 1 x 45 x 45 (N 为图片个数),和前面的 pytorch 输入一致。

  • std::vector<torch::jit::IValue> inputs;

    inputs.push_back(input_tensors);

    auto outputs = module.forward(inputs).toTensor();

    这三条语句为我在网上查到的一种固定写法,大概是要将 Tensor 类型的数据放在一种叫向量的数据结构里才能传递给神经网络。

  • 最总通过 std::cout 直接输出识别结果,多图片时输出结果以单空格隔开。

也不知道为什么,发现把模型置于计算模式时(module.eval();)输出结果是错误的。

/*
神经网络调用程序,根据命令行参数直接输出识别结果,可一次识别多张,上限100张
参数顺序:神经网络模型路径,图片1路径,……,图片n路径
*/

#include <torch/script.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <memory>

std::string decode(at::Tensor code) //解码函数
{
    int char_num = 4; //字符个数
    int a;
    std::string str = ""; //输出字符串
    std::string table[10] = {"1", "2", "3", "b", "c", "m", "n", "v", "x", "z"}; //编码对照表
    code = code.view({ -1, 10 });
    code = torch::argmax(code, 1);
    for (int i = 0; i < char_num; i++)
    {
        a = code[i].item().toInt();
        str += table[a];
    }
    return str;
}

int main(int argc, const char *argv[])
{
    if (argc < 3 || argc > 102)
    {
        std::cerr << "调用错误!" << std::endl;
        return -1;
    }
    try
    {
        int image_num = argc - 2; //读取的图片数量
        bool write = false;
        torch::jit::script::Module module = torch::jit::load(argv[1]); //读取模型
        cv::Mat images; //保存图片数据
        for (int i = 0; i < image_num; i++)
        {
            cv::Mat image = cv::imread(argv[i + 2]); //读取图片
            cv::resize(image, image, cv::Size(45, 45)); //变形成为45 x 45
            cv::cvtColor(image, image, cv::COLOR_BGR2GRAY); //转成灰度图
            image.convertTo(image, CV_32FC1); //转换为32位浮点型
            if (!write)
            {
                images = image;
                write = true;
            }
            else
            {
                cv::vconcat(images, image, images); //拼接图像
            }
        }
        auto input_tensors = torch::from_blob(images.data, {image_num, 1, 45, 45 }); //将mat转成tensor
        std::vector<torch::jit::IValue> inputs;
        inputs.push_back(input_tensors);
        auto outputs = module.forward(inputs).toTensor();
        std::cout << decode(outputs[0]);
        for (int i = 1; i < image_num; i++)
        {
            auto result = decode(outputs[i]);//解码
            std::cout << ' ' << result;
        }
        return 0;
    }
    catch (...)  //捕获任意异常
    {
        std::cerr << "程序执行出错!" << std::endl;
        return -1;
    }
}

编译环境搭建

下面介绍两种编译环境的搭载方法,一种是直接在 visual studio 中配置,一种是官方推荐的用 cmake 配置 visual studio 环境。

确保已安装 visual studio 的 C++ 部件,下面所有方法都以 visual studio 2019 为 IDE,程序编译类型为 x64 release

C++ 库管理

在前面我们已安装了 libtorch 和 opencv,以 libtorch 1.6.0 (release) 和 opencv 4.4.0 为例,假设它们放在一个文件夹:D:\CplusLib\

在这里插入图片描述

程序需要的动态链接库位置:

  • libtorchD:\CplusLib\libtorch\lib
  • opencvD:\CplusLib\opencv\build\x64\vc15\bin

接下来我们需要将这些动态链接库的路径添加进环境变量,以便 C++ 程序通过环境变量找寻这些动态链接库。

以 Windows 10 系统为例,右键 此电脑,选择 属性 -> 高级系统设置 -> 环境变量 打开界面。

在这里插入图片描述

有两种环境变量:用户变量和系统变量,一种只针对一个用户有效,另一种对所有用户都有效。

我们添加系统变量的 path 项,双击 path,点击 新建 输入路径:

在这里插入图片描述

点击 确定 -> 确定

方法一:手动配置 visual studio 环境

直接在 visual studio 环境中配置要一个一个去写包含的库的路径和动态链接库的名称。我是采用属性表的形式直接让工程项目读取,这样每创建一个新工程就不用重复配置了。

以上面的库安装路径为基础提供每个库的属性表:

  • libtorch(release)libtorch.Cpp.x64.user.props

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <ImportGroup Label="PropertySheets" />
      <PropertyGroup Label="UserMacros" />
      <PropertyGroup>
     <IncludePath>D:\CplusLib\libtorch\include;D:\CplusLib\libtorch\include\torch;$(IncludePath)</IncludePath>
        <LibraryPath>D:\CplusLib\libtorch\lib;$(LibraryPath)</LibraryPath>
      </PropertyGroup>
      <ItemDefinitionGroup>
        <Link>
      <AdditionalDependencies>asmjit.lib;c10.lib;c10_cuda.lib;caffe2_detectron_ops_gpu.lib;caffe2_module_test_dynamic.lib;caffe2_nvrtc.lib;clog.lib;cpuinfo.lib;dnnl.lib;fbgemm.lib;libprotobuf-lite.lib;libprotobuf.lib;libprotoc.lib;mkldnn.lib;torch.lib;torch_cpu.lib;torch_cuda.lib;%(AdditionalDependencies)</AdditionalDependencies>
        </Link>
      </ItemDefinitionGroup>
      <ItemGroup />
    </Project>
    
  • opencv (release)opencv.Cpp.x64.user.props

    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <ImportGroup Label="PropertySheets" />
      <PropertyGroup Label="UserMacros" />
      <PropertyGroup>
      <IncludePath>D:\CplusLib\opencv\build\include;D:\CplusLib\opencv\build\include\opencv2;$(IncludePath)</IncludePath>
        <LibraryPath>D:\CplusLib\opencv\build\x64\vc15\lib;$(LibraryPath)</LibraryPath>
      </PropertyGroup>
      <ItemDefinitionGroup>
        <Link>
          <AdditionalDependencies>opencv_world440.lib;%(AdditionalDependencies)</AdditionalDependencies>
        </Link>
      </ItemDefinitionGroup>
      <ItemGroup />
    </Project>
    

其中IncludePath 是库的包含路径,LibraryPath 是链接库路径,AdditionalDependencies 是链接库名称。

如何导入属性表

在 visual studio 中点击 属性管理器,右键 Release|x64 -> 添加现有属性表。最后效果如图:

在这里插入图片描述

新建 C++ 项目,将我们前面编写的 C++ 文件添加进来,按照如图配置:

在这里插入图片描述

点击 本地 Windows调试器 即可完成编译。

方法二:cmake 配置环境

首先安装 cmake:https://cmake.org/download/

比如 Windows 64位系统选择 cmake-3.18.2-win64-x64.msi,跟随安装程序即可完成安装。

新建一个目录 D:\CaptchaRecognize\,在目录下新建 CMakeLists.txt

cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(CaptchaRecognize)

SET(CMAKE_BUILE_TYPE RELEASE)

INCLUDE_DIRECTORIES(
D:/CplusLib/libtorch/include
D:/CplusLib/libtorch/include/torch
D:/CplusLib/opencv/build/include
D:/CplusLib/opencv/build/include/opencv2
)

SET(TORCH_LIBRARIES D:/CplusLib/libtorch/lib)
SET(OpenCV_LIBS D:/CplusLib/opencv/build/x64/vc15/lib)

LINK_DIRECTORIES(
${TORCH_LIBRARIES}
${OpenCV_LIBS}
)

add_executable(CaptchaRecognize 源.cpp)

target_link_libraries(CaptchaRecognize
asmjit.lib
c10.lib
c10_cuda.lib
caffe2_detectron_ops_gpu.lib
caffe2_module_test_dynamic.lib
caffe2_nvrtc.lib
clog.lib
cpuinfo.lib
dnnl.lib
fbgemm.lib
libprotobuf-lite.lib
libprotobuf.lib
libprotoc.lib
mkldnn.lib
torch.lib
torch_cpu.lib
torch_cuda.lib
opencv_world440.lib
)

set_property(TARGET CaptchaRecognize PROPERTY CXX_STANDARD 14)

该文件中的一些属性和属性表的内容相似。将 源.cpp 复制到该目录下,新建目录 build

在这里插入图片描述

打开 Cmake 进行如下配置:

在这里插入图片描述

点击 configure -> finish,然后点击 Generate 生成 visual studio 工程。

build 目录中找到工程文件并打开,打开 解决方案资源管理器,右键 CaptchaRecognize -> 设为启动项目

在这里插入图片描述

编译属性

在这里插入图片描述
点击 本地 Windows调试器 即可完成编译。

python 调用 C++ 程序

该 C++ 程序的命令行调用示例

CaptchaRecognize.exe model_script.pt pic1.png pic2.png

在 python 中通过 os.popen('command').read() 即可像命令行一样调用 C++ 程序并读取程序的输出结果。

例如

import os

result = os.popen('CaptchaRecognize.exe model_script.pt pic1.png pic2.png').read() #调用 C++ 程序
result = result.split(' ')

由前面可知 C++ 程序输出的结果由单空格隔开,通过 .split(' ') 可得到含所有图片识别结果的列表。

这篇文章花了我很长时间,如果觉得不错不妨点个赞哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1120429.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决 Element-ui中 表格(Table)使用 v-if 条件控制列显隐时数据展示错乱的问题

本文 Element-ui 版本 2.x 问题 在 el-table-column 上需根据不同 v-if 条件来控制列显隐时&#xff0c;就会出现列数据展示错乱的情况&#xff08;要么 A 列的数据显示在 B 列上&#xff0c;或者后端返回有数据的但是显示的为空&#xff09;&#xff0c;如下所示。 <tem…

Python教程(15)——Python流程控制语句详解

目录 if语句else if语句for循环遍历类型range关键字 while循环break语句continue语句 Python流程控制是Python编程中非常重要的一部分&#xff0c;它用于控制程序的执行流程。Python提供了多种流程控制语句&#xff0c;包括if语句、while循环、for循环、break和continue语句等。…

[翻译]理解Postgres的IOPS:为什么数据即使都在内存,IOPS也非常重要

理解Postgres的IOPS&#xff1a;为什么数据即使都在内存&#xff0c;IOPS也非常重要 磁盘IOPS&#xff08;每秒输入/输出操作数&#xff09;是衡量磁盘系统性能的关键指标。代表每秒可以执行的读写操作数量。对于严重依赖于磁盘访问的PG来说&#xff0c;了解和优化磁盘IOPS对实…

使用QT实现http里面的get和post

#1024程序员节&#xff5c;参与投稿&#xff0c;赢限定勋章和专属大奖# #假如你有一台服务器&#xff0c;你最想做哪些事&#xff1f;# #你被什么样的BUG困扰过一周以上&#xff1f;# 在http里面下面这些方法和服务器的响应代码一起用于HTTP协议中的请求和响应交互。请注意&…

模板再认识

在前面的文章中我写了关于模板的一些简单的认识&#xff0c;现在我们来再次认识模板文章目录 1.非类型模板参数2.模板特化1). 模板特化的写法2). 类模板特化3). 函数模板特化4). 模板全/偏特化 3.模板分离编译 1.非类型模板参数 在模板中还有一种是非类型的模板参数。我们代码…

【数组拷贝+二维数组遍历】

文章目录 前言数组的拷贝第一种方式&#xff1a;第二种方式:利用工具类拷贝 二维数组二维数组的三种定义打印二维数组的某个元素遍历二维数组 二维数组的每个元素是一维数组遍历二维数组&#xff08;优化&#xff09;打印出一个数组&#xff08;不是数组元素&#xff09;的方法…

linux性能分析(三)性能优化导轮

一 别再让Linux性能问题成为你的绊脚石 ① 学习历程 备注&#xff1a; 跟我的学习经历很相像刚工作时遇到的场景跟我现在一样,深深的无力感驱使我继续前行目标: 性能优化成为我的肌肉记忆,写代码的潜意识 ... ② 常见的问题 ③ 性能问题为什么这么难呢 思考&#xff1a…

顺序表ArrayList

作者简介&#xff1a; zoro-1&#xff0c;目前大二&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a; zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f496; 顺序表 概念Arraylist构造方法相关方法遍历操作 自…

嵌入式实时操作系统的设计与开发(任意大小的内存管理)

任意大小的内存管理是根据用户需要为其分配内存&#xff0c;即用户需要多大内存就通过acoral_malloc2()为之分配多大内存&#xff0c;同时每块分配出去的内存前面都有一个控制块&#xff0c;控制块里记录了该块内存的大小。 同时未分配出去的内存也有一个控制块&#xff0c;寻…

守护进程深度分析

思考 代码中创建的会话&#xff0c;如何关联控制终端&#xff1f; 新会话关联控制终端的方法 会话首进程成功打开终端设备 (设备打开前处于空闲状态) 1、关闭标准输入输出和标准错误输出2、将 stdin 关联到终端设备&#xff1a;STDIN_FILENO > 03、将 stdout 关联到终端设…

FreeRTOS_队列

目录 1. 队列简介 1.1 数据存储 1.2 多任务访问 1.3 出队阻塞 1.4 入队阻塞 1.5 队列操作过程 1.5.1 创建队列 1.5.2 向队列发送第一个消息 1.5.3 向队列发送第二个消息 1.5.4 从队列中读取消息 2. 队列结构体 3. 队列创建 3.1 函数原型 3.1.1 函数 xQueueCreate…

网卡状态检测函数

内核中网卡的开启状态通过__LINK_STATE_START进行标识。网卡开启或关闭时&#xff0c;通过该标识会被置位。 内核专门提供了一个函数用于检测该标志位。函数定义如下&#xff1a; static inline bool netif_running(const struct net_device *dev) {return test_bit(__LINK_S…

YOLOv5源码中的参数超详细解析(1)— 项目目录结构及文件(包括源码+网络结构图)

前言:Hello大家好,我是小哥谈。作为初学者,为了后期能够熟练的使用YOLOv5进行模型的训练,首先必须做的就是了解YOLOv5项目目录结构中各文件以及参数的作用。本篇文章就向大家简单介绍一下每个文件的具体功能及作用,关于YOLOv5详细的代码解读后期会慢慢更新,欢迎大家收藏关…

YOLOv5改进实战 | GSConv + SlimNeck双剑合璧,进一步提升YOLO!

前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…

三十七、【进阶】SQL的explain

1、explain 2、基础使用 在使用explain关键字时&#xff0c;只需要在所执行语句前加上explain即可 mysql> explain select * from stu where id3; ---------------------------------------------------------------------------------------------------------- | id | s…

【MySQL】8.0新特性、窗口函数和公用表表达式

文章目录 1. 新增特性2. 移除旧特性2.1 优点2.2 缺点 3. 新特性1&#xff1a;窗口函数3.1 使用窗口函数前后对比3.2 窗口函数分类3.3 语法结构3.4 分类讲解3.4.1 序号函数3.4.1.1 ROW_NUMBER()函数3.4.1.2 RANK()函数3.4.1.3 DENSE_RANK()函数 3.4.2 分布函数3.4.2.1 PERCENT_R…

HiveServer2负载均衡

有多个HiveServer2服务时&#xff0c;可以借助Zookeeper服务实现访问HiveServer2的负载均衡&#xff0c;将HiveServer2的压力分担到多个节点上去。本文详细介绍HiveServer2负载均衡的配置及使用方法&#xff0c;请根据EMR集群&#xff08;普通集群和Kerberos集群&#xff09;的…

浏览器从输入url到渲染页面发生了什么?

浏览器从输入url到渲染页面发生了什么&#xff1f; 一、解析URL 首先浏览器做的第一步工作就是要对 URL 进行解析&#xff0c;浏览器会判断这个url的合法性 &#xff0c;以及是否有可用缓存&#xff08;如果有缓存即可以不用进行下一步的DNS域名解析&#xff09;&#xff0c;…

《代码随想录》刷题笔记——字符串篇【java实现】

反转字符串 https://leetcode.cn/problems/reverse-string/description/ 【双指针法&#xff1a;一前一后】 public void reverseString(char[] s) {for (int i 0; i < s.length / 2; i) {char temp s[i];s[i] s[s.length - 1 - i];s[s.length - 1 - i] temp;} }反转…

【软考中级】网络工程师:10.组网技术(理论篇)

交换机基础 交换机分类 > 根据交换方式分      - 存储转发式交换&#xff08;Store and Forward&#xff09;完整接收数据帧&#xff0c;缓存、验证、碎片过滤&#xff0c;然后转发。   优点&#xff1a;可以提供差错校验和非对称交换。   缺点&#xff1a;延迟大。…