cv::Laplacian の使い方【OpenCV/C++】〜ラプラシアンフィルタでエッジを検出する〜

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

cv::Laplacian の使い方【OpenCV/C++】〜ラプラシアンフィルタでエッジを検出する〜

cv::Laplacian は画像の二次微分を計算してエッジを検出する関数です。Sobel フィルタのような一次微分と異なり、輝度の変化率そのものではなく「変化の急峻さ」に反応するため、細い線や点状の特徴を拾いやすい特性があります。

最小限の呼び出しはこれだけです。

cv::Laplacian(src, dst, CV_16S, 3);

動作環境

  • OpenCV 4.x
  • ビルド・実行例(Linux):
g++ -std=c++17 main.cpp `pkg-config --cflags --libs opencv4` -o main && ./main

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


基本の使い方

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

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_16S で符号付きに)
    cv::Mat lap16s;
    cv::Laplacian(blurred, lap16s, CV_16S, 3);

    // 絶対値を取って CV_8U に変換
    cv::Mat dst;
    cv::convertScaleAbs(lap16s, dst);

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

    return 0;
}

実行結果

保存しました: output.png

output.png を確認すると、元画像の輝度変化が急な箇所(輪郭・テクスチャの境界)が白く浮かび上がったエッジ画像になっています。背景のように輝度が均一な領域はほぼ黒になります。

コード解説

内容
cv::imread(..., IMREAD_GRAYSCALE) ラプラシアンはグレースケール画像に適用するのが基本
cv::GaussianBlur ノイズが残ったままだとノイズにも激しく反応するため、前処理は必須
cv::Laplacian(..., CV_16S, 3) 出力を符号付き 16bit に。CV_8U で受けると負の値が欠落する
cv::convertScaleAbs 符号付き値の絶対値を取り、表示・保存に使える CV_8U へ変換

引数と戻り値

void cv::Laplacian(
    InputArray  src,
    OutputArray dst,
    int         ddepth,
    int         ksize   = 1,
    double      scale   = 1,
    double      delta   = 0,
    int         borderType = BORDER_DEFAULT
);
引数 説明
src InputArray 入力画像(グレースケール推奨)
dst OutputArray 出力画像。サイズ・チャンネル数は src と同じ
ddepth int 出力のビット深度。CV_16S または CV_32F を推奨
ksize int カーネルサイズ(奇数)。1 の場合は 3×3 の単純なラプラシアンカーネル相当
scale double 計算結果に乗じるスケール係数(デフォルト 1)
delta double 計算結果に加算するオフセット値(デフォルト 0)
borderType int 境界処理方法。通常はデフォルトのまま

⚠️ ddepthCV_8U を指定すると負の微分値がすべてクリップされ、エッジが半分しか検出されません。必ず CV_16SCV_32F を使ってください。


実践例

実践例 1: エッジ強調画像の生成

エッジ画像を元画像に加算してシャープネスを強調するテクニックです。ラプラシアンの出力を元画像から引くことでシャープネス強調が得られます。

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

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::Mat lap16s;
    cv::Laplacian(blurred, lap16s, CV_16S, 3);

    // CV_16S → CV_32F に変換して元画像と差し引き
    cv::Mat lap32f, src32f, sharpened32f;
    lap16s.convertTo(lap32f, CV_32F);
    src.convertTo(src32f, CV_32F);

    // Laplacian of Gaussian (LoG) シャープネス強調: 元画像 - ラプラシアン
    sharpened32f = src32f - lap32f;

    // 値域クリップして CV_8U に変換
    cv::Mat sharpened;
    sharpened32f.convertTo(sharpened, CV_8U, 1.0, 0.0);

    cv::imwrite("sharpened.png", sharpened);
    std::cout << "シャープ画像を保存しました: sharpened.png" << std::endl;

    return 0;
}

実践例 2: カラー画像のエッジ検出

カラー画像に対してラプラシアンを適用する場合は、チャンネル別に処理するか、グレースケールに変換してから処理します。以下はグレースケール変換後に処理し、結果を擬似カラー表示する例です。

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

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

    // グレースケールに変換してからエッジ検出
    cv::Mat gray;
    cv::cvtColor(srcColor, gray, cv::COLOR_BGR2GRAY);

    cv::Mat blurred;
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);

    cv::Mat lap16s;
    cv::Laplacian(blurred, lap16s, CV_16S, 3);

    cv::Mat lapAbs;
    cv::convertScaleAbs(lap16s, lapAbs);

    // 擬似カラー表示
    cv::Mat colored;
    cv::applyColorMap(lapAbs, colored, cv::COLORMAP_JET);

    cv::imwrite("edge_colored.png", colored);
    std::cout << "擬似カラーエッジ画像を保存しました: edge_colored.png" << std::endl;

    return 0;
}

つまずきポイント

⚠️ ddepth を CV_8U にすると負のエッジが消える

ラプラシアンは二次微分なので、明るい領域から暗い領域への変化では負の値が出ます。ddepth = CV_8U のままだと負の値がゼロにクリップされ、エッジが片側しか検出されません。CV_16SCV_32F で受け、その後 cv::convertScaleAbs で絶対値変換するのが定石です。

⚠️ ノイズに非常に敏感

ラプラシアンは二次微分であるため、一次微分の Sobel よりもノイズへの感度が高くなります。事前のガウシアンブラーを省くと、センサーノイズや圧縮アーティファクトにも激しく反応し、エッジとノイズの区別がつかなくなります。cv::GaussianBlur との組み合わせは必須と考えてください。

⚠️ ksize=1 と ksize=3 の違い

ksize=1 は次の 3×3 カーネルを使います。

 0  1  0
 1 -4  1
 0  1  0

ksize=3 以上を指定すると内部で cv::Sobel を二回適用した近似カーネルに切り替わり、応答特性が変わります。用途に応じて比較検討してください。


関連する関数


まとめ

cv::Laplacian は二次微分によるエッジ検出フィルタです。ddepth は必ず CV_16SCV_32F を指定し、cv::convertScaleAbs で絶対値変換してから使います。ノイズへの感度が高いため、cv::GaussianBlur との前処理セットで使うことを強くおすすめします。

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

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

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

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