pytorch手势识别分类网络的学习记录


本文的运行环境在jupuyter下的pytorch使用。
本文参考了亚博智能的代码,该代码应该改编自jetbot的避免碰撞的收集和训练,测试代码。
在jetbot的github主页的notebook可以看到相应的代码。

文件的组织

在这里插入图片描述

收集数据

首先调用相机,此处使用的是继承traitlets类的相机调用,并使用虚拟控件,进行图片的显示

调用相机

import traitlets
import ipywidgets

import ipywidgets.widgets as widgets
from IPython.display import display
from camera import Camera
from image import bgr8_to_jpeg

camera = Camera.instance(width=224, height=224)

image = widgets.Image(format='jpeg', width=224, height=224)  # this width and height doesn't necessarily have to match the camera

camera_link = traitlets.dlink((camera, 'value'), (image, 'value'), transform=bgr8_to_jpeg)

display(image)

运行完上面的代码块后,就可以实时的看到摄像头拍摄到的画面。

接下来让我们创建一些目录存储数据。我们将会建立一个叫dataset的文件夹,里面有3个文件夹。

import os
CLASS = ['one', 'two', 'three']
one = 'dataset/one'
two = 'dataset/two'
three = 'dataset/three'



# we have this "try/except" statement because these next functions can throw an error if the directories exist already
try:
    os.makedirs(one)
    os.makedirs(two)
    os.makedirs(three)

    
except FileExistsError:
    print('Directories not created becasue they already exist')

如果刷新左边的Jupyter文件浏览器,现在应该可以看到这些目录出现了。
在这里插入图片描述


# 计算文件夹的文件数量。
# 对应的,接受的是插件传递的参数值
import os
import re
#re正则表达式无效,提取出是图片.jpg结尾的部分
# files=  [f for f in os.listdir('dataset/'+str(value)) if re.match(r'*.jpg',f)]
# 该函数会以列表的形式列出文件夹下的文件名。
files = [f for f in os.listdir('dataset/one/') if f.endswith('jpg')]
# for f in os.listdir('dataset/one/'):
#     print(f)/
len(files)

界面的搭建

# 下拉框,选项,描述
CLASS = ['one', 'two', 'three']
#创建一个下拉框控件,有class的几个类
dataset widget = ipywidgets.Dropdown(options=CLASS, description='class')
# 布局,宽和高度
button_layout = widgets.Layout(width='128px', height='64px')
#保存按钮
save_button = widgets.Button(description='Save', button_style='success', layout=button_layout)

# blocked_button = widgets.Button(description='add blocked', button_style='danger', layout=button_layout)
#保存计数,是一个int型的文本框,值使用前面的计数函数,来进行更新
save_count = widgets.IntText(layout=button_layout, value=len([f for f in os.listdir('dataset/'+str(dataset_widget.value))if f.endswith('jpg')]))
# blocked_count = widgets.IntText(layout=button_layout, value=len(os.listdir(blocked_dir)))
#使用水平布局的方式,传入一个列表。
display(widgets.HBox([save_button, save_count,dataset_widget]))

# display(widgets.HBox([blocked_count, blocked_button]))

界面如下
在这里插入图片描述
向按钮添加事件绑定函数。
对收集的图片进行编号。
使用uuid的编码
uuid的几种算法

# -*- coding:utf-8 -*-
import uuid
 
print(uuid.uuid1())
#bf1dfacf-67d8-11e8-9a23-408d5c985711
print(uuid.uuid3(uuid.NAMESPACE_DNS, 'yuanlin'))
#ddb366f5-d4bc-3a20-ac68-e13c0560058f
print(uuid.uuid4())
#144d622b-e83a-40ea-8ca1-66af8a86261c
print(uuid.uuid5(uuid.NAMESPACE_DNS, 'yuanlin'))
#4a47c18d-037a-5df6-9e12-20b643c334d3

如何去除中间的横杠

uuid.uuid1().hex

建立文件命名

绑定功能

from uuid import uuid1
# 传入的是分类的路径,加上生成的编号和后缀
def save_snapshot(directory):
    image_path = os.path.join(directory, str(uuid1().hex) + '.jpg')
#     写字节流,创建一个文件,写入图片的值
    with open(image_path, 'wb') as f:
        f.write(image.value)

def save_(x):
    global save_count
    save_snapshot('dataset/'+str(dataset_widget.value))
#     save_count.value = len(os.listdir('dataset/'+str(dataset_widget.value)))
    save_count.value =len([f for f in os.listdir('dataset/'+str(dataset_widget.value))if f.endswith('jpg')])
