非同期化して高速化


今回画像処理があり、その高速化のためTaskを使って非同期化しました。
まずはカメラの画像を白黒にする普通のコード。だいたい僕のPCで20fps出るか出ないくらい。

using System;
using System.Collections;
using UnityEngine;

/// <summary>
/// 白黒カメラ。
/// </summary>
public class WBCamera : MonoBehaviour {
	/// <summary>
	/// テクスチャ。
	/// </summary>
	Texture2D texture;
	WebCamTexture webCamTex;

	/// <summary>
	/// ウェブカムの大きさ。
	/// </summary>
	int webCamW;
	int webCamH;

	/// <summary>
	/// カメラが初期化されたか。
	/// </summary>
	bool cameraInitialized = false;

	/// <summary>
	/// 全ピクセルのカラー。
	/// </summary>
	Color32[] colors;

	/// <summary>
	/// Start.
	/// </summary>
	void Start() {
		try {
			this.webCamTex = new WebCamTexture(WebCamTexture.devices[0].name, 1920, 1080, 30);
			this.webCamTex.Play();
			this.GetComponent<Renderer>().material.mainTexture = this.webCamTex;
		}
		catch(Exception e) {
			Debug.LogError("Camera didn't initialize. : " + e.Message);
		}

		this.StartCoroutine(this.WaitCameraInitializedCoroutine());
	}
	
	/// <summary>
	/// Updatea.
	/// </summary>
	void Update () {
		if (this.cameraInitialized) {
			Color32[] webCamColors = this.webCamTex.GetPixels32();
			int index = 0;

			if (this.cameraInitialized) {
				for (int x = 0; x < this.webCamW; ++x) {
					for (int y = 0; y < this.webCamH; ++y) {
						index = x + y * this.webCamW;
						Color32 webCamC = webCamColors[index];
						byte luminance = (byte)(0.298912f * webCamC.r + 0.586611 * webCamC.g + 0.114478 * webCamC.b);
						this.colors[index] = new Color32(luminance, luminance, luminance, 255);
					}
				}
			}

			this.texture.SetPixels32(this.colors);
			this.texture.Apply();
		}
	}

	/// <summary>
	/// カメラの初期化を待つ。
	/// </summary>
	IEnumerator WaitCameraInitializedCoroutine() {
		while (true) {
			if (this.webCamTex.width > 16 && this.webCamTex.height > 16) {
				print("WebCam Initialized : (" + this.webCamTex.width + ", " + this.webCamTex.height + ")");
				this.webCamW = this.webCamTex.width;
				this.webCamH = this.webCamTex.height;
				this.colors = new Color32[this.webCamW * this.webCamH];
				this.cameraInitialized = true;
				this.texture = new Texture2D(this.webCamW, this.webCamH, TextureFormat.RGBA32, false);
				this.GetComponent<Renderer>().material.mainTexture = this.texture;

				break;
			}

			yield return null;
		}
	}
}

続いてTaskを使って非同期化。Taskを使うには.Net4.6にしないといけなくて、その他もろもろ詳しいことは下記を参考に。
Qiita | Unity2017で始めるTask(async~await)

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections;
using UnityEngine;

/// <summary>
/// 白黒カメラ。
/// </summary>
public class WBCamera : MonoBehaviour {
	/// <summary>
	/// テクスチャ。
	/// </summary>
	Texture2D texture;
	WebCamTexture webCamTex;

	/// <summary>
	/// ウェブカムの大きさ。
	/// </summary>
	int webCamW;
	int webCamH;

	/// <summary>
	/// カメラが初期化されたか。
	/// </summary>
	bool cameraInitialized = false;

	/// <summary>
	/// 全ピクセルのカラー。
	/// </summary>
	Color32[] webCamColors;
	Color32[] colors;

	/// <summary>
	/// Start.
	/// </summary>
	void Start() {
		try {
			this.webCamTex = new WebCamTexture(WebCamTexture.devices[0].name, 1920, 1080, 30);
			this.webCamTex.Play();
			this.GetComponent<Renderer>().material.mainTexture = this.webCamTex;
		}
		catch(Exception e) {
			Debug.LogError("Camera didn't initialize. : " + e.Message);
		}

		this.StartCoroutine(this.WaitCameraInitializedCoroutine());
	}

	/// <summary>
	/// 白黒に変換。
	/// </summary>
	async Task ConvertToBWAsync() {
		int index = 0;
		SynchronizationContext context = SynchronizationContext.Current;

		await Task.Run(() => {
			if (this.cameraInitialized) {
				for (int x = 0; x < this.webCamW; ++x) {
					for (int y = 0; y < this.webCamH; ++y) {
						index = x + y * this.webCamW;
						Color32 webCamC = webCamColors[index];
						byte luminance = (byte)(0.298912f * webCamC.r + 0.586611 * webCamC.g + 0.114478 * webCamC.b);
						this.colors[index] = new Color32(luminance, luminance, luminance, 255);
					}
				}
			}

			context.Post(state => {
				if (this.texture != null) { // 破棄された際
					this.texture.SetPixels32(this.colors);
					this.texture.Apply();
					this.webCamColors = this.webCamTex.GetPixels32();
					this.ConvertToBWAsync();
				}
			}, null);
		});
	}

	/// <summary>
	/// カメラの初期化を待つ。
	/// </summary>
	IEnumerator WaitCameraInitializedCoroutine() {
		while (true) {
			if (this.webCamTex.width > 16 && this.webCamTex.height > 16) {
				print("WebCam Initialized : (" + this.webCamTex.width + ", " + this.webCamTex.height + ")");
				this.webCamW = this.webCamTex.width;
				this.webCamH = this.webCamTex.height;
				this.colors = new Color32[this.webCamW * this.webCamH];
				this.webCamColors = this.webCamTex.GetPixels32();
				this.cameraInitialized = true;
				this.texture = new Texture2D(this.webCamW, this.webCamH, TextureFormat.RGBA32, false);
				this.GetComponent<Renderer>().material.mainTexture = this.texture;

				this.ConvertToBWAsync();

				break;
			}

			yield return null;
		}
	}
}

Updateからの変換をやめて、Webカメラが初期化されたタイミングで非同期で行われる変換処理を実行。フレームレート自体は50fps以上出るようになった。実際の白黒の変換は秒間25回程度だったので、白黒の変換は多少早くなった程度だけどメインスレッドの足をひっぱることがなくなったのが大きい。
非同期処理はとても難しいようだけど、あまり全体に影響を及ぼさない程度のことであれば割と便利に使えて非常にいまやっているプロジェクトで助けられました。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です