使用C++调用PyTorch模型的弯弯绕绕,推荐LibTorch加载,C++处理

发布于:2024-08-19 ⋅ 阅读:(142) ⋅ 点赞:(0)

需求:使用C++调用Pytorch模型,对处理后的图像进行预测。
第一种,使用C++调用Python代码处理,使用pybind11源代码再末尾
缺点,导入Python包非常麻烦,执行的C++程序找不到cv2 torch包等等
本人解决了cv2 numpy等包,但是torch一致搞不定,大概率包不兼容导致的(pip下载的PyTorch官网,搞不懂)
明明Python’直接执行可以,但是C++调用Python执行就不行,非常奇怪
第二种,C++使用LibTorch将加载模型,进行处理源代码再末尾
这个会不用例会网络架构以及包的问题,只需将PyTorch模型保存为pt,然后使用C++加载即可,非常简单
需要重写图像处理的一些代码,保持和Python一致,输入模型进行预测。

第一种方法的经验之谈
在使用conda的环境中,conda和pip都可以下载包,两种方式最好不要混用,避免出现奇奇怪怪的错误
例如C:\ProgramData\anaconda3\DLLs_ctypes.pyd
PIL_imaging.cp311-win_amd64.pyd”。模块已生成,不包含符号。等等无法加载的错误
本人使用conda下载的,使用pip卸载后,重新使用pip装就解决了问题
cv 2,numpy,pillow等包都是如此,统一conda,或者统一使用pip,推荐使用pip
这样使用visual studio C++调用python import的包就不会出现加载不了的问题
务必注意!!!
pip install 包名==版本号

不要轻易使用conda 一键升级包,会导致部分版本太高不兼容
本人将python升级到3.11.9后,cv2版本对不上,也找不到对应版本的
冒险使用官网上最新的版本,竟然可以使用

expected np.ndarray (got numpy.ndarray)
在这里插入图片描述
这个问题也是numpy版本过高导致
pip install "numpy<1.26.4"就解决了
总之,版本太高就会出现各种奇奇怪怪的bug,修来修去非常浪费时间。

手动清除包缓存,可以使用以下命令:
pip cache purge
这个命令会清除所有缓存,包括已下载但未安装的软件包和已安装但未被使用的缓存。

只想清除特定软件包的缓存,可以使用以下命令:
pip cache remove package-name

第二种
在C++部署Pytorch(Libtorch)模型的方法总结(Win10+VS2017)
读取torchlib文件夹所有的lib文件的python代码

import os

def list_lib_files(directory):
    try:
        # 获取目录中的所有文件和文件夹
        files_and_dirs = os.listdir(directory)

        # 筛选出以“lib”结尾的文件
        lib_files = [f for f in files_and_dirs if f.endswith('.lib')]

        # 打印每个文件的全名
        for file_name in lib_files:
            print(file_name)

    except Exception as e:
        print(f"发生错误: {e}")
directory_path = r'C:\Users\TomSawyer\Downloads\Compressed\libtorch\lib'
list_lib_files(directory_path)
asmjit.lib
c10.lib
cpuinfo.lib
dnnl.lib
fbgemm.lib
fbjni.lib
fmt.lib
kineto.lib
libprotobuf-lite.lib
libprotobuf.lib
libprotoc.lib
pthreadpool.lib
pytorch_jni.lib
sleef.lib
torch.lib
torch_cpu.lib
XNNPACK.lib

VS 配置外部DLL的引用路径【可执行文件的环境路径】
右键项目,属性->配置属性->调试->环境,在这里写入可执行文件运行时的环境路径
在这里插入图片描述

本人莎莎的以为要重写PyTorch模型网络架构,于是使用C++的LibTorch和gpt重写了一下,结果一致卡在load不上,非常奇怪,于是检查两个网络的架构,其实非常类似,忘记截图载下来了,就下面一张图
检查python的Pytorch模型 与 使用C++创建的LibTorch模型是否一致,先看看VGG模型长啥样
在这里插入图片描述

其实可以直接加载模型,pt文件里面保存了网络的架构的,无需重写,具体代码找一找就有
我之前都注释了,代码没问题的,自行理解一下
将pth保存为pt,再加载pt模型
.pt文件保存的是模型的全部,在加载时可以直接赋值给新变量model = torch.load(“filename.pt”)。
.pth保存的是模型参数,通过字符字典进行保存,在加载该类文件时应该先实例化一个具体的模型,然后对新建立的空模型,进行参数赋予。

import os
import cv2
from PIL import Image
from torchvision import transforms as T
import torch

load_model_path = "./checkpoints/Cnn.pth"
model = getattr(models, "Cnn")()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)
model.load(load_model_path)
model.eval()

