OpenCV for Unityでカメラ映像を描画
実行結果
PCに付いているWebカメラ(インカメ)で撮影したもの。
リアルタイムでMatで取得し描画しているのでフィルタや認識などに転用も可能。
https://gyazo.com/1b660e0406e58777c7981023c57a6424
準備
カメラ機能を使いますのでカメラを用意してください。
カメラ付きPC、USBで外付けのWebカメラ、実機であればスマホ等のカメラでも可能です。
Scriptの追加
OpenCVでは画像をMatという形式で処理します。
ここではWebCamTextureをMatにしたり、MatをTexture2Dに変換してくれるCVCameraMat.csと、Matを受け取り加工処理をした後Texture2Dを画面に表示するSimpleScript.csを用意します。
プロジェクトごとに修正するのはSimpleScript.csになります。
WebCamTextureとはWebカメラの映像をUnityで扱えるテクスチャにしたものです。
CVCameraMat.cs
WebCamTextureを初期化しMatに変換、加工後にMatを渡すことでTexture2Dを返してくれるクラスです。
メソッド内の処理や役割はコメントアウトしております。
public変数の説明
requestDeviceName - 希望のカメラ端末名
flipVertical - trueの時、上下反転する
flipHorizontal - trueの時、左右反転する
OnInitedEvent - 初期化後に呼ぶイベントを設定できる
OnDisposedEvent - 破棄後に呼ぶイベントを設定できる
requestWidth - 希望の横サイズ
requestHeight - 希望の縦サイズ
requestWidth,requestHeightで指定している希望の縦サイズ,横サイズは、ソース内では(768,768)がデフォルトになっていますが、カメラ機器により受け取れる比率が違うのかそれに近い別のサイズが受け取れます。
code:CVCameraMat.cs
using UnityEngine;
using System.Collections;
using System;
using OpenCVForUnity;
using UnityEngine.Events;
public class CVCameraMat : MonoBehaviour
{
public string requestDeviceName = null;
public bool flipVertical = false;
public bool flipHorizontal = false;
public UnityEvent OnInitedEvent;
public UnityEvent OnDisposedEvent;
public int requestWidth = 768;
public int requestHeight = 768;
private WebCamTexture webCamTexture;
private WebCamDevice webCamDevice;
private Mat rgbaMat;
private Mat rotatedRgbaMat;
private Color32[] colors;
private bool initDone = false;
private ScreenOrientation screenOrientation = ScreenOrientation.Unknown;
/*------------------------------------------*
* Default Method
*------------------------------------------*/
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (initDone)
{
if (screenOrientation != Screen.orientation)
{
//角度が変わったので再度初期化
Init();
}
}
}
/*------------------------------------------*
* Init Method
*------------------------------------------*/
//設定せず初期化を開始
public void Init()
{
if (OnInitedEvent == null)
{
OnInitedEvent = new UnityEvent();
}
if (OnDisposedEvent == null)
{
OnDisposedEvent = new UnityEvent();
}
webCamInit ();
}
//設定して初期化を開始
public void Init(string deviceName, int requestWidth, int requestHeight)
{
this.requestDeviceName = deviceName;
this.requestWidth = requestWidth;
this.requestHeight = requestHeight;
Init();
}
//初期化
private void webCamInit()
{
//すでに初期化されている時は一旦解放する
if (initDone) {
Dispose ();
}
//カメラの希望があるかどうか確認する
if (!String.IsNullOrEmpty (requestDeviceName)) {
//使用できるカメラを参照する
for (int cameraIndex = 0; cameraIndex < WebCamTexture.devices.Length; cameraIndex++) {
//希望のカメラと同じカメラがあったらそれを使用する
if (webCamDevice.name == requestDeviceName) {
webCamTexture = new WebCamTexture (requestDeviceName, requestWidth, requestHeight);
}
}
}
//希望のカメラが無かった場合
if (webCamTexture == null) {
//一番最初に認識したカメラを使用する
if (WebCamTexture.devices.Length > 0) {
webCamDevice = WebCamTexture.devices 0; webCamTexture = new WebCamTexture (webCamDevice.name, requestWidth, requestHeight);
} else {
webCamTexture = new WebCamTexture (requestWidth, requestHeight);
}
}
//カメラの準備ができたかどうか確認
if (webCamTexture) {
//準備したカメラから映像の取得を開始する
webCamTexture.Play ();
//最初の撮影フレームを取得するまでコルーチンで待機
GameObject coroutineObj = new GameObject("waitWebCamTexture");
CVCameraMat coroutine = coroutineObj.AddComponent<CVCameraMat> ();
coroutine.StartCoroutine(waitWebCamFrame(coroutineObj));
} else {
//カメラの準備ができなかったので再度初期化する
webCamInit ();
}
}
//Webカメラの最初のフレームが取得できるまで待機
public IEnumerator waitWebCamFrame(GameObject coroutine)
{
while (true) {
if (webCamTexture) {
if (webCamTexture.isPlaying) {
if (webCamTexture.didUpdateThisFrame) {
rgbaMat = new Mat (webCamTexture.height, webCamTexture.width, CvType.CV_8UC4);
screenOrientation = Screen.orientation;
initDone = true;
if (OnInitedEvent != null) {
OnInitedEvent.Invoke ();
}
Destroy(coroutine);
break;
}
}
}
yield return new WaitForSeconds (1);
}
}
//初期化したかどうか
public bool isInited()
{
return initDone;
}
//破棄処理
public void Dispose()
{
initDone = false;
if (webCamTexture != null)
{
webCamTexture.Stop();
webCamTexture = null;
}
if (rgbaMat != null)
{
rgbaMat.Dispose();
rgbaMat = null;
}
if (rotatedRgbaMat != null)
{
rotatedRgbaMat.Dispose();
rotatedRgbaMat = null;
}
colors = null;
if (OnDisposedEvent != null)
OnDisposedEvent.Invoke();
}
/*------------------------------------------*
* WebCamTexture Method
*------------------------------------------*/
//WebCamTextureの撮影開始
public void Play()
{
if (initDone)
{
webCamTexture.Play();
}
}
//WebCamTextureの停止
public void Pause()
{
if (initDone)
{
webCamTexture.Pause();
}
}
//WebCamTextureの撮影終了
public void Stop()
{
if (initDone)
{
webCamTexture.Stop();
}
}
//WebCamTextureが初期化されているか
public bool isPlaying()
{
if (!initDone)
{
return false;
}
return webCamTexture.isPlaying;
}
//WebCamTextureを返す
public WebCamTexture GetWebCamTexture()
{
return webCamTexture;
}
//WebCamDeviceを返す
public WebCamDevice GetWebCamDevice()
{
return webCamDevice;
}
//WebCamTextureが最後のフレームから更新されているかを返す
public bool didUpdateThisFrame()
{
if (!initDone)
{
return false;
}
return webCamTexture.didUpdateThisFrame;
}
//WebCamTextureをMatに変換して返す
public Mat GetMat()
{
if (!initDone || !webCamTexture.isPlaying)
{
if (rotatedRgbaMat != null)
{
return rotatedRgbaMat;
}
else
{
return rgbaMat;
}
}
if (rgbaMat == null)
{
rgbaMat = new Mat(webCamTexture.height, webCamTexture.width, CvType.CV_8UC4);
}
Utils.webCamTextureToMat(webCamTexture, rgbaMat, colors);
int flipCode = int.MinValue;
if (webCamDevice.isFrontFacing)
{
if (webCamTexture.videoRotationAngle == 0)
{
flipCode = 1;
}
else if (webCamTexture.videoRotationAngle == 90)
{
flipCode = 0;
}
if (webCamTexture.videoRotationAngle == 180)
{
flipCode = 0;
}
else if (webCamTexture.videoRotationAngle == 270)
{
flipCode = 1;
}
}
else
{
if (webCamTexture.videoRotationAngle == 180)
{
flipCode = -1;
}
else if (webCamTexture.videoRotationAngle == 270)
{
flipCode = -1;
}
}
if (flipVertical)
{
if (flipCode == int.MinValue)
{
flipCode = 0;
}
else if (flipCode == 0)
{
flipCode = int.MinValue;
}
else if (flipCode == 1)
{
flipCode = -1;
}
else if (flipCode == -1)
{
flipCode = 1;
}
}
if (flipHorizontal)
{
if (flipCode == int.MinValue)
{
flipCode = 1;
}
else if (flipCode == 0)
{
flipCode = -1;
}
else if (flipCode == 1)
{
flipCode = int.MinValue;
}
else if (flipCode == -1)
{
flipCode = 0;
}
}
if (flipCode > int.MinValue)
{
Core.flip(rgbaMat, rgbaMat, flipCode);
}
if (rotatedRgbaMat != null)
{
using (Mat transposeRgbaMat = rgbaMat.t())
{
Core.flip(transposeRgbaMat, rotatedRgbaMat, 1);
}
return rotatedRgbaMat;
}
else
{
return rgbaMat;
}
}
/*------------------------------------------*
* OpenCV Support Method
*------------------------------------------*/
//MatをTexture2Dに変換して反映する
public void matToTexture2D(Mat mat, Texture2D texture)
{
Utils.matToTexture2D (mat, texture, colors);
}
}
SimpleScript.cs
CVCameraMat.csを実際に使用しているクラスです。
主にRawImageへ描画するTexture2Dの作成と紐付けを行なっています。
Start()でCVCameraMatの初期化
OnCVCameraMatInited()で初期化終了を受け取り、取得したMatに合わせたTexture2Dの作成とRawImageへの紐付け
Update()でMatを受け取り加工してTexture2Dへ更新
public変数の説明
rawImage - カメラ映像を描画するRawImage(Texture2Dが使えれば修正可)
code:SimpleScript.cs
using UnityEngine;
using UnityEngine.UI;
using OpenCVForUnity;
public class SimpleScript : MonoBehaviour {
public RawImage rawImage;
private CVCameraMat cvCameraMat;
private Texture2D texture;
private bool startCVCam = false;
/*--------------------------------
: Default Method
--------------------------------*/
// Use this for initialization
void Start()
{
Init();
}
// Update is called once per frame
void Update()
{
//カメラ画像の表示先があるか確認
if (rawImage) {
//カメラの取得準備ができているか確認
if (startCVCam) {
//CVCameraMatの初期化が終了している
if (cvCameraMat) {
//CVCameraMatが撮影中か確認
if (cvCameraMat.isPlaying ()) {
//CVCameraMatのフレームが更新されているか確認
if (cvCameraMat.didUpdateThisFrame ()) {
//Texture2Dが初期化されているか確認
if (texture != null) {
//カメラのMat画像を取得
Mat cvCamMat = cvCameraMat.GetMat ();
//Matが取得できているか確認
if (cvCamMat != null) {
//Matが空のデータじゃないか確認
if (!cvCamMat.empty ()) {
/* 画像加工開始 */
/* 画像加工終了 */
//加工したMatが存在するか確認
if (cvCamMat != null) {
//Matが空のデータじゃないか確認
if (!cvCamMat.empty ()) {
try {
//cvCamMatをTexture2Dに変換して反映する
cvCameraMat.matToTexture2D (cvCamMat, texture);
} catch (System.ArgumentException e) {
Debug.Log (e.Message);
} catch {
Debug.Log ("OtherError");
}
}
}
}
cvCamMat = null;
}
}
}
}
}
}
} else {
Debug.LogError("NotFound:rawImage");
}
}
/*--------------------------------
: Load Method
--------------------------------*/
//CVCameraMatを起動させる
private void Init()
{
startCVCam = false;
//カメラ画像の表示先があるか確認
if (rawImage) {
//cvCameraMatを取得していなければ取得
if (cvCameraMat == null) {
cvCameraMat = GetComponent<CVCameraMat> ();
}
if (cvCameraMat != null) {
if (cvCameraMat.isInited ()) {
//初期化している
//カメラの状態を確認
if (!cvCameraMat.isPlaying ()) {
//カメラの撮影が止まっている
//撮影開始
cvCameraMat.Play ();
} else {
//カメラは撮影中
//Texture2Dが初期化されているか確認
if (texture != null) {
//初期化されている
startCVCam = true;
} else {
//初期化されていない
//Texture2Dを初期化
OnCVCameraMatInited();
}
}
} else {
//初期化していない
//初期化して起動
cvCameraMat.Init();
}
}
} else {
Debug.LogError("NotFound:rawImage");
}
}
/*--------------------------------
: CVCameraMat Method
--------------------------------*/
//CVCameraMatが初期化された時
public void OnCVCameraMatInited()
{
if (rawImage) {
//CVMat読み込み待ち
bool loadMatFlag = false;
Mat cvCamMat = new Mat ();
while (!loadMatFlag) {
if (cvCameraMat) {
if (cvCameraMat.isPlaying ()) {
if (cvCameraMat.didUpdateThisFrame ()) {
cvCamMat = cvCameraMat.GetMat ();
if (cvCamMat != null) {
if (!cvCamMat.empty ()) {
loadMatFlag = true;
}
}
}
}
}
}
//CVMatをTextureにセット
if (loadMatFlag) {
//Texture2Dの作成
texture = new Texture2D ((int)cvCamMat.cols (), (int)cvCamMat.rows (), TextureFormat.RGBA32, false);
if (texture) {
//RawImageへTexture2Dを設定(表示されない時はrawImage.material.mainTexture)
rawImage.texture = texture;
startCVCam = true;
} else {
Init ();
}
}
} else {
Debug.LogError("NotFound:rawImage");
}
}
//CVCameraMatが解放された時
public void OnCVCameraMatDisposed()
{
startCVCam = false;
texture = null;
}
}
Hierarchyの構成
一応動作する状態のHierarchyの構成がどうなっているかを説明します。
サンプルとしてUnity2Dで動作させています。
Canvas,Camera,EventSystemは標準のままです。
https://gyazo.com/6176f0e6055c4f0fa305e67c4b4a304a
CameraMaskはRectMask2Dのコンポーネントを追加したGameObjectです。
カメラ映像を画面に表示したいサイズで設定してください。
https://gyazo.com/cbb6a60cb6754011ab7b69cce14979cb
CameraImageはカメラ映像を描画するRawImageです。
今回使ったカメラは横長に取得できるものだったので縦サイズはCameraMaskと同じサイズで横サイズを少し長くしました。
https://gyazo.com/02e1f6366dfabe57c771119cd4d8a56c
CameraImageにCVCameraMat.csとSimpleScript.csを追加してください。
CVCameraMat.csのOnInitedEventとOnDisposedEventの設定はSimpleScript.csで通知を受け取るのに必須です。
SimpleScript.csのRawImageは自身であるCameraImageを設定します。
https://gyazo.com/849d707120f51e83fa0db1d8a8956cbc