simplestarの技術ブログ

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

AWS:AMIの作り方3-OpenSSLでX.509 証明書

Amazon のマシンイメージ作りたいだけなんだよ、なんで IAM とか SSL の話ばっかりになるんだよもう

X.509 証明書ってなんですか
なんで 509 なの?
なんで X. なの?

ITU という国際機関が定めたデジタル証明書の標準仕様
X.509は仕様の管理番号とみる
今日ではRFC 5280だそうだ(Request for Commentの5280の仕様を満たす証明システム)

現在ではバージョン3が使用されている
公開鍵とその公開鍵の持ち主の対応関係を保証するための電子署名として機能

CAによって割り当てられた正の整数
発行者が証明書に署名する際に用いるアルゴリズムハッシュ関数MD5SHA-1、SHA-2など。署名アルゴリズムRSA、DSA、ECDSAなど)

OpenSSL と X.509 証明書の関係は?
SSLはセキュアなソケットレイヤーのことで、公開鍵を使った暗号化基盤 PKI を提供するソフトウェアライブラリとして OpenSSL がある
OpenSSL は X.509 はもちろん、SSL に関する機能を提供してくれるとみた(つか X.509 または RFC5280の仕様以外のデジタル署名機能を OpenSSLはサポートしているのか?知らん )

コマンドラインツールとして無料で入手可能で、パスを通しておけばコマンドから適当な秘密鍵を作ったり、その秘密鍵から公開鍵を作ったりできる

よーし、じゃ OpenSSL をダウンロードしてコマンドを使えるようにして
RSA アルゴリズム秘密鍵、公開鍵をコマンドから作る書式を確認してファイル作ればそれが X.509 証明書ってことでいい?

Open SSL 入手先
https://www.openssl.org/

The 1.0.2 series is our Long Term Support (LTS) release, supported until 31st December 2019. とのことで、1.0.2 を手に入れてみた
Windows で動くバイナリ入っているのかなこれ?

入っていない
OpenSSLのソースファイルのみが配布されている。そこからWindows用にコンパイルするのは(開発者でない限り)ハードルが高い
じゃあ
「Shining Light Productions」ここから Windows 版を入手する
Shining Light Productions - Win32 OpenSSL

あれ?
1.1.0 の新しい OpenSSL のインストーラがある
以下の手順を参考に openSSL コマンドを利用できるようにパスまで通しました。
www.atmarkit.co.jp

今は openssl コマンドを打てます。
そこで、次のAWS の公式ドキュメントの説明の通り X.509 証明書を自己署名で 365 日期限で作ってみました。
docs.aws.amazon.com

あれ?
IAMのユーザーごとに設定できる認証情報には SSH 公開鍵のアップロードとあるけど
X.509 証明書と SSH 公開鍵って同一のみなせるのだっけ?


>X.509 証明書に記述された公開鍵

なるほど、同一か
証明書の中身をユーザーの認証情報に貼り付けておくことにした

あ、れ?
無効な SSH publick key だってさ

以下を参考に、手順を洗います。(エラー去ってまたエラー)
docs.aws.amazon.com

ふっふっふ。
なんと自己署名の手順を踏む必要なかったみたいですね。

openssl の以下のコマンドで作った publickey を IAM ユーザーの SSL 公開鍵として登録することに成功しました。

openssl rsa -pubout < privatekey.pem > publickey.pem

具体的には publickey.pem のファイルの中身をテキストコピーして、コンソール画面から貼り付けるだけ
これで invalid publick key のエラーが出ないことを確認しました。

この IAM ユーザーが SSL 通信する際は、元となる秘密鍵があればうまくいくはずです。(たぶん)

これで X.509 証明書をユーザーに設定する作業も完了だぜ(実際はただの公開鍵でしたが…周辺知識も少しは埋まったってもんだ)
IAM作成のための手順は次に進みます。

AWS:AMIの作り方2-IAM ユーザー作成

AMIというAmazon マシンイメージを適当に作りたいだけなのに、やれ権限だのマニフェストファイルを用意しておけなど
なんで一発で作らせないんだこのやろうと思うこのごろ

さて、まずはアクセスキー情報を作るために、IAM ユーザー作成が求められるようになっています。
仕方ないので IAM ユーザー作成の書式から学ぶことにしました。

