OpenCV

【C++】OpenCVで実現する画像輪郭検出の基本手法と実装サンプル

C++とOpenCVを使うと、画像から輪郭を高精度に抽出できる仕組みが実現できます。

まずグレースケール変換や平滑化で画像のノイズを抑え、Cannyエッジ検出でエッジを抽出します。

さらにfindContoursで輪郭を取得し、drawContoursで描画することで検出結果を視認できるため、シンプルなコードで効果的な輪郭検出が可能です。

画像前処理の手法

画像の品質向上と計測精度アップを目指して、画像の前処理が重要な役割を果たします。

色の情報を簡略化し、不要なノイズを減らす手法を順に見ていきます。

グレースケール変換の効果

カラー画像は情報量が多いため、輪郭検出の前にグレースケール変換を行うと計算量が軽減されます。

RGBの各チャンネルから輝度のみを抽出することで、エッジ検出に集中できるようになり、処理がスムーズになります。

平滑化によるノイズ除去

画像内の不要なノイズはエッジ検出の精度に悪影響を与えることがあるため、平滑化処理を実施します。

平滑化を行うと、細かいノイズが減少し、主要な輪郭が際立つようになります。

Gaussian Blurの適用

Gaussian Blurは連続的なカーネルを用いて画像をぼかす方法です。

ノイズの影響を柔らかく吸収し、エッジ検出前の下準備に適しているため、広く利用されています。

たとえば、以下のサンプルコードでは、画像をグレースケールに変換後、Gaussian Blurを適用しています。

#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
    // 画像の読み込み
    cv::Mat src = cv::imread("image.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    // グレースケール変換
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    // Gaussian Blurによる平滑化
    cv::Mat gaussianBlurred;
    cv::GaussianBlur(gray, gaussianBlurred, cv::Size(5, 5), 0);
    // 結果を表示
    cv::imshow("Gaussian Blur", gaussianBlurred);
    cv::waitKey(0);
    return 0;
}
※ 実行すると「Gaussian Blur」というウィンドウが表示され、平滑化されたグレースケール画像が確認できます。

Median Blurとの比較

Median Blurは各画素の周囲の中央値を採用するため、スポット状のノイズに対して強い特徴があります。

Gaussian Blurとの違いは、エッジ部分の保存に工夫がある点です。

目的に応じて、どちらの平滑化手法を採用するか選択するのがおすすめです。

Cannyエッジ検出でのエッジ抽出

画像の輪郭抽出において、Cannyエッジ検出は非常に人気のあるアルゴリズムです。

処理の流れや閾値の設定方法に注目すると、精度の向上につながります。

Cannyエッジ検出の基本原理

Cannyエッジ検出は、勾配情報を基にエッジ部分を強調する手法です。

画像の輝度変化を計算し、急激な変化がある場所のみ抽出するため、背景やノイズから重要な部分を際立たせます。

数学的には、以下のような式でエッジの強さを表すことができます。

G=(Gx)2+(Gy)2

ここで、GxGyは横方向と縦方向の輝度勾配です。

閾値設定のポイント

Cannyエッジ検出では、2つの閾値設定が鍵となります。

これにより、弱いエッジと強いエッジの判定が行われ、エッジ候補の選別が行われます。

高閾値と低閾値の使い分け

高閾値はエッジ候補の中でも特に強い部分のみを採用し、低閾値は隣接する弱いエッジを含めるために利用されます。

両者の組み合わせで、連続したエッジを保ちながらノイズを排除する工夫が行われます。

実用上の設定例

一般的には、低閾値に50、 高閾値に150といった設定例がよく使われます。

これにより、エッジ検出の際に適度な数のエッジが抽出され、後続の輪郭解析が円滑に進むように工夫されています。

輪郭検出アルゴリズムの理解

画像内のエッジから輪郭を検出する手法では、OpenCVのfindContours関数が中心的な役割を果たします。

輪郭検出の流れやパラメータの設定次第で、最終的な解析結果に大きな影響を与えます。

findContours関数の概要

findContours関数は、2値化された画像から連結成分の輪郭情報を抽出します。

画像内のエッジ部分をリストとして取得するため、後続の処理にスムーズに引き継げる強力な関数です。

パラメータにより、階層構造や輪郭の近似方法を設定できます。

輪郭抽出の手順

輪郭抽出では、各手順の理解が重要です。

エッジ画像から輪郭リストを生成する過程で、多くの設定が可能です。

輪郭のリスト化と階層情報

関数は輪郭の位置情報とともに、輪郭間の階層情報も取得できます。

階層情報を活用すると、主な輪郭と穴の関係を明確に把握することが可能です。

輪郭近似手法の選択

輪郭情報を最適な形状に簡略化するための近似手法がいくつか存在します。

画像の内容や用途に最適な手法を採用することで、解析や描画に適したデータが得られます。

CHAIN_APPROX_SIMPLEの特徴

CHAIN_APPROX_SIMPLEは、不要な点を省略し、輪郭の重要な点のみを保持する手法です。

これにより、メモリ負担が軽減され、処理速度が向上します。

例えば、直線状のエッジでは冗長なポイントが減り、結果として輪郭の形が簡潔に表現されます。

他の近似手法との違い

他の近似手法として、CHAIN_APPROX_NONEがあり、すべての輪郭点を保持するため、詳細な情報を必要とする場合に用いられます。

