輪郭に関連した画像処理 (OpenCV3 C++)
[History] [Last Modified] (2020/02/07 14:47:48)

概要

cv::Canny などで検出したエッジをもとに cv::findContours で輪郭を計算できます。輪郭に関連した処理の例を記載します。

輪郭の描画

Uploaded Image

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

int main() {
    cv::Mat src = cv::imread("aaa.png", cv::IMREAD_COLOR);

    // グレースケールに変換
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    // エッジ検出はノイズの影響を受けやすいため平滑化しておきます。
    cv::Mat blur;
    cv::blur(gray, blur, cv::Size(3,3));

    // エッジ検出のアルゴリズムとして cv::Canny を利用します。
    cv::Mat canny;
    int thresh = 100;
    cv::Canny(blur, canny, thresh, thresh * 2);

    // cv::findContours は第一引数を破壊的に利用するため imshow 用に別変数を用意しておきます。
    cv::Mat canny2 = canny.clone();

    // cv::Point の配列として、輪郭を計算します。
    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(canny, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    std::cout << contours.size() << std::endl; //=> 36
    std::cout << contours[contours.size() - 1][0] << std::endl; //=> [154, 10]

    // 輪郭を可視化してみます。分かりやすさのため、乱数を利用して色付けします。
    cv::Mat drawing = cv::Mat::zeros(canny.size(), CV_8UC3);
    cv::RNG rng(12345);

    for( size_t i = 0; i< contours.size(); i++ ) {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256));
        cv::drawContours(drawing, contours, (int)i, color);
    }

    cv::imshow("src", src);
    cv::imshow("gray", gray);
    cv::imshow("blur", blur);
    cv::imshow("canny", canny2);
    cv::imshow("drawing", drawing);
    cv::waitKey(0);
    return 0;
}

バウンディングボックス、最小内包円

Uploaded Image

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

int main() {
    cv::Mat src = cv::imread("bbb.png", cv::IMREAD_COLOR);

    // 輪郭を計算します。
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::blur(gray, gray, cv::Size(3,3));
    cv::Mat canny;
    int thresh = 150;
    cv::Canny(gray, canny, thresh, thresh * 2);
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(canny, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    // 輪郭を描画します。
    cv::RNG rng(12345);
    cv::Mat dstContours = cv::Mat::zeros(canny.size(), CV_8UC3);
    for(size_t i = 0; i < contours.size(); i++) {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256));
        cv::drawContours(dstContours, contours, (int)i, color);
    }

    // 少ない点で輪郭を近似します。
    std::vector<std::vector<cv::Point> > contours_poly(contours.size());
    for(size_t i = 0; i < contours.size(); i++) {
        std::vector<cv::Point> poly;
        cv::approxPolyDP(contours[i], poly, 3, true);
        contours_poly[i] = poly;
    }

    cv::Mat dstContoursPoly = cv::Mat::zeros(canny.size(), CV_8UC3);
    for(size_t i = 0; i < contours.size(); i++) {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256));
        cv::drawContours(dstContoursPoly, contours_poly, (int)i, color);
    }

    std::cout << contours[0].size() << std::endl; //=> 9
    std::cout << contours_poly[0].size() << std::endl;//=> 2

    // x-y 軸に沿ったバウンディングボックス
    std::vector<cv::Rect> boundRect(contours.size());

    // 最小内包円
    std::vector<cv::Point2f>centers(contours.size());
    std::vector<float>radius(contours.size());

    for(size_t i = 0; i < contours.size(); i++) {
        boundRect[i] = cv::boundingRect(contours_poly[i]);
        cv::minEnclosingCircle(contours_poly[i], centers[i], radius[i]);
    }

    cv::Mat dst = src.clone();
    for( size_t i = 0; i< contours.size(); i++ ) {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0,256), rng.uniform(0,256));
        cv::rectangle(dst, boundRect[i].tl(), boundRect[i].br(), color, 2);
        cv::circle(dst, centers[i], (int)radius[i], color, 2);
    }

    cv::imshow("src", src);
    cv::imshow("dstContours", dstContours);
    cv::imshow("dstContoursPoly", dstContoursPoly);
    cv::imshow("dst", dst);
    cv::waitKey(0);
    return 0;
}

