手势点灯任务四:手势识别数字(基于手指弯曲程度)

任务四:手势识别数字(基于手指弯曲程度)


tis:本篇内容更倾向于数学问题,难度不大,就当是放松一下吧ヽ(≧∀≦)ノ

一、来源于网上的思路

1、理解网上的代码

(1)整体思路
  • 通过get_angleError()函数得到一个手指是否弯曲,不是则返回1,是则返回0

  • 通过getGesture()函数,通过输入每个手指是否弯曲的列表来判断是什么数字

  • 主函数中:先得到手的相关信息->分别将每个手指关键点调用至get_angleError()函数中判断手指弯曲->将数据返回至一个列表中->代入getGesture()函数,返回数字

(2)一个surprise的东西
  • 关于角度的计算,没有想到是用的斜率
angle_1 = math.degrees(math.atan((point_3_cx - point_4_cx) / (point_3_cy - point_4_cy)))
angle_2 = math.degrees(math.atan((point_1_cx - point_2_cx) / (point_1_cy - point_2_cy)))
angle_error = abs(angle_1 - angle_2)

tis:abs指绝对值

  • 博主原以为的是用三角形的正余弦定理,不过回想起来确实很蠢。。(T_T)

2、自己重新手搓detectNumber()

(1)个人想法
  • 可以将代入get_angleError()和getGesture()函数的过程全部装入detectNumber()函数中,使主函数可读性更强,同时省去了列表作为参数的传递
  • get_angleError()函数可以不用动,在detectNumber()中5次调用即可
  • 主函数部分变化不大,findHind()也不用动
(2)代码实现
  • detectNumber()函数
