新人贴 机飞前置之肌电运动估计
Cosecant2022/05/08原创 电子技术极客DIY IP:上海
关键词
肌电
sEMG

前言:没做完,发出来图一乐,最好能抛砖引玉,大佬带我飞。

       先说这个项目的由来,起因是我看现在的研究热点都是脑电,EEG脑控之类的。但我觉得这个科技实际很靠后,属于跳科技树而且步子太大容易扯到蛋的那种。可能是高大上的题目好申请经费?总之,我认为前置科技是肌电也就是EMG,这玩意很早以前就有了,但是一直没怎么应用,我觉得还挺可惜的,毕竟采样并不困难,直接反映了肌肉运动,而且超前于运动本身出现,作为自动控制的传感器信号来源可谓得天独厚,可以说是人体增强技术的必然前置。目前的动捕基本都是靠CV和惯导,现在圈里CV方向到是搞得很花,但是CV对图像要求本身就比较高,盲区怎么处理?也很难做成可穿戴,而且响应总会大大滞后。惯导的数据处理困难,价格高昂,每个joint都要一个传感器,数据量还贼大。

blablabla·····

总之,怀揣着中二的梦想和我毕设的压力,我——开工了。

然后,所有人看向我,我宣布个事!我是XX!

起初其实还挺顺利的。商用的EMG仪器都贼贵,tb单路ECG改的EMG模块都几百,正儿八经的EMG仪器都是w,我自己做着玩的毕设又报不了销,而且商用款除了采样精度比我高一点(堆料)也不一定有我这个强。起码体积和channel数量上能和我的设计打的并不多。总之商用EMG方案不能满足我的需求,所以就自己做了。

芯片选型花的时间最多,主要是要考虑成本,选型结束后,原型机的生产就很快了。

电极背面实物.jpg

电极正面实物.jpg

这里注意下,我想用同轴线传输信号所以选用了

错误的,间接的,虚假的,庸俗的,主观的,残缺的,平面的,片面的,孤立的,辩证法的,雅俗之分的,离题万里的,难以言喻的,不痛不痒的,批判性的,针对性的,创造性的,发散性的,具有独特意义的 IPEX端子

这玩意太容易松动了,第二版决定同轴线直接焊了。Orz

不过数据采样的测试还是没问题的,如下。

电极测试.mp4  点击下载

另,人体耦合的工频干扰问题很大,我的座位上有500mV+的工频干扰,190dB的CMR都压不住,毕竟皮肤接触电阻不好控制,但离了2m就只有几mV了。理论上等我加了右腿驱动这个情况就会改善。


数据采集的主控部分也进展顺利。毕竟本身没啥难度。

留档2d.png 留档3d.png

单面贴装不是很紧凑,验证机,后面缩小体积的话大概能砍掉一半的面积。模拟部分就32路ADC和一个右腿驱动,数字部分也只加了RTC和TF卡数据存储的功能。

上文出现的IPEX端子不牢固的问题也改进了。

8cell仿真图.png

然后就遇到了最痛苦的事情,随着答辩时间一天天临近,上海它封城了~~欧耶快乐延毕

目前已经封了接近两个月了。我的进度就一直停滞,板子和芯片都卡到厂家拿不到。我寻思我数据采不到我毕设要凉凉啊,临答辩一个月换题是不是有点做大死?

但天命难违,硬件暂时没法完工了,只能先做点软件工作。

我就去看了看别人的sEMG数据集,选用的数据集为NinaProDB8,其实本来想找个既有肌电数据又有关节角度数据的,然而貌似没有,有人知道的话请@我,感谢!

众所周知,想要做到动态运动估计,就不能把肌电信号当做简单的分类问题,用机器学习对这些数据简单分类也太low了。

那么,答案只有一个了。用RNN,俺打算用LSTM试试,首先把嫖到的数据集简单处理下。

import numpy as np
import scipy.io as sio
import matplotlib.pyplot as plt
import os

dataDir='./EMG_data/NinaProDB8/'
saveDir='./EMG_data/splitDB8/'
newData={}
def findAllMat():
    """
    @description  :找到dataDir下所有的mat文件
    ---------
    @param  :
    -------
    @Returns  :
    -------
    """
    for root,dirs,files in os.walk(dataDir):
        for f in files:
            if(f.split('.')[-1]=="mat"):
                yield f

