首页/文章/ 详情

如何在树莓派4B上通过Google Mediapipe解决方案实现手势检测

2年前浏览3399

说起Google 的Mediapipe项目,它是一个跨平台,可定制的实时视频流机器学习解决方案。

能够实现的解决方案很多,可以根据自己的需求选择。

这里面有人脸检测,面部网格,虹膜检测, 手势检测, 姿态检测,全身姿态检测(包含面部信息), 头发分割, 物体检测, 物体box追踪, 实时运动追踪, KNIFT检测(交通信号检测) 3D对象外框检测等等,并且支持不同平台和不同语言的实现, 如下图所示:

这里我们看到支持Python语言的部分能够使用的解决方案有:Face detection面部检测,

Face mesh面部网格, Hands 手势识别, Pose 姿态检测, Holistic 全身检测, Selfie Segmentation **分割, Objectron 移动实时 3D 物体检测解决方案。


在这里,我们就拿Hands手势识别来做个比较有趣的应用。


关于手势

感知手的形状和运动的能力可能是改善跨各种技术领域和平台的用户体验的重要组成部分, 例如,可以通过对手部的动作,伸出的手指数量以及手指变化的动态来执行某些特定的操作,在树莓派上可以通过GPIO控制外部设备来响应这些特定操作,就实现了手势互动,是一个非常酷的应用。

MediaPipe Hands 是一种高解析度的手部和手指跟踪解决方案。它使用机器学习 (ML) 从单帧中推断出一只手的 21 个 3D 地标(Landmarks)。

MediaPipe Hands 利用一个由多个模型组成的 ML 管道协同工作:一个手掌检测模型,它对完整图像进行操作并返回一个定向的手部边界框。手部地标(landmarks)模型,对手掌检测器定义的裁剪图像区域进行操作并返回高解析度的 3D 手部关键点,方便我们通过OpenCV的画圈方法来绘制手部关键点的信息,从而判断这些关键点(地标,landmarks)在图像中的位置和姿态,通过这些信息可以实现一些操作的绑定,例如,我在摄像头前伸出两只手指,视频流中的每一帧图片被Mediapipe的Hands解决方案检测,检测出手部21点地标的信息,然后通过对这些地标点之间的变化实现控制GPIO的操作,我们以看看下面的手部地标信息模型的图示:

其中21个点可以将手部的每个关键点都表示出来,我们举个例子,假设我先要判断食指的指头尖在屏幕中的X,Y坐标信息,那么我们就需要获取landmark为8在屏幕中出现时所在的位置信息。然后通过openCV的circle方法就可以绘制一个圈,在屏幕上判断手指所在的位置就可以触发一些灵异事件,例如,手指滑向坐标位置为:(10,20)到(20, 40) 这个区间的时候,我们触发一个操作,在屏幕的图像上叠加一个图片或者文字。就能实现一个很好玩儿的应用了,大家可以开开脑洞,鬼屋逃生里面可以用到的应用。哈哈!

渲染合成的手部图像是不是感觉很清晰明了?


如何在树莓派上安装?

1. 下载烧录系统并接入摄像头

这一步建议通过下载etcher工具和官方镜像文件,烧录完成。(下载链接见附件)

建议32bit的,因为64bit的因为mmal支持不好,导致树莓派libcamera-lib不好用。


摄像头安装非常简单,参考下图进行安装即可,两侧向上拔起卡销,然后插入FPC排线,然后按下卡销,注意保持水平和注意方向即可。

2. 安装虚拟环境

执行命令:

sudo apt update

sudo apt –y install vim virtualenv

3. 配置虚拟环境并安装OpenCV库

执行:

virtualenv –p python3 venv

进入虚拟环境并激活:

cd venv

source bin/activate

python3 -m pip install opencv-python

pip3 install opencv-contrib-python

耐心等待安装完成,如果失败了,多执行几次,直到成功再继续。

看到Successfully installed 就好了。

4. 安装mediapipe库

执行:

pip3 install mediapipe-rpi4

等待安装完成,基本环境就搭建好了。

同样等待看到Successfully安装就好了。

5. 开启树莓派摄像头

执行命令:

sudo raspi-config

然后选择 3 Interface options,

然后选择Legacy Camera Enable…


然后检测一下摄像头是否能够被检测到:

vcgencmd get_camera


6. 编写代码测试

