【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

各位同学好,今天和大家分享一下如何使用 opencv+Mediapipe通过手势识别来演示PPT,先放张图看效果。

当只有大拇指翘起时,向左翻页;当只有小拇指翘起时,向右翻页;当食指和中指翘起时,表示鼠标指针移动,如图中黄色指针;当只有食指翘起时,红色线条绘制板书;当所有手指都弯曲时,擦除板书。

【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

1. 安装工具包

pip install opencv_python==4.2.0.34  # 安装opencv
pip install mediapipe  # 安装mediapipe
pip install mediapipe --user  #有user报错的话试试这个
pip install cvzone  # 安装cvzone

导入工具包
import cv2
import cvzone
import numpy as np
from cvzone.HandTrackingModule import HandDetector  # 导入手部检测模块
import os

21个手部关键点信息如下

【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

2. 手部关键点检测

(1)cvzone.HandTrackingModule.HandDetector()是手部关键点检测方法

参数:

mode: 默认为 False,将输入图像视为视频流。 它将尝试在第一个输入图像中检测手,并在成功检测后进一步定位手的坐标。在随后的图像中, 一旦检测到所有 maxHands 手并定位了相应的手的坐标,它就会跟踪这些坐标,而不会调用另一个检测,直到它失去对任何一只手的跟踪。这减少了延迟,非常适合处理视频帧。如果设置为 True,则在每个输入图像上运行手部检测, 用于处理一批静态的、可能不相关的图像

maxHands:最多检测几只手, 默认为 2

detectionCon: 手部检测模型的 最小置信值(0-1之间),超过阈值则检测成功。 默认为 0.5

minTrackingCon: 坐标跟踪模型的 最小置信值 (0-1之间), 用于将手部坐标视为成功跟踪,不成功则在下一个输入图像上自动调用手部检测。将其设置为更高的值可以提高解决方案的稳健性,但代价是更高的延迟。如果 mode 为 True,则忽略这个参数,手部检测将在每个图像上运行。 默认为 0.5

它的参数和 返回值类似于官方函数 mediapipe.solutions.hands.Hands()

MULTI_HAND_LANDMARKS: 被检测/跟踪的手的集合,其中每只手被表示为21个手部地标的列表, 每个地标由x, y, z组成x和y分别由图像的宽度和高度归一化为[0,1]。Z表示地标深度。

MULTI_HANDEDNESS:被检测/追踪的手是左手还是右手的集合。每只手由label(标签)和score(分数)组成。 label 是 ‘Left’ 或 ‘Right’ 值的字符串。 score 是预测左右手的估计概率。

(2)cvzone.HandTrackingModule.HandDetector.findHands()找到手部关键点并绘图

参数:

img:需要检测关键点的帧图像, 格式为BGR

draw:是否需要在原图像上绘制关键点及识别框

flipType:图像是否需要 翻转当视频图像和我们自己不是镜像关系时,设为True就可以了

返回值:

hands:检测到的手部信息, 由0或1或2个字典组成的列表。如果检测到两只手就是由两个字典组成的列表。 字典中包含21个关键点坐标(x,y,z),检测框左上坐标及其宽高,检测框中心点坐标,检测出是哪一只手。

img:返回绘制了关键点及连线后的图像

(3)处理PPT图片

将ppt都导出成图片,放在一个文件夹中。pathImage变量中保存所有图片文件的文件名。pathFullImage中保存所有图片的路径。

代码如下:

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector  # 导入手部跟踪模块
import os

#(1)视频捕获
cap = cv2.VideoCapture(0)  # 获取电脑摄像头
cap.set(3, 1280)  # 读入图像的宽度
cap.set(4, 720)  # 图像的高度

获取ppt图片所在文件夹的路径
folderpath = 'ppt'
列表,存放每个文件的名称。根据文件名排序后存放到变量pathImage中
pathImage = sorted(os.listdir(folderpath), key=len)

设置当前展示的ppt的编号
imgNumeber = 0

相机的帧图显示在ppt上的size
alpha = 0.8  # 缩放比例
ws, hs = int(320*alpha), int(180*alpha)  # 16:9的图像显示比例

#(2)手部识别模块配置
detector = HandDetector(maxHands=1, detectionCon=0.8)  # 最多检测1只手,检测置信度0.8

