cv::Sobel の使い方【OpenCV/C++】〜Sobelフィルタで勾配を求める〜

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

cv::Sobel の使い方【OpenCV/C++】〜Sobelフィルタで勾配を求める〜

cv::Sobel は、画像の輝度勾配(エッジ強度・方向)を X 方向・Y 方向に分けて計算する関数です。エッジ検出の前処理や、方向依存の特徴抽出に広く使われます。最小限の呼び出しは次のとおりです。

cv::Sobel(src, dst, CV_16S, 1, 0, 3);  // X方向の1次微分(カーネルサイズ3)

動作環境

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

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


基本の使い方

X 方向・Y 方向それぞれの勾配を求め、合成して出力する基本パターンです。

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

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

    // ノイズ低減のため事前にガウシアンブラーを適用
    cv::Mat blurred;
    cv::GaussianBlur(src, blurred, cv::Size(3, 3), 0);

    // X・Y 方向の勾配を CV_16S で計算(符号付き16bit整数)
    cv::Mat grad_x, grad_y;
    cv::Sobel(blurred, grad_x, CV_16S, 1, 0, 3);  // dx=1, dy=0
    cv::Sobel(blurred, grad_y, CV_16S, 0, 1, 3);  // dx=0, dy=1

    // 絶対値を取って 8bit に変換
    cv::Mat abs_x, abs_y;
    cv::convertScaleAbs(grad_x, abs_x);
    cv::convertScaleAbs(grad_y, abs_y);

    // X・Y を合成してエッジ画像を生成
    cv::Mat grad;
    cv::addWeighted(abs_x, 0.5, abs_y, 0.5, 0, grad);

    cv::imwrite("output_sobel.png", grad);
    std::cout << "保存完了: output_sobel.png" << std::endl;
    return 0;
}

実行結果

保存完了: output_sobel.png

output_sobel.png を確認すると、元画像のエッジ(輝度が急変する輪郭部分)が白く浮かび上がった画像が得られます。背景の平坦な領域は黒に近い値になります。

処理の流れ

  1. GaussianBlur でノイズを除去してから Sobel を適用するのが定石です。ノイズがあると微分演算が不安定になります(cv::GaussianBlur の解説記事)。
  2. CV_16S(符号付き16bit整数) を出力型に指定します。8bit のまま受け取ると負の勾配値がクリップされて情報が失われます。
  3. cv::convertScaleAbs で絶対値を取りつつ 8bit にキャストします。
  4. cv::addWeighted で X・Y 成分を重み付き合成します。

引数と戻り値