def rwMat(mData,name):
    """
    @description  :读取并生成mat文件
    ---------
    @param  :mData mat数据
    -------
    @Returns  :
    -------
    """
    newData.clear()
    newData['emg'] = mData['emg']
    newData['exercise'] = mData['exercise']
    newData['subject'] = mData['subject']
    newData['glove'] = mData['glove']
    newData['restimulus'] = mData['restimulus']
    newData['rerepetition'] = mData['rerepetition']
    sio.savemat(saveDir+name, newData)

#ssd下大概需要3min 
for matFile in findAllMat(): 
    matData=sio.loadmat(dataDir+matFile)
    rwMat(matData,matFile)

原始数据集太大了,包括很多不需要的数据,提取出emg exercise subject glove restimulus 和rerepetition数据即可  

以下是截取用的代码。 22G->8G 爽了。




啥?你问我后面呢?没做完哪有什么后面啊?室友叫我吃隔离餐了,有空再更。

大概这个月中能把RNN的代码撸完测试,不过没人看也许就算了。可能 也许 maybe

+0.5  科创币    zRed洲虹    2022/05/14 楼主加油啊
来自:电子信息 / 电子技术综合交流区 / 极客DIY动手实践:实验报导
3
11
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
Cosecant 作者
2年7个月前 IP:上海
904101
第二天~八点(pm)~

查阅了NinaPro的原始论文,ps:他们的官网是真的烂,图片没显示,害我找了好久。

XXXXXXXXXXXXXXXXXXXXXX/DB9_Instructions

另外使用了DB9论文中的角度换算

尝试使用这个转换DB8中的手套数据,并使用UE4进行可视化。  

然而论文里的源码并不能运行,在连续改了两个bug跑出来以后,得到的角度数据没有参考系,等于无法复现。焯! sticker

然后我就寻思了,手套传感器数据通过隐函数映射到手指姿态,虽然论文里的隐函数转换脚本没法用了,但是我可以直接输出传感器数据啊,四舍五入我也得到手指姿态了。绝对不是为了赶论文。 sticker

然后我就快乐的写了一大片代码开始跑上面得到的数据集。


考虑到论文最好不全公开,我就贴下网络结构。PS 目前没调好,不一定能收敛。

class MyNet(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers, output_chan):
        super(MyNet, self).__init__()
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.lstm = nn.LSTM(input_size,
                            hidden_size,
                            num_layers,
                            batch_first=True)
        #input tensor of shape (sequence length, batch size, input_size)
        self.fc=nn.Linear(hidden_size, output_chan)
        """ self.fc = nn.Sequential(
            nn.Dropout(droprate),  #Dropout层
            nn.LeakyReLU(),
            nn.Linear(hidden_size, 64),
            nn.LeakyReLU(),
            nn.Linear(64, output_chan)) """

        #把隐藏状态空间用全连接层映射到输出空间。

    def forward(self, x):
        #初始化hidden和cell的states 不赋予初值所以用zeros
        #详情:https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html
        h0 = torch.zeros(self.num_layers, x.size(0),
                         self.hidden_size).to(device)
        c0 = torch.zeros(self.num_layers, x.size(0),
                         self.hidden_size).to(device)
        #LSTM
        output, (hn, cn) = self.lstm(x, (h0, c0))
        #tensor of shape (batch size, sequence length, hidden_size)

        output = self.fc(output[:, -1, :])  #取sequence的最后一个输出hn
        #tensor of shape (batch size, sequence length, hidden_size)

        return output

开始炼丹!

1652116462(1).png

1652116635967.png

这400多w的Loss是怎么来的???

刚开始,我还以为是梯度爆炸了之类的,毕竟我的Loss从几十万到几百万都有,很是奇怪,所以我先从这方面入手

这Loss这么大是不是我网络有问题啊?

询问了大佬后突然发现了一个盲点,我的数据是EMG数据集,用的是LSTM,激活函数默认为tanh,然后EMG数据大概是e-6量级,感觉会有问题,所以做了归一化。

归一化脚本:

