效果展示
整个项目主要分为三部分,一开始无限循环验证是否有狗狗照片,找到狗狗照片之后就进入狗子的MMD部分,狗子跳完病名为爱之后会有一场烟花,烟花之后是一封信【当然信部分删掉了233
TensorFlow 在Unity中引入
TensorFlow CSharp
TensorFlow得以成功的部署在Unity中完全得益于这个项目,现在CSharp版本的Tensorflow已经更新到1.5,支持和Python一样的各种操作,但是今天主要只使用其中的部署部分
在宇宙第一IDE中Nuget下载TensorFlow Cshap之后
using TensorFlow
var graph = new TFGraph();
// 从文件加载序列化的GraphDef
var model = File.ReadAllBytes(modelFile);
//导入GraphDef
graph.Import(model, "");
using (var session = new TFSession(graph)){
var tensor = CreateTensorFromImageFile(file);
var runner = session.GetRunner();
runner.AddInput(graph["input"][0], tensor).Fetch(graph["output"][0]);
var output = runner.Run();
}
但是查阅了官方lib.so文件,TensorFlow Csharp的lib只有Mac, Windows, Linux三个平台,一开始尝试强制在Unity中使用lib,但是最后会报引用错误,索性最后找到了官方的ML-Agent中的TensorFlow库,支持所有平台【比心
所以整个开发流程就变成了,在宇宙第一IDE中跑通本地测试,然后再放入Unity中稍加修改就好
ML-Agent
这个引擎本来是Unity希望和Python联动的一个过程,今天我们忽略其中训练的部分,只采用部署部分
引入整个ML-Agent引擎之后,就可以
using TensorFlow
在安卓平台的开头要加上
##if UNITY_ANDROID
TensorFlowSharp.Android.NativeBinding.Init();
##endif
同时要注意,因为Unity自身特性,需要使用.bytes的图文件,其中要包含本身的pt图文件,也要包含checkpoint部分官方有合并的Demo,在此不再赘述。
导入图部分(只导入一次):
public Classifier(byte[] model, string[] labels){
##if UNITY_ANDROID
TensorFlowSharp.Android.NativeBinding.Init();
##endif
this.labels = labels;
this.graph = new TFGraph();
this.graph.Import(model, "");
}
识别部分:
public Task<bool> IsDogAsync(Color32[] data){
return Task.Run(() =>{
using (var session = new TFSession(graph)){
//采用Color32 pic的方式读取图片
var tensor = TransformInput(data, INPUT_SIZE, INPUT_SIZE);
//采用byte[]方式读取图片
//var tensor = CreateTensorFromImageFile(data);
var runner = session.GetRunner();
runner.AddInput(graph[INPUT_NAME][0], tensor).Fetch(graph[OUTPUT_NAME][0]);
var output = runner.Run();
// output[0].Value() is a vector containing probabilities of
// labels for each image in the "batch". The batch size was 1.
// Find the most probably label index.
var result = output[0];
//return部分略掉了
}
});
}
注意调用识别部分全部采用的Task Async+InvokeRepeating模式,而Unity中自带的yield(比如定时5s执行一次)+IEnumerator就必须写在Update中(限制在主线程中),这样其实只是改变了运行的时间,而不是单独开一个线程运行,所以写在Update里,实际就还是在Update中执行,所以会造成明显卡顿,这里需要一直不断用多线程函数循环
InvokeRepeating(nameof(checkDog), 1f, 1f);
private async void checkDog() {
var snap = TakeTextureSnap();
var scaled = Scale(snap);
var rotated = await RotateAsync(scaled.GetPixels32(), scaled.width, scaled.height);
bool isDog = await classifier.IsDogAsync(rotated);
if (isDog) {
DogTimes += 1;
}
if (DogTimes > 1) {
IsMagicSystemStart = true;
IsDogSystemStart = true;
}
}
Unity中背景调用摄像头
目的
因为本身是想做成 AR 的,所以背景层是实时摄像头,同时又需要满足Tensorflow获取实时画面。
采用EasyAR + 截图方式 (Failed)
一开始是使用EasyAR的包,好处是不用自己考虑各平台调用摄像头的问题,但是查阅了EasyAR的手册并没有发现把当前帧输出出来的API,在论坛有人问过类似的问题,官方给出的答复是去参考他们的Demo【就不能直说么……】,看完他们的Demo发现其实是调用了截图,把当前背景的截图保存成Texture,于是尝试采用截图:
Unity自带的截图主要有两种:
1.直接调用内部API截图,但是及其慢,而且不能指定截图位置和大小【虽然在这里不需要指定大小】
Application.CaptureScreenshot("Screenshot.png", 0);
2.把当前屏幕保存成一个Texture2D,然后把当前纹理保存成PNG
/// <param name="rect">Rect.截图的区域,左下角为o点</param>
Texture2D CaptureScreenshot2(Rect rect)
{
// 先创建一个的空纹理,大小可根据实现需要来设置
Texture2D screenShot = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGB24,false);
// 读取屏幕像素信息并存储为纹理数据,
screenShot.ReadPixels(rect, 0, 0);
screenShot.Apply();
// 然后将这些纹理数据,成一个png图片文件
byte[] bytes = screenShot.EncodeToPNG();
string filename = Application.dataPath + "/Screenshot.png";
System.IO.File.WriteAllBytes(filename, bytes);
Debug.Log(string.Format("截屏了一张图片: {0}", filename));
// 最后,我返回这个Texture2d对象,这样我们直接,所这个截图图示在游戏中,当然这个根据自己的需求的。
return screenShot;
}
经过尝试确实比直接调用截图API快一些,但是在截图的时候还是有肉眼可查的明显的卡顿,【也可能是只在电脑上尝试,性能不够】
参考网上其他把TensorFlow部署到Unity里面的教程,使用了从相机Texture直接读取的方式
直接读取相机的Texuture获取图片(Succeed)
参考natsupy GithubGist
在TextureTools中使用的
result.SetPixels(texture.GetPixels(Mathf.FloorToInt(xRect), Mathf.FloorToInt(yRect),
Mathf.FloorToInt(widthRect), Mathf.FloorToInt(heightRect)));
是直接使用相机的texture,实测速度优于从把相机的texture输出到底层屏幕,再截图底层屏幕的速度。
【为什么同样截取texture,从相机输入会比屏幕更快……】
Unity调用相机的那些坑
本身调用相机规规矩矩的使用用WebCamDevice就好
WebCamDevice[] devices = WebCamTexture.devices;
if (devices.Length == 0){
camAvailable = false;
return;
}
backCam = new WebCamTexture(devices[0].name, Screen.height, Screen.width);
background.texture = backCam;
backCam.Play();
camAvailable = true;
但是在安卓上直接显示在背景上是有问题的,获取的相机实际图像其实是旋转90°,比如实际竖屏(X,Y) 1080 x 1920, 然而安卓相机获取的却是 1920 x 1080 竖着的相机图像 ,并且会自动旋转90°再放到屏幕上(1080x1980)……所以最后的效果是满屏的摄像头图像,但是是旋转九十度的。
【这里网上有把 height 和 width输入时反置的,得到 1080 x 1920竖屏图像,但是之后还是会旋转90°,变成1920x1080,会造成上下显示不全,但是这样后续只需要逆向旋转90°就可以了】
所以如果我们希望得到竖屏的图像,需要把整个背景图像旋转90°,但是没完,这样得到的图像是 1920 x 1080 的竖屏图像,显示出来就是 1080 x 1080的 图像,之后还需要进行等比缩放,把原来1920x1080中的 1080 缩放到 1920
//form youtube:https://www.youtube.com/watch?v=c6NXkZWXHnc
// github :https://github.com/Chamuth/unity-webcam/blob/master/MobileCam.cs
float ratio = (float)backCam.width / (float)backCam.height;
fitter.aspectRatio = ratio; // Set the aspect ratio
float scaleY = backCam.videoVerticallyMirrored ? -1f : 1f; // Find if the camera is mirrored or not
background.rectTransform.localScale = new Vector3(1f, scaleY, 1f); // Swap the mirrored camera
int orient = -backCam.videoRotationAngle;
background.rectTransform.localEulerAngles = new Vector3(0, 0, orient);
background.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, Screen.height);
background.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, Screen.width);
烟花部分
网上所有的烟花都是基于js的unity4.x版本……各个资源站都是传来传去的,就是Unity商城中的一个项目,最后在一个不起眼的地方找到了这个盛世烟花。
Comments | NOTHING