【机器视觉案例】(5) AI视觉,手势调节物体尺寸,附python完整代码

各位同学好,今天和大家分享一下如何使用opencv+mediapipe完成远程手势调节图片尺寸的案例。先放张图看效果。当拇指和食指竖起时,根据食指间的连线的长度自由缩放图片尺寸。图片的中点始终位于指尖连线的中点。16代表FPS值

这里需要用到mediapipe中的手部关键点检测方法,并且需要判断哪根手指是弯下的,哪根手指是翘起来的。手部关键点检测方法有不明白的可以看我之前的一篇文章:【MediaPipe】(1) AI视觉,手部关键点实时跟踪,附python完整代码_立同学的博客-CSDN博客判断哪个手指朝上的方法在后面的章节会介绍。


1. 导入工具包

# 安装工具包
pip install opencv-contrib-python  # 安装opencv
pip install mediapipe  # 安装mediapipe
# pip install mediapipe --user  #有user报错的话试试这个
pip install cvzone  # 安装cvzone
 
# 导入工具包
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import mediapipe as mp
import time

21个手部关键点信息如下,本节我们主要研究食指指尖"8"的坐标信息。


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()

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

参数:

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

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

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

返回值:

hands: 检测到的手部信息,包含:21个关键点坐标,检测框坐标及宽高,检测框中心坐标,检测出是哪一只手。

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

代码如下

# 远程手势缩放物体尺寸
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import time

#(1)获取摄像头信息
cap = cv2.VideoCapture(0)  # 0代表电脑自带的摄像头
cap.set(3, 1280)  # 设置显示框的宽 
cap.set(4, 720)   # 设置显示框的高

pTime = 0  # 处理第一帧图像的起始时间

#(2)获取手部检测方法,传入参数,手部最小检测置信度0.8,最多检测2只手
detector = HandDetector(detectionCon=0.8, maxHands=2)

