14、嵌入式系统设计与机器人技术中的Unity应用指南

嵌入式系统设计与机器人技术中的Unity应用指南

1. 使用Kinect记录和显示关节坐标

1.1 初始设置

首先,你需要完成一些初始设置。如果你还没有安装Unity,可以从官网下载安装;若已安装则可忽略此步骤。接着创建一个新的项目。之后,为Kinect接口安装三款软件,并为项目导入一个资源,具体信息如下表所示:
| 软件/资源 | 下载链接 |
| — | — |
| Kinect SDK 1.8 | https://www.microsoft.com/en-us/download/details.aspx?id=40278 |
| Kinect Developer Toolkit 1.8 | https://www.microsoft.com/en-us/download/details.aspx?id=40276 |
| Kinect Runtime for Windows 1.8 | https://www.microsoft.com/en-us/download/details.aspx?id=40277 |
| 导入资源 | https://assetstore.unity.com/packages/tools/kinect-with-ms-sdk-7747 |

导入资源后,别忘了移除多余的游戏对象,即在层次结构窗口中右键点击并删除U_CharacaterBack和U_CharacterFront这两个游戏对象。最后,连接Xbox 360的Kinect设备,并运行/测试项目,以确保Kinect和PC的软硬件状态正常。

1.2 Unity初始化

从层次结构面板中创建新的游戏对象,右键点击并创建一个空的游戏对象,将其命名为Canvas Group。然后通过右键点击 → UI → Text 或 Component → UI → Text 的路径创建一个新的游戏对象。总共创建四个文本组件,分别命名为RH_Coordinate、RH_X-Axis、RH_Y-Axis和RH_Z-Axis。接着将Canvas命名为RH_Canvas,点击RH_Canvas,将Canvas Scaler的设置调整为按屏幕大小缩放,并将参考分辨率设置为宽度:1920,高度:1080。

1.3 对象设置

根据以下表格和相关说明设置每个对象(RH_Coordinate、RH_X-Axis、RH_Y-Axis和RH_Z-Axis)的参数:
| 对象 | POS X | POS Y | POS Z | Width | Height |
| — | — | — | — | — | — |
| RH_Coordinate | -469.999 | 346 | 0 | 960 | 360 |
| RH_X-Axis | -643 | 455 | 0 | 360 | 50 |
| RH_Y-Axis | -426 | 455 | 0 | 360 | 50 |
| RH_Z-Axis | -210 | 458 | 0 | 360 | 50 |

1.4 对象重命名

将RH_Canvas复制三次,用于另外三个关节。右键点击并在同一位置复制粘贴。完成后,按照以下规则重命名:
| 名称 | 缩写 | 索引 |
| — | — | — |
| 右手 | RH | 1 |
| 右手腕 | RW | 2 |
| 右肘 | RE | 3 |
| 右肩 | RS | 4 |

1.5 创建开始/停止记录按钮

要创建开始/停止记录按钮,从组件菜单中创建游戏对象按钮。右键点击RH_Canvas → UI → Button。设置按钮的属性,将其名称改为startStopLogging Button。展开按钮并点击文本组件,设置文本属性。接着,在项目面板中右键点击 → 创建 → 文件夹,命名为Custom Scripts,进入该文件夹并创建两个新脚本,分别命名为Right-Hand Joint和LabelFollow。

1.6 代码部分

