LibTorch部署图像处理相关算法详细教程(附代码)

news2024/11/24 6:24:52

深度学习图像处理相关代码LibTorch部署详细教程

  • 前言
  • LibTorch简介
  • LibTorch环境安装及问题解决
  • LibTorch涉及的Tensor基本操作
    • 张量初始化
    • 张量变形
    • 张量截取
    • 张量间操作
  • 部署过程
    • 测试环境
    • 推理过程代码Demo
    • 扩展部分

前言

本文写于调研深度学习部署方法工作中,需要将图像分割模型进行部署。前面博客记录了如何直接打包深度学习模型成exe文件,方便快捷,但是不适合实际工作中作为深度学习模型部署的方法。主要由于打包的方式运行较慢,而且与其余代码的兼容性较差,因此学习了一下LibTorch相关内容,并把使用PyTorch训练的模型成功部署。

LibTorch简介

Libtorch是Pytorch的C++接口,实现了在C++中进行网络训练、网络推理的功能。除此之外,由于Libtorch中的大部份接口都是与Pytorch一致的,所以Libtorch还是一个很强大的张量库,有着类似Pytorch的清晰接口,这在C++中很难得的。如果你用过C++ Tensor库,就会发现写法比较复杂,学习成本高。因为强类型的限制和通用容器类型的缺失,C++相比Python天然更复杂,库设计者因为语言使用习惯,以及为了性能等因素,设计的接口一般都是高效但难用的。而Libtorch采用了与Pytorch类似的函数接口,如果你使用过Pytorch的话,使用Libtorch学习成本很低。

LibTorch环境安装及问题解决

此部分内容本人已在另一博客讲解,欢迎浏览

LibTorch涉及的Tensor基本操作

张量初始化

LibTorch(pytorch c++)的大多数api和PyTorch保持一致,因此,LibTorch中张量的初始化也和PyTorch中的类似。本文介绍四种深度图像编程需要的初始化方法。
第一种,固定尺寸和值的初始化。

//常见固定值的初始化方式
auto b = torch::zeros({3,4});
b = torch::ones({3,4});
b= torch::eye(4);
b = torch::full({3,4},10);
b = torch::tensor({33,22,11});

PyTorch中用[]表示尺寸,而cpp中用{}表示。zeros产生值全为0的张量。ones产生值全为1的张量。eye产生单位矩阵张量。full产生指定值和尺寸的张量。torch::tensor({})也可以产生张量,效果和pytorch的torch.Tensor([])或者torch.tensor([])一样。

第二种,固定尺寸,随机值的初始化方法

//随机初始化
auto r = torch::rand({3,4});
r = torch::randn({3, 4});
r = torch::randint(0, 4,{3,3});

rand产生0-1之间的随机值,randn取正态分布N(0,1)的随机值,randint取[min,max)的随机整型数值。

第三种,从c++的其他数据类型转换而来

int aa[10] = {3,4,6};
std::vector<float> aaaa = {3,4,6};
auto aaaaaaa = torch::tensor(aaaa);
auto aaaaa = torch::from_blob(aa,{3},torch::kFloat);
auto aaa = torch::from_blob(aaaa.data(),{3},torch::kFloat);

PyTorch可以接受从其他数据类型如numpy和list的数据转化成张量。LibTorch同样可以接受其他数据指针,通过from_blob函数即可转换。这个方式在部署中经常用到,如果图像是opencv加载的,那么可以通过from_blob将图像指针转成张量。

第四种,根据已有张量初始化

auto b = torch::zeros({3,4});
auto d = torch::Tensor(b);
d = torch::zeros_like(b);
d = torch::ones_like(b);
d = torch::rand_like(b,torch::kFloat);
d = b.clone();

这里,auto d = torch::Tensor(b)等价于auto d = b,两者初始化的张量d均受原张量b的影响,b中的值发生改变,d也将发生改变,但是b如果只是张量变形,d却不会跟着变形,仍旧保持初始化时的形状,这种表现称为浅拷贝。zeros_like和ones_like顾名思义将产生和原张量b相同形状的0张量和1张量,randlike同理。最后一个clone函数则是完全拷贝成一个新的张量,原张量b的变化不会影响d,这被称作深拷贝。

