前言
大家好,我是机长
本专栏将持续收集整理市场上深度学习的相关项目,旨在为准备从事深度学习工作或相关科研活动的伙伴,储备、提升更多的实际开发经验,每个项目实例都可作为实际开发项目写入简历,且都附带完整的代码与数据集。可通过百度云盘进行获取,实现开箱即用
正在跟新中~
项目背景
(基于CNN实现手势识别)
在5G时代,随着网络速度的大幅提升和低延迟特性的普及,手势识别技术迎来了前所未有的发展机遇,特别是在视频直播、智能家居和智能驾驶等领域。这些应用场景都极大地依赖于用户与设备之间的高效、直观交互,而手势识别正是实现这一目标的关键技术之一。
项目环境
- 平台:windows 10
- 语言环境:python 3.8
- 编辑器:PyCharm
- PyThorch版本:1.8
1.创建并跳转到虚拟环境
python -m venv myenv
myenv\Scripts\activate.bat
2. 虚拟环境pip命令安装其他工具包
pip install torch torchvision torchaudio
注:此处只示范安装pytorch,其他工具包安装类似,可通过运行代码查看所确实包提示进行安装
3.pycharm 运行环境配置
进入pytcharm =》点击file =》点击settings=》点击Project:...=》点击 Python Interpreter,进入如下界面
点击add =》点击Existing environment =》 点击 ... =》选择第一步1创建虚拟环境目录myenv\Scripts\下的python.exe文件点击ok完成环境配置
数据集介绍
训练数据样式
数据集是一个包含手势识别信息的综合数据集,具体特点如下:
参与者数量:数据集由14个不同的个体(或“受试者”)组成,每个人都在数据集中贡献了他们的手势数据。
手势种类:每个参与者执行了10种不同的手势,这些手势可能代表了特定的命令、符号或动作,具体取决于数据集的设计目的。
数据重复性:为了增加数据集的多样性和鲁棒性,每种手势都被每个参与者重复了10次。这意味着对于每个手势,数据集都包含了来自每个参与者的10个样本。
总数据量:综合以上信息,数据集总共包含了14(人)x 10(手势种类)x 10(重复次数)= 1400个手势样本。
数据来源:
Kinect数据:这些数据通过Microsoft Kinect传感器获取,可能包括深度图像、彩色图像、骨骼跟踪数据等。Kinect的校准参数也被提供,这对于确保数据的准确性和一致性至关重要。
Leap Motion数据:Leap Motion是一种小型的手部追踪设备,能够提供高精度的手部姿势和手指运动数据。Leap SDK(软件开发工具包)提供的所有相关参数都被包含在内,这些参数可能包括手掌位置、手指关节角度、指尖位置等。
训练数据获取:
私信博主获取
LeNet网络介绍
LeNet网络,由Yann LeCun及其团队在1990年代初期设计并优化,是卷积神经网络(CNN)领域的先驱之作。其最为人熟知的版本是LeNet-5,该网络在1998年被正式提出,主要用于手写数字识别,尤其是MINIST数据集上的表现尤为出色。
LeNet-5的结构相对简洁而高效,包括两个卷积层(C1、C3)、两个池化层(S2、S4)、两个全连接层(F6、OUTPUT)以及输入层和输出层。卷积层通过卷积核自动提取图像中的特征,池化层则通过下采样减少数据的空间尺寸,同时保留重要信息。全连接层则将提取的特征映射到最终的分类结果上。
LeNet网络的核心优势在于其自动提取特征的能力,这大大减少了传统图像识别方法中对手动设计特征的依赖。此外,其结构简单、计算量相对较小,使得在当时的硬件条件下也能实现较快的训练和推理速度。
然而,受限于当时的硬件条件和计算资源,LeNet网络的规模相对较小,难以处理更大规模或更复杂的图像识别任务。随着计算机硬件和深度学习技术的飞速发展,更加深层、更加复杂的卷积神经网络被设计出来,如AlexNet、VGG、ResNet等,它们在图像识别、分类、检测等领域取得了更加卓越的性能。
尽管如此,LeNet网络作为卷积神经网络的开山之作,其设计思想和基本结构仍然对后来的研究产生了深远的影响。它证明了卷积神经网络在图像识别领域的巨大潜力,并为后续的研究提供了宝贵的经验和启示。在今天,LeNet网络仍然被广泛应用于教学和科研领域,作为学习深度学习和卷积神经网络的基础模型之一。
定义CNN网络
- 卷积层:用于图像的高级特征
- 输出层:将卷积提取出的特征进行分类
class LeNet5(nn.Module):
def __init__(self,num_class=10):
super(LeNet5,self).__init__()
self.conv1 = nn.Conv2d(3, 8, 5)
self.pool1 = nn.AvgPool2d((2, 2))
self.conv2 = nn.Conv2d(8, 16, 5)
self.pool2 = nn.AvgPool2d((2, 2))
self.conv3 = nn.Conv2d(16, 32, 5)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(28800, 1024)
self.fc2 = nn.Linear(1024, num_class)
def forward(self, x):
# x: torch.Size([32, 3, 150, 150])
x = self.conv1(x) # torch.Size([32, 8, 146, 146])
x = self.relu(x)
x = self.pool1(x) # torch.Size([32, 8, 73, 73])
x = self.conv2(x) # torch.Size([32, 16, 69, 69])
x = self.relu(x)
x = self.pool2(x) # torch.Size([32, 16, 34, 34])
x = self.conv3(x) # torch.Size([32, 32, 30, 30])
x = self.relu(x)
x = x.flatten(start_dim=1) # torch.Size([32, 28800])
x = self.fc1(x) # torch.Size([32, 2024])
x = self.relu(x)
x = self.fc2(x) # torch.Size([32, 4])
return x
加载数据集
# 1.数据转换
data_transform = {
# 训练中的数据增强和归一化
'train': transforms.Compose([
transforms.RandomResizedCrop(150), # 随机裁剪
transforms.ToTensor(), # 均值方差归一化
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
}
# 2.形成训练集
train_dataset = datasets.ImageFolder(root=os.path.join(image_path),
transform=data_transform['train'])
# 3.形成迭代器
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size,
True)
print('using {} images for training.'.format(len(train_dataset)))
测试代码
# 加载索引与标签映射字典
with open('class_dict.pk', 'rb') as f:
class_dict = pickle.load(f)
# 数据变换
data_transform = transforms.Compose([
transforms.CenterCrop(150),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
# 图片路径
img_path = r'./data/000-one/gesture-one-2021-03-07_23-07-48-1_37388.jpg'
# 打开图像
img = Image.open(img_path)
print(np.array(img).shape)
# 对图像进行变换
img = data_transform(img)
plt.imshow(img.permute(1,2,0))
plt.show()
# 将图像升维,增加batch_size维度
img = torch.unsqueeze(img, dim=0)
# 获取预测结果
pred = class_dict[model(img).argmax(axis=1).item()]
print('【预测结果分类】:%s' % pred)
完整运行代码
import math
import pickle
import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from keras.preprocessing.sequence import pad_sequences
from sklearn.model_selection import train_test_split
from torch.utils.data import TensorDataset
from torchvision import transforms, datasets
from torch import optim
from torchnet import meter
from tqdm import tqdm
from PIL import Image
import matplotlib.pyplot as plt
# 模型输入参数,需要自己根据需要调整
num_class = 14 # 分类数
epochs = 20 # 迭代次数
batch_size = 64 # 每个批次样本大小
lr = 0.003 # 学习率
image_path = './data' # 图像数据路径
save_path = './best_model.pkl' # 模型保存路径
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # 设备
# 1.数据转换
data_transform = {
# 训练中的数据增强和归一化
'train': transforms.Compose([
transforms.RandomResizedCrop(150), # 随机裁剪
transforms.ToTensor(), # 均值方差归一化
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
}
# 2.形成训练集
train_dataset = datasets.ImageFolder(root=os.path.join(image_path),
transform=data_transform['train'])
# 3.形成迭代器
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size,
True)
print('using {} images for training.'.format(len(train_dataset)))
# 4.建立分类标签与索引的关系
cloth_list = train_dataset.class_to_idx
class_dict = {}
for key, val in cloth_list.items():
class_dict[val] = key
with open('class_dict.pk', 'wb') as f:
pickle.dump(class_dict, f)
class LeNet5(nn.Module):
def __init__(self,num_class=10):
super(LeNet5,self).__init__()
self.conv1 = nn.Conv2d(3, 8, 5)
self.pool1 = nn.AvgPool2d((2, 2))
self.conv2 = nn.Conv2d(8, 16, 5)
self.pool2 = nn.AvgPool2d((2, 2))
self.conv3 = nn.Conv2d(16, 32, 5)
self.relu = nn.ReLU()
self.fc1 = nn.Linear(28800, 1024)
self.fc2 = nn.Linear(1024, num_class)
def forward(self, x):
# x: torch.Size([32, 3, 150, 150])
x = self.conv1(x) # torch.Size([32, 8, 146, 146])
x = self.relu(x)
x = self.pool1(x) # torch.Size([32, 8, 73, 73])
x = self.conv2(x) # torch.Size([32, 16, 69, 69])
x = self.relu(x)
x = self.pool2(x) # torch.Size([32, 16, 34, 34])
x = self.conv3(x) # torch.Size([32, 32, 30, 30])
x = self.relu(x)
x = x.flatten(start_dim=1) # torch.Size([32, 28800])
x = self.fc1(x) # torch.Size([32, 2024])
x = self.relu(x)
x = self.fc2(x) # torch.Size([32, 4])
return x
# 6.模型训练
model = LeNet5(num_class)
model = model.to('cpu')
criterion = nn.CrossEntropyLoss() # 损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=lr) # 优化器
best_acc = 0 # 最优精确率
best_model = None # 最优模型参数
for epoch in range(epochs):
model.train()
running_loss = 0 # 损失
epoch_acc = 0 # 每个epoch的准确率
epoch_acc_count = 0 # 每个epoch训练的样本数
train_count = 0 # 用于计算总的样本数,方便求准确率
train_bar = tqdm(train_loader)
for data in train_bar:
images, labels = data
optimizer.zero_grad()
output = model(images.to(device))
loss = criterion(output, labels.to(device))
loss.backward()
optimizer.step()
running_loss += loss.item()
train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
epochs,
loss)
# 计算每个epoch正确的个数
epoch_acc_count += (output.argmax(axis=1) == labels.view(-1)).sum()
train_count += len(images)
# 每个epoch对应的准确率
epoch_acc = epoch_acc_count / train_count
# 打印信息
print("【EPOCH: 】%s" % str(epoch + 1))
print("训练损失为%s" % str(running_loss))
print("训练精度为%s" % (str(epoch_acc.item() * 100)[:5]) + '%')
if epoch_acc > best_acc:
best_acc = epoch_acc
best_model = model.state_dict()
# 在训练结束保存最优的模型参数
if epoch == epochs - 1:
# 保存模型
torch.save(best_model, save_path)
print('Finished Training')
# 加载索引与标签映射字典
with open('class_dict.pk', 'rb') as f:
class_dict = pickle.load(f)
# 数据变换
data_transform = transforms.Compose([
transforms.CenterCrop(150),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
# 图片路径
img_path = r'./data/000-one/gesture-one-2021-03-07_23-07-48-1_37388.jpg'
# 打开图像
img = Image.open(img_path)
print(np.array(img).shape)
# 对图像进行变换
img = data_transform(img)
plt.imshow(img.permute(1,2,0))
plt.show()
# 将图像升维,增加batch_size维度
img = torch.unsqueeze(img, dim=0)
# 获取预测结果
pred = class_dict[model(img).argmax(axis=1).item()]
print('【预测结果分类】:%s' % pred)