cv::floodFill の使い方【OpenCV/C++】〜塗りつぶし処理を実装する〜

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

cv::floodFill の使い方【OpenCV/C++】〜塗りつぶし処理を実装する〜

cv::floodFill は、指定した座標を起点に、隣接する近似色のピクセルを指定色で塗りつぶす関数です。ペイントソフトのバケツツールと同じ動作で、背景除去・領域抽出・欠陥検査など幅広い用途に使えます。

最小限のコードはこちら:

cv::floodFill(image, cv::Point(x, y), cv::Scalar(255, 0, 0));

動作環境

  • OpenCV 4.x
  • C++17
  • ビルド: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4) -o main

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


基本の使い方

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

int main() {
    // 200x200 の白い画像を作成し、黒い矩形を描画
    cv::Mat image(200, 200, CV_8UC3, cv::Scalar(255, 255, 255));
    cv::rectangle(image, cv::Point(50, 50), cv::Point(150, 150),
                  cv::Scalar(0, 0, 0), 2);

    // 矩形の内側 (100, 100) を赤で塗りつぶす
    int lo = 10; // 下方向の色差閾値
    int up = 10; // 上方向の色差閾値
    cv::Rect boundRect;
    int area = cv::floodFill(
        image,
        cv::Point(100, 100),       // シード点(塗りつぶし起点)
        cv::Scalar(0, 0, 255),     // 塗りつぶし色(赤)
        &boundRect,                // 塗りつぶされた領域の外接矩形(任意)
        cv::Scalar(lo, lo, lo),    // loDiff: 現在色との下方向許容差
        cv::Scalar(up, up, up),    // upDiff: 現在色との上方向許容差
        cv::FLOODFILL_FIXED_RANGE  // フラグ: シード色基準で判定
    );

    std::cout << "塗りつぶしピクセル数: " << area << std::endl;
    std::cout << "外接矩形: " << boundRect << std::endl;

    cv::imwrite("result.png", image);
    std::cout << "result.png を保存しました" << std::endl;
    return 0;
}

実行結果:

塗りつぶしピクセル数: 9604
外接矩形: [99 x 99 from (51, 51)]
result.png を保存しました

矩形の内側が赤く塗りつぶされた画像が result.png に保存されます。黒い枠線が境界として機能し、外側の白い領域には影響が及びません。

各行のポイント

  • シード点 cv::Point(100, 100): 塗りつぶしの起点。この点の色を基準に隣接ピクセルを探索します。
  • loDiff / upDiff: シード点の色と比較したときの許容色差。cv::Scalar(0,0,0) にすると完全一致のみ塗りつぶします。
  • cv::FLOODFILL_FIXED_RANGE: 隣接ピクセルではなく、シード点の色を基準に判定します。グラデーション画像での意図しない漏れを防ぎやすいです。
  • 戻り値: 塗りつぶされたピクセルの総数。面積フィルタに使えます。

引数と戻り値

引数 説明
image cv::InputOutputArray 入出力画像(8U または 32F、1ch/3ch)
mask cv::InputOutputArray (オーバーロード版)塗りつぶし制御マスク。画像より2px大きい8U1ch
seedPoint cv::Point 塗りつぶしの起点
newVal cv::Scalar 塗りつぶし後の色
rect cv::Rect* 塗りつぶされた領域の外接矩形(nullptr 可)
loDiff cv::Scalar 現在ピクセルとの下方向許容差
upDiff cv::Scalar 現在ピクセルとの上方向許容差
flags int 接続性・固定範囲・マスクフラグの組み合わせ

戻り値: int — 塗りつぶされたピクセル数

flags の主要な値:

定数 説明
4(デフォルト) 4近傍接続で探索
8 8近傍接続で探索
cv::FLOODFILL_FIXED_RANGE シード点色を基準に色差判定
cv::FLOODFILL_MASK_ONLY 画像を変更せず、マスクのみ更新

flags(8 | cv::FLOODFILL_FIXED_RANGE | (255 << 8)) のようにビット演算で組み合わせます。上位16ビットはマスクに書き込む値(1〜255)を指定します。


実践例

マスクを使って塗りつぶし領域を取り出す