张量变形

torch改变张量形状,不改变张量存储的data指针指向的内容,只改变张量的取数方式。LibTorch的变形方式和PyTorch一致,有view,transpose,reshape,permute等常用变形。

auto b = torch::full({10},3);
b.view({1, 2,-1});
std::cout<<b;
b = b.view({1, 2,-1});
std::cout<<b;
auto c = b.transpose(0,1);
std::cout<<c;
auto d = b.reshape({1,1,-1});
std::cout<<d;
auto e = b.permute({1,0,2});
std::cout<<e;

.view不是inplace操作,需要加=。变形操作没太多要说的,和PyTorch一样。还有squeeze和unsqueeze操作,也与PyTorch相同。

张量截取

通过索引截取张量,代码如下

auto b = torch::rand({10,3,28,28});
std::cout<<b[0].sizes();//第0张照片
std::cout<<b[0][0].sizes();//第0张照片的第0个通道
std::cout<<b[0][0][0].sizes();//第0张照片的第0个通道的第0行像素 dim为1
std::cout<<b[0][0][0][0].sizes();//第0张照片的第0个通道的第0行的第0个像素 dim为0

除了索引,还有其他操作是常用的,如narrow,select,index,index_select。

std::cout<<b.index_select(0,torch::tensor({0, 3, 3})).sizes();//选择第0维的0,3,3组成新张量[3,3,28,28]
std::cout<<b.index_select(1,torch::tensor({0,2})).sizes(); //选择第1维的第0和第2的组成新张量[10, 2, 28, 28]
std::cout<<b.index_select(2,torch::arange(0,8)).sizes(); //选择十张图片每个通道的前8列的所有像素[10, 3, 8, 28]
std::cout<<b.narrow(1,0,2).sizes();//选择第1维,从0开始,截取长度为2的部分张量[10, 2, 28, 28]
std::cout<<b.select(3,2).sizes();//选择第3维度的第二个张量,即所有图片的第2行组成的张量[10, 3, 28]

index需要单独说明用途。在pytorch中,通过掩码Mask对张量进行筛选是容易的直接Tensor[Mask]即可。但是c++中无法直接这样使用,需要index函数实现,代码如下:

auto c = torch::randn({3,4});
auto mask = torch::zeros({3,4});
mask[0][0] = 1;
std::cout<<c;
std::cout<<c.index({mask.to(torch::kBool)});

有网友提问,这样index出来的张量是深拷贝的结果,也就是得到一个新的张量,那么如何对原始张量的mask指向的值做修改呢。查看torch的api发现还有index_put_函数用于直接放置指定的张量或者常数。组合index_put_和index函数可以实现该需求。

auto c = torch::randn({ 3,4 });
auto mask = torch::zeros({ 3,4 });
mask[0][0] = 1;
mask[0][2] = 1;
std::cout << c;
std::cout << c.index({ mask.to(torch::kBool) });
std::cout << c.index_put_({ mask.to(torch::kBool) }, c.index({ mask.to(torch::kBool) })+1.5);
std::cout << c;

此外python中还有一种常见取数方式tensor[:,0::4]这种在第1维,起始位置为0,间隔4取数的方式,在c++中实现需要借助torch::linspace实现。linspace本身接受三个参数,start,end和step,分别表示起始,终止和间隔。组合前面提到的index_select和linspace即可实现:

auto tensor = torch::randn({ 3,12 });
auto tensor_slice = tensor.index_select(1, torch::linspace(0, tensor.size(1), 4));

张量间操作

拼接和堆叠

auto b = torch::ones({3,4});
auto c = torch::zeros({3,4});
auto cat = torch::cat({b,c},1);//1表示第1维,输出张量[3,8]
auto stack = torch::stack({b,c},1);//1表示第1维,输出[3,2,4]
std::cout<<b<<c<<cat<<stack;

