이번에는 지난번에 이어 응용편으로 꾸며 봤어요.
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);
}
}
cv::putText 관련되어서는 이전 강의를 참조하세요.
이렇게 알아낸 결과를 한번 보면, 아래와 같습니다.
전체 소스는 아래와 같습니다.
#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;
}