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::VideoCapture の使い方【OpenCV/C++】〜カメラ映像を取り込む〜
- cv::VideoWriter の使い方【OpenCV/C++】〜動画ファイルを書き出す〜
- cv::countNonZero の使い方【OpenCV/C++】〜非ゼロ画素を数える〜 — 前景マスクの面積(白画素数)を素早く取得するのに便利
cv::BackgroundSubtractorKNN— MOG2 の代替アルゴリズム。照明変化に強いcv::findContours/cv::morphologyEx— 前景マスクの後処理に必須
まとめ
cv::BackgroundSubtractorMOG2 は createBackgroundSubtractorMOG2() でインスタンスを作り、各フレームに apply() を呼ぶだけで背景差分を実現できます。実務では「ウォームアップのスキップ」「モルフォロジーによるノイズ除去」「面積フィルタによる輪郭の絞り込み」の3点を組み合わせることで、実用的な動体検出が構築できます。
🛠 画像処理のプロが開発するSDK/API
本ブログを運営するスワローインキュベートは、OpenCV ベースのなりすまし判定SDK/API(C++製・OpenCV 4.10)を開発しています。顔認証システムへの組み込み実績多数。
→ 顔認証のなりすまし対策 設計チェックリスト(当ブログ)
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

