【dToF传感器光电设计竞赛】树莓派5+TMF8821实现角度测量和手势识别
该项目使用了TMF8821和Python,实现了角度测量和手势识别的设计,它的主要功能为:在树莓派5平台上,通过TMF8821来探测数据,进行角度测量和手势识别。
标签
Python
dToF
艾迈斯欧司朗
TMF8821
HonestQiao
更新2025-03-06
82

项目介绍:

这个项目使用树莓派5做为主控板,使用TMF8821模块做为探测设备,完成了任务一 (角度测算)和任务二(基础手势识别)。


设计思路:

要实现本项目所需要的功能,要完成以下的工作:

  • 了解TMF8821的功能
  • 了解TMF8821的用法
  • 在树莓派5上,与TMF8821模块通讯
  • 通过从TMF8821获取的数据,进行计算
  • 完成角度测算和手势识别



硬件介绍:

要实现本项目的功能,用到了下面的两款设备:

74eca75aea2f77338faad4147ec5d1fe.jpg

分别为:

  • 树莓派5:左边的设备,树莓派5 4G版本,加装了SSD和风扇
  • 硬禾TMF8821模块:右边的设备


实现步骤:

1. 了解TMF8821模块

通过活动页面https://www.eetree.cn/activity/115,可以获取到TMF8821模块的各项资料。

其中最主要的直播讲解,以及1.TMF882X 传感器数据手册_TMF882X_DS000693_8-00.pdf。

从其中,可以了解到TMF882X的用途,以及具体的技术细节,使用方式。

image.png



2. 使用TMF8821

通过官方提供的各种不同环境的代码,结合数据手册,能够详细了解到TMF8821的具体用法。

首先要和TMF8821通信,需要使用I2C接口,通过I2C接口控制TMF8821完成各种设定,以及进行测算返回数据。

基本的使用过程如下:

  • 打开I2C接口,检测设备是否正常连接
  • 获取app_id,可以用于识别实在启动阶段,还是已经加载firmware数据
  • 加载firmware
  • 设置spad_map_id,调用指定的配置
  • 进行校准
  • 启动测量
  • 获取测量数据
  • 通过计算数据,完成实际的任务


其中,app_id为0x03表示测量程序运行,也就是已经加载了firmware,0x80表示bootloader运行,需要加载firmware

spad_map_id用于加载预定的配置,从手册可以得知预定义的配置:

image.png

从手册可以得知,TMF8821支持3x3、4x4、3x6等多种配置。



另外,在校准的时候,需要先加上一个透明盖板,且上方40cm内没有物体遮挡,否则校准失败。

我放了一个打开的透明盒子上去进行校准:

45d76c44cc1833ca58c06568fd626c0c.jpg

校准完成后,拿走即可。





3. 树莓派5连接TMF8821模块

在树莓派5上,使用的是标准40Pin接口:




其中,默认的I2C1接口为GPIO02/SDA、GPIO03/SCL,可以用于与TMF8821模块对接:


image.png

要在树莓派5上启用I2C1,可以使用 sudo raspi-config 命令进行配置:

image.png

配置完成后,使用 sudo vim /boot/firmware/config.txt 配置I2C连接速度:

image.png

配置完成后,重启树莓派生效。


正确连接,并重启设备后,使用 sudo i2cdetect -y 1,可以用于检测是否连接好了:

image.png

其中,0x41即为TMF8821的I2C地址;如果没有出现,则说明链接不正确。



在树莓派5上,使用SMBUS2来完成I2C通讯,具体库为:https://pypi.org/project/smbus2/

参考下面的代码,可以从TMF8821的寄存器读取数据:

image.png

具体的寄存器说明,可以参考数据手册:

image.png

上面代码中读取的0x00,对应的就是APPID,具体功能如下:

image.png


从官方提供的文档、代码等,可以了解寄存器数据的具体用途和使用方法。


4. 任务一:角度测算

完成该任务,主逻辑部分代码如下:

from smbus2 import SMBus
from tmf8821 import TMF8821
import time
import sys


import numpy as np
import math


from config_calibrate import factory_calibrate


# 计算角度
def calculate_angle(depth_data):
mean_depth = np.mean(depth_data)
depth_deviation = depth_data - mean_depth
max_deviation = np.max(depth_deviation)
min_deviation = np.min(depth_deviation)
theta_rad = math.atan((max_deviation - min_deviation) / mean_depth)
theta_deg = math.degrees(theta_rad)
return theta_deg


with SMBus(1) as bus:
device = TMF8821(bus=bus)
print("app_id:", hex(device.app_id))
print("mode:", hex(device.mode))
print("enable->")
device.enable()
print("mode:", hex(device.mode))


spad_map_id = 1
print("spad_map->", spad_map_id)
device.spad_map = spad_map_id
time.sleep(0.5)
print("spad_map<-", device.spad_map)


print("calibrate->")
if False and spad_map_id in factory_calibrate:
device.write_calibration(factory_calibrate[spad_map_id])
time.sleep(0.1)
else:
times = 10
while times>0:
data = device.calibrate()
if device.calibration_ok:
# 校准成功
data_hex = ["\\x%02x" % i for i in data]
print("校准数据: b'%s'" % "".join(data_hex))
break
times -=1
time.sleep(1)


if not device.calibration_ok:
print("校准失败")
sys.exit()


print("measure->")


while True:
try:
times = 5
data_list = []