接下来就可以尝试编写代码进行测试了,其中可以参考官方提供Python代码示例来进行学习:

import cv2

import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils

mp_drawing_styles = mp.solutions.drawing_styles

mp_hands = mp.solutions.hands


# For static images:

IMAGE_FILES = []

with mp_hands.Hands(

static_image_mode=True,

max_num_hands=2,

min_detection_confidence=0.5) as hands:

for idx, file in enumerate(IMAGE_FILES):

# Read an image, flip it around y-axis for correct handedness output (see

# above).

image = cv2.flip(cv2.imread(file), 1)

# Convert the BGR image to RGB before processing.

results = hands.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))


# Print handedness and draw hand landmarks on the image.

print('Handedness:', results.multi_handedness)

if not results.multi_hand_landmarks:

continue

image_height, image_width, _ = image.shape

annotated_image = image.copy()

for hand_landmarks in results.multi_hand_landmarks:

print('hand_landmarks:', hand_landmarks)

print(

f'Index finger tip coordinates: (',

f'{hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].x * image_width}, '

f'{hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y * image_height})'

)

mp_drawing.draw_landmarks(

annotated_image,

hand_landmarks,

mp_hands.HAND_CONNECTIONS,

mp_drawing_styles.get_default_hand_landmarks_style(),

mp_drawing_styles.get_default_hand_connections_style())

cv2.imwrite(

'/tmp/annotated_image'   str(idx)   '.png', cv2.flip(annotated_image, 1))

# Draw hand world landmarks.

if not results.multi_hand_world_landmarks:

continue

for hand_world_landmarks in results.multi_hand_world_landmarks:

mp_drawing.plot_landmarks(

hand_world_landmarks, mp_hands.HAND_CONNECTIONS, azimuth=5)



让我们对其进行简单的分析:

import cv2

import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils

mp_drawing_styles = mp.solutions.drawing_styles

mp_hands = mp.solutions.hands

这些段落在导入opencv的库,mediapipe的库,并且导入了绘图工具drawing_utils和绘图风格,还有hands解决方案的类,并且实例化为mp_hands对象。

后面初始化部分中,非常关键的就是通过实例化Hands对象,并对支持的配置选项进行微调,例如,static_image_mode 就是启用静态图片模式, max_num_hands可以检测手的最大数量为2, 最小置信值设置0.5, 就只要判断达到0.5 就会认为检测成功,虽然会有误判,但是速度会稍微有所提升,如果想要达到更高,可以调整这里面的数值。


with mp_hands.Hands(

static_image_mode=True,

max_num_hands=2,

min_detection_confidence=0.5) as hands:

for idx, file in enumerate(IMAGE_FILES):

# Read an image, flip it around y-axis for correct handedness output (see

# above).

image = cv2.flip(cv2.imread(file), 1)

# Convert the BGR image to RGB before processing.

results = hands.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

这段内容中,image = cv2.flip(cv2.imread(file), 1)这句的含义是让整个图像沿着Y轴翻转,这样你在屏幕上看到的画面中,你的左右手就成镜像分布,看上去就像看着镜子里面一般,否则就是反向的,你伸出左手,它显示在右侧。

另外还有一个非常关键的步骤就是, hands.process()函数在进行检测的时候,需要传参,整个参数应该是RGB格式的图像,但是默认我们使用OpenCV采集到的图像默认是BGR格式,需要转换一下,否则无法检测。


后一段的内容解析:

定义三个变量为图像高和宽,通道,但是通道用不到,就用“_”替代,变量内容通过图象个对象的shape属性获取,并且通过copy()方法拷贝了一个副本作为操作的对象。这里非常关键的一步是通过遍历整个results对象中的multi_hand_landmarks的值,并且通过landmarks的21个地标的信息获取每个关键点在屏幕上的位置,通过获取每个地标的x坐标和y坐标, 乘以图像的宽和高,就可以算出在图像中的实际位置。

image_height, image_width, _ = image.shape

annotated_image = image.copy()


for hand_landmarks in results.multi_hand_landmarks:

print('hand_landmarks:', hand_landmarks)

print(

f'Index finger tip coordinates: (',

f'{hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].x * image_width}, '

f'{hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y * image_height})'

)


这部分就开始调用draw_landmarks()方法在图片上绘制手部21点的图示,并且通过将这些点通过线段连接起来,形成手部骨架的图示。

