MediaPipe手势识别极速部署:CPU版Flask API服务搭建全流程
MediaPipe手势识别极速部署CPU版Flask API服务搭建全流程1. 引言想为你的应用添加炫酷的手势交互功能吗比如通过一个手势控制智能家居或者让虚拟角色模仿你的手部动作今天我们就来手把手搭建一个属于自己的手势识别API服务。这个服务基于Google的MediaPipe Hands模型它能在普通电脑的CPU上快速识别出图片中手部的21个关键点并且我们还会给它加上一个“彩虹骨骼”的可视化效果让识别结果一目了然。整个过程不需要昂贵的GPU部署简单代码清晰非常适合集成到你的Web应用、移动App或者创意项目中。通过这篇教程你将学会如何从零开始用Flask框架搭建一个RESTful API服务。集成MediaPipe Hands模型实现高精度的手部关键点检测。编写“彩虹骨骼”绘制算法为识别结果添加科技感十足的可视化效果。封装成一个完整的、可独立运行的后端服务并对外提供图片上传和结果返回接口。学完本文你将获得一个功能完整、可直接调用的手势识别后端为你的创意插上AI的翅膀。2. 环境准备与项目初始化2.1 创建项目与安装依赖首先我们创建一个干净的项目目录并准备好所有需要的工具包。打开你的终端或命令行工具执行以下步骤创建项目文件夹mkdir hand_tracking_api cd hand_tracking_api创建并编辑依赖文件 在项目根目录下创建一个名为requirements.txt的文件并填入以下内容flask2.3.3 opencv-python4.8.0.74 mediapipe0.10.9 numpy1.24.3这个文件列出了我们项目需要的所有Python库flask: 用于构建Web API的轻量级框架。opencv-python: 强大的计算机视觉库用于读取、处理和保存图像。mediapipe: Google提供的机器学习管道框架内置了手部检测模型。numpy: 科学计算基础库处理数组数据。一键安装所有依赖 在项目目录下运行以下命令系统会自动下载并安装上述所有库。pip install -r requirements.txt如果安装速度较慢可以考虑使用国内的镜像源例如pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 项目目录结构规划清晰的目录结构能让代码更易于管理和维护。在开始编码前我们先规划好文件夹和文件。在hand_tracking_api文件夹内创建如下结构hand_tracking_api/ ├── app.py # Flask应用主入口文件 ├── utils/ │ └── hand_tracker.py # 手势识别核心逻辑类 ├── static/ │ └── uploads/ # 用于存放用户上传的图片 ├── templates/ │ └── index.html # 一个简单的Web测试页面可选 └── requirements.txt # 我们刚刚创建的依赖文件你可以使用以下命令快速创建这些目录和文件mkdir -p utils static/uploads templates touch app.py utils/hand_tracker.py templates/index.html现在我们的开发环境就准备好了接下来开始编写核心代码。3. 核心代码实现3.1 手势识别模块 (utils/hand_tracker.py)这个文件封装了所有与MediaPipe交互和图像处理的核心逻辑。我们创建一个HandTracker类。import cv2 import mediapipe as mp import numpy as np class HandTracker: def __init__(self): 初始化MediaPipe手部检测模型和绘图工具。 # 导入MediaPipe的绘图工具和手部解决方案 self.mp_drawing mp.solutions.drawing_utils self.mp_hands mp.solutions.hands # 初始化手部检测模型 # static_image_modeTrue: 设置为静态图片模式适合单张图片处理 # max_num_hands2: 最多检测2只手 # min_detection_confidence0.6: 手部检测的最小置信度阈值 # min_tracking_confidence0.5: 手部追踪的最小置信度阈值 self.hands self.mp_hands.Hands( static_image_modeTrue, max_num_hands2, min_detection_confidence0.6, min_tracking_confidence0.5 ) # 定义“彩虹骨骼”的颜色 (BGR格式: Blue, Green, Red) self.finger_colors [ (0, 255, 255), # 黄色 - 拇指 (128, 0, 128), # 紫色 - 食指 (255, 255, 0), # 青色 - 中指 (0, 255, 0), # 绿色 - 无名指 (0, 0, 255) # 红色 - 小指 ] # 定义每根手指在21个关键点中的起止索引范围 # MediaPipe的关键点顺序是固定的0是手腕1-4是拇指5-8是食指以此类推。 self.finger_segments [(1,4), (5,8), (9,12), (13,16), (17,20)] def draw_rainbow_skeleton(self, image, landmarks): 在原始图像上绘制彩虹色的手部骨骼和白色的关节点。 参数: image: 原始BGR格式的OpenCV图像。 landmarks: MediaPipe返回的21个手部关键点列表。 返回: 绘制了骨骼和关节点的新图像。 # 获取图像的宽度和高度 h, w, _ image.shape # 将MediaPipe返回的归一化坐标(0-1之间)转换为图像上的实际像素坐标 points [] for lm in landmarks: x_px int(lm.x * w) # x坐标 y_px int(lm.y * h) # y坐标 points.append((x_px, y_px)) # 第一步绘制所有白色的关节点21个点 for point in points: cv2.circle(image, point, 5, (255, 255, 255), -1) # -1表示实心圆 # 第二步为每根手指绘制彩色的骨骼线 for finger_idx, (start_idx, end_idx) in enumerate(self.finger_segments): color self.finger_colors[finger_idx] # 获取当前手指对应的颜色 # 连接当前手指的各个关键点形成骨骼 for i in range(start_idx, end_idx): pt1 points[i] # 当前点 pt2 points[i 1] # 下一个点 cv2.line(image, pt1, pt2, color, 2) # 绘制线段粗细为2像素 return image def process_image(self, image_path): 处理单张图片检测手部关键点并绘制彩虹骨骼。 参数: image_path: 待处理图片的路径。 返回: 一个包含检测结果和文件路径的字典。 # 使用OpenCV读取图片 image cv2.imread(image_path) if image is None: return {error: 无法读取图像文件请检查路径或文件格式} # MediaPipe需要RGB格式的图片而OpenCV默认读取为BGR所以需要转换 rgb_image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 将图片送入MediaPipe模型进行检测 results self.hands.process(rgb_image) # 如果没有检测到任何手部返回提示信息 if not results.multi_hand_landmarks: return {landmarks: [], message: 未在图片中检测到手部} # 复制一份原始图像用于绘制结果 output_image image.copy() all_landmarks [] # 用于存储所有检测到的手的关键点数据 # 遍历每一只检测到的手 for hand_landmarks in results.multi_hand_landmarks: # 提取这只手的21个关键点的3D坐标 (x, y, z) landmarks_3d [] for lm in hand_landmarks.landmark: landmarks_3d.append({ x: lm.x, # 归一化的x坐标 (0-1) y: lm.y, # 归一化的y坐标 (0-1) z: lm.z # 深度信息值越小表示离摄像头越近 }) all_landmarks.append(landmarks_3d) # 在这只手上绘制彩虹骨骼 output_image self.draw_rainbow_skeleton(output_image, hand_landmarks.landmark) # 生成结果图片的保存路径在原文件名前加‘result_’ import os dir_name os.path.dirname(image_path) base_name os.path.basename(image_path) result_filename fresult_{base_name} output_path os.path.join(dir_name, result_filename) # 保存绘制好的结果图片 cv2.imwrite(output_path, output_image) # 返回结构化的结果 return { hand_count: len(all_landmarks), # 检测到的手的数量 landmarks: all_landmarks, # 所有手的关键点坐标列表 result_image: output_path # 结果图片的保存路径 }代码要点解析颜色定义我们使用BGR格式定义了五种颜色分别对应五指这样画出来的骨骼就像彩虹一样。关键点索引MediaPipe定义了固定的21个关键点顺序从手腕0开始到指尖结束。我们的finger_segments列表精确地划分了每根手指的起止点。坐标转换MediaPipe返回的坐标是归一化的0到1之间我们需要乘以图片的宽高才能得到在图像上的实际像素位置。3.2 Flask API主程序 (app.py)这个文件是我们的Web服务入口负责接收HTTP请求、调用识别模块并返回结果。from flask import Flask, request, jsonify, send_file, render_template import os from utils.hand_tracker import HandTracker # 导入我们刚刚写的核心类 # 初始化Flask应用 app Flask(__name__) # 初始化手势追踪器全局单例避免重复加载模型 tracker HandTracker() # 配置上传文件的保存目录 UPLOAD_FOLDER static/uploads os.makedirs(UPLOAD_FOLDER, exist_okTrue) # 如果目录不存在则创建 app.config[UPLOAD_FOLDER] UPLOAD_FOLDER app.route(/) def index(): 根路径返回一个简单的HTML测试页面。 return render_template(index.html) app.route(/api/track, methods[POST]) def track_hand(): 核心API接口接收图片文件进行手势识别返回JSON结果。 请求方法: POST 表单字段: file (图片文件) # 1. 检查请求中是否包含文件 if file not in request.files: return jsonify({error: 请求中缺少‘file’文件字段}), 400 file request.files[file] # 2. 检查用户是否选择了文件 if file.filename : return jsonify({error: 未选择任何文件}), 400 # 3. 如果文件存在则保存到本地 if file: # 使用原始文件名生产环境建议使用uuid生成唯一文件名防止覆盖 filename file.filename filepath os.path.join(app.config[UPLOAD_FOLDER], filename) file.save(filepath) # 4. 调用手势识别模块处理图片 try: result tracker.process_image(filepath) # 如果处理过程中发生错误如图片无法读取返回错误信息 if error in result: # 删除已保存的无效文件 if os.path.exists(filepath): os.remove(filepath) return jsonify(result), 400 # 5. 处理成功返回包含关键点数据和结果图片路径的JSON return jsonify(result) except Exception as e: # 捕获其他未知异常返回500服务器内部错误 return jsonify({error: f服务器内部错误: {str(e)}}), 500 app.route(/result/filename) def result_image(filename): 提供一个接口让前端可以通过URL直接访问生成的结果图片。 例如: http://你的服务器地址/result/result_myhand.jpg # 安全起见可以在这里添加对filename的路径安全检查 return send_file(fstatic/uploads/{filename}) if __name__ __main__: # 启动Flask开发服务器 # host0.0.0.0 表示监听所有网络接口可以从外部访问 # port5000 是默认端口 # debugFalse 生产环境应设置为False app.run(host0.0.0.0, port5000, debugFalse)3.3 创建简易前端测试页面 (templates/index.html)为了方便测试我们创建一个最简单的HTML页面用于上传图片。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleAI手势识别API测试页面/title style body { font-family: sans-serif; max-width: 600px; margin: 40px auto; padding: 20px; } h2 { color: #333; } form { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 8px; } input[typefile] { margin: 10px 0; } button { background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #45a049; } #result { margin-top: 20px; } img { max-width: 100%; border: 1px solid #ccc; margin-top: 10px; } .error { color: red; } /style /head body h2️ AI手势识别与追踪 - 测试端/h2 p上传一张包含手部的图片建议使用“比耶”、“点赞”或“张开手掌”等清晰手势系统将自动识别并绘制彩虹骨骼。/p form iduploadForm label forfileInput选择图片文件 (JPG/PNG):/labelbr input typefile idfileInput namefile acceptimage/* required /brbr button typesubmit上传并分析/button /form div idresult !-- 结果将动态显示在这里 -- /div script document.getElementById(uploadForm).addEventListener(submit, async function(event) { event.preventDefault(); // 阻止表单默认提交行为 const fileInput document.getElementById(fileInput); const formData new FormData(); formData.append(file, fileInput.files[0]); const resultDiv document.getElementById(result); resultDiv.innerHTML p正在分析中请稍候.../p; try { const response await fetch(/api/track, { method: POST, body: formData }); const data await response.json(); if (response.ok) { resultDiv.innerHTML h3✅ 分析成功/h3 p检测到 strong${data.hand_count}/strong 只手。/p p关键点数据已返回下面是绘制了彩虹骨骼的结果图/p img src/result/${data.result_image.split(/).pop()} alt手势识别结果 / psmall结果图片路径: ${data.result_image}/small/p details summary查看原始关键点数据 (JSON)/summary pre${JSON.stringify(data.landmarks, null, 2)}/pre /details ; } else { resultDiv.innerHTML p classerror❌ 分析失败: ${data.error || 未知错误}/p; } } catch (error) { resultDiv.innerHTML p classerror❌ 网络请求失败: ${error.message}/p; } }); /script /body /html这个页面包含一个文件上传表单和一个用于显示结果的区域。当用户选择图片并提交后JavaScript代码会通过fetchAPI 将图片发送到我们的/api/track接口然后动态地将返回的结果包括图片和关键点数据展示在页面上。4. 服务部署与接口测试4.1 启动Flask服务所有代码准备就绪后启动服务就非常简单了。确保你的终端位于hand_tracking_api项目根目录下然后运行python app.py如果一切正常你将会看到类似下面的输出* Serving Flask app app * Debug mode: off WARNING: This is a development server. Do not use it in a production deployment. * Running on all addresses (0.0.0.0) * Running on http://127.0.0.1:5000 * Running on http://你的本地IP地址:5000这表示你的手势识别API服务已经在本地5000端口成功启动了。4.2 通过Web界面测试打开你的浏览器。在地址栏输入http://127.0.0.1:5000或http://你的本地IP地址:5000。你应该能看到我们刚刚创建的测试页面。点击“选择文件”上传一张包含手部的图片建议手势清晰背景不要太杂乱。点击“上传并分析”。稍等片刻通常不到1秒页面下方就会显示分析结果检测到的手的数量、绘制了彩虹骨骼的结果图以及可以展开查看的详细关键点坐标数据。4.3 通过API工具测试如Postman或cURL除了网页你还可以直接调用API接口这对于集成到其他后端服务或移动端非常有用。使用cURL命令测试curl -X POST -F file/你的图片路径/your_hand.jpg http://127.0.0.1:5000/api/track命令执行后会直接返回一个JSON格式的结果。API接口规范总结端点 (Endpoint):POST /api/track请求格式:multipart/form-data参数:file: (必填) 图片文件支持JPG、PNG等常见格式。成功响应 (HTTP 200):{ hand_count: 1, landmarks: [ [ {x: 0.512, y: 0.643, z: -0.031}, {x: 0.489, y: 0.601, z: -0.045}, // ... 共21个关键点 ] ], result_image: static/uploads/result_your_hand.jpg }错误响应:400 Bad Request: 缺少文件或文件无效。500 Internal Server Error: 服务器内部处理错误。5. 进阶优化与生产建议我们的基础服务已经可以运行了但如果想要用于更正式的场景还需要考虑一些优化和安全问题。5.1 性能优化建议虽然MediaPipe在CPU上已经很快但我们还可以做得更好调整图片尺寸对于网络传输或实时性要求高的场景可以在处理前将图片缩放到一个固定尺寸如640x480这能显著减少计算量。# 在process_image方法中读取图片后添加 target_width 640 height, width image.shape[:2] scale target_width / width new_height int(height * scale) image cv2.resize(image, (target_width, new_height))模型参数调优根据你的场景调整min_detection_confidence和min_tracking_confidence。提高阈值可以减少误检但可能漏检降低阈值则相反。启用缓存如果频繁处理相同图片可以考虑对结果进行缓存。5.2 增强安全性与健壮性文件类型和大小限制防止用户上传恶意文件或过大的文件。from flask import abort from werkzeug.utils import secure_filename ALLOWED_EXTENSIONS {png, jpg, jpeg, gif} MAX_CONTENT_LENGTH 5 * 1024 * 1024 # 5MB def allowed_file(filename): return . in filename and filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS # 在track_hand函数中保存文件前检查 if not allowed_file(file.filename): return jsonify({error: 不支持的文件类型}), 400 # 使用安全文件名防止路径遍历攻击 filename secure_filename(file.filename)异步处理对于处理时间可能较长的请求如高分辨率图片可以考虑使用Celery等工具进行异步任务队列处理避免HTTP请求超时。输入验证除了文件如果API有其他参数也需要进行验证。5.3 扩展功能思路这个基础服务可以很容易地扩展出更多有趣的功能手势识别基于返回的21个关键点坐标可以计算手指间的角度、距离从而定义出特定的手势如握拳、OK、比耶等并返回手势标签。视频流处理将接口改造成支持视频流输入如通过WebSocket实现实时的手势识别与追踪。多模型集成除了手部MediaPipe还提供人脸、姿态、物体检测等模型可以构建一个多功能的视觉感知API。Docker容器化将整个应用打包成Docker镜像实现一次构建随处运行极大简化部署流程。6. 总结6.1 核心收获回顾通过本篇教程我们完成了一个从零到一、功能完整的AI手势识别API服务的搭建。我们主要实现了环境与项目搭建清晰规划了项目结构并安装了必要的依赖。核心识别模块封装了MediaPipe Hands模型实现了高精度的21点3D手部关键点检测并创新性地加入了“彩虹骨骼”可视化算法让结果更加直观。Flask API服务构建了健壮的RESTful API接口包括文件上传、错误处理、结果返回等功能。前端测试界面提供了一个简单的Web页面方便进行功能测试和演示。部署与测试成功在本地启动了服务并通过多种方式验证了接口的有效性。这个服务完全在CPU上运行无需任何外部网络请求保证了低延迟和高稳定性非常适合集成到需要本地化、实时性手势交互的应用中。6.2 下一步行动建议你已经拥有了一个强大的手势识别引擎接下来可以直接集成将API服务部署到你的服务器供你的Web应用、移动App或桌面程序调用快速添加手势交互功能。深入开发参考“进阶优化”部分增强服务的性能、安全性和功能。探索更多可能结合其他AI模型如语音识别、表情识别创造更丰富的多模态交互体验。希望这个项目能成为你探索AI应用开发的一个有力起点。动手尝试修改代码添加你想要的功能你会发现构建AI应用并没有想象中那么困难。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。