おもちゃラボ

Unityで遊びを作っていきます

【Unity】M1 MacBook AirでUnityのビルド時間を測ってみた

M1 MacBook Airを購入したのでUnityのiOS/Androidのビルド時間やSwitch Platformをしたときの時間を計測してみました。今回、計測&比較に使用したPCのスペックは次のとおりです。

PC名 M1 MacBook Air iMac
CPU M1 4Core+4Core Core i5 QuadCore 3.2GHz
GPU M1 Radeon R9 M380
メモリ 8GB 24GB

計測結果

実験はUnity2021.1.6で3Dのプロジェクトを作成した直後の状態(空のプロジェクト)で行っています。

Switch Platformの時間はTarget platformをMac→iOSの変更にかかった時間を測っています。また、iOSビルド・Androidビルド・Xcodeビルドについても空のプロジェクトをビルドした結果です。

PC名 M1 MacBook Air iMac
Switch Platform 2分40秒 0分42秒
iOSビルド 1分5秒 1分12秒
Androidビルド 1分40秒 1分33秒
Xcodeのビルド 0秒18秒 1分20秒

続・計測結果(2021年6月追記)

Unity 2021.2.0a19のPreviewパッケージでついにApple Silicon対応のUnityエディタがリリースされました!

forum.unity.com

それぞれのプラットフォームのビルド結果を測定してみたので追記しておきます。

PC名 M1 MacBook Air
Switch Platform 0分14秒
iOSビルド 0分29秒
Androidビルド 1分02秒

iOSビルドが2.3倍、Androidビルドが1.5倍、Switch Platoformが2分40秒→14秒と12.8倍の高速化!!(笑) Apple Siliconに対応したエディタだとビルドが爆速になりました。ということでApple SiliconのMacを買おう。

考察

M1 MacとIntel Macでメモリの差はあるので単純比較はできないですが、Unityのビルド時間は思ったほど速くなかったですね。。。

Unity 2021.1.6のUnity Editorは Apple Silicon にネイティブ対応していなくて、RosettaでIntelの命令コードをApple Sliconの命令コードへとバイナリを変換するオーバーヘッドが生じます。これがM1 MacでUnityを動かしたときに時間のかかる原因でしょう。

Xcodeのビルド時間はかなり短縮されているので、Unity2021.2でUnityエディタがApple Sliiconにネイティブ対応すれば、かなり高速になるんじゃないかなぁ、と期待しています!
forum.unity.com

まとめ

Unity2021.1.6時点では、M1 MacでのUnityのビルド時間はそこまで速くない!

UnityエディタがApple Siliconに対応(2021.2.0a19)したら爆速になった!

【Unity】これだけは覚えておきたいRectTransformの基本

UnityのUIを作るときに使うuGUIですが、このuGUIを動かしたいときに使う座標系ってちょっと分かりにくくないですか?uGUIの座標系についてはあやふやなまま使っているという方も多いかと思います。

そこで、ここではuGUIで座標を扱うときに、最低限押さえて置かなければいけないポイントをまとめておこうと思います。CanvasのRender Modeの設定はデフォルトの「Screen Space - Overlay」になっている前提で説明しますね。

RectTransformについて

UIで座標を指定するにはTransformではなくRectTransformを使用します。RectTransformのメンバには「position」と「anchoredPosition」の2種類が用意されています。

f:id:nn_hokuson:20210512072509p:plain

RectTransformを使うときは、この2つのメンバがあることを意識して、きちんと使い分けられるようになればOKです!

positionの場合

画面の解像度を1280x720に設定した場合、RectTransformのpositionを使った座標系では画面左下が(0,0)、右上が(1280, 720)になります。
f:id:nn_hokuson:20210511231403p:plain:w500

anchoredPositionの場合

一方、anchoredPositionを使った座標系では、インスペクタで設定したアンカーが原点になります。例えばアンカーをセンターに指定した場合、画面の座標は次のように-640 < x < 640、-360 < y < 360の範囲になります。

f:id:nn_hokuson:20210511231416p:plain:w500

インスペクタで座標を設定する

さて、ではインスペクタで設定する値はpositionとanchoredPositionのどちらでしょうか?(すぐに答えを言っちゃうんですけど)インスペクタに表示されている値はanchoredPositionになります!このあたりがあやふやだと混乱するので注意してください。