var=torch.ones((1,3,224,224))
traced_script_module = torch.jit.trace(model, var)
traced_script_module.save("./checkpoints/Cnn.pt")

# 测试pt模型
model_pt = torch.load("./checkpoints/Cnn.pt")
model_pt.to(device)
model_pt.eval()

将Python保存的pth模型与pt模型进行对比,看看保存的两个模型是否有差异,没有任何差异
在这里插入图片描述
使用C++实现Python的图像处理部分,看看实现是否有差异,发现数值类似,略有差异在这里插入图片描述
猜测可能是摄像头保存一帧 与 C++读取图像的不同所引起的,于是使用Python读取图像试试
发现结果一致了,原来是保存为jpg格式带来的有损压缩导致的
统一保存为png格式即可,无损格式
在这里插入图片描述

全部代码
Python使用Pytorch加载模型,处理图像,模型预测结果(模型是错的,只是走个流程)

import os
import cv2
from PIL import Image
from torchvision import transforms as T
import models
import datetime
import torch

# 创建保存图像的文件夹
if not os.path.exists('testModel'):
    os.makedirs('testModel')

# 1  先使用摄像头读取一帧图像,保存
# 0,读取摄像头保存的图片
flag = 0
if flag==1:
    cap = cv2.VideoCapture(0)
    if not cap.isOpened():
        print("无法打开摄像头")
    else:
        # 读取一帧图像
        ret, frame = cap.read()
        if ret:
            # 保存原始图像
            original_image_path = 'testModel/test1_original_image.png'
            cv2.imwrite(original_image_path, frame)
            print(f"原始图像已保存到 {original_image_path}")
            # 释放摄像头资源
    cap.release()
else:
    ##读取摄像头保存的一帧画面
    frame =cv2.imread('testModel/test1_original_image.png');


"""
    此类主要提供模型调用方法,接受一个图片,同时返回一个力的结果
"""
model_list = os.listdir("./checkpoints/")
# load_model_path = "./checkpoints/"+ model_list[0]
load_model_path = "./checkpoints/Cnn.pth"
model = getattr(models, "Cnn")()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)
model.load(load_model_path)
model.eval()

# 测试pt模型
model_pt = torch.load("./checkpoints/Cnn.pt")
model_pt.to(device)
model_pt.eval()

# veision1 预定义图像处理模式,图像预处理设置,正态化数据来自于图片空间,因此需要Normalize.py获取,此处已指定
normalize = T.Normalize(mean=[0.684, 0.831, 0.83],
                        std=[0.684, 0.831, 0.83])
transform = T.Compose(
    [
        T.ToTensor(),
        # normalize
    ]
)

img = frame
cv2.imshow("  ", img)
cv2.waitKey(0)
img=cv2.resize(img,[640,360])
# 裁剪图像,保留从第170列到第530列的区域
img=img[::,170:530,::] #样品1
cv2.imshow("  ", img)
cv2.waitKey(0)
# 保存处理后的图像
processed_image_path = 'testModel/test1_processed_image.png'
cv2.imwrite(processed_image_path, img)
print(f"处理后的图像已保存到 {processed_image_path}")

img = Image.fromarray(img)
img = transform(img)
img = img.view(1, img.shape[0], img.shape[1], img.shape[2])#改变张量的形状,但不会改变其数据 第一、第二、第三和第四维度大小。
train = img.to(device)
force= model(train)
force = force.squeeze().detach().cpu()
force_act = force.tolist()
# 时间格式为分钟:秒.微秒
force_act.append(datetime.datetime.now().strftime("%M:%S.%f"))
# force_act为输出力的列表格式【f(x),f(y),f(z),时间】
print('pth模型结果:',force_act)

force_pt= model_pt(train)
force_pt = force_pt.squeeze().detach().cpu()
force_act_pt = force_pt.tolist()
# 时间格式为分钟:秒.微秒
force_act_pt.append(datetime.datetime.now().strftime("%M:%S.%f"))
# force_act为输出力的列表格式【f(x),f(y),f(z),时间】
print('pt模型结果:',force_act_pt)

C++加载torch script保存的pt模型,重新实现图像处理,给出结果

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

// 假设你已经定义了transform函数
torch::Tensor transform(const cv::Mat &img) {
    // 这里需要根据你的transform逻辑来实现
    // 例如,假设你只是将图像转换为Tensor
    torch::Tensor tensor_image = torch::from_blob(img.data, { img.rows, img.cols, img.channels() }, torch::kFloat32);将OpenCV图像数据(存储在img.data中)转换为一个PyTorch张量,张量的形状为[img.rows, img.cols, img.channels()],数据类型为kFloat32
    tensor_image = tensor_image.permute({ 2, 0, 1 }).to(torch::kFloat); // 转换为CHW格式并转换为Float类型
    return tensor_image;
}

