simplestarの技術ブログ

目的を書いて、思想と試行、結果と考察、そして具体的な手段を記録します。

Unity:OpenCVを直接使う3

Unity は WebCamTexture でカメラ画像テクスチャを管理します。
OpenCV は Mat (行列) でカメラ画像情報を管理します。

目標は Unity の WebCamTexture を Mat に変換して、OpenCV の機能を存分に活用し
たとえば次のような MarkerlessAR Example の実行を行ってみたいと思います。

code/Chapter3_MarkerlessAR at master · MasteringOpenCV/code · GitHub

Unity でデバイスカメラから画像情報を毎フレーム取得する方法は1年前に記事で書いたことありました。
simplestar-tech.hatenablog.com

こちらの記事の

private void PlayDeviceCamera()

関数を参考にすると、画面をタップまたはスペースキーを押すたびにカメラを切り替えて ComputeShader による画像処理が試せます。

このカメラの使い方は複数のコンポーネントから共通して呼び出せた方がうれしいので、WebCamTexture を扱うコンポーネントを以下のように作り直してみました。

using System.Collections;
using UnityEngine;
using UnityEngine.Events;

public class WebCamHelper : MonoBehaviour {

    public string requestedDeviceName;
    public int requestedWidth = 640;
    public int requestedHeight = 480;
    public int requestedFPS = 60;
    public WebCamTexture webCamTexture { get; private set; }
    
    public UnityEvent onInitialized = new UnityEvent();
    public UnityEvent onDisposed = new UnityEvent();
    public UnityEvent onErrorOccurred;

    public void InitializeCamera()
    {
        StartCoroutine(CoInitializeCamera());
    }

    IEnumerator CoInitializeCamera()
    {
        if (hasInitDone)
            _Dispose();

        isInitWaiting = true;

        if (!System.String.IsNullOrEmpty(requestedDeviceName))
        {
            webCamTexture = new WebCamTexture(requestedDeviceName, requestedWidth, requestedHeight, requestedFPS);
        }
        else
        {
            // Checks how many and which cameras are available on the device
            for (int cameraIndex = 0; cameraIndex < WebCamTexture.devices.Length; cameraIndex++)
            {
                if (WebCamTexture.devices[cameraIndex].isFrontFacing == requestedIsFrontFacing)
                {

                    webCamDevice = WebCamTexture.devices[cameraIndex];
                    webCamTexture = new WebCamTexture(webCamDevice.name, requestedWidth, requestedHeight, requestedFPS);

                    break;
                }
            }
        }

        if (webCamTexture == null)
        {
            if (WebCamTexture.devices.Length > 0)
            {
                webCamDevice = WebCamTexture.devices[0];
                webCamTexture = new WebCamTexture(webCamDevice.name, requestedWidth, requestedHeight, requestedFPS);
            }
            else
            {
                isInitWaiting = false;

                if (onErrorOccurred != null)
                    onErrorOccurred.Invoke();

                yield break;
            }
        }

        // Starts the camera
        webCamTexture.Play();

        int initFrameCount = 0;
        bool isTimeout = false;

        while (true)
        {
            if (initFrameCount > timeoutFrameCount)
            {
                isTimeout = true;
                break;
            }
            else if (webCamTexture.didUpdateThisFrame)
            {

                Debug.Log("name " + webCamTexture.name + " width " + webCamTexture.width + " height " + webCamTexture.height + " fps " + webCamTexture.requestedFPS);
                Debug.Log("videoRotationAngle " + webCamTexture.videoRotationAngle + " videoVerticallyMirrored " + webCamTexture.videoVerticallyMirrored + " isFrongFacing " + webCamDevice.isFrontFacing);

                isInitWaiting = false;
                hasInitDone = true;

                if (onInitialized != null)
                    onInitialized.Invoke();

                break;
            }
            else
            {
                initFrameCount++;
                yield return 0;
            }
        }

        if (isTimeout)
        {
            webCamTexture.Stop();
            webCamTexture = null;
            isInitWaiting = false;

            if (onErrorOccurred != null)
                onErrorOccurred.Invoke();
        }
        if (null != onInitialized)
        {
            onInitialized.Invoke();
        }
        yield return 0;
    }

    /// <summary>
    /// To release the resources for the initialized method.
    /// </summary>
    protected virtual void _Dispose()
    {
        isInitWaiting = false;
        hasInitDone = false;

        if (webCamTexture != null)
        {
            webCamTexture.Stop();
            webCamTexture = null;
        }

        if (onDisposed != null)
            onDisposed.Invoke();
    }
    bool requestedIsFrontFacing = false;
    bool hasInitDone = false;
    bool isInitWaiting = false;
    int timeoutFrameCount = 300;
    WebCamDevice webCamDevice;
}

この WebCamHelper クラスの使い方は以下の通りですね。
Update の中で画像処理用の OpenCV につなげていきます。
このあたりの続きは次の記事で

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(WebCamHelper))]
public class UnityDeOpenCV : MonoBehaviour {

    public GameObject quad;

	void Start () {
        webCamHelper = GetComponent<WebCamHelper>();
        webCamHelper.onInitialized.AddListener(OnInitalizedCamera);
        webCamHelper.InitializeCamera();
    }
	
	void Update () {
        if (webCamHelper.webCamTexture && webCamHelper.webCamTexture.didUpdateThisFrame)
        {

        }	
	}

    void OnInitalizedCamera()
    {
        int width = webCamHelper.webCamTexture.width;
        int height = webCamHelper.webCamTexture.height;

        if (quad)
        {
            quad.GetComponent<Renderer>().material.mainTexture = webCamHelper.webCamTexture;
        }
    }

    WebCamHelper webCamHelper;
}