自学内容网 自学内容网

Pytorch学习_02 NumPy(下):深度学习中的常用操作

目录

数据加载阶段

Pillow方式

OpenCV方式

索引与切片

数组的拼接

方法一:使用np.newaxis

方法二:直接赋值

使用Pillow读取图像并显示通道代码

深拷贝(副本)与浅拷贝(视图)

使用OpenCV读取图像并显示通道代码

模型评估

Argmax  /  Argmin:求最大/最小值对应的索引

Argsort:数组排序后返回原数组的索引

小结


其实基本所有深度学习的项目都可以这样划分(分解成数据加载、训练与模型评估三部分),其中数据加载跟模型评估中,就经常会用到NumPy数组的相关操作。就以传统的解决图片分类问题来说,先来看看数据的加载。

数据加载阶段

这个阶段我们要做的就是把训练数据读进来,然后给模型训练使用。

训练数据不外乎这三种:图片文本以及类似二维表那样的结构化数据

不管使用PyTorch还是TensorFlow,或者是传统机器学习的scikit-learn,我们在读入数据这一块,都会先把数据转换成NumPy的数组,然后再进行后续的一系列操作。

对于图片的处理,一般会使用PillowOpenCV这两个模块。

虽然Pillow和OpenCV功能看上去都差不多,但还是有区别的。在PyTorch中,很多图片的操作都是基于Pillow的,所以当使用PyTorch编程出现问题,或者要思考、解决一些图片相关问题时,要从Pillow的角度出发。

下面我们先以单张图片为例,将下面这个动物图片分别用Pillow与OpenCV读入,然后转换为NumPy的数组。

Pillow方式

首先,我们需要使用Pillow中的下述代码读入上面的图片。

Pillow是以二进制形式读入保存的,那怎么转为NumPy格式呢?这个并不难,我们只需要利用NumPy的asarray方法,就可以将Pillow的数据转换为NumPy的数组格式。

OpenCV方式

OpenCV的话,不再需要我们手动转格式,它直接读入图片后,就是以NumPy数组的形式来保存数据的,如下面的代码所示。

结合代码输出可以发现,我们读入后的数组的最后一个维度是3,这是因为图片的格式是RGB格式,表示有R、G、B三个通道。对于计算视觉任务来说,绝大多数处理的图片都是RGB格式,如果不是RGB格式的话,要记得事先转换成RGB格式。这里有个地方需要关注,Pillow读入后通道的顺序就是R、G、B (也就是说,当你用Pillow读取一张图像时,图像的第一个通道是红色,第二个通道是绿色,第三个通道是蓝色。),而OpenCV读入后顺序是B、G、R

模型训练时的通道顺序需与预测的通道顺序要保持一致。也就是说使用Pillow训练,使用OpenCV读入图片直接进行预测的话,不会报错,但结果会不正确,一定要注意。

接下来,我们验证一下Pillow与OpenCV读入数据通道的顺序是否如此,借此引出有关Numpy数组索引与切片、合并等常见问题。

怎么验证这条结论呢?只需要将R、G、B三个通道的数据单独提取出来,然后令另外两个通道的数据全为0即可。

为什么要这样做?

RGB色彩模式是工业界的一种颜色标准,RGB分别代表红、绿、蓝三个通道的颜色,将这三种颜色混合在一起,就形成了我们眼睛所能看到的所有颜色。

RGB三个通道各有256个亮度,分别用数字0到255表示,数字越高代表亮度越强,数字0则是代表最弱的亮度。

在一个例子中,如果一个通道的数据再加另外两个全0的通道(相当于关闭另外两个通道),最终图像以红色格调呈现出来的话,我们就可以认为该通道的数据是来源于R通道,G与B通道的证明同样可以如此。

首先我们提取出RGB三个通道的数据,这可以从数组的索引与切片说起。

索引与切片

NumPy数组的索引方式与Python的列表的索引方式相同,也同样支持切片索引。

这里需要你注意的是在NumPy数组中经常会出现用冒号来检索数据的形式,如下所示:

