基于手势识别的厨房无接触电子食谱助手
一、项目背景
在日常做饭时,我发现电子菜谱和短视频虽然使用方便,但真正操作起来,经常需要一边处理食材、一边查看屏幕和切换步骤。尤其是在双手沾水、油污或食材后,再去触碰手机或屏幕不仅不方便,也很容易打断烹饪节奏。基于这个实际体验,我尝试设计了一款厨房无接触电子食谱助手,以行空板 K10 为主控,通过手势识别传感器完成菜谱选择和步骤切换,并结合语音合成模块播报当前烹饪内容;同时利用 K10 连接的 LED 灯,在手势识别成功后提供直观反馈。整个项目主要希望通过手势、语音和界面交互的结合,让电子菜谱在真实厨房环境中使用起来更加自然方便。
二、项目目标
完成一个能够通过手势选择菜谱、查看食材、逐步制作并获得语音和计时提示的厨房电子菜谱助手。以下视频是整个项目的体验过程。
三、器材清单

四、 准备工作
1.设置菜单翻页专用手势
在正式编写程序之前·,首先我们要先通过配套的上位机软件来快速体验传感器的核心功能。AI视觉手势/姿势识别传感器的更多详细使用方法见下面的网址:
wiki.dfrobot.com.cn/SKU_SNE0670_Gravity_AI视觉手势和姿势识别传感器
根据下载的AI视觉手势/姿态传感器自带的官方软件,解压后双击.exe文件。
打开之后电脑通过USB线连接传感器,可以看到下图界面,连接端口后点击打开即可显示传感器摄像头画面,打开软件后可以先通过右上角修改显示语言为中文。
上位机默认为自学习手势识别模式。如下图所示,任意摆出一个手势,传感器将会进行识别,输出对应姿势的ID和置信度,并且输出该手势的21个关键点实时数据。点击学习按钮即可对手势进行学习并记录对应手势ID。本项目自学习两种手势,分别对应ID1和ID2,用于电子菜谱助手的翻页功能。
接下来切换模式为固定手势识别模式。如下图所示,根据右侧图片中的手势类型,摆出任意一个手势,传感器会识别对应手势的ID和名称。
另外该传感器还可以切换姿势识别,本项目由于没有进行相关姿势识别的学习,故不对其进行介绍。按照上面的步骤学习手势后,即可开始编写代码,实现一个基于手势识别的厨房无接触电子菜谱助手。
2.录入菜谱相关图片
在正式编写程序前需要将提前准备好的图片资源上传至TF卡中,之后将TF卡插入行空板K10即可。共使用 20 张图片。每张图片为 140×128 像素的 PNG。注意:插入行空板K10的TF卡建议最大16GB,推荐使用DF商城在售的正品闪迪卡,使用未知品牌的内存卡,易出现读写卡失败、程序崩溃等问题。
下方表格内容为本项目上传的对应菜谱的图片资源文件名。
| 菜谱 | 文件名前缀 | 步骤编号 |
| 西红柿炒鸡蛋 | recipe_tomato_ | 1~5 |
| 蛋炒饭 | recipe_fried_rice_ | 1~5 |
| 可乐鸡翅 | recipe_cola_wings_ | 1~5 |
| 青椒炒肉 | recipe_pepper_pork_ | 1~5 |
3.下载编程相关库
(1)项目使用 Arduino C 完成,首先需要下载安装Arduino IDE(https://www.arduino.cc/en/software/)。
下载完成后,按提示步骤安装Arduino IDE,配置行空板K10准备开始制作项目。具体如何使用可以参考下方链接:https://www.unihiker.com.cn/wiki/k10/ArduinoIDE_prepare
(2)下载资源附件,找到语音合成和AI视觉姿势/手势传感器的依赖库文件夹。DFRobot_HumanPose和 DFRobot_SpeechSynthesis_M。
五、项目搭建
1. 流程分析
系统由手势传感器完成本地手势识别,K10读取识别结果并转换为内部操作编号,再由页面状态机结合当前页面确定具体功能。
项目要实现的核心功能如下图:

系统执行过程:
首页支持四道菜谱循环选择,并根据选中位置自动调整卡片显示区域;进入食材页后显示对应食材信息,再通过手势进入制作步骤。制作过程中可通过左右摆切换前后步骤,也可随时握拳返回首页。界面显示、语音播报和计时均随当前菜谱及步骤同步更新,同时在手势识别成功后通过数字LED闪烁进行提示。
本项目使用的手势及功能对应表如下:
具体手势 | 系统反应 | 识别标识ID |
|---|---|---|
左摆(自学习手势) | 上一个/步 | 1 |
右摆(自学习手势) | 下一个/步 | 2 |
OK(固定手势) | 确认选择 | 8 |
握拳(固定手势) | 返回首页 | 0 |
2. 硬件连接
将AI视觉手势/姿势传感器接在行空板K10的I²C接口,LED灯接在P0口,行空板插入扩展板,语音合成模块连接在扩展板上,上传程序时K10通过USB线连接电脑。
说明:当程序通过电脑编程上传至K10后本项目实现也可直接采用充电宝进行供电。
设备连接成功后,进行程序的完整编写。
3. 编写程序
项目实现的核心逻辑
程序启动后首先进行初始化,然后进入菜谱首页。运行过程中,主循环持续处理LED状态、语音播放状态、手势识别和步骤计时,根据当前页面及识别到的手势决定需要执行的操作。
识别手势的核心逻辑:
- 程序在
eHand和eGesture两种模型之间轮换。模型每次切换成功后,传感器内部推理结果可能仍然属于上一个模型,或者图像数据尚未稳定。因此程序等待200 ms后才开始读取结果。 - 等待模型稳定后,程序在当前模型下读取手势结果约650 ms。如果在这个窗口中没有形成有效手势,并且系统没有处于锁定状态,就切换到另一个模型继续识别。
- 程序第一次识别到有效手势时,只把它记录为候选手势,并通过
candidateSince记录出现时间,不立即执行操作。只有连续识别为同一个手势达到250 ms,程序才确认该手势有效。 - 手势稳定确认后,程序先将
gestureLocked设置为true,再调用handleGesture()执行页面操作。数字LED同时点亮200 ms,用于提示用户手势已经被系统接受。 - 手势执行后,系统进入锁定状态。只有传感器连续约600 ms没有检测到手,程序才解除锁定,允许识别下一次操作。
项目文件结构
本项目的资源文件结构如下,其中RecipeAssistant_K10.ino 是项目主程序,需要修改菜谱名称、食材、制作步骤、计时时长、界面文字或颜色时,主要修改这个文件。
RecipeAssistant_K10/
│
├─ data/ 【图片及调试资源】
│ ├─ recipe_images_preview/ 【步骤图片,可按规则替换】
│ │ ├─ recipe_tomato_1.png ~ recipe_tomato_5.png
│ │ ├─ recipe_fried_rice_1.png ~ recipe_fried_rice_5.png
│ │ ├─ recipe_cola_wings_1.png ~ recipe_cola_wings_5.png
│ │ └─ recipe_pepper_pork_1.png ~ recipe_pepper_pork_5.png
│ │
│ └─ 白屏排查前代码.ino.txt 【历史备份,不参与编译】
│
└─ RecipeAssistant_K10_c/ 【Arduino程序文件夹】
│
├─ RecipeAssistant_K10.ino 【项目主程序,可修改】
│
└─ src/ 【本地依赖库,通常不要修改】
│
├─ unihiker_k10_local/ 【行空板K10适配库】
│ ├─ unihiker_k10.h
│ ├─ unihiker_k10.cpp
│ ├─ GT30L24A3W.h
│ └─ DFRobot_GT30L24A3W.cpp
│
├─ DFRobot_HumanPose/ 【SEN0670手势传感器库】
│ ├─ DFRobot_HumanPose.h
│ ├─ DFRobot_HumanPose.cpp
│ ├─ Result.h
│ ├─ Result.cpp
│ ├─ library.properties
│ └─ LICENSE
│
└─ DFRobot_SpeechSynthesis_M/ 【语音模块参考库】
├─ DFRobot_SpeechSynthesis_M.h
└─ DFRobot_SpeechSynthesis_M.cpp部分关键代码如下:
(1)初始化设备与界面
setup() 启动串口和 K10,配置 LED 输出,再创建横屏画布并初始化 TF 卡。先建立背景对象、再创建画布,才能获得有效显示对象。屏幕方向为 1,对应 320×240 像素。以下是相关代码:
void setup() {
Serial.begin(115200);
delay(500);
k10.begin();
digitalWrite(GESTURE_LED_PIN, GESTURE_LED_ON == HIGH ? LOW : HIGH);
pinMode(GESTURE_LED_PIN, OUTPUT);
k10.initScreen(SCREEN_DIR);
k10.setScreenBackground(COLOR_BG);
// 当前 K10 库需要先建立背景对象,Canvas 才能取得有效父对象。
k10.initBgCamerImage();
k10.creatCanvas();
k10.setBgCamerImage(false);
// 使用用户提供的 MindPlus TF 卡初始化接口,只在启动时调用一次。
Serial.println(F("[TF] Initializing SD file system..."));
k10.initSDFile();
Serial.println(F("[TF] SD initialization returned."));随后初始化 V1 语音模块,再连接手势传感器并选择 eHand 模型。两种设备共用 Wire,但地址与访问时钟不同。
const uint8_t SENSOR_ADDR = 0x3A;
const uint8_t SPEECH_ADDR = 0x40;
const uint32_t GESTURE_I2C_CLOCK = 400000;
const uint32_t SPEECH_I2C_CLOCK = 100000;(2)映射手势编号并轮换识别模型
自学习模型 eHand 的 ID1、ID2 对应左摆和右摆;固定模型 eGesture 的 ID8、ID0 对应 OK 和握拳。mapGestureResult() 结合当前模型转换原始编号:内部 GESTURE_OK=3、GESTURE_FIST=4 是操作编号,无需额外学习 ID3 或 ID4。自学习模型的 ID0 不触发返回。以下是相关代码:
uint8_t mapGestureResult(uint8_t rawId) {
if (readingLearnedModel) {
if (rawId == 1) return GESTURE_LEFT;
if (rawId == 2) return GESTURE_RIGHT;
} else {
if (rawId == 8) return GESTURE_OK;
if (rawId == 0) return GESTURE_FIST;
}
return GESTURE_NONE;
}两个模型轮换工作。切换成功后等待 200 ms,再进入约 650 ms 的采样窗口;失败时暂停读取,等待 1000 ms 后重试同一目标模型。切换时清除旧结果与候选手势,避免跨模型混用编号。以下是相关代码:
void switchGestureModel() {
const bool nextLearned = !readingLearnedModel;
restoreGestureI2CClock();
const DFRobot_HumanPose::eCmdCode_t status = sensor.setModelType(
nextLearned ? DFRobot_HumanPose::eHand : DFRobot_HumanPose::eGesture);
if (status != DFRobot_HumanPose::eOK) {
// 返回失败时无法确定设备端模型,不读取结果,等待重试同一目标。
modelSwitchFailed = true;
modelRetryAt = millis() + MODEL_RETRY_MS;
Serial.println(F("[Gesture] model switch failed; retrying."));
return;
}
readingLearnedModel = nextLearned;
modelSwitchFailed = false;
while (sensor.availableResult()) sensor.popResult();
candidateGesture = GESTURE_NONE;
candidateSince = millis();
modelReadyAt = millis() + MODEL_SETTLE_MS;
modelWindowSince = modelReadyAt;
}(3)稳定识别并防止重复触发
首先读取当前模型的结果,映射后选择有效操作中置信度最高的一项;如果通信失败会清空候选,但不当作手已移开。processGestureFrame() 要求同一候选持续至少 250 ms 才执行操作,随后锁定;手移开约 600 ms 后才允许再次触发。
以下是相关代码:
void processGestureFrame(uint8_t id) {
unsigned long now = millis();
if (id == GESTURE_NONE) {
if (now - lastGestureSeen >= 600) {
gestureLocked = false;
candidateGesture = GESTURE_NONE;
}
return;
}
lastGestureSeen = now;
if (gestureLocked) return;
if (id != candidateGesture) {
candidateGesture = id;
candidateSince = now;
return;
}
if (now - candidateSince >= 250) {
gestureLocked = true;
// 稳定识别250ms后只闪一次;持续保持同一手势不会反复闪灯。
// 即使该手势在当前页没有动作,也反馈“已识别”。
flashGestureLed();
handleGesture(id);
}
}4. 运行程序
完成接线、手势学习和 TF 图片拷贝后,将完整程序编译上传至 K10。上电后等待语音模块与传感器初始化,出现“请选择菜谱”后开始操作。在Arduino IDE软件中打开文件运行即可查看程序运行效果。
程序运行后,具体实现效果如下图 :
注意:详细程序可查看附件。
调试与注意事项
- 传感器连接失败:检查传感器是否处于 I²C 模式,确认地址为 0x3A、SDA/SCL 接线正确、设备共地且供电正常;调整拨码开关后需重新上电。
- 手势识别不准确或无响应:确认手势ID是否正确,可先通过上位机验证识别结果,并调整手型朝向、光照、背景。每次操作后应将手完全移出识别区域,等待约 0.6 秒再做下一次手势,避免触发锁定影响连续操作。
- 切换界面没有语音:检查语音模块接线、0x40 地址、V1 模式及音量设置。若上一段语音仍在播放,程序会跳过本次播报,可等待播放结束后再操作。
- 项目中的食材用量、制作步骤和计时仅供参考,用户可根据实际烹饪经验与需求修改代码中的菜谱数据,并同步更新对应的步骤图片。
- DFRobot_HumanPose 手势传感器库和项目专用的 unihiker_k10_local 库必须保留在工程的 src 文件夹内。Arduino IDE 打开主程序后会自动编译这些依赖,不需要再次通过库管理器安装。