AWSってサービス一覧を見ると画面を埋め尽くす勢いで項目が並びますが、ここからセキュリティうんたらの先頭付近にある項目
IAM (個別のアクセス管理)の項目を選択
さらにサブメニューにグループ→ユーザー→ロール→ポリシーなどが現れるので、ここのメニューから新規作成を行っていきます。

迷ったらもう一度ベストプラクティスを最初から読み直します。
docs.aws.amazon.com

確かにルートユーザーのアクセスキーをロックして、IAMユーザー作れってある
具体的にIAMユーザー作る手順はこちらと示される
docs.aws.amazon.com
仕方ないから全部読んで、手順通りにユーザーとグループを作成してみることにした。

一応 IAM ダッシュボードで 5つのステップの操作をこなして全部緑チェックの入った状態になりました。
IAMユーザーと管理者グループの作成は完了したことになります。

X.509 証明書?
OpenSSL で作成できるので、作った IAM ユーザーの認証情報に SSL 公開鍵をアップロードすることもあるだろう
https://www.openssl.org/

次 Open SSL の証明書とか公開鍵の話書きます。

AWSのAMIの作り方

Amazon のマシンイメージの略字として AMI というものがあるのですけど、要はどの OS で、どの CPU でコア数はいくつか、どんな感じのストレージにするのかなどを指定するわけです。

UIを直感的に操作して AMI を作れると思ったのですが、初めて登録するときにAMIマニフェストパスを求められるのです。
https://s3.amazonaws.com:443/~ の下にあるアレだよアレ 的な感じで

知らんがな、AMIマニフェストパスってなんぞや?

こちらの記事を参考に調べてみた
AWS: AMI の作り方(Amazon Machine Image)

証明書とかの情報を得るのに、まずはIAMというアクセス管理方法のベストプラクティスを読むことにしました。
docs.aws.amazon.com

AmazonWebサービス利用時にはIDとパスワードを利用するわけですけど
いきなり アクセスキー ID およびシークレットアクセスキー つかうなって話から始まる

そう、まずはコンソールにアクセスするのはIDとパスワードを使うことになる
ただし、AWSにフルアクセスできるようなアクセスキーは作らないと持つことはないと示されている
そして、そんなものを利用する機会はないのだから、絶対に作るなということだそうだ

うわ、作ってたし、Terraform でのインスタンス操作に使っていた…
今後のために削除しました。

で?代わりに IAM ユーザーを作ろうって話になっています。

IAMユーザー作成の前に実はグループを理解しないといけなかったんですよ
グループとは名ばかり、役割とでも言った方がしっくり来るでしょうか?
その役割を IAM ユーザーに設定することで管理を行っていくのです

最初は最低限の権限を持つ IAM ユーザーを作り、少しずつ権限を与えていくような運用にしましょう。
ポリシー概要で各グループやIAMユーザーの権限を確認してセキュリティを保ちます

ユーザーごとにパスワードを定期更新するように指示できる設定もあるそうだよ、管理者にとって便利な機能だね
より安全にワンタイムパスワード認証も取り入れられるとのこと(MFAって呼ぶらしい)

IAMユーザーの話を続けていたけれど、IAMロールってのもあるのですよ
IAMロールをアプリとかインスタンスに設定すればアプリ自体が AWS の別のリソースにアクセスできるようになります

説明わかりずらい、 IAM ユーザーが IAM ロールを引き継ぐことができるという文章があるので、ロールは IAM ユーザーに与えるものとして、混乱しながら読むことにしました。

ところで IAM ユーザーはアクセスキーとシークレットキーの二つを持ち、パスワードを持ちません
さらに管理者からIAMユーザーのアクセスキーを非アクティブ化することができます。
私、仕事で使っているアクセスキーを非アクティブ化されてたので、正しい操作を行ってはじかれるという経験をしてハマったことがありました。初めて AWS 触る人は、自分のアクセスキーがアクティブなのか、インフラ担当に聞いてから作業に入りましょう。

じゃ、そろそろ具体的に IAM ユーザー作成と権限の表示というものを見てみましょう
ということで次の記事に進みます

Windows:自宅で使っているPCのグローバルなパブリックIPアドレスを確認したい

