cv::morphologyEx の使い方【OpenCV/C++】〜オープニング・クロージングでノイズ除去〜

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

cv::morphologyEx の使い方【OpenCV/C++】〜オープニング・クロージングでノイズ除去〜

cv::morphologyEx は、膨張・収縮を組み合わせたモルフォロジー変換をまとめて実行する関数です。オープニング(小さなノイズ除去)・クロージング(小さな穴埋め)・勾配抽出など6種類の操作を1回の呼び出しで行えます。

最小の呼び出し例は次のとおりです。

cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(src, dst, cv::MORPH_OPEN, kernel);

動作環境

  • 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.png", cv::IMREAD_GRAYSCALE);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }

    // 構造要素(カーネル)を作成: 5x5 の矩形
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));

    // オープニング: 収縮 → 膨張(小さなノイズを除去)
    cv::Mat dst_open;
    cv::morphologyEx(src, dst_open, cv::MORPH_OPEN, kernel);

    // クロージング: 膨張 → 収縮(小さな穴・隙間を埋める)
    cv::Mat dst_close;
    cv::morphologyEx(src, dst_close, cv::MORPH_CLOSE, kernel);

    cv::imwrite("result_open.png",  dst_open);
    cv::imwrite("result_close.png", dst_close);

    std::cout << "処理完了: result_open.png / result_close.png" << std::endl;
    return 0;
}

実行結果

処理完了: result_open.png / result_close.png

カレントディレクトリに result_open.png(白いゴミ点が消えた画像)と result_close.png(白い領域の小さな穴が埋まった画像)が出力されます。

各行の解説

処理 内容
cv::imread(..., IMREAD_GRAYSCALE) グレースケールで読み込む。二値画像に対して使うことが多い
cv::getStructuringElement カーネル形状を作成。MORPH_RECT・MORPH_ELLIPSE・MORPH_CROSS から選ぶ
cv::morphologyEx(..., cv::MORPH_OPEN, ...) 収縮→膨張。白い孤立点(塩ノイズ相当)を除去する
cv::morphologyEx(..., cv::MORPH_CLOSE, ...) 膨張→収縮。白い領域内の小さな黒い穴を埋める

引数と戻り値

void cv::morphologyEx(
    InputArray  src,        // 入力画像
    OutputArray dst,        // 出力画像(src と同サイズ・同型)
    int         op,         // 変換の種類(下表参照)
    InputArray  kernel,     // 構造要素(getStructuringElement で生成)
    Point       anchor      = Point(-1,-1),  // カーネルのアンカー点(-1,-1 で中心)
    int         iterations  = 1,             // 繰り返し回数
    int         borderType  = BORDER_CONSTANT,
    const Scalar& borderValue = morphologyDefaultBorderValue()
);

op の主な選択肢

定数 内容
cv::MORPH_ERODE 収縮
cv::MORPH_DILATE 膨張
cv::MORPH_OPEN オープニング(収縮→膨張)
cv::MORPH_CLOSE クロージング(膨張→収縮)
cv::MORPH_GRADIENT モルフォロジー勾配(膨張-収縮)。エッジ抽出に使う
cv::MORPH_TOPHAT トップハット(元画像-オープニング)。明るい局所特徴抽出
cv::MORPH_BLACKHAT ブラックハット(クロージング-元画像)。暗い局所特徴抽出

iterations を増やすと、1回のカーネル適用を繰り返します。カーネルサイズを大きくするのと似た効果ですが、大きいカーネルより計算コストを抑えられる場合があります。


実践例

実践例①:二値化後のノイズ除去パイプライン

カメラ画像を二値化した後、オープニングで白いゴミを取り、クロージングで文字・図形の隙間を埋める定番パターンです。

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

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

    // 大津の方法で二値化
    cv::Mat binary;
    cv::threshold(src, binary, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);

    // 楕円カーネル: 文字の丸みに合わせて MORPH_ELLIPSE を選択
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(3, 3));

    // オープニングでノイズ除去
    cv::Mat opened;
    cv::morphologyEx(binary, opened, cv::MORPH_OPEN, kernel, cv::Point(-1, -1), 2);

    // クロージングで文字内の穴埋め
    cv::Mat result;
    cv::morphologyEx(opened, result, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1), 2);

    cv::imwrite("result_clean.png", result);
    std::cout << "クリーンアップ完了: result_clean.png" << std::endl;
    return 0;
}

iterations = 2 を指定し、カーネルを2回適用することで効果を強めています。カーネルサイズを大きくするより、小さいカーネルを複数回適用した方が輪郭が崩れにくい傾向があります。


実践例②:モルフォロジー勾配によるエッジ抽出

MORPH_GRADIENT は「膨張画像-収縮画像」を計算し、物体の輪郭(エッジ)を取り出します。

#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 kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));

    cv::Mat gradient;
    cv::morphologyEx(src, gradient, cv::MORPH_GRADIENT, kernel);

    cv::imwrite("result_gradient.png", gradient);
    std::cout << "エッジ抽出完了: result_gradient.png" << std::endl;
    return 0;
}

物体の境界が白く浮かび上がった画像が出力されます。Canny エッジとは異なり、閾値チューニングなしで安定したエッジを得やすいのが特徴です。


つまずきポイント

⚠️ カラー画像に適用すると各チャンネルに独立して処理される

cv::morphologyEx はカラー画像(CV_8UC3)にも適用できますが、BGR 各チャンネルに独立して膨張・収縮が走ります。二値化マスクに対して適用するつもりが、うっかりカラー画像のまま渡してしまうケースに注意してください。意図した結果にならない場合は cv::cvtColor でグレースケールに変換してから処理しましょう。

⚠️ iterations はカーネル適用の繰り返し回数であり、op の繰り返しではない

MORPH_OPENiterations=2 とした場合、「(収縮×2)→(膨張×2)」が行われます。「オープニングを2回」ではない点に注意してください。より強力にノイズを取りたい場合はカーネルサイズを大きくするか、morphologyEx 自体を2回呼び出す方が意図通りになります。

⚠️ Windows で Debug/Release・アーキテクチャを取り違えるとリンクエラーになる

Visual Studio でリンクするライブラリが opencv_world4xx.lib(Release)と opencv_world4xxd.lib(Debug)で異なります。プロジェクト構成を切り替えた後にリンクエラーが出たら、プロパティ → リンカー → 入力のライブラリ名を確認してください。詳細はopencv_world の使い方とリンク方法【OpenCV/C++ Windows】を参照してください。


関連する関数


まとめ

cv::morphologyEx は、オープニング・クロージング・勾配など6種類のモルフォロジー変換を1関数で呼び出せる実装上の要所です。二値化後のノイズ除去・穴埋めパイプラインにそのまま組み込めるため、実務でも使用頻度が高い関数です。カーネル形状(MORPH_RECT / MORPH_ELLIPSE)と iterations の調整で、後段の処理精度が大きく変わる点を押さえておきましょう。

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

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

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

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