用途や解析の目的に合わせて、どちらの手法を使うか選択するとよいでしょう。

輪郭描画と視覚的評価

輪郭検出後の視覚的な確認は、処理結果を評価するうえで非常に重要です。

描画方法により結果が直感的に把握でき、後続の処理の指針にもなります。

drawContoursによる描画方法

drawContours関数を使うと、検出した輪郭を元画像に重ねて表示することができます。

以下のコード例は、画像に検出された輪郭を描画する基本的な方法を示しています。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
int main() {
    // 画像の読み込みと前処理
    cv::Mat src = cv::imread("image.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::Mat blurred;
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
    cv::Mat edges;
    cv::Canny(blurred, edges, 50, 150);
    // 輪郭検出
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    // 輪郭の描画
    cv::Mat result = src.clone();
    cv::RNG rng(12345);
    for (size_t i = 0; i < contours.size(); i++) {
        cv::Scalar color(rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256));
        cv::drawContours(result, contours, static_cast<int>(i), color, 2);
    }
    // 結果を表示
    cv::imshow("Detected Contours", result);
    cv::waitKey(0);
    return 0;
}
※ 実行すると、元画像にランダムな色で描画された輪郭が確認できるウィンドウが表示されます。

ランダムな色付けの効果

ランダムな色付けを適用すると、複数の輪郭が重なった場合でも個々の輪郭が区別しやすくなります。

色の変化により、各輪郭の形状や階層関係が直感的に理解でき、視覚評価が容易になります。

輪郭情報の解析と応用

輪郭情報からさらなる解析を行うと、画像内の物体の特徴や形状を定量的に評価することが可能です。

面積、周囲長、そして幾何学的なモーメントなどを計測することで、詳細な解析につなげられます。

輪郭面積の計測手法

contourArea関数を使って、各輪郭に囲まれた領域の面積を算出できます。

面積情報は物体の大きさや形状の特徴の把握に非常に有用です。

たとえば、以下のコード断片は各輪郭の面積を計算して出力する例です。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
int main() {
    cv::Mat src = cv::imread("image.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::Mat gray, blurred, edges;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
    cv::Canny(blurred, edges, 50, 150);
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    for (size_t i = 0; i < contours.size(); i++) {
        double area = cv::contourArea(contours[i]);
        std::cout << "輪郭 " << i << ": 面積 = " << area << std::endl;
    }
    return 0;
}
輪郭 0: 面積 = 0
輪郭 1: 面積 = 2
輪郭 2: 面積 = 3
輪郭 3: 面積 = 35.5
輪郭 4: 面積 = 6
輪郭 5: 面積 = 5
輪郭 6: 面積 = 157.5
輪郭 7: 面積 = 105.5
...

輪郭周囲長の取得方法

輪郭の周囲長はarcLength関数を利用して取得できます。

輪郭の周囲長は、図形の形状の粗さや複雑さを評価する際に役立ちます。

arcLength関数の役割

arcLength関数は、輪郭上の各点間の距離を合計することで、輪郭の周囲長を計算する仕組みです。

閉じた輪郭かどうかを引数で指定でき、用途に合わせて適切な計測が可能です。

モーメント計算による形状解析

画像の形状解析において、モーメント計算を用いると重心や面積分布を求めることができます。

この情報は、回転やスケール変換に対して不変な特徴量として利用でき、物体認識にとても役立ちます。

モーメント計算の手法と応用例

OpenCVのmoments関数を使用すると、輪郭に対してモーメントを算出できます。

算出したモーメントから、重心(中心点)を求める場合は以下の式が利用できます。

xcenter=m10m00,ycenter=m01m00

ここで、miji次、j次のモーメントです。

以下のサンプルコードは、輪郭のモーメントを計算し、重心を画像上に描画する例です。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
int main() {
    cv::Mat src = cv::imread("image.png", cv::IMREAD_COLOR);
    if (src.empty()) {
        std::cerr << "画像の読み込みに失敗しました。" << std::endl;
        return -1;
    }
    cv::Mat gray, blurred, edges;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);
    cv::Canny(blurred, edges, 50, 150);
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edges, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
    cv::Mat result = src.clone();
    for (size_t i = 0; i < contours.size(); i++) {
        cv::Moments mu = cv::moments(contours[i]);
        // 輪郭の面積がゼロでない場合にのみ重心を計算
        if (mu.m00 != 0) {
            int cx = static_cast<int>(mu.m10 / mu.m00);
            int cy = static_cast<int>(mu.m01 / mu.m00);
            // 重心に円を描画
            cv::circle(result, cv::Point(cx, cy), 4, cv::Scalar(0, 0, 255), -1);
        }
    }
    cv::imshow("Contours with Centroids", result);
    cv::waitKey(0);
    return 0;
}
※ 実行すると、元画像に各輪郭の重心位置が赤い円で表示されます。

まとめ

今回の内容では、画像の前処理からエッジ検出、輪郭検出、描画、そして輪郭情報の解析まで、各工程の手法や選択のポイントについて触れました。

各処理ごとに適切なアルゴリズムを用いることで、画像内の重要な形状情報を抽出できることが分かりました。

各サンプルコードを参考にして、自身の画像処理プログラムに応用してもらえれば嬉しいです。

関連記事

Back to top button
目次へ