#(3)处理每一帧图像
while True:
    
    # 返回是否读取成功,已经读取的帧图像
    success, img = cap.read()
    
    #(4)检测手部信息
    # 返回每只手的检测框信息hands,以及绘制后的手部图像
    hands, img = detector.findHands(img, draw=True, flipType=True) # fliptype代表是否翻转图像,上面以及翻转过了
    
    # 打印检测到的是左手还是右手,以及关键点的像素坐标
    print(hands)

    #(5)展示视频图像
    # 计算fps
    cTime = time.time()  # 处理每一帧图像所需的时间
    fps = 1/(cTime-pTime)
    pTime = cTime  # 更新处理下一帧图像的起始时间

    # 把fps值显示在图像上,img画板,显示字符串,显示的坐标位置,字体,字体大小,颜色,线条粗细
    cv2.putText(img, str(int(fps)), (30,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)
    
    # 显示图像,输入窗口名及图像数据
    # cv2.namedWindow("img", 0)  # 窗口大小可手动调整
    cv2.imshow('img', img)    
    if cv2.waitKey(20) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出
        break

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

打印某一帧中检测到的手部信息,lmList 代表每只手的21个手部关键点坐标bbox 代表检测框的左上角坐标,以及检测框的宽和高;center 代表检测框的中心坐标;type 代表检测出是哪一只手。

----------------------------------------------------
[{'lmList': [[1214, 809], [1111, 806], [1024, 769], [983, 732], [939, 727], [972, 576], [896, 461], [851, 392], [810, 331], [1052, 528], [985, 393], [943, 310], [905, 237], [1145, 514], [1109, 376], [1081, 289], [1052, 214], [1247, 525], [1256, 407], [1254, 332], [1245, 262]], 
'bbox': (810, 214, 446, 595), 
'center': (1033, 511), 
'type': 'Right'}, 
{'lmList': [[174, 753], [329, 742], [469, 707], [589, 687], [695, 674], [451, 519], [539, 414], [596, 358], [653, 311], [394, 480], [488, 365], [558, 299], [628, 249], [321, 459], [409, 347], [481, 286], [555, 243], [232, 450], [293, 350], [348, 291], [411, 242]], 
'bbox': (174, 242, 521, 511), 
'center': (434, 497), 
'type': 'Left'}]
----------------------------------------------------

显示结果如图:


3. 确定缩放方法

首先,我们通过 cv2.imread() 导入需要缩放的图像,img[0:180, 0:320] = img1,先把图像显示在视频帧图像的固定位置。

确定缩放方法的思路是,如果检测到两只手,并且这两只手是拇指和食指朝上,那么检测到的第一帧时的两只手的食指尖之间的距离,作为初始距离 startDist = length,接下去图片在这个初始距离的基础上进行缩放如果检测的手消失,那么就重置初始距离 startDist = None,将下一次检测到的指尖距离作为初始值。

通过 detector.findHands() 方法来检测哪个手指朝上,传入参数是每只手的所有关键点坐标。返回值是由0和1组成的长度为5的列表,0代表该手指弯曲,1代表该手指朝上。我们需要得到的是[1,1,0,0,0],即拇指和食指朝上,其他手指弯曲

通过 detector.findDistance() 方法来检测某两个关键点之间的距离。length, info, img  = distance = detector.findDistance(lmList1[8], lmList2[8], img), 返回值: length 代表两个关键点之间的距离,info 是一个6个元素组成的列表,包含关键点之间连线的起点、终点、中点坐标img 是绘制连线后的图像。

因此,我们在上述代码中补充。

# 远程手势缩放物体尺寸
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import time

#(1)获取摄像头信息
cap = cv2.VideoCapture(0)  # 0代表电脑自带的摄像头
cap.set(3, 1280)  # 设置显示框的宽 
cap.set(4, 720)   # 设置显示框的高

pTime = 0  # 处理第一帧图像的起始时间

#(2)获取手部检测方法,传入参数,手部最小检测置信度0.8,最多检测2只手
detector = HandDetector(detectionCon=0.8, maxHands=2)

startDist = None  # 设置一个初始距离

#(3)处理每一帧图像
while True:
    
    # 返回是否读取成功,已经读取的帧图像
    success, img = cap.read()    
    
    #(4)检测手部信息
    # 返回每只手的检测框信息hands,以及绘制后的手部图像
    hands, img = detector.findHands(img, draw=True, flipType=True) # fliptype代表是否翻转图像
    
    #(5)导入需要调节的图片,图像的(w,h)为[1280,720]  
    img1 = cv2.imread('C:\\GameDownload\\Deep Learning\\TF2.jpg')
    # 由于我导入的图像太大了,这里把它缩小一下
    img1 = cv2.resize(img1, (320,180))
    # 把这张图片放在屏幕的固定位置,先指定h再指定w, 即img[h,w]
    img[0:180, 0:320] = img1
    
    # 放大和缩小步骤:当两只手的拇指和食指竖起,其他指弯下,那么就执行放大和缩小
    #(6)如果检测到有两只手,进行放大缩小的操作
    if len(hands) == 2:
        
        # 检测手指是否朝上的,hands[0]代表第一只手,hands[1]代表第二只手
        print('which up:', detector.fingersUp(hands[0]), detector.fingersUp(hands[1]))
        # 返回值是[1,1,0,0,0]代表一只手中拇指和食指竖起,其他指都没有竖起
        if detector.fingersUp(hands[0]) == [1,1,0,0,0] and detector.fingersUp(hands[1]) == [1,1,0,0,0]:
            
            # 通过两只手食指的关键点之间的距离来缩放图片
            lmList1 = hands[0]['lmList']  # 第一只手的关键点坐标信息,hands是一个字典
            lmList2 = hands[1]['lmList']  # 第二只手的关键点坐标信息
            
            # 第一次检测到食指间的距离
            if startDist is None:
                
                # 计算食指间的距离并绘图;食指的关键点索引是8;返回值:连线长度,连线的信息(起点、终点、中点坐标),绘制后的图像
                length, info, img  = distance = detector.findDistance(lmList1[8], lmList2[8], img)
                # print('length',length,'info',info)
                
                # 检测到的第一帧的食指间的距离作为初始距离,接下来超过这个长度就放大,小于这个长度就缩小
                startDist = length
            
            # 第一帧检测到距离之后,接下来变动的距离就是用于缩放图片大小
            length, info, img  = distance = detector.findDistance(lmList1[8], lmList2[8], img)
            
            # 计算变化量,正数代表放大,负数代表缩小
            scale = length - startDist
            print('scale:',scale)
            
    # 如果两只手中至少有一只消失了,重置初始距离
    else:
        startDist = None
            
    #(7)展示视频图像
    # 计算fps
    cTime = time.time()  # 处理每一帧图像所需的时间
    fps = 1/(cTime-pTime)
    pTime = cTime  # 更新处理下一帧图像的起始时间

    # 把fps值显示在图像上,img画板,显示字符串,显示的坐标位置,字体,字体大小,颜色,线条粗细
    cv2.putText(img, str(int(fps)), (30,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)
    
    # 显示图像,输入窗口名及图像数据
    # cv2.namedWindow("img", 0)  # 窗口大小可手动调整
    cv2.imshow('img', img)    
    if cv2.waitKey(20) & 0xFF==27:  #每帧滞留20毫秒后消失,ESC键退出
        break

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

打印某几帧的结果,数字1代表该指时朝上的,0代表该指是弯着的。scale代表食指间的距离。

----------------------------------------------
which up: [1, 1, 0, 0, 0] [1, 1, 0, 0, 0]
scale: 225.00591450309486
which up: [1, 1, 0, 0, 0] [1, 1, 0, 0, 0]
scale: 18.837911081298728
which up: [1, 1, 0, 0, 0] [1, 1, 0, 0, 0]
scale: -30.173165115569134
which up: [1, 1, 0, 0, 0] [1, 1, 0, 0, 0]
scale: -28.422575826465106
-----------------------------------------------

显示结果如图,需要改变尺寸的图片暂时位于左上方,当拇指和食指朝上时绘制指尖连线,并计算连线长度。


4. 按比例缩放图片

info中存放指尖连线的起点、终点、中点坐标,其中 cx, cy = info[4:] 代表连线中点的坐标。现在让图片的中点落在指尖连线的中点上,使图片的位置随手指位置而实时发生变化

在计算中点时用到 newH//2 newW//2 ,这时候需要保证新的宽和高能被2整除。否则在执行 img[cy - newH//2:cy + newH//2, cx - newW//2:cx + newW//2] = img1 时,屏幕上划分给图片的shape和图片自身的shape不一致,从而导致程序报错。因此在执行之前,通过int(((h1+scale)//2)*2) int(((w1+scale)//2)*2)  ,提前使宽高能被整除。如果是奇数除以2,也只是忽略了一个像素,不会有影响。

当我们把图片放的过大,或图像坐标出现负数时,程序会抛出异常不能执行,因此使用 try,except 方法,当遇到异常就执行except中的内容,这里是pass,直接跳过;没发出异常就正常运行try中的内容

因此,在上述代码中补充。

# 远程手势缩放物体尺寸
import cv2
from cvzone.HandTrackingModule import HandDetector  # 手部追踪方法
import time

#(1)获取摄像头信息
cap = cv2.VideoCapture(0)  # 0代表电脑自带的摄像头
cap.set(3, 1280)  # 设置显示框的宽 
cap.set(4, 720)   # 设置显示框的高

pTime = 0  # 处理第一帧图像的起始时间


#(2)获取手部检测方法,传入参数,手部最小检测置信度0.8,最多检测2只手
detector = HandDetector(detectionCon=0.8, maxHands=2)

startDist = None  # 设置一个初始距离

scale = 0  # 设置一个初始的需要缩放的大小

cx, cy = 200, 200  # 确定初始的图像中心点在屏幕上的显示位置

#(3)处理每一帧图像
while True:
    
    # 返回是否读取成功,已经读取的帧图像
    success, img = cap.read()    
    
    # 翻转图像,保证摄像机画面和人的动作是镜像
    # img = cv2.flip(img, flipCode=1)  #0竖直翻转,1水平翻转
    
    
    #(4)检测手部信息
    # 返回每只手的检测框信息hands,以及绘制后的手部图像
    hands, img = detector.findHands(img, draw=True, flipType=True) # fliptype代表是否翻转图像
    

    #(5)导入需要调节的图片,图像的(w,h)为[1280,720]  
    img1 = cv2.imread('C:\\GameDownload\\Deep Learning\\TF2.jpg')
    # 由于我导入的图像太大了,这里把它缩小一下
    img1 = cv2.resize(img1, (320,180))
    # 把这张图片放在屏幕的固定位置,先指定h再指定w, 即img[h,w]
    # img[0:180, 0:320] = img1
    
    # 放大和缩小步骤:当两只手的拇指和食指竖起,其他指弯下,那么就执行放大和缩小
    #(6)如果检测到有两只手,进行放大缩小的操作
    if len(hands) == 2:
        
        # 检测手指是否朝上的,hands[0]代表第一只手,hands[1]代表第二只手
        # print('which up:', detector.fingersUp(hands[0]), detector.fingersUp(hands[1]))
        # 返回值是[1,1,0,0,0]代表一只手中拇指和食指竖起,其他指都没有竖起
        if detector.fingersUp(hands[0]) == [1,1,0,0,0] and detector.fingersUp(hands[1]) == [1,1,0,0,0]:
            
            # 通过两只手食指的关键点之间的距离来缩放图片
            lmList1 = hands[0]['lmList']  # 第一只手的关键点坐标信息,hands是一个字典
            lmList2 = hands[1]['lmList']  # 第二只手的关键点坐标信息
            
            # 第一次检测到食指间的距离
            if startDist is None:
                
                # 计算食指间的距离并绘图;食指的关键点索引是8;返回值:连线长度,连线的信息(起点、终点、中点坐标),绘制后的图像
                length, info, img  = distance = detector.findDistance(lmList1[8], lmList2[8], img)
                # print('length',length,'info',info)
                
                # 检测到的第一帧的食指间的距离作为初始距离,接下来超过这个长度就放大,小于这个长度就缩小
                startDist = length
            
            # 第一帧检测到距离之后,接下来变动的距离就是用于缩放图片大小
            length, info, img  = distance = detector.findDistance(lmList1[8], lmList2[8], img)
            
            # 计算变化量,正数代表放大,负数代表缩小。scale的变化范围过大,除以2使它变化缓慢一些
            scale = (length - startDist) // 2
            
            #(7)按比例缩放图像
            # 获取食指连线的中心点坐标,用于实时改变图像的位置
            cx, cy = info[4:]  # info是一个列表索引4和5存放中心点坐标
            
    # 如果两只手中至少有一只消失了,重置初始距离
    else:
        startDist = None
    
    
    try:  # 用于处理异常,因为一旦缩放的区间变成负数,就会报错
        # 确定需要缩放的图像的宽高
        h1, w1, _ = img1.shape  # 获取原始图像的宽高
        # 如果scale是奇数,那么计算结果不能被2整除,使得img中的空出的位置的shape和img1的shape不一样
        # newH, newW = h1+scale, w1+scale  
        newH, newW = int(((h1+scale)//2)*2), int(((w1+scale)//2)*2)  
        
        # 改变原图像的shape,先指定宽,后指定高
        img1 = cv2.resize(img1, (newW, newH))
        
        # 实时改变图像的位置,使图像中心点随着食指间的连线的中点的位置变化
        # 确保newH和newW可以被2整除,不然重组后的img中的shape和img1的shape不同
        img[cy - newH//2:cy + newH//2, cx - newW//2:cx + newW//2] = img1  # 先指定高,再指定宽
    
    except:  # 如果报错了的话上面try的内容不起作用
        pass

    #(7)展示视频图像
    # 计算fps
    cTime = time.time()  # 处理每一帧图像所需的时间
    fps = 1/(cTime-pTime)
    pTime = cTime  # 更新处理下一帧图像的起始时间

    # 把fps值显示在图像上,img画板,显示字符串,显示的坐标位置,字体,字体大小,颜色,线条粗细
    cv2.putText(img, str(int(fps)), (30,50), cv2.FONT_HERSHEY_PLAIN, 3, (255,0,0), 3)
    
    # 显示图像,输入窗口名及图像数据
    # cv2.namedWindow("img", 0)  # 窗口大小可手动调整
    cv2.imshow('img', img)    
    if cv2.waitKey(1) & 0xFF==27:  #每帧滞留1毫秒后消失,ESC键退出
        break

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

显示结果如下:

Guess you like

Origin blog.csdn.net/dgvv4/article/details/122201114