资讯动态

OpenCV+MediaPipe+CNN:手势控制鼠标的完整实现指南

发布时间:2026/9/24 18:54:50 来源:尧图企业网站定制
简介这套基于OpenCV、MediaPipe与CNN的手势识别项目面向计算机视觉和人机交互学习者用指尖的相对移动和运动速度控制鼠标指针通过特定指尖动作完成点击、滚动页面并支持手势触发快捷键与虚拟键盘输入。项目提供可直接运行的exe程序、完整Python源码、设计文档和操作说明覆盖从模型加载、关键点检测到事件映射的完整流程适合作为毕业设计、课程设计或入门实践。压缩包共109个文件大小约300.75MB结构上以38个Python脚本为核心配套XML界面配置、pb模型文件、QSS样式表、PNG/JPG图片素材及说明文档模块划分清晰便于对照学习和二次开发。资源目前已有254人学习或下载作者说明代码均测试运行成功下载后可按README或设计文档快速搭建环境并验证效果。整体上这是一套可运行、可复现的完整工程样本尤其适合想借助实际项目掌握手势识别控制鼠标键盘方法的读者。1. 手势控制鼠标为什么偏偏是 OpenCV、MediaPipe、CNN 三个模块组合用手势控制鼠标听起来像科幻其实拆开就是三个开源模块的串接OpenCV 负责把摄像头画面变成一帧帧能处理的图像MediaPipe 从图像里找到手并抽出 21 个关键点的坐标CNN 拿这些关键点拼成的图像判断你比的是哪种手势最后把手势翻译成鼠标事件。很多初学者上来就想用一个端到端模型同时完成“检测手在哪”和“识别手势是什么”但实际项目里这么做既慢又难调——实时性、误触发、不同人手差异都会让你崩溃。这个方向适合想在 PC 上跑通一套完整人机交互 pipeline 的开发者也适合做毕业设计或桌面工具原型。下面按我自己的落地顺序把架构、数据集、训练、鼠标映射和打包后的坑一步步讲清楚。2. 从摄像头到手势类别OpenCV、MediaPipe、CNN 各自在解决什么2.1 为什么不是单模型端到端而是三分工早期手势识别常见做法是用 YOLO 这类目标检测模型直接框出手和手势类别好处是部署单一坏处是你得手工准备大量手势边界框数据集而且手部小目标检测在摄像头实时流里经常漏检。我这个项目用的是严格分工OpenCV 管图像输入和输出。它从摄像头读帧把 BGR 转成后续模块要的格式最后把识别结果画在画面上给人眼反馈。OpenCV 在整个链路里是最“脏”的活但不参与语义判断。MediaPipe 管手部感知。它输出的是 21 个手部关键点每个点有归一化坐标。这个模块本身也是一个深度模型但它是 Google 训练好的我们直接用。它解决了“手在哪、手指关节在哪”的问题而我们不需要关心它是怎么训练出来的。CNN 管手势分类。MediaPipe 只给出关键点坐标并没有告诉你这是“握拳”还是“张手”。CNN 负责从关键点结构里学出空间规律。为什么图像处理要用 CNN 而不是前馈神经网络原因很实在25 个全连接层的前馈网络接收一张 64×64 的图像第一层就要学 4096 个像素的全局组合参数爆炸不说手指稍微平移几个像素特征向量就全变了。CNN 用卷积核做局部感受野同一根手指不管出现在画面左边还是右边卷积核都能提取到相近的特征这就是平移不变性。手势识别恰恰需要这种性质——手的绝对位置不重要重要的是手指之间的相对结构。CNN 和 RNN 的选择也很简单RNN 用来处理序列时序我们这里单帧手势是独立判断的没有前后依赖所以用 CNN。2.2 OpenCV 的预处理BGR、镜像、分辨率与 FPSOpenCV 的 VideoCapture 读出来的帧默认是 BGR 通道顺序MediaPipe 要求 RGB这里必须转换否则手的肤色和轮廓会被通道顺序搞乱。另一个常见问题是镜像摄像头拍你的时候你往左挥手画面里你是往右移动的。如果直接把坐标映射到鼠标光标会和你反着走所以读帧后要水平翻转让操作符合正常直觉。分辨率不是越高越好。MediaPipe 在 1280×720 和 640×480 下的检测耗时差很多但关键点精度差别不大。我一般把采集分辨率设为 640×480这样 CNN 推理和鼠标控制的总延迟能控制在 100ms 以内。FPS 方面OpenCV 默认的 VideoCapture 是不限帧率的但 MediaPipe 单帧处理需要时间实际跑起来可能只有 15~20 帧。这时不要开线程去无限读帧否则会积压帧队列导致延迟越来越大。简单做法是在主循环里每次读取一帧并处理让摄像头帧率自然跟随处理速度。2.3 最小可运行的摄像头读取与预处理代码下面这段是最小可用的 OpenCV 采集代码也是整个项目的入口import cv2 import mediapipe as mp cap cv2.VideoCapture(0) # 0 表示默认摄像头 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) mp_hands mp.solutions.hands hands mp_hands.Hands( static_image_modeFalse, max_num_hands1, min_detection_confidence0.7, min_tracking_confidence0.5, ) mp_draw mp.solutions.drawing_utils while cap.isOpened(): ret, frame cap.read() if not ret: break frame cv2.flip(frame, 1) # 镜像翻转避免光标移动方向相反 rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # OpenCV 默认 BGRMediaPipe 要 RGB rgb.flags.writeable False # 防止 MediaPipe 内部修改 RGB 数据 results hands.process(rgb) if results.multi_hand_landmarks: for landmarks in results.multi_hand_landmarks: mp_draw.draw_landmarks(frame, landmarks, mp_hands.HAND_CONNECTIONS) cv2.imshow(Gesture Mouse, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码的逻辑很直白每次循环从摄像头读一帧翻转后转成 RGB 交给 Hands 模型推理得到的关键点画回原图。有两个参数影响很大。min_detection_confidence是手部检测置信度阈值0.7 比较平衡调到 0.9 会减少误检但手稍微离远点就检测不到。min_tracking_confidence是跟踪阈值0.5 表示检测到手后后续帧只要跟踪置信度大于 0.5 就沿用上一帧的关键点这样能跳过部分检测计算提升帧率。注意rgb.flags.writeable False这个细节如果不设MediaPipe 内部可能会修改数组内容而且还会警告你性能变差。3. 用 MediaPipe 把关键点转成 CNN 能吃的输入数据集构建与参数选择3.1 Hands 模型输出的 21 个关键点到底怎么用MediaPipe Hands 返回的landmarks是一个包含 21 个点的列表每个点有x, y, z三个量其中x和y是相对画面宽高的归一化坐标0 到 1z是相对手腕的深度信息。关键点编号从 0 到 200 是手腕1 到 4 是大拇指指根到指尖5 到 8 是食指9 到 12 是中指13 到 16 是无名指17 到 20 是小拇指。做手势分类时最常用是 8食指指尖、4拇指指尖、12中指指尖这几个点的组合因为握拳、张开、比一这些手势主要靠指尖相对位置区分。但如果你只把 21 对 (x, y) 坐标拉平成一个 42 维向量喂给神经网络模型也能学但没有发挥 CNN 的空间结构优势。更稳的做法是把关键点画到一张空白的图像上用线段连接相邻关节把这张“骨架图”作为 CNN 的输入。这样做的好处是骨架图不受衣服、肤色、光照和背景干扰CNN 学到的不是“这只手是黑皮肤还是白皮肤”而是“食指和中指之间是否分开”这种结构规律。实践中我对比过直接喂坐标向量在训练集上能到 99% 的准确率但一到摄像头实时画面换了个人就掉到 80%用骨架图之后跨人的泛化能力明显更好。3.2 为什么画骨架图而不是直接使用关键点坐标骨架图还顺便解决了关键点抖动问题。MediaPipe 在单帧上的关键点会有微小抖动直接传坐标给分类器这种抖动会被当成本质特征学进去。而画成图像后只要抖动不超过几个像素卷积核提取到的边缘和角点特征基本不变。我把关键点坐标缩放到一个固定尺寸的画布上比如 128×128用线条把有连接关系的关键点连起来指尖点单独画一个实心圆表示位置。如下图所示意的数据结构import numpy as np import cv2 CANVAS_SIZE 128 SKELETON_CONNECTIONS [ (0, 1), (1, 2), (2, 3), (3, 4), (0, 5), (5, 6), (6, 7), (7, 8), (5, 9), (9, 10), (10, 11), (11, 12), (9, 13), (13, 14), (14, 15), (15, 16), (13, 17), (17, 18), (18, 19), (19, 20), (0, 17) ] def landmarks_to_sketch(landmarks, sizeCANVAS_SIZE): sketch np.zeros((size, size, 1), dtypenp.float32) pts [] for lm in landmarks: px int(lm.x * size) py int(lm.y * size) pts.append((px, py)) for start, end in SKELETON_CONNECTIONS: cv2.line(sketch, pts[start], pts[end], 1.0, thickness2) for idx in [4, 8, 12, 16, 20]: # 五个指尖加粗 cv2.circle(sketch, pts[idx], 4, 1.0, thickness-1) return sketch这段代码里landmarks_to_sketch把 21 个点的归一化坐标映射到 128×128 的画布然后按照手的骨骼连接关系画线。SKELETON_CONNECTIONS里的连接关系对应 MediaPipe 官方定义的 HAND_CONNECTIONS但这里显式写出来方便你调整。指尖点用圆点加粗因为很多手势点击、捏合最关键的信息就在指尖距离上。注意画布是单通道 float32值域 0 到 1后面喂给 CNN 不用再做归一化。为什么不用三通道因为骨架图本身没有颜色信息单通道能减少 CNN 计算量而且单通道训练收敛更快。3.3 录制手势数据集用键盘打标签边录边存有了关键点转骨架图的函数下一步就是录制训练样本。常见做法是打开摄像头在手势稳定的前提下按键盘数字键打标签每按一次把当前帧的骨架图存成 npy 文件。这样比先录视频再离线提取要省事得多。下面这段代码是我录制时的核心逻辑重点在保存和标签计数import os import cv2 import numpy as np import mediapipe as mp GESTURE_LABELS {0: open_palm, 1: fist, 2: point_up, 3: peace} DATA_DIR gesture_data os.makedirs(DATA_DIR, exist_okTrue) mp_hands mp.solutions.hands hands mp_hands.Hands(static_image_modeFalse, max_num_hands1, min_detection_confidence0.7) cap cv2.VideoCapture(0) current_label 0 counter 0 while True: ret, frame cap.read() if not ret: break frame cv2.flip(frame, 1) rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results hands.process(rgb) if results.multi_hand_landmarks: landmarks results.multi_hand_landmarks[0].landmark sketch landmarks_to_sketch(landmarks) # 边录边看原始画面便于确认手势 cv2.putText(frame, flabel{current_label} count{counter}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow(Record, frame) # 同时显示骨架图确认转换没有出错 cv2.imshow(Sketch, sketch) key cv2.waitKey(1) 0xFF if key in GESTURE_LABELS: current_label key elif key ord(s) and results.multi_hand_landmarks: np.save(os.path.join(DATA_DIR, f{GESTURE_LABELS[current_label]}_{counter}.npy), sketch) counter 1 print(fSaved label{GESTURE_LABELS[current_label]}, count{counter}) elif key ord(q): break cap.release() cv2.destroyAllWindows()参数说明static_image_modeFalse表示视频流模式MediaPipe 会先检测后跟踪如果改为True每一帧都做检测准确率稍高但速度慢一半建议录制时也用 False这样才能模拟真实使用场景。保存的文件名里带标签和序号方便之后读取。这里的sketch是 (128,128,1) 的 float32 数组np.save会直接保存为你需要的训练数据格式。注意录制时手要完整出现在画面里尤其指尖不能切出画面边界否则关键点坐标会被钳制在 0 到 1 的边缘生成的骨架图上手指连成一条直线模型会被这种脏数据污染。每种手势至少录 300 帧。录制时要有意识地变换手的角度、距离、左右手最好在不同光照条件下录几组。用 YOLO 手势识别数据集的人可能会想直接下载公开数据但 YOLO 数据集给的是矩形框不是关键点标注你仍然要跑一遍 MediaPipe 去提取关键点不如自己录来得干净。另外我建议每类手势单独存一个子目录不要写在同一个文件名里这样方便训练时做分层采样。4. CNN 手势分类模型结构设计、训练参数与踩过的调参坑4.1 CNN 模型的输入与输出骨架图到手势类别的映射CNN 的输入是上一章生成的 128×128 单通道骨架图输出是 4 类手势的概率分布。这里用 4 类作为最小示例open_palm张手、fist握拳、point_up食指指天、peace比耶。如果你想控制更多操作可以增加“食指中指同伸”“拇指小指”等类别但每增加一类录制数据的量至少翻倍否则分类器会混淆。为什么不用循环神经网络 RNN 或者 Transformer 处理这个任务因为单帧手势分类是静态图像分类没有时序依赖。RNN 适合读句子、看视频这种有先后顺序的输入强制把骨架图序列当成时间序列会使问题变复杂。有些项目尝试用 Transformer 做关键点序列分类但那是为了识别动态手势比如挥手、画圈而我们这里控制鼠标需要的是单帧稳定映射用 CNN 是最直接、最容易调通的路子。4.2 搭建和训练 CNN 的完整代码下面是可以用 Keras 直接跑起来的最小 CNN 模型。为了让新手能复现我把模型结构、数据加载和训练写在一个脚本里import numpy as np import os from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout from tensorflow.keras.utils import to_categorical from tensorflow.keras.preprocessing.image import ImageDataGenerator from sklearn.model_selection import train_test_split from sklearn.preprocessing import LabelEncoder # ---------- 1. 加载 npy 数据 ---------- DATA_DIR gesture_data X [] y [] for fname in os.listdir(DATA_DIR): if fname.endswith(.npy): label_str fname.split(_)[0] sketch np.load(os.path.join(DATA_DIR, fname)) X.append(sketch) y.append(label_str) X np.array(X) # (N, 128, 128, 1) y np.array(y) # ---------- 2. 标签编码与划分 ---------- le LabelEncoder() y_encoded le.fit_transform(y) y_onehot to_categorical(y_encoded) X_train, X_test, y_train, y_test train_test_split( X, y_onehot, test_size0.2, stratifyy_onehot, random_state42 ) # ---------- 3. 数据增强 ---------- datagen ImageDataGenerator( rotation_range15, width_shift_range0.1, height_shift_range0.1, zoom_range0.1, fill_modenearest ) datagen.fit(X_train) # ---------- 4. 构建 CNN ---------- model Sequential([ Conv2D(32, (3, 3), activationrelu, paddingsame, input_shape(128, 128, 1)), MaxPooling2D(pool_size(2, 2)), Conv2D(64, (3, 3), activationrelu, paddingsame), MaxPooling2D(pool_size(2, 2)), Conv2D(128, (3, 3), activationrelu, paddingsame), MaxPooling2D(pool_size(2, 2)), Flatten(), Dense(128, activationrelu), Dropout(0.5), Dense(len(le.classes_), activationsoftmax) ]) model.compile(optimizeradam, losscategorical_crossentropy, metrics[accuracy]) # ---------- 5. 训练 ---------- history model.fit( datagen.flow(X_train, y_train, batch_size32), validation_data(X_test, y_test), epochs50, verbose1 ) model.save(gesture_cnn.keras)模型结构参数说明三组Conv2D MaxPooling2D逐层把空间尺寸从 128 降到 16通道数从 32 升到 128。这种先降分辨率、再升通道的设计是 CNN 图像分类标配能保证高层特征既保留空间结构又有足够的抽象表达能力。Dropout(0.5)放在全连接层后防止模型记住训练集里的角度和位置导致换个人就失灵。训练时epochs50通常够了数据增强已经把每个 batch 的样本做了随机旋转和平移所以即使只有 1200 张图模型也能见到多种变体。训练完成后看两个指标训练准确率应该接近 100%验证准确率至少 95%。如果验证准确率一直卡在 75%~85%常见原因是某两类手势的骨架图太相似比如point_up和peace在角度偏斜时中指的轮廓会重叠。这时不要盲目加卷积层优先检查录制数据里这两类的图片是不是真的可分再决定是补录数据还是合并类别。4.3 训练时容易忽略的三个参数细节第一个是ImageDataGenerator的fill_modenearest。骨架图旋转时边缘会空出来默认填 0 会引入黑色边框CNN 会把这边框当成特征。改为nearest后用邻近像素填充虽然不完美但至少不会产生突兀的黑色边界。第二个是batch_size我用 32如果显存不够会报 OOM改小到 16 或 8但验证准确率会略有波动。第三个是paddingsame如果去掉每次卷积后尺寸变小三层之后 128 会变成 122虽然也能算但边缘关键点比如手腕位置的信息会更快被丢失所以一定要用 same padding。5. 鼠标操控映射与避坑为什么光标乱飞、点击没反应、打包后黑屏5.1 从手势到鼠标坐标归一化、平滑与灵敏度当 CNN 输出手势类别后还需要把关键点坐标映射到屏幕坐标。映射的核心是确定“用什么点代表人想移动的位置”。常见做法是用食指指尖坐标关键点 8但缺点是食指移动幅度太大微调鼠标很不舒服。我用手腕点关键点 0作为基准因为手腕在画面里相对稳定能够反映整只手的水平移动意图而食指指尖专门用来做点击判断。映射公式很简单import ctypes import numpy as np import time # 获取屏幕尺寸 SM_CXSCREEN 0 SM_CYSCREEN 1 screen_w ctypes.windll.user32.GetSystemMetrics(SM_CXSCREEN) screen_h ctypes.windll.user32.GetSystemMetrics(SM_CYSCREEN) smooth_x, smooth_y 0.3, 0.3 # 平滑系数值越大光标越跟手但越抖 def hand_landmark_to_mouse(landmarks, prev_mouse, cur_mouse): wrist landmarks[0] # 取手腕关键点 # 关键点坐标是 0-1 的归一化值直接乘屏幕宽高 raw_x wrist.x * screen_w raw_y wrist.y * screen_h # 指数平滑新位置 上一位置 * (1-alpha) 原始坐标 * alpha mx prev_mouse[0] * (1 - smooth_x) raw_x * smooth_x my prev_mouse[1] * (1 - smooth_y) raw_y * smooth_y cur_mouse[0] int(mx) cur_mouse[1] int(my) return cur_mouse这里我用了指数平滑而不是直接移动是因为 MediaPipe 关键点在视频流里会有几个像素的抖动如果直接移动鼠标光标会像触电一样抖个不停。smooth_x和smooth_y是平滑系数0.3 意味着 70% 保留上一帧的位置相当于低通滤波。系数调大比如 0.8会更跟手但手指轻微晃动也会被放大。实际调参时建议从 0.3 开始手感太肉就调到 0.5太飘就降到 0.2。注意有些项目用类似pyautogui.moveTo的 API那个自带 0.1 秒的延迟而且有 fail-safe 机制鼠标移到屏幕角落会触发异常用起来很憋屈所以我直接用ctypes调用 Win32 API 移动鼠标响应在几毫秒级。5.2 点击事件为什么不能检测到握拳就立即触发如果检测到fist手势就调用mouse_down()你会发现只想点一次结果系统执行了十几次点击。原因是摄像头每帧都会识别到握拳而鼠标左键按下事件持续触发。正确的做法是维护一个状态机只有“从非点击手势变成点击手势”的那一帧才触发按下松开时触发抬起。比如定义open_palm为鼠标松开状态fist为按下状态那么当上一帧是open_palm、当前帧是fist时才执行一次mouse_down()。下面这段代码实现了这个逻辑prev_gesture None is_mouse_down False def handle_click(gesture): global prev_gesture, is_mouse_down if gesture fist and not is_mouse_down: ctypes.windll.user32.mouse_event(2, 0, 0, 0, 0) # 左键按下 is_mouse_down True elif gesture open_palm and is_mouse_down: ctypes.windll.user32.mouse_event(4, 0, 0, 0, 0) # 左键抬起 is_mouse_down False prev_gesture gesture参数说明mouse_event的第一个参数2表示左键按下4表示左键抬起这是 Windows 标准的 event 常量。这里把“握拳”当成“按下左键拖动”“张手”当成“松开”。如果你只想单击可以这样检测到握拳后等待 0.2 秒在 0.2 秒内如果手势变成张手就只做一次点击否则进入拖拽模式。这个简单的时序判断能避免大量误操作。5.3 鼠标操控常见问题排查3 个真实踩坑记录踩坑一光标移动方向是反的或者上下颠倒。现象是手往左移动光标往右手往上移光标往下。原因是 OpenCV 读帧后没有做镜像导致画面里的坐标系和你实际操作方向相反。解决就是在读帧后立即执行cv2.flip(frame, 1)。另外如果你把关键点的y坐标直接映射屏幕纵坐标屏幕原点在左上角摄像头画面里手抬高时y值减小所以光标向上是正常的不要画蛇添足加一个负号。踩坑二点击不跟手偶尔会连点或漏点。现象是握拳后鼠标会执行好几下点击或者明明握拳了却没反应。原因有两个一是状态机的重置没有处理好上一帧手势识别错误导致状态翻转二是关键点在握拳和张开之间抖动CNN 输出的概率一会是fist一会是open_palm。我的解决办法是给手势输出加一个 3 帧的防抖缓存连续 3 帧都是同一类才改变状态效果立竿见影。代码可以这样实现gesture_buffer [] def stable_gesture(gesture, buffer_size3): global gesture_buffer gesture_buffer.append(gesture) if len(gesture_buffer) buffer_size: gesture_buffer.pop(0) from collections import Counter counter Counter(gesture_buffer) most_common counter.most_common(1)[0][0] if counter[most_common] buffer_size - 1: return most_common return gesture # 没有稳定时返回当前帧结果踩坑三打包成 exe 后模型加载失败提示文件不存在。现象是你用 PyInstaller 打包后在别的电脑上双击 exe打开摄像头黑屏或者加载 CNN 模型时报文件找不到。原因是 PyInstaller 打包后模型文件被解压到临时_MEIPASS目录代码里写的相对路径指向的是当前工作目录当然找不到。解决方法是把模型路径改为使用sys._MEIPASS并配合os.path.join定位。一个通用写法是import sys import os def resource_path(relative_path): base_path getattr(sys, _MEIPASS, os.path.abspath(.)) return os.path.join(base_path, relative_path) model_path resource_path(gesture_cnn.keras)注意打包时要在 PyInstaller 的 spec 文件里把.keras文件加到datas否则_MEIPASS目录里也不会包含它。这些坑不是理论问题是每个做桌面手势应用的人都会遇到的。6. 进阶一档动态手势、延迟验证与换到其他平台如果你跑通了上面的基础版本可以进一步把手势控制玩出更多花样。第一个是滚轮识别出食指和中指同时伸开且指尖向上就发送滚轮上滑事件向下则是滚轮下滑。第二个是拖拽握拳状态下手腕移动就是按住左键拖动这在上面的状态机里已经实现了。第三个是双指缩放用拇指指尖和食指指尖的欧氏距离变化来控制 Ctrl加号和 Ctrl减号模拟浏览器缩放。验证系统实时性有一个我自己常用的土办法在屏幕上显示一个随机移动的目标点你用手势移动光标去点击记录点击命中率和从目标出现到实际点击的时间差。目标点可以用 OpenCV 的cv2.circle在单独窗口画出来点击成功就换新点。连续操作 20 次如果命中率在 80% 以上同时光标移动流畅无撕裂这个系统就基本可以给别人演示了。如果延迟超过 300ms优先检查是否为上面说的平滑系数太大或者是否开了额外的cv2.imshow窗口占用主线程。这个方案的下一步也可以迁移到其他场景比如把鼠标移动替换成发送 UDP 指令就能控制 Tello 无人机的飞行姿态想用红外手势识别的话换一个能输出深度图的摄像头MediaPipe 对深度图的兼容性同样支持核心骨架图转换逻辑不用改。如果你想省掉 CNN 训练可以用 MediaPipe Model Maker 自定义手势分类器它基于更轻量的模型 Edge 架构能在树莓派上跑实时推理但需要准备一套带标注的手势视频训练时间和产物体积会跟你的数据量直接挂钩。最后说一个我自己的习惯每次调参前我会先把视频流、关键点绘制、CNN 预测结果和鼠标事件日志四个信号叠加显示在一个画面上这样出问题时能一眼看出是检测环节坏了还是映射环节坏了。这个调试画面我会保留到项目结束而不是放到说明书里。说句实在话这个项目最大的价值不在“识别出多少种手势”而在于让你把摄像头、感知模型和系统程序之间的延迟、坐标、状态这些边界问题真正理解一遍。我最早用原图直接训 CNN准确率看着很高一换灯光就崩改成关键点骨架图以后才体会到“特征工程”在深度学习里依然重要。希望这个拆解能帮你少走我走过的弯路——希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价