右手关节脚本代码
using System;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class RightHandJoint : MonoBehaviour
{
    // the joint we want to track
    public KinectWrapper.NuiSkeletonPositionIndex RHjoint = 
KinectWrapper.NuiSkeletonPositionIndex.HandRight;
    public KinectWrapper.NuiSkeletonPositionIndex RWjoint = 
KinectWrapper.NuiSkeletonPositionIndex.WristRight;
    public KinectWrapper.NuiSkeletonPositionIndex REjoint = 
KinectWrapper.NuiSkeletonPositionIndex.ElbowRight;
    public KinectWrapper.NuiSkeletonPositionIndex RSjoint = 
KinectWrapper.NuiSkeletonPositionIndex.ShoulderRight;
    public Text RH_X;
    public Text RH_Y;
    public Text RH_Z;
    public Text RW_X;
    public Text RW_Y;
    public Text RW_Z;
    public Text RE_X;
    public Text RE_Y;
    public Text RE_Z;
    public Text RS_X;
    public Text RS_Y;
    public Text RS_Z;
    Button loggingStartStop;
    Text loggingStartStopText;
    // joint position at the moment, in Kinect coordinates
    public Vector3 outputPosition;
    // if it is saving data to a csv file or not
    public bool isSaving = false;
    // how many seconds to save data into the csv file, or 0 to save non-stop
    public float secondsToSave = 0f;
    // path to the csv file (;-limited)
    public string RHsaveFilePath = “RH_joint_pos.csv”;
    public string RWsaveFilePath = “RW_joint_pos.csv”;
    public string REsaveFilePath = “RE_joint_pos.csv”;
    public string RSsaveFilePath = “RS_joint_pos.csv”;

    // start time of data saving to csv file
    private float saveStartTime = -1f;
    private void Start()
    {
        RH_X = GameObject.Find(“RH_X-Axis”).GetComponent<Text>();
        RH_Y = GameObject.Find(“RH_Y-Axis”).GetComponent<Text>();
        RH_Z = GameObject.Find(“RH_Z-Axis”).GetComponent<Text>();
        RW_X = GameObject.Find(“RW_X-Axis”).GetComponent<Text>();
        RW_Y = GameObject.Find(“RW_Y-Axis”).GetComponent<Text>();
        RW_Z = GameObject.Find(“RW_Z-Axis”).GetComponent<Text>();
        RE_X = GameObject.Find(“RE_X-Axis”).GetComponent<Text>();
        RE_Y = GameObject.Find(“RE_Y-Axis”).GetComponent<Text>();
        RE_Z = GameObject.Find(“RE_Z-Axis”).GetComponent<Text>();
        RS_X = GameObject.Find(“RS_X-Axis”).GetComponent<Text>();
        RS_Y = GameObject.Find(“RS_Y-Axis”).GetComponent<Text>();
        RS_Z = GameObject.Find(“RS_Z-Axis”).GetComponent<Text>();
      loggingStartStop = GameObject.Find(“startStopLoggingButton”).GetComponent<Button>();
        loggingStartStopText = 
GameObject.Find(“startStopLoggingButton”).GetComponentInChildren<Text>();
        if (loggingStartStop != null)
            loggingStartStop.onClick.AddListener(buttonTask);
    }
    private void buttonTask()
    {
        isSaving = !isSaving;
        if (isSaving)
        {
            loggingStartStopText.text = “Stop Logging”;
        }
        if (!isSaving)
        {
            loggingStartStopText.text = “Start Logging”;
        }
        throw new NotImplementedException();
    }

    void Update()
    {
        if (isSaving)
        {
            // create the file, if needed
            if (!File.Exists(RHsaveFilePath))
            {
                using (StreamWriter writer = File.CreateText(RHsaveFilePath))
                {
                    // csv file header
                    string sLine = “time;joint;pos_x;pos_y;poz_z”;
                    writer.WriteLine(sLine);
                }
            }
            if (!File.Exists(RWsaveFilePath))
            {
                using (StreamWriter writer = File.CreateText(RWsaveFilePath))
                {
                    // csv file header
                   string sLine = “time;joint;pos_x;pos_y;poz_z”;
                   writer.WriteLine(sLine);
                }
            }
            if (!File.Exists(REsaveFilePath))
            {
                using (StreamWriter writer = File.CreateText(REsaveFilePath))
                {
                    // csv file header
                    string sLine = “time;joint;pos_x;pos_y;poz_z”;
                   writer.WriteLine(sLine);
                }
            }
            if (!File.Exists(RSsaveFilePath))
            {
                using (StreamWriter writer = File.CreateText(RSsaveFilePath))
                {
                    // csv file header
                    string sLine = “time;joint;pos_x;pos_y;poz_z”;
                    writer.WriteLine(sLine);
                }
            }

            // check the start time
            if (saveStartTime < 0f)
            {
                saveStartTime = Time.time;
            }
        }
        // get the joint position
        KinectManager manager = KinectManager.Instance;

        if (manager && manager.IsInitialized())
        {
            if (manager.IsUserDetected())
            {
                uint userId = manager.GetPlayer1ID(); 
                if (manager.IsJointTracked(userId, (int)RHjoint))
                {
                    // output the joint position for easy tracking
                     Vector3 RH_jointPos = manager.GetJointPosition(userId, (int)RHjoint);
                    outputPosition = RH_jointPos;
                    RH_X.text = string.Format(“{0:F3}”, RH_jointPos.x);
                    //Debug.Log(RH_jointPos.x);
                    RH_Y.text = string.Format(“{0:F3}”, RH_jointPos.y);
                    RH_Z.text = string.Format(“{0:F3}”, RH_jointPos.z);
                    if (isSaving)
                    {
                             if ((secondsToSave == 0f) || ((Time.time - saveStartTime) <= secondsToSave))
                       {
                            using (StreamWriter writer = File.AppendText(RHsaveFilePath))
                            {
                               string sLine = string.Format(“{0:F3};{1};{2:F3};{3:F3};{4:F3}”, 
Time.time, (int)RHjoint, RH_jointPos.x, RH_jointPos.y, RH_jointPos.z);
                               writer.WriteLine(sLine);
                            }
                        }
                    }
                }
                if (manager.IsJointTracked(userId, (int)RWjoint))
                {
                    // output the joint position for easy tracking
                    Vector3 RW_jointPos = manager.GetJointPosition(userId, (int)RWjoint);
                    outputPosition = RW_jointPos;
                    RW_X.text = string.Format(“{0:F3}”, RW_jointPos.x);
                    //Debug.Log(RH_jointPos.x);
                    RW_Y.text = string.Format(“{0:F3}”, RW_jointPos.y);
                    RW_Z.text = string.Format(“{0:F3}”, RW_jointPos.z);
                    if (isSaving)
                    {
                        if ((secondsToSave == 0f) || ((Time.time - saveStartTime) <= secondsToSave))
                       {
                            using (StreamWriter writer = File.AppendText(RWsaveFilePath))
                           {
                                string sLine = string.Format(“{0:F3};{1};{2:F3};{3:F3};{4:F3}”, 
Time.time, (int)RHjoint, RW_jointPos.x, RW_jointPos.y, RW_jointPos.z);
                                writer.WriteLine(sLine);
                            }
                        }
                    }
                }
                if (manager.IsJointTracked(userId, (int)REjoint))
                {
                    // output the joint position for easy tracking
                    Vector3 RE_jointPos = manager.GetJointPosition(userId, (int)REjoint);
                    outputPosition = RE_jointPos;
                    RE_X.text = string.Format(“{0:F3}”, RE_jointPos.x);
                    //Debug.Log(RH_jointPos.x);
                    RE_Y.text = string.Format(“{0:F3}”, RE_jointPos.y);
                    RE_Z.text = string.Format(“{0:F3}”, RE_jointPos.z);
                    if (isSaving)
                    {
                        if ((secondsToSave == 0f) || ((Time.time - saveStartTime) <= secondsToSave))
                       {
                            using (StreamWriter writer = File.AppendText(REsaveFilePath))
                            {
                               string sLine = string.Format(“{0:F3};{1};{2:F3};{3:F3};{4:F3}”, 
Time.time, (int)RHjoint, RE_jointPos.x, RE_jointPos.y, RE_jointPos.z);
                               writer.WriteLine(sLine);
                            }
                        }
                    }
                }
                if (manager.IsJointTracked(userId, (int)RSjoint))
                {
                    // output the joint position for easy tracking
                    Vector3 RS_jointPos = manager.GetJointPosition(userId, (int)RSjoint);
                    outputPosition = RS_jointPos;
                    RS_X.text = string.Format(“{0:F3}”, RS_jointPos.x);
                    //Debug.Log(RH_jointPos.x);
                    RS_Y.text = string.Format(“{0:F3}”, RS_jointPos.y);
                    RS_Z.text = string.Format(“{0:F3}”, RS_jointPos.z);
                    if (isSaving)
                    {
                        if ((secondsToSave == 0f) || ((Time.time - saveStartTime) <= secondsToSave))
                       {
                            using (StreamWriter writer = File.AppendText(RSsaveFilePath))
                            {
                               string sLine = string.Format(“{0:F3};{1};{2:F3};{3:F3};{4:F3}”, 
Time.time, (int)RHjoint, RS_jointPos.x, RS_jointPos.y, RS_jointPos.z);
                               writer.WriteLine(sLine);
                            }
                        }
                    }
                }
            }
        }
    }
}
标签跟随脚本代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class LabelFollow : MonoBehaviour
{
    public Text RH_Label;
    // Start is called before the first frame update
    void Start()
    {
    }
    // Update is called once per frame
    void Update()
    {
        Vector3 RH_LabelPos = Camera.main.WorldToScreenPoint(this.transform.position);
        RH_Label.transform.position = RH_LabelPos;
    }
} 

