2024艾迈斯欧司朗竞赛 - 基于TMF8821传感器实现手势识别与键盘控制系统
该项目使用了TMF8821传感器、树莓派4B、ESP32-S3开发板,实现了手势识别与键盘控制的设计,它的主要功能为:基于ToF传感器测量数据识别手势挥动,并根据识别结果以Wi-Fi TCP的方式控制ESP32-S3,使其模拟特定的USB-HID键盘动作。。
标签
TMF8821
2024艾迈斯欧司朗竞赛
fyjh2023
更新2025-03-06
48

一、项目介绍

本系统通过TMF8821 dToF传感器捕捉三维手势信息,结合树莓派4B实现深度学习手势识别算法,最终通过Wi-Fi远程控制ESP32-S3开发板,实现可手势控制计算机的HID键盘设备。系统实现了非接触式菜单导航与设备控制功能,具有控制模式多样、高准确率等特点。

 

二、硬件系统组成

1. TMF8821 ToF传感器

TMF8821是一种直接飞行时间(dToF)传感器,采用单个模块化封装,带有相关的 VCSEL(垂直腔面发射激光器)。基于SPADTDC和直方图技术,该传感器可实现5000 mm的检测范围。由于它的镜头位于SPAD上,它支持 3x34x4 3x6 多区域输出数据以及宽广的、动态可调的视野。原始数据的所有处理都在片上进行,随后在I2C接口上提供距离信息和置信度值。

 

2. 树莓派4B

本项目采用1GB RAM的树莓派4B运行Ubuntu 20.04 LTS操作系统,负责传感器数据采集、模型推理和系统协调等工作。通过硬件I²C接口(引脚编号35)连接TMF8821,通过USB-UART模块连接串口屏,通过Wi-FiESP32-S3开发板建立TCP通讯。

 

3. ESP32-S3开发板

本项目使用ESP32-S3-DevKitM-1U开发板,搭载双核Xtensa LX7处理器,通过Wi-Fi接收控制指令并模拟USB HID设备。本项目使用Arduino进行ESP32-S3软件开发。

 

4. 串口屏

本项目使用TJC1612118_011N串口屏,屏幕大小1.8寸,分辨率160x128,工作电压4.5~6.0V。该屏幕通过CH340 USB转串口模块,与树莓派建立连接。


d8c040add7d59a0d256ff4b2a61438f.jpg

 

三、系统架构设计

1. 硬件架构

  • TMF8821 dToF传感器:通过I²C接口与树莓派4B通信,实时采集手势数据。
  • 树莓派4B:作为主控单元,负责数据处理和系统协调,支持SSH远程连接或直接连接显示器。
  • ESP32-S3:通过Wi-Fi接收树莓派的控制指令,并模拟USB-HID设备控制电脑。
  • 1.8寸串口屏:通过UART接口接收树莓派的指令,显示系统状态和菜单。

2. 软件架构

  • 传感器驱动:负责TMF8821传感器的初始化和数据采集。
  • 数据处理模块:对传感器数据进行预处理和手势特征提取。
  • 控制指令模块:生成并发送控制指令至ESP32-S3和串口屏。
  • 用户界面模块:通过串口屏提供用户交互界面,显示系统状态和菜单。


3.数据处理流程

  • 树莓派通过I²C接口读取TMF8821传感器的原始数据。
  • 数据处理后,树莓派通过UART更新串口屏显示内容,并通过Wi-Fi发送控制指令至ESP32-S3
  • ESP32-S3接收指令后,通过USB-HID接口控制电脑,模拟键盘输入。


4.系统特点

  • 模块化设计:各功能模块独立,便于维护和升级。
  • 灵活性:树莓派支持SSH远程连接和本地显示器操作,适应不同使用场景。

 

四、软件实现细节

这是运行于树莓派4B上的手势识别与系统控制等核心代码情况

注:相关代码的执行方式与流程请看附录

 

