使用OpenCV和mediapipe实现手部信息抓取

news2024/11/18 17:37:14

目录

运行效果

挨行解读

(1)初始化MediaPipe Hand模块

(2)打开摄像头

(3)初始化计时器

(4)开始程序主题部分

(5)读取视频帧

(6)将图像转换为RGB格式(MediaPipe要求输入为RGB格式)

(7)使用MediaPipe Hands模块处理图像

(8)绘制手部关键点及骨骼线

(9)计算FPS

(10)显示处理后的图像

(11)检测按键,按下ESC键退出循环

(12)最后来释放资源退出程序

总体代码

总结


运行效果

由于用两只手我就没法按键盘截图了,就放上一只手的效果吧~~~

挨行解读

(1)初始化MediaPipe Hand模块

mp_hands = mp.solutions.hands

        这一行代码是导入了 mediapipe 库中的 hands 模块,然后通过 mp_hands 变量来引用这个模块。

        说的详细一点的话就是 mediapipe.solutions.hands 模块包含了 Hands 类,该类提供了对手部关键点检测的功能。通过使用这个类,咱们就可以轻松地在图像或视频流中检测手的位置、手指的姿势等信息。

        这样我们就可以在代码中使用 mp_hands 来引用 mediapipe.solutions.hands 模块,使代码更easy!!!

hands = mp_hands.Hands()

        这一行代码创建了一个 mediapipe 库中 Hands 类的实例,这样我们就可以使用 mediapipe 提供的手部关键点检测功能。

        说的详细一点就是 mp_hands.Hands() 的调用初始化了一个手部关键点检测器,可以在输入的图像或视频流中检测手的位置、手指的姿势等信息。这个实例化的对象(hands 变量)具有方法 process(),可以用来对图像进行处理并获得手部关键点的检测结果。

        

mp_drawing = mp.solutions.drawing_utils

        这一行代码导入了 mediapipe 库中的 drawing_utils 模块,并使用 mp_drawing 变量引用了这个模块。

       mediapipe.solutions.drawing_utils 模块包含了一些辅助的函数,可以在图像上绘制关键点、连接线等。这样就可以绘制出手部的效果。

      

(2)打开摄像头

cap = cv2.VideoCapture(0)

        这一行代码使用 OpenCV 中的 cv2.VideoCapture 类创建了一个视频捕捉对象 cap,并打开了系统默认的摄像头(通常是编号为 0 的摄像头)。

   cv2.VideoCapture(0) 传递了参数 0,意思是要打开系统中的第一个摄像头。如果我们有多个摄像头的话,可以通过更改参数来选择不同的摄像头,例如 cv2.VideoCapture(1) 表示打开第二个摄像头。

(3)初始化计时器

fps_time = time.time()

  fps_time = time.time() 我们通过这一行代码初始化一个变量 fps_time,其目的是记录程序开始运行时的时间戳。

(4)开始程序主题部分

while cap.isOpened():

  while cap.isOpened(): 表示在视频捕捉对象 cap 处于打开状态时,循环将一直执行。循环内的代码将不断读取视频帧并进行处理,直到视频捕捉对象被释放或关闭。

       cap.isOpened() 返回一个布尔值,指示视频捕捉对象是否成功打开。如果返回 True,表示对象成功打开,循环会继续执行;如果返回 False,表示对象未成功打开或已经关闭,循环将退出。

        我们这个循环结构是一个非常常见的视频处理模式,这样就可以持续地从摄像头中读取帧并进行处理,直到程序结束。

(5)读取视频帧

    # 读取视频流中的一帧
    ret, frame = cap.read()
    if not ret:
        break    # 读取视频流中的一帧
  1. ret, frame = cap.read(): 这一行代码使用 cap.read() 方法来读取视频的一帧。cap.read() 返回两个值,第一个是布尔值 ret,表示读取是否成功;第二个是图像帧 frame,是一个表示图像数据的 NumPy 数组。

  2. if not ret: break: 这一行检查 ret 的值,如果 retFalse,表示读取失败(可能是因为视频结束或发生了错误),那么我们就跳出循环,结束程序。

(6)将图像转换为RGB格式(MediaPipe要求输入为RGB格式)

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

(7)使用MediaPipe Hands模块处理图像