f:id:nn_hokuson:20210511232246p:plain

スクリプトから値を設定する

スクリプトからuGUIの座標を設定するには、ゲームオブジェクトにアタッチされているRectTransformコンポーネントを取得して、そのメンバであるpositionかanchoredPoistionに値を設定します。

// positionを使って設定する。
// この場合、positionに(0,0)を設定しているので画面左下に表示される
GetComponent<RectTransform>().position = Vector3.zero;

// anchoredPositionを使って設定する
// この場合、anchoredPositionに(0,0)を設定しているのでアンカーの位置に表示される
GetComponent<RectTransform>().anchoredPosition = Vector3.zero;

ワールド座標系をuGUIの座標系に変換する

ゲーム中のオブジェクトの上に、何かUIで情報を表示したい場合には「ワールド座標」→「スクリーン座標」の変換ができると便利です。この変換のため、UnityにはRectTransformUtility.WorldToScreenPointというメソッドが用意されています。

f:id:nn_hokuson:20210512143315p:plain:w550

使い方は次のとおりです。WorldToScreenPointの戻り値はRectTransformのpositionに代入します。anchoredPositionではないので注意しましょう。

//Utility.WorldToScreenPointを使って座標変換をする
GetComponent<RectTransform>().position 
= RectTransformUtility.WorldToScreenPoint (Camera.main, player.transform.position);

まとめ

uGUIの座標系にはpositionとanchoredPositionがあります。positionは画面左下が原点になり、anchoredPositionはインスペクタで設定したアンカーが原点になります。

OpenCV plus Unityサンプル集 20選

UnityでOpenCVを使いたい場合には「OpenCV for Unity」と「OpenCV plus Unity」の2種類のアセットがあります。OpenCV for Unityは有償($104)でメンテナンスもこまめに行われている一方、APIが独特でpythonやC++のOpenCVに慣れている人には少し使いにくいイメージです。

一方、OpenCV plus Unityは オープンソースのOpenCVSharpをUnity用にカスタムした無償のアセットです。APIの形式も他言語のものと似ていて比較的使いやすいため、この記事ではOpenCV plus Unityを使ったサンプルを紹介します。

ここで紹介するOpenCVのサンプルは以下のとおりです。

下準備

Asset StoreからUnity plus OpenCVをダウンロードして、プロジェクトにimportします。

assetstore.unity.com

インポート直後はエラーが表示されます。メニューバーからEdit→Project Settingsを選択して、「Allow unsafe code」のチェックボックスにチェックを入れて下さい。

f:id:nn_hokuson:20210422222024p:plain:w450

OpenCVで処理した画像を表示するため、UIのRawImageを使います。ヒエラルキーウインドウで「+」→「UI」→「Raw Image」を選択して下さい。表示する画像を画面サイズに合わせるため、RawImageにCameraScalerスクリプトをアタッチしておきます。
CameraScalerスクリプトはAssets/OpenCV+Unity/Demo/Scriptsの中にあります。

f:id:nn_hokuson:20210422222411p:plain:w300

これで下準備は完了です。次に新規でスクリプトを作成して、OpenCVで画像を表示していきましょう。

画像の読み込み

まずは画像をTexture2D型として読み込み、それを一旦OpenCVのMatに変換して、最後にもう一度Texture2D型に戻して表示するサンプルです。

次のTestスクリプトを作成して、上で作成したRawImageのオブジェクトにアタッチして下さい。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using OpenCvSharp.Demo;
using OpenCvSharp;
using OpenCvSharp.Aruco;

public class Test : MonoBehaviour
{
    public Texture2D texture;

    void Start()
    {
        // 画像読み込み
        Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

        // 画像書き出し
        Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
        OpenCvSharp.Unity.MatToTexture(mat, outTexture);

        // 表示
        GetComponent<RawImage>().texture = outTexture;
    }
}

スクリプトをRaw Imageにアタッチできたら、インスペクタからtexture変数に表示したい画像を指定します。このときOpenCVが画像のピクセルにアクセスできるように事前に設定する必要があります。表示したい画像を選択してインスペクタから「Read/Write Enable」にチェックを入れて下さい。

