【Augmented Reality】增强现实中的光学透射式头盔显示器的标定进阶

本文主要是介绍【Augmented Reality】增强现实中的光学透射式头盔显示器的标定进阶,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

      上次在“增强现实中的光学透射式头盔显示器的标定初步”一文中,我们讲到了基于视觉跟踪的光学透射式头盔显示系统的一般标定方法,即单点主动对齐算法(SPAAM),在分析完基本理论后,给出了编写软件的思路。但是对于大部分没有编程经验,或者编程基础比较弱的同学来说,还是会有一些困难。于是,本文将继续上篇博客的内容,介绍软件编写的详细流程。

      上一篇相关博客地址是:http://blog.csdn.net/zzlyw/article/details/53215105

--------------------------------------------------------------------------------------

1 环境配置

      软件需求:

      (1Windows 1064-bit

      (2MATLAB R2015b

      (3Unity 3D 5.4.1f1 64-bit

      (4Vuforia 6 SDK

      硬件需求:

      (1PC机一台

      (2)光学透射式头盔显示器一台

      (3)微软高清摄像头

2 硬件设备的制作

      首先,你需要有一个光学透射式头盔显示器,单目或者双目都可以。我用的是一个比较简陋的单目设备,分辨率是800*600。摄像头绑定到头盔显示器上。虽然我们叫头盔显示器,但是我用的其实只是一个镜片和一个微投影器件,然后用金属结构固定在了一起。但是原理都是相同的。如果已有一个现成的带有摄像头的商品级头盔显示器就更好了。



      请确保你的摄像头可以正常连接电脑捕获图像,同时你的头盔显示器可以连接电脑并正常显示。

 

3 建立一个Unity工程

      Unity3D是一个集成度很高的游戏开发引擎,但是它在科研中扮演的角色同样很重要。很多仿真都可以使用Unity来完成。

      打开unity,新建一个工程叫做“HMD_tutorials”,并建立一个叫做“AR”的scene。导入Vuforia SDK,并且把ARCameraImageTarget拖到场景中,设置自己的要跟踪的标志板图案。如果对于Vuforia使用方法不熟悉,具体的使用方法可以参考另一篇教程:

      地址是 http://blog.csdn.net/zzlyw/article/details/53215172

      我使用的标志图案是这幅图:


      你可以使用自己的图案,也可以直接把这幅图另存到你的本地计算机使用。建立好之后,在ImageTarget下设置一个子物体,这个子物体是一个名为CubeMarker的空物体,只需要有transform组件就够了。把它的位置拖动到和上述标志图案最中央那个十字叉重合,这样最中央的那个十字叉就可以作为我们标定时使用的marker了。

      生成一个叫做EyeCamera的摄像机,作为ARCamera的子物体,然后生成一个脚本“GetCalibrationData.cs”挂载到EyeCamera物体上。

      该脚本的代码如下:

using UnityEngine;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
public class GetCalibrationData : MonoBehaviour
{      public Camera arCamera; //跟踪摄像机public Camera renderCamera;//渲染摄像机,显示在眼前public Texture2D cursor;  //鼠标光标public GameObject calibMarker;//标志点public static bool trackState; //存储Vuforia跟踪状态bool calibrating; //标志着是否正处于标定任务中    float[,] calibCameraCoord; //标志点在跟踪摄像机下的三维坐标float[,] calibUV; //标志点在屏幕上的二维坐标int calibCount;//存储当前已经获得的标定点数const int CalibrationCount = 12; //总共需要的标定点数float[,] ProjectionMatrix; //标定出的投影矩阵//测试模块public GameObject[] markerPosition;Vector2[] screenPos;bool displayResult = false;void Start(){Cursor.visible = false;trackState = false;calibrating = false;calibCameraCoord = new float[CalibrationCount, 3];calibUV = new float[CalibrationCount, 2];calibCount = 0;ProjectionMatrix = new float[3, 4];readProjectionMatrix();    //读取投影矩阵screenPos = new Vector2[markerPosition.Length];}    void Update(){//进行test1的实验for (int i = 0; i < markerPosition.Length; i++){Vector3 p = arCamera.transform.InverseTransformPoint(markerPosition[i].transform.position);//转换成右手坐标p = new Vector3(p.x, p.y, p.z);float _u = ProjectionMatrix[0, 0] * p.x + ProjectionMatrix[0, 1] * p.y + ProjectionMatrix[0, 2] * p.z + ProjectionMatrix[0, 3];float _v = ProjectionMatrix[1, 0] * p.x + ProjectionMatrix[1, 1] * p.y + ProjectionMatrix[1, 2] * p.z + ProjectionMatrix[1, 3];float _w = ProjectionMatrix[2, 0] * p.x + ProjectionMatrix[2, 1] * p.y + ProjectionMatrix[2, 2] * p.z + ProjectionMatrix[2, 3];if (_w != 0){float u = _u / _w;float v = _v / _w;screenPos[i] = new Vector2(u, v); //直接得到目标点的屏幕坐标}}// 按下C,选择是进行标定还是显示if (Input.GetKeyDown(KeyCode.C)){displayResult = !displayResult;}// 按下F12键进行标定if (Input.GetKeyDown(KeyCode.F12)){calibrating = true;           calibCount = 0;Debug.Log("开始标定!");Debug.Log("#########请标定calibCount:" + calibCount);}if (calibrating == true){if (Input.GetMouseButtonDown(0) ){if (calibCount < CalibrationCount){//获取calibMarker在跟踪摄像机坐标系下的坐标Vector3 temp = arCamera.transform.InverseTransformPoint(calibMarker.transform.position); //将获取的三维坐标存储到数组中calibCameraCoord[calibCount, 0] = temp.x;calibCameraCoord[calibCount, 1] = temp.y;calibCameraCoord[calibCount, 2] = temp.z;//获取相应的二维图像点坐标calibUV[calibCount, 0] = Input.mousePosition.x;calibUV[calibCount, 1] = Input.mousePosition.y;calibCount++; //基数增加                   Debug.Log("#########请标定calibCount:" + calibCount);}else if (calibCount >= CalibrationCount){Debug.Log("标定完成!");calibrating = false; calibCount = 0;                                       //输出标定点CreateFile(Application.dataPath, "最新标定坐标.txt", "##########################");CreateFile(Application.dataPath, "最新标定坐标.txt",System.DateTime.Now.ToLocalTime().ToString() );CreateFile(Application.dataPath, "最新标定坐标.txt", "--------------------------");for (int i = 0; i < CalibrationCount; i++){CreateFile(Application.dataPath, "最新标定坐标.txt", calibCameraCoord[i, 0].ToString()+"  "+calibCameraCoord[i, 1].ToString()+"  "+calibCameraCoord[i, 2].ToString());}CreateFile(Application.dataPath, "最新标定坐标.txt", "--------------------------");for (int i = 0; i < CalibrationCount; i++){CreateFile(Application.dataPath, "最新标定坐标.txt", calibUV[i, 0].ToString() + "  " + calibUV[i, 1].ToString() );}}}}}void OnGUI(){if (displayResult){for (int i = 0; i < screenPos.Length; i++){Rect rect0 = new Rect(screenPos[i].x - 25, Screen.height - screenPos[i].y - 25, 50, 50);GUI.DrawTexture(rect0, cursor);}}else{//绘制鼠标位置的十字叉丝Vector3 msPos = Input.mousePosition;Rect _rect = new Rect(msPos.x - 25, Screen.height - msPos.y - 25, 50, 50);GUI.DrawTexture(_rect, cursor);//绘制当前跟踪状态if (trackState == false){GUI.color = Color.red;Rect rect0 = new Rect(300, 10, 300, 20);GUI.Label(rect0, "trackState:" + trackState);}else{GUI.color = Color.green;Rect rect0 = new Rect(300, 10, 300, 20);GUI.Label(rect0, "trackState:" + trackState);}//显示标定状态if (calibrating == true){GUI.color = Color.red;Rect rect0 = new Rect(50, 10, 300, 20);GUI.Label(rect0, "In calibration...");Rect rect1 = new Rect(50, 30, 300, 40);GUI.Label(rect1, "Please select calibCount:" + calibCount);}else{GUI.color = Color.red;Rect rect0 = new Rect(50, 10, 300, 40);GUI.Label(rect0, "Not in Calibration!");}        }  }//读取txtvoid readProjectionMatrix(){FileStream fs = new FileStream("ProjectionMatrix.txt", FileMode.Open);StreamReader sr = new StreamReader(fs, System.Text.UnicodeEncoding.Default);string str;for (int i = 0; i < 3; i++){for (int j = 0; j < 4; j++){str = sr.ReadLine();if (str != null){float result;result = float.Parse(str);ProjectionMatrix[i, j] = (result);}}}fs.Close();sr.Close();}void CreateFile(string path, string name, string info){StreamWriter sw;FileInfo t = new FileInfo(path + "//" + name);if (!t.Exists){sw = t.CreateText();}else{sw = t.AppendText();}sw.WriteLine(info);sw.Close();sw.Dispose();}
}

      为了能够实时监测跟踪状态,我们需要做下面的操作:

      在工程中,找到并打开DefaultTrackableEventHandler.cs脚本,将“GetCalibrationData.trackState= true;”加入到OnTrackingFound()函数中,将“GetCalibrationData.trackState = false;”加入到OnTrackingLost()函数中。这样,trackState变量就可以监测系统跟踪状态了。 

      在ImageTarget下建立四个空物体,分别将它们的位置拖动到标志图案的某个十字处,以备测试使用。将场景中的物体与GetCalibrationData.cs脚本的相应变量绑定,如下图:

      其中脚本中的TrackingCamera变量绑定的是场景中ARCamera下的Camera物体,RenderCamera变量绑定的是场景中ARCamera下的EyeCamera物体,Cursor绑定的是一张十字叉丝的PNG图像(该图像需要自己制作),CalibMarker绑定场景中的ImageTarget下的CubeMarker物体。MarkerPosition4个元素,对应ImageTarget下的其他四个物体。对于EyeCamera物体,还有一些东西需要设置。点击EyeCamera,在其Inspector面板中会有Camera组件如下图。将ClearFlags设置为“SolidColor”,Background设置为纯黑色。Depth值应超过场景中所有其他摄像机。

      在运行程序前,请转到工程的根目录,创建一个叫做“ProjectionMatrix.txt”的文件,里面随意写上12行数字,比如12个零。这是因为程序会从这个地方读取投影矩阵,没有该文件会出问题。然后点击unity正上方的小三角按钮,在Game窗口中会显示为纯黑色背景,以及一些提示信息。

      将Game窗口拖到已经连接在电脑上的头盔显示器界面上全屏显示,效果如下:

      按F12,开始标定。移动标志板,使其在空间中离散地进行位置变化,鼠标每次点击到最中央的那个十字叉上,尽量使得屏幕上的点位置也要分散开。大概收集12组对应点,程序会自动将收集到的点信息写到Assets目录下一个叫做“最新标定坐标.txt”的文件中。

      得到的内容格式大概是这样的:

4 使用MATLAB计算标定数据

      新建一个MATLAB脚本“SVD.m”,将下面的代码拷贝进去。

clc
clear
format long;
A = load('world.txt');
F = load('screen.txt');
[m1 n1] = size(A);
B=[0 0 0 0 ];
F=[F(:,1:2) ones(m1,1)]';
F=[F(1:2,:)]';
A = [A ones(m1,1)];
[m2 n2] = size(F);
if m1~=m2disp('xyz and uv are not the same in number of rows.');return;
end
C=[ A(1,:) B -F(1,1)*A(1,:) B A(1,:) -F(1,2)*A(1,:)];
for i= 2:m1temp=[A(i,:) B -F(i,1)*A(i,:) B A(i,:) -F(i,2)*A(i,:) ];C=[C;temp];
end
[U, S, V]=svd(C);
target= V(:,12)


      程序中调用了world.txtscreen.txt,所以我们还需要在MATLAB当前目录先生成两个TXT文件。将“最新标定坐标.txt”中的三列的数据拷贝到world.txt中,将两列的数据拷贝到screen.txt中。然后运行SVD.m,可以在输出窗口中看到target变量的值为12个浮点数。

      然后将target的值拷贝到unity工程根目录下的ProjectionMatrix.txt中,然后再次运行unity程序。此时,程序已经可以正常将标定结果显示了,你需要按C键将程序切换为显示标定结果模式。把刚才使用的标定板放在你的眼前,相应的四个测试用的CubeMarker1~4)所在的位置会绘制蓝色十字。

      好了,但目前头盔已经标定完成并且成功地显示了标定结果。

---------------------------------------------------------------------------------------------------------

小结

      标定只是第一步,后面可以做的东西可以多到令人发指的地步。只要有创意,一切皆有可能。这个标定是光学透射式增强现实所特有的,对于视频透射式增强现实设备,不需要这些步骤,但是视频透射式设备永远不能带给最贴近真实环境的AR体验,因为它提供的全部都是视频流。而光学透射式增强现实可以使用户看到真实的环境,而非拍摄后再呈现出来的视频影像。后续的系列文章可能也会涉及到视频透射式增强现实,希望可以和大家分享。




这篇关于【Augmented Reality】增强现实中的光学透射式头盔显示器的标定进阶的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/318514

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

[MySQL表的增删改查-进阶]

🌈个人主页:努力学编程’ ⛅个人推荐: c语言从初阶到进阶 JavaEE详解 数据结构 ⚡学好数据结构,刷题刻不容缓:点击一起刷题 🌙心灵鸡汤:总有人要赢,为什么不能是我呢 💻💻💻数据库约束 🔭🔭🔭约束类型 not null: 指示某列不能存储 NULL 值unique: 保证某列的每行必须有唯一的值default: 规定没有给列赋值时的默认值.primary key:

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

从0到1,AI我来了- (7)AI应用-ComfyUI-II(进阶)

上篇comfyUI 入门 ,了解了TA是个啥,这篇,我们通过ComfyUI 及其相关Lora 模型,生成一些更惊艳的图片。这篇主要了解这些内容:         1、哪里获取模型?         2、实践如何画一个美女?         3、附录:               1)相关SD(稳定扩散模型的组成部分)               2)模型放置目录(重要)

java学习,进阶,提升

http://how2j.cn/k/hutool/hutool-brief/1930.html?p=73689

【408DS算法题】039进阶-判断图中路径是否存在

Index 题目分析实现总结 题目 对于给定的图G,设计函数实现判断G中是否含有从start结点到stop结点的路径。 分析实现 对于图的路径的存在性判断,有两种做法:(本文的实现均基于邻接矩阵存储方式的图) 1.图的BFS BFS的思路相对比较直观——从起始结点出发进行层次遍历,遍历过程中遇到结点i就表示存在路径start->i,故只需判断每个结点i是否就是stop

【Python从入门到进阶】64、Pandas如何实现数据的Concat合并

接上篇《63.Pandas如何实现数据的Merge》 上一篇我们学习了Pandas如何实现数据的Merge,本篇我们来继续学习Pandas如何实现数据的Concat合并。 一、引言 在数据处理过程中,经常需要将多个数据集合并为一个统一的数据集,以便进行进一步的分析或建模。这种需求在多种场景下都非常常见,比如合并不同来源的数据集以获取更全面的信息、将时间序列数据按时间顺序拼接起来以观察长期趋势等

【Linux 从基础到进阶】 Python脚本在运维中的应用

Python脚本在运维中的应用 在现代运维工作中,Python因其简洁、高效和跨平台的特性,成为了系统管理员自动化工作的重要工具。Python不仅可以轻松处理系统管理任务,还可以与各种运维工具进行无缝集成。本文将介绍Python脚本在运维中的常见应用场景,帮助运维人员提高效率,减少人工操作错误。 1. 自动化任务调度 示例介绍 运维工作中,定时任务是非常常见的需求。虽然cron是Linux