본문 바로가기

프로그래밍 강좌/C++ - OpenCV

[OpenCV][C++] 영상 라벨링(Labeling) 총정리(2) - 동전 개수 세기 총 금액 알아내기 image 레이블링 인덱싱 indexing 원 circle count

이번에는 지난번에 이어 응용편으로 꾸며 봤어요.

https://m.blog.naver.com/dorergiverny/223075828736

영상 라벨링을 통한 동전 개수 세기와 총 금액 알아내기를 해 볼 꺼에요.

 

동전 개수 총 금액 알아내기

1. 영상 로딩하기

동전 개수를 알아내기 위해서는 먼저 영상을 로딩합니다.

어짜피 gray-scale로 변환을 해야 해서 gray-scale로 로딩을 하였습니다.

cv::Mat src = cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE);
 

2. 영상 사이즈 조절하기

영상 사이즈가 커서 일단 사이즈를 줄입니다.

cv::resize(src, src, cv::Size(src.cols / 4, src.rows / 4));
 

3. median 필터링 하기

영상에 salt&pepper noise가 있어서 제거를 합니다. 배경 일부를 확대해 봤어요.

cv::Mat median;
cv::medianBlur(src, median, 5);
 

4. 이진화 하기

cv::threshold를 이용하여 binarization을 하였습니다.

cv::Mat bin;
cv::threshold(median, bin, 120, 255, cv::THRESH_BINARY);
 

5. 이진화 노이즈 제거하기

오른쪽 하단에 보면 흰색 노이즈가 있고, 동전 안쪽에 보면 검은색 노이즈들이 있어요.

이걸 없애려고 morphology연산을 사용하였습니다.

cv::Mat open, close;
cv::Mat element_open = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::Mat element_close = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(31, 31));

cv::morphologyEx(bin, open, cv::MORPH_OPEN, element_open);
cv::morphologyEx(open, close, cv::MORPH_CLOSE, element_close);
 

6. 레이블링 하기

CCL 방법으로 라벨링(레이블링)을 수행합니다.

여기에서 크기를 알기 위해서 connectedComponentsWithStats()을 사용합니다.

동전 총 개수 num -1 이 됩니다.

cv::Mat label, stats, centroids;
int num = cv::connectedComponentsWithStats(close, label, stats, centroids, 8, CV_32S);
 

영상에 결과를 표시하기 위해 영상의 컬러 버전을 만듭니다.

cv::Mat src_color;
cv::cvtColor(src, src_color, cv::COLOR_GRAY2BGR);
 

8. 객체 알아내기/표시하기

객체 개수만큼 순회하면서 각 객체들의 중심 값(centroid)과 반지름을 구합니다.

반지름은 boundingRect의 높이/2 로 구했어요.

반지름의 크기를 보고 10원짜리인지, 100원짜리인지, 500원짜리인지를 구분합니다.

현재 영상에서는 10원짜리 반지름이 약 60 pixel이고, 100원짜리가 80 pixel, 그리고

500원짜리 반지름이 90 pixel 정도 되었습니다.

(카메라마다, 영상마다 다르기 때문에 절대값을 사용하지는 마세요 현재 영상에서만 적용되는 거에요.)

반지름의 크기를 보고 결정할 수도 있지만 면적(area)를 보고도 구분할 수 있어요.

총 금액은 10원짜리, 100원짜리, 500원짜리 개수로 계산을 합니다.

cv::RNG rng(12345);
int coin_10 = 0, coin_100 = 0, coin_500 = 0;
for (int i = 1; i < num; ++i) {
    int* p = stats.ptr<int>(i);
    double* c = centroids.ptr<double>(i);
                
    int radius = static_cast<int>(p[2] / 2);

    cv::circle(src_color, cv::Point((int)c[0], (int)c[1]), radius, 
               cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)), 4);

    if (radius < 65) {
        coin_10++;
        cv::putText(src_color, "10", cv::Point((int)c[0], (int)c[1]), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
    }
    else if (radius < 82) {
        coin_100++;
        cv::putText(src_color, "100", cv::Point((int)c[0], (int)c[1]), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
    }
    else {
        coin_500++;
        cv::putText(src_color, "500", cv::Point((int)c[0], (int)c[1]), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
    }        
}
 

전체 소스는 아래와 같습니다.

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

int main()
{    
    cv::Mat src = cv::imread("../coins.jpg", cv::IMREAD_GRAYSCALE);
    cv::resize(src, src, cv::Size(src.cols / 4, src.rows / 4));

    cv::Mat median;
    cv::medianBlur(src, median, 5);

    cv::Mat bin;
    cv::threshold(median, bin, 120, 255, cv::THRESH_BINARY);

    cv::Mat open, close;
    cv::Mat element_open = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
    cv::Mat element_close = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(31, 31));

    cv::morphologyEx(bin, open, cv::MORPH_OPEN, element_open);
    cv::morphologyEx(open, close, cv::MORPH_CLOSE, element_close);

    cv::Mat label, stats, centroids;
    int num = cv::connectedComponentsWithStats(close, label, stats, centroids, 8, CV_32S);
    cv::Mat src_color;
    cv::cvtColor(src, src_color, cv::COLOR_GRAY2BGR);

    cv::RNG rng(12345);
    int coin_10 = 0, coin_100 = 0, coin_500 = 0;
    for (int i = 1; i < num; ++i) {
        int* p = stats.ptr<int>(i);
        double* c = centroids.ptr<double>(i);
                
        int radius = static_cast<int>(p[2] / 2);

        cv::circle(src_color, cv::Point((int)c[0], (int)c[1]), radius, 
                   cv::Scalar(rng.uniform(0, 256), rng.uniform(0, 256), rng.uniform(0, 256)), 4);

        if (radius < 65) {
            coin_10++;
            cv::putText(src_color, "10", cv::Point((int)c[0], (int)c[1]), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
        }
        else if (radius < 82) {
            coin_100++;
            cv::putText(src_color, "100", cv::Point((int)c[0], (int)c[1]), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
        }
        else {
            coin_500++;
            cv::putText(src_color, "500", cv::Point((int)c[0], (int)c[1]), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);
        }        
    }
    int coinNum = num - 1;
    std::string strCoinNum = "CoinNum: " + std::to_string(coinNum);
    cv::putText(src_color, strCoinNum, cv::Point(10, 50), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(0, 255, 0), 2);
    int total = 10 * coin_10 + 100 * coin_100 + 500 * coin_500;
    std::string strTotal = "Total: " + std::to_string(total) + " Won";
    cv::putText(src_color, strTotal, cv::Point(10, 100), cv::FONT_HERSHEY_COMPLEX, 1, cv::Scalar(255, 0, 0), 2);
    return 0;
}