本代码基于 Advance Computer Vision with Python 进行修改,更加适合中国宝宝体质
我的相关代码及数据集已经上传GitHub仓库,欢迎使用 Advance-Computer-Vision-with-Python
代码1:Basics.py
import cv2
import mediapipe as mp
import time
# 打开摄像头
cap = cv2.VideoCapture(0) # 0是默认摄像头
# 初始化手部检测模块
mpHands = mp.solutions.hands # 引用 MediaPipe 的手部解决方案模块
hands = mpHands.Hands() # 创建一个 Hands 对象,用于检测和跟踪手部关键点
mpDraw = mp.solutions.drawing_utils # 引用绘图工具,用于在图像上绘制检测到的手部关键点和连接线
# 初始化时间变量用于计算帧率
pTime = 0 # 表示前一帧的时间,previous time
cTime = 0 # 表示当前帧的时间,current time
# cTime - pTime 计算时间差,从而计算帧率。最后将 cTime 赋值给 pTime,以便在下一次循环时使用
while True:
# 读取摄像头图像
success, img = cap.read()
# success:一个布尔值,表示是否成功读取帧
# img:读取的图像帧,如果读取失败,这个值可能为空
# 水平翻转图像
img = cv2.flip(img, 1)
# 第 0 维表示垂直方向(高度),对应图像的行数,上下
# 第 1 维表示水平方向(宽度),对应图像的列数,左右
# 将图像从 BGR 格式转换为 RGB 格式
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# BGR 是图像在 OpenCV 中的默认颜色格式,代表蓝色(Blue)、绿色(Green)、红色(Red)。这种格式与通常使用的 RGB(红、绿、蓝)顺序相反。转换为 RGB 是因为许多图像处理库(如 MediaPipe)使用这种格式进行处理
# 处理图像以检测手部
results = hands.process(imgRGB)
# 如果检测到手部
if results.multi_hand_landmarks:
# 遍历检测到的每只手
for handLms in results.multi_hand_landmarks:
# results.multi_hand_landmarks 会返回一个列表,其中包含检测到的每只手的关键点信息。如果检测到多只手,它会包含多个元素,每个元素代表一只手的所有关键点
# 遍历手部关键点
for id, lm in enumerate(handLms.landmark):
# enumerate 返回一个迭代器,每次迭代返回一个包含索引和值的元组
# id 是手部关键点的索引,lm 是 landmark 的缩写,表示手部关键点的坐标信息
# 获取图像的尺寸
h, w, c = (img.shape)
# img.shape 返回一个包含图像维度的元组,具体包括:高度(行数)、宽度(列数)、通道数(如 RGB 图像的通道数为 3)
# 计算关键点在图像中的坐标
cx, cy = int(lm.x * w), int(lm.y * h)
# lm.x 和 lm.y 是关键点的归一化坐标,范围在 0 到 1 之间。通过乘以图像的宽度和高度,可以将它们转换为图像中的像素坐标
print(id, cx, cy)
# 在关键点处画一个圆圈
cv2.circle(img, (cx, cy), 15, (255, 0, 255), -1)
# img 表示要绘制图像的地方,(cx, cy) 圆心的坐标,15 圆的半径
# (255, 0, 255) 圆的颜色(BGR格式),这里是紫色
# 红色:(0, 0, 255)、绿色:(0, 255, 0)、蓝色:(255, 0, 0)、黄色:(0, 255, 255)、青色:(255, 255, 0)、品红:(255, 0, 255)、白色:(255, 255, 255)、黑色:(0, 0, 0)
# cv2.FILLED 或 -1 填充圆的实心样式,也可以为具体的数字(值为边框厚度)
# 绘制手部关键点和连接线
mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)
# img 要绘制的图像,handLms 手部关键点的坐标
# mpHands.HAND_CONNECTIONS 定义手部关键点之间的连接关系,用于绘制骨架结构
# 计算帧率
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
# 在图像上显示帧率
cv2.putText(img, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 255), 3)
# img 要绘制文本的地方,str(int(fps)) 要显示的文本内容,这里是帧率的整数部分
# (10, 70) 文本的左下角坐标,cv2.FONT_HERSHEY_PLAIN 字体样式
# 3 字体大小,(255, 0, 255) 文本颜色(紫色,BGR格式),3 文本的粗细
# cv2.putText 不支持关键字传参,必须按照顺序提供参数
# 显示图像
cv2.imshow("Image", img) # 在窗口中显示图像,窗口标题为“Image”
cv2.waitKey(1) # 等待键盘事件,参数为 1 表示等待 1 毫秒
# 它也允许图像窗口响应用户输入(如关闭窗口)
# 检测退出键
if cv2.waitKey(1) & 0xFF == ord("q"): # ord('q') 获取字符 'q' 的 ASCII 值
# cv2.waitKey(1) & 0xFF 用来读取键盘输入
# cv2.waitKey(1) 返回的是一个 32 位整数,其中低 8 位是实际的键值,& 0xFF 是一个位运算,用于提取这 8 位
# "低 8 位"指的是一个数值的二进制表示中最右边的 8 位。这些位表示数值的较小部分,与"高 8 位"(最左边的 8 位)相对,后者表示数值的较大部分。对于 32 位整数来说,低 8 位用于表示键盘输入的实际键值
break
cap.release() # 释放摄像头资源
cv2.destroyAllWindows() # 关闭所有 OpenCV 窗口
代码2:HandTrackingModule.py
import cv2
import mediapipe as mp
import time
# 定义手部检测类
class handDetector:
def __init__(self, mode=False, maxHands=2, detectionCon=0.5, trackCon=0.5):
# 初始化参数
self.mode = mode # 静态图像模式
self.maxHands = maxHands # 最大检测手数
self.detectionCon = detectionCon # 检测置信度
self.trackCon = trackCon # 跟踪置信度
self.mpHands = mp.solutions.hands # Mediapipe手部解决方案
self.hands = self.mpHands.Hands(
static_image_mode=self.mode,
max_num_hands=self.maxHands,
min_detection_confidence=self.detectionCon,
min_tracking_confidence=self.trackCon,
)
self.mpDraw = mp.solutions.drawing_utils # 用于绘制手部连接
def findHands(self, img, draw=True):
# 将图像从BGR转换为RGB
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 处理图像,检测手部
self.results = self.hands.process(imgRGB)
# 如果检测到手部
if self.results.multi_hand_landmarks:
# 遍历每个手部
for handLms in self.results.multi_hand_landmarks:
if draw:
# 绘制手部连接
self.mpDraw.draw_landmarks(
img, handLms, self.mpHands.HAND_CONNECTIONS
)
return img
def findPosition(self, img, handNo=0, draw=True):
# 初始化列表存储手部位置
lmList = []
# 如果检测到手部
if self.results.multi_hand_landmarks:
# self.results 是在 findHands 方法中定义的。由于 findHands 方法在 findPosition 方法之前被调用,因此 self.results 会被正确地初始化并存储检测结果
# 这种设计依赖于调用顺序,我们得确保在调用 findPosition 之前已经调用过 findHands,否则 self.results 可能没有数据,导致 findPosition 无法正常工作
# 在 Python 中,self 参数用于引用类的实例。只要在类的方法中通过 self 定义了属性(例如 self.results),该属性就可以在同一个类的其他方法中访问和使用。这样可以在不同的方法之间共享数据
# 获取指定手部
myHand = self.results.multi_hand_landmarks[handNo]
# 遍历每个关键点
for id, lm in enumerate(myHand.landmark):
# 获取图像尺寸
h, w, c = img.shape
# 计算关键点在图像中的位置
cx, cy = int(lm.x * w), int(lm.y * h)
lmList.append([id, cx, cy])
if draw:
# 在图像上绘制关键点
cv2.circle(img, (cx, cy), 1, (255, 0, 255), -1)
# 由于现有的 findHands,再有的 handDetector,所以“关键点”的图层在“手部连接”之上,前一个程序则相反
return lmList
# 主函数
def main():
pTime = 0 # 前一帧时间
cTime = 0 # 当前时间
cap = cv2.VideoCapture(0) # 打开摄像头
detector = handDetector() # 创建手部检测器
while True:
success, img = cap.read() # 读取摄像头图像
img = cv2.flip(img, 1) # 水平翻转图像
img = detector.findHands(img) # 检测手部
lmList = detector.findPosition(img) # 获取手部关键点位置
if len(lmList) != 0:
print(lmList[4]) # 打印大拇指指尖位置
cTime = time.time()
fps = 1 / (cTime - pTime) # 计算帧率
pTime = cTime
# 在图像上显示帧率
cv2.putText(
img, str(int(fps)), (10, 70), cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 255), 3
)
cv2.imshow("Image", img) # 显示图像
if cv2.waitKey(1) & 0xFF == ord("q"):
break # 按下'q'退出
if __name__ == "__main__":
main()
mp.solutions.hands
mpHands = mp.solutions.hands
引用 MediaPipe 的手部解决方案模块
hands = mpHands.Hands()
创建一个 Hands 对象,用于检测和跟踪手部关键点
results = hands.process(imgRGB)
处理图像以检测手部
for handLms in results.multi_hand_landmarks
results.multi_hand_landmarks会返回一个列表,其中包含检测到的每只手的关键点信息
for id, lm in enumerate(handLms.landmark)
id 是手部关键点的索引,lm 是 landmark 的缩写,表示手部关键点的坐标信息
lm.x 和 lm.y 是关键点的归一化坐标,范围在 0 到 1 之间。通过乘以图像的宽度和高度,可以将它们转换为图像中的像素坐标
mpHands.Hands()
对象的参数:
static_image_mode: 是否将每帧作为静态图像处理
max_num_hands: 最大检测手数
min_detection_confidence: 检测置信度阈值
min_tracking_confidence: 跟踪置信度阈值
mp.solutions.drawing_utils
mpDraw = mp.solutions.drawing_utils
引用绘图工具,用于在图像上绘制检测到的手部关键点和连接线
mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS)
绘制手部关键点和连接线,img 要绘制的图像,handLms 手部关键点的坐标,mpHands.HAND_CONNECTIONS 定义手部关键点之间的连接关系,用于绘制骨架结构
pycache文件夹
当导入一个 .py 文件时,Python 会编译它,并将编译后的字节码存储在 pycache 目录中。这有助于提高程序的执行效率,因为下次运行时可以直接使用编译后的字节码,而不必重新编译源代码
“Cache” 是指缓存,一种用于临时存储数据的机制,以便更快速地访问。缓存可以减少数据的重复计算或从慢速存储设备读取的次数,从而提高程序性能
对于HandTrackingModule.py而言,当你在其他文件中导入这个模块时,只有类 handDetector 和其中的方法会被使用,而 main() 函数不会,main() 函数只有在直接运行该脚本时才会执行