0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】【第3回】オプティカルフローで動きを検出してみよう

Posted at

calcOpticalFlowPyrLK()で特徴点を追跡~

このシリーズについて

このシリーズでは、Unity上でカメラ映像を解析し、その結果をゲーム画面へ反映する仕組みを構築していきます。
扱うのは、OpenCV for Unityを使った「リアルタイム画像処理 × インタラクション制御」です。

内容 主なテーマ
第1回 OpenCV for Unityの導入と動作確認 開発環境構築・サンプル実行
第2回 Webカメラからリアルタイム映像を取得 WebCamTextureMat 変換
第3回(今回) オプティカルフローで動きを検出 特徴点の動きを解析
第4回 Unity Editorとは別ウィンドウで映像表示 検出結果を可視化・応用

🎯 今回の目的

今回は、カメラ映像の中で動く点を検出して、特徴点の動きを追跡することが目的です。
OpenCVの calcOpticalFlowPyrLK() 関数を使って、リアルタイムにオプティカルフロー(動きの方向と速さ)を算出します。

これにより、「カメラ前で動く物体がどちらに動いているか」 をUnity上で確認できるようになります。


🧩 オプティカルフローとは?

「オプティカルフロー(Optical Flow)」とは、連続する画像間でピクセルの動きを推定する技術 です。
簡単に言えば、
「前のフレームと次のフレームで、同じ点がどれだけ動いたか」
を数値として求める手法です。

今回使用する PyrLK(ピラミッドLucas-Kanade)法 は、
軽量でリアルタイム処理に向いており、Unityのようなリアルタイム環境に最適です。


1. 使用する関数:calcOpticalFlowPyrLK()

OpenCVでは、以下の関数を使って特徴点の動きを追跡します。

Video.calcOpticalFlowPyrLK(
    prevGray,          // 前のフレーム(グレースケール)
    nextGray,          // 現在のフレーム(グレースケール)
    prevPoints,        // 前フレームの特徴点座標
    nextPoints,        // 次フレームの対応点(出力)
    status,            // 成功フラグ
    err                // 誤差情報
);

2. 🎥 サンプルコード(Unity C#)

以下のスクリプトでは、
カメラ映像をフレームごとに取得し、動く特徴点を追跡して画面上に描画します。

using UnityEngine;
using OpenCVForUnity.UnityUtils.Helper;
using OpenCVForUnity.CoreModule;
using OpenCVForUnity.ImgprocModule;
using OpenCVForUnity.VideoModule;

public class OpticalFlowExample : MonoBehaviour
{
    WebCamTextureToMatHelper webCamHelper;

    Mat prevGray;
    Mat gray;
    MatOfPoint2f prevPoints;
    MatOfPoint2f nextPoints;
    MatOfByte status;
    MatOfFloat err;
    Texture2D texture;

    void Start()
    {
        webCamHelper = gameObject.AddComponent<WebCamTextureToMatHelper>();
        webCamHelper.Initialize();
    }

    void OnWebCamTextureToMatHelperInitialized()
    {
        gray = new Mat();
        prevGray = new Mat();
        prevPoints = new MatOfPoint2f();
        nextPoints = new MatOfPoint2f();
        status = new MatOfByte();
        err = new MatOfFloat();

        Mat frame = webCamHelper.GetMat();
        texture = new Texture2D(frame.cols(), frame.rows(), TextureFormat.RGBA32, false);
        GetComponent<Renderer>().material.mainTexture = texture;
    }

    void Update()
    {
        if (!webCamHelper.IsPlaying() || !webCamHelper.DidUpdateThisFrame())
            return;

        Mat frame = webCamHelper.GetMat();
        Imgproc.cvtColor(frame, gray, Imgproc.COLOR_RGBA2GRAY);

        // 初回のみ特徴点を検出
        if (prevPoints.empty())
        {
            MatOfPoint corners = new MatOfPoint();
            Imgproc.goodFeaturesToTrack(gray, corners, 200, 0.01, 10);
            prevPoints.fromArray(corners.toArray());
            gray.copyTo(prevGray);
        }
        else
        {
            // オプティカルフロー計算
            Video.calcOpticalFlowPyrLK(prevGray, gray, prevPoints, nextPoints, status, err);

            // 動きを描画
            Point[] p0 = prevPoints.toArray();
            Point[] p1 = nextPoints.toArray();

            for (int i = 0; i < p0.Length; i++)
            {
                if (status.toArray()[i] == 1)
                {
                    Imgproc.line(frame, p0[i], p1[i], new Scalar(0, 255, 0, 255), 2);
                    Imgproc.circle(frame, p1[i], 2, new Scalar(0, 0, 255, 255), -1);
                }
            }

            // 次のフレームに備えて更新
            gray.copyTo(prevGray);
            prevPoints.fromArray(nextPoints.toArray());
        }

        // テクスチャ更新
        OpenCVMatUtils.MatToTexture2D(frame, texture);
    }

    void OnDestroy()
    {
        if (webCamHelper != null)
            webCamHelper.Dispose();
    }
}

3. 実行結果

再生ボタン(▶)を押すと、
🟢 緑の線で動きの軌跡、🔴 赤い点で現在の特徴点が描画されます。

オプティカルフロー.png

カメラの前で手を振ると、
手の動きに沿って矢印のようなベクトルがリアルタイムに描かれるはずです。

💡 動きが少ない場合は、goodFeaturesToTrack() のパラメータを変更して特徴点を増やすと改善します。


⚠️ 注意:カメラFPSとUpdate周期のズレについて

この実装では webCamHelper.DidUpdateThisFrame() により、
新しいカメラフレームが来たときだけ オプティカルフロー(PyrLK)を実行しています。

そのため、Update() の呼び出し頻度とカメラのフレームレートが異なっても、
特徴点の対応づけや動きの方向の検出精度は基本的に維持 されます。

ただし、次の点に注意してください:


✔ 速度判定を行う場合は「時間正規化」が必要

calcOpticalFlowPyrLK() が返す値は px/フレーム です。
カメラFPSが揺らぐと“速さ”が不安定になるため、経過時間 Δt で割って px/秒 に変換 してください。

// フレームごとの時刻(秒)で正規化
static float lastT = 0f;
float now = Time.realtimeSinceStartup;
float dt = (lastT == 0f) ? (1f / 60f) : (now - lastT);
lastT = now;

// 例:各点の移動を px/秒 に変換
var vx = (float)(p1[i].x - p0[i].x) / Mathf.Max(dt, 1e-6f);
var vy = (float)(p1[i].y - p0[i].y) / Mathf.Max(dt, 1e-6f);

✔ フレームが間引かれる場合の耐性を上げる

フレームレートが落ちたり間引かれる場合、PyrLK追跡が外れやすくなります。
以下のようにパラメータを調整するとロバストになります:

var winSize = new Size(21, 21); // 例:15→21
int maxLevel = 3;               // 例:2→3
var term = new TermCriteria(TermCriteria.COUNT | TermCriteria.EPS, 30, 0.01);
double minEig = 1e-4;

Video.calcOpticalFlowPyrLK(prevGray, gray, prevPoints, nextPoints, status, err,
                           winSize, maxLevel, term, 0, minEig);

✔ 特徴点の再検出を適宜行う

goodFeaturesToTrack() で得た特徴点は時間の経過とともに減っていきます。
点数が少なくなったら再検出してください。

👌 方向の可視化が目的なら、サンプルコードのままで問題ありません。

「速さ」を活用する処理(速度判定・しきい値判定)だけ、上記の Δt 正規化を導入してください。


4. 応用:動作方向の推定

特徴点の差分から、動きの方向(角度)を算出することができます。

Point diff = new Point(p1[i].x - p0[i].x, p1[i].y - p0[i].y);
double angle = Mathf.Atan2((float)diff.y, (float)diff.x) * Mathf.Rad2Deg;

これにより、
👉 「右に動いたら次のステージへ」
👉 「上に動いたらジャンプ」

などのプレイヤー動作に応じたゲーム制御 が実現できます。


5. 次回予告

次回(第4回)は、
🪟 Unity Editorとは別ウィンドウで映像を表示する方法
を解説します。

  • デバッグ用のサブウィンドウに映像を表示
  • 処理結果を別画面で可視化
  • 複数ウィンドウで解析と表示を分離

など、より実用的な構成を紹介します。


まとめ

  • calcOpticalFlowPyrLK() で特徴点の動きを追跡
  • 線と点で「動きの可視化」が可能
  • カメラFPSとUpdate周期のズレは基本的に問題なし
  • 速度判定をする場合のみ Δt 正規化が必要
  • 次回は「別ウィンドウ表示」を解説

📚 シリーズ一覧

  1. 【第1回】OpenCV for Unityの導入と環境設定
  2. 【第2回】Webカメラからリアルタイム映像を取得して表示する
  3. 【第3回】オプティカルフローで動きを検出してみよう(この記事)
  4. 【第4回】Unity Editorとは別ウィンドウで映像を表示する

今回はここまで。
最後まで読んで下さりありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?