while times > 0:
measurement = device.measure(spad_map_id)
if True:
# print("result:")
for spad in measurement:
# print(["%d:%d" % (p["confidence"],p["distance"]) for p in spad])
pass
# print("")
data_list.append([[p["distance"] for p in spad] for spad in measurement])
times -= 1
time.sleep(0.05)
# continue


# 对每次采集的数据计算角度
angles = []
for depth_data in data_list:
angle = calculate_angle(np.array(depth_data))
angles.append(angle)


# 计算平均角度
average_angle = np.mean(angles)


print(f"每次计算的角度: {angles}")
print(f"平均角度: {average_angle:.2f}°")


except Exception as e:
print(e)
break

在上述代码中,tmf8821.py封装了在树莓派5下面与TMF8821通过I2C打交道的接口。

代码的主要逻辑,就是先使能设备,使能的时候会自动根据app_id自动加载firmware。

然后进行校准,并检测校准是否成功。校准的数据,可以保存下来,用于后续直接写入校准数据,而无需重新校准。

在测量部分,多次获取测量的数据,然后使用numpy进行角度计算。

image.png

image.png

当没有防止物体在TMF8821探测窗口上方时,测量的是天花板,基本水平。


当放置一个手机上去时,结果如下:

image.png


5. 任务二、手势识别

在任务一的基础上,获取到了数据,可以进一步,进行手势的识别。

这里仅做了基本的手势识别:接近、离开、挥动

主要逻辑代码如下:

from smbus2 import SMBus
from tmf8821 import TMF8821
import time


import numpy as np
import math


from config_calibrate import factory_calibrate


# 计算每次数据的平均深度
def calculate_average_depth(depth_data):
return np.mean(depth_data)


# 计算左右差异
def calculate_left_right_difference(depth_data):
left = np.mean(depth_data[:, 0]) # 左列
right = np.mean(depth_data[:, 2]) # 右列
return right - left


with SMBus(1) as bus:
device = TMF8821(bus=bus)
print("app_id:", hex(device.app_id))
print("mode:", hex(device.mode))
print("enable->")
device.enable()
print("mode:", hex(device.mode))


spad_map_id = 7
print("spad_map->", spad_map_id)
device.spad_map = spad_map_id
time.sleep(0.1)
print("spad_map<-", device.spad_map)


print("calibrate->")
if False and spad_map_id in factory_calibrate:
device.write_calibration(factory_calibrate[spad_map_id])
time.sleep(0.1)
else:
times = 10
while times>0:
data = device.calibrate()
if device.calibration_ok:
# 校准成功
data_hex = ["\\x%02x" % i for i in data]
print("校准数据: b'%s'" % "".join(data_hex))
break
times -=1
time.sleep(1)


if not device.calibration_ok:
print("校准失败")
sys.exit()


print("measure->")


data_list = []
while True:
try:
measurement = device.measure(spad_map_id)
print("result:")
for spad in measurement:
print(["%d:%d" % (p["confidence"],p["distance"]) for p in spad])
print("")


data_list.append([[p["distance"] for p in spad] for spad in measurement])
# print("")
time.sleep(0.1)


if len(data_list)>2:
data_list.pop(0)
if len(data_list)<2:
continue


# 初始化变量
previous_average = None
previous_difference = None
trend = []
left_right_trend = []
action_detected = False


# 设置阈值
DEPTH_THRESHOLD = 150 # 深度变化阈值
DIFFERENCE_THRESHOLD = 150 # 左右差异变化阈值


# 遍历每次采集的数据
for depth_data in data_list:
current_average = calculate_average_depth(np.array(depth_data))
current_difference = calculate_left_right_difference(np.array(depth_data))


if previous_average is not None:
# 判断深度变化趋势
difference_change = abs(current_average - previous_average)
if difference_change > DEPTH_THRESHOLD:
if current_average < previous_average:
trend.append("接近")
elif current_average > previous_average:
trend.append("远离")
else:
trend.append("无变化")


# 判断左右差异变化
difference_change = abs(current_difference - previous_difference)
if difference_change > DIFFERENCE_THRESHOLD:
if current_difference < previous_difference:
left_right_trend.append("左挥动")
else:
left_right_trend.append("右挥动")
action_detected = True
else:
left_right_trend.append("无左右动作")
action_detected = False


previous_average = current_average
previous_difference = current_difference


# 输出结果
for i, (t, lrt) in enumerate(zip(trend, left_right_trend)):
print(f"第 {i+1} 次与第 {i+2} 次比较: {t}, {lrt}")
if "挥动" in lrt and action_detected:
print("挥动检测: 挥动超过阈值")
elif "接近" in t:
print("动作检测: 接近")
elif "远离" in t:
print("动作检测: 远离")
else:
print("动作检测: 无动作")


except Exception as e:
print(e)
break


上述代码中,从开始到测量的部分,与任务一的代码基本相同,主要连续累计数据,然后和上一次进行对比,从而检测手势的状态。


当接近时:

image.png

当原理时:

image.png

当挥动时:

image.png


总结:

这次的TMF8821模块,因为时间原因,只是做了基础的摸索使用。

通过的基础的使用,已经能够充分了解到这款模块的强大功能。

后续会安排时间,进一步研究学习,充分了解和挖掘这款模块的功能。



附件下载
代码.zip
团队介绍
一个狂热的开源爱好者和传播者,同时也是一名极客爱好者,长期关注嵌入式发展和少儿创客教育,既擅长互联网系统架构设计与研发,又拥有丰富的嵌入式研发经验。为人精力充沛,古道热肠,圈内人称乔大妈、乔帮主。
团队成员
HonestQiao
狂热的开源爱好者和传播者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号