1. Driver模块

  • 用途TMF8821传感器底层驱动
  • 功能
    • 通过I²C接口读取传感器原始数据
    • 解析每个检测区域的深度信息和置信度值
    • 实现数据校验和错误处理
  • 输出:经过校验的多区域ToF数据,通过pipe传输至Compute.py
  • 部分代码:
    • 驱动程序使用C++编程,调用i2ctransfer工具收发I²C数据
int get_result_tof_sensor(void){
std::string cmd;
std::stringstream ss;
std::vector<uint8_t> byteArray;
std::string ret_val;
std::string expected_ret_val="0x00 0x00 0xff\n";

while(1){
while(1){
delay_ms(std::chrono::milliseconds(1));
cmd = "i2ctransfer -y -f 1 w1@0x41 0xe1 r1";
ret_val=executeI2CTransfer(cmd);
if(ret_val == "0x23\n"){
delay_ms(std::chrono::milliseconds(1));
cmd = "i2ctransfer -y -f 1 w2@0x41 0xe1 0x23";
executeI2CTransfer(cmd);
delay_ms(std::chrono::milliseconds(2));
break;
}
else if(ret_val == "0x03\n"){
delay_ms(std::chrono::milliseconds(1));
cmd = "i2ctransfer -y -f 1 w2@0x41 0xe1 0x03";
executeI2CTransfer(cmd);
delay_ms(std::chrono::milliseconds(1));
break;
}
}
delay_ms(std::chrono::milliseconds(1));
cmd = "i2ctransfer -y -f 1 w1@0x41 0x20 r132";
ret_val = executeI2CTransfer(cmd);
delay_ms(std::chrono::milliseconds(1));
byteArray = hexStringToUint8Array(ret_val);
printResults(byteArray.data());
}
}

 

2. Compute.py模块

  • 用途:手势识别推理引擎
  • 功能
    • 接收Driver模块的ToF数据
    • 执行数据预处理(归一化、FFT变换)
    • 运行CNN模型进行手势分类
    • 生成推理过程调试信息
  • 输出:手势类别及每个类别的可能性,通过pipe传输至Execute.py
  • CNN模型结构

  • CNN输入数据的预处理:
    • 收集24条相邻的TMF8821原始测量数据,每个数据包含18个测距结果及其置信度
    • 对这些数据在通道维度上执行FFT,拼接得到的实部、虚部,完成输入数据预处理
    • 适当降低手势识别的频率,降低误判的概率,并且在浏览短视频时,上下翻页的动作是低频动作
  • CNN代码
    • 基于pytorch编程
class SimpleCNN(nn.Module):
def __init__(self):
super(SimpleCNN, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=(3, 3), stride=1, padding=1)
self.bn1 = nn.BatchNorm2d(16)
self.pool1 = nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))

self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=(3, 3), stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(32)
self.pool2 = nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2))

self.conv3 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=(3, 3), stride=1, padding=1)
self.bn3 = nn.BatchNorm2d(32)

self.fc1 = nn.Linear(32 * 18 * 6, 32 * 4)
self.fc2 = nn.Linear(32 * 4, 5) #
self.act = nn.LeakyReLU(0.1)

def forward(self, x):
x = self.pool1(self.act(self.bn1(self.conv1(x))))
x = self.pool2(self.act(self.bn2(self.conv2(x))))
x = self.act(self.bn3(self.conv3(x)))
x = x.view(-1, 32 * 18 * 6) # Flatten
x = self.fc1(x)
x = self.fc2(x)
return x
  • CNN训练:
    • 请参考提交代码中的train.py,默认训练500轮
    • 重要说明:我采集的数据集不大,所以训练的模型对手势移动的速度和到传感器的距离有一定要求,泛化性有较大提升空间。如需在新环境部署,可以使用我设计的交互式数据采集程序重新采集数据集并训练。


3. Execute.py模块

  • 用途:系统控制与任务执行
  • 功能
    • 解析手势识别结果
    • 更新1.3寸串口屏显示内容
    • 生成ESP32-S3控制指令
    • 管理系统状态机
  • 输出串口屏显示更新,ESP32-S3控制指令
  • 系统状态机与串口屏显示页面:
    • 包含两种可选择的操作模式(模式1、2),后续可添加更多模式

    • 利用串口屏厂家的USART HMI软件设计各个页面,与状态机对应

  • 部分树莓派4B代码
    • 通过/dev/ttyUSB0端口与串口屏通讯
    • 通过TCP与ESP32-S3通讯
 def read_from_pipe(path):
