空中绘图板:用 Mediapipe 和 OpenCV 实现的创新手势识别应用
在这个数字化飞速发展的时代,手势识别技术正逐渐走入我们的日常生活,从智能家居到增强现实,无处不在。而今天,我将与大家分享一个充满创意和趣味的项目——空中绘图板。这个项目利用了强大的 Mediapipe 库和 OpenCV,实现了通过手势在空中绘制图形的功能。无论你是编程新手还是资深开发者,这个项目都将为你带来灵感和乐趣。
项目概述
空中绘图板 是一个基于计算机视觉和手势识别技术的应用程序。用户可以通过简单的手势在空中“绘制”图形,切换颜色,甚至保存和加载绘图内容。这个项目不仅展示了手势识别的实用性,还提供了一种全新的交互体验。
主要功能
- 手势绘图:通过检测食指和拇指的尖端位置,实现自由绘图、直线绘图和圆形绘图。
- 颜色选择:使用特定的手势切换绘图颜色,支持多种颜色选择。
- 撤销与重做:通过手势实现绘图操作的撤销与重做。
- 保存与加载:允许用户保存当前绘图内容,并在需要时加载。
- 多手检测:支持同时检测多只手,提供更多交互方式。
- 用户界面:在窗口中显示当前颜色、绘图模式以及操作指南。
技术栈
- Mediapipe:用于实时手势检测和关键点识别。
- OpenCV:处理视频流、绘图和图像显示。
- NumPy:处理数组和数学运算。
- Pickle:序列化和反序列化绘图内容,实现保存与加载功能。
代码详解
这个项目的核心是一个名为 AirDrawingApp
的类,它封装了摄像头捕捉、手势识别、绘图操作以及用户界面显示等功能。下面我们将逐步解析这个项目的关键部分。
手势识别
手势识别是整个应用的核心,通过 Mediapipe 的手部模型实时检测用户的手部位置,并根据手指的张开状态识别不同的手势。
def recognize_gesture(hand_landmarks):"""简单手势识别:- 张开五指:无动作- 做出“OK”手势:颜色切换- 做出“拳头”手势:清除画布- 做出“食指伸出”手势:撤销- 做出“中指伸出”手势:重做- 做出“拇指和小指伸出”手势:保存画布- 做出“食指和中指伸出”手势:加载画布- 做出四指伸出手势:切换绘图模式"""# 获取各个手指的状态finger_tips_ids = [4, 8, 12, 16, 20]fingers = []for tip_id in finger_tips_ids:if hand_landmarks.landmark[tip_id].y < hand_landmarks.landmark[tip_id - 2].y:fingers.append(1)else:fingers.append(0)total_fingers = fingers.count(1)# 判断手势if total_fingers == 0:return GESTURE_CLEARelif total_fingers == 1:# 判断是否为“食指伸出”手势(用于撤销)if fingers[1] == 1 and fingers[0] == fingers[2] == fingers[3] == fingers[4] == 0:return GESTURE_UNDOelif total_fingers == 2:# 判断是否为“OK”手势(拇指和食指接触,用于颜色切换)distance = np.sqrt((hand_landmarks.landmark[4].x - hand_landmarks.landmark[8].x) ** 2 +(hand_landmarks.landmark[4].y - hand_landmarks.landmark[8].y) ** 2)if distance < 0.05:return GESTURE_COLOR_CHANGEelse:# 判断是否为“食指和中指伸出”手势(用于加载画布)if fingers[1] == 1 and fingers[2] == 1 and fingers[0] == fingers[3] == fingers[4] == 0:return GESTURE_LOADelif total_fingers == 3:# 判断是否为“中指伸出”手势(用于重做)if fingers[2] == 1 and fingers[0] == fingers[1] == fingers[3] == fingers[4] == 0:return GESTURE_REDOelif total_fingers == 4:# 判断是否为“绘图模式切换”手势(四指伸出)return GESTURE_DRAW_MODEelif total_fingers == 5:# 全部五指伸出,无动作return Nonereturn None
通过手指的张开状态和相互之间的距离,我们可以识别出不同的手势并执行相应的操作。
AirDrawingApp 类
AirDrawingApp
类是整个应用的核心,负责处理摄像头输入、手势识别、绘图逻辑和用户界面显示。
class AirDrawingApp:def __init__(self):# 初始化摄像头self.cap = cv2.VideoCapture(0)self.cap.set(3, 1280) # 设置宽度self.cap.set(4, 720) # 设置高度# 初始化 Mediapipe Handsself.hands = mp_hands.Hands(max_num_hands=2,min_detection_confidence=0.7,min_tracking_confidence=0.5)# 创建一个空白的画布self.canvas = np.zeros((720, 1280, 3), dtype=np.uint8)# 用于存储前一个点的位置self.prev_points = {} # 针对每只手# 撤销和重做栈self.undo_stack = deque(maxlen=20)self.redo_stack = deque(maxlen=20)# 画笔粗细self.brush_thickness = 5# 当前选中的颜色self.current_color = current_colorself.color_index = color_index# 当前绘图模式self.current_draw_mode = current_draw_modeself.draw_mode_index = draw_mode_index# 标记画布是否被修改self.canvas_modified = False
核心功能方法
- 绘图方法:支持自由绘图、直线绘图和圆形绘图。
- 撤销与重做:利用栈结构实现撤销和重做功能。
- 保存与加载:使用
pickle
序列化画布内容,实现保存与加载。 - 颜色和绘图模式切换:通过手势切换当前绘图颜色和绘图模式。
def save_canvas(self, filename='canvas.pkl'):with open(filename, 'wb') as f:pickle.dump(self.canvas, f)print("Canvas saved.")def load_canvas(self, filename='canvas.pkl'):if os.path.exists(filename):with open(filename, 'rb') as f:self.canvas = pickle.load(f)print("Canvas loaded.")else:print("No saved canvas found.")def undo(self):if self.undo_stack:self.redo_stack.append(self.canvas.copy())self.canvas = self.undo_stack.pop()print("Undo performed.")else:print("Nothing to undo.")def redo(self):if self.redo_stack:self.undo_stack.append(self.canvas.copy())self.canvas = self.redo_stack.pop()print("Redo performed.")else:print("Nothing to redo.")def clear_canvas(self):self.undo_stack.append(self.canvas.copy())self.canvas = np.zeros((720, 1280, 3), dtype=np.uint8)print("Canvas cleared.")def change_color(self):self.color_index = (self.color_index + 1) % len(colors)self.current_color = colors[self.color_index]print(f"Color changed to {color_names[self.color_index]}.")def change_draw_mode(self):self.draw_mode_index = (self.draw_mode_index + 1) % len(draw_modes)self.current_draw_mode = draw_modes[self.draw_mode_index]print(f"Draw mode changed to {self.current_draw_mode}.")
处理视频帧
通过摄像头捕捉每一帧视频,进行手势识别和绘图操作。
def process_frame(self, frame):frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)results = self.hands.process(frame_rgb)gesture = Noneif results.multi_hand_landmarks:for hand_idx, hand_landmarks in enumerate(results.multi_hand_landmarks):# 绘制手部关键点mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS,mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2, circle_radius=2),mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2))# 识别手势gesture = recognize_gesture(hand_landmarks)# 获取食指和拇指的坐标x1 = int(hand_landmarks.landmark[8].x * 1280)y1 = int(hand_landmarks.landmark[8].y * 720)x2 = int(hand_landmarks.landmark[4].x * 1280)y2 = int(hand_landmarks.landmark[4].y * 720)# 计算中点mid_x = int((x1 + x2) / 2)mid_y = int((y1 + y2) / 2)# 绘制食指和拇指的连线cv2.line(frame, (x1, y1), (x2, y2), self.current_color, 2)cv2.circle(frame, (x1, y1), 5, self.current_color, -1)cv2.circle(frame, (x2, y2), 5, self.current_color, -1)# 根据绘图模式绘制不同形状if self.current_draw_mode == DRAW_MODE_FREE:self.draw_free(hand_idx, x1, y1)elif self.current_draw_mode == DRAW_MODE_LINE:self.draw_line(hand_idx, x1, y1)elif self.current_draw_mode == DRAW_MODE_CIRCLE:self.draw_circle(hand_idx, x1, y1)else:self.prev_points = {}# 根据手势执行操作if gesture == GESTURE_CLEAR:self.clear_canvas()elif gesture == GESTURE_COLOR_CHANGE:self.change_color()elif gesture == GESTURE_UNDO:self.undo()elif gesture == GESTURE_REDO:self.redo()elif gesture == GESTURE_SAVE:self.save_canvas()elif gesture == GESTURE_LOAD:self.load_canvas()elif gesture == GESTURE_DRAW_MODE:self.change_draw_mode()return frame
用户界面显示
在应用窗口中显示当前颜色、绘图模式和操作指南,提升用户体验。
def display_ui(self, frame):# 显示当前颜色cv2.rectangle(frame, (10, 10), (60, 60), self.current_color, -1)cv2.putText(frame, 'Color', (70, 40),cv2.FONT_HERSHEY_SIMPLEX, 0.7, self.current_color, 2, cv2.LINE_AA)# 显示当前绘图模式cv2.rectangle(frame, (10, 80), (250, 120), (50, 50, 50), -1)cv2.putText(frame, f'Mode: {self.current_draw_mode}', (20, 110),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2, cv2.LINE_AA)# 显示帮助信息cv2.putText(frame, 'Gestures:', (10, 160),cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2, cv2.LINE_AA)cv2.putText(frame, 'OK Gesture: Change Color', (10, 190),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(frame, 'Fist Gesture: Clear Canvas', (10, 220),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(frame, 'Index Finger: Undo', (10, 250),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(frame, 'Middle Finger: Redo', (10, 280),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(frame, 'Thumb & Pinky: Save', (10, 310),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(frame, 'Index & Middle: Load', (10, 340),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)cv2.putText(frame, 'Four Fingers: Change Mode', (10, 370),cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 1, cv2.LINE_AA)
主循环
应用的主循环负责不断捕捉摄像头帧,处理手势识别和绘图操作,并在窗口中显示结果。
def run(self):while True:ret, frame = self.cap.read()if not ret:print("Failed to grab frame.")break# 翻转图像水平翻转以便镜像显示frame = cv2.flip(frame, 1)# 处理手部检测和绘图processed_frame = self.process_frame(frame)# 合并画布和当前帧combined = cv2.addWeighted(processed_frame, 0.5, self.canvas, 0.5, 0)# 显示用户界面self.display_ui(combined)# 显示合并后的图像cv2.imshow('Air Drawing - Enhanced', combined)# 按 'q' 键退出if cv2.waitKey(1) & 0xFF == ord('q'):break# 释放资源self.cap.release()cv2.destroyAllWindows()
使用指南
- 启动应用:运行上述代码,摄像头窗口将会启动并显示“Air Drawing - Enhanced”界面。
- 绘图:
- 自由绘图:默认模式下,通过食指在空中移动进行自由绘图。
- 切换绘图模式:做出四指伸出手势,可以在自由绘图、直线绘图和圆形绘图模式之间切换。
- 颜色选择:做出“OK”手势(拇指和食指接触),绘图颜色将切换到下一个预设颜色,并在控制台输出颜色更改信息。
- 撤销和重做:
- 撤销:做出“食指伸出”手势,撤销上一步绘图操作。
- 重做:做出“中指伸出”手势,重做上一步被撤销的绘图操作。
- 清除画布:做出拳头手势,当前画布上的所有内容将被清除。
- 保存与加载:
- 保存画布:做出“拇指和小指伸出”手势,将当前画布保存到文件。
- 加载画布:做出“食指和中指伸出”手势,加载之前保存的画布。
- 退出应用:按下键盘上的 'q' 键即可退出应用。
创意扩展与优化
这个项目不仅仅是一个简单的手势识别绘图应用,它还可以不断扩展和优化,以下是一些建议:
- 多手检测与交互:支持同时检测多只手,实现更复杂的交互方式,例如一只手用于绘图,另一只手用于控制工具。
- 更多绘图工具:增加更多绘图工具,如矩形、椭圆、喷枪效果等,丰富用户的绘图体验。
- 界面优化:使用图形用户界面库(如 Tkinter 或 PyQt)来创建更美观的界面,包含工具栏、颜色选择器等。
- 手势自定义:允许用户自定义手势,以执行更多操作,如调整画笔大小、切换图层等。
- 性能优化:优化图像处理和绘图算法,提高应用的响应速度和稳定性。
- 跨平台支持:优化代码,使其在不同操作系统(Windows、macOS、Linux)上都能稳定运行。
相关文章:

空中绘图板:用 Mediapipe 和 OpenCV 实现的创新手势识别应用
在这个数字化飞速发展的时代,手势识别技术正逐渐走入我们的日常生活,从智能家居到增强现实,无处不在。而今天,我将与大家分享一个充满创意和趣味的项目——空中绘图板。这个项目利用了强大的 Mediapipe 库和 OpenCV,实…...
讲一个自己写的 excel 转 html 的 java 工具
由来 这是一个从开发需求中诞生的工具,在工作中因为有一个 excel 转 html 的任务,又没找到一个专门做这方面的工具(其他工具几乎都是简单的转换,无法还原 excel 样式,而且转换的宽高有点儿差距)࿰…...
前端往后端传递参数的方式有哪些?
文章目录 1. URL 参数1.1. 查询参数(Query Parameters)1.2. 路径参数(Path Parameters) 2. 请求体(Request Body)2.1. JSON 数据2.2. 表单数据2.3. 文件上传 3. 请求头(Headers)3.1. 自定义请求…...
Vue axios 异步请求,请求响应拦截器
在 Vue.js 中使用 axios 进行网络请求是非常常见的做法,因为它提供了比原生的 Fetch API 更丰富的功能,并且更易于处理错误和配置。结合 Axios 的拦截器功能,你可以对所有的请求或响应进行预处理,比如添加认证头信息、统一处理错误…...