这是什么意思呢?“:”代表全部选中的意思。我们的图片读入后,会以下图的状态保存在数组中。

上述代码的含义就是取第三个维度索引为0的全部数据,换句话说就是,取图片第0个通道的所有数据。

这样的话,通过下面的代码,我们就可以获得每个通道的数据了。

获得了每个通道的数据,接下来就需要生成一个全0数组,该数组要与im_pillow具有相同的宽高。

全0数组你还记得怎么生成吗?生成的代码如下所示。

然后,我们只需要将全0的数组与im_pillow_c1、im_pillow_c2、im_pillow_c3进行拼接,就可以获得对应通道的图像数据了。

数组的拼接

刚才我们拿到了单独通道的数据,接下来就需要把一个分离出来的数据跟一个全0数组拼接起来。如下图所示,红色的可以看作单通道数据,白色的为全0数据。

NumPy数组为我们提供了np.concatenate((a1, a2, …), axis=0)方法进行数组拼接。其中,a1,a2, …就是我们要合并的数组;axis是我们要沿着哪一个维度进行合并,默认是沿着0轴方向。

对于我们的问题,是要沿着2轴的方向进行合并,也是我们最终的目标是要获得下面的三幅图像

那么,我们先将im_pillow_c1与全0数组进行合并,生成上图中最左侧的数组,有了图像的数组才能获得最终图像。合并的代码跟输出结果如下:

错误的原因是在2维数组中,axis如果等于2的话会越界。

  • 使用 axis 时: 指明的轴需要存在于所有数组的维度中。对于二维数组,轴 0 和 1 是有效的,而 axis=2 会导致错误。

我们看看im_pillow_c1与zeros的形状。

我们要合并的两个数组维度并不一样。那么如何统一维度呢?

将im_pillow_c1变成(116, 318, 1)即可。

方法一:使用np.newaxis

我们可以使用np.newaxis让数组增加一个维度,使用方式如下。

运行上面的代码,就可以将2个维度的数组转换为3个维度的数组了。

 这个操作在看深度学习相关代码的时候经常会看到,只不过PyTorch中的函数名unsqueeze(), TensorFlow的话是与NumPy有相同的名字,直接使用tf.newaxis就可以了。

然后我们再次将im_pillow_c1与zeros进行合并,这时就不会报错了,代码如下所示:

方法二:直接赋值

增加维度的第二个方法就是直接赋值,其实我们完全可以生成一个与im_pillow形状完全一样的全0数组,然后将每个通道的数值赋值为im_pillow_c1、im_pillow_c2与im_pillow_c3就可以了。我们用这种方式生成上图中的中间与右边图像的数组。

这样的话,我们就可以将三个通道的RGB图片打印出来了。

代码如下:

使用Pillow读取图像并显示通道代码

from PIL import Image  
import numpy as np  
import matplotlib.pyplot as plt  

# 使用Pillow读取图像  
image_path = 'F:/anim.jpg'  
image = Image.open(image_path)  

# 验证图像的大小和模式  
print("Original Image Size:", image.size)  # (宽度, 高度)  
print("Original Image Mode:", image.mode)   # Mode should be 'RGB'  

# 将图像转换为NumPy数组  
rgb_image = np.array(image)  

# 分离 R、G、B 通道  
r_channel = rgb_image[:, :, 0]  # 红色通道  
g_channel = rgb_image[:, :, 1]  # 绿色通道  
b_channel = rgb_image[:, :, 2]  # 蓝色通道  

# 只保留红色通道  
red_only = np.zeros_like(rgb_image)  # 创建与原图相同形状的全零数组  
red_only[:, :, 0] = r_channel  # 只将红色通道保留下来  

# 只保留绿色通道  
green_only = np.zeros_like(rgb_image)  # 创建与原图相同形状的全零数组  
green_only[:, :, 1] = g_channel  # 只将绿色通道保留下来  

# 只保留蓝色通道  
blue_only = np.zeros_like(rgb_image)  # 创建与原图相同形状的全零数组  
blue_only[:, :, 2] = b_channel  # 只将蓝色通道保留下来  