results = hands.process(frame_rgb)

        这一行代码使用了 mediapipe 库中 Hands 类的 process 方法来处理图像帧,即 hands.process(frame_rgb)。我来挨个解释一下:

  1. hands 是我们之前通过 mp_hands.Hands() 创建的 ,它提供了手部关键点检测的功能。

  2. frame_rgb 是一个图像帧的 NumPy 数组,表示图像数据。注意了mediapipe 库要求输入图像为 RGB 格式。

  3. hands.process()Hands 类中的一个方法,用于处理输入的图像帧。它会对图像进行手部关键点检测,并返回一个包含检测结果的对象。

  4. results 是接收检测结果的变量。通过调用 hands.process(frame_rgb),检测结果将被存储在 results 中。这个结果包括了检测到的手的位置、手指关键点的坐标等信息。

        因此,这一行代码的目的是使用 mediapipe 库提供的手部关键点检测功能,处理输入的图像帧,并将检测结果存储在 results 变量中,然后我们后续的代码就可以通过results来实现可视化。

(8)绘制手部关键点及骨骼线

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # 获取手指关键点
            finger_points = [hand_landmarks.landmark[i] for i in [4, 8, 12, 16, 20]]

            # 绘制手指骨骼线
            for i in range(4):
                start_point = (int(finger_points[i].x * frame.shape[1]), int(finger_points[i].y * frame.shape[0]))
                end_point = (int(finger_points[i + 1].x * frame.shape[1]), int(finger_points[i + 1].y * frame.shape[0]))
                cv2.line(frame, start_point, end_point, (255, 0, 255), 2)

        这段代码的主要作用是处理手部关键点检测的结果,将检测到的手部关键点和手指骨骼线绘制在图像上。挨个来看:

  1. if results.multi_hand_landmarks:这个条件语句检查是否检测到了不只一只手。results.multi_hand_landmarks 是一个列表,每个元素代表一个检测到的手部的关键点坐标。

  2. for hand_landmarks in results.multi_hand_landmarks: 这个循环遍历每个检测到的手部,其中 hand_landmarks 包含了该手部的关键点坐标。

  3. mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS): 这一行代码使用 drawing_utils 模块的 draw_landmarks 函数在图像上绘制手部关键点和手指骨骼线。frame 是要绘制的图像,hand_landmarks 是该手部的关键点坐标,mp_hands.HAND_CONNECTIONS 表示绘制手部关键点之间的连接线。

  4. finger_points = [hand_landmarks.landmark[i] for i in [4, 8, 12, 16, 20]]: 这一行代码获取手部关键点中特定手指的坐标,我们这里选择的是食指。landmark 对象包含了手部关键点的三维坐标(x、y、z)。

  5. for i in range(4): ...: 这个循环遍历食指的关键点,然后再绘制手指骨骼线。在每次迭代中,获取相邻两个关键点的坐标,然后使用 cv2.line() 绘制一条连接这两个点的线段。

        这样,我们就实现了可视化手部的姿势。

