0 前言
图像的读取和处理是计算机视觉领域中的一个基本任务,在Python中,有几个流行的库可以用来读取和处理图像数据
0.1 Matplotlib介绍
Matplotlib是Python中一个非常流行的绘图库,它通常用于数据可视化,虽然它不是专门的图像处理库,但它提供了基本的图像读取和显示功能,其特点如下:
- 数据科学三剑客之一:Matplotlib常与Pandas和NumPy一起使用,是数据科学领域的重要工具
- 数据可视化:它主要用于创建高质量的图表和图形,但也可以用来显示图像
- 高仿Matlab:它的许多功能和界面与Matlab相似,对于熟悉Matlab的用户来说很容易上手
- 图像处理功能有限:Matplotlib没有内置的图像处理功能,因此,如果需要进行复杂的图像操作,则需要结合其他库,如NumPy或SciPy
- 基本图像操作:它提供了
plt.imread()
来读取图像,plt.imshow()
来显示图像,以及plt.imsave()
来保存图像
0.2 PIL介绍
PIL(Python Imaging Library)是Python的一个图像处理库,它提供了广泛的图像文件格式支持和强大的图像处理能力,其特点如下:
- Python内置库:PIL是Python的一个标准库(安装后即可使用)
- PyTorch无缝衔接:PIL与PyTorch有很好的兼容性,可以方便地用于深度学习项目中的图像预处理
- 广泛的图像处理功能:提供了图像的打开、操作、保存等多种功能
0.3 OpenCV介绍
OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库,它提供了大量的图像处理和计算机视觉功能,其特点如下:
- 平面图像处理的金标准:OpenCV是图像处理和计算机视觉领域中使用最广泛的库之一
- C++编写:它最初是用C++编写的(效率高),但提供了Python等其他语言的接口
- Python调用:通过安装
opencv-python
包,即可在Python中方便地使用OpenCV的功能 - 功能强大:提供了从基本的图像操作到复杂的图像分析和机器学习算法的广泛功能
0.4 选择条件
-
如果需要进行复杂的图像处理或计算机视觉任务,则OpenCV是最佳选择
-
如果需求主要是数据可视化,则Matplotlib可能更适合
-
如果需要和深度学习框架配合使用,则PIL可能更适合
1 Matplotlib的图像操作实践
# 引入matplotlib的pyplot函数,为图像处理做准备
from matplotlib import pyplot as plt
# 如果是用pycharm等后端工具绘图,需要指定图形用户界面工具包
# import matplotlib
# matplotlib.use('TkAgg') # 设置绘图后端为 TkAgg# 1、图像的读取
# 1.1 读取图像
# 随便找一张图片(比如我在网上找了一张哆啦A梦的jpg图,命名为dlam.jpg,然后即可使用imread方法进行读取)
img = plt.imread(fname="dlam.jpg")
# 1.2 查看形状
# 输出为[H, W, C]格式
print(img.shape)
# 1.3 图像显示
plt.imshow(X=img)# 2、图像的色彩处理
# 处理1:读取图像的 R 层,C=3,代表有3个颜色通道,也就是RGB三层颜色(要读取R层,就取值C=0;G层则为C=1;B层则为C=2)
img_r = img[:, :, 0]
plt.imshow(X=img_r)
# 处理2:改变图像颜色(获取R层后,用cmap="gray"将其置为灰色)
plt.imshow(X=img_r, cmap="gray")# 3、图像的截取显示
# 处理1:截取图像的上半部分
H, W, C = img.shape
plt.imshow(X=img[:H//2, :, :])
# 处理2:截取图像的右半部分
plt.imshow(X=img[:, W//2:, :])
# 处理3:截取图像的左下角
plt.imshow(X=img[H//2:, :W//2, :])
# 处理4:亚采样(将图片H和W都每隔4行取第一行数据,且颜色不变),相当于减少图片大小并压缩画质
plt.imshow(X=img[::4, ::4, :])# 4、图像的保存
# 压缩图片后保存为名为“dlam_small.jpg"”的图片文件
plt.imsave(fname="dlam_small.jpg", arr=img[::2, ::2, :])
2 PIL的图像操作实践
# 引入PIL的Image模块,为图像处理做准备
from PIL import Image
# 引入numpy,提供一些科学计算的方法
import numpy as np# 1、图像的读取
# 1.1 读取图像
# 随便找一张图片(比如我在网上找了一张哆啦A梦的jpg图,命名为dlam.jpg,然后即可使用imread方法进行读取)
img = Image.open(fp="dlam.jpg")
# 1.2 查看形状
# 输出为[W, H]格式
print(img.size)
# 1.3 显示图像
img.show()# 2、图像的色彩处理
# 处理1:查看图片中某个像素点的RGB
print(img.getpixel(xy=(100, 100)))
# 处理2:改变图片颜色为灰色
# 用convert方法中的mode参数来控制,mode参数常用值如下:
# "1":二值图像,只有黑白两种颜色,每个像素用1位表示,0表示黑,1表示白
# "L":灰度模式,每个像素用8位表示,0表示黑,255表示白,其他数字表示不同的灰度
# "P":调色板模式,使用调色板来为图像中的颜色进行索引,适用于8位颜色深度的图像
# "RGB":真彩色模式,使用三个8位通道分别表示红色、绿色和蓝色,可以表示全彩色图像
# "RGBA":在RGB基础上增加一个8位的Alpha通道,用于表示像素的透明度
# "CMYK":用于打印的四色模式,使用四个通道分别表示青色、品红色、黄色和黑色
# "YCbCr":彩色视频格式,常用于数字视频和图像压缩
# "I":32位整型模式,每个通道用32位整数表示,适用于需要宽动态范围的应用
# "F":32位浮点模式,每个通道用32位浮点数表示,用于高精度图像处理
img.convert(mode="L")# 3、图像的截取显示
# 处理1:强行改变图像的size
img.resize(size=(100, 100))
# 处理2:旋转图像(angle参数指定旋转多少度,负数为顺时针,正数为逆时针)
img.rotate(angle=-20)# 4、转换为matplotlib格式
print(np.array(img).shape)# 5、保存图像
# 转换为灰色后保存
img1 = img.convert(mode="L")
img1.save(fp="dlam_gray.jpg")
3 OpenCV的操作实践
3.1 图像基本操作
# 引入OpenCV
import cv2# 1、读取图像
img = cv2.imread("dlam.jpg")
# 2、显示图像
cv2.imshow(winname="dlam", mat=img)
# 3、按键等待
# delay参数指定图片显示的毫秒时间,如果填0,则表示一直显示、按任意键退出显示
cv2.waitKey(delay=0)
# 4、释放资源
# 在退出图片显示之后,释放windows资源
cv2.destroyAllWindows()
3.2 图像视频操作
视频读取本质上还是图像读取,只不过是一帧一帧读取图像,连贯起来就是视频效果
# 引入OpenCV库
import cv2# VideoCapture函数用于建立视频捕获的连接
# 参数值0表示计算机上的第一个摄像头(通常内置摄像头),如果需要使用外部摄像头,可以尝试更改为1或其他数字
cap = cv2.VideoCapture(0)
# 如果想显示本地视频,则可以将视频路径作为参数值进行传入
# video_path = 'test.mp4'
# cap = cv2.VideoCapture(video_path)# 循环读取每一帧的图像,实现视频效果
while True:# read方法用于尝试读取一帧图像,并返回两个值:# (1)status(布尔值,表示是否成功读取帧)# (2)frame(读取到的帧图像)status, frame = cap.read()# 如果读取失败,则跳出循环if not status:print("error")break# 显示图像(循环显示图像,就相当于视频效果)cv2.imshow(winname="demo", mat=frame)# waitKey函数表示暂停当前线程指定的毫秒数# 1000 // 24,大约等于41毫秒,这意味着大约每24帧暂停一次,模拟24帧每秒的视频播放速率# 该函数返回一个整数,表示按下的键的ASCII码【如果按下的是ESC键(ASCII码为27),则跳出循环】if cv2.waitKey(delay=1000 // 24) == 27:break# 释放摄像头相关资源
cap.release()
# 释放windows窗口资源
cv2.destroyAllWindows()
3.3 图像进阶操作
3.3.1 滤波处理
对于滤波处理而言,卷积核是关键!
(0)原始图像
# 引入OpenCV和matplotlib,提供图像处理的方法
# cv2.filter2D函数会将卷积核的边缘与图像边缘对齐,并对图像进行填充(padding),以确保卷积操作可以覆盖图像的所有像素
# 默认情况下,OpenCV使用边界填充(border padding),填充的值通常是零,因此,输出图像的尺寸不会因为卷积核的大小而减小。
import cv2
from matplotlib import pyplot as plt
# 引入numpy,提供科学计算方法
import numpy as np# 读取图像
img = plt.imread(fname="dlam.jpg")
# 获取第r层(RGB,第0层)
img_r = img[:, :, 0]
# 置灰显示
plt.imshow(X=img_r, cmap="gray")
读取第r层,并置灰显示
# 获取第r层(RGB,第0层)
img_r = img[:, :, 0]
# 置灰显示
plt.imshow(X=img_r, cmap="gray")
(1)平均滤波
"""平均滤波:设置 N * N 的矩阵,来压缩图像
"""
# 矩阵大小
N = 11
# 设置滤波器
kernel = np.ones(shape=(N, N)) / N ** 2
# ddepth=-1,表示输出图像的深度将与输入图像相同
# 在图像处理中,图像的深度通常指的是每个像素值的数据类型或位深(bit depth),它决定了图像可以表示的颜色数量。图像深度通常以位(bits)表示,常见的有8位、16位、32位等
# 用dtype方法即可查出图像的深度值,ddepth的常用值如下:
# ddepth=0:输出图像将是8位无符号整数(0-255)
# ddepth=1:输出图像将是8位有符号整数
# ddepth=2:输出图像将是16位无符号整数
# ddepth=3:输出图像将是16位有符号整数
# ddepth=4:输出图像将是32位浮点数
# ddepth=5:输出图像将是64位浮点数
img1 = cv2.filter2D(src=img_r, ddepth=-1, kernel=kernel)
# 将滤波处理后的图像置灰显示
plt.imshow(X=img1, cmap="gray")
(2)上下相减
"""上下相减
"""
# 设置卷积核为3*3的矩阵,且最上边都是1、中间是0、下班是-1、
# 这样做可以将图像中上下亮度相差较大的内容凸显出来
kernel = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]])
img2 = cv2.filter2D(src=img_r, ddepth=-1, kernel=kernel)
plt.imshow(X=img2, cmap="gray")
img2.shape
(3)左右相减
"""左右相减
"""
# 设置卷积核为3*3的矩阵,且最左边都是1、中间是0、右边是-1
# 这样做可以将图像中上下亮度相差较大的内容凸显出来
kernel = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]]).T
img3 = cv2.filter2D(src=img_r, ddepth=-1, kernel=kernel)
plt.imshow(X=img3, cmap="gray")
(4)上下左右相减
"""上下左右相减
"""
# 设置卷积核为3*3的矩阵,且上下左右都是1,只有正中间是-8
# 这样做可以将图像中上下左右亮度相差较大的内容凸显出来
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]).T
img4 = cv2.filter2D(src=img_r, ddepth=-1, kernel=kernel)
plt.imshow(X=img4, cmap="gray")
3.3.2 滤波(简单的卷积原理)
卷积是一种数学运算,它在多个领域和不同的上下文中有广泛应用,包括信号处理、图像处理、机器学习,特别是在深度学习的卷积神经网络(CNNs)中
一般来讲,卷积是两个函数(一个信号和一个滤波器)之间的运算,它描述了当一个函数被另一个函数修改时会发生什么,在图像处理中,卷积可以用来描述图像(信号)与一个滤波器(如边缘检测器或模糊器)的相互作用
卷积操作的基本步骤:
-
滤波器(Kernel)覆盖:将滤波器放置在图像的一个局部区域上
-
元素相乘:将滤波器中的每个元素与图像中对应的元素相乘
-
求和:将上一步得到的乘积求和,得到一个新的数值
-
生成新像素:将求和的结果作为新图像在对应位置的像素值
-
重复:将滤波器滑动到图像的下一个局部区域,并重复上述步骤,直到覆盖整个图像
卷积的基本实现:
# 引入numpy,提供一些科学计算的方法
import numpy as np# 生成一个随机数组成图像
img = np.random.randn(64, 64)
# 定义一个3*3的卷积核
kernel = np.random.randn(3, 3)
# 获取原始图像和卷积核的初始尺寸
H, W = img.shape
k, k = kernel.shape
# 定义卷积后的初始结果
result = np.zeros(shape=(H-k+1, W-k+1))"""卷积的基本计算备注:1、一般还需要对图像进行填充(padding),并加上一个偏置,这里为简单演示上面动图的原理,暂且不做特殊操作2、RGB三层通道都需要进行计算"""
# 遍历 H 方向
for h_idx in range(H-k+1):# 遍历 W 方向for w_idx in range(W-k+1):# 图像块img_block = img[h_idx: h_idx + k, w_idx: w_idx + k]# 填充结果(相乘再相加)result[h_idx, w_idx] = (img_block * kernel).sum()# 打印卷积后的图像内容
print(result)
3.3.3 RGB三层图像通道的二维卷积
类比上面的单层图像通道的卷积,三层图像通道(甚至更多层)也是用卷积核相乘再相加
下图是经典RGB三层图像通道的卷积原理介绍,其特点如下:
(1)需要补0
(2)输出为几个通道,就有几组卷积核,以及几组偏置
(3)原始图像有几层通道,每组就有几个卷积核
(4)每组只有一个偏置
(5)stride=2表示卷积核不挨着处理,而是跳两个处理
上图的代码实现:
# 引入pytorch和nn神经网络
import torch
from torch import nn# 二维卷积:在 H 和 W 两个方向做卷积
# in_channels=3:输入rgb三层(由原始图像决定)
# out_channels=2:输出两层(由自己决定)
# kernel_size=3:卷积核都为3*3,也可以写成kernel_size=(3, 3)
# stride=2:隔两个跳着做卷积(不是正常那样一个一个挨着做卷积),也可以写成stride=(2, 2)
# padding=1:在输入数据(图像)的每个边界上添加一圈像素值(通常是0),这圈像素的宽度为1
# 具体来说,就是对于每个维度(高度和宽度),都会在上下左右各添加1个像素的填充
conv2d = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=3, stride=2, padding=1)# 模拟图像 [N, C, H, W]
X = torch.randn(1, 3, 5, 5)
# 打印形状
conv2d(X).shape# 有两层输出,所卷积核和偏置各有两组
# 有rgb三层通道的图像作为输入,所以每组卷积核都有三个卷积核,而每一个卷积核都是3*3的,所以权重的维度为2*3*3*3,偏置的维度为2
print(conv2d.weight.shape)
print(conv2d.weight)
print(conv2d.bias.shape)
print(conv2d.bias)
4 卷积与池化
4.1 批量规范化层
在PyTorch中,nn.BatchNorm2d
类实现了二维批量规范化(Batch Normalization),这是一种在深度学习中常用的技术,用于提高训练速度、稳定性和性能
批量规范化通过规范化(归一化)层的输入来工作,即对每个特征的mini-batch进行均值和方差的计算,然后利用这些统计量来规范化数据
在批量规范化中,有两个可学习的参数,通常被称为γ(gamma)和β(beta):
-
γ(gamma):是一个尺度参数,用于缩放规范化后的值。它允许模型重新获得在规范化过程中可能丢失的信息
-
β(beta):是一个偏移参数,用于在缩放后对值进行偏移。它提供了额外的灵活性,允许模型在规范化后调整激活的均值
在创建BatchNorm2d
层时,γ和β参数默认初始化为1和0,这意味着规范化后的值最初不会受到尺度或偏移的影响,然而,这些参数将在训练过程中通过反向传播进行调整,以最小化损失函数
查看γ和β
# 引入pytorch和nn神经网络
import torch
import torch.nn as nn# num_features参数指定输入数据的通道数
bn = nn.BatchNorm2d(num_features=2)
print(bn.weight) # 打印γ参数
print(bn.bias) # 打印β参数
bn.weight
将输出γ参数的当前值,它会是一个形状为[2]
的张量,因为num_features=2
bn.bias
将输出β参数的当前值,同理,它也会是一个形状为[2]
的张量
在实际使用中,通常不需要手动打印或设置这些参数,因为它们会自动通过训练过程中的反向传播进行学习和更新,只有在特定的调试或高级应用中,才可能需要直接访问或修改这些参数
4.2 最大池化
最大池化就是在卷积核盖住的原图内容中找到最大值,视为池化后的内容,而不是像卷积那样相乘再相加
# 引入pytorch和nn神经网络
import torch
import torch.nn as nn# 最大池化类似于亚采样,通过丢掉不重要的特征、保留最重要的特征,从而使图像变小
# 卷积核为2*2,隔两个跳着做卷积,不做边缘填充
mp = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)X = torch.randint(low=0, high=101, size=(1, 1, 4, 4),dtype=torch.float32)# [N, C, H, W]
X.shapemp(X)
4.3 平均池化
平均池化就是在卷积核盖住的原图内容中求均值,视为池化后的内容,而不是像卷积那样相乘再相加
# 引入pytorch和nn神经网络
import torch
import torch.nn as nn# 卷积核为2*2,隔两个跳着做卷积,不做边缘填充
avg_pool = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)X = torch.randint(low=0, high=101, size=(1, 1, 4, 4),dtype=torch.float32)# [N, C, H, W]
X.shapeavg_pool(X)
均值的计算方法:
# 值的计算方法(比如第一个值)
X[:, :, :2, :2].mean()
4.4 卷积与池化的特点、区别及应用
在深度学习和计算机视觉中,卷积(Convolution)和池化(Pooling)是两种非常重要的操作,它们在构建卷积神经网络(Convolutional Neural Networks, CNNs)时扮演着核心角色,他们的区别如下:
4.4.1 卷积(Convolution)
卷积操作是CNN中的核心,它通过卷积核(或滤波器)在输入数据上滑动来提取特征。卷积层可以有以下几个关键特性:
-
参数学习:卷积核的参数在训练过程中学习得到更新,能够自动提取输入数据的重要特征
-
多卷积核:通常一个卷积层会有多个卷积核,每个卷积核负责提取不同的特征
-
维度保持:卷积操作后,输出特征图的维度通常与输入特征图的维度相关,可以通过步长(Stride)和填充(Padding)来控制(Padding除了可以填写数字之外,还可以填写为'same',same会自动补全合适个数的0,使得卷积层的输出特征图在空间维度上与输入数据保持一致)
-
非线性:卷积操作是高度非线性的,因为它涉及加权求和激活函数
4.4.2 池化(Pooling)
池化操作用于降低特征图的维度,减少计算量,同时保留重要特征,池化层通常放在卷积层之后,有以下几个关键特性:
-
固定操作:池化操作通常是固定不变的,意味着它不涉及学习参数
-
减少维度:池化层输出特征图的尺寸小于输入特征图的尺寸,这有助于减少计算量和过拟合
-
下采样:池化通过下采样(如最大池化或平均池化)来提取局部区域的统计信息
-
线性:池化操作是线性的,因为它只涉及加权和求和平均操作
4.4.3 区别
-
操作类型:卷积是参数学习的操作,池化是固定操作
-
维度变化:卷积保持或增加特征图的维度,池化减少特征图的维度
-
特征提取:卷积用于提取特征,池化用于降维和提取统计特征
-
计算量:卷积操作的计算量通常大于池化操作
4.4.4 应用
- 卷积:用于特征提取,如边缘检测、纹理分析等
- 池化:用于降维,如图像压缩、特征图尺寸调整等
4.5 卷积、池化后的图像大小
卷积/池化之后,图像大小的计算公式如下
5 图像相关神经网络的设计原则
Step1 卷积:Channel 逐渐变大,Size 几乎不变(图像“越来越厚”)
Step2 池化:Channel 保持不变,Size 逐渐减半(图像“越来越小”)
(一般指的是最大池化)
Step3 展平:在全连接层之前,将多维的特征图展平成一维向量
Step4 输出:
- 分类任务:有多少个类别,就输出几个神经元
- 回归任务:有几个回归值,就输出几个神经元
6 传统图像和人工智能图像在卷积处理上的区别
6.1 传统图像处理
- 传统图像处理依赖于人为设计的卷积核,这要求设计者具有信号处理和复杂数学的背景知识
- 卷积核的设计是图像处理中的重点和难点
- 传统方法的难度大,且效果可能不如人意
- 传统图像处理方法需要设计者自己考虑如何解决问题,类似于一个农民工,需要手动完成从第一步到最后一步的所有工作
6.2 人工智能算法
- 在人工智能中,卷积核被视为参数,通过训练数据来优化
- 人工智能算法让算法自己定义需要提取的特征,通过反向传播算法调整卷积核,从而获得所需的特征
- 人工智能算法体现了甩锅理念,即算法的设计和优化过程由数据驱动,而不是由人手动设计
- 人工智能算法的结果由数据决定,算法的设计者(或训练者)不需要详细定义规则,只需关注最终结果
FAQ
Q: jupyter卡死,报错OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.
A: 导包处理(引入以下代码即可)
import os
os.environ['KMP_DUPLICATE_LIB_OK']='TRUE'