引数 説明
src InputArray 入力画像(8bit/16bit/32bit浮動小数点)
dst OutputArray 出力画像(ddepth で指定した型)
ddepth int 出力の深度(CV_16S / CV_32F / -1 で入力と同型)
dx int X 方向の微分次数(0 or 1、dx+dy >= 1 が必須)
dy int Y 方向の微分次数(0 or 1、dx+dy >= 1 が必須)
ksize int Sobel カーネルサイズ(1 / 3 / 5 / 7、デフォルト 3
scale double 微分値に掛けるスケール係数(デフォルト 1
delta double 出力に加算するオフセット(デフォルト 0
borderType int 境界処理方法(デフォルト cv::BORDER_DEFAULT

戻り値はありません(dst に結果が書き込まれます)。

ddepth の選び方

用途 推奨型
後続で convertScaleAbs → 可視化 CV_16S
正確な勾配値を保持したい・合成演算を続ける CV_32F
cv::magnitude で勾配強度を求める CV_32F

実践例

例1: 浮動小数点で勾配強度マップを生成する

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

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

    cv::Mat blurred;
    cv::GaussianBlur(src, blurred, cv::Size(3, 3), 0);

    // CV_32F で勾配を計算
    cv::Mat grad_x, grad_y;
    cv::Sobel(blurred, grad_x, CV_32F, 1, 0, 3);
    cv::Sobel(blurred, grad_y, CV_32F, 0, 1, 3);

    // ユークリッド距離で勾配強度を計算
    cv::Mat magnitude;
    cv::magnitude(grad_x, grad_y, magnitude);

    // 0〜255 に正規化して保存
    cv::Mat magnitude_8u;
    cv::normalize(magnitude, magnitude_8u, 0, 255, cv::NORM_MINMAX, CV_8U);
    cv::imwrite("output_magnitude.png", magnitude_8u);

    std::cout << "保存完了: output_magnitude.png" << std::endl;
    return 0;
}

cv::magnitude を使うと √(Gx² + Gy²) を正確に計算できます。addWeighted による近似より精度が高く、特徴量計算に向いています。

例2: 水平エッジのみを強調する

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

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

    cv::Mat blurred;
    cv::GaussianBlur(src, blurred, cv::Size(3, 3), 0);

    // Y方向の勾配のみ(水平方向のエッジが反応する)
    cv::Mat grad_y;
    cv::Sobel(blurred, grad_y, CV_16S, 0, 1, 3);

    cv::Mat abs_y;
    cv::convertScaleAbs(grad_y, abs_y);
    cv::imwrite("output_horizontal_edge.png", abs_y);

    std::cout << "保存完了: output_horizontal_edge.png" << std::endl;
    return 0;
}

ドキュメント画像の罫線検出や、棚・段差の検出など、特定方向のエッジだけが欲しい場合は dx / dy を片方だけ 1 にします。


つまずきポイント

⚠️ ddepth を -1 や CV_8U にすると負の勾配が消える

cv::Sobel の結果は正負の値を持ちます。CV_8U(符号なし8bit)で受け取ると、負値が 0 にクリップされ、片方向のエッジが消えます。必ず CV_16S または CV_32F で受け取り、その後 cv::convertScaleAbs で絶対値変換してください。

// NG: 負の勾配が消える
cv::Sobel(src, dst, CV_8U, 1, 0, 3);

// OK
cv::Sobel(src, dst, CV_16S, 1, 0, 3);
cv::convertScaleAbs(dst, dst_abs);

⚠️ dx=0, dy=0 はアサーションエラーになる

dxdy の合計が 1 以上でなければなりません。両方 0 を渡すと実行時アサーションが発生します。「X も Y も計算したい」場合は、2 回に分けて呼び出してください。

⚠️ ksize=1 は Sobel ではなく 3×1(または 1×3)の単純差分になる

カーネルサイズ 1 を指定すると、Sobel カーネルの平滑化成分がなくなり、単純な隣接差分フィルタとして動作します。ノイズに弱くなるため、通常は 3(デフォルト)以上を使います。ksize=3 で感度不足を感じる場合は 57 を試してください。


関連する関数

  • cv::GaussianBlur — Sobel の前処理として必須に近い平滑化フィルタ。詳しくはcv::GaussianBlur の使い方を参照。
  • cv::medianBlur — 塩胡椒ノイズが多い場合はこちらが有効。cv::medianBlur の使い方も合わせてどうぞ。
  • cv::Canny — Sobel を内部で使用する高精度エッジ検出器。閾値で二値化されたエッジが欲しい場合はこちらが便利。
  • cv::magnitude — X・Y 勾配から勾配強度を正確に計算する関数。
  • cv::convertScaleAbs — Sobel 出力を 8bit 可視化画像に変換する際に使用。
  • cv::adaptiveThreshold — 勾配画像をさらに二値化する場合の選択肢。詳しくはcv::adaptiveThreshold の使い方を参照。

まとめ

  • cv::Sobel は X・Y 方向の輝度勾配を計算するフィルタで、エッジ検出の基本ブロックです。
  • 出力型は CV_16S または CV_32F を選び、cv::convertScaleAbs で可視化するのが定石です。
  • 事前に cv::GaussianBlur でノイズを除去することで、安定した結果が得られます。

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

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

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

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