第一次自己做手势识别就是在大三那年的大学生电子设计竞赛,那也是我第一次开始思考“算法”。那一年的电赛有一道题目,使用FDC2214(TI的一款四通道电容传感器)设计一个识别装置,要求能够识别,“石头”、“剪刀”、“布”,三种手势以及,放置了几个手指。
拿到题目的我最先想到的就是在识别区域放置四组电极,当手放在电极上方区域的时候记录四组电极的电容值,然后按照阈值判断到底属于哪个手势。
但是随着一点点的把想法变成现实,问题渐渐的浮现了出来,首先就是确定阈值时候的难度超过了我的想象,因为有四个通道,需要同时满足条件才能判定为某个手势,这就导致你必须合理的安排每一个手势的每一个通道的阈值,而起比赛要求能够学习不同人的手势,这让这个方案实施起来变得极其困难。在一个就是手放在电极上方测出来的电容很容易受到环境的影响,而阈值判别的方式容错性又极低。在尝试了一番后就放弃了这个方案,转而开始想其他的办法。
这个问题的本质其实是怎么判断一个数据到底是属于哪一个集合,也就是说着其实是一个典型的分类问题,那么就从常用的分类算法入手,首先想到的就是神经网络,但当时也就是想到,因为主控使用的是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写。
(未完待续 ······)