cv::CascadeClassifier の使い方【OpenCV/C++】〜Haar Cascadeで顔検出〜

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

cv::CascadeClassifier の使い方【OpenCV/C++】〜Haar Cascadeで顔検出〜

cv::CascadeClassifier は、学習済みの Haar Cascade または LBP Cascade を使って画像内のオブジェクト(顔・目・車など)を検出するクラスです。OpenCV に同梱されている haarcascade_frontalface_default.xml を読み込むだけで、数行のコードで顔検出が動作します。

cv::CascadeClassifier classifier("haarcascade_frontalface_default.xml");
std::vector<cv::Rect> faces;
classifier.detectMultiScale(gray, faces);

動作環境

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

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


基本の使い方

完全プログラム

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

int main(int argc, char* argv[]) {
    if (argc < 3) {
        std::cerr << "Usage: " << argv[0] << " <image> <cascade_xml>" << std::endl;
        return 1;
    }

    // 画像読み込み
    cv::Mat img = cv::imread(argv[1]);
    if (img.empty()) {
        std::cerr << "画像を読み込めませんでした: " << argv[1] << std::endl;
        return 1;
    }

    // Cascade 分類器の読み込み
    cv::CascadeClassifier classifier;
    if (!classifier.load(argv[2])) {
        std::cerr << "カスケードファイルを読み込めませんでした: " << argv[2] << std::endl;
        return 1;
    }

    // グレースケール変換 + ヒストグラム平坦化(検出精度向上)
    cv::Mat gray;
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    cv::equalizeHist(gray, gray);

    // 顔検出
    std::vector<cv::Rect> faces;
    classifier.detectMultiScale(
        gray,           // 入力画像(グレースケール)
        faces,          // 検出結果
        1.1,            // scaleFactor: 画像縮小率
        5,              // minNeighbors: 検出矩形の最小隣接数
        0,              // flags: 通常は0
        cv::Size(30, 30) // minSize: 最小検出サイズ
    );

    std::cout << "検出した顔の数: " << faces.size() << std::endl;

    // 結果を矩形で描画
    for (const auto& face : faces) {
        cv::rectangle(img, face, cv::Scalar(0, 255, 0), 2);
        std::cout << "  位置: (" << face.x << ", " << face.y << ") "
                  << "サイズ: " << face.width << "x" << face.height << std::endl;
    }

    cv::imwrite("result.jpg", img);
    std::cout << "結果を result.jpg に保存しました" << std::endl;

    return 0;
}

実行結果

$ ./main photo.jpg /usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml
検出した顔の数: 2
  位置: (142, 88) サイズ: 96x96
  位置: (310, 102) サイズ: 88x88
結果を result.jpg に保存しました

result.jpg には検出された顔の領域に緑色の矩形が描画されています。

行ごとの解説

処理 ポイント
classifier.load() 戻り値が bool。false の場合はファイルパスが誤っているか、XML が破損している
cvtColor → equalizeHist 顔検出はグレースケール前提。equalizeHist で明暗差を補正すると暗い環境での検出率が上がる
detectMultiScale 複数スケールで検索し、複数の検出結果を std::vector<cv::Rect> で返す
cv::rectangle 検出矩形を元のカラー画像に描画

引数と戻り値

classifier.load()

引数 型 説明
filename const cv::String& XML ファイルのパス
戻り値 bool 読み込み成功なら true

detectMultiScale()(主要オーバーロード)

引数 型 既定値 説明
image InputArray — グレースケール画像
objects std::vector<Rect>& — 検出結果(出力)
scaleFactor double 1.1 検索スケールの縮小率。1 に近いほど精度↑・速度↓
minNeighbors int 3 矩形をマージする閾値。大きくすると誤検出↓・見逃し↑
flags int 0 旧API互換フラグ。通常は 0
minSize Size Size() 検出する最小サイズ
maxSize Size Size() 検出する最大サイズ(省略で無制限)

実践例

例1: ROI を切り出して目の検出を重ねる

