我如何构建了一个实时端到端的手势识别系统,使用深度学习和Raspberry Pi以实现无缝、非接触控制。
通过AI生成的图片,在ChatGPT上生成。
开始第一次尝试Apple Vision Pro时,我被介绍到了人机互动的世界,更具体地说是自然用户界面(NUI)和人机互动。
这让我思考,如果我们能仅仅通过裸手来控制日常设备,比如恒温器、电视、咖啡机,甚至电脑,那该有多酷?
不需要遥控器。不需要按钮。不需要触屏。只需要自然、无接触的互动——这是我们与家里设备互动最舒适的方式。
不久前,我买了一个树莓派,深入研究真正的边缘人工智能应用,于是决定将这个想法变成一个业余项目。
因为我对无接触交互和边缘计算都挺感兴趣的,所以我完成了一个整个基于手势控制的系统,它可以应用于各种地方,比如智能家居。
该系统完全运行在树莓派上,只用了加速度计和陀螺仪的数据,不需要摄像头来保护隐私。
以后,我会让这个系统认更多的手势,并尝试用雷达、声音和ToF传感器让系统更强大。以后我会继续改进这个项目,大家记得关注哦。
在这篇文章中,我将带你完成整个旅程,从收集运动数据开始:从训练一个深度学习模型,到最后将其部署在边缘设备上,以实现实时手势识别。
NUI:模糊人与科技间的界限要了解自然用户界面(NUI),我们首先需要了解它们背后更广泛的领域——人机交互(HCI)。HCI 的核心是设计更自然、更直观的人机交互方式。正是有了 HCI,我们才有了触摸屏而非命令行,有了语音助手而非按钮,以及能响应手势、凝视甚至情绪的界面。
随着技术变得越来越智能,我们与技术互动的方式也随之改变。我们从输入命令,到轻触屏幕,再到挥一挥手或说话。这种向更加无缝和人性化的交互方式的转变,正是自然用户界面(NUI)大显身手的地方。这些系统感觉就像是我们自己行为的自然延伸,而不是我们学习的工具。如果您想了解更多关于自然用户界面的详情,这篇论文和这篇博客会很有帮助。
作者的图片
概览
简单来说,我实现了一个可以识别四种基本手势动作的系统:向上、向下、向左滑动 和 向右滑动,每个手势可以代表一个简单直观的命令,用于与数字设备互动。目标是创造一种 轻便、舒适、价格亲民 且 保护隐私安全 的无接触控制。整个系统运行在 Raspberry Pi 上,并设计为实时运行,使其适合日常使用。
在本文的其余部分,我将带您了解开发此系统的几个关键步骤,包括:
⚙️ 硬件搭建
📈 收集传感器数据
🧹 预处理
🧠 训练模型阶段
🚀 部署到边缘
👋💡 将系统与外部世界连接
在收集任何数据之前,我设置了以下简单的硬件配置:
- 一个Raspberry Pi 4 作为主处理器。
- 一个GY-85传感器模块,其中包括一个加速度计(ADXL345)、一个陀螺仪(ITG-3205)和一个磁传感器(HMC5883L)。
- I2C接口。
想法是构建一个不依赖摄像头的手势识别系统。通过仅使用运动传感器,我打算创建一个能在低功耗和资源受限设备(如 Raspberry Pi)上高效运行的设置。
这是我让所有事情正常运转的方法,比如:
- 启用树莓派上的I2C。
I2C 是 GY-85 传感器使用的通讯协议。要让它工作,请按照以下步骤操作:
+ 在 Raspberry Pi 上运行以下命令:
sudo raspi-config
进入 设置 → I2C → 开启。
重启 Raspberry Pi ,重启 Raspberry Pi , 保持英文原名。
2. 将GY-85连接到树莓派,如图所示:
这张图片是我拍的
一切顺利的话,你会看到传感器地址会在I2C网格中出现,如下命令所示:
# 执行i2cdetect命令来检测I2C设备
sudo i2cdetect -y 1
3. 安装所需的库在树莓派上,以便与GY-85模块通信。
在安装GY-85所需的读取包之前,本项目在树莓派上设置了一个虚拟环境(virtual environment)。这有助于将本项目与系统安装隔离,并防止将来安装其他包时可能出现的冲突。
要创建一个虚拟环境,请运行以下命令:
sudo apt-get install python3-venv
python3 -m venv <path>
source <path>/bin/activate
接下来,在虚拟环境中安装所需的库文件
pip3 install Adafruit-Blinka adafruit_adxl34x
运行这个命令来安装Adafruit-Blinka和adafruit_adxl34x库。
最初,我使用低级的
smbus
库与 Raspberry Pi 上的 I2C 传感器进行交互。不过,后来我切换到了 Adafruit 的高级 CircuitPython 库 (busio
和board
),这简化了 I2C 通信,对于 ADXL345 加速度计,我使用了 Adafruit 提供的官方驱动程序,它内部处理了所有的配置和数据转换,——无需进行低级寄存器访问。由于 Adafruit 没有提供官方驱动程序支持 ITG-3205 陀螺仪,我使用
busio.I2C
手动写入寄存器并读取原始数据。
读取GY-85模块的完整代码在IMU.py文件中。
2. 数据采集过程:这听起来没那么简单为了使项目简单可行,我从四个基本的手势开始:向上、向下、向左滑动、向右滑动。对于每个手势,我以50Hz的频率记录了5秒的加速度计和陀螺仪数据,由两个人各重复20次以确保数据的多样性。总共收集了20,000个数据点——每个动作有250个数据点,最终产生了80个标记的手势样本。
这听起来很简单,对吧? 但因为这是我第一次处理运动传感器信号,确定合适的数据收集策略结果成了最费时的部分。
细小的差异——比如动作的类型和速度、记录时长、传感器的方向,甚至两个人做同一个手势的方式不同——都对模型的准确性产生了很大的影响。我反复调整了这一步,最终发现数据采集的方法甚至比选择模型还要重要。🙂
最终的设置如下:每个手势都记录了 1秒。
关于 _预处理 的更多内容将在后续部分介绍。
收集四个手势的图片数据 — 作者提供图片
3. 预处理阶段:最具挑战性的环节我开始进行预处理(分词、过滤、归一化)并迅速进入模型训练阶段。但模型的表现不尽如人意。这时我才意识到,预处理不仅仅是走一些固定的流程,它还涉及到理解你的数据,并明白每个步骤背后的原因。
最初,我将数据分段成包含125个数据点并有50%重叠的窗口,以增加训练样本的数量并缩短输入序列,然后应用了卡尔曼滤波来减少噪声并提高信号质量,接着对数据进行了归一化。但在可视化预处理信号后,我注意到一个重要的点:这些手势是微妙的,快速的,并且是短暂事件,而不是连续的信号。它们在大约一秒钟内开始、达到顶峰并结束。所以,为了保留它们的时间形态,模型需要在每个窗口中看到完整的手势。
使用固定 5秒的录音长度 和 2.5秒重叠窗口 导致了两个主要问题:
- 每个手势的开始和结束经常包括了闲置的手势状态。
- 滑动窗口有时会错过手势的开始或结束部分。
这不仅增加了噪音,还通过将混合样本(部分是手势,部分不是手势)标记为同一类而混淆了模型。
所以我精简了数据,将每个手势精简到实际的动作时间窗口——一秒钟,并去除了空闲期。对于后续的数据收集,我记录了每个动作在一秒钟的持续时间里,并在预处理阶段完全去除了重叠,以确保每个样本完整捕捉了从开始到结束的手势。
左图显示了四个手势在5秒内记录的数据。中间的图使用125点窗口(50%重叠)可视化了第三个样本的分段。可以看出,第一个窗口(500–625)错过了手势的结尾部分,第二个窗口(562–687)仅仅捕获了手势的结束,而最后一个窗口(625–750)包含了手休息时的数据。
相对地,右边的图展示了当我为每个手势记录一秒时的情况——它在一个窗口内完整捕捉了整个手势,从而消除了滑动窗口的需要。
从过滤的角度来看,我想要更好地理解卡尔曼滤波的实际效果,所以我可视化了五个样本的滤波和未滤波信号(如下图所示)。如预期,滤波后的信号更平滑了,我最初认为这样会通过减少传感器噪声让模型更容易捕捉模式。
左:未滤,右:已滤
为了评估滤波器的效果,我比较了滤波数据和未滤波数据在CNN-LSTM架构下的模型性能。关于训练的更多细节会在训练部分中讨论。
尽管差异很小,但基于未过滤数据训练的模型表现更好,达到了85.94%的准确率,而过滤版本的准确率仅为81.25%。
过滤似乎对某些手势,如“向上”和“向右”,产生了更显著的影响,这可能是由于过度平滑导致移除了细微但重要的变化。
虽然基于过滤数据训练的模型在转换为TFLite后参数更少(大约轻了30KB,意味着模型更简单、更紧凑),但这种取舍并不值得。
通过这个实验,我意识到了,深度模型通常足够健壮,能够处理轻微的噪声,而无需进行激进的预处理。因此,为了减少不必要的计算并加快推理速度,我决定直接使用原始的未过滤传感器数据。
根据我在预处理过程中观察到的所有情况,我决定仅使用缩放作为训练前的信号调整。
原因很明显:这两个传感器,即加速度计和陀螺仪的数值范围非常不同——加速度计的读数通常在-19.6到+19.6 m/s²的范围内,而陀螺仪的输出则在-2000到+2000°/s的范围内。
没有规范化,模型可能会偏向一个传感器而忽略另一个。我尝试了两种方法:StandardScaler(去均值化并标准化方差)和MinMaxScaler(将所有特征调整到一个固定的范围内,如[-1, 1]),并选择了最适合该模型架构的那个。
为了比较这些缩放方法的效果,我将一部分训练信号数据进行了可视化:原始数据(左侧),使用StandardScaler进行缩放的数据(中间),以及使用MinMaxScaler进行缩放的数据(右侧)。
左:原始信号,中间:使用standardScaler标准化,右:使用MinMaxScaler标准化
经过MinMax缩放的数据很好地保留了原始形状——就像是原始信号的一个缩小版本。一开始,我认为这会帮助模型更好地学习,因为原始信号的比例得到了更好的保留。
但令人惊讶的是,用StandardScaler处理的数据训练的模型在准确率上几乎翻了一番。
这时我才发现:许多深度学习模型实际上期望输入数据服从均值为0、方差为1的正态分布,而StandardScaler正好满足这一要求。有关不同缩放方法的比较,您可以访问原始的scikit-learn文档这里。
所以,最终,考虑到问题的性质,信号预处理最终简化为仅仅是正确的缩放。
让我们结束这一部分,然后继续训练模型。在下一篇文章中,我将解析模型训练过程,以及它在树莓派上的实现过程,还有如何利用手势控制智能灯。
让我们这么说……事情很快就会变得更酷炫。😊
[MCP]:模型上下文协议
[LLM]:大语言模型
[RAG]:检索增强生成
[SSE]:服务器发送事件
共同學習,寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章