FLOODFILL_MASK_ONLY フラグと mask 引数を組み合わせると、元画像を変えずに塗りつぶし領域をマスクとして取得できます。背景領域の抽出や後段処理のROI指定に便利です。

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

int main() {
    cv::Mat src = cv::imread("input.png");
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました" << std::endl;
        return 1;
    }

    // mask は画像より縦横それぞれ2px大きい必要がある
    cv::Mat mask = cv::Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);

    // flags の上位16ビットにマスクへの書き込み値(1)を指定
    int flags = 4
        | cv::FLOODFILL_FIXED_RANGE
        | cv::FLOODFILL_MASK_ONLY
        | (1 << 8); // マスクに書き込む値

    cv::floodFill(
        src,
        mask,
        cv::Point(0, 0),          // 左上隅が背景と仮定してシード
        cv::Scalar(0),            // MASK_ONLY なので newVal は使われない
        nullptr,
        cv::Scalar(15, 15, 15),
        cv::Scalar(15, 15, 15),
        flags
    );

    // mask は画像より2px大きいため、ROI で切り出す
    cv::Mat maskRoi = mask(cv::Rect(1, 1, src.cols, src.rows));

    // マスク領域を確認用に保存
    cv::imwrite("mask_result.png", maskRoi * 255);
    std::cout << "mask_result.png を保存しました" << std::endl;
    return 0;
}

実行結果:

mask_result.png を保存しました

背景に相当する領域が白(255)のマスク画像が得られます。このマスクを cv::bitwise_and や ROI 指定に流用することで、前景オブジェクトだけを処理対象にできます。


つまずきポイント

⚠️ mask のサイズは「画像 + 2px」でなければならない

マスクありオーバーロードを使う場合、mask のサイズは cv::Size(src.cols + 2, src.rows + 2) である必要があります。同じサイズにすると実行時に cv::Exception が発生します。

// NG: src と同じサイズ
cv::Mat mask = cv::Mat::zeros(src.size(), CV_8UC1);

// OK: +2
cv::Mat mask = cv::Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);

また、前回の floodFill の結果が残った mask を再利用すると、既にマスクが立っている箇所が塗りつぶしの障壁になります。毎回 mask = cv::Mat::zeros(...) でリセットしてください。

⚠️ loDiff / upDiff を 0 にするとグラデーション画像で止まらない

JPEG などの圧縮画像は近似色にわずかなノイズが乗っています。cv::Scalar(0,0,0) にすると完全一致しか塗りつぶせず、想定より狭い領域しか塗れません。実画像には cv::Scalar(10〜20, 10〜20, 10〜20) 程度を試すのが実務上の出発点です。逆に大きすぎると隣接領域に漏れるため、値は用途に合わせて調整が必要です。

⚠️ flags の上位16ビット指定を忘れるとマスクに何も書かれない

FLOODFILL_MASK_ONLY 使用時、flags の上位16ビットでマスクへの書き込み値を指定しなければなりません。0 のままだとマスクに何も書き込まれず、「なぜか mask が空」という状況になります。

// NG: マスクへの書き込み値が 0
int flags = cv::FLOODFILL_MASK_ONLY;

// OK: 書き込み値 1 を上位16ビットに指定
int flags = cv::FLOODFILL_MASK_ONLY | (1 << 8);

関連する関数

  • cv::inRange: 色範囲でピクセルを二値化する。floodFill の前処理としてノイズ除去に使いやすい
  • cv::findContours / cv::drawContours: 塗りつぶし後の領域輪郭を取得・描画する
  • cv::connectedComponents: 連結成分ラベリング。塗りつぶしより高速に全領域を一括処理したい場合の代替手段
  • cv::matchTemplate の使い方【OpenCV/C++】〜テンプレートマッチングで物体位置を検出する〜: 塗りつぶすべき領域をテンプレートマッチングで特定してからシード点を決める、という組み合わせが実務で頻出

まとめ

cv::floodFill は起点・色差閾値・フラグの3要素を押さえれば、背景除去や領域抽出を数行で実装できます。FLOODFILL_MASK_ONLYmask を組み合わせると元画像を保持したまま塗りつぶし領域だけ取り出せるため、後段の処理と組み合わせやすくなります。まずは loDiff / upDiff をゼロから少しずつ広げながら、対象画像に合った閾値を探るのが実装の近道です。

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

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

→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード

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