🍓 第十二章 AI 实验范例(二):进阶计算机视觉+AI 识别-TensorFlow+MediaPipe+CVZone
📄 一句话概括

用 CVZone 的 fingersUp 数出五根手指的伸直状态,再用三条 if 把【剪刀/石头/布】判出来——这一章的 AI 只有前一半:检测靠 MediaPipe 的模型;后一半【这是布】的判断是你自己写的规则,没有训练任何东西。

📝 本节和前面几节的关系

12-2 已经给了全部家底(findHands+fingersUp),本节是第一次完整走通【取帧→检测→判断→显示】的实验闭环——它和 11-5 的 YOLO 循环、12-3 的推理循环是同一副骨架。学完你会发现:视觉实验的【代码结构】其实只有一种,变的只是中间那段【判断逻辑】。

一、先把语义掰正:这不是训练,是规则判断

教材把本节叫【AI 实验范例】,但严格说:

为什么不直接训练一个手势分类模型?——可以(树莓派上跑 TFLite 手势模型也行),但对三个类别的清晰手势,规则判断又快又稳又好调试:认错了你一眼能看出是哪根手指判错。先规则后模型是边缘 AI 的实用主义顺序。

二、判据:五根手指的真值表

fingersUp 返回 [拇指, 食指, 中指, 无名指, 小指] 的 0/1 列表,三种手势的真值表就固定下来了:

手势 fingersUp 记法
布 [1, 1, 1, 1, 1] 全伸
石头 [0, 0, 0, 0, 0] 全弯
剪刀 [0, 1, 1, 0, 0] 只伸食指+中指

前提纪律(12-2 误区 4 的实战版):手腕朝上、掌心或手背正对摄像头;手腕一翻,fingersUp 的上下几何全乱,真值表作废。

三、动手 Demo:实时手势识别

# demo12_4_rps.py —— 剪刀石头布实时识别
import cv2, cvzone
from cvzone.HandTrackingModule import HandDetector

detector = HandDetector(detectionCon=0.8, maxHands=1)
cap = cv2.VideoCapture(0)
cap.set(3, 640); cap.set(4, 480)

def classify(fingers):
    if fingers == [1, 1, 1, 1, 1]:
        return "Paper"
    if fingers == [0, 0, 0, 0, 0]:
        return "Rock"
    if fingers == [0, 1, 1, 0, 0]:
        return "Scissors"
    return "..."

while True:
    ok, img = cap.read()
    if not ok:
        break
    hands, img = detector.findHands(img)
    if hands:
        fingers = detector.fingersUp(hands[0])
        gesture = classify(fingers)
        cvzone.putTextRect(img, gesture, (40, 60), scale=2,
                           colorR=(0, 128, 255))
    cv2.imshow("RPS", img)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
cap.release(); cv2.destroyAllWindows()

看到什么算成功:摆出剪刀手,窗口显示 Scissors;握拳变 Rock;张开变 Paper——三种手势切换无明显迟滞。画面上方偶尔显示 ...(过渡姿态),属正常。

读结果:classify 是纯函数——输入五个 0/1、输出字符串,和摄像头毫无关系。这个设计让你可以先离线测逻辑、再接摄像头(见 30 秒实验),是【检测与判断解耦】的最小示范。

四、30 秒实验:不接摄像头,先测判断逻辑

把 classify 抄进一个新文件,喂它假数据:

cases = {
    "布":   [1, 1, 1, 1, 1],
    "石头": [0, 0, 0, 0, 0],
    "剪刀": [0, 1, 1, 0, 0],
    "六指?": [1, 1, 1, 1, 0],
    "比心": [0, 1, 0, 0, 1],
}
for name, fingers in cases.items():
    print(name, "->", classify(fingers))

看到什么算成功:前三行正确映射;后两行打印 ...——未定义的姿态落进兜底分支而不是乱猜。亲手验证一遍真值表,比对着屏幕摆十次手势都更能确信【逻辑层没有 bug】;以后摄像头识别错了,你就知道该查检测层(12-2 的几何问题)而不是这里。

五、往前走一步:三个自然的延伸

  1. 猜拳对战:加 random.choice(["Rock", "Paper", "Scissors"]) 当电脑出招,定格判断胜负——教材下一章的方向。
  2. 手势当遥控器:Rock 时开灯、Paper 时关灯——用 8-5 的串口三规矩发给 Arduino,或用 9-4 的 machine.UART 发给 Pico;手势版智能家居就此成立。
  3. 升级成模型分类:姿态更复杂(数字 1~10、比 OK)后真值表爆炸,那时再换 TFLite 手势分类模型——规则的尽头是模型。

六、常见误区

  1. 以为要做什么训练——本节没有一行训练代码;MediaPipe 检测是现成的,映射是手写的。分清这两半,出错才知道找谁。
  2. 拿 count 当判据——伸直数量 2 可能是剪刀也可能是【比耶走样】的任何姿态;必须比较整张真值表,不能只数个数。
  3. 过渡态没有兜底——手在半空切换姿势时 fingersUp 是中间值,没有 return "..." 兜底就会闪烁显示错误手势;凡分类必留【不确定】出口。
  4. 在镜像画面上左右不分——findHands 的 flipType=True 默认把画面当镜像处理,做【左右手区分】的延伸实验时记得这个参数在管什么。
自测三问(点开对答案) 1. 本节的【AI】具体指哪部分?——只有手部检测(MediaPipe 预训练模型);剪刀/石头/布的判断是手写 if 规则。 2. 为什么不能用【伸直手指的数量】当判据?——数量不唯一对应手势(数量 2 的姿态不止剪刀),必须比较完整真值表。 3. 检测与判断解耦的好处是什么?——classify 是纯函数可离线测试;摄像头识别错误时能立刻定位该查哪一层。

相关笔记