顔領域を検出した後、その ROI に対してさらに目を検出するパターンは実務でよく使います。

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

int main(int argc, char* argv[]) {
    cv::Mat img = cv::imread("photo.jpg");
    if (img.empty()) {
        std::cerr << "画像を読み込めませんでした" << std::endl;
        return 1;
    }

    cv::CascadeClassifier faceCls, eyeCls;
    // OpenCV インストール先の haarcascades ディレクトリを指定
    if (!faceCls.load("/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml") ||
        !eyeCls.load("/usr/share/opencv4/haarcascades/haarcascade_eye.xml")) {
        std::cerr << "カスケードファイルの読み込みに失敗しました" << std::endl;
        return 1;
    }

    cv::Mat gray;
    cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    cv::equalizeHist(gray, gray);

    std::vector<cv::Rect> faces;
    faceCls.detectMultiScale(gray, faces, 1.1, 5, 0, cv::Size(60, 60));

    for (const auto& face : faces) {
        cv::rectangle(img, face, cv::Scalar(0, 255, 0), 2);

        // 顔 ROI 内で目を検出
        cv::Mat faceRoi = gray(face);
        std::vector<cv::Rect> eyes;
        eyeCls.detectMultiScale(faceRoi, eyes, 1.1, 3, 0, cv::Size(20, 20));

        for (const auto& eye : eyes) {
            // 目の座標を元画像座標系に変換して描画
            cv::Rect eyeInImg(face.x + eye.x, face.y + eye.y, eye.width, eye.height);
            cv::rectangle(img, eyeInImg, cv::Scalar(255, 0, 0), 1);
        }
    }

    cv::imwrite("result_eye.jpg", img);
    std::cout << "検出完了: result_eye.jpg に保存しました" << std::endl;
    return 0;
}

ROI 座標は顔領域ローカルなので、描画時に face.x / face.y を加算して元画像座標に戻す点に注意してください。

例2: maxSize で画面に近い大きな顔を絞る

監視カメラなど「至近距離の顔だけを拾いたい」ケースでは minSize と maxSize で範囲を絞ると誤検出が減ります。

classifier.detectMultiScale(gray, faces, 1.05, 6, 0,
                            cv::Size(100, 100),   // minSize
                            cv::Size(400, 400));  // maxSize

scaleFactor を 1.05 に下げるとスケールステップが細かくなり精度が上がりますが、処理時間が増加します。本番では画像解像度と用途に応じて調整してください。


つまずきポイント

⚠️ load() が false を返す

最もよくあるミスはパスの指定誤りです。classifier.load() はファイルが見つからない場合でも例外を投げず false を返すだけなので、戻り値チェックを必ず行ってください。
OpenCV に同梱の XML ファイルはインストール先によってパスが異なります。

# Debian/Ubuntu 系の代表的なパス
/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml
# または pkg-config で確認
pkg-config --variable=prefix opencv4

⚠️ detectMultiScale に渡す画像がグレースケールでない

cv::CascadeClassifier::detectMultiScale はグレースケール(シングルチャンネル)画像を期待しています。BGR のまま渡すと実行時に assertion エラーが発生します。

cv::error: (-215:Assertion failed) image.depth() == CV_8U in function 'detectMultiScale'

cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY) を必ず通してから渡してください。

⚠️ minNeighbors の調整なしで誤検出が多発する

既定値 3 のままだと背景や衣服の模様を顔として誤検出しやすいです。実写に近い環境では 5〜8 程度に上げて運用し、今度は見逃しが増えすぎないか確認するという二段階チューニングが実務上の定石です。


関連する関数


まとめ

cv::CascadeClassifier は XML ファイルを load() して detectMultiScale() を呼ぶだけで顔検出が動作します。精度の鍵は グレースケール変換・scaleFactor・minNeighbors・minSize の4つで、用途に合わせて調整してください。目や顔部品の検出も同じクラスで連鎖的に実装できるため、弊社の顔周り SDK 開発でも現役で使用しています。

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

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

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

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