#(3)处理每一帧图像
while True:

    # 图像是否读取成功success,读取的帧图像img
    success, img = cap.read()  # 每次执行读取一帧

    # 文件路径拼接,如果各组件名首字母不包含'/',则函数会自动加上,如果最后一个组件为空,则生成的路径以一个'/'分隔符结尾
    pathFullImage = os.path.join(folderpath, pathImage[imgNumeber])

    # 获取指定路径的图片文件
    imgCurrent = cv2.imread(pathFullImage)

    #(4)手部关键点检测
    # 翻转图像,使电脑图像和我们自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # flipCode=0上下翻转,1左右翻转

    # 返回手部信息hands,绘制关键点后的图像img
    hands, img = detector.findHands(img)

    print(hands)  # 查看手部信息

    #(5)显示图像
    # 在ppt图片旁边接一张摄像机的帧图像
    imgSmall = cv2.resize(img, (ws, hs))  # 将摄像机帧图片缩小

    h, w, _ = imgCurrent.shape  # 获取ppt图像的高,宽,不要通道数

    # ppt图片的右下角位置上显示视频图像,[h,w]
    imgCurrent[h-hs:h, w-ws:w] = imgSmall

    cv2.imshow('imgCurrent', imgCurrent)  # 显示ppt图像
    cv2.imshow('img', img)  # 显示摄像头图像

    k = cv2.waitKey(1)  # 每帧滞留1毫秒后消失
    if k & 0xFF == 27:  # ESC键退出程序
        break

释放视频资源
cap.release()
cv2.destroyAllWindows()

输出手部信息结果如下:

[{'lmList': [[440, 446, 0], [498, 408, -30], [540, 348, -37], [561, 290, -42], [567, 241, -46], [495, 280, -6], [502, 211, -20], [501, 169, -35], [499, 133, -45], [461, 277, -3], [467, 206, -15], [468, 159, -31], [468, 122, -43], [426, 286, -5], [429, 218, -20], [430, 174, -33], [433, 139, -42], [388, 303, -10], [385, 250, -26], [385, 213, -35], [388, 177, -40]],
'bbox': (385, 122, 182, 324),
'center': (476, 284),
'type': 'Right'}]

效果图如下:

【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

3. PPT换页

接下来对手指关键点进行处理,判断那些手指是翘起的,使用 detector.fingersUp()函数将手部信息 hands 传进去, 判断哪几个手指是翘起的,返回一个列表, 1代表翘起,0代表弯曲,如[1,0,0,0,0],就代表只有大拇指翘起。

给换页操作限制区域,只有当 手部中心点(cx, cy)在屏幕中上方时才能激活换页操作cx, cy = hand[‘center’]

为了避免每一帧都执行换页操作从而使换页过于快,设置 延时器buttonDelay规定 每30帧才能执行一次换页操作。使用 buttonCounter 记录当前帧距离上一次换页已经过去多少帧了。如果执行了换页操作那么 buttonPressed=True,接下来的30帧不能换页。

在上面的代码中补充。

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector  # 导入手部跟踪模块
import os

#(1)视频捕获
cap = cv2.VideoCapture(0)  # 获取电脑摄像头
cap.set(3, 1280)  # 读入图像的宽度
cap.set(4, 720)  # 图像的高度

获取ppt图片所在文件夹的路径
folderpath = 'ppt'
列表,存放每个文件的名称。根据文件名排序后存放到变量pathImage中
pathImage = sorted(os.listdir(folderpath), key=len)

设置当前展示的ppt的编号
imgNumeber = 0

相机的帧图显示在ppt上的size
alpha = 0.8  # 缩放比例
ws, hs = int(320*alpha), int(180*alpha)  # 16:9的图像显示比例

在相机上画一条线,手在线条以上做手势才能触发
threshold = 350  # 线条的高度阈值

处理换页过快的问题
buttonPressed = False   # 只有当False才能换一次页
buttonCounter = 0  # 记录距离上一次换页已经过去多少帧了
buttonDelay = 30  # 每30帧才能执行一次换页

#(2)手部识别模块配置
detector = HandDetector(maxHands=1, detectionCon=0.8)  # 最多检测1只手,检测置信度0.8