yarn install 安装报错:Workspaces can only be enabled in private projects.
在本地运行项目的时候,使用yarn install 安装模块依赖的时候,遇到报错:Workspaces can only be enabled in private projects. 一、原因分析 报这个错误是因为你使用了yarn的workspace,但并未将工程标记为private。 二、解决办法 …...

http 请求总结get
关于get请求传递body的问题 错误代码 有400 , 415 等情况 <!doctype html><html lang"zh"><head><title>HTTP Status 400 – 错误的请求</title><style type"text/css">body {font-family:Tahoma,Arial,sans-seri…...
TCP 和 UDP 的区别:解析网络传输协议
引言 在计算机网络的世界中,TCP(Transmission Control Protocol,传输控制协议)和 UDP(User Datagram Protocol,用户数据报协议)是两种极为重要且应用广泛的传输层协议。它们在功能、特性以及适…...

【已解决】pyinstaller打包ico图片报错:OSError: [WinError 225] 无法成功完成操作,因为文件包含病毒或潜在的垃圾软件。
起因: pyinstaller加上 --icon 参数打包时报错。 命令如下: 解决: 关闭 Windows 的病毒防护即可,步骤如下。 点屏幕右下角通知栏,进入“病毒和威胁防护”: 打开: 关闭实时保护(…...

SpringBoot项目配置文件的优先级
从外部讲 内部讲 所以优先级是:外部的config里的application.yml最高 然后是外部与jar包同目录下的application.yml 再到内部的classpath下config下的application.yml 最后到classpath下的application.yml 最后来个优先级最高的 启动时候 指定spring.config.location…...
JS中类型化数组(Typed Arrays)详解和常见应用场景
在JavaScript中,类型化数组(Typed Arrays) 是用于处理二进制数据的对象。它们允许我们以一种高效的方式操作和存储大量的数值数据,特别适合处理类似于图像、音频、视频等场景的原始二进制数据。 类型化数组的基本概念 类型化数组…...

虚幻引擎是什么?
Unreal Engine,是一款由Epic Games开发的游戏引擎。该引擎主要是为了开发第一人称射击游戏而设计,但现在已经被成功地应用于开发模拟游戏、恐怖游戏、角色扮演游戏等多种不同类型的游戏。虚幻引擎除了被用于开发游戏,现在也用于电影的虚拟制片…...

LabVIEW生物医学信号虚拟实验平台
介绍了一款基于LabVIEW的多功能生物医学信号处理实验平台的设计和实现。平台通过实践活动加强学生对理论的理解和应用能力,特别是在心电图(ECG)和脑电图(EEG)的信号处理方面。实验平台包括信号的滤波、特征提取和频谱分析等功能,能直观体验和掌握生物医学…...

【软件工程】十万字知识点梳理 | 期末复习专用
原创文章,禁止转载。 文章目录 图CRC卡片用例图类图状态图活动图泳道图软件质量因素自顶向下集成自底向上集成人员与工作量之间的关系时序图关键路径软件结构基本路径测试判定表数据流图(DFD)体系结构设计问题数据字典挣值分析等价划分程序流程图PAD | N-S燃尽图甘特图对象模…...
Android --- 在AIDL进程间通信中,为什么使用RemoteCallbackList 代替 ArrayList?
1.RemoteCallbackList vs ArrayList RemoteCallbackList 是一个特殊的 List,它用来管理跨进程的回调,特别是当回调对象是在不同进程中时。它在 AIDL(Android Interface Definition Language)通信中常常用来处理跨进程的通信。 Arr…...

ADC(二):外部触发
有关ADC的基础知识请参考标准库入门教程 ADC(二):外部触发 1、TIM1的CC1事件触发ADC1DMA重装载2、TIM3的TRGO事件(的更新事件)触发ADC1DMA重装载3、TIM3的TRGO事件(的捕获事件)触发ADC1DMA重装载4、优化TIM3的TRGO事件(的捕获事件)触发ADC1D…...

数仓开发那些事(8)
程序员圣经 为什么刚刚能运行,现在就不行 为什么刚刚不运行,现在就可以 为什么他的可以跑,我的不能跑 为什么我的可以跑,他的就不行 为什么这台电脑能,那台就不行 为什么这台电脑不行,那台就行 神州员工&a…...

【CSS in Depth 2 精译_096】16.4:CSS 中的三维变换 + 16.5:本章小结
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第五部分 添加动效 ✔️【第 16 章 变换】 ✔️ 16.1 旋转、平移、缩放与倾斜 16.1.1 变换原点的更改16.1.2 多重变换的设置16.1.3 单个变换属性的设置 16.2 变换在动效中的应用 16.2.1 放大图标&am…...

【连续学习之ResCL算法】2020年AAAI会议论文:Residual continual learning
1 介绍 年份:2020 会议: AAAI Lee J, Joo D, Hong H G, et al. Residual continual learning[C]//Proceedings of the AAAI Conference on Artificial Intelligence. 2020, 34(04): 4553-4560. 本文提出的算法是Residual Continual Learning (ResC…...
【zookeeper核心源码解析】第二课:俯瞰QuorumPeer启动核心流程,实现选举关键流程
系列文章目录 【zookeeper核心源码解析】第一课:zk启动类核心流程序列图 【zookeeper核心源码解析】第二课:俯瞰QuorumPeer启动核心流程,实现选举关键流程 【zookeeper核心源码解析】第三课:leader与follower何时开始同步&#…...

数据流图和流程图的区别
在结构化建模中,数据流图和流程图都是非常重要的工具,它们为开发人员提供了强大的手段来分析和设计系统。尽管两者在表面上看起来有些相似,但它们在功能、用途和表达方式上存在显著的区别。本文将详细探讨数据流图和流程图的区别,…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...

以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

在 Visual Studio Code 中使用驭码 CodeRider 提升开发效率:以冒泡排序为例
目录 前言1 插件安装与配置1.1 安装驭码 CodeRider1.2 初始配置建议 2 示例代码:冒泡排序3 驭码 CodeRider 功能详解3.1 功能概览3.2 代码解释功能3.3 自动注释生成3.4 逻辑修改功能3.5 单元测试自动生成3.6 代码优化建议 4 驭码的实际应用建议5 常见问题与解决建议…...
深入理解 React 样式方案
React 的样式方案较多,在应用开发初期,开发者需要根据项目业务具体情况选择对应样式方案。React 样式方案主要有: 1. 内联样式 2. module css 3. css in js 4. tailwind css 这些方案中,均有各自的优势和缺点。 1. 方案优劣势 1. 内联样式: 简单直观,适合动态样式和…...
raid存储技术
1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划,涵盖存储系统的布局、数据存储策略等,它明确数据如何存储、管理与访问,为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...
python打卡day49@浙大疏锦行
知识点回顾: 通道注意力模块复习空间注意力模块CBAM的定义 作业:尝试对今天的模型检查参数数目,并用tensorboard查看训练过程 一、通道注意力模块复习 & CBAM实现 import torch import torch.nn as nnclass CBAM(nn.Module):def __init__…...

二维数组 行列混淆区分 js
二维数组定义 行 row:是“横着的一整行” 列 column:是“竖着的一整列” 在 JavaScript 里访问二维数组 grid[i][j] 表示 第i行第j列的元素 let grid [[1, 2, 3], // 第0行[4, 5, 6], // 第1行[7, 8, 9] // 第2行 ];// grid[i][j] 表示 第i行第j列的…...