cv::Canny の使い方【OpenCV/C++】〜エッジ検出の基本と閾値の決め方〜

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

cv::Canny の使い方【OpenCV/C++】〜エッジ検出の基本と閾値の決め方〜

cv::Canny はグレースケール画像からエッジを抽出する関数です。ヒステリシス閾値を用いた Canny エッジ検出アルゴリズムを1行で呼び出せます。

cv::Canny(src, edges, threshold1, threshold2);

動作環境

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

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


基本の使い方

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

int main()
{
    // グレースケールで読み込む
    cv::Mat src = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました" << std::endl;
        return -1;
    }

    // ノイズ除去のためにガウシアンブラーをかける
    cv::Mat blurred;
    cv::GaussianBlur(src, blurred, cv::Size(5, 5), 1.4);

    // Canny エッジ検出
    cv::Mat edges;
    cv::Canny(blurred, edges, 50.0, 150.0);

    // 結果を保存
    cv::imwrite("edges.jpg", edges);
    std::cout << "エッジ画像を edges.jpg に保存しました" << std::endl;

    return 0;
}

実行結果

エッジ画像を edges.jpg に保存しました

edges.jpg を確認すると、背景が黒・エッジ部分が白の2値画像が生成されています。

行ごとの解説

箇所 内容
cv::IMREAD_GRAYSCALE Canny はグレースケール入力が必須。カラー画像はここで変換する
cv::GaussianBlur 前処理でノイズを除去し、誤検出を減らす(後述)
threshold1 = 50.0 弱エッジの下限閾値。これ未満のエッジ候補は除外される
threshold2 = 150.0 強エッジの上限閾値。これ以上は確実にエッジと判定される
出力 edges CV_8UC1・255=エッジ、0=非エッジ の2値画像

引数と戻り値

void cv::Canny(
    InputArray  image,       // 入力: グレースケール画像(CV_8UC1)
    OutputArray edges,       // 出力: エッジ画像(CV_8UC1)
    double      threshold1,  // ヒステリシス閾値(低い方)
    double      threshold2,  // ヒステリシス閾値(高い方)
    int         apertureSize = 3,  // Sobel フィルタのカーネルサイズ(3/5/7)
    bool        L2gradient = false // 勾配強度の計算式(false: L1, true: L2)
);
引数 説明
image InputArray グレースケール入力画像(必須)
edges OutputArray エッジ出力画像。関数内で自動生成される
threshold1 double 低閾値。弱エッジの取捨選択に使用
threshold2 double 高閾値。強エッジの確定に使用
apertureSize int Sobel カーネルサイズ。デフォルト 3 で大抵OK
L2gradient bool true にすると精度が上がるが計算コスト増

閾値の比率の目安: threshold2 : threshold1 = 3 : 1 または 2 : 1 が一般的な出発点です。値を下げると細かいエッジも拾い、上げるとノイズが減ります。


実践例

例1: 大津の方法で閾値を自動決定する

閾値の手動調整が難しいケースでは、cv::threshold の大津法で推定した値を流用するテクニックが使えます。

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

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

    // 大津法で最適な閾値を取得(二値化画像は使わない)
    cv::Mat dummy;
    double otsu = cv::threshold(src, dummy, 0, 255,
                                cv::THRESH_BINARY | cv::THRESH_OTSU);

    // 大津閾値を基準に Canny の閾値を決定
    double low  = otsu * 0.5;
    double high = otsu;
    std::cout << "Otsu: " << otsu << "  low: " << low << "  high: " << high << std::endl;

    cv::Mat blurred, edges;
    cv::GaussianBlur(src, blurred, cv::Size(5, 5), 1.4);
    cv::Canny(blurred, edges, low, high);

    cv::imwrite("edges_otsu.jpg", edges);
    std::cout << "保存完了: edges_otsu.jpg" << std::endl;

    return 0;
}
Otsu: 112  low: 56  high: 112
保存完了: edges_otsu.jpg

画像ごとに閾値を手動調整する手間を省けます。大津法の詳細は cv::threshold の使い方【OpenCV/C++】〜二値化と大津の方法〜 を参照してください。


例2: カラー画像のエッジを可視化する

エッジ検出後、元のカラー画像に重ねて表示したい場合の実装例です。

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

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

    // グレースケールに変換してから Canny を適用
    cv::Mat gray, blurred, edges;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 1.4);
    cv::Canny(blurred, edges, 50.0, 150.0);

    // エッジを緑色でカラー画像に重ねる
    cv::Mat overlay = src.clone();
    overlay.setTo(cv::Scalar(0, 255, 0), edges);  // edges が mask 代わり

    cv::imwrite("overlay.jpg", overlay);
    std::cout << "保存完了: overlay.jpg" << std::endl;

    return 0;
}
保存完了: overlay.jpg

overlay.jpg では、元画像の上にエッジ部分だけが緑色で描画されます。


つまずきポイント

⚠️ カラー画像をそのまま渡すと assertion エラーになる

cv::Canny の入力は CV_8UC1(グレースケール)限定です。CV_8UC3 のカラー画像を渡すと実行時に以下のエラーが出ます。

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

cv::imread でカラー読み込みした場合は cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY) で変換してから渡してください。


⚠️ 前処理なしで Canny をかけると誤検出が多発する

カメラ画像や JPEG 圧縮を経た画像にはノイズが多く含まれます。前処理なしで Canny を適用するとノイズ部分もエッジとして検出され、結果がざらついた印象になります。

ブラーのカーネルサイズを大きくするほどノイズは減りますが、細いエッジも潰れるのでトレードオフに注意してください。


⚠️ apertureSize を変えると閾値の効き方が変わる

apertureSize を 3→5→7 と大きくすると Sobel フィルタのスケールが変わり、同じ閾値でも検出されるエッジ数が変化します。apertureSize を変えたときは閾値の再調整が必要です。


関連する関数


まとめ

cv::Canny は前処理(GaussianBlur/medianBlur)と組み合わせることで安定したエッジ検出が可能です。閾値の出発点は threshold2 : threshold1 = 3 : 1 か大津法の流用が実務的です。入力は必ずグレースケール(CV_8UC1)で渡してください。

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

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

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

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