人工智能2025展望——第五天:我开发了一款可通过手势控制的非接触式飞行追踪器
鹅
快速链接
坏脾气的命运生成器
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
我编辑了这篇文章,人工智能帮了我不少忙。这些文章都是关于人工智能兴起的简短介绍。如果我每天都写一篇,就没时间花几个小时来写一篇了。😅
AI系列的出现 利用 了Goose这款开源AI代理。如果你之前没听说过它,赶紧去了解一下吧!
一款开源、可扩展的AI代理,其功能远不止代码建议——可与任何LLM配合使用,进行安装、执行、编辑和测试。
鹅
一个本地的、可扩展的、开源的人工智能代理,可以自动执行工程任务
goose 是您的机器端 AI 代理,能够自动完成从头到尾的复杂开发任务。goose 不仅限于代码建议,它还可以从零开始构建整个项目,编写和执行代码,调试故障,协调工作流程,并与外部 API 交互——所有这些都是自主完成 的 。
无论您是在构思原型、改进现有代码,还是管理复杂的工程流程,goose 都能适应您的工作流程并精确执行任务。
goose 的设计旨在实现最大的灵活性,可与任何 LLM 配合使用,并支持多模型配置以优化性能和成本,与 MCP 服务器无缝集成,并且既可作为桌面应用程序,也可作为 CLI 使用——使其成为希望加快速度并专注于创新的开发人员的终极 AI 助手。
快速链接
需要帮助吗?
…
在 Goose 的“ 人工智能新纪元”第五天活动 中,挑战是搭建“返航指示牌”。这是一个手势控制的航班到达信息显示屏,戴着手套或连指手套的人可以通过手势进行导航。这样在严寒中就不用触摸屏幕了。挑战要求至少有两种不同的导航手势,需要真实的航班数据,如果能提供手势识别的语音反馈就更好了。
简而言之:如果你等不及了,我做了一个很酷的东西,你可以在 flightboard.nickyt.co上找到它。
技术栈
我使用 TanStack Start(React + TypeScript 和 SSR)、 MediaPipe (用于手势识别)和 OpenSky Network API (用于实时航班数据)构建了它。
说实话,这是我第一次用计算机视觉开发应用,所以这次人工智能日活动我可是被“技术宅”们“洗脑”了,恨不得把所有相关内容都体验一遍。计算机视觉这种技术竟然如此容易上手,真是令人惊叹。这让我想起了之前和 Gant Laborde 一起做的那场关于人工智能和 Tensorflow.js 的精彩直播。
VIDEO
我选择 TanStack Start 是因为我之前在一个重要的项目——Pomerium MCP 应用演示中已经使用过它。
演示应用程序展示了如何使用 Pomerium 和上下文访问策略构建和保护 MCP 服务器和客户端。
欢迎使用 Pomerium Chat,这是一个用于展示使用 Pomerium 保护的远程模型上下文协议服务器的极简聊天应用程序。
快速入门.mp4
先决条件
Linux 或 MacOS 主机
Docker 和 Docker Compose
您的机器应该将 443 端口暴露给互联网,以便它可以从 Let's Encrypt 获取 TLS 证书,并且 OpenAI 可以调用您的 MCP 服务器端点。
OpenAI API密钥
快速入门
环境变量
在根目录下创建一个 .env-mcp-app-demo文件,并添加以下环境变量:
OPENAI_API_KEY=your_api_key_here
Enter fullscreen mode
Exit fullscreen mode
Pomerium 配置
更新 pomerium-config.yaml并替换 YOUR-DOMAIN 为您控制的子域名。为相关主机创建 A DNS 记录(或 *.YOUR-DOMAIN)。
默认情况下,访问策略会将访问权限限制在邮箱地址属于 YOUR-DOMAIN 的 用户。 如果需要调整此策略,请参阅 策略语言参考。
Docker Compose
请查看 docker-compose.yaml此仓库中的文件。
docker compose up -d
Enter fullscreen mode
Exit fullscreen mode
它包含一个演示用的 SQLite 服务器,需要一个演示数据库,例如 https://github.com/jpwhite3/northwind-SQLite3 …
提供 API 端点是避免第三方 API 出现 CORS 问题的经典方法。在本例中,我可以通过自己的服务器函数代理对 OpenSky API 的请求。
以下是我成功实现的功能:
利用 MediaPipe 的 WASM 运行时实现实时手部追踪
四种手势类型(握拳、张开手掌、拇指向上、拇指向下)
左右手独立手势检测
通过 TanStack Query 从 OpenSky Network 获取实时航班数据,并利用智能缓存技术实现。
每个手势都有语音反馈(可关闭声音)
一款能够适应您手部的手势训练系统
符合 WCAG AAA 标准的浅色和深色冬季主题
虽然这不是什么人能轻易做到的事,但我还是添加了选择摄像头的功能(如果您有多个摄像头)。
在手机上运行良好
从产品需求文档 (PRD) 开始
我首先编写了一份产品需求文档(PRD)来规划工作。这已经成为我应对这类挑战的必备工具。PRD 提供了挑战所需的所有信息,然后我可以根据这些信息,结合自己的理解,制定相应的实施方案。
使用 MediaPipe 进行手部追踪
一开始在浏览器中运行 MediaPipe 对我来说有点麻烦。我对 MediaPipe 还不熟悉,尝试按照基本设置进行操作,但不知为何出了点问题,所以我尝试了 TensorFlow.js,虽然成功了,但最终还是让 MediaPipe 运行起来了。
我之所以选择 MediaPipe WASM 版本,是因为我想把它部署到 Netlify 上。WASM 运行时在浏览器中运行,这意味着我可以把它托管在任何 PaaS 平台上,而无需担心 Python 的问题。不过,我知道Vercel 现在也 支持 Python 了。
// useMediaPipe.ts - Custom hook for MediaPipe integration
const hands = new Hands ({
locateFile : ( file ) => `/mediapipe/ ${ file } ` ,
});
hands . setOptions ({
maxNumHands : 2 ,
modelComplexity : 1 ,
minDetectionConfidence : 0.7 ,
minTrackingConfidence : 0.5 ,
});
Enter fullscreen mode
Exit fullscreen mode
手部追踪以 30-60 帧/秒的速度运行,并带有地标可视化功能。我同时镜像了视频画面和地标,因此当您移动手部时,感觉非常自然。
我遇到一个奇怪的问题:绿色的骨骼叠加层(手部特征点可视化)竟然和我的手一起出现在我的头上。MediaPipe 把面部特征识别成了类似手的形状。这可能算不上什么问题,但我通过只在检测到至少一只手时才渲染骨骼来修正了我的逻辑。
我还做了超出挑战要求的额外工作。挑战要求至少两种手势,但我实现了四种:握拳、张开手掌、竖起大拇指和竖起大拇指向下。我还让它能够独立检测双手,这样每只手就可以同时做出不同的手势。
手势检测:难点
手势检测比我想象的要复杂得多。我最初的方法是使用基于手指弯曲比例的固定阈值。计算方法很简单:测量每个指尖到手腕的距离,除以指关节到手腕的距离,即可得到弯曲比例。低于阈值的值表示手指处于弯曲状态。
// Finger curl ratio: distance(tip, wrist) / distance(knuckle, wrist)
const fingerCurl = ( finger : FingerLandmarks ) => {
const tipDist = distance ( finger . tip , wrist );
const knuckleDist = distance ( finger . knuckle , wrist );
return tipDist / knuckleDist ;
};
// Gesture classification
const isClosedFist = fingersCurled >= 4 && avgCurl > fistThreshold ;
const isOpenPalm = fingersExtended >= 4 && avgCurl < palmThreshold ;
Enter fullscreen mode
Exit fullscreen mode
在我最初的开发阶段,这个方法效果很好。但是当我在不同的光照条件和不同的相机距离下进行测试时,手势几乎无法识别。不同的手部位置和光照条件完全打乱了我设定的阈值。
解决方案是添加手势训练模式。用户重复几次每个手势,系统会根据方差计算个性化阈值。训练数据方差越大,阈值就越宽松,这样就能更好地应对人们手势表现的自然差异。
这原本是挑战赛的附加功能之一,但结果却至关重要,而非可有可无。没有它,手势检测功能过于脆弱,根本无法使用。
这是一个典型的过度拟合自身训练数据的例子。教训是:构建机器学习系统(即使是简单的系统)时,务必考虑方差。固定间隔适用于演示环境,自适应间隔适用于生产环境。
飞行数据集成
航班数据方面,我使用了 OpenSky Network 的免费 API 。它无需身份验证,因此设置非常简单。该 API 返回实时航班位置,我使用边界框筛选出特定机场附近的到达航班。
TanStack Query 处理所有缓存和自动刷新逻辑:
// useFlightData.ts
const { data : flights , isLoading , error } = useQuery ({
queryKey : [ ' flights ' , ' arrivals ' ],
queryFn : fetchFlights ,
// Caching and refetching configuration
// Cache for 5 minutes in dev, 20 seconds in prod
staleTime : import . meta . env . DEV ? 300000 : 20000 ,
gcTime : 5 * 60 * 1000 , // Cache for 5 min
refetchInterval : 30 _000 , // Auto-refresh every 30s
retry : 3 , // Exponential backoff
});
Enter fullscreen mode
Exit fullscreen mode
OpenSky Network 有严格的速率限制(最小间隔 10 秒),我在开发过程中就遇到过速率限制。因此,我 staleTime在开发模式下将间隔时间延长到了 5 分钟。如果你也遇到了速率限制,可以使用 VPN 获取新的 IP 地址,这样就能获得更长的 API 使用时间。
staleTime20 秒和 30 秒的 间隔 refetchInterval可以保持数据最新,而不会对 API 造成过大压力。
音频反馈
这项挑战需要为手势识别添加音频反馈,事实证明这至关重要。我为每种手势添加了不同的声音:握拳时发出“嗖”的一声,张开手掌时发出“叮”的一声,竖起大拇指时发出“叮”的一声,竖起大拇指向下时发出“嗡嗡”的一声。
声音是预先缓存的,只有在手势发生变化时才会播放,而不是每一帧都播放:
// gestureAudio.ts - Audio caching and playback
const audioCache = new Map < GestureType , HTMLAudioElement > ();
export const playGestureSound = ( gesture : GestureType ) => {
let audio = audioCache . get ( gesture );
if ( ! audio ) {
audio = new Audio ( GESTURE_SOUNDS [ gesture ]);
audio . volume = currentVolume ;
audioCache . set ( gesture , audio );
}
audio . currentTime = 0 ; // Reset for quick replay
audio . play (). catch (() => {}); // Ignore autoplay errors
};
Enter fullscreen mode
Exit fullscreen mode
您可以在设置中打开和关闭声音,这在您反复测试相同手势时非常重要。
虽然这只是个额外功能,但我认为这在无障碍访问方面是一项重大进步。想象一下,屏幕阅读器可以朗读航班信息并导航到相应航班,用户只需竖起大拇指即可表示“就是这个航班!”
航班详情模态框
用竖起大拇指的手势选择航班,即可打开一个详细的模态框,其中包含所有航班信息:国旗、呼号、位置、高度、速度、航向和最后联系时间。
对于模态界面,我引入了 ShadCN 组件,具体来说是 Dialog 和 Drawer。我之前在类似场景下也使用过它们。Dialog 负责桌面端的模态体验,而 Drawer 则提供了移动端流畅的底部向上滑动交互。两者都内置了完善的辅助功能,这当然是一大优势。
< div className = " fixed inset-0 z-50 " >
< div className = " absolute inset-0 bg-black/70 backdrop-blur-sm z-0 " /> { /* Backdrop */ }
< div className = " relative z-10 " > { /* Content on top */ }
{ /* Modal content */ }
< /div >
< /div >
Enter fullscreen mode
Exit fullscreen mode
在移动端,我将模态框替换为从底部向上滑动的抽屉组件。窗口宽度决定渲染哪个组件。
主题系统
我制作了浅色和深色的冬季主题,与第 4 天的主题非常相似。
浅色模式采用纯白色背景,搭配深冰蓝色主色调,对比度为 13:1,符合 WCAG AAA 标准。深色模式则采用深蓝紫色夜空背景,并带有明亮的蓝色光芒。
主题会保存到本地存储中,并在首次加载时遵循系统偏好设置。
定制钩子:有趣的部分
我特别满意的一点是自定义钩子架构。这个项目最终使用了 8 个自定义钩子,它们让组件结构更加简洁。
useMediaPipe负责 MediaPipe 的所有初始化和清理工作。它会设置 Hands 实例、配置选项并返回处理函数。此钩子封装了所有 WASM 加载逻辑,因此组件无需了解 MediaPipe 的内部机制。
useWebcam它负责管理摄像头访问权限和设备选择。它会处理权限请求,列出可用摄像头,并将我的摄像头选择保存到本地存储。这一点至关重要,因为我有多台摄像头,并且需要在笔记本电脑摄像头和外置摄像头之间切换。
useGestures这是手势检测逻辑所在的位置。它从 MediaPipe 获取手部特征点,并返回当前手势类型。此钩子还处理防抖动(需要 300 毫秒的稳定性才能识别手势)以及基于训练数据的方差感知阈值计算。
useFlightData它封装了用于获取航班信息的 TanStack 查询。它处理 OpenSky Network API 调用,将响应解析为我们的 ProcessedFlight 格式,并管理缓存/重新获取间隔。所有航班数据逻辑都隔离在这个钩子函数中。
useLocalStorage这是一个简单但至关重要的钩子,用于将状态与 localStorage 同步。我用它来控制相机选择、主题偏好和音量设置。状态一旦改变,就会自动更新。我通常会在大多数 React 项目中添加它。
useWindowFocus它能检测浏览器标签页何时失去焦点,以便我们暂停摄像头。这大大节省了电量。如果没有这项功能,即使我切换标签页,MediaPipe 也会继续处理帧,无谓地消耗 CPU 资源。不仅如此,如果你的注意力不在当前窗口,你也不希望它识别手势。
useGestureTraining管理手势训练流程。我会多次做出每个手势,这个钩子会收集手指弯曲数据,计算均值和标准差,并生成具有方差感知裕度的个性化阈值。
useAudio它负责处理所有音效。它会预先缓存音频文件,管理播放,并且只在手势发生变化时播放声音(而不是每帧都播放)。它还会遵循我的音量设置和静音开关。
自定义钩子在 React 应用中非常常见,我对最终使用的钩子非常满意。
我学到了什么
如前所述,这是我第一次开发计算机视觉应用程序,所以有几点需要学习:
手势识别需要进行实际测试并调整阈值。我之前设置的固定阈值在一种光照条件下有效,但在其他光照条件下则无效。即使经过手势训练,我认为仍然有一些地方需要改进。
窗口焦点检测可以节省电量。MediaPipe 即使在我切换标签页时也会持续运行,消耗大量 CPU 资源。当窗口失去焦点时暂停摄像头就解决了这个问题。我一开始没想到这一点,但除了节省电量之外,它还能防止在不使用应用时检测到手势。我在另一个应用中看到它运行时才发现这一点。
接下来会发生什么?
该应用已部署并可在 flightboard.nickyt.co 上运行。快去体验一下吧!你可以训练自己的手势,并通过手部动作导航航班。代码位于我的“2025 年人工智能展望”代码库中,欢迎查看。
nickytonline 的《人工智能在 2025 年的到来》
坏脾气的命运生成器
╭───────────────────────────────────────╮ │ 🦆 脾气暴躁的幸运鹅说: │ ╰───────────────────────────────────────╯ ∩───∩ │ │ │ │ │ │ ← “真的吗?又是这个?” │ ○ │ │ __│ ∩─│ │─∩ │ │ │ │ │ ╰────╯ │ │ ∩──────∩ │ ╰─╯ ╰─╯
╔════════════════════════════════════════════════════════════════════════════╗ ║ 宇宙自有其幽默感。可惜,被捉弄的是你。 ║ ╚══════════════════════════════════════════════════════════════════════════╝
═════════════════════════════════════════════════════════════ 生成于:2025-12-09 11:03:39 心情:暴躁 ═══════════════════════════════════════════════════════════
由 Sassy Goose Fortune Generator 生成
还有一些细节需要完善。手势检测可以更可靠,用户界面也需要改进,还有一些特殊情况我还没有处理。但这是人工智能的时代,这意味着是时候迎接下一个挑战了。
未来潜在的改进方向包括支持多机场、飞行轨迹可视化、双手手势操作以及集成 PartyKit 以实现多人控制。但考虑到这是一个 48 小时的开发版本,我对目前的成果已经很满意了。
如果你想和我保持联系,我的所有社交账号都在 nickyt.online 上。
期待下次!
文章来源:https://dev.to/nickytonline/advent-of-ai-2025-day-5-i-built-a-touchless-flight-tracker-you-control-with-hand-gestures-1jn8