1.7 右手关节脚本操作

将Right-Hand Joint脚本拖放到CanvasGroup对象上。

1.8 文本标签设置

在CubeMan游戏对象下创建文本标签,使其能够跟随移动。分别在Shoulder_Left、Elbow_Left、Wrist_Left和Hand_Left下创建UI → Text,并按照相关说明设置每个文本的内容。

1.9 标签跟随设置

将LabelFollow脚本拖放到每个文本Canvas的父对象(Shoulder_Left、Elbow_Left、Wrist_Left和Hand_Left)上,然后将相应的文本标签拖放到标签部分(RH_Label)。

1.10 运行项目

运行项目后,你将看到与示例相同的环境。点击“Start Logging”即可开始将数据导出到CSV文件。

1.11 浏览项目文件夹

右键点击项目窗口,选择“Show in Explorer”,在资源管理器窗口中你将看到四个CSV文件。

1.12 打开CSV文件

要查看计算机驱动器中的数据,双击CSV文件,这些文件将在Microsoft Excel中打开,从而显示文件内容。

2. Unity与运动学

2.1 运动学基础

运动学是研究物体或物体系统运动的学科,不考虑物体的质量和作用在其上的力。机器人运动学则是对机器人操纵器运动的分析研究。在运动学中,我们涉及到逆运动学和正运动学两个概念。逆运动学问题是指根据给定物体元素(通常是末端执行器)的特定位置和/或方向,找出对应的关节值;而正运动学则是在已知关节角度和角速度的情况下,获取末端执行器的位置和速度。为机器人机构建立合适的运动学模型对于分析工业操纵器的行为至关重要,它本质上是从任务空间坐标到关节空间坐标的转换。在Unity中,你也可以为角色考虑逆运动学,因为动画需要处理关节角度的旋转。

