APP下载

基于MediaPipe的Python编程手势识别应用

2023-04-18牟奕炫

电脑报 2023年14期
关键词:赋值食指指尖

牟奕炫

MediaPipe 是一个以视觉算法为核心的机器学习工具库,集成了包括人脸及关键点检测、手势和姿态的检测与识别等多种模型的训练数据,识别的运行速度非常快,尤其适用于实时监控和流媒体视频画面内容的检测和识别。

以识别与检测人的单只手掌为例,MediaPipe 中的训练数据会识别出总共21 个手指关键点(标注为0-20),比如0 号点是WRIST( 腕关节),7 号点是INDEX_FINGER_DIP(食指下第一关节),16 号点是RING_FINGER_TIP(无名指指尖)(如图1)。

一、在Windows中识别关键点与标注

1. 前期的准备工作

台式机接好摄像头或使用笔记本电脑;在CMD 窗口定位至Python3.8 的安装路径, 用“pip install mediapipe” 安装MediaPipe。根据Python 环境安装或升级matplotlib 等相关库,注意确保提前安装好OpenCV, 直至“Successfullyinstalled”的成功提示。

启动PythonIDLE,新建文件开始程序代码的编写及运行测试。

2. 检测单(双)手并进行关键点的标注及连线

导入OpenCV 和MediaPipe 库模块; 定义变量camera, 并赋值为0,如果默认的摄像头编号不是0则更改为1, 打开摄像头; 建立变量hand_detector,用于创建检测人手关键点的检测对象,判断并获取视频画面中是否有符合“手”特征的信息,该参数保持为空时默认状态是“model_complexity=0,min_detection_confidence=0.5,min_tracking_confidence=0.5”,分别表示动态画面跟踪速度增益、检测及跟踪的置信度。

在循环中, 通过语句“ret,img =camera.read()” 实现对摄像头所拍摄画面的数据读取; 变量img_rgb, 用于将OpenCV 读取的BGR 模式图像转换为常规的RGB 模式; 变量result, 通过检测模型来提取变量img_rgb 所存储的图片信息(比如每帧画面检测出几只手);每次在检测到画面中出现一只手时,就可以通过语句“print(result.multi_hand_landmarks)”来打印输出对应的数据信息:一个内嵌有21 个字典数据(对应手的21个关键点)的列表,其中的每个字典均包含一组相对位置信息,形式为“landmark{x:0.8149930238723755,y:0.7734243273735046,z:-0.11069273203611374}”,其中的x、y 分别表示该点在视频画面中位置的百分比,z 表示该点距离摄像头的远近。在内嵌的“for handlms in result.multi_hand_landmarks:” 循环中, 语句“mp.solutions.drawing_utils.draw_landmarks(img,handlms,mp.solutions.hands.HAND_CONNECTIONS)”的作用是在img 图片上进行“作画”操作,其中的参数handlms 对应的是每个关键点(默认为红色小圆点),参数mp.solutions.hands.HAND_CONNECTIONS 对应的是相邻两个关键点进行连线(默认为白色细线段)。

语句“cv2.imshow('Video',img)” 是Python 调用OpenCV 的常规操作,对应的功能是将摄像头捕获的画面(包括使用MediaPipe 进行画点和连线的内容)输出显示在电脑屏幕上;变量k 的值为“cv2.waitKey(1)”,等待键盘响应事件时间为1毫秒,如果检测到用户按下了q 键(“if k== ord('q'):”)则执行break 跳出“whileTrue:”循环;语句“camera.release()”和“cv2.destroyAllWindows()”的功能是分别对应释放摄像头资源和关闭Video 监控窗口。

程序保存为“[01]Get_Points_Draw_Lines.py”并运行:当手掌朝向摄像头,在监控窗口中会即时标注有21 个关键点以及白色连接线;当手背朝向摄像头或者同时伸出两只手时,程序能否正常捕捉和标注;此时还会在PythonShell 窗口中不断显示有“Squeezed text(106 lines)”模块信息,双击即可显示其详细数据内容,即对应21 个关键点的landmark 列表(如图2)。

3. 检测右手三个指尖关键点并标注为不同颜色的圆点

