欢迎访问我的网站,希望内容对您有用,感兴趣的可以加入我们的社群。

OpenCV检测手指个数

OpenCV 迷途小书童 4年前 (2020-12-09) 2739次浏览 0个评论

软硬件环境

  • windows 10 64bit
  • anaconda with python 3.7
  • nvidia gtx 1066
  • opencv 4.4.0

基本原理

我们先来看余弦定理,它是检测手指个数的根本原理。下面是一个三角形

opencv_count_fingers

余弦定理表述为,对于任意三角形,任何一边的平方等于其他两边平方的和减去这两边与它们夹角的余弦的积的两倍。用数学公式可以如下图表示

opencv_count_fingers

对应到本文的手指检测,我们手指之间其实也有一个三角形,因此利用余弦定理,就可以计算出2根手指的夹角,如果这个夹角小于90度(正常情况下不可能超出),就可以认为这就是手指了

opencv_count_fingers

代码示例

import numpy as np
import cv2

def skinmask(img):
    '''

    :param img:
    :return:
    '''

    # 从BGR转换成HSV,即色调、饱和度、明度
    # HSV(Hue、Saturation、Value)基本颜色分量范围,可以参考 https://image.xugaoxiang.com/imgs/2020/12/a3cceeb4f7c9ea2f.jpg
    hsvim = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    # 颜色范围下边界
    lower = np.array([0, 48, 80], dtype="uint8")
    # 颜色范围上边界
    upper = np.array([20, 255, 255], dtype="uint8")

    # 检查数组元素是否位于另外两个数组的元素之间,指定范围内的图像显示为白色,相反为黑色
    skinRegionHSV = cv2.inRange(hsvim, lower, upper)

    # 均值滤波,核大小2*2
    blurred = cv2.blur(skinRegionHSV, (2, 2))

    # 阈值处理,阈值为0,填充色为255,小于阈值的像素点置0,大于阈值的像素点置填255
    ret, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY)

    return thresh

def getcnthull(mask_img):
    # 找轮廓
    contours, hierarchy = cv2.findContours(mask_img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    contours = max(contours, key=lambda x: cv2.contourArea(x))

    # 找凸包
    hull = cv2.convexHull(contours)

    return contours, hull

def getdefects(contours):
    # 找凸包,returnPoints=False
    hull = cv2.convexHull(contours, returnPoints=False)

    # 找凸缺陷,也就是我们手指打开时,2指之间凹的部分
    defects = cv2.convexityDefects(contours, hull)

    return defects

if __name__ == '__main__':

    # 读取摄像头数据
    cap = cv2.VideoCapture(0)
    while cap.isOpened():
        _, img = cap.read()
        try:
            mask_img = skinmask(img)
            contours, hull = getcnthull(mask_img)

            # 画轮廓
            cv2.drawContours(img, [contours], -1, (255, 255, 0), 2)
            cv2.drawContours(img, [hull], -1, (0, 255, 255), 2)

            # 轮廓线为convexity hull, 而convexity hull与手掌之间的部分为convexity defects。每个convexity defect区域有四个特征量:起始点(startPoint),结束点(endPoint),距离convexity hull最远点(farPoint),最远点到convexity hull的距离(depth)。
            defects = getdefects(contours)
            if defects is not None:
                cnt = 0
                for i in range(defects.shape[0]):
                    s, e, f, d = defects[i][0]
                    start = tuple(contours[s][0])
                    end = tuple(contours[e][0])
                    far = tuple(contours[f][0])

                    # 得到三角形3条边的长度
                    a = np.sqrt((end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2)
                    b = np.sqrt((far[0] - start[0]) ** 2 + (far[1] - start[1]) ** 2)
                    c = np.sqrt((end[0] - far[0]) ** 2 + (end[1] - far[1]) ** 2)

                    # 使用余弦定理计算角度
                    angle = np.arccos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c))

                    # 如果角度小于90度,则认为是手指
                    if angle <= np.pi / 2:
                        cnt += 1
                        cv2.circle(img, far, 4, [0, 0, 255], -1)
                if cnt > 0:
                    cnt = cnt + 1

                # 显示手指个数
                cv2.putText(img, str(cnt), (0, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 0, 0), 2, cv2.LINE_AA)
            cv2.imshow("img", img)
        except:
            pass

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

执行上述代码,可以得到

opencv_count_fingers

喜欢 (2)

您必须 登录 才能发表评论!