基于Media+Unity的手部位姿三维位姿估计

在这里插入图片描述

使用mediapipe + Unity 手部位姿三维位姿估计

参考文章

基于Mediapipe的姿势识别并同步到Unity人体模型中

Mediapipe+Unity3d实现虚拟手_unity mediapipe-CSDN博客

需求

我的需求就是快速、准确的跟踪手部位姿并实现一个三维显示。

主要思路

搭建mdeiapipe系统,这个很简单,可以参考官方文档,配置好环境,下载一个相应的权重,然后就可以识别手部姿态了。

这里最好采用threading的方式来异步执行,因为我弄了一个小软件。

适配线程函数

处理数据的函数和可视化在同一个线程,

发送数据的线程是单独的线程

最终实现的结果是这样的

在这里插入图片描述

源码不太好贴

from queue import Queue

import mediapipe as mp
from matplotlib import pyplot as plt
from mediapipe.python.solutions.drawing_utils import draw_axis
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from mediapipe import solutions
from mediapipe.framework.formats import landmark_pb2
import numpy as np
import cv2
from mediapipe.tasks.python.vision import HandLandmarkerResult

from run.media_hand.med_hand3d_base import MedHandInferBase
from run.media_hand.view_3d import MyView3D
from run.rootnet.root_infer import HandRootNet


