为上肢残疾人士设计的可穿戴脚部手势输入系统





5.00/5 (15投票s)
一项针对上肢残疾患者的辅助技术计划。
摘要
上肢瘫痪是一种不常见的瘫痪类别。脊髓中央综合征、交叉瘫痪以及许多脊髓或大脑相关的异常都可能导致这种瘫痪。我们很少听到或谈论它们,因为它们不像截瘫那样被广泛观察到。由于病例罕见,这在医学领域吸引了最少的研究和开发,你几乎看不到关于这个主题的论文。不仅是瘫痪,还有其他形式的异常使得手部功能受限。手部麻木就是一种不那么罕见的医学问题。许多人也因事故失去双手。简而言之,世界上有些人由于他们的医疗状况,对手的依赖程度低于对脚的依赖。
我们需要问的问题是:“作为技术人员,我们为这群人做了足够多的事情吗?” 在物联网和可穿戴设备时代,人们正在谈论将心跳记录到云端,以及健身手环、睡眠分析等,我们也必须努力采用技术来帮助那些需要技术支持和援助的人群。
下面这位名叫Sheela Sharma的印度艺术家的图片是这项工作的主要灵感来源。
坦率地说,我们已经对各种手势技术进行了大量研究,包括头部手势、手部手势、眼部手势、唇部手势。但是,我们对足部手势的研究是否足够呢?我们为帮助手部残疾人士所做的努力是否充足?
我们认为没有。我认为我们可以做得更好,提出新的技术并创造出急需的解决方案。
这个名为“基于脚部手势的可穿戴文本输入系统”的项目,旨在通过仅通过脚的四个动作:向上、向下、向左和向右手势,为上肢(尤其是手部)残疾人士提供文本输入解决方案,从而强调开发可行解决方案的必要性。
本文将包含两个部分;一个安卓应用程序,我们将使用普通智能手机进行手势识别,然后是一个基于Intel Edison的解决方案。我们认为基于Intel Edison的“纯嵌入式物联网”解决方案需要更可行的可穿戴实现,而安卓手机因其广泛使用可以立即投入使用。我们已经构建了一个用于脚部手势识别的框架。
看起来很简单?从技术上讲,这可能看起来是一个相当容易的任务,但试着将Arduino或手机缠绕在你的脚上,然后尝试以准确性和最小延迟生成手势。因为我们的脚部运动与手部运动相比,受到极大的限制,所以跟踪、操纵、过滤和使用脚部手势变得相当具有挑战性。
在本文中,我们不仅将涵盖技术方面,还将涵盖系统的生理学方面。
1. 引言
问题陈述
根据世界卫生组织的报告,目前全球约有15%的人口患有某种形式的残疾。辅助和康复解决方案已成功开发并应用,以使患者的生活更轻松。
由于上肢残疾在所有残疾病例中占据极小的比例,因此针对这些患者的专用解决方案并不多见。
手部麻木或反向截瘫患者在日常生活中依赖双脚完成许多任务。
为了为这小部分残疾人开发辅助技术,识别足部手势成为一个重要的方向。上肢或手部残疾的人在日常活动中面临很多困难。打字和文本输入就是这样一个绝对需要支持的领域。
观看这段令人惊叹的用脚打字的YouTube视频。有人可能会争辩说,为什么残疾患者不能尝试像视频中提到的那样使用普通键盘呢?
因为,即使是这样的打字也需要特定的键盘位置和坐姿,这在许多情况下可能不可行。
因此,我们引入了**基于脚部手势的文本输入系统**,该系统能够识别脚部手势并将其转换为文本。以下是我们工作目标的摘要。
目标
该项目的主要目标是识别脚部手势。这些手势被转换为打字文本。使用智能手机生成各种脚部手势,不同的手势对应不同的动作,即,对于基本的四种脚部手势(上、下、右、左),有四种不同的动作,其中两种是显示点和划线符号。这些符号序列借助摩尔斯电码转换模块转换为英文字符。
关于足部手势识别的说明
手势大致可以通过两种方式识别
- 手势识别的计算机视觉解决方案和
- 可穿戴解决方案
计算机视觉解决方案要求用户始终处于摄像头前方。这或多或少是一种低成本且经济实惠的解决方案。可穿戴设备是一种可以穿戴在身体上的电子物品。它可以是一个简单的挂坠、衣服、手链、微控制器设备和智能手机。可穿戴解决方案是专门用于特定目的的设备。助听器就是我们几乎都熟悉的一种可穿戴医疗设备。
在生产层面创建专门的医疗解决方案需要巨大的投资和漫长的时间线。因此,研究人员更倾向于“概念验证”或原型设计,以了解概念是否可行。我们希望将重点放在基于足部手势的文本输入系统的可穿戴解决方案上。更重要的是,我们将展示使用常用智能手机进行的概念验证。我们还将介绍使用Intel Edison开发的纯物联网解决方案的早期原型。
最简单的可穿戴设备是智能手机(你难道不是一直把它放在口袋或包里吗?)。智能手机有各种传感器,如加速度计、陀螺仪、振动传感器、环境传感器等。其中,加速度计是主要的传感器,用于测量我们手部、脚部等运动的加速度。例如:神庙逃亡游戏。
在理解了问题、我们的目标和系统的技术方面之后,让我们简要描述我们提出的框架。
所提出的系统是同类中的第一个,之前没有使用足部手势提供技术辅助的工作,我们使用足部手势来提供手势运动,智能手机作为可穿戴设备用于提供输入,足部手势用于输入文本和显示消息,它用于为上肢瘫痪的人开发打字机,他们可以通过提供各种简单的手势来执行简单任务并显示消息以满足他们的需求,并且仅通过基本运动,人们将能够输入文本。它包含4种手势运动,即上、下、左、右和空格。通过这些手势运动,文本被显示出来。
工作的技术概念
基于智能手机的手势在移动游戏中非常流行。我们难道都不习惯玩神庙逃亡并倾斜手机来改变跑步者的方向吗?
然而,识别腿部手势需要比仅仅编码更多的努力。由于手机从未被开发用于腿部使用,因此识别出如何做到这一点是一个挑战。
有很多种方法可以想到将手机用作脚部可穿戴设备。其中一个常见的用例可能会出现在我们脑海中,那就是把它放在袜子里可能是一个最佳主意。但当我们开始研究这个项目时,我们意识到腿部运动与手部运动相比受到更多限制。将手机放在袜子里消除了检测脚背任何运动的可能性。因此,如果选择基于袜子的可穿戴设备,用例将需要用户移动整条腿。我们并非所有人都能像Messy或Christiano Ronaldo那样灵活地使用双腿。上肢残疾人士通常拥有更强壮、更灵活的腿。即便如此,要求他们将整条腿在空中向所有方向移动以生成手势也意义不大。
经过大量的试错和实验,我们发现将手机用带子固定在脚背上方可以获得更好的数据。因此,我们继续开发了一种基于脚部、手机用带子固定在脚背上的可穿戴文本输入解决方案。
下一个挑战是将手机加速度计的数据分类为手势。我们可以采用模糊逻辑等机器学习技术。但是,我们认为我们可以使用线性逻辑(简单的if else)对数据进行分类。因为我们不仅想开发一个基于移动设备的解决方案,还想将解决方案移植到物联网设备应用程序中,所以我们以测量的方式记录了加速度计数据,进行了基于Matlab的分析,最后创建了用于所需手势识别的简单逻辑。
这个框架大约需要几个月的工作。一旦完成,我们就可以在两天内轻松地将逻辑导入Intel Edison!
当一个人向上或向下移动腿时,加速度计的 z 值会发生变化。向上移动腿时,重力会减小。向下移动腿时,重力会增大。同样地,当我们向左侧移动腿时,x 方向会减小;向右侧移动腿时,x 会增大。
加速度被测量并取平均值,如果z大于x和y,则为上下移动;如果z减小,则为上移;如果z增加,则为下移。类似地,如果x高于z和y,则为左右移动。如果减小为左移,如果增加为右移。基于这个简单的原理,我们开发了一个可以主要识别四种手势的系统。
然后,我们利用摩尔斯电码将手势映射到文本输入系统。由于我几年前已经参与过一个类似的项目,名为头部打字机,所以这部分并不太困难。下一个问题是如何将这些检测到的手势实时映射到摩尔斯电码映射系统。
摩尔斯电码主要用于第二次世界大战期间。它构成了电报系统的基本原理。摩尔斯电码仅依赖于两个符号的传播,即点(.)和划线(-)。划线有用于A的编码序列,点有用于B的编码序列,甚至还有用于数字的编码序列。
我们将利用第二次世界大战的优势,将向上和向下的初步动作映射到点(.)和划线(-)。向上足部手势代表划线,向下足部手势代表点。删除序列时,我们使用向左足部手势;确定时,我们使用向右足部手势。还记得《星际穿越》中复杂的量子数据如何编码成摩尔斯电码,最终拯救了世界的方块场景吗?(由于我们的实时摩尔斯电码文本输入系统视频和文章至少比《星际穿越》早三年发布,我们可以安全地假设诺兰先生一定一直在阅读CodeProject的文章!)
我们使用了在“头部文本输入器”项目中开发的C#接口来将手势转换为文本。最后一个难题是如何将手势从可穿戴设备发送到PC。我们使用了最流行的物联网协议Mqtt,它几乎是M2M通信的事实标准。由于大多数物联网设备、操作系统、编程语言都支持Mqtt,我们选择使用Mqtt将检测到的手势从手机发送到PC。
嗯,这就是这个项目的故事,我们为什么开始,我们做了什么,以及我们使用的技术。但是,在最终转向编码之前,我们想更详细地阐述项目的技术和其他方面。
工作范围
尽管该系统主要作为上肢残疾患者的文本输入助手而开发,但它也可以用于患者和许多情况下,普通人的其他辅助用例(当您在舒适的沙发上观看您最喜欢的节目时,何不尝试仅通过腿部动作来更换电视频道呢?)
这项工作可用于控制各种应用,如机器人、家庭自动化、军用车辆、远程汽车控制,以及在电视场景中,例如夜间躺着看电视的人需要更换频道或静音时,都可以使用脚部手势完成。
限制
足部手势的局限性纯粹取决于使用原始数据对传感器值进行分类,因为足部运动因用户而异,它需要用户进行大量的训练才能准确识别手势,它还涉及传输延迟,因为使用中间协议传输数据。
2. 需求规格
硬件要求
- PC(至少4GB内存,Intel i3Core及以上处理器)
- 安卓手机 (安卓 4.0) 及以上
- 互联网连接 (Wi-Fi)
- Intel Edison开发板、Grove扩展板和加速度计传感器
- 爱迪生移动电源
软件要求
- Windows 7 及以上操作系统
- 框架: .NET Framework 4.0 及以上
- 集成开发环境: Visual Studio .NET 2012 及以上, Android Studio
- 用于开发设备应用的 Intel XDK IoT Edison
- 用于与设备通信的PuTTY
- Windows版Mosquitto服务器,带32位Open SSL
- 语言:C#,Android-Java,Node.js用于物联网
3. 系统设计
有些IT人员没有参考设计就无法完成工作!在本章中,我们将稍微关注设计问题。尽管方法论的总结已经解释,但遵循SRS总是一个好主意。因此,我们将用设计图来详细阐述一些概念,以便读者更好地理解这些概念。
系统架构
上图展示了我们提出的基于脚部手势打字机制的系统架构。该系统由智能手机组成,它作为可穿戴设备,通过腕带固定在用户的脚上。智能手机配有加速度计传感器,用于测量脚部运动的加速度。手机的加速度数据在脚部每次移动时都会被收集。这些数据经过聚合和分类,形成特定的手势集。然后,这些手势数据通过名为MQTT服务器的服务器传输到PC。MQTT服务器/协议是连接PC应用程序和智能手机应用程序的协议。PC端则编程/安装了摩尔斯电码转换模块,该模块将手势数据转换为短线和点,然后再转换为英文字符。每当用户进行脚部移动时,智能手机应用程序就会识别该移动并将其分类为手势集,然后将这些数据发送到MQTT服务器。MQTT服务器识别接收数据的特定PC。
PC从MQTT服务器接收此手势数据,然后根据脚部动作将其转换为破折号(-)和点(.)。之后,点和破折号序列借助摩尔斯电码转换为英文字符。不同的动作有不同的手势集。脚部向上移动用于输入破折号(-)符号,脚部向下移动用于输入点(.)符号。脚部向右移动用于显示与当前点和破折号序列相关的字符,脚部向左移动用于删除序列。
摩尔斯电码在第二次世界大战期间被广泛使用。它构成了电报系统的基本原理。摩尔斯电码依赖于两种符号的传播,即点(.)和划线(-)。有用于字母A的划线序列,用于字母B的点序列,甚至还有用于数字的序列。
我们将利用第二次世界大战的优势,将向上和向下的初步动作映射到点和划线。向上脚部手势代表划线,向下脚部手势代表点。删除序列时,我们使用向左手势;确定时,我们使用向右脚部手势。
数据流图
第0级DFD
1级DFD
用例图
序列图
活动图
4. 编码
安卓代码
图:Android布局设计
该项目的用户界面相当简单。我们只有一个名为`edServer`的`EditText`,用户需要在此处输入MqTT代理地址。**连接**按钮名为`btnConnect`。当用户点击按钮时,传感器管理器必须被激活并开始感应数据。分类后的数据将被发布到代理中的一个Mqtt通道。
一个名为`tvStatus`的`TextView`用于调试目的,以便显示加速度计数据、Mqtt消息和分类结果。
上述UI的布局如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<TextView android:text="Foot Gesture Recognition[MqTT Topic: rupam/gesture]"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvHello" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="MqTT Server"
android:id="@+id/tvServer"
android:layout_marginTop="56dp"
android:layout_below="@+id/tvHello"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/edServer"
android:width="200dp"
android:layout_alignTop="@+id/tvServer"
android:layout_toRightOf="@+id/tvServer"
android:layout_instepndOf="@+id/tvServer"
android:text="192.168.1.3" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Connect"
android:id="@+id/btnConnect"
android:layout_below="@+id/edServer"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Status"
android:id="@+id/tvStatus"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
我们使用Eclipse Paho Mqtt客户端库jar进行Mqtt实现。您需要通过文件管理器进入物理项目目录,创建一个名为“lib”的文件夹,将jar文件粘贴到其中。在Android Studio左上角的标签页中,选择“项目”,进入树形结构中的lib,右键单击添加的jar文件,然后选择“添加为库”。
Gradle 构建将在后台重新构建项目。
在 `MainActivity` 类中实现 `SensorEventListener`。
public class MainActivity extends Activity implements SensorEventListener
声明一个名为`sampleClient`的`IMqttClient`,它将用于与代理通信。
Sensor类对象sensor将用于监听加速度计数据的变化并进行处理。`edServer`、`tvStatus`和`btnConnect`是Java元素,对应于UI元素,它们将通过`onCreate()`方法使用`findViewById()`进行初始化。我们使用一个名为“`rupam/gesture`”的mqtt通道,它将存储在一个名为`topic`的变量中。
private SensorManager sensorManager;
private Sensor sensor;
EditText edServer;
Button btnConnect;
TextView tvStatus;
String sAddress = "iot.eclipse.org";
String sUserName = null;
String sPassword = null;
String sDestination = null;
String sMessage = null;
String topic = "rupam/gesture";
String content = "FORWARD";
int qos = 0;
String broker = "tcp://iot.eclipse.org:1883";
//String broker = "tcp://192.168.1.103:1883";
String clientId = "RUPAM_DAS";
MemoryPersistence persistence = new MemoryPersistence();
IMqttClient sampleClient;
由于Android不允许主线程调用任何与网络相关的调用,我们将网络调用(即,连接到代理和发布消息)抽象到`AsyncTask`中。为了连接到代理,我们使用`ConnectionClass`。
请注意,连接方法中的`Looper.prepare()`调用。我们发现,如果将代码目标设定为Android 5或更高版本,`AsyncTask`根本不会将调用返回给主线程。在论坛中搜索了数小时后,我们在Stackoverflow上发现,如果没有这条语句,控制权根本不会交还给UI线程。此外,如果不对该调用进行条件判断,检查Looper是否已初始化,它将不断创建新的Looper实例,这将立即导致您的应用程序崩溃。
Paho客户端要求Mqtt代理具有`tcp://`前缀。一旦连接,我们就可以通过样本客户端发布消息。由于我们不需要检索任何数据,我们避免将客户端订阅到任何通道以接收消息。
////////CONNECTION CLASS/////
public class ConnectionClass extends AsyncTask<ActivityGroup, String, String>
{
Exception exc = null;
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override protected String doInBackground(ActivityGroup... params) {
try
{
if (Looper.myLooper()==null)
Looper.prepare();
sampleClient = new MqttClient(broker, clientId, persistence);
// sampleClient=new MqttClient(broker,clientId);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
IMqttToken imt=sampleClient.connectWithResult(connOpts);
Log.d("MQTT MODULE.....","....DONE..."+
sampleClient.getServerURI()+"--"+imt.getResponse().getPayload());
if(sampleClient.isConnected()) {
return "CONNECTED";
}
else
{
return "Connection Failed.....";
}
}
catch(Exception ex )
{
Log.d("MQTT MODULE", "CONNECTION FAILED " +
ex.getMessage() + " broker: " + broker + " clientId " + clientId);
// Toast.makeText(MainActivity.this, "FAILED", Toast.LENGTH_LONG).show();
// tv2.setText("Failed!!");
return "FAILED";
}
// return null;
}
@Override protected void onPostExecute(String result) {
super.onPostExecute(result);
if(result!= null)
{
isConnected=true;
tvStatus.setText(result);
}
}
}
发布或发送消息是资源密集度较低的任务。因此,我们避免创建单独的类。由于将有很多消息,我们也不需要通知用户每条发送的消息。因此,我们创建了一个简单的`AsyncTask`类的匿名扩展,并发布消息。
void Send(String content)
{
final String data=content;
isConnected =sampleClient.isConnected();
AsyncTask.execute(new Runnable()
{
@Override
public void run()
{
try
{
if(isConnected)
{
MqttMessage message = new MqttMessage(data.getBytes());
message.setQos(qos);
sampleClient.publish(topic, message);
}
else
{
// Connect();
broker="tcp://"+edServer.getText().toString().trim()+":1883";
ConnectionClass con=new ConnectionClass();
con.execute();
}
Log.d("MQTT MODULE",data+" SENT");
}
catch(Exception ex)
{
}
//TODO your background code
}
});
}
在`onCreate()`方法中,我们初始化UI组件并注册`ACCELEROMETER`传感器监听器。我们还为`btnConnect`点击附加了一个事件监听器,在该监听器中,我们`execute()`一个连接类对象,该对象在后台尝试连接到代理。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
sensorManager.registerListener
(this, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
sensorManager.SENSOR_DELAY_UI);
edServer=(EditText)findViewById(R.id.edServer);
btnConnect=(Button)findViewById(R.id.btnConnect);
tvStatus=(TextView)findViewById(R.id.tvStatus);
btnConnect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Connect();;
broker="tcp://"+edServer.getText().toString().trim()+":1883";
ConnectionClass con=new ConnectionClass();
con.execute();
}
});
}
最后,我们来看看代码中将加速度计数据转换为手势的最重要部分。
float prevX=-1,prevY=-1,prevZ=-1;
float DX=0;
long lastUpdate = System.currentTimeMillis();
int WAIT=1000;
boolean detected=false;
@Override
public void onSensorChanged(SensorEvent event) {
String command="";
if(!isConnected)
{
return;
}
float[] values = event.values;
// Movement
float x = Round(values[0], 1);
float y = Round(values[1], 1);
float z = Round(values[2], 1);
long actualTime = System.currentTimeMillis();
if ((actualTime - lastUpdate) > WAIT)
{
if(detected)
{
detected=false;
WAIT=2000;
return;
}
else
{
WAIT=1000;
}
long diffTime = (actualTime - lastUpdate);
lastUpdate = actualTime;
///// Make the Calculations Here//////
float diffX=x-prevX;
float diffY=y-prevY;
float diffz=z-prevZ;
Log.d("Sensors Change:","X="+diffX+" Y="+diffY+" Z="+diffz);
if(diffY>5.0 )//&& Math.abs(diffz)>.9
{
Log.d("RECOGNIZED", "\n\n UP \n\n");
detected=true;
WAIT=3000;
Send("UP");
}
if(diffY<-2.0 )//&& Math.abs(diffz)>.9
{
Log.d("RECOGNIZED","\n\n DOWN \n\n");
detected=true;
WAIT=3000;
command="DOWN";
//Send("DOWN");
}
if(diffX<-4.1 )//&& Math.abs(diffz)>.9
{
if(!detected) {
Log.d("RECOGNIZED", "\n\n RIGHT \n\n");
detected = true;
WAIT=3000;
command="RIGHT";
//Send("RIGHT");
}
}
if(diffX>.9 && diffX<3.5 )//&& Math.abs(diffz)>.9
{
if(!detected)
{
Log.d("RECOGNIZED", "\n\n LEFT \n\n");
detected = true;
WAIT=3000;
//Send("LEFT");
command="LEFT";
}
}
//////////////////////
/// Finally Update the past values with Current Values
if(prevX!=-1)
{
DX=DX+diffX;
prevX = x;
prevZ = y;
prevZ = z;
}
else
{
prevX = x;
prevY = y;
prevZ = z;
}
if(detected)
{
if(command.length()>1) {
Send(command);
command = "";
}
}
}
///////////////
}
我们假设手机被固定在用户右脚背的顶部。我们使用一种通常称为基于位移的检测技术。请注意,我们使用`global prevX`、`prevY`和`prevZ`。加速度计的当前`x`、`y`和`z`值减去先前的值。当手机水平放置时,重力变化将通过`z`反映出来,但非常有限。尝试抬起脚背,您会发现`y`的变化比`z`的变化显著得多,因为您只是抬起脚背,而不是整个手机。同样,当您放下脚背时,位移将为负值。
如果您使用手势,这将发生变化。当用户手持手机时,他可以轻松地上下举起手机,使 `z` 值显著变化。
现在试着将你的脚背向左和向右倾斜(右腿)。你会发现将右脚背向左倾斜很困难,你无法正确完成。这对手部也同样适用。试着在保持手部其余部分稳定的情况下,将右手腕向左旋转。除非你是有天赋的罗杰·费德勒,否则你不会太成功!
但对于手部来说,限制较小,因为你可以借助肘部舒适地将手机向左倾斜。我们为这个手势困扰了几天。然后我们偶然发现了这个手势的解决方案。
如果你保持腿部稳定,不通过脚踝移动脚背,而是将大腿向左脚踝移动,这样脚背就会自动向左倾斜。负 X 表示向右,而正 X 的微小变化表示向左。
一旦检测到手势,用户需要将脚背恢复到中立位置。因此,我们给他两秒钟的时间让腿回到中立位置。一旦检测到手势,这个填充时间可以防止重复手势被发送。
手势检测通过`send`方法触发mqtt发布。
你只需触发应用程序,连接代理,然后将其固定在你的脚背上。现在生成手势。
Mqtt Broker
为了测试该应用,您可以使用iot.eclipse.org。但是,由于代理是远程的,数据通信存在显著延迟。基于足部手势的文本输入系统本身就有点耗时。因此,您不会希望增加远程服务器的网络延迟。对吗?
因此,我们将使用适用于Windows的本地Mqtt代理。Windows只允许1063个连接(我不知道为什么)或客户端。但是,由于我们只会连接一个客户端到我们的代理,因此在本地机器上运行的Windows Mqtt服务器就足够了。
您需要先安装Win32 Open SSL。
现在下载并安装Windows版Mosquitto Broker
现在,进入OpenSSL安装目录,该目录应位于 `C:\OpenSSL-Win32`,然后复制 `pthreadVC2.dll` 文件并替换Mosquitto安装目录中的同名文件。就这样。
从命令提示符进入`Mosquitto`目录并运行`mosquitto.exe`以查看代理是否在您的系统中运行。要关闭代理,请使用**Ctrl+c**。代理的IP地址将是您PC的本地IP地址,您可以使用`ifconfig`命令获取。
基于C#的手势到摩尔斯电码转换器
像CodeProject中的许多人一样,C#是我们的初恋。这门语言健壮,当您想开发原生Windows桌面应用程序时,没有什么能比得上它。选择C#的另一个原因是其优秀的库和快速简便的开发周期,这对于原型设计很重要。就像我们的Android应用程序一样,这是一个单窗体应用程序,具有连接到代理的功能。在Android应用程序中,我们发布到通道,但在C#中,我们将订阅通道。当消息收到时(它将是二进制格式),我们需要将其格式化为字符串。接收到的手势被解释并发送到摩尔斯电码转换方法。字符串被追加。当给出LEFT手势时,最后一个符号被删除。在RIGHT手势之后,当前的符号集被转换为字符(观看视频以更好地理解)。
如果你查看我们 C# 解释器 UI 的一个快照,你会看到一个“启动代理”按钮。我们希望只从我们的应用程序启动代理。这使用 .NET 的 `System.Diagnostics.Process` 类很简单。我们首先找到 `Mosquitto` 的安装位置,然后以编程方式启动服务器。如果服务器正在运行,我们会关闭它并重新启动它。
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
private void button1_Click(object sender, EventArgs e)
{
string s=Environment.GetFolderPath
(Environment.SpecialFolder.ProgramFilesX86);
s = s + "\\mosquitto\\mosquitto.exe";
//System.Diagnostics.Process.Start(s);
procMqtt = new System.Diagnostics.Process();
procMqtt.StartInfo = new System.Diagnostics.ProcessStartInfo(s," -v");
procMqtt.StartInfo.UseShellExecute = true;
Process[] pname = Process.GetProcessesByName("mosquitto");
for (int i = 0; i < pname.Length; i++)
{
pname[i].Kill();
}
procMqtt.Start();
System.Threading.Thread.Sleep(2000);
SetParent(procMqtt.MainWindowHandle, groupBox2.Handle);
}
在“连接到代理”按钮上,我们初始化Mqtt客户端对象,将其设置为服务器地址(即运行应用程序的PC的本地IP地址),并通过向`MqttMsgPublishReceived`添加事件处理程序来订阅消息。
MqttClient mc = null;
System.Diagnostics.Process procMqtt;
private void button2_Click(object sender, EventArgs e)
{
try
{
var ip = IPAddress.Parse(labIP.Text);
mc = new MqttClient(ip);
mc.Connect("RUPAM");
mc.Subscribe(new string[]{topic},new byte[]{(byte)0});
mc.MqttMsgPublishReceived += mc_MqttMsgPublishReceived;
mc.MqttMsgSubscribed += mc_MqttMsgSubscribed;
MessageBox.Show("Connected");
mc.Publish(topic, GetBytes("VM Broker Started"));
}
catch
{
}
}
为了方便用户查找服务器地址(或PC的本地IP地址),我们在表单的`Load()`事件中获取它。
public string LocalIPAddress()
{
IPHostEntry host;
string localIP = "";
host = Dns.GetHostEntry(Dns.GetHostName());
foreach (IPAddress ip in host.AddressList)
{
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
localIP = ip.ToString();
break;
}
}
return localIP;
}
这是通过遍历所有DNS条目并找出`IPAddress`来完成的。
由于Mqtt客户端在不同的线程上运行,如果您想从`messagePublishReceived`事件处理程序访问UI元素,您需要使用委托来访问UI元素。请记住,Mqtt消息以字节形式出现,需要转换为字符。
当试图在我们C#应用中转换从安卓手机接收到的字节时,我们遇到了障碍。`Encoding`类无法将字节转换为ASCII字符。这是因为安卓为每个字符发送两个字节(UTF)。第一个字节是字符代码,而第二个字节是“`\0`”。因此我们需要调整代码进行转换。
void mc_MqttMsgPublishReceived(object sender,
uPLibrary.Networking.M2Mqtt.Messages.MqttMsgPublishEventArgs e)
{
//throw new NotImplementedException();
//MessageBox.Show(GetString(e.Message));
this.Invoke((MethodInvoker)delegate
{
if (e.Message[1] == (byte)0)
{
// listBox1.Items.Add(GetString(e.Message));
}
else
{
try
{
string command = "";
for (int i = 0; i < e.Message.Length; i++)
{
// command = command + ('A' + ((int)e.Message[i] - 64));
command = command + ((char)('A' +
((int)e.Message[i] - 65))).ToString();
}
if (command.Equals("UP"))
{
txtCommands.Text = txtCommands.Text + "-";
}
if (command.Equals("DOWN"))
{
txtCommands.Text = txtCommands.Text + ".";
}
if (command.Equals("LEFT"))
{
txtCommands.Text = txtCommands.Text.Substring
(0, txtCommands.Text.Length - 1);
}
if (command.Equals("RIGHT"))
{
txtTyping.Text = txtTyping.Text +
ConvertMorseToText(txtCommands.Text);
txtCommands.Text = "";
}
}
catch
{
}
}
});
}
请注意,我们在UP和DOWN命令上追加DASH(-)和DOT(.),并调用名为`ConvertMorseCodeTotext()`的方法将符号集转换为文本。此函数本质上是将摩尔斯电码映射到英文字符。
#region Morse code related part
private Char[] Letters = new Char[] {'a', 'b', 'c', 'd', 'e', 'f', 'g',
'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', ' '};
private String[] MorseCode = new String[] {".-", "-...", "-.-.",
"-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..",
"--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-",
"...-", ".--", "-..-", "-.--", "--..", "-----", ".----", "..---",
"...--", "....-", ".....", "-....", "--...", "---..", "----.", "----"};
public String ConvertMorseToText(String text)
{
text = "@" + text.Replace(" ", "@@") + "@";
int index = -1;
foreach (Char c in Letters)
{
index = Array.IndexOf(Letters, c);
text = text.Replace("@" + MorseCode[index] +
"@", "@" + c.ToString() + "@");
}
return text.Replace("@@@@", " ").Replace("@", "");
}
我们使用正则表达式来查找匹配的摩尔斯电码序列并将其替换为字符。
就这样!现在当你通过脚生成手势时,字母就会在屏幕上打出来。
Intel Edison 设备应用
您可以参考Moumita Das关于Intel Edison手势识别的文章,了解如何在设备层面纯粹实现这一概念。
5. 结果与讨论
性能分析
表格形式的测试值
读取次数 | 1个序列 | 时间(秒) | 2个序列 | 时间(秒) | 3个序列 | 时间(秒) | 4个序列 | 时间(秒) |
1 | 好的 | 4 | 好的 | 6 | 好的 | 10 | 好的 | 14 |
2 | 好的 | 3 | 好的 | 8 | 好的 | 11 | 好的 | 16 |
3 | 好的 | 5 | 好的 | 8 | 好的 | 11 | 好的 | 14 |
4 | 好的 | 6 | 好的 | 8 | 好的 | 11 | 好的 | 17 |
5 | 好的 | 4 | 好的 | 6 | 好的 | 18 | 好的 | 10 |
6 | 好的 | 5 | 好的 | 7 | 好的 | 10 | 好的 | 20 |
7 | 好的 | 5 | 好的 | 7 | 好的 | 10 | 好的 | 14 |
8 | 好的 | 5 | 好的 | 7 | 好的 | 11 | 好的 | 16 |
9 | 好的 | 5 | 好的 | 11 | 好的 | 12 | 好的 | 14 |
10 | 好的 | 4 | 失败 | - | 好的 | 11 | 好的 | 14 |
字母的准确性
- 1个序列的平均时间= 4.6秒
- 2个序列的平均时间= 7秒
- 3个序列的平均时间= 11.5秒
- 4个序列的平均时间= 15秒
- 1个字符的总平均时间=9.52秒
上表描述了工作的测试部分,我们把智能手机安装在用户腿上,测试了每个字母序列,即,对于1个序列,即点(.)或破折号(-),平均10次所用的时间,我们得到了所有正确的结果,即,对于我们脚部的1次手势运动,我们测试了10次,所有10次都得到了正确的结果,1个序列的平均时间是4.6秒,即,我们选择了字母“E”,它有点(.)序列,实际结果与预期相同。
同样,我们测试了2个序列,即我们考虑了字母“A”,它有点-划线[.-],我们测试了10次,其中有9次结果正确,1次结果失败,2个序列的平均时间需要7秒,这里实际输出与预期相同。接下来我们测试了3个序列,即我们考虑了字母“D”,它包含划线-点-点[-..],我们测试了10次,所有次数都得到了正确的输出,3个序列的平均时间是11.5秒。
最后,我们测试了4个序列,即我们考虑了字母“B”,它包含破折号-点-点-点[-...],我们测试了10次,所有次数结果都正确,4个序列的平均时间是15秒。
一个字符的总平均时间为9.52秒。序列数量及其对应时间如下所示:
通过考虑上述表格值,结果以图表形式显示。上图6.2表示序列数与平均时间的关系图,其中x轴表示平均时间(秒),y轴表示序列数,其中一个输入序列所需时间为4.6秒,两个输入序列所需时间为7秒,三个输入序列所需时间为11.5秒,四个输入序列所需时间为15秒。
**结果**:该项目适用于各种上肢残疾人士,但它也有一些缺点,例如,由于心跳等原因,我们的人体从一开始就从未稳定过,所以它总是处于运动状态,因此PC应用程序在没有任何手势形成的情况下会接收少量输入,甚至我们的Android应用程序也包含加速度计传感器,它会感应脚部运动。尽管存在一些缺点,但只要Wi-Fi范围速度快,输出就会准确形成,该项目成功适用于各种领域,通过进一步努力可以减少一些缺点,由于这是该工作的第一个版本,它需要处理协议对应的延迟。
屏幕截图
a. 传感器数据分类器
b. PC上的应用
c. 不同的脚部手势
6. 结论
一般来说,世界上有许多上肢残疾的瘫痪患者,在某些阿尔茨海默症病例中,瘫痪属于这一类别。对于下肢残疾人士,为了帮助他们进行手势运动,计算方面取得了巨大的进步,但借助脚部手势的计算改进却不显著。在这项特定的工作中,我们开发了一个用于分类脚部手势的应用程序,此外,我们已将基于手势的库扩展到特定应用程序,即基于脚部手势的文本输入系统,尽管该文本输入系统相当缓慢,但它仍然可以作为原型,用于研究针对上肢瘫痪人士的辅助技术。
未来工作
该工作必须处理协议带来的延迟,通过将手势整合到可穿戴设备中(如Google Watch或其他类型的腕表设备),可以进一步改善,从而显著降低传输和手势识别的延迟,在这种情况下,我们可以提高文本输入系统的准确性。
- 该作品可应用于机器人、家庭自动化等各种领域。
- 此外,这项工作还可以用于更多手势分类,例如摇晃手势、以圆形方式移动脚背等。