前面我们已经编写完了神经网络代码,虽然有些简陋,但是麻雀虽小五脏俱全,这是一个完整的神经网络结构,我们可以应用这个神经网络到AR中,下面我将利用这个神经网络来做AR中的碰撞检测(这里的碰撞检测是指用户(手机终端)与ARCore检测到的特征点距离过近时发出警报以防止用户与真实物体发生碰撞。当然,实现这个功能可以完全不用神经网络而采取直接计算,这里只是为了演示需要,讲解如何将神经网络运用到AR中去,同时加深对神经网络的理解)。
我们的实现思路是:
- 构建一个两层的神经网络。
- 对这个两层的神经网络进行训练。
- 从ARCore中获取到特征点,利用这个特征点到用户(手机终端)的距离,通过神经网络进行计算,如果特征点到用户(手机终端)的距离过近则发出声音警报以提醒用户。
一、编写环境扫描代码
在Project窗口中,在ARCoreML/Scripts 文件夹上右键,选择Create ,新建一个C# Script,取名为EnvironmentalScanner.cs,这个代码用来扫描环境、提取特征点、训练神经网络、进行环境评估。先把所有代码写出来,我们再逐行解释。
[RequireComponent(typeof(AudioSource))]
public class EnvironmentalScanner : MonoBehaviour
{
public Camera FirstPersonCamera;
private NeuralNet net;
private List<DataSet> dataSets;
private float min = float.MaxValue;
private double[] output;
private bool warning = false;
private AudioSource audioSource;
public void Awake()
{
int numInputs, numHiddenLayers, numOutputs;
numInputs = 1; numHiddenLayers = 4; numOutputs = 1;
net = new NeuralNet(numInputs, numHiddenLayers, numOutputs);
dataSets = new List<DataSet>();
}
// Use this for initialization
void Start()
{
dataSets.Add(new DataSet(new double[]{ 1,.1,0.0}, new double[] { 0.0,1.0,1.0 } ));
net.Train(dataSets, .001);
audioSource = GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if (warning)
{
audioSource.Play();
}
else
{
audioSource.Stop();
}
if (Session.Status != SessionStatus.Tracking)
{
return;
}
min = float.MaxValue;
if (Frame.PointCloud.PointCount > 0 && Frame.PointCloud.IsUpdatedThisFrame)
{
for (int i = 0; i < Frame.PointCloud.PointCount; i++)
{
var rng = Mathf.Clamp01(((Vector3)Frame.PointCloud.GetPoint(i) - FirstPersonCamera.transform.position).magnitude);
min = Mathf.Min(rng, (float)min);
}
output = net.Compute(new double[] { (double)min });
if(output.Length > 0)
{
warning = output[0] > .001;
}
else
{
warning = false;
}
}
}
}
[RequireComponent(typeof(AudioSource))] 这是一个Unity Editor扩展指令,使用这个指令[RequireComponent(typeof(类型))]可以保证该脚本被挂载时会将typeof(类型)的这个组件一并挂载,如果之前就有typeof(类型)组件,则不做任何操作,如果没有typeof(类型)组件,则会自动挂载该组件,以避免装配错误。因为我们需要AudioSource组件播放声音,所以我们添加这个指令。
接下来我们定义了若干变量,FirstPersonCamera这个需要将场景的摄像头赋给它,这里用来定位用户(手机设备)的位置以便与特征点进行距离计算。然后我们还定义了神经网络以及其他的辅助变量。
Awake()方法是Unity中的一个特殊方法,该方法运行在Start()方法前,它是在对象第一次变得活跃时被调用。Awake()与Start()方法运行先后上有所区别,Awake()它是在对象初始化时调用的,而Start()是对象在被渲染的第一帧前被调用。这种差异是微妙的,通常只有当您关心对象加载时间先后时才用考虑。接下来我们构建了一个输入层是1,隐藏层为4,输出层为1的神经网络。
在Start()方法中,我们添加了训练数据集,并对神经网络进行训练。这里我们使用的DataSet非常简单,因为只有一个输入和一个输出,其训练图如下:
随后,我们将神经网络训练成最小误差为.001的神经网络。这里我们还获取了所需的AudioSource,并将其设置到私有的AudioSource变量中,因为我们后面会使用声音来作为当用户太靠近物体时的警告。
在Update()方法中,我们根据用户与特征点的距离来决定是否播放警告声。我们首先检查warning标志是否为true,如果是,我们播放一个声音,否则我们停止播放;warning是一个标志,他用神经网络输出来控制的。接下来,我们首先确保当前帧正处于被跟踪状态,重置min,并从帧中获取当前点云。在if块内,判断当前帧中有已检测到的特征点,并且这些特征点是被更新过的,否则我们什么也做不了。在这些条件符合后,通过循环遍历所有特征点来计算当前距离用户最近的特征点到用户的距离,然后使用神经网络net.Computer(最小点)来计算nn的输出,并根据这个输出这将warning设置为true或false。
二、配置实现
在Unity中,在Hierarchy窗口根目录下新建一个空对象,并命名为EnvironmentScanner,选择这个对象,在Inspector窗口中为这个对象添加AudioSource组件,并将我们准备好的警示声拖到AudioClip属性上,同时取消Play On Awake多选框以防止自鸣。选择 Add Component 添加我们刚编写的EnvironmentScanner脚本(或者将脚本直接拖到这个Inspector窗口上),结果如下图所示:
好了,大功告成,打开Build Settings对话框,确保我们刚才的场景已选择,编译,运行。
至此,我们利用神经网络做了一个接近告警器,当用户太接近一个对象时就会发出警报。如前所述,我们也可以完全不用神经网络来而采用更简单的方法实现这个功能,但这个简单的例子为我们提供了一个很好的基础来理解神经网络、训练、评估是如何工作的,也验证了我们前面写的神经网络的正确性,对下步利用神经网络进行更复杂的开发打下了很好的基础。