卷积在图像处理中的作用:
- 图片是一个二维的矩阵
- 通过一个3×3的矩阵与原图进行相乘再相加的计算,得到一个新的图片
- 通过设计不同的3×3矩阵内容,可以得到不同的新图片
卷积神经网络
- 将图片(本质是矩阵)作为输入
- 将卷积核作为待调整的参数
- 让卷积运算作为机器学习的一部分
- 让机器学习寻找特征,寻找过程中:
- 将一个卷积的操作,作为另外一个卷积的输入
- 对于一个特征矩阵,可以利用不同的卷积核,生成不同的特征矩阵
- 将这些输入输出全部连接到一起
卷积网络组件
-
BatchNorm层
-
ReLU层
-
线性层
-
Dropout层
张量的维度
数据科学
-
按维度定义和处理数据
图像展开
- 常规图像:[N, C, H, W]
- N个样本图像
- C:通道数
- H:高度
- W:宽度
- 图像展平:[N, C * H * W]
- N:图像的个数
- C * H * W:将其展成一行,可理解为将图像像素全部排成一行
- 改变shape:
- 不会改变像素值
- 不会改变数据本身的信息
- 为了对口型,方便科学计算,方便矩阵相乘
卷积操作
import numpy as np
# 图像
img = np.random.randn(64,64)
# 卷积核
kernel = np.random.randn(3,3)
# 初始尺寸
H, W = img.shape
k, k = kernel.shape
# 结果
result = np.zeros(shape=(H-k+1,W-k+1))
# 遍历 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()
import torch
from torch import nn
# 二维卷积:H和W两个方向做卷积
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
conv2d.weight.shape
conv2d.bias
# 批规范化层
bn = nn.BatchNorm2d(num_features=2)
bn.bias
torch.relu
import torch
from torch import nn
# 自定义一个神经网络类
class Model(nn.Module):
def __init__(self, in_channels=1, n_classes=10):
# 调用父类的构造函数进行初始化
super(Model, self).__init__()
# 1. 特征抽取部分
# 使用 nn.Sequential 来序列化卷积层和池化层
self.feature_extractor = nn.Sequential(
# 第一个卷积层,输入通道数为 in_channels,输出通道数为 6,卷积核大小为 5x5
nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5, stride=1, padding=0),
# 第一个最大池化层,池化核大小为 2x2,步长为 2
nn.MaxPool2d(kernel_size=2, stride=2, padding=0),
# 第二个卷积层,输入通道数为 6(上一个卷积层的输出通道数),输出通道数为 16,卷积核大小为 5x5
nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0),
# 第二个最大池化层,池化核大小为 2x2,步长为 2
nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
)
# 2. 分类输出部分
# 使用 nn.Sequential 来序列化展平层和全连接层
self.classifier = nn.Sequential(
# 展平层,从第二个维度开始展平到最后一个维度
nn.Flatten(start_dim=1, end_dim=-1),
# 第一个全连接层,输入特征数为 400(这个值取决于前面的卷积和池化层以及输入图像的大小),输出特征数为 120
# 注意:这里的 400 是一个假设的值,实际使用时需要根据输入图像大小和前面的层来计算
nn.Linear(in_features=400, out_features=120),
# 第二个全连接层,输入特征数为 120,输出特征数为 84
nn.Linear(in_features=120, out_features=84),
# 第三个全连接层(输出层),输入特征数为 84,输出特征数为 n_classes(分类的类别数)
nn.Linear(in_features=84, out_features=n_classes)
)
def forward(self, x):
# 前向传播函数
# 1. 先通过特征抽取部分
x = self.feature_extractor(x)
# 2. 再通过分类输出部分
x = self.classifier(x)
return x
# 实例化模型,设置输入通道数为 1,分类类别数为 10(这里可以根据实际任务调整)
model = Model(in_channels=1)
# 创建一个随机输入张量,模拟一个批次的图像数据
# 形状为 (batch_size, channels, height, width),这里设置为 (2, 1, 32, 32)
X = torch.randn(2, 1, 32, 32)
# 通过模型进行前向传播,得到预测结果
y_pred = model(X)
# 打印预测结果的形状,形状为 (batch_size, n_classes) 的张量
print(y_pred.shape) # 输出结果是 torch.Size([2, 10])
# 打印模型结构信息
print(model)
手势识别
1. 原始数据读取
- 并不是把所有图像全部读进内存
- 而是把所有图像的
路径
和类别
归纳和梳理出来 - img_path
- img_label
# 导入os模块,用于处理文件和目录路径
import os
# 设置训练数据集的根目录路径
train_root = os.path.join("gesture", "train")
# 初始化训练数据集的路径列表和标签列表
train_paths = []
train_labels = []
# 遍历训练数据集的根目录下的每个标签文件夹
for label in os.listdir(train_root):
# 获取当前标签文件夹的完整路径
label_root = os.path.join(train_root, label)
# 遍历当前标签文件夹下的每个文件
for file in os.listdir(label_root):
# 获取当前文件的完整路径
file_path = os.path.join(label_root, file)
# 将文件路径添加到训练路径列表中
train_paths.append(file_path)
# 将当前文件的标签添加到训练标签列表中
train_labels.append(label)
# 设置测试数据集的根目录路径
test_root = os.path.join("gesture", "test")
# 初始化测试数据集的路径列表和标签列表
test_paths = []
test_labels = []
# 遍历测试数据集的根目录下的每个标签文件夹
for label in os.listdir(test_root):
# 获取当前标签文件夹的完整路径
label_root = os.path.join(test_root, label)
# 遍历当前标签文件夹下的每个文件
for file in os.listdir(label_root):
# 获取当前文件的完整路径
file_path = os.path.join(label_root, file)
# 将文件路径添加到测试路径列表中
test_paths.append(file_path)
# 将当前文件的标签添加到测试标签列表中
test_labels.append(label)
# 构建一个包含所有可能标签的列表(这里假设了标签是"zero"到"nine")
labels = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
# 构建一个从标签到索引的字典
label2idx = {label: idx for idx, label in enumerate(labels)}
# 构建一个从索引到标签的字典
idx2label = {idx: label for label, idx in label2idx.items()}
2. 批量化打包
- 继承 Dataset,自定义一个数据集
- 实例化 DataLoader
# 引入必要的工具类
from torch.utils.data import Dataset # 从torch.utils.data模块导入Dataset类,用于创建自定义数据集
from torch.utils.data import DataLoader # 从torch.utils.data模块导入DataLoader类,用于包装数据集并提供数据加载功能
from PIL import Image # 从PIL库导入Image类,用于图像处理
from torchvision import transforms # 从torchvision库导入transforms模块,用于图像变换
import torch # 导入torch库,用于深度学习
class GestureDataset(Dataset):
"""
自定义手势识别数据集类,继承自Dataset类
"""
def __init__(self, X, y):
"""
初始化方法
:param X: 图像路径列表
:param y: 图像标签列表
"""
self.X = X # 存储图像路径
self.y = y # 存储图像标签
def __getitem__(self, idx):
"""
根据索引获取单个样本及其标签
:param idx: 索引
:return: 图像和标签
"""
# 获取图像路径
img_path = self.X[idx]
# 读取图像
img = Image.open(fp=img_path) # 使用PIL的Image类打开图像文件
# 统一大小到32x32
img = img.resize((32, 32))
# 将图像转换为张量,并归一化到[0, 1]范围
img = transforms.ToTensor()(img)
# 将图像张量归一化到[-1, 1]范围
img = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])(img)
# 读取标签
img_label = self.y[idx]
# 标签转索引(这里假设label2idx是一个全局变量或需要在外部定义)
img_idx = label2idx.get(img_label) # 使用字典获取标签对应的索引
# 将索引转换为张量,并指定数据类型为long
label = torch.tensor(data=img_idx, dtype=torch.long)
return img, label # 返回图像张量和标签张量
def __len__(self):
"""
返回数据集中的样本数量
"""
return len(self.X) # 返回图像路径列表的长度
# 创建训练集加载器
train_dataset = GestureDataset(X=train_paths, y=train_labels) # 使用训练数据创建GestureDataset实例
train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=16) # 创建DataLoader实例,设置随机打乱和批量大小
# 创建测试集加载器
test_dataset = GestureDataset(X=test_paths, y=test_labels) # 使用测试数据创建GestureDataset实例
test_dataloader = DataLoader(dataset=test_dataset, shuffle=False, batch_size=32) # 创建DataLoader实例,不设置随机打乱和设置批量大小
# 测试DataLoader
for X, y in test_dataloader:
print(X.shape) # 打印图像张量的形状(批量大小, 通道数, 高度, 宽度)
print(y.shape) # 打印标签张量的形状(批量大小,)
break