将“[01]Get_Points_Draw_Lines.py”复制粘贴并重命名为“[02]Draw_Three_Points.py”, 代码段的开始库文件的导入、变量camera和hand_detector 等初始化代码,以及“while True:”循环体中的“cv2.waitKey(1)”等待键盘响应事件,还有最终摄像头资源释放、程序窗口关闭等不变。现以检测和标注右手三个指尖关键点为例,进行其他代码的编写:

语句“h,w,c = img.shape” 中的变量h 和w 对应摄像头所捕获画面的高度与宽度(c 对应的是通道),这些数据是从img.shape 中获取的;而在“while True:” 循环体嵌套的“forid,lm in enumerate(hand_lms):”中,通过语句“x,y = int(lm.x * w),int(lm.y * h)”来实现变量x、y 的动态赋值,也就是将视频画面中横向与纵向的百分比值(lm.x 和lm.y)分别与宽度w、高度h 进行乘法运算,再通过int() 取整得出视频画面中某手指关键点的真实相对二维坐标值(x,y)。当摄像头正常捕获到监控画面时(“if ret:”),对变量img 进行镜像翻转处理,赋值为“cv2.flip(img,1)”, 否则左手和右手的对应识别标志会有Right 和Left的“ 错位”; 建立变量position, 赋值为“{'Left':{},'Right':{}}”, 用来保存两只手各自的21个关键点坐标数据(此处可添加语句“print(position)”来测试); 在“for point in hands_data.multi_handedness:” 循環中,建立变量score, 赋值为“point.classification[0].score”,对应判断是否为某关键点的置信度,如果该值超过80%(“if score >= 0.8:”),则为变量label 赋值“point.classification[0].label”,也就是从classification 分类信息中获取其中的label值。

变量right_finger_4、right_finger_8 和right_finger_12 分别对应拇指、食指和中指的指尖位置共三个关键点,也就是MediaPipe 内手的训练数据(21个关键点):4号THUMB_TIP、8号INDEX_FINGER_TIP 和12 号MIDDLE_FIGER_TIP; 为变量right_finger_4 赋值为“position['Right'].get(4,None)”,表示右手的4 号关键点( 拇指指尖),而“position['Right'].get(8,None)” 和“position['Right'].get(12,None)” 则分别表示右手8 号(食指指尖)和12 号(中指指尖)关键点。接着,在三个if 判断语句(是否检测到对应的三个指尖关键点)中,分别为三个关键点进行不同颜色的圆点区域填充操作——如果检测到有右手拇指指尖出现,则调用OpenCV 中的circle 开始画圆:“cv2.circle(img,(right_finger_4[0],right_finger_4[1]),10,(0,0,255),cv2.FILLED)”,注意其中的“(0,0,255)”颜色代码是BGR模式,也就是表示纯红的颜色;同样,为右手食指和中指的指尖分别绘制绿色和蓝色圆点所对应的颜色代码就是“(0,255,0)”和“(255,0,0)”;另外,其中的参数10 表示的是所绘制圆点的半径为10 个像素大小。

测试时看是否能准确识别右手,掌心方向、掌背方向、侧向(也包括各种手指弯曲状态)时三个指尖的实时位置,并分别以红、绿、蓝颜色的圆点进行标注。

4. 左手食指指尖关键点的二维坐标值获取及标注

再将“[02]Draw_Three_Points.py” 复制粘贴并重命名为“[03]Get_IndexFinger_TIP.py”,继续进行代码更新,以左手食指指尖关键点为例实现二维坐标值的获取并绘制红色圆点。

除了需要在开始部分导入时间库(“import time”) 之外,“whileTrue:”循环体部分的代码基本上保持不变, 用变量left_finger_8 来替代之前的right_finger_4、right_finger_8 和right_finger_12, 赋值为“position['Left'].get(8,None)”, 即左手食指指尖的关键点;如果检测到该关键点出现(条件“ifleft_finger_8:” 为真), 则先执行语句“cv2.circle(img,(left_finger_8[0],left_finger_8[1] ) ,10 , ( 0 , 0 , 2 5 5 ) , c v 2 .FILLED)”, 在左手食指指尖上绘制半径为10 个像素的红色圆点; 再执行语句“print(left_finger_8[0],left_finger_8[1])”,作用是打印输出该关键点的二维坐标值——左上角为坐标原点(0,0),右下角为(640,480),由于程序中在关键点坐标值计算时会进行小数位数精度取舍以及取整等运算,存在一定的误差。考虑到截图显示左手食指指尖关键点二维坐标值,循环中加有延时语句“time.sleep(100)”,控制每次循环等待100毫秒。

