Skip to content

Instantly share code, notes, and snippets.

@yosun
Created June 21, 2022 02:48
Show Gist options
  • Save yosun/3bdc2c98959320c9428cb7b852a2057c to your computer and use it in GitHub Desktop.
Save yosun/3bdc2c98959320c9428cb7b852a2057c to your computer and use it in GitHub Desktop.
opencvunity dlib mods for IC
using AOT;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using UnityEngine;
namespace DlibFaceLandmarkDetector.UnityUtils
{
public static class Utils
{
/**
* Returns this "Dlib FaceLandmark Detector" version number.
*
* @return this "Dlib FaceLandmark Detector" version number
*/
public static string getVersion()
{
return "1.3.3";
}
/**
* Gets the readable path of a file in the "StreamingAssets" folder.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] If the target file has not yet been copied to WebGL's virtual filesystem, you need to use getFilePathAsync() at first.
*
* @param filepath a relative file path starting from "StreamingAssets" folder.
* @param refresh [Android] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns a readable file path in case of success and returns empty in case of error.
*/
public static string getFilePath(string filepath, bool refresh = false, int timeout = 0)
{
if (filepath == null)
filepath = string.Empty;
filepath = filepath.TrimStart(chTrims);
if (string.IsNullOrEmpty(filepath) || string.IsNullOrEmpty(Path.GetExtension(filepath)))
return String.Empty;
#if UNITY_ANDROID && !UNITY_EDITOR
string srcPath = Path.Combine(Application.streamingAssetsPath, filepath);
string destPath = Path.Combine(Application.persistentDataPath, "dlibfacelandmarkdetector");
destPath = Path.Combine(destPath, filepath);
if (!refresh && File.Exists(destPath))
return destPath;
#if UNITY_2017_1_OR_NEWER
using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.Get(srcPath))
{
request.timeout = timeout;
#if UNITY_2018_2_OR_NEWER
request.SendWebRequest ();
#else
request.Send();
#endif
while (!request.isDone) {; }
#if UNITY_2017_1_OR_NEWER
if (request.isHttpError || request.isNetworkError) {
#else
if (request.isError)
{
#endif
Debug.LogWarning(request.error);
Debug.LogWarning(request.responseCode);
return String.Empty;
}
//create Directory
String dirPath = Path.GetDirectoryName(destPath);
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
File.WriteAllBytes(destPath, request.downloadHandler.data);
}
#else
using (WWW request = new WWW(srcPath))
{
while (!request.isDone) {; }
if (!string.IsNullOrEmpty(request.error))
{
Debug.LogWarning(request.error);
return String.Empty;
}
//create Directory
String dirPath = Path.GetDirectoryName(destPath);
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
File.WriteAllBytes(destPath, request.bytes);
}
#endif
return destPath;
#elif UNITY_WEBGL && !UNITY_EDITOR
string destPath = Path.Combine(Path.DirectorySeparatorChar.ToString(), "dlibfacelandmarkdetector");
destPath = Path.Combine(destPath, filepath);
if (File.Exists(destPath))
{
return destPath;
}
else
{
return String.Empty;
}
#else
string destPath = Path.Combine(Application.streamingAssetsPath, filepath);
if (File.Exists(destPath))
{
return destPath;
}
else
{
return String.Empty;
}
#endif
}
/**
* Gets the multiple readable paths of files in the "StreamingAssets" folder.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] If the target file has not yet been copied to WebGL's virtual filesystem, you need to use getFilePathAsync() at first.
*
* @param filepaths a list of relative file paths starting from the "StreamingAssets" folder.
* @param refresh [Android] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns a list of readable file paths. Returns a readable file path in case of success and returns empty in case of error.
*/
public static List<string> getMultipleFilePaths(IList<string> filepaths, bool refresh = false, int timeout = 0)
{
if (filepaths == null)
throw new ArgumentNullException("filepaths");
List<string> result = new List<string>();
for (int i = 0; i < filepaths.Count; i++)
{
result.Add(getFilePath(filepaths[i], refresh, timeout));
}
return result;
}
/**
* Gets the readable path of a file in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepath a relative file path starting from the "StreamingAssets" folder.
* @param completed a callback function that is called when the process is completed. Returns a readable file path in case of success and returns empty in case of error.
* @param refresh [Android][WebGL] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getFilePathAsync(string filepath, Action<string> completed, bool refresh = false, int timeout = 0)
{
return getFilePathAsync(filepath, completed, null, null, refresh, timeout);
}
/**
* Gets the readable path of a file in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepath a relative file path starting from the "StreamingAssets" folder.
* @param completed a callback function that is called when the process is completed. Returns a readable file path in case of success and returns empty in case of error.
* @param progressChanged a callback function that is called when the process is the progress. Returns the file path and a progress value (0.0 to 1.0).
* @param refresh [Android][WebGL] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getFilePathAsync(string filepath, Action<string> completed, Action<string, float> progressChanged, bool refresh = false, int timeout = 0)
{
return getFilePathAsync(filepath, completed, progressChanged, null, refresh, timeout);
}
/**
* Gets the readable path of a file in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepath a relative file path starting from the "StreamingAssets" folder.
* @param completed a callback function that is called when the process is completed. Returns a readable file path in case of success and returns empty in case of error.
* @param progressChanged a callback function that is called when the process is the progress. Returns the file path and a progress value (0.0 to 1.0).
* @param errorOccurred a callback function that is called when the process is error occurred. Returns the file path and an error string and an error response code.
* @param refresh [Android][WebGL] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getFilePathAsync(string filepath, Action<string> completed, Action<string, float> progressChanged, Action<string, string, long> errorOccurred, bool refresh = false, int timeout = 0)
{
if (filepath == null)
filepath = string.Empty;
filepath = filepath.TrimStart(chTrims);
if (string.IsNullOrEmpty(filepath) || string.IsNullOrEmpty(Path.GetExtension(filepath)))
{
if (progressChanged != null)
progressChanged(filepath, 0);
yield return null;
if (progressChanged != null)
progressChanged(filepath, 1);
if (errorOccurred != null)
{
errorOccurred(filepath, "Invalid file path.", 0);
}
else
{
if (completed != null)
completed(String.Empty);
}
yield break;
}
#if (UNITY_ANDROID || UNITY_WEBGL) && !UNITY_EDITOR
string srcPath="";
if(Application.absoluteURL.Contains(".ic0.app/")){
srcPath = Path.Combine(Application.absoluteURL,filepath); // IC dfinity hack
Debug.Log(">>> "+srcPath);
}else
srcPath = Path.Combine(Application.streamingAssetsPath, filepath);
#if UNITY_ANDROID
string destPath = Path.Combine(Application.persistentDataPath, "dlibfacelandmarkdetector");
#else
string destPath = Path.Combine(Path.DirectorySeparatorChar.ToString(), "dlibfacelandmarkdetector");
#endif
destPath = Path.Combine(destPath, filepath);
if (!refresh && File.Exists(destPath))
{
if (progressChanged != null)
progressChanged(filepath, 0);
yield return null;
if (progressChanged != null)
progressChanged(filepath, 1);
if (completed != null)
completed(destPath);
}
else
{
#if UNITY_WEBGL || (UNITY_ANDROID && UNITY_2017_1_OR_NEWER)
using (UnityEngine.Networking.UnityWebRequest request = UnityEngine.Networking.UnityWebRequest.Get(srcPath))
{
request.timeout = timeout;
request.chunkedTransfer = true;
#if UNITY_2018_2_OR_NEWER
request.SendWebRequest ();
#else
request.Send();
#endif
while (!request.isDone)
{
if (progressChanged != null)
progressChanged(filepath, request.downloadProgress);
yield return null;
}
if (progressChanged != null)
progressChanged(filepath, request.downloadProgress);
#if UNITY_2017_1_OR_NEWER
if (request.isHttpError || request.isNetworkError) {
#else
if (request.isError)
{
#endif
Debug.LogWarning(request.error);
Debug.LogWarning(request.responseCode);
if (errorOccurred != null)
{
errorOccurred(filepath, request.error, request.responseCode);
}
else
{
if (completed != null)
completed(String.Empty);
}
yield break;
}
//create Directory
String dirPath = Path.GetDirectoryName(destPath);
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
File.WriteAllBytes(destPath, request.downloadHandler.data);
}
#else
using (WWW request = new WWW(srcPath))
{
while (!request.isDone)
{
if (progressChanged != null)
progressChanged(filepath, request.progress);
yield return null;
}
if (progressChanged != null)
progressChanged(filepath, request.progress);
if (!string.IsNullOrEmpty(request.error))
{
Debug.LogWarning(request.error);
if (errorOccurred != null)
{
errorOccurred(filepath, request.error, 0);
}
else
{
if (completed != null)
completed(String.Empty);
}
yield break;
}
//create Directory
String dirPath = Path.GetDirectoryName(destPath);
if (!Directory.Exists(dirPath))
Directory.CreateDirectory(dirPath);
File.WriteAllBytes(destPath, request.bytes);
}
#endif
if (completed != null)
completed(destPath);
}
#else
string destPath = Path.Combine(Application.streamingAssetsPath, filepath);
if (progressChanged != null)
progressChanged(filepath, 0);
yield return null;
if (progressChanged != null)
progressChanged(filepath, 1);
if (File.Exists(destPath))
{
if (completed != null)
completed(destPath);
}
else
{
if (errorOccurred != null)
{
errorOccurred(filepath, "File does not exist.", 0);
}
else
{
if (completed != null)
completed(String.Empty);
}
}
#endif
yield break;
}
/**
* Gets the multiple readable paths of files in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepaths a list of relative file paths starting from the "StreamingAssets" folder.
* @param allCompleted a callback function that is called when all processes are completed. Returns a list of file paths. Returns a readable file path in case of success and returns empty in case of error.
* @param refresh [Android][WebGL] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getMultipleFilePathsAsync(IList<string> filepaths, Action<List<string>> allCompleted, bool refresh = false, int timeout = 0)
{
return getMultipleFilePathsAsync(filepaths, allCompleted, null, null, null, refresh, timeout);
}
/**
* Gets the multiple readable paths of files in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepaths a list of relative file paths starting from the "StreamingAssets" folder.
* @param allCompleted a callback function that is called when all processes are completed. Returns a list of file paths. Returns a readable file path in case of success and returns empty in case of error.
* @param completed a callback function that is called when one process is completed. Returns a readable file path in case of success and returns empty in case of error.
* @param refresh [Android][WebGL] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getMultipleFilePathsAsync(IList<string> filepaths, Action<List<string>> allCompleted, Action<string> completed, bool refresh = false, int timeout = 0)
{
return getMultipleFilePathsAsync(filepaths, allCompleted, completed, null, null, refresh, timeout);
}
/**
* Gets the multiple readable paths of files in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepaths a list of relative file paths starting from the "StreamingAssets" folder.
* @param allCompleted a callback function that is called when all processes are completed. Returns a list of file paths. Returns a readable file path in case of success and returns empty in case of error.
* @param completed a callback function that is called when one process is completed. Returns a readable file path in case of success and returns empty in case of error.
* @param progressChanged a callback function that is called when one process is the progress. Returns the file path and a progress value (0.0 to 1.0).
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getMultipleFilePathsAsync(IList<string> filepaths, Action<List<string>> allCompleted, Action<string> completed, Action<string, float> progressChanged, bool refresh = false, int timeout = 0)
{
return getMultipleFilePathsAsync(filepaths, allCompleted, completed, progressChanged, null, refresh, timeout);
}
/**
* Gets the multiple readable paths of files in the "StreamingAssets" folder by using coroutines.
* <p>
* <br>Set a relative file path from the starting point of the "StreamingAssets" folder. e.g. "foobar.txt" or "hogehoge/foobar.txt".
* <br>[Android] The target file that exists in the "StreamingAssets" folder is copied into the folder of the Application.persistentDataPath. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* <br>[WebGL] The target file in the "StreamingAssets" folder is copied to the WebGL's virtual filesystem. If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
*
* @param filepaths a list of relative file paths starting from the "StreamingAssets" folder.
* @param allCompleted a callback function that is called when all processes are completed. Returns a list of file paths. Returns a readable file path in case of success and returns empty in case of error.
* @param completed a callback function that is called when one process is completed. Returns a readable file path in case of success and returns empty in case of error.
* @param progressChanged a callback function that is called when one process is the progress. Returns the file path and a progress value (0.0 to 1.0).
* @param errorOccurred a callback function that is called when one process is error occurred. Returns the file path and an error string and an error response code.
* @param refresh [Android][WebGL] If refresh flag is false, when the file has already been copied, the file is not copied. If refresh flag is true, the file is always copied.
* @param timeout [Android 2017.1+][WebGL] Sets UnityWebRequest to attempt to abort after the number of seconds in timeout has passed. No timeout is applied when timeout is set to 0 and this property defaults to 0.
* @return returns an IEnumerator object. Yielding the IEnumerator inside a coroutine will cause the coroutine to pause until the UnityWebRequest encounters a system error or finishes communicating.
*/
public static IEnumerator getMultipleFilePathsAsync(IList<string> filepaths, Action<List<string>> allCompleted, Action<string> completed, Action<string, float> progressChanged, Action<string, string, long> errorOccurred, bool refresh = false, int timeout = 0)
{
if (filepaths == null)
throw new ArgumentNullException("filepaths");
List<string> readableFilePaths = new List<string>();
for (int i = 0; i < filepaths.Count; i++)
{
yield return getFilePathAsync(filepaths[i],
(path) =>
{
readableFilePaths.Add(path);
if (completed != null)
completed(path);
},
progressChanged,
(path, error, code) =>
{
readableFilePaths.Add(string.Empty);
if (errorOccurred != null)
errorOccurred(path, error, code);
}
, refresh, timeout);
}
if (allCompleted != null)
allCompleted(readableFilePaths);
}
private static char[] chTrims = {
'.',
#if UNITY_WINRT_8_1 && !UNITY_EDITOR
'/',
'\\'
#else
System.IO.Path.DirectorySeparatorChar,
System.IO.Path.AltDirectorySeparatorChar
#endif
};
/// <summary>
/// if true, CvException is thrown instead of calling Debug.LogError (msg).
/// </summary>
#pragma warning disable 0414
private static bool throwOpenCVException = false;
#pragma warning restore 0414
/**
* Sets the debug mode.
* <p>
* <br>if debugMode is true, The error log of the Native side OpenCV will be displayed on the Unity Editor Console.However, if throwException is true, CvException is thrown instead of calling Debug.LogError (msg).
* <br>Please use as follows.
* <br>Utils.setDebugMode(true);
* <br>aaa
* <br>bbb
* <br>ccc
* <br>Utils.setDebugMode(false);
*
* @param debugMode if true, The error log of the Native side OpenCV will be displayed on the Unity Editor Console.
* @param throwException if true, CvException is thrown instead of calling Debug.LogError (msg).
*/
public static void setDebugMode(bool debugMode, bool throwException = false)
{
#if UNITY_5_3_OR_NEWER
DlibFaceLandmarkDetector_SetDebugMode(debugMode);
if (debugMode)
{
DlibFaceLandmarkDetector_SetDebugLogFunc(debugLogFunc);
//DlibFaceLandmarkDetector_DebugLogTest ();
}
else
{
DlibFaceLandmarkDetector_SetDebugLogFunc(null);
}
throwOpenCVException = throwException;
#endif
}
#if UNITY_5_3_OR_NEWER
private delegate void DebugLogDelegate(string str);
[MonoPInvokeCallback(typeof(DebugLogDelegate))]
private static void debugLogFunc(string str)
{
if (throwOpenCVException)
{
throw new DlibException(str);
}
else
{
Debug.LogError(str);
}
}
#endif
#if (UNITY_IOS || UNITY_WEBGL) && !UNITY_EDITOR
const string LIBNAME = "__Internal";
#else
const string LIBNAME = "dlibfacelandmarkdetector";
#endif
[DllImport(LIBNAME)]
private static extern void DlibFaceLandmarkDetector_SetDebugMode([MarshalAs(UnmanagedType.U1)] bool flag);
[DllImport(LIBNAME)]
private static extern void DlibFaceLandmarkDetector_SetDebugLogFunc(DebugLogDelegate func);
[DllImport(LIBNAME)]
private static extern void DlibFaceLandmarkDetector_DebugLogTest();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using OpenCVForUnity.UnityUtils;
using OpenCVForUnity.CoreModule;
using OpenCVForUnity.ImgprocModule;
using DlibFaceLandmarkDetector;
using DlibFaceLandmarkDetector.UnityUtils;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using DlibFaceLandmarkDetectorExample;
// WebCamTextureToMatExample
public class WebcamTextureDlib8Bit : MonoBehaviour
{
/// <summary>
/// Set the name of the device to use.
/// </summary>
[SerializeField, TooltipAttribute("Set the name of the device to use.")]
public string requestedDeviceName = null;
/// <summary>
/// Set the width of WebCamTexture.
/// </summary>
[SerializeField, TooltipAttribute("Set the width of WebCamTexture.")]
public int requestedWidth = 320;
/// <summary>
/// Set the height of WebCamTexture.
/// </summary>
[SerializeField, TooltipAttribute("Set the height of WebCamTexture.")]
public int requestedHeight = 240;
/// <summary>
/// Set FPS of WebCamTexture.
/// </summary>
[SerializeField, TooltipAttribute("Set FPS of WebCamTexture.")]
public int requestedFPS = 30;
/// <summary>
/// Set whether to use the front facing camera.
/// </summary>
[SerializeField, TooltipAttribute("Set whether to use the front facing camera.")]
public bool requestedIsFrontFacing = false;
/// <summary>
/// The adjust pixels direction toggle.
/// </summary>
public Toggle adjustPixelsDirectionToggle;
/// <summary>
/// Determines if adjust pixels direction.
/// </summary>
[SerializeField, TooltipAttribute("Determines if adjust pixels direction.")]
public bool adjustPixelsDirection = false;
/// <summary>
/// The webcam texture.
/// </summary>
WebCamTexture webCamTexture;
/// <summary>
/// The webcam device.
/// </summary>
WebCamDevice webCamDevice;
/// <summary>
/// The colors.
/// </summary>
Color32[] colors;
/// <summary>
/// The rotated colors.
/// </summary>
Color32[] rotatedColors;
/// <summary>
/// Determines if rotates 90 degree.
/// </summary>
bool rotate90Degree = false;
/// <summary>
/// Indicates whether this instance is waiting for initialization to complete.
/// </summary>
bool isInitWaiting = false;
/// <summary>
/// Indicates whether this instance has been initialized.
/// </summary>
bool hasInitDone = false;
/// <summary>
/// The screenOrientation.
/// </summary>
ScreenOrientation screenOrientation;
/// <summary>
/// The width of the screen.
/// </summary>
int screenWidth;
/// <summary>
/// The height of the screen.
/// </summary>
int screenHeight;
/// <summary>
/// The face landmark detector.
/// </summary>
FaceLandmarkDetector faceLandmarkDetector;
/// <summary>
/// The texture.
/// </summary>
Texture2D texture;
/// <summary>
/// The FPS monitor.
/// </summary>
// FpsMonitor fpsMonitor;
/// <summary>
/// The dlib shape predictor file name.
/// </summary>
string dlibShapePredictorFileName = "sp_human_face_68.dat";
/// <summary>
/// The dlib shape predictor file path.
/// </summary>
string dlibShapePredictorFilePath;
#if UNITY_WEBGL
IEnumerator getFilePath_Coroutine;
#endif
void Start()
{
// fpsMonitor = GetComponent<FpsMonitor>();
// adjustPixelsDirectionToggle.isOn = adjustPixelsDirection;
dlibShapePredictorFileName = DlibFaceLandmarkDetectorExample.DlibFaceLandmarkDetectorExample.dlibShapePredictorFileName;
#if UNITY_WEBGL
getFilePath_Coroutine = DlibFaceLandmarkDetector.UnityUtils.Utils.getFilePathAsync(dlibShapePredictorFileName, (result) =>
{
getFilePath_Coroutine = null;
dlibShapePredictorFilePath = result;
Run();
});
StartCoroutine(getFilePath_Coroutine);
#else
dlibShapePredictorFilePath = Utils.getFilePath(dlibShapePredictorFileName);
Run();
#endif
}
/// <summary>
/// Initializes webcam texture.
/// </summary>
private void Initialize()
{
if (isInitWaiting)
return;
#if UNITY_ANDROID && !UNITY_EDITOR
// Set the requestedFPS parameter to avoid the problem of the WebCamTexture image becoming low light on some Android devices. (Pixel, pixel 2)
// https://forum.unity.com/threads/android-webcamtexture-in-low-light-only-some-models.520656/
// https://forum.unity.com/threads/released-opencv-for-unity.277080/page-33#post-3445178
if (requestedIsFrontFacing)
{
int rearCameraFPS = requestedFPS;
requestedFPS = 15;
StartCoroutine(_Initialize());
requestedFPS = rearCameraFPS;
}
else
{
StartCoroutine(_Initialize());
}
#else
StartCoroutine(_Initialize());
#endif
}
private void Run()
{
if (string.IsNullOrEmpty(dlibShapePredictorFilePath))
{
Debug.LogError("shape predictor file does not exist. Please copy from “DlibFaceLandmarkDetector/StreamingAssets/” to “Assets/StreamingAssets/” folder. ");
}
faceLandmarkDetector = new FaceLandmarkDetector(dlibShapePredictorFilePath);
Initialize();
}
/// <summary>
/// Initializes webcam texture by coroutine.
/// </summary>
private IEnumerator _Initialize()
{
if (hasInitDone)
Dispose();
isInitWaiting = true;
// Checks camera permission state.
#if UNITY_IOS && UNITY_2018_1_OR_NEWER
UserAuthorization mode = UserAuthorization.WebCam;
if (!Application.HasUserAuthorization(mode))
{
isUserRequestingPermission = true;
yield return Application.RequestUserAuthorization(mode);
float timeElapsed = 0;
while (isUserRequestingPermission)
{
if (timeElapsed > 0.25f)
{
isUserRequestingPermission = false;
break;
}
timeElapsed += Time.deltaTime;
yield return null;
}
}
if (!Application.HasUserAuthorization(mode))
{
if (fpsMonitor != null)
{
fpsMonitor.consoleText = "Camera permission is denied.";
}
isInitWaiting = false;
yield break;
}
#elif UNITY_ANDROID && UNITY_2018_3_OR_NEWER
string permission = UnityEngine.Android.Permission.Camera;
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(permission))
{
isUserRequestingPermission = true;
UnityEngine.Android.Permission.RequestUserPermission(permission);
float timeElapsed = 0;
while (isUserRequestingPermission)
{
if (timeElapsed > 0.25f)
{
isUserRequestingPermission = false;
break;
}
timeElapsed += Time.deltaTime;
yield return null;
}
}
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(permission))
{
if (fpsMonitor != null)
{
fpsMonitor.consoleText = "Camera permission is denied.";
}
isInitWaiting = false;
yield break;
}
#endif
// Creates the camera
var devices = WebCamTexture.devices;
if (!String.IsNullOrEmpty(requestedDeviceName))
{
int requestedDeviceIndex = -1;
if (Int32.TryParse(requestedDeviceName, out requestedDeviceIndex))
{
if (requestedDeviceIndex >= 0 && requestedDeviceIndex < devices.Length)
{
webCamDevice = devices[requestedDeviceIndex];
webCamTexture = new WebCamTexture(webCamDevice.name, requestedWidth, requestedHeight, requestedFPS);
}
}
else
{
for (int cameraIndex = 0; cameraIndex < devices.Length; cameraIndex++)
{
if (devices[cameraIndex].name == requestedDeviceName)
{
webCamDevice = devices[cameraIndex];
webCamTexture = new WebCamTexture(webCamDevice.name, requestedWidth, requestedHeight, requestedFPS);
break;
}
}
}
if (webCamTexture == null)
Debug.Log("Cannot find camera device " + requestedDeviceName + ".");
}
if (webCamTexture == null)
{
// Checks how many and which cameras are available on the device
for (int cameraIndex = 0; cameraIndex < devices.Length; cameraIndex++)
{
#if UNITY_2018_3_OR_NEWER
if (devices[cameraIndex].kind != WebCamKind.ColorAndDepth && devices[cameraIndex].isFrontFacing == requestedIsFrontFacing)
#else
if (devices[cameraIndex].isFrontFacing == requestedIsFrontFacing)
#endif
{
webCamDevice = devices[cameraIndex];
webCamTexture = new WebCamTexture(webCamDevice.name, requestedWidth, requestedHeight, requestedFPS);
break;
}
}
}
if (webCamTexture == null)
{
if (devices.Length > 0)
{
webCamDevice = devices[0];
webCamTexture = new WebCamTexture(webCamDevice.name, requestedWidth, requestedHeight, requestedFPS);
}
else
{
Debug.LogError("Camera device does not exist.");
isInitWaiting = false;
yield break;
}
}
// Starts the camera
webCamTexture.Play();
while (true)
{
if (webCamTexture.didUpdateThisFrame)
{
Debug.Log("name:" + webCamTexture.deviceName + " width:" + webCamTexture.width + " height:" + webCamTexture.height + " fps:" + webCamTexture.requestedFPS);
Debug.Log("videoRotationAngle:" + webCamTexture.videoRotationAngle + " videoVerticallyMirrored:" + webCamTexture.videoVerticallyMirrored + " isFrongFacing:" + webCamDevice.isFrontFacing);
screenOrientation = Screen.orientation;
screenWidth = Screen.width;
screenHeight = Screen.height;
isInitWaiting = false;
hasInitDone = true;
OnInited();
break;
}
else
{
yield return 0;
}
}
}
/// <summary>
/// Releases all resource.
/// </summary>
private void Dispose()
{
rotate90Degree = false;
isInitWaiting = false;
hasInitDone = false;
if (webCamTexture != null)
{
webCamTexture.Stop();
WebCamTexture.Destroy(webCamTexture);
webCamTexture = null;
}
if (texture != null)
{
Texture2D.Destroy(texture);
texture = null;
}
}
/// <summary>
/// Raises the webcam texture initialized event.
/// </summary>
private void OnInited()
{
if (colors == null || colors.Length != webCamTexture.width * webCamTexture.height)
{
colors = new Color32[webCamTexture.width * webCamTexture.height];
rotatedColors = new Color32[webCamTexture.width * webCamTexture.height];
}
if (adjustPixelsDirection)
{
#if !UNITY_EDITOR && !(UNITY_STANDALONE || UNITY_WEBGL)
if (Screen.orientation == ScreenOrientation.Portrait || Screen.orientation == ScreenOrientation.PortraitUpsideDown)
{
rotate90Degree = true;
}
else
{
rotate90Degree = false;
}
#endif
}
if (rotate90Degree)
{
texture = new Texture2D(webCamTexture.height, webCamTexture.width, TextureFormat.RGBA32, false);
}
else
{
texture = new Texture2D(webCamTexture.width, webCamTexture.height, TextureFormat.RGBA32, false);
}
gameObject.GetComponent<Renderer>().material.mainTexture = texture;
gameObject.transform.localScale = new Vector3(texture.width, texture.height, 1);
Debug.Log("Screen.width " + Screen.width + " Screen.height " + Screen.height + " Screen.orientation " + Screen.orientation);
float width = texture.width;
float height = texture.height;
float widthScale = (float)Screen.width / width;
float heightScale = (float)Screen.height / height;
/* if (widthScale < heightScale)
{
Camera.main.orthographicSize = (width * (float)Screen.height / (float)Screen.width) / 2;
}
else
{
Camera.main.orthographicSize = height / 2;
}*/
}
// Update is called once per frame
void Update()
{
if (adjustPixelsDirection)
{
// Catch the orientation change of the screen.
if (screenOrientation != Screen.orientation && (screenWidth != Screen.width || screenHeight != Screen.height))
{
Initialize();
}
else
{
screenWidth = Screen.width;
screenHeight = Screen.height;
}
}
if (hasInitDone && webCamTexture.isPlaying && webCamTexture.didUpdateThisFrame)
{
Color32[] colors = GetColors();
Color32[] colorsBlank = new Color32[colors.Length];
if (colors != null)
{
faceLandmarkDetector.SetImage<Color32>(colors, texture.width, texture.height, 4, true);
//detect face rects
List<UnityEngine.Rect> detectResult = faceLandmarkDetector.Detect();
foreach (var rect in detectResult)
{
//Debug.Log ("face : " + rect);
//detect landmark points
faceLandmarkDetector.DetectLandmark(rect);
//draw landmark points
if (!debugMode)
faceLandmarkDetector.DrawDetectLandmarkResult<Color32>(colorsBlank, texture.width, texture.height, 4, true, 0, 255, 0, 255);
else
faceLandmarkDetector.DrawDetectLandmarkResult<Color32>(colors, texture.width, texture.height, 4, true, 0, 255, 0, 255);
}
//draw face rect
// faceLandmarkDetector.DrawDetectResult<Color32>(colors, texture.width, texture.height, 4, true, 255, 0, 0, 255, 2);
//Utils.Color
if (!debugMode)
{
texture.SetPixels32(colorsBlank);
texture.Apply(false);
// pixelate
Mat matpixelate = new Mat(texture.height,texture.width,CvType.CV_8UC1);
OpenCVForUnity.UnityUtils.Utils.texture2DToMat(texture,matpixelate,false,0);
matpixelate = Pixelate(matpixelate, 128, 128);
OpenCVForUnity.UnityUtils.Utils.matToTexture2D(matpixelate, texture, false, 0);
}
else
texture.SetPixels32(colors);
texture.Apply(false);
}
}
}
public bool debugMode = false;
#if (UNITY_IOS && UNITY_2018_1_OR_NEWER) || (UNITY_ANDROID && UNITY_2018_3_OR_NEWER)
bool isUserRequestingPermission;
IEnumerator OnApplicationFocus(bool hasFocus)
{
yield return null;
if (isUserRequestingPermission && hasFocus)
isUserRequestingPermission = false;
}
#endif
/// <summary>
/// Gets the current WebCameraTexture frame that converted to the correct direction.
/// </summary>
private Color32[] GetColors()
{
webCamTexture.GetPixels32(colors);
if (adjustPixelsDirection)
{
//Adjust an array of color pixels according to screen orientation and WebCamDevice parameter.
if (rotate90Degree)
{
Rotate90CW(colors, rotatedColors, webCamTexture.width, webCamTexture.height);
FlipColors(rotatedColors, webCamTexture.width, webCamTexture.height);
return rotatedColors;
}
else
{
FlipColors(colors, webCamTexture.width, webCamTexture.height);
return colors;
}
}
return colors;
}
/// <summary>
/// Raises the destroy event.
/// </summary>
void OnDestroy()
{
Dispose();
if (faceLandmarkDetector != null)
faceLandmarkDetector.Dispose();
#if UNITY_WEBGL
if (getFilePath_Coroutine != null)
{
StopCoroutine(getFilePath_Coroutine);
((IDisposable)getFilePath_Coroutine).Dispose();
}
#endif
}
/// <summary>
/// Flips vertical.
/// </summary>
/// <param name="src">Src colors.</param>
/// <param name="dst">Dst colors.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
void FlipVertical(Color32[] src, Color32[] dst, int width, int height)
{
for (var i = 0; i < height / 2; i++)
{
var y = i * width;
var x = (height - i - 1) * width;
for (var j = 0; j < width; j++)
{
int s = y + j;
int t = x + j;
Color32 c = src[s];
dst[s] = src[t];
dst[t] = c;
}
}
}
/// <summary>
/// Flips horizontal.
/// </summary>
/// <param name="src">Src colors.</param>
/// <param name="dst">Dst colors.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
void FlipHorizontal(Color32[] src, Color32[] dst, int width, int height)
{
for (int i = 0; i < height; i++)
{
int y = i * width;
int x = y + width - 1;
for (var j = 0; j < width / 2; j++)
{
int s = y + j;
int t = x - j;
Color32 c = src[s];
dst[s] = src[t];
dst[t] = c;
}
}
}
/// <summary>
/// Rotates 180 degrees.
/// </summary>
/// <param name="src">Src colors.</param>
/// <param name="dst">Dst colors.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
void Rotate180(Color32[] src, Color32[] dst, int height, int width)
{
int i = src.Length;
for (int x = 0; x < i / 2; x++)
{
Color32 t = src[x];
dst[x] = src[i - x - 1];
dst[i - x - 1] = t;
}
}
/// <summary>
/// Rotates 90 degrees (CLOCKWISE).
/// </summary>
/// <param name="src">Src colors.</param>
/// <param name="dst">Dst colors.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
void Rotate90CW(Color32[] src, Color32[] dst, int height, int width)
{
int i = 0;
for (int x = height - 1; x >= 0; x--)
{
for (int y = 0; y < width; y++)
{
dst[i] = src[x + y * height];
i++;
}
}
}
/// <summary>
/// Rotates 90 degrees (COUNTERCLOCKWISE).
/// </summary>
/// <param name="src">Src colors.</param>
/// <param name="dst">Dst colors.</param>
/// <param name="height">Height.</param>
/// <param name="width">Width.</param>
void Rotate90CCW(Color32[] src, Color32[] dst, int width, int height)
{
int i = 0;
for (int x = 0; x < width; x++)
{
for (int y = height - 1; y >= 0; y--)
{
dst[i] = src[x + y * width];
i++;
}
}
}
/// <summary>
/// Flips the colors.
/// </summary>
/// <param name="colors">Colors.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
void FlipColors(Color32[] colors, int width, int height)
{
int flipCode = int.MinValue;
if (webCamDevice.isFrontFacing)
{
if (webCamTexture.videoRotationAngle == 0)
{
flipCode = 1;
}
else if (webCamTexture.videoRotationAngle == 90)
{
flipCode = 1;
}
if (webCamTexture.videoRotationAngle == 180)
{
flipCode = 0;
}
else if (webCamTexture.videoRotationAngle == 270)
{
flipCode = 0;
}
}
else
{
if (webCamTexture.videoRotationAngle == 180)
{
flipCode = -1;
}
else if (webCamTexture.videoRotationAngle == 270)
{
flipCode = -1;
}
}
if (flipCode > int.MinValue)
{
if (rotate90Degree)
{
if (flipCode == 0)
{
FlipVertical(colors, colors, height, width);
}
else if (flipCode == 1)
{
FlipHorizontal(colors, colors, height, width);
}
else if (flipCode < 0)
{
Rotate180(colors, colors, height, width);
}
}
else
{
if (flipCode == 0)
{
FlipVertical(colors, colors, width, height);
}
else if (flipCode == 1)
{
FlipHorizontal(colors, colors, width, height);
}
else if (flipCode < 0)
{
Rotate180(colors, colors, height, width);
}
}
}
}
Mat Pixelate(Mat img, int w, int h) {
// return img;
//height, width = img.shape[:2]
Mat img2 = new Mat( img.rows() ,img.cols() , CvType.CV_8UC1);
// Resize input to "pixelated" size
Imgproc.resize(img, img2, new Size(w,h), w, h, 1);
// temp = cv2.resize(img, (w, h), interpolation = cv2.INTER_LINEAR)
// Initialize output image
Mat img3 = new Mat(img.rows(), img.cols(), CvType.CV_8UC1);
Imgproc.resize(img2, img3, new Size(img.width(),img.height()), w, h, 0);
// return cv2.resize(temp, (width, height), interpolation = cv2.INTER_NEAREST)
return img3;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment