BackgroundSubtractorMOG2 の使い方【OpenCV/C++】〜背景差分で動体を検出する〜

OpenCV for C++
📌 準備: OpenCV の環境構築がまだの方はこちら → C++ 環境構築ガイド / C#(OpenCvSharp)セットアップ

BackgroundSubtractorMOG2 の使い方【OpenCV/C++】〜背景差分で動体を検出する〜

cv::BackgroundSubtractorMOG2 は、映像の各フレームから背景モデルを自動生成し、動いている物体(前景)をマスク画像として抽出するクラスです。工場ラインの異常検知・防犯カメラの動体アラートなど、実務での使用頻度が高い機能です。

最小の呼び出しは次の通りです。

auto pMOG2 = cv::createBackgroundSubtractorMOG2();
pMOG2->apply(frame, fgMask);  // fgMask が前景マスク(CV_8UC1)

動作環境

  • OpenCV 4.x
  • ビルド: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)
  • 実行: ./a.out

環境構築がまだの方は環境構築ガイドを先にどうぞ。


基本の使い方

以下のサンプルは動画ファイルを読み込み、各フレームに MOG2 を適用して前景マスクを表示します。

#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
    // 動画ファイルを開く(カメラなら 0 を指定)
    cv::VideoCapture cap("input.mp4");
    if (!cap.isOpened()) {
        std::cerr << "動画を開けませんでした" << std::endl;
        return 1;
    }

    // MOG2 背景差分器を生成
    // history: 背景モデルに使うフレーム数(デフォルト 500)
    // varThreshold: 前景/背景の判定閾値(デフォルト 16)
    auto pMOG2 = cv::createBackgroundSubtractorMOG2(500, 16, true);

    cv::Mat frame, fgMask;

    while (true) {
        cap >> frame;
        if (frame.empty()) break;  // 動画終端

        // フレームを適用して前景マスクを更新
        // learningRate = -1 で自動調整
        pMOG2->apply(frame, fgMask, -1);

        // 結果表示
        cv::imshow("Original", frame);
        cv::imshow("FG Mask", fgMask);

        if (cv::waitKey(30) == 'q') break;
    }

    cap.release();
    cv::destroyAllWindows();
    return 0;
}

実行結果:

ウィンドウ "Original" に元映像、"FG Mask" に白黒の前景マスクが表示されます。
動いている物体の領域が白(255)、背景が黒(0)で描画されます。
影を検出した場合はグレー(127)で表示されます(detectShadows=true 時)。

コード解説

行 説明
createBackgroundSubtractorMOG2(500, 16, true) 背景差分器を生成。第3引数 true で影検出を有効化
pMOG2->apply(frame, fgMask, -1) フレームを渡して前景マスクを取得。learningRate=-1 は自動
fgMask の画素値 前景=255、背景=0、影=127

引数と戻り値

cv::createBackgroundSubtractorMOG2()

引数 型 デフォルト 説明
history int 500 背景モデルに使用する過去フレーム数
varThreshold double 16 マハラノビス距離の2乗閾値。値が小さいほど敏感
detectShadows bool true 影検出を行うか

戻り値: cv::Ptr<cv::BackgroundSubtractorMOG2>

apply() メソッド

引数 型 説明
image cv::InputArray 入力フレーム(グレースケール・カラー両対応)
fgmask cv::OutputArray 出力前景マスク(CV_8UC1)
learningRate double 背景モデルの学習率(0〜1。-1 で自動)

実践例

例1: ノイズ除去 + 輪郭抽出で動体を矩形で囲む

前景マスクにはカメラノイズ由来の細かい白点が混入します。モルフォロジー演算でノイズを除去してから輪郭を検出するのが実務のセオリーです。

#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
    cv::VideoCapture cap("input.mp4");
    if (!cap.isOpened()) {
        std::cerr << "動画を開けませんでした" << std::endl;
        return 1;
    }

    auto pMOG2 = cv::createBackgroundSubtractorMOG2(500, 25, false);

    cv::Mat frame, fgMask, cleaned;
    // モルフォロジー用カーネル
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, {5, 5});

    while (true) {
        cap >> frame;
        if (frame.empty()) break;

        pMOG2->apply(frame, fgMask, -1);

        // オープニング処理でノイズ除去 → クロージングで穴埋め
        cv::morphologyEx(fgMask, cleaned, cv::MORPH_OPEN,  kernel);
        cv::morphologyEx(cleaned, cleaned, cv::MORPH_CLOSE, kernel);

        // 輪郭検出
        std::vector<std::vector<cv::Point>> contours;
        cv::findContours(cleaned, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

        cv::Mat output = frame.clone();
        for (const auto& cnt : contours) {
            // 面積が小さい輪郭は無視(カメラノイズ対策)
            if (cv::contourArea(cnt) < 500) continue;
            cv::Rect bbox = cv::boundingRect(cnt);
            cv::rectangle(output, bbox, {0, 255, 0}, 2);
        }

        cv::imshow("Detected", output);
        if (cv::waitKey(30) == 'q') break;
    }

    cap.release();
    cv::destroyAllWindows();
    return 0;
}

実行結果:

動いている物体に緑色の矩形が描画されます。
面積 500px² 未満の輪郭は無視されるため、ノイズによる誤検出が大幅に減ります。

例2: 学習率を手動制御して背景を素早く固定する

シーン冒頭の数秒だけ高速学習させ、以降は学習率を下げて誤検出を抑える手法です。

#include <opencv2/opencv.hpp>
#include <iostream>

int main()
{
    cv::VideoCapture cap(0);  // カメラ入力
    if (!cap.isOpened()) {
        std::cerr << "カメラを開けませんでした" << std::endl;
        return 1;
    }

    auto pMOG2 = cv::createBackgroundSubtractorMOG2(200, 16, false);

    cv::Mat frame, fgMask;
    int frameCount = 0;

    while (true) {
        cap >> frame;
        if (frame.empty()) break;

        // 最初の 60 フレームは高速学習(0.1)、以降は低速(0.001)
        double lr = (frameCount < 60) ? 0.1 : 0.001;
        pMOG2->apply(frame, fgMask, lr);
        ++frameCount;

        cv::imshow("FG Mask", fgMask);
        if (cv::waitKey(30) == 'q') break;
    }

    cap.release();
    cv::destroyAllWindows();
    return 0;
}

つまずきポイント

⚠️ 最初の数十フレームはほぼ全面が前景になる

背景モデルが構築されるまでの間(history フレーム数が溜まるまで)、前景マスクは不安定です。冒頭フレームの検出結果を業務ロジックに流さないよう、フレームカウンタでガードしてください。

if (frameCount < 30) { ++frameCount; continue; }  // ウォームアップ期間をスキップ

⚠️ 影検出が誤検出の原因になることがある

detectShadows=true(デフォルト)のとき、影の領域は画素値 127 で出力されます。cv::threshold で 254 以上のみを取り出すか、createBackgroundSubtractorMOG2(..., false) で影検出を無効化することで、影を前景として扱わずに済みます。

// 影(127)を除外して前景(255)のみを取得
cv::threshold(fgMask, fgMask, 254, 255, cv::THRESH_BINARY);

⚠️ VideoCapture が空フレームを返し apply() で assertion が落ちる

cap >> frame の直後に frame.empty() チェックを入れないと、動画終端やカメラ抜けのタイミングで apply() 内部の assertion が発生してクラッシュします。サンプルコードのように必ず empty() チェックを入れてください。
cv::VideoCapture の使い方も合わせて参照してください。


関連する関数


まとめ

cv::BackgroundSubtractorMOG2 は createBackgroundSubtractorMOG2() でインスタンスを作り、各フレームに apply() を呼ぶだけで背景差分を実現できます。実務では「ウォームアップのスキップ」「モルフォロジーによるノイズ除去」「面積フィルタによる輪郭の絞り込み」の3点を組み合わせることで、実用的な動体検出が構築できます。

🛠 画像処理のプロが開発するSDK/API

本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。

→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

タイトルとURLをコピーしました