到这读者会发现,从pytorch到libtorch,掌握了[]到{}的变化就简单很多,大部分操作可以直接迁移。

四则运算操作同理,像对应元素乘除直接用*和/即可,也可以用.mul和.div。矩阵乘法用.mm,加入批次就是.bmm。

auto b = torch::rand({3,4});
auto c = torch::rand({3,4});
std::cout<<b<<c<<b*c<<b/c<<b.mm(c.t());

其他一些操作像clamp,min,max这种都和pytorch类似,仿照上述方法可以自行探索。

部署过程

测试环境

当你在电脑上的LIbTorch的环境配置完成,需要用代码测试一下环境是否配置成功,cuda以及cudnn是否可以正常使用。可以复制以下代码添加到cpp文件进行测试。

int main()
{
	//定义使用cuda
	auto device = torch::Device(torch::kCUDA);
	std::cout << "CUDA:" << torch::cuda::is_available();
	std::cout << "CUDNN:  " << torch::cuda::cudnn_is_available() << std::endl;
	std::cout << "GPU(s): " << torch::cuda::device_count() << std::endl;
}

当上述代码前两项返回True,最后一项返回设备GPU个数时,即证明环境已成功配置,cuda,cudnn可以正常调用,这样就可以进行部署代码的编写了。

推理过程代码Demo

以下是一个完整的推理过程代码,包括通过OpenCV加载图像,并转为Tensor进行推理操作。

int main()
{
	//定义使用cuda
	auto device = torch::Device(torch::kCUDA);
	//读取图片并展示
	cv::Mat image = cv::imread("E:/深度学习部署相关/TransUNet-main/data/train/images/1.2.826.0.1.3680043.2.461.13267976.60458625.png");
	cv::Size size = image.size();
	std::cout << size;
	//打印三维图像像素值,需要使用以下方式,先定义一个cv::Vec类型Vec1,在通过cv::Mat.at<Vec1>(i, j)[0]访问,具体见下实例
	typedef cv::Vec<uchar, 3> Vecci; //uchar为cv::Mat的数据类型,3为图像通道数。
	for (int i = 52; i < 53; i++)
		{
			for (int j = 371; j < 385; j++)
			{
				cout << "Value0 is:" << image.at<Vecci>(i, j)[0] << endl;
				cout << "Value1 is:" << image.at<Vecci>(i, j)[1] << endl;
				cout << "Value2 is:" << image.at<Vecci>(i, j)[2] << endl;
			}
		}
	cv::imshow("img", image);
	cv::waitKey(0);

	//读取标贴并展示
	cv::Mat lable = cv::imread("E:/深度学习部署相关/TransUNet-main/data/train/labels/1.2.826.0.1.3680043.2.461.13267976.60458625.png");
	cv::Mat gray;
	cv::cvtColor(lable, gray, cv::COLOR_BGR2GRAY);
	cv::normalize(gray, gray, 0, 255, cv::NORM_MINMAX);
	cv::imshow("label", gray);
	cv::waitKey(0);

	//缩放至指定大小
	cv::resize(image, image, cv::Size(256, 256));
	//转成张量
	auto input_tensor = torch::from_blob(image.data, { image.rows, image.cols, 3 }, torch::kByte).permute({ 2, 0, 1 }).unsqueeze(0).to(torch::kFloat32);
	//加载模型
	auto model = torch::jit::load("E:/深度学习部署相关/LibTorch/Project1/TransUNet.pt");
	model.to(device);
	model.eval();
	//前向传播
	auto output = model.forward({ input_tensor.to(device) }).toTensor();
	output = torch::squeeze(torch::argmax(torch::softmax(output, 1), 1), 0);
	std::cout << output.sizes() << std::endl;
	output = output.to(torch::kU8).to(torch::kCPU);
	//将tensor转为cv::Mat格式,进行展示
	cv::Mat Img(output.sizes()[0], output.sizes()[1], CV_8U, output.data_ptr());
	cv::resize(Img, Img, size);
	cv::normalize(Img, Img, 0, 255, cv::NORM_MINMAX);
	cv::imshow("result", Img);
	cv::waitKey(0);
	return 0;
	}