家でも会社でも、マシンに割当たっているIPアドレスはローカルエリア接続だけが許されたプライベートIPアドレスです。
Windowsコマンドプロンプトから ipconfig /all コマンドを打って調べられるのもこのプライベート IP アドレスのみ

あの…私はクラウドインスタンスにアクセス許可するグローバルなプライベートIPアドレスが知りたいのですけど
具体的な方法知っている方いますか?

Google で調べる
教えてもらった情報はこちら
>グローバルIPアドレスの情報を確認する場合は、IPアドレスを調べるWebサービスを利用するのが最も手軽です。
自分のIPアドレスを調べてみる/Webサービスを利用 | IPラーニング
ここで紹介されているWebサービスがこちら
www.iphiroba.jp
一日100回までしか調べられないので注意

紹介サイトの方では以下の HTML スクリプトで IP アドレスを確認していました。

<script>
function load(){

var pref = SURFPOINT.getPrefCode();

document.getElementById("spip").innerHTML = "" + SURFPOINT.getIP();
}
</script>

Web ページでグローバルIPアドレスをどうやって取得しているか気になりましたが
SURFPOINT?

www.surfpoint.jp

の機能を埋め込んで表示していたみたいです。

なんだ、やっぱり有料サービスだった
https://www.docodoco.jp/download/manual_docodoco_user.pdf
でも API の説明が読めてよかった。

マシンの機能ではグローバルIPアドレスを取得することはできず、外部の Web サービスを利用しないとわからないことがわかっただけ収穫でした。
個人で使う分には問題なく確認できたので、ひとまず ok

AWSのセキュリティ設定

ゲーム開発会社のエンジニアとして生きていると、ある時 GNU / Linux というものを扱えないといけない時がやってくる

パソコンに触り始めてそろそろ10年くらい経つけど、ずっと Windows 系のOSマシンでしか仕事してこなかった。
そのためいきなり GNU / Linux のコマンドのオンパレードが続くと疲れる

少しでも慣れるために、まずは GNU / Linux マシンを触っておこうと思う。

ということで今回は安全にアクセスできる AWS インスタンスを立ち上げて、自分だけがその GNU / Linux にアクセスできるようになるお話
少し前に Terraform で AWSインスタンスを立ち上げることができていたけど、具体的には、ここにセキュリティ設定を追加するのが今回の狙いとなる
simplestar-tech.hatenablog.com

まずは、AWS がセキュリティの設定につてドキュメントを書いていると思うので、読んで知識の下地をつくります。
docs.aws.amazon.com

IPアドレスでポート番号ごとにアクセス制限するだけだと素人は考えるので設定はとっても簡単わかりやすいものだと楽観するのですけど
なんかわざと難しく説明している気がしてならない。読んでて疲れる

読んだので概要を書いてみます。

VPCってのは Amazon が考える仮想ネットワーク機構のことで、柔軟に通信をさばくために作られた新しめのシステム
終始 Classic と VPC の違いについて述べている
具体的には、セキュリティーグループの設定を後から切り替えられるのはVPCだけ、Classic で変えられると思うなを連発

その後、インスタンスとセキュリティーグループの結びつきを強調し、連続で説明しながら、実はセキュリティーグループはネットワークインタフェースに関連づけるものとほのめかして混乱を招く

その混乱を持ったまま、セキュリティーグループに設定できるのは通信の許可のみで、通信を禁止するような設定は行えない仕組みであることを示す。
具体的にすべての通信を許可するときは 0埋めのアドレスを指定すると、すべてのアドレスを意味すると仕様を述べる

外からインスタンスに情報が入ることをインバウンドやイングレスと呼称し、逆にインスタンスから外部に情報を渡すことをアウトバウンドやイグレスと呼称していることを暗にほのめかす。

セキュリティの通信許可はインバウンドとアウトバウンドの通信に対して行うことができる(VPCだけがアウトバウンドの設定が可能)

削除することのできないディフォルトのセキュリティーグループはすべてのインバウンド、アウトバウンドの通信を許可する設定、安全ではないので絶対設定しない方がいいが、最初はディフォルトが設定される
強い許可が生き残るので、ディフォルトのセキュリティーグループが当たっていると、どんなに厳しい制約を追加で与えても、どこからでもアクセスできる状態のままになる、危険