# attach the callbacks, we use a 'lambda' function to ignore the
# parameter that the on_click event would provide to our function
# because we don't need it.

# 和opencv的事件一样,会传递一个回调事件,可以通过这个事件的类型来判断触发的事件类型。
# save_button.on_click(lambda x: save_())
#传入的是函数名,即C中的函数指针,python中是函数对象
save_button.on_click(save_)
#blocked_button.on_click(lambda x: save_blocked())

现在上面的按钮和选项栏已经可以将图像保存到对应的目录中。您可以使用Jupyter左边目录文件浏览器来查看这些文件! 现在继续收集一些数据,选择不同的class后让着摄像头对着相应的场景,用save对图片进行保存

1.尝试不同的方向
2.尝试不同的照明 运行下面单元格代码后就会显示图像和按钮,你就可以开始采集数据了
在这里插入图片描述

#后续处理,解除绑定
camera_link.unlink() 
image.close()
#按照opencv中的函数,释放相机对象
camera.cap.release()
#或者
camera.stop()

Camera.py的链接

训练网络模型

训练神经网络模型

我们将训练我们的图像分类器来检测几个类,对于这个模型,事实上是一个通用的分类模型,我们可以训练一个碰撞物和安全的分类,也可以构建一个手势分类,做一些简单的迁移训练,就可以达到效果。最好在主机端进行训练,jetson nano 2G的可能会卡死。

导入包

import torch
import torch.optim as optim
import torch.nn.functional as F
import torchvision
import torchvision.datasets as datasets
import torchvision.models as models
import torchvision.transforms as transforms

ColorJitter图像增强,修改图像的属性:亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)

现在我们使用torchvision.datasets 库中的ImageFolder数据集类创建数据集实例。里面有个附加torchvision.transforms库用于转换数据,为训练模型做好准备。,如果打印出来多出来文件夹名,将多出来的文件夹删掉,重新运行下列代码,例如我们dataset只有“one”,“two”,"three"三个文件夹,打印出来应该为{‘one’: 0, ‘three’: 1, ‘two’: 2}

#图像变换的集成
dataset = datasets.ImageFolder(
    'dataset',
    transforms.Compose([
        transforms.ColorJitter(0.1, 0.1, 0.1, 0.1),
        transforms.Resize((224, 224)),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])
)
print(dataset.class_to_idx )

将数据集拆分为训练集和测试集

接下来,我们将数据集拆分为 训练集 和 测试集。测试集将用于验证我们训练完的模型准确性。
对于数据集划分可以参考李沐的交叉验证。

train_dataset, test_dataset = torch.utils.data.random_split(dataset, [len(dataset) - 80, 80])

创建数据加载器以批量加载数据

我们将创建两个DataLoader实例,它们为洗牌数据提供实用程序,生成批次图像,并与多个任务并行加载样本。
笔记端运行应选择0并行工作,因为无法指定并行工作数,会报错,0是指默认使用全部的。

# 创建训练的并行加载
train_loader = torch.utils.data.DataLoader(
    train_dataset,
    batch_size=16,
    shuffle=True,
    num_workers=1
)

test_loader = torch.utils.data.DataLoader(
    test_dataset,
    batch_size=16,
    shuffle=True,
    num_workers=1
)

定义神经网络

torchvision 库提供了一系列我们可以使用的预训练模型。

在一个称为迁移学习的过程中,我们可以重新利用预先训练的模型(在数百万图像上进行训练),以获得可能少的数据,准确完成尽量多的任务。

在预训练模型的原始训练中学到的重要特征可重复用于新任务。 我们将使用alexnet模型。

model = models.alexnet(pretrained=True)

在这里插入图片描述
https://pytorch.org/vision/stable/models.html
运行该命令会下载,如果网络不好,可以将预训练权重直接放在该路径下。

此处指定预训练后的结果,即导入在源数据集上训练过后的模型。
对于网络,可以输出它的一些结构信息来了解它的原理。

print(model)

在这里插入图片描述
修改模型的分类层,将输出从原来的1000变成我们要输出的个数

model.classifier[6] = torch.nn.Linear(model.classifier[6].in_features, 3)

将模型放进cuda中

device = torch.device('cuda')
model = model.to(device)

训练神经网络