mp_drawing.draw_landmarks(

annotated_image,

hand_landmarks,

mp_hands.HAND_CONNECTIONS,

mp_drawing_styles.get_default_hand_landmarks_style(),

mp_drawing_styles.get_default_hand_connections_style())

后面就是将结果写入图形文件中的操作。

cv2.imwrite(

'/tmp/annotated_image'   str(idx)   '.png', cv2.flip(annotated_image, 1))

# Draw hand world landmarks.

if not results.multi_hand_world_landmarks:

continue

for hand_world_landmarks in results.multi_hand_world_landmarks:

mp_drawing.plot_landmarks(

hand_world_landmarks, mp_hands.HAND_CONNECTIONS, azimuth=5)

临时文件路径和文件索引,并传入翻转过后的图片(沿着Y轴旋转后的)。

最后就是判断如果检测到手就显示手的节点信息并显示连接信息否则就继续检测。

再就是通过对视频流的操作。

# For webcam input:

初始化一个实例cap,将摄像头索引为0的设备初始化为一个捕获对象,从而得到视频的每一帧图片。

cap = cv2.VideoCapture(0)

同样实例化一个Hands类,生成一个叫hands的对象。

with mp_hands.Hands(

model_complexity=0,

min_detection_confidence=0.5,

min_tracking_confidence=0.5) as hands:

不停的判断摄像头是否是开着,如果开着,就利用cap.read()读取一帧图片。

while cap.isOpened():

success, image = cap.read()

if not success:

print("Ignoring empty camera frame.")

# If loading a video, use 'break' instead of 'continue'.

continue


# To improve performance, optionally mark the image as not writeable to

# pass by reference.

将图片变为只读模式

image.flags.writeable = False

转换图片格式,从BGR转换到RGB

image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

将图片交给process方法处理。

results = hands.process(image)


# Draw the hand annotations on the image.

绘制手部的关键点的图形和连接部分的图像。

image.flags.writeable = True

image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

if results.multi_hand_landmarks:

for hand_landmarks in results.multi_hand_landmarks:

mp_drawing.draw_landmarks(

image,

hand_landmarks,

mp_hands.HAND_CONNECTIONS,

mp_drawing_styles.get_default_hand_landmarks_style(),

mp_drawing_styles.get_default_hand_connections_style())

# Flip the image horizontally for a selfie-view display.

翻转图片并显示出来。

cv2.imshow('MediaPipe Hands', cv2.flip(image, 1))

5毫秒检测是否按下ESC,如果按下就退出循环并释放摄像头,最好还要关闭窗口。

if cv2.waitKey(5) & 0xFF == 27:

break

cap.release()

cv2.destroyAllWindows()

实际测试效果:

然后通过检测手指的所有landmarks并绘制圆形的点:

我们只需要食指,因此判断id为8的时候才绘图,即可实现以食指的index值8的x和y的坐标参数,乘以图像的宽和高,得到实际在图像中位置,然后绘制一个圆点。

代码内容如下:

import cv2

import time

import mediapipe as mp



cap = cv2.VideoCapture(0)

mp_draw = mp.solutions.drawing_utils

mp_hands = mp.solutions.hands

pTime = 0


detector = mp_hands.Hands(min_detection_confidence=0.5)


while True:

ret, frame = cap.read()

cTime = time.time()

fps = 1 / (cTime - pTime)

pTime = cTime

frame_RGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)


result = detector.process(frame_RGB)


# print(result.multi_hand_landmarks)

if result.multi_hand_landmarks is not None:

print(result.multi_hand_landmarks[0].landmark)

for id, landmark in enumerate(result.multi_hand_landmarks[0].landmark):

print(id, landmark)

height, width, channel = frame.shape

if id == 8:

fx = int(landmark.x * width)

fy = int(landmark.y * height)

cv2.circle(frame, (fx, fy), 5, (0, 0, 255), cv2.FILLED)