#(3)处理每一帧图像
while True:

    # 图像是否读取成功success,读取的帧图像img
    success, img = cap.read()  # 每次执行读取一帧

    # 文件路径拼接,如果各组件名首字母不包含'/',则函数会自动加上,如果最后一个组件为空,则生成的路径以一个'/'分隔符结尾
    pathFullImage = os.path.join(folderpath, pathImage[imgNumeber])

    # 获取指定路径的图片文件
    imgCurrent = cv2.imread(pathFullImage)

    #(4)手部关键点检测
    # 翻转图像,使电脑图像和我们自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # flipCode=0上下翻转,1左右翻转

    # 返回手部信息hands,绘制关键点后的图像img
    hands, img = detector.findHands(img)

    # 在相机上画一条线,线以上做手势才能触发
    cv2.line(img, (0,threshold), (1280,threshold), (0,255,0), 3)

    #(5)关键点处理
    if hands and buttonPressed is False:  # 如果检测到了手,并且距离上一次换页已经过去了规定时间

        # 获取一只手的所有信息
        hand = hands[0]  # hands是一个由多个字典组成的列表,只有第一个字典有信息,其他都是空

        # 统计多少根手指翘起,返回一个列表,1表示手指是翘起的,0表示弯曲
        fingers = detector.fingersUp(hand)  # 最好手掌正对摄像机
        print(fingers)  #[0,1,1,1,1]

        # 手部中心点坐标,hand是一个字典
        cx, cy = hand['center']  # 返回中心点坐标

        # 如果手的中心点坐标在线条以上就能继续操作
        if cy < threshold: # 在图像上,坐标y向下为正,x向右为正

            # 手掌正对摄像机,只有大拇指翘起,执行向左换页操作
            if fingers == [1,0,0,0,0]:
                print('to left')

                # 如果当前的ppt不是第一张才能再向前移动一张
                if imgNumeber > 0:

                    # 完成一次操作后,下次就不能再换页操作了
                    buttonPressed = True

                    # 当前展示的ppt编号向前移动一个,编号减一
                    imgNumeber -= 1  # pathImage[imgNumeber]指向的图片文件名改变

            # 手掌正对摄像机,只有小拇指翘起,执行向右换页操作
            if fingers == [0,0,0,0,1]:
                print('to right')

                # 如果当前的ppt不是最后一张才能再向后移动一张
                if imgNumeber < len(pathImage)-1:  # pathImage列表,存放所有的图片文件名

                    # 完成一次操作后,下次就不能再换页操作了
                    buttonPressed = True

                    # 当前展示的ppt编号向后移动一个,编号加一
                    imgNumeber += 1   # pathImage[imgNumeber]指向的图片文件名改变

    #(6)设置延时器
    if buttonPressed is True:  # 此时已经换过页了
        buttonCounter += 1  # 延时器计数加一
        if buttonCounter > buttonDelay:  # 如果延时器超过规定的帧数
            buttonPressed = False  # 下一帧可以换页
            buttonCounter = 0  # 延时计时器重置

    #()显示图像
    # 在ppt图片旁边接一张摄像机的帧图像
    imgSmall = cv2.resize(img, (ws, hs))  # 将摄像机帧图片缩小

    h, w, _ = imgCurrent.shape  # 获取ppt图像的高,宽,不要通道数

    # ppt图片的右下角位置上显示视频图像,[h,w]
    imgCurrent[h-hs:h, w-ws:w] = imgSmall

    cv2.imshow('imgCurrent', imgCurrent)  # 显示ppt图像
    cv2.imshow('img', img)  # 显示摄像头图像

    k = cv2.waitKey(1)  # 每帧滞留1毫秒后消失
    if k & 0xFF == 27:  # ESC键退出程序
        break

释放视频资源
cap.release()
cv2.destroyAllWindows()

效果图如下,当大拇指翘起,向左翻页。

【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

4. PPT板书

食指和中指都竖起时,食指指尖位置就相当于 激光笔位置。由于ppt图片每一帧都在输入,因此激光笔前几帧的位置不会被保留下来,每帧只有一个激光笔位置。只需要在每帧ppt上画个圈就行。

只有食指竖起时,相当于 写板书,ppt图像上需要绘制当前帧之前的很多帧的食指指尖坐标,并把点与点之间的连线绘制出来。为了记录下 每一次绘制板书的起点和终点的这段绘图点集合,定义 二维列表 annotations = [[]]每个子列表存放每个一次完整绘图的曲线点坐标集合annotationNumber变量用来指定 当前的一次完整绘图保存在哪个索引对应的子列表中

为了避免手部在相机边缘框位置移动时,关键点检测效果很差的问题。 手指在一个小范围的矩形框中移动,将这个小范围映射到整个电脑屏幕的大范围。使用 np.interp()函数,将食指的 指尖坐标(lmList[8][0],lmList[8][1])的移动范围做映射。