扩展部分

上述部分涉及了自然图像进行深度学习处理的全过程,但是不是所有的图像数据都是自然图像,OpenCV并不适合加载所有的像素数据,作者就是需要在工作中加载二进制存储的非标准图像数据,这时需要如何加载数据并转换成Tensor进行模型推理呢,过程还是较为复杂,笔者在此由于数据类型转换卡了很久,最后终于成功运行。以下是相关部分代码,需要的读者可以参考。

#include <opencv2/opencv.hpp>
#include <torch/torch.h>
#include <torch/script.h> 
#include <iostream>
#include "dirent.h"
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <string>
#include <windows.h>
using namespace std;

//读取二进制图像文件,并将其值归一化到0~255
int* ReadSlice(std::string file, const size_t size)
{
	std::ifstream ifs(file, std::ios::binary);
	signed short* img = new signed short[size];
	if (ifs.is_open())
	{
		ifs.read((char*)img, sizeof(int16_t) * size);
		ifs.close();
	}
	else
	{
		std::cout << "Unable to open file" << std::endl;
	}
	signed short maxValue = *max_element(img, img + size);
	signed short minValue = *min_element(img, img + size);

	int* newImg = new int[size];
	for (int i = 0; i < size; i++)
	{
		newImg[i] = int((float(img[i] - minValue) / float(maxValue - minValue)) * 255);
	}

	cout << "success loaded img" << endl;
	return newImg;
}


int main(int argc, char* argv[])
{
	// 检查参数个数
	if (argc != 3)
	{
		cout << "Usage: " << argv[0] << " folder_path" << endl;
		return 1;
	}

	// 获取文件夹路径
	string path = argv[1];
	int size = atoi(argv[2]);
	cout << path << endl;
	cout << size << endl;
	//int size = 562500;

	// 打开文件夹
	DIR* dir = opendir(path.c_str());

	if (dir == NULL)
	{
		cout << "Failed to open directory!" << endl;
		return 1;
	}

	auto device = torch::Device(torch::kCUDA);
	// 遍历文件夹
	struct dirent* entry;
	while ((entry = readdir(dir)) != NULL)
	{
		// 排除 . 和 .. 目录
		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || string(entry->d_name) == "ImageParam.ini")
		{
			continue;
		}

		// 输出文件名
		string filePath = path + "\\" + entry->d_name;
		cout << filePath << endl;
		cout << entry->d_name << endl;
		// 读取图像数据,并将其存储在数组结构中
		int* image = ReadSlice(filePath, size);

		const int length = sqrt(size);
		cv::Size imageSize (int(sqrt(size)), int(sqrt(size)));
		// 新建cv::Mat数据结构,并用读取的数组值进行赋值,注意cv::Mat的数据类型要前后保持一致
		cv::Mat Img(int(sqrt(size)), int(sqrt(size)), CV_8UC1);
		
		typedef cv::Vec<uchar, 3> Vec3c;
		for (int i = 0; i < Img.rows; i++)
		{
			for (int j = 0; j < Img.cols; j++)
			{
				//cout << int(image[i * length + j]) << endl;
				Img.at<uchar>(i, j) = int(image[i * length + j]);
			}
		}
		//将一维cv::Mat进行拼接,生成三维cv::Mat数据
		vector<cv::Mat> ImgMerge = { Img, Img, Img };
		cv::Mat ImgCopy = cv::Mat::zeros(int(sqrt(size)), int(sqrt(size)), CV_8UC3);
		cv::merge(ImgMerge, ImgCopy);

		cv::imwrite("E:\\demo.png", Img);
		cout << ImgCopy.size() << endl;
		cv::imshow("label", Img);
		cv::waitKey(0);
		cv::resize(ImgCopy, ImgCopy, cv::Size(256, 256));
		return 0}

总结: 至此LibTorch整体流程已经跑通,希望大家写代码可以顺顺利利,少出bug 。有任何问题可以评论区留言讨论 _在这里插入图片描述
参考文献
【1】https://zhuanlan.zhihu.com/p/369930315
【2】https://www.cnblogs.com/allentbky/p/14163898.html

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

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