f:id:nn_hokuson:20210422223640p:plain:w350

表示したい画像をインスペクタからTestスクリプトのtexture変数にセットしたら実行してみて下さい。ゲームビューに画像が表示されればOKです。

f:id:nn_hokuson:20210422224515j:plain:w500

グレースケール化

カラー画像を読み込んで、グレースケール化するサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// 画像書き出し
Texture2D outTexture = new Texture2D(gray.Width, gray.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(gray, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

画像をグレースケールに変換するにはCvtColorメソッドを使います。第三引数に指定する値によって変換する形式を選択できます。よく使う値は次のとおりです。

意味
RGB2GRAY RGB画像をグレースケールに変換
RGB2HSV RGBをHSV形式に変換
RGBA2RGB 透明チャネルを削除する
RGB2GBR RとBチャネルを入れ替える

実行結果は次のようになります。
f:id:nn_hokuson:20210423080015j:plain:w500

画像を2値化する

カラー画像を読み込んで、しきい値よりも明るいピクセルは白、暗いピクセルは黒にする2値化のサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// 2値化
Mat bin = new Mat();
Cv2.Threshold(gray, bin, 127, 255, ThresholdTypes.Binary);

// 画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(bin, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

画像を2値化するにはThresholdメソッドを使います。第3引数にしきい値、第5引数には変換形式を設定します。第5引数でよく使う値には次のものがあります。

意味
Binary 白黒に変換
Binary 白黒反転して変換
Otsu 大津の2値化アルゴリズムで変換

実行結果は次のようになります。
f:id:nn_hokuson:20210423080900j:plain:w500

画像から輪郭検出する

2値化した画像から輪郭を抽出するサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// 2値化
Mat bin = new Mat();
Cv2.Threshold(gray, bin, 127, 255, ThresholdTypes.Binary);

// 輪郭抽出
Point[][] contours;
HierarchyIndex[] hierarchy;
Cv2.FindContours(bin, out contours, out hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxNone, null);

foreach (Point[] contour in contours)
{
    // 面積
	double area = Cv2.ContourArea(contour);

	// 重心
	Moments m = Cv2.Moments(contour);
	int cx = (int)(m.M10 / m.M00);
	int cy = (int)(m.M01 / m.M00);

	Cv2.DrawContours(mat, new Point[][] { contour }, 0, new Scalar(0, 0, 255), 8);
}

// 画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(mat, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture; 

輪郭抽出をするには、画像を2値化してからFindContoursメソッドを使用します。第5引数にExternalを指定した場合は最外周の輪郭のみ検出します。一方、Treeを指定した場合は入れ子になった輪郭も抽出します。

意味
External 最外周の輪郭のみを検出
Tree 内側の輪郭も検出

実行結果は次のようになります。
f:id:nn_hokuson:20210423081500j:plain:w500

画像を射影変換する

画像を射影変換するサンプルです。射影変換とは四角形の頂点をつまんで好きな形に変形するイメージです。例えば撮影した画像に含まれるQRコード部分だけを取り出して、正方形に変換する場合などに使用します。

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

Vector2[] srcData = new Vector2[]
{
	new Vector2(0, 0),
	new Vector2(mat.Width, 0),
    new Vector2(mat.Width, mat.Height),
    new Vector2(0, mat.Height)
};
Vector2[] dstData = new Vector2[]
{
	new Vector2(100, 50),
	new Vector2(400, 200),
	new Vector2(440, 350),
	new Vector2(0, 500)
};
Mat srcMat = new Mat(4, 1, MatType.CV_32FC2, srcData);
Mat dstMat = new Mat(4, 1, MatType.CV_32FC2, dstData);
Mat T = Cv2.GetPerspectiveTransform(srcMat, dstMat);

Mat dst = new Mat();
Cv2.WarpPerspective(mat, dst, T, new Size(mat.Width, mat.Height));

// 画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(dst, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

射影変換するにはGetPerspectiveTransformメソッドを使い、入力座標を出力座標に射影するための行列を求めます。次にWarpPerspectiveメソッドに計算した射影行列を指定して画像を変換します。

実行結果は次のようになります。
f:id:nn_hokuson:20210423082435j:plain:w500

座標の射影変換

画像の射影変換では出力結果は射影変換した後の画像でした。座標自体を変換したい場合にはこちらのスクリプトを使用します。

Vector2[] srcData = new Vector2[]
{
	new Vector2(100, 100),
	new Vector2(300, 120),
	new Vector2(300, 280),
	new Vector2(120, 300),
};
Vector2[] dstData = new Vector2[]
{
	new Vector2(0, 0),
	new Vector2(1, 0),
	new Vector2(1, 1),
	new Vector2(0, 1),
};
Mat srcMat = new Mat(4, 1, MatType.CV_32FC2, srcData);
Mat dstMat = new Mat(4, 1, MatType.CV_32FC2, dstData);

// 射影変換行列
Mat T = Cv2.GetPerspectiveTransform(srcMat, dstMat);

Point2f[] srcPos = { new Point2f(300, 280) };
Point2f[] dstPos = Cv2.PerspectiveTransform(srcPos, T);
Debug.Log(dstPos[0].X + "," + dstPos[0].Y);

GetPerspectiveTransformメソッドを使って射影変換行列を求めるところまでは同じです。座標自体を変換するにはPerspectiveTransformメソッドを使います。PerspectiveTransformには入力座標の配列と射影変換行列を指定し、戻り値として出力座標の配列を受け取ります。

ガウシアンブラーでぼかす

ガウシアンブラーを使って画像をぼかすサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// ぼかし
Mat blur = new Mat();
Cv2.GaussianBlur(mat, blur, new Size(11, 11), 0);

// 画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(blur, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture; 

OpenCVでガウシアンブラーをかけるにはGaussianBlurメソッドを使います。第3引数に渡すフィルタのサイズを大きくすればするほど、よりボケた画像になります。

実行結果は次のようになります。
f:id:nn_hokuson:20210423082929j:plain:w500

画像の膨張処理

白色のピクセルを周囲8方向に膨張させる処理を行うサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// 2値化
Mat bin = new Mat();
Cv2.Threshold(gray, bin, 127, 255, ThresholdTypes.Binary);

// 膨張処理
Mat dst = new Mat();
Mat k = new Mat();
Cv2.Dilate(bin, dst, k, null, 3);

// 画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(dst, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

膨張処理を行うにはDilateメソッドを使用します。第5引数には何回膨張処理を繰り返すかを指定します。この値が大きければ大きいほど白色ピクセルの膨張量が大きくなります。また、逆に白色のピクセルを縮小させたい場合はCv2.Erodeメソッドを使います。引数の指定方法はDilateとほぼ同じです。

実行結果は次のようになります。
f:id:nn_hokuson:20210423201247j:plain:w500

Sovelフィルタを使う

縦方向や横方向のエッジを検出できるSovelフィルタを使うサンプルです。

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// Sovelフィルタ
Mat sobel = new Mat();
Cv2.Sobel(gray, sobel, MatType.CV_8UC1, 0, 1);

// 書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(sobel, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

OpenCVでSobelフィルタを使うにはSobelメソッドを使います。第3引数には入力画像の形式を指定します。第4と第5引数で検出するエッジの方向を指定します。(1, 0)であれば縦方向、(0, 1)であれば横方向のエッジを検出します。

下図(左)は縦方向のエッジを検出、下図(右)は横方向のエッジを検出したものです。
f:id:nn_hokuson:20210425073130j:plain:w320 f:id:nn_hokuson:20210425073153j:plain:w320

ハイパスフィルタを使う

ハイパスフィルタを使って画像中から輝度値の変化が大きい領域のみを抽出するサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);
		
// ハイパスフィルタのカーネル
double[] data = { -1, -1, -1,
                    -1,  8, -1,
                    -1, -1, -1 };
Mat kernel = new Mat(3, 3, MatType.CV_64FC1, data);

// ハイパス
Mat dst = new Mat();
Cv2.Filter2D(gray, dst, MatType.CV_8UC1, kernel);
  
// 画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(dst, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

OpenCVにはハイパスフィルタに使えるメソッドは用意されていません。そこでハイパスフィルタのカーネルを定義して、Filter2Dメソッドを使って畳み込み演算を行います。カーネルの値はマイナスの値も含まれるので、MatTypeにはCV_64FC1を指定していることに注意して下さい。

実行結果は次のようになります。
f:id:nn_hokuson:20210423201627j:plain:w500

論理演算を使ってマスクする

OpenCVで論理演算をするサンプルです。

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// 黒色に白丸のマスク(カラー画像の場合はmaskも3chぶん用意する)
Mat mask = new Mat(mat.Width, mat.Height, MatType.CV_8UC3, new Scalar(0,0,0));
Cv2.Circle(mask, new Point(mat.Width / 2, mat.Height / 2), 200, new Scalar(255,255,255), -1);

// 論理演算
Mat dst = new Mat();
Cv2.BitwiseAnd(mat, mask, dst);

// 書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(dst, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

OpenCVの論理演算にはBitwiseAnd、BitwiseOr、BitwiseNot、BitwiseXOrが用意されています。1chのグレースケール画像に対して論理演算をする場合はマスクも1chで大丈夫ですが、3chのカラー画像に対して論理演算をする場合はマスク画像も3chぶん用意する必要があります。カラー画像に対して1chのマスクを使うとRレイヤのみマスクされてしまいます。

実行結果は次のようになります。
f:id:nn_hokuson:20210424063000j:plain:w500

画像のレイヤを分ける

複数チャネルを持つ画像をレイヤごとに分割するサンプルです。

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// 各レイヤを取り出す
Mat[] rgba = Cv2.Split(mat);

// Rレイヤを書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(rgba[2], outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture; 

複数レイヤを分割するにはSplitメソッドを使用します。戻り値はMatの配列になります。また、逆に各レイヤを1つの画像に合成する場合はMergeメソッドを使います。

実行結果は次のようになります。
f:id:nn_hokuson:20210425072655j:plain:w500

画像を左右反転する

OpenCVで読み込んだ画像を左右反転するサンプルです。

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// 左右反転
Mat dst = new Mat();
Cv2.Flip(mat, dst, FlipMode.Y);

//画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(dst, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

画像を左右反転するにはFlipメソッドを使います。第3引数で縦方向に反転するか、横方向に反転するかを選ぶことができます。

実行結果は次のようになります。
f:id:nn_hokuson:20210424063128j:plain:w500

図形を描画する

OpenCVで円や四角形を描画するサンプルです。

Mat mat = new Mat(512, 512, MatType.CV_8UC3, new Scalar(30, 30, 30));

// 円
Cv2.Circle(mat, new Point(100, 100), 50, new Scalar(255, 0, 0), 3, LineTypes.AntiAlias);

// 円塗りつぶし
Cv2.Circle(mat, new Point(300, 100), 50, new Scalar(255, 0, 0), -1, LineTypes.AntiAlias);

// 四角形
Cv2.Rectangle(mat, new OpenCvSharp.Rect(50, 180, 300, 100), new Scalar(0, 0, 255), 3, LineTypes.AntiAlias);

//画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(mat, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

OpenCVで円を描画するにはCircleメソッドを使用します。第2引数に円の中心座標、第3引数に半径、第5引数に線の太さを指定します。線の太さをマイナスの値にすると図形の内部が塗りつぶされます。また、第6引数のLineTypesにAntiAliasを指定することで、ギザギザのないなめらかな図形を描画できます。

OpenCVで四角形を描画するにはRectangleメソッドを使います。第2引数のRectメソッドで左上座標と幅、高さを指定します。塗りつぶしやLineTypesはCircleメソッドと同様です。

実行結果は次のようになります。
f:id:nn_hokuson:20210423202108j:plain:w500

Arucoマーカーを作成する

OpenCVにはARマーカーを検出するためのArucoライブラリが付属しています(実際にはOpenCV Contribモジュールに含まれているものです)。次のスクリプトはArucoマーカーを作成するためのサンプルです。

Dictionary dictionary = CvAruco.GetPredefinedDictionary(PredefinedDictionaryName.Dict6X6_250);

int size = 150;
Mat mat = new Mat();
CvAruco.DrawMarker(dictionary, 5, size, mat);

Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(mat, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

作成するARマーカーはGetPredefinedDictionaryの引数で指定します。ここではDict6X6_250を指定しているので6セルx6セルのサイズのマーカー(外側の黒枠も入れると8セルx8セル)を250個まで生成できます。実際に生成するにはDrawMarkerメソッドを使用します。このDrawMarkerメソッドの第2引数で250個のうち何番目のマーカーを生成するかを指定します。

作成したマーカーは次のようになります。
f:id:nn_hokuson:20210423202957p:plain:w200

Arucoマーカーを検出する

上で生成したArucoマーカーを撮影した画像中から検出するサンプルです。

DetectorParameters detectorParameters = DetectorParameters.Create();

Dictionary dictionary = CvAruco.GetPredefinedDictionary(PredefinedDictionaryName.Dict6X6_250);

Point2f[][] corners;
int[] ids;
Point2f[][] rejectedImgPoints;

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール化
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// Arucoマーカーの検出
CvAruco.DetectMarkers(gray, dictionary, out corners, out ids, detectorParameters, out rejectedImgPoints);
CvAruco.DrawDetectedMarkers(mat, corners, ids);

//画像書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(mat, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

Arucoマーカーを検出するにはDetectMarkersメソッドを使用します。出力結果として、マーカーの四隅の座標とマーカーのIDを受け取ることができます。この情報を使ってマーカーの姿勢を推定することもできます。姿勢推定の手法はコチラの記事が詳しいです。

qiita.com

OpenCVでピクセルにアクセスする

ピクセルにアクセスして画像の左上だけ白色に塗りつぶすサンプルです。

Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

unsafe
{
	byte* data = mat.DataPointer;
	int width = mat.Width;
	int height = mat.Height;
	int channel = mat.Channels();

	for (int y = 0; y < height/4; y++)
	{
		for (int x = 0; x < width/4 ; x++)
		{
			int idx = (x + y * width) * channel;
			data[idx+0] = 255;
			data[idx+1] = 255;
			data[idx+2] = 255;
		}
	}
}

// 書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(mat, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

OpenCVのMatクラスにはAtメソッドが用意されていて、これでもピクセル(画素)にアクセスすることはできます。ただ、このメソッドは呼び出しに時間がかかるため、全ピクセルを処理するには向いていません。高速にピクセルにアクセスするためには、DataPointerで取得できるポインタを使います。ポインタを使う箇所はunsafeブロックで囲む必要があります。

ピクセルの座標を(x,y)、画像の幅と高さを(width, height)、チャネル数をchannelとすると、そのピクセルのインデックスは (x + y * width) * channelで調べられます。

実行結果は次のとおりです。
f:id:nn_hokuson:20210425074450j:plain:w500

顔検出をする

画像中から顔を検出して、その位置を表示するサンプルです。

// 画像読み込み
Mat mat = OpenCvSharp.Unity.TextureToMat(this.texture);

// グレースケール
Mat gray = new Mat();
Cv2.CvtColor(mat, gray, ColorConversionCodes.BGR2GRAY);

// カスケード分類器の準備
CascadeClassifier haarCascade = new CascadeClassifier("Assets/OpenCV+Unity/Demo/Face_Detector/haarcascade_frontalface_default.xml");

// 顔検出
OpenCvSharp.Rect[] faces =  haarCascade.DetectMultiScale(gray);

// 顔の位置を描画
foreach (OpenCvSharp.Rect face in faces)
{
	Cv2.Rectangle(mat, face, new Scalar(0, 0, 255), 3);
}

// 書き出し
Texture2D outTexture = new Texture2D(mat.Width, mat.Height, TextureFormat.ARGB32, false);
OpenCvSharp.Unity.MatToTexture(mat, outTexture);

// 表示
GetComponent<RawImage>().texture = outTexture;

OpenCVで顔検出をするにはカスケード分類器を使います。カスケード分類器のパラメータはOpenCV plus Unityの/OpenCV+Unity/Demo/Face_Detector/に含まれているので、CascadeClassifierのコンストラクタでパスを指定して読み込みます。顔検出はDetectMultiScaleメソッドを使います。検出した顔の領域は、DetectMultiScaleメソッドの戻り値としてRect型の配列で受け取ります。

実行結果は次のようになります。
f:id:nn_hokuson:20210425074942j:plain:w500