如果 annotationStart == False 表示当前帧是绘制板书的起点位置,将当前帧的指尖保存在二维列表annotations的索引annotationNumber对应子列表中, annotations[annotationNumber].append((xval, yval))。每次画完板书,即不是只有食指之间翘起时,annotationStart = False 停止保存指尖坐标。

在绘制板书时,遍历annotations中的每个子列表,再遍历每个子列表的所有坐标元素,就能得出所有的坐标点,绘制两两坐标点之间的连线即可。

在上述代码中补充:

import cv2
import cvzone
from cvzone.HandTrackingModule import HandDetector  # 导入手部跟踪模块
import numpy as np
import os

#(1)视频捕获
cap = cv2.VideoCapture(0)  # 获取电脑摄像头
cap.set(3, 1280)  # 读入图像的宽度
cap.set(4, 720)  # 图像的高度

获取ppt图片所在文件夹的路径
folderpath = 'ppt'
列表,存放每个文件的名称。根据文件名排序后存放到变量pathImage中
pathImage = sorted(os.listdir(folderpath), key=len)

设置当前展示的ppt的编号
imgNumeber = 0

相机的帧图显示在ppt上的size
alpha = 0.8  # 缩放比例
ws, hs = int(320*alpha), int(180*alpha)  # 16:9的图像显示比例

在相机上画一条线,手在线条以上做手势才能触发
threshold = 350  # 线条的高度阈值

处理换页过快的问题
buttonPressed = False   # 只有当False才能换一次页
buttonCounter = 0  # 记录距离上一次换页已经过去多少帧了
buttonDelay = 30  # 每30帧才能执行一次换页

保存板书的每一个坐标点
annotations = [[]] # 二维列表,每个列表保存连续绘制一次后的坐标点
annotationNumber = -1  # 当前使用的是第几个列表中的一次绘图后的关键点
annotationStart = False  # 开始绘图后,需要知道一次绘图的终点和起点

#(2)手部识别模块配置
detector = HandDetector(maxHands=1, detectionCon=0.8)  # 最多检测1只手,检测置信度0.8