#设置轮数
NUM_EPOCHS = 10
#最好的模型的路径
BEST_MODEL_PATH = 'gesture_model.pth'
best_accuracy = 0.0
#定义优化器,使用SGD,学习率为0.001,动量算法为0.9,即是累积值的十倍
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
#对于每轮的代码
for epoch in range(NUM_EPOCHS):
    #图片和标签在训练数据加载的迭代器中,放入cuda
    for images, labels in iter(train_loader):
        images = images.to(device)
        labels = labels.to(device)
        #将梯度清零
        optimizer.zero_grad()
        #输出图片放入模型的推理结果。
        outputs = model(images)
        #将输出和标签进行交叉熵损失函数,计算损失值,即优化的方向
        loss = F.cross_entropy(outputs, labels)
        #使用损失的方向传递优化参数
        loss.backward()
        #通过逐步的优化运行
        optimizer.step()
    #训练过程结束
    #进行测试集的验证,导入测试集
    test_error_count = 0.0
    for images, labels in iter(test_loader):
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        #计算分类错误的数量
        #argmax是输出的参数最大的位置的量,1这个参数应该是按维度,列来进行计算	output的结果是
        test_error_count += float(torch.sum(torch.abs(labels - outputs.argmax(1))))
    #精确度是1.0减去错误率
    test_accuracy = 1.0 - float(test_error_count) / float(len(test_dataset))
    
    print('%d: %f' % (epoch, test_accuracy))
    #要设置最后的出口,即最后的精确度。,如果达到了最优的准确度,就保存模型。
    if test_accuracy > best_accuracy:
        torch.save(model.state_dict(), BEST_MODEL_PATH)
        best_accuracy = test_accuracy

解释
iter会产生一个迭代器,对于迭代器,会根据batchsize来确定每次返回的数据,对于一个迭代器,一次运行会返回该次的所有值,是在创建迭代器的数据源的时候规定的。output类型是一个根据批次生成的,分类的概率值,如推理数为3,放入的批次为25,则output的形状为[25,3],在通过argmax的处理后,即传入维度,便成了25。且数组对应的是标签的index,也就是预测值。将不一致的预测值的绝对值进行加和,就得到了最后的错误率。

加载训练模型

执行以下代码,初始化PyTorch模型。

import torch
import torchvision

model = torchvision.models.alexnet(pretrained=False)
model.classifier[6] = torch.nn.Linear(model.classifier[6].in_features, 3)
model.load_state_dict(torch.load('gesture_model.pth'))

转移到cuda上

device = torch.device('cuda')
model = model.to(device)

预处理功能