ser=serial.Serial(port="/dev/ttyUSB0",baudrate=9600,timeout=5)
screen_display = ScreenDisplay(ser)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((SERVER_HOST, SERVER_PORT))
server_socket.listen(5)
print(f"Server listening on {SERVER_HOST}:{SERVER_PORT}")
try:
while True:
client_socket, client_address = server_socket.accept()
print(f"Connection from {client_address}")
try:
while True:
try:
with open(path, 'r') as fifo:
print(f"Reading from named pipe at {path}")
while True:
line = fifo.readline()
if not line:
time.sleep(0.01)
continue
break
except FileNotFoundError:
print(f"Named pipe at {path} does not exist.")
except PermissionError:
print(f"Permission denied when trying to read from named pipe at {path}.")
except Exception as e:
print(f"An error occurred: {e}")

print(line)
line = line.strip()
ret = screen_display.handle_command(line)

if ret is not None:
data = ret
client_socket.sendall(data.encode('utf-8'))
data = client_socket.recv(1024).decode('utf-8')
if not data:
break
print(f"Received: {data.strip()}")
time.sleep(1)
finally:
client_socket.close()
print(f"Closed connection from {client_address}")
except KeyboardInterrupt:
print("Exiting due to keyboard interrupt.")
finally:
server_socket.close()

 

 4. Arduino程序实现

  • 用途ESP32-S3控制程序,实现HID设备模拟功能
  • 功能
    • 建立Wi-Fi连接,接收树莓派控制指令
    • 模拟键盘和鼠标操作
    • 实现基础的人机交互功能
  • ESP32-S3的指令解析与执行代码
void loop()
{
Serial.println("尝试访问服务器");
if (client.connect(serverIP, serverPort)) //尝试访问目标地址
{
Serial.println("访问成功");
while (client.connected() || client.available()) //如果已连接或有收到的未读取的数据
{
if (client.available()) //如果有数据可读取
{
String line = client.readStringUntil('\n'); //读取数据到换行符
Serial.print("读取到数据:");
Serial.println(line);
String answer = "OK!";
if (line == "b0") {
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('c');
delay(100);
Keyboard.releaseAll();
}
else if (line == "b1") {
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press('v');
delay(100);
Keyboard.releaseAll();
}
else if (line == "b2") {
Mouse.move(0, 0, 2, 0);
}
else if (line == "b3") {
Mouse.move(0, 0, -2, 0);
}
client.write(line.c_str());
}
}
Serial.println("关闭当前连接");
client.stop(); //关闭客户端
}
else
{
Serial.println("访问失败");
client.stop(); //关闭客户端
}
delay(5000);
}

 

5. DataCollect.py模块

  • 用途:实现TMF8821传感器数据的实时采集与处理
  • 功能:
    • 通过FIFO管道读取传感器原始数据
    • 数据解析与格式转换
    • 交互式数据采集控制
    • 数据存储与分类管理
  • 树莓派4B数据收集部分代码
 def collect_data(self, category):
async def collect_data_inner():
async for result_dict in self.processor.get_processed_data():
if category not in self.current_categories:
break
self.collected_data[category].append(result_dict)
print(result_dict)
await asyncio.sleep(0.01) # Collect data every 0.1 seconds

return collect_data_inner()
  • 交互式界面
    • 点击按钮后,收集该动作的数据,再次点击停止收集
    • 数据文件以动作名+编号的方式存储


五、功能演示

涉及手势动作,不便截图,具体请看演示视频,包括:手势控制上下翻页、手势控制复制粘贴、交互式数据采集等内容。

 