int test_pt() {
    // 假设模型路径和加载方式
    torch::jit::script::Module module;
    try {
        module = torch::jit::load(R"(C:\Users\TomSawyer\source\repos\testPython\force_indentify\checkpoints\Cnn.pt)");
    } catch (const c10::Error &e) {
        std::cerr << "Error loading the model\n";
        return {};
    }

    // 检查CUDA是否可用
    bool is_cuda = torch::cuda::is_available();
    torch::Device device(is_cuda ? torch::kCUDA : torch::kCPU);
    std::cout << (is_cuda ? "CUDA is available. Using GPU." : "Using CPU.") << std::endl;
    module.to(device);
    module.eval();

    // 指定图像路径
    std::string imagePath = R"(C:\Users\TomSawyer\source\repos\testPython\force_indentify\testModel\test1_original_image.png)";

    while (1) {
        cv::Mat frame = cv::imread(imagePath, cv::IMREAD_COLOR);

        // 检查图像是否加载成功
        if (frame.empty()) {
            std::cerr << "无法加载图片,请检查路径: " << imagePath << std::endl;
            return -1;
        }
        // 显示图片
        cv::imshow("Loaded Image", frame);
        cv::waitKey(0);

        cv::resize(frame, frame, cv::Size(640, 360));
        cv::imshow("frame", frame);
        cv::waitKey(0);
        frame = frame(cv::Rect(170, 0, 360, 360)); // 裁剪图像170到360列
        cv::imshow("frame", frame);
        cv::waitKey(0);

        frame.convertTo(frame, CV_32FC3, 1.0 / 255.0);
        // 应用transform
        torch::Tensor tensor_image = transform(frame);
        // 调整形状
        tensor_image = tensor_image.unsqueeze(0); // 增加batch维度
        tensor_image = tensor_image.to(device);

        // 模型预测
        std::vector<torch::jit::IValue> inputs;
        inputs.push_back(tensor_image);
        at::Tensor output = module.forward(inputs).toTensor();//bug
        output = output.squeeze().detach().cpu();
        std::vector<float> force_act = { output[0].item<float>(), output[1].item<float>(), output[2].item<float>() };
        std::cout << "pt_Force: " << force_act[0] << ", " << force_act[1] << ", " << force_act[2] << std::endl;
    }
    return {};
}


int main() {
    test_pt(); //测试pt模型和python的pth
    return 0;
}

pybind11,运行一直没解决,cv2,numpy包解决了,Torch包一直搞不定

#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <iostream>
#include <cstdlib> // for _putenv

namespace py = pybind11;

int main() {
    // 设置 Python 解释器的路径为 Conda 环境
    std::string pythonHome = R"(C:\ProgramData\anaconda3)";
    std::string pythonHomeEnv = "PYTHONHOME=" + pythonHome;
    _putenv(pythonHomeEnv.c_str());

    // 设置 Python 路径
    std::string pythonPath = R"(C:\ProgramData\anaconda3\Lib\site-packages;C:\Users\TomSawyer\source\repos\testPython\force_indentify;)";
    std::string pythonPathEnv = "PYTHONPATH=" + pythonPath;
    _putenv(pythonPathEnv.c_str());

    // 添加 PATH 环境变量
    std::string path = R"(C:\ProgramData\anaconda3\Library\bin;)";
    std::string pathEnv = "PATH=" + path + ";" + getenv("PATH");
    _putenv(pathEnv.c_str());


    //std::cout << "PythonHome: " << pythonHomeEnv << std::endl;
    //std::cout << "PythonPath: " << pythonPathEnv << std::endl;
    std::cout << "PATH: " << getenv("PATH")<< std::endl;

    // 初始化 Python 解释器
    py::scoped_interpreter guard{};
    //在使用 pybind11 导入 Python 模块时,路径的指定方式需要注意。通常情况下,Python 模块的导入路径应该是模块的名称,而不是文件的绝对路径
    // 添加模块路径到 sys.path
    py::module::import("sys").attr("path").attr("append")(R"(C:\Users\TomSawyer\source\repos\testPython\force_indentify;C:\Users\TomSawyer\source\repos\testPython\force_indentify\models)");

    //std::cout <<std::endl <<"PATH: " << getenv("PATH") << std::endl;
    // 导入 Python 模块
    py::module script = py::module::import("interface_single");

    // 调用 Python 函数并获取返回的列表
    py::object result = script.attr("inter")();
    py::list py_list = result.cast<py::list>();

    // 将 Python 列表转换为 C++ 向量
    std::vector<int> cpp_list;
    for (auto item : py_list) {
        cpp_list.push_back(item.cast<int>());
    }

    // 处理 C++ 向量
    for (int value : cpp_list) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到