现在我们加载了模型,但有一个小问题,就是我们的摄像头的图像格式要与训练模型时的图像格式完全相同。要做到这一点,我们需要做一些预处理。分如下几个步骤:

  1. 从BGR转换为RGB模式
  2. 从HWC布局转换为CHW布局
  3. 使用与训练期间相同的参数进行标准化(我们的摄像机提供[0,255]范围内的值,并在[0,1]范围内训练加载的图像,因此我们需要缩放255.0
  4. 将数据从CPU内存传输到GPU内存
  5. 批量添加维度
import cv2
import numpy as np
#图像的基础设置,均值和标准差,这个是原数据集图像的一些数据,应该是RGB的归一化后的均值
mean = 255.0 * np.array([0.485, 0.456, 0.406])
stdev = 255.0 * np.array([0.229, 0.224, 0.225])

#根据图像的基础属性初始化一个参数
normalize = torchvision.transforms.Normalize(mean, stdev)

#处理相机的值,声明全局变量device和标准值,这个是初始值。
def preprocess(camera_value):
    global device, normalize
    #对于相机值,将其转化为RGB模式
    x = camera_value
    x = cv2.cvtColor(x, cv2.COLOR_BGR2RGB)
    #修改其行列通道顺序,对于0,1,2,转变为2,1,0,从通道最后,转变为通道在最前
    x = x.transpose((2, 0, 1))
    #将值从numpy转变为torch的tensor
    x = torch.from_numpy(x).float()
    #标准化
    x = normalize(x)
    x = x.to(device)
    #添加一个维度
    x = x[None, ...]
    return x

通过jetcam来获得相机的数据

#from jetcam.usb_camera import USBCamera
from jetcam.csi_camera import CSICamera
from jetcam.utils import bgr8_to_jpeg
import traitlets
from IPython.display import display
import ipywidgets
import ipywidgets.widgets as widgets


#camera = USBCamera(width=WIDTH, height=HEIGHT, capture_fps=30)
camera = CSICamera(width=224, height=224, capture_fps=30)
#如果使用笔记本的相机,可以修改初始化的内容后,声明
#相机运行状态
camera.running = True

image = widgets.Image(format='jpeg', width=224, height=224)
display(widgets.HBox([image]))

接下来,我们创建一个函数,只要相机的值发生变化,就会调用该函数。 此功能将执行以下步骤

  1. 预处理相机图像
  2. 执行神经网络
import torch.nn.functional as F
import time
import sys

a=0
one_blocked=0.0
two_blocked=0.0
three_blocked=0.0
#自定义的更新函数
def update(change):
    global one_blocked, two_blocked, three_blocked,a
    x = change['new'] 
    #原相机的值已经被修改,将中间的宽度进行取反
    image.value = bgr8_to_jpeg(x[:, ::-1, :])
    #预处理
    x = preprocess(x)
    #根据模型输出预测值
    y = model(x)
    
    # we apply the `softmax` function to normalize the output vector so it sums to 1 (which makes it a probability distribution)
#    y = F.softmax(y, dim=1).detach().cpu().numpy().flatten()
    #按照维度将y的最终预测值取出
    y = F.softmax(y, dim=1)
    one_blocked = float(y.flatten()[0])
    two_blocked = float(y.flatten()[1])
    three_blocked = float(y.flatten()[2])
    #做比较,判断输出的值是什么
    if(one_blocked > two_blocked and one_blocked > three_blocked):
        a = 0
    elif(two_blocked > one_blocked and two_blocked > three_blocked):
        a = 1
    elif(three_blocked > one_blocked and three_blocked > one_blocked):
        a = 2

    
    
    '''index = y.argmax()
    if y[index]>0.70:
        prediction_widget.value = hand[index]
    else:
        prediction_widget.value = hand[0]'''


    time.sleep(0.001)

#update在此处调用,传入的监测值是相机的值。
update({'new': camera.value})  # we call the function once to intialize

我们已经创建了神经网络执行功能,但现在我们需要将它附加到相机进行处理。

我们用observe 函数完成这个处理。

camera.observe(update, names='value')  # this attaches the 'update' function to the 'value' traitlet of our camera
### import colors
from colorama import Fore, Back, Style
import random
display(widgets.HBox([image]))
def name_of_value(val):
    if val == 0:
        return "Rock    ";
    if val == 1:
        return "Paper   ";
    if val == 2:
        return "Scissor ";
    
# main process
game_count = won_count = 0
TIME_DELTA = 0.7
try:
    while True: # forever loop
        # wait for signal
        sys.stdout.write("\n\rAre you ready?") 
        time.sleep(2.0)
        #GPIO.wait_for_edge(BUTTON_PIN, GPIO.RISING)
        
        # reset light and rotation
        game_count = game_count+1
        sys.stdout.flush()
        sys.stdout.write("\rGame %2s: Rock-" % game_count)
        time.sleep( TIME_DELTA )    
        
        # Rock-
        sys.stdout.flush()        
        sys.stdout.write("\rGame %2s: Paper-" % game_count)
        time.sleep( TIME_DELTA )    
        
        # Paper-
        sys.stdout.flush()
        sys.stdout.write("\rGame %2s: Scissors-" % game_count)
        time.sleep( TIME_DELTA )
              
        # Scissors (GO!)
        sys.stdout.flush()
        sys.stdout.write("\rGame %2s: GO!" % game_count )
        
        rint = random.randint(0,2)
              
        # Wait a little and detect hand gesture
        time.sleep( TIME_DELTA )
        sint = a; 
        sys.stdout.write("\rGame %2s  " % game_count)
        sys.stdout.write("Opponent: %s You: %s " % (name_of_value(rint), name_of_value(sint)))  
        sys.stdout.write("\tR:%f, P:%f, S:%f\t" % (one_blocked, two_blocked, three_blocked) )
        
        # win or lose
        diff = 0
        diff = (rint - sint) % 3;
        if diff == 0:
            sys.stdout.write(Fore.YELLOW+"Draw")
            #tie_game();
        elif diff == 1:
            sys.stdout.write(Fore.RED+"You lose")
            #won_game();
        elif diff == 2:
            sys.stdout.write(Fore.GREEN + "You win!!")
            won_count = won_count + 1
            #lost_game();
        sys.stdout.write(Style.RESET_ALL)
        
except KeyboardInterrupt:
    sys.stdout.write(Style.RESET_ALL + "\n")
    # statistics at the end
    if game_count != 0:
        print("Toatal score: %s/%s\n" % (won_count, game_count) )
    sys.exit(0)
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值