程序保存之后按F5 运行测试:当右手出现在摄像头前时,画面中没有对任何一个关键点进行标注;换为左手时,食指的指尖立刻会出现一个红色圆点,同时会在程序界面不断有一组数据输出:在左上角区域时,显示为“105 57”“103 58”等;在右上角区域时,显示为“560 72”“56174”等;在左下角区域时,显示为“206289”“202 290”等;在右下角区域时,显示为“601 394”“598 383”等。

二、在树莓派中进行关键点识别与LED灯操控

在Windows 中进行各种测试成功的基础上,我们就可以到树莓派中再结合开源硬件库(像GPIOZero)进行许多创客项目的开发,比如使用左手的食指指尖来控制LED的亮度变化。

1. 前期的准备工作

实验硬件包括树莓派3B+主板一块,古德微扩展板一块,P5V04A SUNNY 定焦摄像头(带排线)一个,红色LED灯一支。首先将树莓派主板“CAMERA”卡槽接口黑色塑料锁扣轻轻向上拉起,然后将摄像头的“金手指”一面对准15根竖纹金属接触面,小心插入进去(银色部分差不多都要插进白色塑料插孔);接着,扶住软排数据线,将黑色塑料锁扣小心向下压紧、锁好,使摄像头与树莓派主板紧密接触;再将古德微扩展板安装上去,注意要特别小心别夹断摄像头的软排数据线,将其从侧面缝隙中引出;最后,将红色LED 灯按照“长腿正、短腿负”的原则,正确插入扩展板的5 号引脚处。

接下来,给树莓派加电,启动操作系统,通过Windows 的远程桌面连接(IP:192.168.1.120)程序进入后,运行“LX终端”开始安装MediaPipe,输入命令:“sudo pip3 install mediapipe-rpi3”,其中的参数“-rpi3”表示安装版本为树莓派3,回车,直至最终出现“Successfullyinstalled”的成功提示即可。

2. 在Thonny 中进行Python 代码编程

将“[03]Get_IndexFinger_TIP.py”复制粘贴并重命名为“[04]Control_LED_Bright.py”,开始代码的修改——

首先, 要导入GPIOZero 中的PWMLED:“from gpiozero importPWMLED”,目的是以PWM 模式来控制LED 灯的亮度;接着,建立变量Red_LED 并赋值为“PWMLED(5)”,作用是初始化插入5 号引脚的红色LED灯,并且通過语句“Red_LED.value = 0”将其默认状态设置为“不发光”(亮度为0),其他的代码基本不需要改变,在“whileTrue:”循环的“if left_finger_8:”识别左手食指指尖判断中,除了在该关键点上绘制红色圆点外,增加语句“Red_LED.value = left_finger_8[1]/500”,作用是为控制LED 亮度的变量Red_LED.value 重新赋值, 其中的“left_finger_8[1]”表示摄像头捕获画面中左手食指指尖所处的竖向纵坐标值,由于其范围为0-480,而GPIOZero库中控制LED灯亮度的PWM 范围是0-1,所以添加“除以500”的运算来匹配对应的数据区间(如图3)。

3. 测试左手食指指尖操控LED灯的亮度

运行程序,左手出现在摄像头的捕获画面中,识别并标注出左手食指的指尖关键点,红色LED灯的亮度会随之变化:当食指指尖处在屏幕靠上的区域时,LED灯的亮度比较低,因为此时该关键点的纵坐标值小;当食指指尖向屏幕下方滑动时,LED的亮度会越来越高,因为它的纵坐标值在逐渐变大(如图4)。(源代码可在“壹零社”公众号下载)

猜你喜欢

赋值食指指尖
L-代数上的赋值
治理“指尖乱像”不宜一散了之
虔诚之花在指尖绽放
指尖上的生活,指尖上的美
磨 刀
强赋值幺半群上的加权Mealy机与加权Moore机的关系*
指尖童话
利用赋值法解决抽象函数相关问题オ
平衡尺子
拇食指巨指症1例