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

news2025/1/15 21:08:52

在这里插入图片描述

使用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()

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

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

相关文章

构建高性能网络服务:从 Socket 原理到 Netty 应用实践

1. 引言 在 Java 网络编程中&#xff0c;Socket 是实现网络通信的基础&#xff08;可以查看我的上一篇博客&#xff09;。它封装了 TCP/IP 协议栈&#xff0c;提供了底层通信的核心能力。而 Netty 是在 Socket 和 NIO 的基础上&#xff0c;进一步封装的高性能、异步事件驱动的…

Python入门10:高阶函数

一、什么是高阶函数 1.1、高阶函数的概念和作用&#xff1a; 高阶函数是指 接受函数作为参数 或者 返回函数 作为结果的函数。它在函数式编程中是一个重要概念&#xff08;函数式编程&#xff08;Functional Programming &#xff0c; FP &#xff09;是一 种编程范式&#xf…

python-leetcode-矩阵置零

73. 矩阵置零 - 力扣&#xff08;LeetCode&#xff09; class Solution:def setZeroes(self, matrix: List[List[int]]) -> None:"""Do not return anything, modify matrix in-place instead."""m, n len(matrix), len(matrix[0])row_zero …

MySQL数据库(SQL分类)

SQL分类 分类全称解释DDLData Definition Language数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;DMLData Manipulation Language数据操作语言&#xff0c;用来对数据库表中的数据进行增删改DQLData Query Languag…

计算机网络 笔记 网络层1

网络层功能概述 主要的任务是把分组从源端传输到目的端&#xff0c;为分组交换网上的不同主句提供通信服务&#xff0c;网络层的传输单位是数据报。 主要的功能&#xff1b; 1&#xff0c;路由选择&#xff1a;路由选择指网络层根据特定算法&#xff0c;为数据包从源节点到目…

MyBatis-什么是MyBatis?以及MyBatis的快速入门。

简介 什么是 MyBatis&#xff1f; 什么是MyBatis? MyBatis是一款优秀的 持久层 框架&#xff0c;用于简化JDBC的开发。&#xff08;框架&#xff1a;是一个半成品软件&#xff0c;是一套可重用的、通用的、软件基础代码模型。在框架的基础上进行软件开发更加高效、规范、通用、…

Linux Kernel 之十 详解 PREEMPT_RT、Xenomai 的架构、源码、构建及使用

概述 现在的 RTOS 基本可以分为 Linux 阵营和非 Linux 阵营这两大阵营。非 Linux 阵营的各大 RTOS 都是独立发展,使用上也相对独立;而 Linux 阵营则有多种不同的实现方法来改造 Linux 以实现实时性要求。本文我们重点关注 Linux 阵营的实时内核实现方法! 本文我们重点关注 …

Swift 趣味开发:查找拼音首字母全部相同的 4 字成语(上)

概述 Swift 语言是一门现代化、安全、强大且还算性感的语言。在去年 WWDC 24 中苹果正式推出了秃头码农们期待许久的 Swift 6.0&#xff0c;它进一步完善了 Swift 语言的语法和语义&#xff0c;并再接再厉——强化了现代化并发模型的安全性和灵活性。 这里我们不妨用 Swift 来…

docker一张图理解

1、push 将本地的镜像上传到镜像仓库,要先登陆到镜像仓库。参数说明&#xff1a; –disable-content-trust : 忽略镜像的校验,默认开启 # 上传本地镜像myapache:v1到镜像仓库中。 docker push myapache:v1 1.2、search 从Docker Hub查找镜像。参数说明&#xff1a; –…

IoTDB 常见问题 QA 第三期

关于 IoTDB 的 Q & A IoTDB Q&A 第三期持续更新&#xff01;我们将定期汇总我们将定期汇总社区讨论频繁的问题&#xff0c;并展开进行详细回答&#xff0c;通过积累常见问题“小百科”&#xff0c;方便大家使用 IoTDB。 Q1&#xff1a;查询最新值 & null 数据相加方…