# 转换为 RGB 以便使用 Matplotlib 显示  
# 因为Pillow读取的是RGB格式,所以不需要进行色彩空间转换  
red_only_pil = Image.fromarray(red_only)  
green_only_pil = Image.fromarray(green_only)  
blue_only_pil = Image.fromarray(blue_only)  

# 显示结果  
plt.figure(figsize=(10, 8))  

plt.subplot(2, 2, 1)  
plt.title('Original Image (RGB)')  
plt.imshow(image)  # 显示原图  
plt.axis('off')  

plt.subplot(2, 2, 2)  
plt.title('Red Channel Only')  
plt.imshow(red_only_pil)  # 显示只包含红色通道的图像  
plt.axis('off')  

plt.subplot(2, 2, 3)  
plt.title('Green Channel Only')  
plt.imshow(green_only_pil)  # 显示只包含绿色通道的图像  
plt.axis('off')  

plt.subplot(2, 2, 4)  
plt.title('Blue Channel Only')  
plt.imshow(blue_only_pil)  # 显示只包含蓝色通道的图像  
plt.axis('off')  

# 显示图像  
plt.show()

深拷贝(副本)与浅拷贝(视图)

刚才我们通过获取图片通道数据的练习,不过操作确实比较繁琐,介绍这些方法也主要是为了更好掌握切片索引和数组拼接的知识点。

其实我们还有一种更加简单的方式获得三个通道的BGR数据,只需要将图片读入后,直接将其中的两个通道赋值为0即可。代码如下所示:

Pillow 图像转换为 NumPy 数组时,一些情况下生成的数组是只读的,导致无法直接进行切片赋值。

我们将刚才报错的程序修改成下面的形式就可以了。

  • 1: 表示选择从第二个通道(索引1,通常对应绿色通道)开始到最后一个通道的所有通道(也就是说,选择绿色和蓝色通道)。

使用Pillow库读入图像,然后直接将其中的两个通道赋值为0,以便得到每个颜色通道的单独图像,上面代码的提取通道部分可以这样修改:

我们可以使用copy来复制一个数组。前面说到创建数组时就提过,np.array()属于深拷贝,np.asarray()则是浅拷贝。

  • 浅拷贝(视图): 浅拷贝是指创建一个新的数组,但这个新数组会与原数组共享同样的数据。这意味着对新数组的修改会影响原数组,因为它们实际上指向同一块内存。

  • 视图我们通常使用view()来创建。常见的切片操作也会返回对原数组的浅拷贝

  • 举例:

  • shallow_copy 是 original_array 的一个视图。尽管 shallow_copy 是一个新数组,但它的前几个元素指向的是 original_array 中的同一数据。
  • 修改 shallow_copy 的元素会影响 original_array 中对应位置的元素,因为它们共享同一块内存。
  • 深拷贝: 深拷贝则是创建原数据的完全独立副本。对新数组的修改不会影响原数组。

  • 如果希望创建一个与原数组完全独立的副本,可以使用 np.copy() 方法。

  • 举例:

使用OpenCV读取图像并显示通道代码

用上述的方法对OpenCV读取图片读入通道顺序进行验证,以下是完整代码:

只需要更改图像读取和通道提取部分的代码

import cv2  
import numpy as np  
from matplotlib import pyplot as plt  

# 使用 OpenCV 读取图像  
bgr_image = cv2.imread('F:/anim.jpg')  

# 验证图像的形状  
print("Original BGR image shape:", bgr_image.shape)  # (高度, 宽度, 3)  

# 分离 B、G、R 通道  
b_channel = bgr_image[:, :, 0]  # 蓝色通道  
g_channel = bgr_image[:, :, 1]  # 绿色通道  
r_channel = bgr_image[:, :, 2]  # 红色通道  

# 只保留红色通道  
red_only = np.zeros_like(bgr_image)  # 创建与原图相同形状的全零数组  
red_only[:, :, 2] = r_channel  # 只将红色通道保留下来  