2.2 逆运动学实现步骤

2.2.1 Unity安装与准备

如果你还没有安装Unity 3D,可以从unity.com下载安装;若已安装则可跳过此步骤。然后从Unity Hub创建一个新项目。

2.2.2 资源安装

访问Asset store,通过以下链接将资源添加到你的Unity账户:https://assetstore.unity.com/packages/tools/animation/inverse-kinematics-1829 。安装完成后,点击“Open in Unity”在Unity中打开。在Unity内,进入Window → Package Manager,从Packages下拉菜单中选择“My assets”,从包管理器中下载并导入Inverse Kinematics,通过下载选项获取IK部分。

2.2.3 打开默认场景

进入Assets → Inverse Kinematics → Scenes,双击Arm,即可看到默认场景。

2.3 快速逆运动学实现步骤

2.3.1 Unity安装

若未安装Unity 3D,可从Unity官网下载安装;若已安装则忽略此步骤。

2.3.2 资源安装

从Unity Asset Store通过以下链接下载资源:https://assetstore.unity.com/packages/tools/animation/fast-ik-139972 。

2.3.3 创建项目并导入资源

在Unity中创建一个新项目,下载并导入Unity资源到项目中。

2.3.4 导入对话框操作

在包管理器中刷新资产列表,点击fast IK并下载。下载完成后,点击Import,会弹出一个新的对话框,再次点击Import完成导入。