def data_scaler(input_list):
    """
    @description  :
    ---------
    @param  :
    -------
    @Returns  :
    -------
    """
    scaler = MinMaxScaler(copy=False)
    matData=input_list[0]
    matFile=input_list[1]
    norm_data={}
    norm_data['emg'] = []

    scaler.fit(matData['emg'])
    norm_data['emg']=scaler.transform(matData['emg'])
    matData['emg']=norm_data['emg']
    sio.savemat(saveDir+matFile, matData)

pool = ThreadPool()
mat_list=[]
for matFile in findAllMat(): 
    matData=sio.loadmat(dataDir+matFile)
    mat_list.append([matData,matFile])
results = pool.map(data_scaler, mat_list)
pool.close()
pool.join()

抽空再试试标准化。

然后继续训练,中间又改小了batch_size,由于Loss是整个batch的Loss和,所以Loss确实小了点,但是也没小多少,归一化以后结果好像没啥变化···不管了,归一化做了是对的,先这样跑吧,只要能收敛就行。

然后我发现了Loss的一个规律

1652116260.png

这玩意一会儿高一会儿低的怎么?

经过分析我发现

大概23680条数据为一个周期,一个文件大概有2292000条  

有0-9个动作,其中0为复位  

每个动作会重复10次  

data_total=2292000  

data_total/9(个动作)/10(10次重复)=25472 非常接近23680的数量级,也就是说动作改变的时候loss会突增突减


那行吧,好像也没啥问题,先跑着。

然后在我的3080跑了一小时之后

e2790b2744788c3bb8d1c72fb93ca99.png

???我的Loss呢!!!

我那么大的Loss,几百万呢?怎么突然就没了?

没了Loss.gif

俺跑了两遍,都是在这里Loss消失,又百度了下。高度怀疑是数据集本身有问题。

经过写脚本定位查找以后,从7个G的数据集里成功揪出来了丢失的数据

mad,这群*人不会做数据集能不能不要做,跑了俩小时直接给我干废了

PL5H{GKQV}`(BJWOQHVTL60.png

又把他们截掉然后重新跑。

脚本贴一下吧,这个nan问题很是花了我一番功夫

import numpy as np
import scipy.io as sio
import matplotlib.pyplot as plt
import os
from torch.utils.data import Dataset
import torch
from math import isnan
dataDir = './EMG_data/linear/'
saveDir = './EMG_data/nan_deled/'
def findAllMat():
    """
    @description  :找到dataDir下所有的mat文件
    ---------
    @param  :
    -------
    @Returns  :
    -------
    """
    for root,dirs,files in os.walk(dataDir):
        for f in files:
            if(f.split('.')[-1]=="mat"):
                yield f
def readMat(mData, name, del_nan):
    """
    @description  :读取并处理mat文件,找到nan的数据
    ---------
    @param  :mData 数据 name文件名 del_nan是否处理
    -------
    @Returns  :
    -------
    """
    h=-1
    l=-1
    mlist=['emg','glove','rerepetition','restimulus']
    for i in range(0,4):
        foo=mData[mlist[i]]
        if(len(np.where(np.isnan(foo))[0])):
            h=np.where(np.isnan(foo))[0][0] #行数-1(索引)
            l=np.where(np.isnan(foo))[1][0] #列数-1
            #print(name)
            break
    if(h!=-1 and del_nan):
        for i in range(0,4):
            mData[mlist[i]]=mData[mlist[i]][:h,]
        sio.savemat(saveDir+name, mData)    


for matFile in findAllMat():
    matData = sio.loadmat(dataDir + matFile)
    readMat(matData, matFile, True)

最后都整的差不多了,打算挂一晚上,看看能不能跑收敛。

明天更不更看心情了。

引用
评论(1)
7
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
罗布Carrot
1年10个月前 IP:山东
917303

真的惨,这提醒我们要做好对数据集的检查和处理

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
眏水之镜
1年10个月前 IP:江苏
917327

好强大,这是采集手部手势么?

引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
Cosecant
进士 机友 笔友
文章
1
回复
1
学术分
0
2020/01/03注册,9天10时前活动

人类的痛苦源自于对自己无能的愤怒。

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:上海
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}