相关文章

如何将PDF转Excel并保持原有格式不变?分享三个方法给大家!

在日常办公中&#xff0c;我们常常面临一个问题&#xff1a;领导给我们发了一个PDF文件&#xff0c;并要求我们尽快修改其中的表格数据。然而&#xff0c;当我们将PDF文件转换成Excel格式时&#xff0c;经常会出现文件排版错乱的情况。时间的紧迫和数据的混乱可能会让我们感到十…

【FTP】FTP被动模式跨网传输失败

FTP被动模式所需的端口21、20、60000-60050 假如端口20未开策略的话&#xff0c;造成传输失败 需要在FTP服务端用户配置文件里设置pasv_address&#xff08;对外的ip地址&#xff09; 特此记录一下&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;…

NLP(2)N-gram language Model (缺了一些平滑的方式介绍)

文章目录 N-gram Language ModelTrigram Example存在的问题smoothingLaplacian &#xff08;add-one&#xff09; smoothing案例 1案例 2 Add-k smoothingAbsolute Discounting案例 Interpolation 在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;语言模型&#xff08…

机器鸟实现扇动翅膀功能

1. 功能说明 本文示例将实现机器鸟扇动翅膀的功能。 2. 结构说明 鸟类的翅膀主要由肩关节、肘关节、腕关节组成&#xff0c;本样机利用组合机构设计机器鸟的扑翼机构。 拥有两个关节的机器鸟扑翼机构结构图 单侧翅膀 双翅 尾部 整机 3. 电子硬件 在这个示例中&#xff0c;我们…

使用Optuna进行PyTorch模型的超参数调优

Optuna是一个开源的超参数优化框架&#xff0c;Optuna与框架无关&#xff0c;可以在任何机器学习或深度学习框架中使用它。本文将以表格数据为例&#xff0c;使用Optuna对PyTorch模型进行超参数调优。 Optuna可以使用python pip安装&#xff0c;如pip install Optuna。也可以使…

【Spring】— 动态SQL :<foreach>元素、<bind>元素

目录 <foreach>元素<bind>元素 <foreach>元素 MyBatis中已经提供了一种用于数组和集合循环遍历的方式&#xff0c;那就是使用<foreach>元素。假设在一个用户表中有1000条数据&#xff0c;现在需要将id值小于100的用户信息全部查询出来&#xff0c;就可…

MFC(十三)多个对话框

设置向导模式 1.打开类视图&#xff0c;右键项目--->类向导-->添加Cpropsheet类&#xff0c;命名为mypropsheet CPropertySheet 是 MFC 的一个类&#xff0c;用于创建包含多个属性页的对话框。它可以使用内置的向导模式&#xff0c;向用户显示“下一步”和“上一步”按…

unity创建urp的方法

Unity里URP项目的介绍和创建 URP(Universal Render Pipeline)通用渲染管线&#xff0c;是Unity在2019.3版本之后推出的一种新的渲染管线。 对比起默认的渲染管线&#xff0c;他的使用上会产生了一些变化&#xff0c;包括了摄像机的使用、Shader的编写等。 传统的渲染管线在渲染…

采用动态规划来处理有向无环图最短路径问题,c++实现

采用动态规划来处理有向无环图最短路径问题&#xff0c;c实现 需求描述实现思路代码实现 需求描述 如图&#xff0c;在一个无环有向图中&#xff0c;找到起点0到终点的最短路径 实现思路 设s1,s2,…, st 是一条最短路径假设s1&#xff0c;s2&#xff0c;已求出&#xff0c;则…

JS 事件循环机制、调用栈、堆、主线程、宏任务队列、微任务队列、缓存管理之间的关系

一、事件循环机制 你是否想过&#xff0c;在控制台执行代码时&#xff0c;为什么能立即得到响应&#xff1f; 实际上&#xff0c;底层有一套模型机制叫 事件循环&#xff0c;换句话说&#xff0c;它是一个”死循环“&#xff0c; 里面负责监听&执行我们写的 JS 代码&#…