カスタムセキュリティーグループを何の設定もせずに作ると、インバウンド通信をすべて禁止し、アウトバウンド通信をすべて許可する設定となっている。インバウンド・アウトバウンドの通信について TCP/UDP などのプロトコルを決め、ポート番号ごとにどのIPアドレスからの通信を許可するかを設定する必要がある

コマンドラインからセキュリティーグループを設定するときだけ、インバウンドを ingress, アウトバウンドを egress と呼称して利用する

つまり

インバウンド・アウトバウンドごとに
1.通信プロトコルTCPUDP か)
2.ポート番号範囲 (22, 80, 8081 とか)
3.通信許可対象の IP アドレス (10.16.192.0/32 とか)

をセットに、いくつもルールを追加したセキュリティーグループを作って、インスタンスのネットワークインタフェースに関連付ける

という方法で AWS は信用するアクセス元からの通信のみを許可するようにセキュリティ設定を行っている。

ここまでわかれば、あとはその情報をどのように設定すればよいのかの書式を確認する作業になる。
次の記事でその書式や、信用するIPアドレスの確認の仕方などを調べておこうと思います。

Unity:OpenCVを直接使う4

今回は Android 端末で OpenCV を直接使う Unity 製アプリを作ってみます。

先に Unity 側のコードを書くと次の通り
前回の WebCam 画像の取得の枠組みを用意して、そこの Update 関数にて次のようにカメラ画像の pixel 配列の先頭アドレスを固定して渡します。

	void Update () {
        if (webCamHelper.webCamTexture && webCamHelper.webCamTexture.didUpdateThisFrame)
        {
            Color32[] colors = webCamHelper.webCamTexture.GetPixels32();
            GCHandle handle = GCHandle.Alloc(colors, GCHandleType.Pinned);
            System.IntPtr pinnedPointer = handle.AddrOfPinnedObject();

            cv_work(pinnedPointer, webCamHelper.webCamTexture.width, webCamHelper.webCamTexture.height);

            handle.Free();
        }	
	}

では cv_work 関数をどうやって呼べるようにするか、手順を記録しておきますね。

OpenCVAndroid 用のパッケージを次の公式URLより取得します
opencv.org

今回は Assets フォルダの外に AndroidAssets フォルダを作って、そこに jni フォルダを作り、その下に opencv-3.4.2-android-sdk フォルダとして配置しました。
ここからの手順は Android 用の ndk build を参考に行っていきます。
プロジェクトのビルド  |  Android NDK  |  Android Developers
書式の意味が知りたくなったら、こちらのドキュメントを参照してください。

手順だけ示すと、まず jni フォルダ直下に Android.mk ファイルと Application.mk ファイルを作って置きます。
Application.mk ファイルを作ったら、以下のようにサポートが必要なAndroid端末の CPU アーキテクチャを指定します。

APP_STL      := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI      := armeabi-v7a arm64-v8a
APP_PLATFORM := android-14

アーキテクチャの指定子はここから選ぶみたいですね。
https://developer.android.com/ndk/guides/abis?hl=ja

Android.mk ファイルにはプロジェクト構成を書きます。
今回は OpenCV の静的ライブラリをインクルードして、適当なモジュール名、適当なソースファイルを指定しています。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

# for OpenCV
OPENCV_CAMERA_MODULES:=on
OPENCV_INSTALL_MODULES:=on
OPENCV_LIB_TYPE:=STATIC
include opencv-3.4.2-android-sdk/OpenCV-android-sdk/sdk/native/jni/OpenCV.mk

# for Module
LOCAL_MODULE := unitydeopencv
LOCAL_SRC_FILES := unity_opencv.cpp
LOCAL_CFLAGS += -std=c++11

include $(BUILD_SHARED_LIBRARY)

では適当なソースファイルとして指定した unity_opencv.cpp の中身を確認してみましょう。

#include <opencv2/opencv.hpp>

extern "C"
{
    void cv_work(unsigned char* imgptr, int width, int height)
    {
        cv::Mat img(height, width, CV_8UC4, imgptr);
        cv::Mat gray_img;
        cv::cvtColor(img, gray_img, CV_BGRA2GRAY);
        return;
    }
}

