需求:使用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;
}