輪郭の凸包

Uploaded Image

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

int main() {
    cv::Mat src = cv::imread("ddd.png", cv::IMREAD_COLOR);

    // 輪郭を計算します。
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::blur(gray, gray, cv::Size(3,3));
    cv::Mat canny, canny2;
    cv::Canny(gray, canny, 5, 50);
    canny2 = canny.clone();
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(canny, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    // 凸包を計算します。
    std::vector<std::vector<cv::Point> > hull(contours.size());
    for(size_t i = 0; i < contours.size(); i++) {
        cv::convexHull(contours[i], hull[i]);
    }

    // 凸包であるかどうかの調査
    std::cout << cv::isContourConvex(contours[contours.size() - 1]) << std::endl; //=> 0
    std::cout << cv::isContourConvex(hull[contours.size() - 1]) << std::endl; //=> 1

    // 分かりやすさのため描画します。
    cv::Mat dst = cv::Mat::zeros(canny.size(), CV_8UC3);
    cv::RNG rng(12345);
    for(size_t i = 0; i< contours.size(); i++) {
        cv::Scalar color = cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
        // cv::drawContours(dst, contours, (int)i, color);
        cv::drawContours(dst, hull, (int)i, color, 3);
    }

    cv::imshow("src", src);
    cv::imshow("canny", canny2);
    cv::imshow("dst", dst);
    cv::waitKey(0);
    return 0;
}

輪郭の内部面積、長さ、モーメント

輪郭の内部面積と長さは、それぞれ cv::contourAreacv::arcLength で計算できます。面積についてはモーメントを計算することによっても得られます。モーメントは以下のように重心を計算したり傾きを補正する際に利用できます。

Uploaded Image

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

int main() {
    cv::Mat src = cv::imread("ddd.png", cv::IMREAD_COLOR);

    // 輪郭を計算します。
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    cv::blur(gray, gray, cv::Size(3,3));
    cv::Mat canny, canny2;
    cv::Canny(gray, canny, 5, 50);
    canny2 = canny.clone();
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours( canny, contours, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE );

    // モーメントの計算
    std::vector<cv::Moments> mu(contours.size());
    for( size_t i = 0; i < contours.size(); i++ ) {
        mu[i] = cv::moments(contours[i]);
    }

    // 重心の計算
    std::vector<cv::Point2f> mc(contours.size());
    for( size_t i = 0; i < contours.size(); i++ ) {
        //add 1e-5 to avoid division by zero
        mc[i] = cv::Point2f(mu[i].m10 / (mu[i].m00 + 1e-5),
                            mu[i].m01 / (mu[i].m00 + 1e-5));
    }

    // 輪郭の長さ、面積の計算
    for( size_t i = 0; i < contours.size(); i++ ) {
        std::cout << "Area (M_00): " << mu[i].m00 << std::endl;
        std::cout << "cv::contourArea: " << cv::contourArea(contours[i]) << std::endl;
        std::cout << "Length: " << cv::arcLength(contours[i], true) << std::endl;
    }

    // 輪郭、重心の描画
    cv::Mat dst = cv::Mat::zeros(canny.size(), CV_8UC3);
    cv::RNG rng(12345);
    for( size_t i = 0; i< contours.size(); i++ ) {
        cv::Scalar color = cv::Scalar( rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256));
        cv::drawContours(dst, contours, (int)i, color, 2);
        cv::circle(dst, mc[i], 4, color);
    }

    cv::imshow("src", src);
    cv::imshow("canny", canny2);
    cv::imshow("dst", dst);
    cv::waitKey(0);
    return 0;
}
Related pages