jni フォルダをカレントにコマンドプロンプトから ndk-build コマンドを実行します。
ndk が古い(11とかだ)と警告が出るので、新しい ndk をこちらから入手しました。
NDK のダウンロード  |  Android NDK  |  Android Developers

ndk-build コマンドに成功すると lib~.so ファイルが libs フォルダに作られます。
あとはこれらを Unity の Assets/Plugins/Android フォルダの下へ libs フォルダごと配置します。
これで dllimport が可能になるはずです。

Unity 側のコードで次の宣言をすることで Android での実行時に関数実装を呼び出せるようになります。
デバッグするなら Windows の DLL も同じコードから作れるといいですね。

    [DllImport("unitydeopencv")]
    private static extern void cv_work(System.IntPtr imgptr, int width, int height);

Unity 側で OpenCV の計算結果を画像として表示する方法を示して「UnityでOpneCVを直接使う」シリーズを締めくくりたいと思います。

C++ コード側は引数を一つ増やして、確認用に4ch画像に戻し、メモリコピー

#include <opencv2/opencv.hpp>

extern "C"
{
    void cv_work(unsigned char* imgptr, int width, int height, unsigned char* resultptr)
    {
        cv::Mat img(height, width, CV_8UC4, imgptr);
        cv::Mat gray_img;
        cv::cvtColor(img, gray_img, CV_BGRA2GRAY);
        cv::cvtColor(gray_img, img, CV_GRAY2BGRA);
        memcpy(resultptr, img.data, img.total() * img.elemSize());
        return;
    }
}

利用側では、受け取った Color32 配列をテクスチャに適用します。

using System.Runtime.InteropServices;
using UnityEngine;

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

    [DllImport("unitydeopencv")]
    private static extern void cv_work(System.IntPtr imgptr, int width, int height, System.IntPtr resultptr);

    public GameObject quad;

	void Start () {
        webCamHelper = GetComponent<WebCamHelper>();
        webCamHelper.onInitialized.AddListener(OnInitalizedCamera);
        webCamHelper.InitializeCamera();
    }
	
	void Update () {
        if (webCamHelper.webCamTexture && webCamHelper.webCamTexture.didUpdateThisFrame)
        {
            Color32[] colors = webCamHelper.webCamTexture.GetPixels32();
            GCHandle handle = GCHandle.Alloc(colors, GCHandleType.Pinned);
            System.IntPtr pinnedPointer = handle.AddrOfPinnedObject();

            Color32[] colorsR = resultImage.GetPixels32();
            GCHandle handleR = GCHandle.Alloc(colorsR, GCHandleType.Pinned);
            System.IntPtr pinnedPointerR = handleR.AddrOfPinnedObject();

            cv_work(pinnedPointer, webCamHelper.webCamTexture.width, webCamHelper.webCamTexture.height, pinnedPointerR);

            resultImage.SetPixels32(colorsR);
            resultImage.Apply(false);

            handleR.Free();
            handle.Free();
        }	
	}

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

        resultImage = new Texture2D(width, height, TextureFormat.ARGB32, false);
        if (quad)
        {
            quad.GetComponent<Renderer>().material.mainTexture = resultImage;
        }
        
    }

    Texture2D resultImage;
    WebCamHelper webCamHelper;
}

あとは Unity の Build Setting ダイアログから Switch Platform にて Android を選択して、Other Settings の Package Name に適当なパッケージ名を書き込んで
開発者モードを有効化した Android 端末を PC に接続して、Build and Run ボタンを押します。

Android SDK バージョンが足りないとか言ってくる場合は、なんとかして SDK Manager を見つけ出して、求められた API をインストールします。
JDKのパスの設定先も 9.0 系だと Gradle 作成エラーが起きるので 8.0 系をインストールしてこれを与えてエラーを解決しました。

期待通り、Quad にカメラのグレースケール化された画像が表示されることを確認できました。
直接 OpenCV に Unity からカメラ画像を渡すことができているので、あとは C++ コード側を色々頑張れば OpenCV パワー全開の Android アプリが作れますね。

コンピュータビジョンに強い学者と、適当にプログラム書けるエンジニアがタッグを組めばきっと面白い世の中になると思います。

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;
}