CVZone 是盖在 OpenCV 与 MediaPipe 上面的一层【积木】:把 MediaPipe 三十行的手部检测代码压成三行,把画角框、带底色文字、拼九宫格这些样板活封装成函数——它自己不是 AI 模型,是省你打字的封装层。
上一节你已经手动用了 MediaPipe(下载模型、转 RGB、读比例坐标——每步都得自己来);本节的 CVZone 把这些样板全部吞进肚子里。关系不是替代而是分工:12-1 教你看清流水线内部,12-2 让你以后写实验时不必每次重搭。11-5 的 YOLO 三行出结果,本节的 CVZone 是【手部任务版的三行出结果】。
一、CVZone 是什么:积木层的定位
把本章三个工具摆在一起看就清楚了:
你写的实验代码
↓
CVZone ← 12-2:三行搞定手部/人脸/姿态,附赠绘图工具箱
↓
MediaPipe ← 12-1:Google 的检测流水线(真模型在这里)
↓
OpenCV ← 11-2:读图、显示、画框(视觉界的瑞士军刀)
CVZone 干的三类事:
1. 压缩样板:HandDetector 一行创建,findHands 一行调用,自动画好骨架
2. 高频小工具:cornerRect(画带角的漂亮框)、putTextRect(带底色的文字)、stackImages(多画面拼一张)、FPS(帧率计)
3. 统一返回格式:所有检测器都返回【列表+字典】,取第几只手、取坐标的写法全一致
一句话:MediaPipe 是发动机,CVZone 是带方向盘的整车。想懂原理拆开看(12-1),想快点跑装上就用(本节)。
二、安装与体检(同一个 NumPy 坑)
cd ~/ai && source venv/bin/activate
pip install cvzone
CVZone 会自动把 MediaPipe 一起装上(它是依赖),所以 12-1 的 NumPy 2 冲突在这一样会犯——装完先跑版本体检:
import cvzone, cv2, numpy, mediapipe
print("cvzone ", cvzone.__version__)
print("mediapipe ", mediapipe.__version__)
print("numpy ", numpy.__version__)
pip install cvzone若连带把 NumPy 升到 2.x,import mediapipe会当场炸——一律先pip install "numpy<2"再装 cvzone(顺序反过来也一样,装完必体检)。- 老教程的
detector.findPosition(img):新版返回值改成了字典列表(hand["lmList"]),老写法返回两个值的部分版本已变——照抄老代码拿不到坐标时,先print(hands)看清字典里有什么键。 - CVZone 的序列号模块(SerialModule) 老教程常用它跟 Arduino 通信——本章 8-5 已经学过原生
pyserial的三规矩,没必要绕回封装层。
三、HandDetector:本节的明星
from cvzone.HandTrackingModule import HandDetector
detector = HandDetector(detectionCon=0.8, maxHands=2) # 置信度、最多几只手
四个高频方法(这就是 12-4 猜拳实验的全部家底):
| 方法 | 干什么 | 返回 |
|---|---|---|
findHands(img, draw=True) |
找手并自动画骨架 | 画面+手列表 |
hand["lmList"] |
21 个关键点坐标(已是像素,与 12-1 的比例值不同!) | 列表 |
fingersUp(hand) |
判断五根手指各自伸直没有 | [0,1,1,0,0] 这样五个 0/1 |
findDistance(8, 12, img) |
量两指尖距离 | 距离+画面+信息 |
注意教材标题里那句【电脑视觉套件】的诚实之处:CVZone 没有训练过【这是剪刀】——fingersUp 只告诉你五根手指的姿态,【剪刀还是石头】的判断下一节你自己写 if。
四、动手 Demo:摄像头实时数手指(CVZone 三行版)
对照 12-1 的三十行 Demo,感受积木层的厚度:
# demo12_2_fingers.py —— CVZone 实时数手指
import cv2, cvzone
from cvzone.HandTrackingModule import HandDetector
cap = cv2.VideoCapture(0) # USB 摄像头(10-6 的 V4L2 之路)
cap.set(3, 640); cap.set(4, 480) # 分辨率压到 640x480,帧率优先
detector = HandDetector(detectionCon=0.8, maxHands=1)
while True:
ok, img = cap.read()
if not ok:
break
hands, img = detector.findHands(img) # 找手+自动画骨架
if hands:
hand = hands[0]
fingers = detector.fingersUp(hand)
count = fingers.count(1)
cvzone.putTextRect(img, f"Fingers: {count}", (40, 60), scale=2)
cv2.imshow("CVZone", img)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release(); cv2.destroyAllWindows()
看到什么算成功:窗口里你的手被绿色骨架线连着,左上角实时显示 Fingers: 3 这类数字;五指张开显示 5、握拳显示 0。树莓派 4 上约 7-8 FPS(够用不流畅),Pi 5 明显更顺。
读结果:fingersUp 的判据是【指尖(点 8/12/16/20)与对应指根的上下关系】+拇指的特殊横比——所以手腕朝下时数字会全错,这不是 bug 是几何:先学会把手腕朝上,再谈识别。
五、30 秒实验:不用摄像头的拼图工具
没接摄像头也能玩 CVZone——stackImages 是纯图像运算:
import cv2, cvzone
img = cv2.imread("hand.jpg") # 任意一张图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray3 = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) # 灰度也升回三通道才能拼
stacked = cvzone.stackImages([img, gray3, gray3, img], 2, 0.5)
cv2.imshow("stack", stacked)
cv2.waitKey(0)
看到什么算成功:一个 2×2 的四宫格窗口(两格彩色、两格灰度)。顺手验证 11-3 的知识点:把 gray3 换回两通道的 gray 再拼一次,stackImages 会直接报错——通道数不同的图不能拼,这就是它内部帮你查的那道岗。
六、常见误区
- 以为 CVZone 是又一个 AI 框架——它零模型,全是封装;模型还是 MediaPipe 的,装它只是装了 12-1 的【方便版外壳】。
- 混用两代的坐标单位——12-1 Tasks API 给比例值(0~1),CVZone 的
lmList给像素值;拿比例值当像素下标去索引数组,画出的点会缩在图的一角。 - 追求满分辨率——1280×720 下 MediaPipe 在 Pi 4 会掉到 3 FPS 以下;640×480 是实时视觉的甜点位(和 11-5 喂小图是同一条法则)。
- 手腕朝下还怪算法不准——
fingersUp基于上下几何关系,握拳/张手的姿态对了但手腕翻转,结果必然错;实验先把姿态摆标准,再谈鲁棒性。
自测三问(点开对答案)
1. CVZone 内部依赖哪两个库?——OpenCV 和 MediaPipe(它自己不含任何模型)。 2. `fingersUp` 返回什么?——五个 0/1 组成的列表,依次代表拇指、食指、中指、无名指、小指是否伸直。 3. CVZone 的关键点坐标和 12-1 Tasks API 的关键点坐标差在哪?——CVZone 给像素值,Tasks API 给 0~1 比例值。- ☐ 下一节(12-3 TensorFlow Lite 物体识别)
相关笔记
- Google MediaPipe 机器学习框架 —— 上一节:CVZone 脚下的发动机
- 使用外接 USB 网络摄像头 —— VideoCapture(0) 的设备从哪来
- AI 实验范例:OpenCV+YOLO 物体识别 —— 喂小图/640 甜点位:同一法则的另一处
- 🍓 树莓派 · 索引 —— 全课路线图