2.3.5 运行快速逆运动学场景

进入文件夹,按照Assets → FastIK → Scenes路径,双击FastIKSampleScene,你将看到逆运动学场景,运行该场景即可查看逆运动学对象的运行情况。

3. 在树莓派上运行Unity项目

3.1 树莓派运行Unity项目概述

树莓派(RPi)是一款体积小但性价比高的计算机,但它并非功能强大到能完美运行Unity项目。在树莓派上运行Unity项目的效果不如在笔记本电脑或台式机上,而且Unity官方并不支持这种运行方式,所以在操作过程中若出现问题,很难从官方获得支持。需要注意的是,Unity应用在树莓派上可以独立运行,但如果项目包含大量图形密集型资源,运行速度会极慢,因为树莓派的Broadcom GPU性能有限,可能无法渲染所有图形组件。不过,还是有几种方法可以尝试在树莓派上运行Unity项目,包括通过Android方法、Unity的原生WebGL导出以及使用模拟器。

3.2 Android方法

使用Android方法在树莓派上运行Unity项目,需要以下硬件和软件:
| 所需硬件 | 所需软件 |
| — | — |
| 树莓派3/4 | 适用于树莓派的Android可刷机ZIP文件(下载链接:https://konstakang.com/devices/rpi3/CM14.1 ) |
| 显示器 | 笔记本/台式机上的Unity 3D(下载链接:https://unity3d.com/get-unity/download ) |
| SD卡 | Balena Etcher(下载链接:https://www.balena.io/etcher/ ) |
| USB读卡器 | Android平台工具(ADB TOOLS) |
| 装有Windows/Linux/Mac的计算机 | |

具体操作步骤如下:
1. 从指定链接下载适用于树莓派的Android可刷机ZIP文件和Balena Etcher。
2. 使用Balena Etcher将Android可刷机ZIP文件写入SD卡。
3. 将SD卡插入树莓派,连接显示器、USB读卡器等设备。
4. 在笔记本/台式机上安装Unity 3D和Android平台工具。
5. 将树莓派通过USB连接到计算机,使用Android平台工具进行相关配置和调试。
6. 在Unity中进行Android平台的导出设置,将项目导出为.apk文件。
7. 将.apk文件传输到树莓派上并安装运行。

3.3 其他可能的方法

除了Android方法,还可以考虑以下两种方法:
- Unity的原生WebGL导出 :WebGL应用程序可以在浏览器中运行,因此在树莓派上运行相对容易。在Unity中进行WebGL导出设置,将项目导出为WebGL格式,然后在树莓派的浏览器中打开导出的文件即可。
- 使用模拟器 :可以使用名为Box86的软件,它可以在树莓派的ARM架构上模拟x86环境,从而运行Unity项目。不过这种方法可能会受到性能和兼容性的影响。

通过以上介绍,你可以了解如何使用Kinect记录和显示关节坐标、在Unity中实现逆运动学以及在树莓派上运行Unity项目的方法。希望这些内容对你在嵌入式系统设计和机器人技术领域的学习和实践有所帮助。

3.4 方法选择建议

在选择在树莓派上运行Unity项目的方法时,需要综合考虑项目的特点和自身的需求:
- Android方法 :如果项目对图形性能要求不是特别高,且希望利用树莓派的硬件资源进行一些简单的交互应用,Android方法是一个不错的选择。它相对较为稳定,并且可以利用Android平台丰富的应用生态。
- Unity的原生WebGL导出 :对于一些轻量级的、以展示为主的项目,WebGL导出是一个便捷的方式。它不需要额外的安装步骤,只需要在浏览器中打开即可,适合快速部署和分享。
- 使用模拟器 :如果项目对x86环境有特定的依赖,或者需要运行一些只能在x86架构上运行的插件,那么使用Box86模拟器可能是唯一的选择。但需要注意的是,这种方法可能会带来性能上的损失,需要根据实际情况进行测试和优化。

3.5 性能优化建议

无论是采用哪种方法在树莓派上运行Unity项目,都可以通过以下方式进行性能优化:
- 减少图形资源 :尽量避免使用高分辨率的纹理、复杂的光照效果和大量的粒子系统等图形密集型元素。可以通过降低纹理分辨率、简化模型等方式来减少图形资源的占用。
- 优化代码逻辑 :检查代码中是否存在不必要的循环、重复计算等问题,尽量减少CPU的负载。可以使用Unity的性能分析工具来找出代码中的瓶颈,并进行针对性的优化。
- 合理分配资源 :根据树莓派的硬件资源情况,合理分配内存、CPU和GPU的使用。例如,可以通过调整游戏的帧率、分辨率等参数来平衡性能和视觉效果。

4. 总结与展望

4.1 总结

本文详细介绍了在嵌入式系统设计和机器人技术中使用Unity的多个方面:
- 使用Kinect记录和显示关节坐标 :通过一系列的设置和代码编写,实现了利用Kinect设备记录人体关节坐标数据,并将其保存为CSV文件的功能。这对于研究人体运动、机器人交互等领域具有重要的意义。
- Unity与运动学 :介绍了运动学的基本概念,包括逆运动学和正运动学,并详细说明了在Unity中实现逆运动学的具体步骤。这为开发具有复杂运动控制的游戏或机器人应用提供了技术支持。
- 在树莓派上运行Unity项目 :探讨了在树莓派上运行Unity项目的多种方法,包括Android方法、WebGL导出和使用模拟器,并给出了相应的操作步骤和性能优化建议。这使得开发者可以将Unity项目部署到低成本、低功耗的树莓派设备上,拓宽了应用的场景。

4.2 展望

随着技术的不断发展,Unity在嵌入式系统设计和机器人技术领域的应用前景将更加广阔:
- 更强大的硬件支持 :未来的树莓派等嵌入式设备可能会拥有更强大的硬件性能,能够更好地支持Unity项目的运行。这将使得开发者可以开发出更加复杂、逼真的应用程序。
- 更多的传感器集成 :除了Kinect设备,未来可能会有更多的传感器与Unity进行集成,如激光雷达、摄像头等。这将为机器人的感知和交互提供更多的可能性。
- 跨平台开发的便利性 :Unity作为一个跨平台的游戏引擎,未来可能会进一步优化跨平台开发的流程,使得开发者可以更加轻松地将项目部署到不同的设备和平台上。

总之,Unity在嵌入式系统设计和机器人技术领域具有巨大的潜力,通过不断地学习和实践,开发者可以利用Unity开发出更加创新、实用的应用程序。

附录:操作流程图

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B(使用Kinect记录和显示关节坐标):::process
    B --> B1(初始设置):::process
    B --> B2(Unity初始化):::process
    B --> B3(对象设置):::process
    B --> B4(对象重命名):::process
    B --> B5(创建开始/停止记录按钮):::process
    B --> B6(代码部分):::process
    B --> B7(右手关节脚本操作):::process
    B --> B8(文本标签设置):::process
    B --> B9(标签跟随设置):::process
    B --> B10(运行项目):::process
    B --> B11(浏览项目文件夹):::process
    B --> B12(打开CSV文件):::process
    B --> C(Unity与运动学):::process
    C --> C1(运动学基础):::process
    C --> C2(逆运动学实现步骤):::process
    C --> C3(快速逆运动学实现步骤):::process
    C --> D(在树莓派上运行Unity项目):::process
    D --> D1(树莓派运行Unity项目概述):::process
    D --> D2(Android方法):::process
    D --> D3(其他可能的方法):::process
    D --> D4(方法选择建议):::process
    D --> D5(性能优化建议):::process
    D --> E([结束]):::startend

以上流程图展示了本文所介绍的主要操作流程,从使用Kinect记录关节坐标开始,到在Unity中实现运动学,最后到在树莓派上运行Unity项目,涵盖了整个嵌入式系统设计和机器人技术中使用Unity的主要环节。通过这个流程图,读者可以更加清晰地了解各个步骤之间的关系和顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值