BP神经网络实现手势识别

第一次自己做手势识别就是在大三那年的大学生电子设计竞赛,那也是我第一次开始思考“算法”。那一年的电赛有一道题目,使用FDC2214(TI的一款四通道电容传感器)设计一个识别装置,要求能够识别,“石头”、“剪刀”、“布”,三种手势以及,放置了几个手指。

拿到题目的我最先想到的就是在识别区域放置四组电极,当手放在电极上方区域的时候记录四组电极的电容值,然后按照阈值判断到底属于哪个手势。

18年电赛时手工制作的电极

但是随着一点点的把想法变成现实,问题渐渐的浮现了出来,首先就是确定阈值时候的难度超过了我的想象,因为有四个通道,需要同时满足条件才能判定为某个手势,这就导致你必须合理的安排每一个手势的每一个通道的阈值,而起比赛要求能够学习不同人的手势,这让这个方案实施起来变得极其困难。在一个就是手放在电极上方测出来的电容很容易受到环境的影响,而阈值判别的方式容错性又极低。在尝试了一番后就放弃了这个方案,转而开始想其他的办法。

这个问题的本质其实是怎么判断一个数据到底是属于哪一个集合,也就是说着其实是一个典型的分类问题,那么就从常用的分类算法入手,首先想到的就是神经网络,但当时也就是想到,因为主控使用的是STM32,而且最主要的是我那时候只是听说过神经网络,根本就不会,所以神经网络也就是一闪而过的念头。在对比了好多分类算法后,最终选择了一个最容易实现的——KNN(k-近邻)算法。虽然KNN相比其它的分类如果样本集比较复杂,可能会导致很大的计算开销,因此一般不会应用到实时性很强的场合,但是在这个场景中只有四个通道,不多的几种手势,就算是在STM32上也会有很好的速度。

最终的比赛作品

虽然当时是哟KNN取得了不错的成绩,也结局了这个问题,但是那个一闪而过的想法却从那之后总会时不时的蹦出我的脑海,这也促使我尝试搞懂神经网络并用神经网络实现当初的功能。

白云苍狗,我都已经毕业三年了,当时的想法我也终于实现了出来,虽然晚了点,但好歹没有缺席。

在明白了神经网络的工作原理后我就迫不及待的用C语言在单片机上实现了网络的前向推理,但是当实现反向传播的时候又难到了我,于是我就使用Python先写了一个简单的前向和反向的代码验证。

# -*- coding: UTF-8 -*-

'''
反向传播神经网络
'''
from os import times
import numpy
from numpy import random
from numpy.core.fromnumeric import shape
import scipy.special
import time 
import matplotlib as mpl
import matplotlib.pyplot as plt


class neuralNetwork:
    
    #初始化神经网络,输入层的节点数,隐藏层的节点数,输出层的节点数,学习率
    def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate ):
        #初始化网络参数
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        self.lr = learningrate
        #初始化网络权重
        self.wih = numpy.random.normal( 0.0, pow(self.hnodes, -0.5),(self.hnodes, self.inodes))
        self.bh = numpy.random.normal( 0.0, pow(self.hnodes, -0.5),(self.hnodes,1))
        self.who = numpy.random.normal( 0.0, pow(self.onodes, -0.5),(self.onodes, self.hnodes))
        self.bo = numpy.random.normal( 0.0, pow(self.hnodes, -0.5),(self.onodes,1))
        #激活函数
        self.activation_function = lambda x: scipy.special.expit(x)
        pass    
    #神经网络训练
    def train(self, inputs_list, targets_list):
        #将输入的列表转换成矩阵
        inputs = numpy.array(inputs_list, ndmin=2).T
        targets = numpy.array(targets_list, ndmin=2).T
        #计算中间层的输入
        hidden_inputs = numpy.dot(self.wih, inputs)+self.bh
        #中间层应用激活函数
        hidden_outputs = self.activation_function(hidden_inputs)
        #计算输出层的输入
        final_inputs = numpy.dot(self.who, hidden_outputs)+self.bo
        #输出层应用激活函数
        final_outputs = self.activation_function(final_inputs)
        #计算误差矩阵
        output_errors = targets - final_outputs
        hidden_errors = numpy.dot(self.who.T, output_errors)
        #更新权重误差
        self.who += self.lr * numpy.dot((output_errors *final_outputs * (1.0 - final_outputs)),numpy.transpose(hidden_outputs))
        self.bo += self.lr * output_errors
        self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
        self.bh += self.lr * hidden_errors
        #print("output_error:\n",output_errors,"\nbo:\n",self.bo,"\nhidden_errors:\n",hidden_errors,"\nbn:\n",self.bh)

        Error = 0
        for num in output_errors:
            Error = Error + abs(num)
        return Error
        pass
    #推理
    def query(self, inputs_list):
        inputs = numpy.array(inputs_list, ndmin=2).T
        hidden_inputs = numpy.dot(self.wih, inputs)+self.bh
        hidden_outputs = self.activation_function(hidden_inputs)
        final_inputs = numpy.dot(self.who, hidden_outputs)+self.bo
        final_outputs = self.activation_function(final_inputs)
        return final_outputs
        pass


input_nodes = 4
hidden_nodes = 200
output_nodes = 3
learning_rate = 0.7

epoch = 10000

n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)

in_list = [[0.000,0.030,0.750,0.500],[0.200,0.300,0.800,0.500],[0.200,0.600,0.900,0.500],[0.065,0.275,0.960,1.2],[0.460,0.500,1.100,1.200],[0.700,1.00,1.400,1.200]]
out_list = [[1,0,0],[0,1,0],[0,0,1],[1,0,0],[0,1,0],[0,0,1]]

i = 0
s_time = time.clock()
error_list = []

while i<epoch:
    print_error = 0
    for l in range(6):
        print_error = print_error + n.train(in_list[l],out_list[l])
    print("epoch:",i,"Error:",print_error/6)
    error_list.append(print_error/6)
    i=i+1
e_time = time.clock()
#输出训练用时
print(s_time,e_time,e_time-s_time)

x=numpy.linspace(0,epoch,epoch)
plt.plot(x,error_list)
plt.show()

print_list = ["石头","剪刀","布"]

while(True):
    a = float(input("CH1:"))
    b = float(input("CH2:"))
    c = float(input("CH3:"))
    d = float(input("CH4:"))
    out = n.query([a,b,c,d])
    out = out.tolist()
    print(out)
    Max_num = out.index(max(out))
    print("当前手势是:",print_list[Max_num])

果然是“人生苦短,我用Python”,这种要啥有啥的感觉真的太爽,不需要考虑内存,不需要考虑矩阵运算的细节….. 不过虽然用着是挺爽的,但是如果想部署到单片机上那还得自己手撸一个,不过既然已经在Python上成功了那只用使用C再实现一遍就好了,当然也可以在单片机上使用用MicroPython不过考虑到单片机的资源以及MicroPython支持的并不是十分完善,所以我还是选择用C写。

(未完待续 ······)