spring+springMVC+mybatis实现的物业管理系统

本系统借用了前辈的页面框架来实现了一个物业管理系统&#xff0c;使用到的主要技术有&#xff1a;SSM框架&#xff0c;MySql 8.0数据库&#xff0c;tomcat 8.0&#xff0c;使用maven进行依赖管理&#xff0c;前端页面使用的是jsp。整个系统分为用户端和管理员端。 用户端功能有…

Vue.js 中的权限控制是什么?如何进行权限控制?

Vue.js 中的权限控制是什么&#xff1f;如何进行权限控制&#xff1f; 在现代 Web 应用程序中&#xff0c;权限控制是一个重要的话题。Vue.js 作为一种现代的前端框架&#xff0c;提供了一些有用的工具和技术来实现权限控制。本文将介绍 Vue.js 中的权限控制的概念、作用以及如…

一文教你认清云渲染的优势和劣势

在当今数字化时代&#xff0c;云渲染作为一项创新的技术方案&#xff0c;正逐渐成为许多行业中的热门话题。与传统的本地渲染相比&#xff0c;云渲染具备许多独特的优势和劣势。本文将深入探讨云渲染的各项特点&#xff0c;帮助您全面了解这一技术&#xff0c;并为您提供有关云…

js+canvas实现劈腿关系图

【我愿称多对一 一对多关系为劈腿关系】 【仓库地址】gitgithub.com:yyccmmkk/rl.git 【显示所有线路细节】 【合并线路】 【合并同一个目标点线路】 【合并同一个出发点线路】 【数据结构】 原始数据只需要提供节点自身id 父点节id&#xff0c;劈腿关系图自动处理 [{id:z…

基于CSDN上海城市开发者社区第一次线下沙龙的筹备经验分享

引言 由于时间较为紧张、邹老师行程安排紧凑等因素影响&#xff0c;结合实际情况&#xff0c;改茶话会为聚餐&#xff0c;所以在举办线下沙龙的时候一定要根据实际情况来斟酌活动形式。本次活动是和哈士奇&#xff0c;哈哥一起筹备&#xff0c;也得到了哈哥的大力支持&#xff…

如何进行物联网开发可以快速完成产品研发?

物联网产品的研发与开发过程&#xff0c;是一个不断探索、创新、实践的过程&#xff0c;需要很长的周期&#xff0c;在这个过程中&#xff0c;项目团队往往需要投入大量的人力、物力和时间。同时&#xff0c;物联网产品研发过程中还涉及到大量的专业技术和知识&#xff0c;需要…

从git上拉取项目

目录 一、前期准备&#xff0c;获取git下载链接 二、idea下载 2.1.打开git下载界面 2.2.进入下载界面 2.3.下载前期配置 2.4.输入账号密码 2.5.下载完成后idea打开 2.6.下载完成后文件目录展示 三、命令行下载 3.1.打开所需要下载的项目路径 3.2.进入黑窗口 …

【算法系列之数组I】leetcode54.螺旋矩阵

704. 二分查找 力扣题目链接 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 输入: nums [-1,0,3,5,9,12], target 9…

【618备战巡礼】“三高”之第一高--如何打造高可用系统 | 京东云技术团队

前言 我们经常会说互联网“三高”&#xff0c;那什么是三高呢&#xff1f;我们常说的三高&#xff0c;高并发、高可用、高性能&#xff0c;这些技术是构建现代互联网应用程序所必需的。对于京东618备战来说&#xff0c;所有的中台系统服务&#xff0c;无疑都是围绕着三高来展开…

一文说清DC-DC BUCK电路(非常详细)

目录 摘要 BUCK原理 DC-DC芯片框图 自举电容 输出电感 输出电容和纹波 损耗 总结 摘要 DC-DC BUCK&#xff0c;是硬件工程师工作中使用频率非常高的电路&#xff0c;可以这么说&#xff0c;只要板子不是迷你型的&#xff0c;十有八九都有DC-DC。因此&#xff0c;对它的…