|
using UnityEngine; |
|
using Unity.Sentis; |
|
using UnityEngine.Video; |
|
using UnityEngine.UI; |
|
using Lays = Unity.Sentis.Layers; |
|
using FF = Unity.Sentis.Functional; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class RunBlazeFace : MonoBehaviour |
|
{ |
|
|
|
public ModelAsset modelAsset; |
|
|
|
|
|
public RawImage previewUI = null; |
|
|
|
|
|
public Sprite boundingboxSprite; |
|
public Texture2D borderTexture; |
|
|
|
|
|
public Sprite[] markerTextures; |
|
|
|
public string videoName = "chatting.mp4"; |
|
|
|
|
|
public Texture2D inputImage; |
|
|
|
public InputType inputType = InputType.Video; |
|
|
|
Vector2Int resolution = new Vector2Int(640, 640); |
|
WebCamTexture webcam; |
|
VideoPlayer video; |
|
|
|
const BackendType backend = BackendType.GPUCompute; |
|
|
|
RenderTexture targetTexture; |
|
public enum InputType { Image, Video, Webcam }; |
|
|
|
|
|
|
|
[SerializeField, Range(0, 1)] float iouThreshold = 0.5f; |
|
[SerializeField, Range(0, 1)] float scoreThreshold = 0.5f; |
|
int maxOutputBoxes = 64; |
|
|
|
IWorker worker; |
|
|
|
|
|
int size = 128; |
|
|
|
Model model; |
|
|
|
|
|
const string deviceName = ""; |
|
|
|
bool closing = false; |
|
|
|
TensorFloat anchors, centersToCorners; |
|
|
|
public struct BoundingBox |
|
{ |
|
public float centerX; |
|
public float centerY; |
|
public float width; |
|
public float height; |
|
} |
|
|
|
void Start() |
|
{ |
|
|
|
|
|
|
|
targetTexture = new RenderTexture(resolution.x, resolution.y, 0); |
|
|
|
SetupInput(); |
|
|
|
SetupModel(); |
|
|
|
SetupBoundingBoxSprite(); |
|
} |
|
|
|
void SetupInput() |
|
{ |
|
switch (inputType) |
|
{ |
|
case InputType.Webcam: |
|
{ |
|
webcam = new WebCamTexture(deviceName, resolution.x, resolution.y); |
|
webcam.requestedFPS = 30; |
|
webcam.Play(); |
|
break; |
|
} |
|
case InputType.Video: |
|
{ |
|
video = gameObject.AddComponent<VideoPlayer>(); |
|
video.renderMode = VideoRenderMode.APIOnly; |
|
video.source = VideoSource.Url; |
|
video.url = Application.streamingAssetsPath + "/"+videoName; |
|
video.isLooping = true; |
|
video.Play(); |
|
break; |
|
} |
|
default: |
|
{ |
|
Graphics.Blit(inputImage, targetTexture); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
void SetupBoundingBoxSprite() |
|
{ |
|
if (boundingboxSprite == null) |
|
{ |
|
boundingboxSprite = Sprite.Create(borderTexture, |
|
new Rect(0, 0, borderTexture.width, borderTexture.height), |
|
new Vector2(borderTexture.width / 2, borderTexture.height / 2)); |
|
} |
|
} |
|
|
|
void Update() |
|
{ |
|
if (inputType == InputType.Webcam) |
|
{ |
|
|
|
if (!webcam.didUpdateThisFrame) return; |
|
|
|
var aspect1 = (float)webcam.width / webcam.height; |
|
var aspect2 = (float)resolution.x / resolution.y; |
|
var gap = aspect2 / aspect1; |
|
|
|
var vflip = webcam.videoVerticallyMirrored; |
|
var scale = new Vector2(gap, vflip ? -1 : 1); |
|
var offset = new Vector2((1 - gap) / 2, vflip ? 1 : 0); |
|
|
|
Graphics.Blit(webcam, targetTexture, scale, offset); |
|
} |
|
if (inputType == InputType.Video) |
|
{ |
|
var aspect1 = (float)video.width / video.height; |
|
var aspect2 = (float)resolution.x / resolution.y; |
|
var gap = aspect2 / aspect1; |
|
|
|
var vflip = false; |
|
var scale = new Vector2(gap, vflip ? -1 : 1); |
|
var offset = new Vector2((1 - gap) / 2, vflip ? 1 : 0); |
|
Graphics.Blit(video.texture, targetTexture, scale, offset); |
|
} |
|
if (inputType == InputType.Image) |
|
{ |
|
Graphics.Blit(inputImage, targetTexture); |
|
} |
|
|
|
if (Input.GetKeyDown(KeyCode.Escape)) |
|
{ |
|
closing = true; |
|
Application.Quit(); |
|
} |
|
|
|
if (Input.GetKeyDown(KeyCode.P)) |
|
{ |
|
previewUI.enabled = !previewUI.enabled; |
|
} |
|
} |
|
|
|
|
|
void LateUpdate() |
|
{ |
|
if (!closing) |
|
{ |
|
RunInference(targetTexture); |
|
} |
|
} |
|
|
|
|
|
|
|
float[] GetGridBoxCoords() |
|
{ |
|
var offsets = new float[896 * 4]; |
|
int n = 0; |
|
AddGrid(offsets, 16, 2, 8, ref n); |
|
AddGrid(offsets, 8, 6, 16, ref n); |
|
return offsets; |
|
} |
|
void AddGrid(float[] offsets, int rows, int repeats, int cellWidth, ref int n) |
|
{ |
|
for (int j = 0; j < repeats * rows * rows; j++) |
|
{ |
|
offsets[n++] = cellWidth * ((j / repeats) % rows - (rows - 1) * 0.5f); |
|
offsets[n++] = cellWidth * ((j / repeats / rows) - (rows - 1) * 0.5f); |
|
n += 2; |
|
} |
|
} |
|
|
|
void SetupModel() |
|
{ |
|
float[] offsets = GetGridBoxCoords(); |
|
|
|
var model = ModelLoader.Load(modelAsset); |
|
|
|
|
|
|
|
size = model.inputs[0].shape.ToTensorShape()[1]; |
|
|
|
anchors = new TensorFloat(new TensorShape(offsets.Length / 4, 4), offsets); |
|
|
|
centersToCorners = new TensorFloat(new TensorShape(4, 4), |
|
new float[] |
|
{ |
|
1, 0, 1, 0, |
|
0, 1, 0, 1, |
|
-0.5f, 0, 0.5f, 0, |
|
0, -0.5f, 0, 0.5f |
|
}); |
|
|
|
var model2 = Functional.Compile( |
|
input => |
|
{ |
|
var outputs = model.Forward(input); |
|
var regressors = outputs[0][0]; |
|
var scores = outputs[1][0].Transpose(0, 1) - scoreThreshold; |
|
var boxCoords = regressors[.., 0..4] + FunctionalTensor.FromTensor(anchors); |
|
var boxCorners = FF.MatMul(boxCoords, FunctionalTensor.FromTensor(centersToCorners)); |
|
var indices = FF.NMS(boxCorners, scores, iouThreshold); |
|
var indices2 = indices.Unsqueeze(-1).BroadcastTo(new int[] { 4 }); |
|
var output = FF.Gather(boxCoords, 0, indices2); |
|
var indices3 = indices.Unsqueeze(-1).BroadcastTo(new int[] { 16 }); |
|
var markersOutput = FF.Gather(regressors, 0, indices3); |
|
return (output, markersOutput); |
|
}, |
|
InputDef.FromModel(model)[0] |
|
); |
|
|
|
worker = WorkerFactory.CreateWorker(backend, model2); |
|
|
|
} |
|
|
|
void DrawFaces(TensorFloat index3, TensorFloat regressors, int NMAX, Vector2 scale) |
|
{ |
|
for (int n = 0; n < NMAX; n++) |
|
{ |
|
|
|
var box = new BoundingBox |
|
{ |
|
centerX = index3[ n, 0] * scale.x, |
|
centerY = index3[ n, 1] * scale.y, |
|
width = index3[ n, 2] * scale.x, |
|
height = index3[ n, 3] * scale.y |
|
}; |
|
DrawBox(box, boundingboxSprite); |
|
if (regressors == null) continue; |
|
|
|
|
|
for (int j = 0; j < 6; j++) |
|
{ |
|
var marker = new BoundingBox |
|
{ |
|
centerX = box.centerX + (regressors[ n, 4 + j * 2] - regressors[ n, 0]) * scale.x, |
|
centerY = box.centerY + (regressors[ n, 4 + j * 2 + 1] - regressors[ n, 1]) * scale.y, |
|
width = 8.0f * scale.x, |
|
height = 8.0f * scale.y, |
|
}; |
|
DrawBox(marker, j < markerTextures.Length ? markerTextures[j] : boundingboxSprite); |
|
} |
|
} |
|
} |
|
|
|
void ExecuteML(Texture source) |
|
{ |
|
var transform = new TextureTransform(); |
|
transform.SetDimensions(size, size, 3); |
|
transform.SetTensorLayout(0, 3, 1, 2); |
|
using var image = TextureConverter.ToTensor(source, transform); |
|
|
|
worker.Execute(image); |
|
|
|
using var output = worker.PeekOutput("output_0") as TensorFloat; |
|
using var markersOutput = worker.PeekOutput("output_1") as TensorFloat; |
|
|
|
output.CompleteOperationsAndDownload(); |
|
markersOutput.CompleteOperationsAndDownload(); |
|
|
|
|
|
|
|
|
|
ClearAnnotations(); |
|
|
|
Vector2 markerScale = previewUI.rectTransform.rect.size / size; |
|
|
|
|
|
DrawFaces(output, markersOutput, output.shape[0], markerScale); |
|
|
|
} |
|
|
|
void RunInference(Texture input) |
|
{ |
|
|
|
ExecuteML(input); |
|
|
|
previewUI.texture = input; |
|
} |
|
|
|
public void DrawBox(BoundingBox box, Sprite sprite) |
|
{ |
|
var panel = new GameObject("ObjectBox"); |
|
panel.AddComponent<CanvasRenderer>(); |
|
panel.AddComponent<Image>(); |
|
panel.transform.SetParent(previewUI.transform, false); |
|
|
|
var img = panel.GetComponent<Image>(); |
|
img.color = Color.white; |
|
img.sprite = sprite; |
|
img.type = Image.Type.Sliced; |
|
|
|
panel.transform.localPosition = new Vector3(box.centerX, -box.centerY); |
|
RectTransform rt = panel.GetComponent<RectTransform>(); |
|
rt.sizeDelta = new Vector2(box.width, box.height); |
|
} |
|
public void ClearAnnotations() |
|
{ |
|
foreach (Transform child in previewUI.transform) |
|
{ |
|
Destroy(child.gameObject); |
|
} |
|
} |
|
|
|
void CleanUp() |
|
{ |
|
closing = true; |
|
anchors?.Dispose(); |
|
centersToCorners?.Dispose(); |
|
if (webcam) Destroy(webcam); |
|
if (video) Destroy(video); |
|
RenderTexture.active = null; |
|
targetTexture.Release(); |
|
worker?.Dispose(); |
|
worker = null; |
|
} |
|
|
|
void OnDestroy() |
|
{ |
|
CleanUp(); |
|
} |
|
|
|
} |
|
|
|
|