class MedHandInfer( MedHandInferBase):

    def __init__(self,_update_table_signal = None,_send_hd_client= None,_img_path = None,_vid_path = None) -> object:
        super().__init__()
        self.options = self.HandLandmarkerOptions(
            base_options=self.BaseOptions(model_asset_path=self.model_path),
            running_mode =self. VisionRunningMode.LIVE_STREAM,
            result_callback = self.print_result,
            num_hands=2)

        self.video_infer_flag = True

        self.root_infer = HandRootNet()
        if _update_table_signal != None:
            self.update_table_signal_h = _update_table_signal
        if _img_path != None:
            self.img_path = _img_path
        if _vid_path != None:
            self.vid_path = _vid_path
        if _send_hd_client != None:  # 在这里获取了client类的实例,因此可以用封装包的函数
            self.send_hd_client_infer = _send_hd_client

    def med_hand_infer_img(self):
        '''执行图片推断'''
        self.options = self.HandLandmarkerOptions(
            base_options=self.BaseOptions(model_asset_path=self.model_path),
            running_mode=self.VisionRunningMode.IMAGE,
            num_hands=2)
        mp_image = mp.Image.create_from_file(self.img_path)
        mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=mp_image)
        with self.HandLandmarker.create_from_options(self.options) as landmarker:
            hand_landmarker_result = landmarker.detect(mp_image)
            print(self.show_time() + "图片的识别结果为{0}".format(hand_landmarker_result))

    def med_hand_infer_video(self):
        '''执行视频推断'''
        self.options = self.HandLandmarkerOptions(
            base_options=self.BaseOptions(model_asset_path=self.model_path),
            running_mode=self.VisionRunningMode.VIDEO,
            num_hands=2)
        cap = cv2.VideoCapture(self.vid_path)
        self.video_infer_flag = True
        while cap.isOpened():
            if self.video_infer_flag:
                ret, frame = cap.read()
                mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame)
                with self.HandLandmarker.create_from_options(self.options) as landmarker:
                    hand_landmarker_result = landmarker.detect(mp_image)
                    print(self.show_time() + "视频帧的识别结果为{0}".format(hand_landmarker_result))
                if not ret:
                    break


    def med_hand_infer_web(self):
        print(self.show_time() + "开始执行media相机推理")
        cap = cv2.VideoCapture(0)
        print(self.show_time() + "开始读取视频帧")
        self.video_infer_flag = True
        while True:
            if not self.video_infer_flag:
                break

            ret,frame = cap.read()
            start_time = cv2.getTickCount()
            # 因为摄像头是镜像的,所以将摄像头水平翻转
            # 不是镜像的可以不翻转
            frame = cv2.flip(frame, 1)
            frame_new = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

            # 获取视频宽度高度
            width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            # 创建一个黑色图像
            black_image = np.zeros((height, width, 3), dtype=np.uint8)
            # 改变图像的着色模式,用于推理,但是不用于显示
            mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_new)
            with self.HandLandmarker.create_from_options(self.options) as landmarker:
                # The landmarker is initialized. Use it here.
                landmarker.detect_async(mp_image,1)
                # 为了绘图获取结果
                cur_res =  self.res_que.get()
                self.h_frame_num += 1

                lm_img  = self.draw_landmarks_on_image(frame, cur_res) # 在每一帧图片上绘图
                lm_img_axis = MyView3D.my_draw_axis(black_image) # 先绘制坐标轴
                lm_img_2 = self.draw_landmarks_on_image(lm_img_axis, cur_res) # 在每一帧图片上绘图
                # 合并图片
                concatenated_image = np.hstack((lm_img, lm_img_2))
                # 关节转换可以放到线程中处理
                con_res_hlm =  self.con_res_que.get()
                # 滤波在绘图线程上进行
                fil_res_hlm, scores = self.data_filter.get_med_fil_pose(con_res_hlm, cur_res.handedness)
                print(self.show_time() + "滤波之后的数据为{}".format(fil_res_hlm))

                ############ 获取手部姿态的xmin ymin xmax ymax 并转化为像素坐标 ###########
                np_hlm_rig = np.array(con_res_hlm[0:21])
                np_hlm_left = np.array(con_res_hlm[21:])
                x_min_r = np.min(np_hlm_rig,axis=0)[0]
                y_min_r = np.min(np_hlm_rig, axis=0)[1]
                x_max_r = np.max(np_hlm_rig, axis=0)[0]
                y_max_r = np.max(np_hlm_rig, axis=0)[1]
                x_min_l = np.min(np_hlm_left, axis=0)[0]
                y_min_l = np.min(np_hlm_left, axis=0)[1]
                x_max_l = np.max(np_hlm_left, axis=0)[0]
                y_max_l = np.max(np_hlm_left, axis=0)[1]
                # 转化为像素值
                x_min_r = int(x_min_r * width); x_max_r = int(x_max_r * width); y_min_r = int(y_min_r * height); y_max_r = int(y_max_r * height);
                x_min_l = int(x_min_l * width); x_max_l = int(x_max_l * width); y_min_l = int(y_min_l * height); y_max_l = int(y_max_l * height);

                # 绘制矩形
                rec_img = cv2.rectangle(frame,(x_min_r-40,y_min_r-40),(x_max_r+40,y_max_r + 40),(0,255,0),2)
                rec_img = cv2.rectangle(rec_img,(x_min_l-40,y_min_l-40),(x_max_l+40,y_max_l + 40),(0,255,0),2)
                # 生成 bbox
                bbox_rig = [x_min_r-40, y_min_r-40, x_max_r+40, y_max_r + 40]
                bbox_left = [x_min_l-40, y_min_l-40, x_max_l+40, y_max_l + 40]

                root_depth_list =  self.root_infer.get_hand_root_depth(frame,scores,[bbox_rig,bbox_left])
                print(self.show_time() + "手部root关节的绝对深度为{}mm".format(root_depth_list))



                ############ 获取世界坐标系的手部坐标 ###########
                scal_res_hlm = self.res_scal(fil_res_hlm, width, height)
                print(self.show_time() + "计算缩放之后的数据为{}".format(scal_res_hlm))

                ############ 向客户端发送数据 发送的是滤波之后的数据 没有发送加上手腕的 ###########
                self.fil_res_que.put(fil_res_hlm) # 准备发送数据
                self.update_table_signal_h.emit(fil_res_hlm) # 更新table

                ############ 绘制滤波后的图 ###########
                fil_img = self.draw_filt_landmarks_on_image(frame, fil_res_hlm)  # 使用处理后的数据绘图 做对比
                fil_img_2 = self.draw_filt_landmarks_on_image(lm_img_axis, fil_res_hlm)  # 在每一帧图片上绘图
                concatenated_image_2 = np.hstack((fil_img, fil_img_2))

            print(self.show_time() + "已经处理了{0}帧".format(self.h_frame_num))

            # 计算时间间隔并实时更新帧率
            end_time = cv2.getTickCount()
            total_time = (end_time - start_time) / cv2.getTickFrequency()
            fps =round(1 / total_time,2)
            fps_text = f"FPS: {round(fps, 2)}"
            cv2.putText(concatenated_image, fps_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
            cv2.imshow('MediaPipe Hands det', rec_img)
            cv2.imshow('MediaPipe Hands', concatenated_image)
            cv2.imshow('MediaPipe Hands Filter', concatenated_image_2)
            # self.draw_hand3d(fig,hand_mark_list_fil)

            if cv2.waitKey(1) & 0xFF == 27: # oh 天呐 我是沙口 原来摁一下 esc 就行
                print("停止手部姿态检测")
                break
        cap.release()
        cv2.destroyAllWindows()
        print("cv2资源已经释放")

    def send_data_packet_thm(self):
        '''线程函数 将数据封装成数据包'''
        print(self.show_time() + "进入send_data_packet_thm函数,开始发送数据!")
        while True:
            # 获取处理后的数据 这里是 double [] 类型的数据
            self.res_kps_handled = self.fil_res_que.get()
            # 将手部数据封装成字节数组 存在了 send_hd_client_infer.joint_data_packet
            self.send_hd_client_infer.create_send_packet(self.res_kps_handled) # 处理结果存在了 joint_data_packet
            self.send_hd_client_infer.send_packet(self.send_hd_client_infer.joint_data_packet) # 发送数据
#
    def draw_hand3d(self,fig,_hand_mark_list:list):
        '''废弃的 在子线程用matlip会崩溃'''
        x = []
        y = []
        z = []
        for item in _hand_mark_list:
            x.append(item[0])
            y.append(item[0])
            z.append(item[0])

        # 创建一个新的三维图形

        ax = fig.add_subplot(111, projection='3d')

        # 绘制三维散点图
        ax.scatter(x, y, z, c='b', marker='o')

        # 绘制连线 1 手腕到拇指
        for i in range(0,5): # 5个手指
            for j in range(4 * i, 4 * i + 3 ): # 循环三次即可
                if self.is_joint_exist(_hand_mark_list[j]):
                    ax.plot([x[j], x[j + 1]], [y[j], y[j + 1]], [z[j], z[j + 1]])
            for k in range(4 * i + 21, 4 * i + 24 ): # 左手
                if self.is_joint_exist(_hand_mark_list[k]):
                    ax.plot([x[k], x[k + 1]], [y[k], y[k + 1]], [z[k], z[k + 1]])

            if i == 4:
                ax.plot([x[3], x[20]], [y[2], y[20]], [z[2], z[20]])
                ax.plot([x[7], x[20]], [y[7], y[20]], [z[7], z[20]])
                ax.plot([x[11], x[20]], [y[11], y[20]], [z[11], z[20]])
                ax.plot([x[15], x[20]], [y[15], y[20]], [z[15], z[20]])
                ax.plot([x[19], x[20]], [y[19], y[20]], [z[19], z[20]])

                ax.plot([x[24], x[20]], [y[24], y[20]], [z[24], z[20]])
                ax.plot([x[28], x[20]], [y[28], y[20]], [z[28], z[20]])
                ax.plot([x[32], x[20]], [y[32], y[20]], [z[32], z[20]])
                ax.plot([x[36], x[20]], [y[36], y[20]], [z[36], z[20]])
                ax.plot([x[40], x[20]], [y[40], y[20]], [z[40], z[20]])

        # 设置图形属性
        ax.set_xlabel('X Label')
        ax.set_ylabel('Y Label')
        ax.set_zlabel('Z Label')

        # 显示图形
        plt.show()
    def is_joint_exist(self,_point:list):
        to = sum(_point)
        return False if to < 0.001 else True

# med = MedHandInfer()
# med.med_hand_infer_web()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】IO 流

文章目录 &#x1f449;C 语言的输入与输出&#x1f448;&#x1f449;流是什么&#x1f448;&#x1f449;C IO 流&#x1f448;C 标准 IO 流C 和 C 语言的输入格式问题C 的多次输入内置类型和自定义类型的转换日期的多次输入C 文件 IO 流文本文件和二进制文件的读写 &#x1…

成功案例分享 — 芯科科技助力涂鸦智能打造Matter over Thread模块,简化Matter设备开发

芯科科技&#xff08;Silicon Labs&#xff09;的愿景之一是让开发者每天都能够更轻松地开发无线物联网&#xff08;IoT&#xff09;。特别是在拥有相同愿景的合作伙伴的帮助下&#xff0c;我们每天都在取得进步。但是要想弥合知识水平和物联网开发之间的差距仍会面临一定的挑战…

MySQL主从部署(保姆版)

一、mysql 同步复制有关概述 一般数据库都是读取压力大于写数据压力&#xff0c;主从复制即为了实现数据库的负载均衡和读写分离。通过将Mysql的某一台主机的数据复制到其它主机&#xff08;slaves&#xff09;上&#xff0c;主服务器只负责写&#xff0c;而从服务器只负责读。…

浅谈计算机网络02 | SDN控制平面

计算机网络控制平面 一、现代计算机网络控制平面概述1.1 与数据平面、管理平面的关系1.2 控制平面的发展历程 二、控制平面的关键技术剖析2.1 网络层协议2.1.1 OSPF协议2.1.2 BGP协议 2.2 SDN控制平面技术2.2.1 SDN架构与原理2.2.2 OpenFlow协议2.2.3 SDN控制器 一、现代计算机…

基于网络爬虫技术的网络新闻分析【源码+文档+部署讲解】

目 录 1 绪论 1.1 论文研究背景与意义 1.2 论文研究内容 2 系统需求分析 2.1 系统需求概述 2.2 系统需求分析 2.2.1 系统功能要求 2.2.2 系统IPO图 2.2 系统非功能性需求分析 3系统概要设计 3.1 设计约束 3.1.1需求约束 3.1.2设计策略 3.1.3 技术实现 3.3 模块…

WeakAuras NES Script(lua)

WeakAuras NES Script 修星脚本字符串 脚本1&#xff1a;NES !WA:2!TMZFWXX1zDxVAs4siiRKiBN4eV(sTRKZ5Z6opYbhQQSoPtsxr(K8ENSJtS50(J3D7wV3UBF7E6hgmKOXdjKsgAvZFaPTtte0mD60XdCmmecDMKruyykDcplAZiGPfWtSsag6myGuOuq89EVDV9wPvKeGBM7U99EFVVVV33VFFB8Z2TJ8azYMlZj7Ur3QDR(…

网络安全学习81天(记录)

前言&#xff1a; 小迪安全&#xff0c;81天&#xff0c;开始了php代码审计 思路&#xff1a; 内容&#xff1a; #知识点&#xff1a; 1、审计漏洞-SQL 数据库注入挖掘 1、审计思路-正则搜索&功能追踪&辅助工具 3、审计类型-常规架构&MVC 架构&三方框架 #章…

1.14 互斥与同步

1.思维导图 2.有一个隧道&#xff0c;长1000m&#xff0c;有一辆高铁&#xff0c;每秒100米&#xff1b;有一辆快车&#xff0c;每秒50米&#xff1b;要求模拟这两列火车通过隧道的场景。 1>程序代码&#xff1a; #include <stdio.h> #include <string.h> #i…

研华 PCI-1751 驱动更新导LabVIEW致程序异常

问题描述&#xff1a; 某 LabVIEW 程序长期运行正常&#xff0c;但在使用研华 PCI-1751 数据采集卡运行一段时间后&#xff0c;程序开始出现不正常的行为。具体过程如下&#xff1a; 初始问题&#xff1a; 更换新的 PCI-1751 板卡后&#xff0c;驱动程序被更新&#xff0c;但程…

基于微信小程序的游泳馆管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

LabVIEW智能水肥一体灌溉控制系统

本文详细介绍了一种基于LabVIEW的智能水肥一体灌溉控制系统的设计与实现。该系统采用模糊控制策略&#xff0c;能够自动调节土壤湿度和肥液浓度&#xff0c;满足不同作物在不同生长阶段的需求&#xff0c;有效提高水肥利用效率&#xff0c;对现代精准农业具有重要的实践和推广价…

ChordCraft荣获重要认可:推动全球音乐教育的数字化革新

2024年12月21日,洛杉矶尔湾市——在今年的文化艺术交流会上,聚集了来自各地的艺术家及社区领袖,共同探讨艺术如何促进全球文化的多样性和教育的普及。 张庭玮(Ting-Wei Chang)女士,University of North Texas音乐艺术博士,是一位享誉国际的单簧管演奏家和音乐教育者。她不仅在…

Public Key Retrieval is not allowed 解决方法

如图&#xff1a;我的报错是Public Key Retrieval is not allowed&#xff0c;我的前后端都能正常加载&#xff0c;但是在请求数据库时就会报错如下&#xff1a; 解决办法&#xff1a; 在 application.yaml 中的数据库设置地方加上allowPublicKeyRetrievaltrue&#xff0c;然后…

unity学习17:unity里的旋转学习,欧拉角,四元数等

目录 1 三维空间里的旋转与欧拉角&#xff0c;四元数 1.1 欧拉角比较符合直观 1.2 四元数 1.3 下面是欧拉角和四元数的一些参考文章 2 关于旋转的这些知识点 2.1 使用euler欧拉角旋转 2.2 使用quaternion四元数,w,x,y,z 2.3 使用quaternion四元数,类 Vector3.zero 这种…

从 Conda 到 Pip-tools:Python 依赖管理全景探索20250113

从 Conda 到 Pip-tools&#xff1a;Python 依赖管理全景探索 引言 在 Python 开发中&#xff0c;依赖管理是一个"常见但复杂"的问题&#xff1a;一次简单的版本冲突可能让团队调试数小时&#xff1b;一次不受控的依赖升级可能让生产环境瘫痪。随着项目规模的增加和…

【容器逃逸实践】挂载/dev方法

0、前置知识 怎么在容器里面执行命令&#xff0c; 有几种方法 # 不进入容器&#xff0c;创建并启动一个新的容器 $ docker run -itd --name ubuntu-test ubuntu /bin/bash # 进入容器&#xff0c;创建并启动一个新的容器 $ docker run -itd --name ubuntu-test ubuntu /bin…

【Linux】进程结束和进程等待

进程的结束 退出码的认识 在我们学习C/C的时候我们通常在进行写main函数时&#xff0c;main函数主体写完后通常会进行写一条语句 " return 0 " &#xff0c;这里的这条语句到底是什么意思呢&#xff1f;&#xff1f; 我们知道当在主函数中调用其他函数或者在其他函…

Apache Hop从入门到精通 第二课 Apache Hop 核心概念/术语

1、apache hop核心概念思维导图 虽然apache hop是kettle的一个分支&#xff0c;但是它的概念和kettle还是有一些区别的&#xff0c;下图是我根据官方文档梳理的appache hop的核心概念思维导图。 2、Tools&#xff08;工具&#xff09; 1&#xff09;Hop Conf Hop Conf 是一个…

28.找出字符串中第一个匹配项的下标【力扣】KMP前缀表 ≈ find() 函数、暴力解法

class Solution { public: //得到前缀表void getNext(int *next,string needle){int j0;for(int i1;i<needle.size();i){while(j>0 && needle[j]!needle[i]) jnext[j-1];//**j>0**>j0是出口if(needle[i]needle[j]) j;next[i]j;//若写入if中&#xff0c;则该…

uniapp 小程序 textarea 层级穿透,聚焦光标位置错误怎么办?

前言 在开发微信小程序时&#xff0c;使用 textarea 组件可能会遇到一些棘手的问题。最近我在使用 uniapp 开发微信小程序时&#xff0c;就遇到了两个非常令人头疼的问题&#xff1a; 层级穿透&#xff1a;由于 textarea 是原生组件&#xff0c;任何元素都无法遮盖住它。当其…