# 只保留绿色通道  
green_only = np.zeros_like(bgr_image)  # 创建与原图相同形状的全零数组  
green_only[:, :, 1] = g_channel  # 只将绿色通道保留下来  

# 只保留蓝色通道  
blue_only = np.zeros_like(bgr_image)  # 创建与原图相同形状的全零数组  
blue_only[:, :, 0] = b_channel  # 只将蓝色通道保留下来  

# 转换为 RGB 以便使用 Matplotlib 显示  
red_only_rgb = cv2.cvtColor(red_only, cv2.COLOR_BGR2RGB)  
green_only_rgb = cv2.cvtColor(green_only, cv2.COLOR_BGR2RGB)  
blue_only_rgb = cv2.cvtColor(blue_only, cv2.COLOR_BGR2RGB)  

# 显示结果  
plt.figure(figsize=(10, 8))  

plt.subplot(2, 2, 1)  
plt.title('Original Image (RGB)')  
plt.imshow(cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB))  # 显示原图  
plt.axis('off')  

plt.subplot(2, 2, 2)  
plt.title('Red Channel Only')  
plt.imshow(red_only_rgb)  # 显示只包含红色通道的图像  
plt.axis('off')  

plt.subplot(2, 2, 3)  
plt.title('Green Channel Only')  
plt.imshow(green_only_rgb)  # 显示只包含绿色通道的图像  
plt.axis('off')  

plt.subplot(2, 2, 4)  
plt.title('Blue Channel Only')  
plt.imshow(blue_only_rgb)  # 显示只包含蓝色通道的图像  
plt.axis('off')  

# 显示图像  
plt.show()

结果:

cv2.cvtColor() 是 OpenCV 中的一个函数,它用于转换图像的颜色空间。OpenCV 默认以 BGR(蓝色、绿色、红色)的顺序读取图像,而大多数图像显示库(如 Matplotlib)则默认使用 RGB(红色、绿色、蓝色)的顺序。如果直接将 BGR 图像传递给 Matplotlib 显示,颜色会出现扭曲,图像的颜色看起来会不正常。

模型评估

在模型评估时,我们一般会将模型的输出转换为对应的标签

假设现在我们的问题是将图片分为2个类别,包含动物的图片与不包含动物的图片。

模型会输出形状为(2, )的数组,我们把它叫做probs,它存储了两个概率,我们假设索引为0的概率是包含动物图片的概率,另一个是其它图片的概率,它们两个概率的和为1。如果动物对应的概率大,则可以推断该图片为包含动物的图片,否则为其他图片。

简单的做法就是判断probs[0]是否大于0.5,如果大于0.5,则可以认为图片是我们要寻找的。

但是如果我们需要判断图片的类别有很多很多种呢?

例如,有1000个类别的ImageNet,那可以遍历这个数组,求出最大值对应的索引。

那如果让你找出概率最大的前5个类别呢?

Argmax  /  Argmin:求最大/最小值对应的索引

NumPy的argmax(a, axis = None )方法可以为我们解决求最大值索引的问题。如果不指定axis,则将数组默认为1维。

获得拥有最大概率值的图片:

np.argmax(probs)

Argmin的用法跟Argmax差不多,它的作用是获得具有最小值的索引。

Argsort:数组排序后返回原数组的索引

比如需要你将图片分成10个类别,要找到具有最大概率的前三个类别。

模型输出的概率如下:

probs = np.array([0.075, 0.15, 0.075, 0.15, 0.0, 0.05, 0.05, 0.2, 0.25])

我们就可以借助argsort ( a , axis = -1, kind = None )函数来解决该问题。np.argsort的作用是对原数组进行从小到大的排序,返回的是对应元素在原数组中的索引。

np.argsort包括后面这几个关键参数:

  • a是要进行排序的原数组;
  • axis是要沿着哪一个轴进行排序,默认是-1,也就是最后一个轴;
  • kind是采用什么算法进行排序,默认是快速排序,还有其他排序算法

小结

对于NumPy的学习,数组的轴不是很好理解,后面花时间再学习一下。


原文地址:https://blog.csdn.net/2301_77254487/article/details/144403342

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!