cv2.putText(frame, "FPS:{}".format(int(fps)), (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 3)

cv2.imshow("image", frame)


if cv2.waitKey(1) & 0xFF == 27:  # press ESC key

break


cap.release()

cv2.destroyAllWindows()


最后,我们只需要通过调用rpi.gpio的库来实现手指一点就点亮一个LED灯, 我们将一颗LED灯接上1k欧电阻并将正极接入12号引脚,负极接入GND引脚。

如果没有安装请在终端执行:

pip3 install RPi.GPIO

代码中添加:

import RPi.GPIO as GPIO

import cv2

import time

import mediapipe as mp



GPIO.setmode(GPIO.BOARD)

GPIO.setup(12, GPIO.OUT)


cap = cv2.VideoCapture(0)

mp_draw = mp.solutions.drawing_utils

mp_hands = mp.solutions.hands

pTime = 0


detector = mp_hands.Hands(min_detection_confidence=0.5)


while True:

ret, frame = cap.read()

cTime = time.time()

fps = 1 / (cTime - pTime)

pTime = cTime

frame_RGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)


result = detector.process(frame_RGB)


# print(result.multi_hand_landmarks)

if result.multi_hand_landmarks is not None:

print(result.multi_hand_landmarks[0].landmark)

for id, landmark in enumerate(result.multi_hand_landmarks[0].landmark):

print(id, landmark)

height, width, channel = frame.shape

if id == 8:

fx = int(landmark.x * width)

fy = int(landmark.y * height)

cv2.circle(frame, (fx, fy), 5, (0, 0, 255), cv2.FILLED)

GPIO.output(12, GPIO.HIGH)

else:

GPIO.output(12, GPIO.LOW)


cv2.putText(frame, "FPS:{}".format(int(fps)), (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 2, (255, 0, 0), 3)

cv2.imshow("image", frame)


if cv2.waitKey(1) & 0xFF == 27:  # press ESC key

break


cap.release()

cv2.destroyAllWindows()


然后保存并执行该程序,这里我的程序名称为test_camera.py。

python test_camera.py


然后将手在摄像头前晃动,只要检测到你的食指尖,就会点亮LED灯,我们这里用最简单的方法带着大家实现一个手指控制LED灯的功能,请大家开开脑洞,尝试一下别的操作。例如,通过检测2个手指的指尖的距离,来实现图片的缩放,或者实现音频音量大小的控制。

通过检测手指的数量来进行语音播报,或者通过检测手指的位置来播放动画。剩下的就交给你们来创造了!



特别推荐:

《树莓派4与人工智能实战项目》

带你深入了解树莓派和人工智能的世界

清华大学出版社

ISBN:9787302603252

出版时间:2022-07-01

定价:79元


内容简介

本书主要介绍树莓派不同类型的特性,以及树莓派入门所需要的基础知识;涵盖了树莓派GPIO 的不同操作方法,以及树莓派的I2C总线、SPI总线、UART串口、PWM脉宽调制等偏硬件操作的内容;同时也为读者准备了一些树莓派上常见的服务类型的搭建和配置,包括树莓派推流服务器搭建的方法, 常见数据库MariaDB、PostgreSQL的安装配置操作,MQTT服务器的搭建配置,DHCP服务器的搭建 配置等。

此外,还加入了一些比较有趣的实验,例如利用TensorFlow实现对象检测,使用OpenCV制作一个树莓派扫描仪,或利用OpenCV实现换鼻子的实验,带领读者了解树莓派通过摄像头能够实现的一些应用。

本书为初学者全面入门了解树莓派提供了很好的切入点,使读者可以了解更多树莓派的使用方法以及操作小技巧。同时,在整体的编程过程中使用了C语言、Python语言及Shell脚本语言等常见语言, 对于拥有此类语言编程经验的用户更友好。希望读者能够在这里找到自己喜欢的实验,并顺利入门树莓派!

作者简介

李伟斌  (漂移菌 )
     目前就职伍艾信息科技(上海)有限公司,首席Linux技术架构师。主要研究方向为嵌入式 Linux 操作系统的应用及研发。 业余时间曾获取中美创客马拉松(上海站)一等奖, Intel IoT 物联网大赛二等奖, 上海国际创客大赛蘑菇云分赛首届脑洞大赛 脑洞大王奖, 上海谷歌Design Sprint Hackathon 优胜奖, Junction 2021 全球黑客大赛 Out of box奖。被朋友誉为树莓派超级爱好者及布道者, 并且兼漂移驴车项目联合创始人角色。




来源:路飞的电子设计宝藏

附件

5积分下载链接.txt
python通信控制渲染人工智能
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2022-09-15
最近编辑:2年前
一路带飞
硕士 一路带飞,高级硬件工程师
获赞 66粉丝 88文章 122课程 4
点赞
收藏
作者推荐
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