任务四:手势识别数字(基于手指弯曲程度)
tis:本篇内容更倾向于数学问题,难度不大,就当是放松一下吧ヽ(≧∀≦)ノ
一、来源于网上的思路
- 该部分的核心灵感来源于基于mediapipe的手势数字识别
- 这种方法从开始就解决了距离问题,理论上可行性更强
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
二、对这种方式的一些看法
- 当我把程序跑起来后,结果很让人破防
- 如图所示:若是手正对着摄像头,则手指的四个特征点处于同一直线上(如图中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、知乎等平台上各位博主的分享,本文用作交流学习予以引用,在此一并表示感谢!