(9)计算FPS

 # 计算帧率
    current_time = time.time()
    fps = 1 / (current_time - fps_time)
    fps_time = current_time

    # 显示帧率
    cv2.putText(frame, f'FPS: {int(fps)}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

        挨个解释一下

  1. current_time = time.time(): 这一行获取当前时间戳,表示当前时刻的时间。

  2. fps = 1 / (current_time - fps_time): 这一行计算帧率。帧率是每秒处理的图像帧数。计算公式是总帧数除以总时间,这里使用时间差来估算每秒的帧数。

  3. fps_time = current_time: 更新 fps_time,将其设置为当前时间戳,以便在下一帧时可以继续计算帧率。

  4. cv2.putText(...): 这一行使用 OpenCV 的 putText 函数在图像上绘制帧率信息。具体参数为:

    • frame: 要绘制文本的图像。
    • f'FPS: {int(fps)}': 要显示的文本,包括字符串 "FPS: " 和计算得到的帧率值。
    • (10, 30): 文本的位置,即左上角的坐标。
    • cv2.FONT_HERSHEY_SIMPLEX: 字体类型。
    • 1: 字体大小。
    • (0, 255, 0): 文本颜色,这里是绿色,RGB嘛g就是绿色哪个单词的首字母。
    • 2: 文本的线宽。

        这样就可以实时显示FPS了,就很爽~~~^_^

(10)显示处理后的图像

    cv2.imshow('Hand grasping', frame)

使用 OpenCV 的 imshow 函数来在窗口中显示图像。

  1. 'Hand grasping': 这是窗口的标题,即显示图像窗口的名称。

  2. frame: 这是咱要在窗口中显示的图像。frame 是通过摄像头捕捉到的一帧图像,或者经过处理后的图像。

  3. cv2.imshow('Hand grasping', frame): 这一行代码将图像 frame 显示在窗口中,窗口标题为 'Hand grasping'。imshow 函数用于在窗口中显示图像。

(11)检测按键,按下ESC键退出循环

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

    

  1. cv2.waitKey(1): 这一行代码等待键盘输入,参数 1 表示等待 1 毫秒。函数返回按键的 ASCII 值,如果没有按键输入,返回 -1

  2. & 0xFF == 27: 这个条件语句检查按键的 ASCII 值是否等于 27。ASCII 值为 27 的键是 ESC 键(Escape 键)。

  3. if ...: break: 如果用户按下 ESC 键,即条件为真,那么程序会执行 break 语句,跳出循环,从而结束程序的执行。

        意思就是按下ESC就退出

(12)最后来释放资源退出程序

  1. cap.release(): 这一行代码释放了通过 cv2.VideoCapture(0) 打开的视频捕获对象的资源。

  2. cv2.destroyAllWindows(): 这一行代码关闭所有通过 OpenCV 创建的窗口。如果关闭时出现问题,可能会在程序结束后窗口继续存在。那么我们通过调用 destroyAllWindows()来确保在程序退出时所有图形窗口都肯定会被关闭。


总体代码

import cv2
import mediapipe as mp
import time

# 初始化MediaPipe Hand模块
mp_hands = mp.solutions.hands
hands = mp_hands.Hands()
mp_drawing = mp.solutions.drawing_utils

# 打开摄像头
cap = cv2.VideoCapture(0)

# 初始化计时器
fps_time = time.time()

while cap.isOpened():
    # 读取视频流中的一帧
    ret, frame = cap.read()
    if not ret:
        break

    # 将图像转换为RGB格式(MediaPipe要求输入为RGB格式)
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # 使用MediaPipe Hands模块处理图像
    results = hands.process(frame_rgb)

    # 绘制手部关键点及骨骼线
    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # 获取手指关键点
            finger_points = [hand_landmarks.landmark[i] for i in [4, 8, 12, 16, 20]]

            # 绘制手指骨骼线
            for i in range(4):
                start_point = (int(finger_points[i].x * frame.shape[1]), int(finger_points[i].y * frame.shape[0]))
                end_point = (int(finger_points[i + 1].x * frame.shape[1]), int(finger_points[i + 1].y * frame.shape[0]))
                cv2.line(frame, start_point, end_point, (255, 0, 255), 2)

    # 计算帧率
    current_time = time.time()
    fps = 1 / (current_time - fps_time)
    fps_time = current_time

    # 显示帧率
    cv2.putText(frame, f'FPS: {int(fps)}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

    # 显示处理后的图像
    cv2.imshow('Hand grasping', frame)

    # 检测按键,按下ESC键退出循环
    if cv2.waitKey(1) & 0xFF == 27:
        break

# 释放资源
cap.release()
cv2.destroyAllWindows()

总结

以后再总结~!~~~~ヾ( ̄▽ ̄)Bye~Bye~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1485058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++基础知识补充5

隐式类型转换型初始化:如A a1, 与全局类没有区别,只是受主类的类域限制,内部类天生是主类的友元 采用匿名构造如A()代替A aa可以加速编译器优化 接收建议新创建一个对象

基于springboot+vue的科研工作量管理系统

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 ​主要内容:毕业设计(Javaweb项目|小程序|Pyt…

大模型(LLM)的token学习记录-I

文章目录 基本概念什么是token?如何理解token的长度?使用openai tokenizer 观察token的相关信息open ai的模型 token的特点token如何映射到数值?token级操作:精确地操作文本token 设计的局限性 tokenizationtoken 数量对LLM 的影响训练模型参…

c语言--qsort函数(详解)

目录 一、定义二、用qsort函数排序整型数据三、用qsort排序结构数据四、qsort函数的模拟实现 一、定义 二、用qsort函数排序整型数据 #include<stdio.h> scanf_S(int *arr,int sz) {for (int i 0; i < sz; i){scanf("%d", &arr[i]);} } int int_cmp(c…

【GitHub】修改默认分支

GitHub的默认分支为main&#xff0c;但我们常常习惯使用master作为默认分支&#xff0c;那在GitHub上如何将master修改为默认分支呢&#xff1f; 全局修改 点击头像&#xff0c;选择菜单栏中的设置 输入master作为默认分支&#xff0c;然后执行updating即可&#xff01; 单项…

【Java数据结构】——二叉搜索树

目录 &#x1f388;概念 &#x1f388;操作-查找 &#x1f388;操作-插入 &#x1f388;操作-删除&#xff08;难点&#xff09; &#x1f388;概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树 : 若它的左子树不为空&a…

【Qt学习笔记】(四)Qt窗口

Qt窗口 1 菜单栏1.1 创建菜单栏1.2 在菜单栏中添加菜单1.3 创建菜单项1.4 在菜单项之间添加分割线1.5 给菜单项添加槽函数1.6 给菜单项添加快捷键 2 工具栏2.1 创建工具栏2.2 设置停靠位置2.3 设置浮动属性2.4 设置移动属性2.5 添加 Action 3 状态栏3.1 状态栏的创建3.2 在状态…

Matlab 机器人工具箱 动力学

文章目录 R.dynR.fdynR.accelR.rneR.gravloadR.inertiaR.coriolisR.payload官网:Robotics Toolbox - Peter Corke R.dyn 查看动力学参数 mdl_puma560; p560.dyn;%查看puma560机械臂所有连杆的动力学参数 p560.dyn(2);%查看puma560机械臂第二连杆的动力学参数 p560.links(2)…

mac命令行下计算文件SHA-256散列值

源起 从国内的第三方网站下载了Android sutiod的zip包下载地址&#xff0c;为了安全起见还是得跟Android官网上的对应的zip包的SHA值做下对比。以前是经常使用md5命令的&#xff0c;所以理论在命令行下应该是有对应的命令行工具可以计算SHA值的。后来搜索到可以用 shasum命令来…

Java中线程安全的集合类

在先前的文章中我们已经讲过了原子类(线程安全的基本类型&#xff0c;基于CAS实现)&#xff0c;详见常见锁策略&#xff0c;synchronized内部原理以及CAS-CSDN博客 &#xff0c;我们在来讲一下集合类&#xff0c;在原来的集合类&#xff0c;大多数是线程不安全的&#xff0c;虽…

深入了解 Android 中的 RelativeLayout 布局

RelativeLayout 是 Android 中常用的布局之一&#xff0c;它允许开发者基于子视图之间的相对位置来排列界面元素。在这篇博客中&#xff0c;我们将详细介绍 RelativeLayout 的各种属性&#xff0c;并提供代码示例和解释。 第一个示例 <RelativeLayoutandroid:layout_width…

【数据分享】2001~2023年中国区域MOD17A3HGF GPP数据

各位同学们好&#xff0c;今天和大伙儿分享的是2001~2023年中国区域MOD17A3HGF GPP数据。如果大家有下载处理数据等方面的问题&#xff0c;您可以私信或评论。 Running, S., M. Zhao. <i>MODIS/Terra Net Primary Production Gap-Filled Yearly L4 Global 500m SIN Grid…

标准库`random`函数大全:探索Python中的随机数生成【第107篇—`random`函数大全】

标准库random函数大全&#xff1a;探索Python中的随机数生成 随机数在计算机科学和数据科学领域中扮演着重要角色&#xff0c;Python的标准库中提供了random模块&#xff0c;用于生成各种随机数。本篇博客将深入探讨random模块的各种函数&#xff0c;以及它们的应用场景和代码…

Leetcoder Day35| 动态规划part02

62.不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff…

如何在2.2.1版Aduino IDE中开发ESP32

ESP32芯片集成了WIFI和蓝牙&#xff0c;而且关于生态也很不错&#xff0c;越来越多的学习者和开发者选择此类芯片&#xff0c;而不像用keil开发STM32或者51一样&#xff0c;ESP32虽然也有官方的ESP32-IDF开发软甲&#xff0c;但是经过我个人的实操体验&#xff0c;不适合小白或…

基于Springboot的人事管理系统 (有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的人事管理系统 &#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&am…

ctf_show笔记篇(web入门---文件包含)

目录 文件包含 78-79&#xff1a;最基础的文件包含&#xff0c;使用伪协议&#xff0c;大小写绕过或者通配符绕过&#xff0c;再或者使用其他方法 ​编辑80-81&#xff1a;可采用日志文件绕过或者大小写绕过&#xff08;81只能日志文件绕过&#xff09; ####80-86&#xff1…

linux系统---nginx(3)核心配置指令及调优

目录 Nginx 核心配置指令 一、Nginx配置文件详解 1、配置文件目录 2、配置文件结构 二、调优 1、在全局域进行的调优 1.1线程池指令 1.2 工作进程数指令 2.1 工作进程并非数指令 2.2 事件处理机制选择指令 2.3 互斥锁指令 3、在http指令域的调优 3.1 Nginx端口监听…

无所不谈,百无禁忌,Win11本地部署无内容审查中文大语言模型CausalLM-14B

无内容审查机制大模型整合包,基于CausalLM-14B量化 目前流行的开源大语言模型大抵都会有内容审查机制&#xff0c;这并非是新鲜事&#xff0c;因为之前chat-gpt就曾经被“玩”坏过&#xff0c;如果没有内容审查&#xff0c;恶意用户可能通过精心设计的输入&#xff08;prompt&a…

leetcode刷题记录:动态规划02,子序列问题

参考labuladong的算法小抄整理 link 子序列问题&#xff0c;用一维dp数组或二维dp数组来解决。 一维数组&#xff1a;最大子数组和&#xff0c;最长递增子序列。dp[i]的定义&#xff1a;在子数组 arr[0…i] 中&#xff0c;以 arr[i] 结尾的子序列的长度是 dp[i]。二维数组&…