def detectNumber(hand_landmarks):
	myhand = hand_landmarks[0]
	isStraight_list = []

    point_4 = myhand.landmark[4]
    point_3 = myhand.landmark[3]
    point_2 = myhand.landmark[2]
    point_1 = myhand.landmark[1]
    angle_error_1, isStraight_1 = get_angleError(point_4, point_3, point_2, point_1)
    isStraight_list.append(isStraight_1)

    point_4 = myhand.landmark[8]
    point_3 = myhand.landmark[7]
    point_2 = myhand.landmark[6]
    point_1 = myhand.landmark[5]
    angle_error_2, isStraight_2 = get_angleError(point_4, point_3, point_2, point_1)
    isStraight_list.append(isStraight_2)

    point_4 = myhand.landmark[12]
    point_3 = myhand.landmark[11]
    point_2 = myhand.landmark[10]
    point_1 = myhand.landmark[9]
    angle_error_3, isStraight_3 = get_angleError(point_4, point_3, point_2, point_1)
    isStraight_list.append(isStraight_3)

    point_4 = myhand.landmark[16]
    point_3 = myhand.landmark[15]
    point_2 = myhand.landmark[14]
    point_1 = myhand.landmark[13]
    angle_error_4, isStraight_4 = get_angleError(point_4, point_3, point_2, point_1)
    isStraight_list.append(isStraight_4)

    point_4 = myhand.landmark[20]
    point_3 = myhand.landmark[19]
    point_2 = myhand.landmark[18]
    point_1 = myhand.landmark[17]
    angle_error_5, isStraight_5 = get_angleError(point_4, point_3, point_2, point_1)
    isStraight_list.append(isStraight_5)

    if isStraight_list[0] == 0 and isStraight_list[1] == 1 and isStraight_list[2] == 0 and isStraight_list[3] == 0 and isStraight_list[4] == 0:
		return 1
    elif isStraight_list[0] == 0 and isStraight_list[1] == 1 and isStraight_list[2] == 1 and isStraight_list[3] == 0 and isStraight_list[4] == 0:
        return 2
    elif isStraight_list[0] == 0 and isStraight_list[1] == 1 and isStraight_list[2] == 1 and isStraight_list[3] == 1 and isStraight_list[4] == 0:
        return 3
    elif isStraight_list[0] == 0 and isStraight_list[1] == 1 and isStraight_list[2] == 1 and isStraight_list[3] == 1 and isStraight_list[4] == 1:
        return 4
    elif isStraight_list[0] == 1 and isStraight_list[1] == 1 and isStraight_list[2] == 1 and isStraight_list[3] == 1 and isStraight_list[4] == 1:
		return 5
    elif isStraight_list[0] == 1 and isStraight_list[1] == 0 and isStraight_list[2] == 0 and isStraight_list[3] == 0 and isStraight_list[4] == 1:
        return 6
    elif isStraight_list[0] == 0 and isStraight_list[1] == 0 and isStraight_list[2] == 0 and isStraight_list[3] == 0 and isStraight_list[4] == 0:
         return 0
    else:
         return -1`
  • get_angleError()函数
def get_angleError(point_4, point_3, point_2, point_1):
	h, w, c = img.shape

	try:
		point_4_cx, point_4_cy = int(point_4.x * w), int(point_4.y * h)
        point_3_cx, point_3_cy = int(point_3.x * w), int(point_3.y * h)
        point_2_cx, point_2_cy = int(point_2.x * w), int(point_2.y * h)
        point_1_cx, point_1_cy = int(point_1.x * w), int(point_1.y * h)

        angle_1 = math.degrees(math.atan((point_3_cx - point_4_cx) / (point_3_cy - point_4_cy)))
        angle_2 = math.degrees(math.atan((point_1_cx - point_2_cx) / (point_1_cy - point_2_cy)))
        angle_error = abs(angle_1 - angle_2)
		if angle_error < 12:
			isStraight = 1
        else:
            isStraight = 0
	except:
		angle_error = 1000
		isStraight = 0

	return angle_error, isStraight

二、对这种方式的一些看法

  • 当我把程序跑起来后,结果很让人破防

piGTJnP.jpg

  • 如图所示:若是手正对着摄像头,则手指的四个特征点处于同一直线上(如图中1、2、3、4),导致,导致弯曲的被判定成直的
  • 所以必须将手微微斜对着摄像头才能正常识别。。。
    这怎么能行呢!

三、改良网上的已有方法

思路一:利用指根特征点

1、之前卡在哪儿?
  • 之前的惯性思维是利用单根手指上的四个点来建立角度关系,必然会导致正对时无法判断
  • 忽略了手掌上的点的使用意义
2、大致思路
  • 通过寻找手掌上距离相对较远(放大误差,便于参数调试)的但相对位置变化不大的点作为基准
  • 将每根手指顶端与其连线,得到角度
3、理论论证
  • 首先是两个相对固定点

    我这里选择的是5和17(食指和小拇指的指根)

  • 用于判断的五个角:5-4-17,5-8-17,5-12-17,5-16-17,5-20-17

    理论可行的原因:手指弯曲后都接近于掌心处,故这五个角的角度在手指弯曲时会由锐角变化为钝角

4、思路具体实施
  • emmm这种方法作者并没有成功
  • 核心问题出在斜率相减绝对值身上:两根手指相对位置改变后,原来的钝角可能被系统读取为锐角,导致判断出错
5、虽然没成功,但却给了我思路二
  • 既然出错出在绝对值上,那就回避,采用向量不就行了吗
  • (代码的尽头是数学,确信)

思路二:利用向量计算夹角

1、代码修改很简单,只用改一个函数
def get_angleError(point_4, point_3, point_2, point_1):
	h, w, c = img.shape

    point_4_cx, point_4_cy = int(point_4.x * w), int(point_4.y * h)
    point_3_cx, point_3_cy = int(point_3.x * w), int(point_3.y * h)
    point_2_cx, point_2_cy = int(point_2.x * w), int(point_2.y * h)
    point_1_cx, point_1_cy = int(point_1.x * w), int(point_1.y * h)

    a = np.array([(point_4_cx - point_3_cx), (point_4_cy - point_3_cy)])
    b = np.array([(point_1_cx - point_2_cx), (point_1_cy - point_2_cy)])

    angle = math.degrees(math.acos((np.dot(a, b)) / (np.linalg.norm(a, ord=2) * np.linalg.norm(b, ord=2))))

    if angle > 150:
		isStraight = 1
    else:
		isStraight = 0

    return angle, isStraight
  • 这里涉及到了范数的知识np.linalg.norm(a, ord=2)
    在后续的任务点中会经常遇到,可以参考:np.linalg.norm简介初步学习
2、效果也是相当amazing!!
  • 不仅有效解决了手对摄像头的角度问题,同时也解决了手与摄像头的距离问题
  • 个人认为是手势识别中最高效的方法

手势识别最终版(利用角度)

五、手势识别点灯项目完结撒花!!

最后为大家提供一个拓展:如何实现识别多位数呢?如何确定个位十位分别是哪只手?
就当做思考题吧!(bushi)

还没结束呢!

  • 是不是感觉最近两个任务很简单呢,没关系,一切才刚刚开始╮( ̄▽ ̄)╭
  • 接下来我们将基于这道题,深⼊了解mediapipe的底层原理和机器学习的基础知识

特别声明:以上的图片部分来自于网络,感谢CSDN、知乎等平台上各位博主的分享,本文用作交流学习予以引用,在此一并表示感谢!

  • 40
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值