cv::adaptiveThreshold の使い方【OpenCV/C++】〜適応的二値化で照明ムラに強い2値化を実現する〜

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

cv::adaptiveThreshold の使い方【OpenCV/C++】〜適応的二値化で照明ムラに強い2値化を実現する〜

cv::adaptiveThreshold は、画像全体ではなく局所領域ごとに閾値を自動計算して二値化する関数です。照明ムラや影の影響を受けやすい場面で、グローバル閾値の cv::threshold より安定した結果が得られます。

最小コード例:

cv::adaptiveThreshold(gray, dst,
    255,
    cv::ADAPTIVE_THRESH_GAUSSIAN_C,
    cv::THRESH_BINARY,
    11, 2);

動作環境

  • OpenCV 4.x
  • コンパイル例: g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)

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

基本の使い方

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

int main() {
    // グレースケールで読み込む
    cv::Mat gray = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE);
    if (gray.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    cv::Mat dst;

    // 適応的二値化(ガウシアン加重平均を閾値として使用)
    cv::adaptiveThreshold(
        gray,                            // 入力(グレースケール必須)
        dst,                             // 出力
        255,                             // 閾値を超えたときに設定する値
        cv::ADAPTIVE_THRESH_GAUSSIAN_C,  // 局所閾値の計算方法
        cv::THRESH_BINARY,               // 二値化の種類
        11,                              // ブロックサイズ(奇数)
        2                                // 定数 C(平均から引く値)
    );

    cv::imwrite("output.png", dst);
    std::cout << "保存しました: output.png" << std::endl;

    return 0;
}

実行結果:

保存しました: output.png

output.png を確認すると、画像内に照明ムラや影があっても、局所領域ごとに最適な閾値が計算されるため、文字や輪郭がきれいに抽出されています。グローバル閾値の cv::threshold では潰れてしまう暗い領域のディテールも残ります。

コード解説

ポイント
cv::imread(..., cv::IMREAD_GRAYSCALE) 入力はグレースケール8ビット1チャンネル必須
cv::ADAPTIVE_THRESH_GAUSSIAN_C ブロック内のガウシアン加重平均を閾値に使用。均一照明でない場合に有効
cv::THRESH_BINARY 閾値以上→maxVal、未満→0
ブロックサイズ 11 局所領域の一辺のピクセル数(奇数のみ、3以上)
定数 C = 2 計算した平均から引く値。正にすると閾値が下がり白が増える

引数と戻り値

void cv::adaptiveThreshold(
    InputArray  src,           // 入力画像(CV_8UC1 のみ)
    OutputArray dst,           // 出力画像
    double      maxValue,      // 閾値を超えた画素に設定する値(通常 255)
    int         adaptiveMethod,// 閾値計算方法
    int         thresholdType, // 二値化の種類
    int         blockSize,     // 局所領域のサイズ(奇数・3以上)
    double      C              // 平均または加重平均から引く定数
);

adaptiveMethod の選択肢

定数 動作
cv::ADAPTIVE_THRESH_MEAN_C ブロック内の単純平均 − C を閾値にする
cv::ADAPTIVE_THRESH_GAUSSIAN_C ブロック内のガウシアン加重平均 − C を閾値にする

実務では ADAPTIVE_THRESH_GAUSSIAN_C を使うことがほとんどです。均一でない照明に対してより滑らかに追従します。

thresholdType の選択肢

定数 動作
cv::THRESH_BINARY 閾値以上 → maxValue、未満 → 0
cv::THRESH_BINARY_INV 閾値以上 → 0、未満 → maxValue

戻り値はありません(void)。

実践例

例1: 文書スキャン画像のOCR前処理

照明ムラのある紙面の文字を抽出するよくある前処理フローです。

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

int main() {
    cv::Mat src = cv::imread("document.jpg");
    if (src.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // グレースケール変換
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    // ガウシアンブラーでノイズ除去してから二値化
    cv::Mat blurred;
    cv::GaussianBlur(gray, blurred, cv::Size(3, 3), 0);

    cv::Mat binary;
    cv::adaptiveThreshold(
        blurred, binary,
        255,
        cv::ADAPTIVE_THRESH_GAUSSIAN_C,
        cv::THRESH_BINARY,
        15,   // 文字サイズに合わせてブロックサイズを調整
        8     // C を大きくすると背景のノイズを抑えやすい
    );

    cv::imwrite("binary_document.png", binary);
    std::cout << "文書二値化完了" << std::endl;

    return 0;
}

blockSizeC の調整が品質のカギです。文字が小さければブロックサイズを小さく(9〜15)、文字が大きければ大きく(21〜51)します。C はノイズが多い場合に大きくします。

例2: グローバル閾値との比較保存

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

int main() {
    cv::Mat gray = cv::imread("uneven_light.jpg", cv::IMREAD_GRAYSCALE);
    if (gray.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    // グローバル閾値(固定値 127)
    cv::Mat global_bin;
    cv::threshold(gray, global_bin, 127, 255, cv::THRESH_BINARY);

    // 適応的二値化
    cv::Mat adaptive_bin;
    cv::adaptiveThreshold(gray, adaptive_bin,
        255, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY, 11, 2);

    // 横に並べて比較
    cv::Mat comparison;
    cv::hconcat(global_bin, adaptive_bin, comparison);

    cv::imwrite("comparison.png", comparison);
    std::cout << "比較画像を保存しました: comparison.png" << std::endl;

    return 0;
}

左がグローバル閾値、右が適応的二値化の結果が comparison.png に保存されます。照明ムラのある画像では右側の方が均一に二値化されていることが確認できます。

つまずきポイント

⚠️ 入力が 8ビット・グレースケールでないと assertion エラー

cv::adaptiveThresholdCV_8UC1(グレースケール8ビット)しか受け付けません。カラー画像をそのまま渡すと実行時に以下のエラーが出ます。

OpenCV Error: Assertion failed (src.type() == CV_8UC1)

cv::imread でカラー画像を読み込んだ場合は必ず cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY) でグレースケールに変換してから渡してください。cv::imread の使い方も参照ください。

⚠️ blockSize は奇数・3以上でなければならない

blockSize に偶数(例: 10)や 1 を渡すと assertion エラーになります。3, 5, 7, 11, 15, 21 のように奇数を指定してください。よく見る間違いとして、ループでブロックサイズをインクリメントする際に偶数を渡してしまうケースがあります。

// NG: 偶数ステップでループ
for (int bs = 4; bs <= 20; bs += 2) { ... }

// OK: 奇数ステップでループ
for (int bs = 5; bs <= 21; bs += 2) { ... }

⚠️ C(定数)を負にするとノイズが増える

C を負の値にすることも API 上は許可されていますが、閾値が平均より高くなるため、ノイズ画素が多く残ります。通常は 210 程度の正の値から試してください。

関連する関数

  • cv::threshold — グローバル閾値による二値化。照明が均一な場面ではこちらで十分
  • cv::GaussianBlur — 適応的二値化の前処理としてノイズを減らすために組み合わせることが多い
  • cv::cvtColor — カラー→グレースケール変換に必要

まとめ

cv::adaptiveThreshold は照明ムラや影の影響を受けやすい画像の二値化に有効です。blockSize と定数 C の2パラメータを対象画像に合わせて調整するだけで、グローバル閾値より安定した結果が得られます。入力は必ず CV_8UC1(グレースケール8ビット)であることと、blockSize は奇数であることを守れば実装上のトラブルはほぼ回避できます。

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

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

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

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