六、遇到的难题及解决方法

  • TMF8821传感器驱动:最初计划采用官方提供的Python驱动程序进行开发,但因软件配置问题未能成功调试。因此,我依据Application NotesAN001015)自行编写了C++驱动程序。由于对C++环境下的I2C读写操作不够熟悉,我选择调用终端工具i2ctransfer来实现I2C的读写功能。此外,传感器固件直接采用了官方示例中的代码。
  • CNN网络架构设计:在调整参数的过程中逐步优化网络性能……
  • 多个程序间的通信:本项目将底层驱动、推理运算以及指令下发的功能分布在三个Python文件中执行,以便于项目的开发与管理。然而,这种方式使得直接使用队列等数据结构在多个Python文件之间进行通信变得困难。为了解决这一问题,我们通过管道以文件读写的形式实现了这些文件之间的通信,并将管道文件存储在ramdisk中以加快读写速度。
  • 屏幕选型:考虑到使用I2CSPI协议的屏幕需要额外编写驱动程序并设计可视化界面,这将大大增加工作量。相比之下,串口屏只需通过串口发送控制指令即可完成界面切换,无需编程设计可视化界面,从而显著减少了工程量。
  • USB-HID控制:原计划是实现蓝牙键盘和鼠标的控制功能,但在使用Arduino库时遇到了编译问题。因此,我们改为实现USB有线控制方式。
  • 程序编写:本项目涉及较多模块,编程工作量比之前几次活动大了许多。因此我使用大模型辅助编程。我负责提供所有的编程思路,功能实现细节由通义千问大模型完成,最后由我进行检查与调试。

 

七、项目总结

本项目成功开发了一个基于TMF8821 dToF传感器的手势识别系统,实现了非接触式手势识别与控制功能,支持四种基本手势(接近、远离、上挥、下挥)的识别。同时,我们还实现了通过手势控制串口屏页面切换(详情请参见演示视频),以及模拟HID设备(如键盘和鼠标)并通过手势控制上下翻页或执行复制粘贴操作。在这个过程中,我不仅深入学习了TMF8821传感器的I2C驱动编写,而且借助详尽准确的Application Notes,获得了良好的编写体验和丰富的知识积累。

  



附录:

0.必要的准备

树莓派必须安装i2ctransfer工具和pytorch环境。我使用的树莓派操作系统是ubuntu20.04。

笔记本电脑需要安装MobaXterm,接收DataCollect.py通过X11转发的可视化窗口。其他时候可以使用VSCode等SSH远程开发工具,或者连接HDMI显示器直接在树莓派本地运行调试程序。


1.管道文件存放

创建ramdisk,大小64MB即可,存放pipe文件。通过ramdisk降低文件读写延迟。


2.编译驱动

编译冷启动驱动:g++ -o Driver tmf882x_image.cpp tmf882x_calib.cpp my_i2c_v3.cpp

编译热启动驱动:g++ -o Driver_simple tmf882x_image.cpp tmf882x_calib.cpp my_i2c_v3.cpp 请修改cpp代码,只保留dToF传感器测量部分的代码


3.训练数据收集

在树莓派的项目文件夹执行3行命令:

mkdir dataset

./Driver &

python DataCollect.py


4.启动手势识别系统

运行主程序:(./Driver &) && (OMP_NUM_THREADS=1 python3 Compute.py &) && (python3 Execute.py)

停止主程序的步骤(a~e):

    1. ctrl-c退出
    2. 不管屏幕打印的内容,直接输入killall ./Driver并回车
    3. 执行 ps -ef | grep python 然后kill掉python3 Compute.py
    4. 执行 sudo fuser -k -n tcp 50037 关闭TCP连接
    5. 稍等片刻后,可重新运行主程序


 5.如果在启动手势识别系统程序后遇到问题,请结束Driver、Compute.py、Execute.py并重新按照操作步骤尝试。

 


附件下载
submit_fyjh2023.zip
树莓派4B的python代码、传感器C++源码和驱动、ESP32-S3 Arduino代码,还有数据集、python训练代码、模型权重(仅供参考)
tof1.HMI
串口屏
团队介绍
我负责核心算法的思路、系统整体框架结构,通义千问根据思路提供代码细节,deepseek辅助写作~~~
团队成员
fyjh2023
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号