MyBatis实现数据库的CRUD

本文主要讲解使用MyBatis框架快速实现数据库中最常用的操作——CRUD。本文讲解的SQL语句都是MyBatis基于注解的方式定义的&#xff0c;相对简单。 Mybatis中#占位符和$拼接符的区别 “#”占位符 在使用MyBatis操作数据库的时候&#xff0c;可以直接使用如下SQL语句删除一条数…

Spring Boot 下的Swagger 3.0 与 Swagger 2.0 的详细对比

先说结论&#xff1a; Swgger 3.0 与Swagger 2.0 区别很大&#xff0c;Swagger3.0用了最新的注释实现更强大的功能&#xff0c;同时使得代码更优雅。 就个人而言&#xff0c;如果新项目推荐使用Swgger 3.0&#xff0c;对于工具而言新的一定比旧的好&#xff1b;对接于旧项目原…

关于2025年智能化招聘管理系统平台发展趋势

2025年&#xff0c;招聘管理领域正站在变革的十字路口&#xff0c;全新的技术浪潮与不断变化的职场生态相互碰撞&#xff0c;促使招聘管理系统成为重塑企业人才战略的关键力量。智能化招聘管理系统平台在这一背景下迅速崛起&#xff0c;其发展趋势不仅影响企业的招聘效率与质量…

go语言的sdk 适合用go原生还是gozero框架开发的判断与总结

在决定是否使用 Go 原生&#xff08;纯 Go&#xff09;开发&#xff0c;还是使用 GoZero 框架开发时&#xff0c;主要取决于项目的需求、规模和开发的复杂性。GoZero 框架提供了一些额外的功能&#xff0c;如微服务架构、RPC 支持、API 网关、任务调度等&#xff0c;这些是基于…

Elasticsearch 批量导入数据(_bluk方法)

官方API&#xff1a;https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html 建议先看API POST /<索引名>/_bulk 格式要求&#xff1a; POST _bulk { "index" : { "_index" : "test", "_id" : &q…

Mysql--重点篇--索引(索引分类,Hash和B-tree索引,聚簇和非聚簇索引,回表查询,覆盖索引,索引工作原理,索引失效,索引创建原则等)

索引是数据库中用于加速查询操作的重要机制。通过索引&#xff0c;MySQL可以快速定位到满足查询条件的数据行&#xff0c;而不需要扫描整个表。合理的索引设计可以显著提高查询性能&#xff0c;但不合理的索引可能会导致性能下降和磁盘空间浪费。因此&#xff0c;理解索引的工作…

mybatis-spring @MapperScan走读分析

接上一篇文章&#xff1a;https://blog.csdn.net/qq_26437925/article/details/145100531&#xff0c; 本文注解分析mybatis-spring中的MapperScan注解&#xff0c;则将容易许多。 目录 MapperScan注解定义ConfigurationClassPostProcessor扫描注册beanDefinitionorg.mybatis.s…

【STM32】HAL库USB实现软件升级DFU的功能操作及配置

【STM32】HAL库USB实现软件升级DFU的功能操作及配置 文章目录 DFUHAL库的DFU配置修改代码添加条件判断和跳转代码段DFU烧录附录&#xff1a;Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作SysTick系统定时器精准延时延时函数阻塞延时非阻塞延时 位带操作位带代码位带宏…

计算机视觉算法实战——实时车辆检测和分类(主页有相关源码)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​ ​​​​​​​​​​​​​​​​​​ 1. 领域介绍✨✨ 实时车辆检测和分类是计算机视觉中的一个重要应用领域&#xff0c;旨在从视频流或…

机器学习(1):线性回归概念

1 线性回归基础 1.1 什么是线性 例如&#xff1a;汽车每小时60KM&#xff0c;3小时可以行使多长距离&#xff1f;已知汽车的速度&#xff0c;则汽车的行使距离只与时间唯一相关。在二元的直角坐标系中&#xff0c;描出这一关系的图是一条直线&#xff0c;所以称为线性关系。 线…