#(3)处理每一帧图像
while True:

    # 图像是否读取成功success,读取的帧图像img
    success, img = cap.read()  # 每次执行读取一帧

    # 文件路径拼接,如果各组件名首字母不包含'/',则函数会自动加上,如果最后一个组件为空,则生成的路径以一个'/'分隔符结尾
    pathFullImage = os.path.join(folderpath, pathImage[imgNumeber])

    # 获取指定路径的图片文件
    imgCurrent = cv2.imread(pathFullImage)

    #(4)手部关键点检测
    # 翻转图像,使电脑图像和我们自己呈镜像关系
    img = cv2.flip(img, flipCode=1)  # flipCode=0上下翻转,1左右翻转

    # 返回手部信息hands,绘制关键点后的图像img
    hands, img = detector.findHands(img)

    # 在相机上画一条线,线以上做手势才能触发
    cv2.line(img, (0,threshold), (1280,threshold), (0,255,0), 3)
    # 在相机上画一个手指移动映射区域
    cv2.rectangle(img, (300, 0), (1100,360), (255,0,0), 3)

    #(5)关键点处理
    if hands and buttonPressed is False:  # 如果检测到了手,并且距离上一次换页已经过去了规定时间

        # 获取一只手的所有信息
        hand = hands[0]  # hands是一个由多个字典组成的列表,只有第一个字典有信息,其他都是空

        # 统计多少根手指翘起,返回一个列表,1表示手指是翘起的,0表示弯曲
        fingers = detector.fingersUp(hand)  # 最好手掌正对摄像机
        print(fingers)  #[0,1,1,1,1]

        # 将手指移动的边界限制在一个框中,框的大小映射到屏幕大小
        lmList = hand['lmList']  # 获取21个关键点的xyz坐标
        # indexFinger = lmList[8][0], lmList[8][1]  # 获取食指指尖的xy坐标
        # 设置映射区域
        xval = int(np.interp(lmList[8][0], [300, 1280], [0, 1280]))  # x的映射区域
        yval = int(np.interp(lmList[8][1], [0, 360], [0, 720]))  # y的映射区域

        # 手部中心点坐标,hand是一个字典
        cx, cy = hand['center']  # 返回中心点坐标

        # 如果手的中心点坐标在线条以上就能继续操作
        if cy < threshold: # 在图像上,坐标y向下为正,x向右为正

            # ① 手掌正对摄像机,只有大拇指翘起,执行向左换页操作
            if fingers == [1,0,0,0,0]:
                print('to left')

                # 如果当前的ppt不是第一张才能再向前移动一张
                if imgNumeber > 0:

                    # 完成一次操作后,下次就不能再换页操作了
                    buttonPressed = True

                    # 当前展示的ppt编号向前移动一个,编号减一
                    imgNumeber -= 1  # pathImage[imgNumeber]指向的图片文件名改变

            # ② 手掌正对摄像机,只有小拇指翘起,执行向右换页操作
            if fingers == [0,0,0,0,1]:
                print('to right')

                # 如果当前的ppt不是最后一张才能再向后移动一张
                if imgNumeber < len(pathImage)-1:  # pathImage列表,存放所有的图片文件名

                    # 完成一次操作后,下次就不能再换页操作了
                    buttonPressed = True

                    # 当前展示的ppt编号向后移动一个,编号加一
                    imgNumeber += 1   # pathImage[imgNumeber]指向的图片文件名改变

        # ③ 指针设置,如果食指和中指同时竖起就绘制一个圈,不需要在线条以上
        if fingers == [0,1,1,0,0]:
            print('circle')

            # 在ppt图片上的食指指尖绘制圆圈
            cv2.circle(imgCurrent, (xval, yval), 10, (0,255,255), -1) # -1代表颜色填充
            cv2.circle(imgCurrent, (xval, yval), 11, (0,0,255), 3)

        # ④ 板书设置,如果只有食指竖起就按食指轨迹移动绘制线条
        if fingers == [0,1,0,0,0]:
            # 如果之前没绘制过图那么annotationStart=False
            if annotationStart == False:
                annotationStart = True  # 那么当前帧开始绘图
                annotationNumber += 1  # 将当前绘图结果到保存在该索引指向的列表中
                annotations.append([])  # 在二维列表中添加一个子列表用来保存坐标

            cv2.circle(imgCurrent, (xval, yval), 10, (0,255,255), -1)
            # 将食指的每一帧坐标都保存在指定的索引列表中
            annotations[annotationNumber].append((xval, yval))
        # 如果不绘制板书了,当前一次绘图的坐标都保存好
        else:
            annotationStart = False  # 不绘制了

        # ⑤ 删除前一次的板书,不全删
        if fingers == [0,1,1,1,0]:
            if annotations:
                annotations.pop(-1)  # 删除最后一次绘图的坐标
                annotationNumber -= 1  # 绘图索引向前移动一个

        # ⑥ 擦除全部板书,如果没有手指竖起就删除所有板书
        if fingers == [0,0,0,0,0]:
            annotations = [[]] # 二维列表重置
            annotationNumber = -1  # 重置当前使用的列表索引
            annotationStart = False  # 重置绘图过程

    #(6)设置延时器
    if buttonPressed is True:  # 此时已经换过页了
        buttonCounter += 1  # 延时器计数加一
        if buttonCounter > buttonDelay:  # 如果延时器超过规定的帧数
            buttonPressed = False  # 下一帧可以换页
            buttonCounter = 0  # 延时计时器重置

    #(7)绘制板书,将第④步保存的坐标点都绘制出来
    for i in range(len(annotations)):  # 取出绘图列表
        # 遍历每个列表,绘制一次绘图的点
        for j in range(len(annotations[i])):
            # 绘制每两个点之间的线条
            if j != 0:
                cv2.line(imgCurrent, annotations[i][j-1], annotations[i][j], (0,0,255),5)

    #(8)显示图像
    # 在ppt图片旁边接一张摄像机的帧图像
    imgSmall = cv2.resize(img, (ws, hs))  # 将摄像机帧图片缩小

    h, w, _ = imgCurrent.shape  # 获取ppt图像的高,宽,不要通道数

    # ppt图片的右下角位置上显示视频图像,[h,w]
    imgCurrent[h-hs:h, w-ws:w] = imgSmall

    cv2.imshow('imgCurrent', imgCurrent)  # 显示ppt图像
    cv2.imshow('img', img)  # 显示摄像头图像

    k = cv2.waitKey(1)  # 每帧滞留1毫秒后消失
    if k & 0xFF == 27:  # ESC键退出程序
        break

释放视频资源
cap.release()
cv2.destroyAllWindows()

效果图如下:

【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

Original: https://blog.csdn.net/dgvv4/article/details/123682731
Author: 立Sir
Title: 【机器视觉案例】(14) 手部识别,手势演示PPT,附python完整代码

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/637225/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球