[이미지 - 동전 검출]
영상을 읽어와 동전별로 검출한 후, 값을 결과로 출력하는 프로그램
우선 위 이미지가 입력으로 프로그램에 들어오게 된다
따라서 프로그램이 10원과 100원 500원을 각각 검출하여 동전의 총합을 구하는 프로그램을 구성했다.
Mat image = imread("../_res/coin.jpg", IMREAD_GRAYSCALE);
CV_Assert(!image.empty());
#우선 Mat객체에 이미지를 읽어와 작업환경을 생성합니다. 이미지가 들어 있는 폴더의 경로를 잘 설정한다.
흑백 (그레이 스케일)으로 읽어온다.
Mat EdgeImage(image.size(), CV_8U, Scalar(0));
CannyEdgeDetection(&image, &EdgeImage, 4.5, 30.0, 60.0);
#엣지 이미지를 따올 Mat객체를 생성하고
케니 엣지를 적용하여 엣지를 detect한 후 저장한다.
int c10, c100, c500;
CircleDetection(&EdgeImage, 35, 36, 1, 0.6, &findCircle);
c10 = findCircle.size();
CircleDetection(&EdgeImage, 42, 43, 1, 0.6, &findCircle);
c100 = findCircle.size() - c10;
CircleDetection(&EdgeImage, 51, 52, 1, 0.6, &findCircle);
c500 = findCircle.size() - c10 - c100;
#허프 변환 원검출을 통하여 10원과 100원 500원 사이즈에 맞게 원을 검출합니다.
그리고 findCircle()함수를 통하여 갯수를 변수에 누적
imshow("원본", image);
imshow("엣지 찾기", EdgeImage);
#결과를 출력하면
정말 이쁘게 각각의 동전을 검출함.
동전의 금액 결과 까지 콘솔창에 뿌려준다.
[함수 정의]
openCV라이브러리를 사용하지 않고 검출에 필요한 개념에 맞게 직접 함수를 구현하여 해결.
func.cpp에 정의된 내용.
케니 엣지 함수
void CannyEdgeDetection(Mat* image, Mat* edgeimg, double sigma, double th_low, double th_high)
Mat gauss(image->size(), CV_8U, Scalar(0));
GaussianFiltering(image, &gauss, sigma);
#우선 가우시안 필터링을 사용하여 영상의 잡음을 제거한다
Mat gradX(image->size(), CV_64F, Scalar(0));
Mat gradY(image->size(), CV_64F, Scalar(0));
Mat gradMag(image->size(), CV_64F, Scalar(0));
for (j = 1; j < h - 1; j++)
{
for (i = 1; i < w - 1; i++)
{
gradX.at(j, i) = -gauss.at(j - 1, i - 1) - 2.0*gauss.at(j, i - 1) - gauss.at(j + 1, i - 1)
+ gauss.at(j - 1, i + 1) + 2.0*gauss.at(j, i + 1) + gauss.at(j + 1, i + 1);
gradY.at(j, i) = -gauss.at(j - 1, i - 1) - 2.0*gauss.at(j - 1, i) - gauss.at(j - 1, i + 1)
+ gauss.at(j + 1, i - 1) + 2.0*gauss.at(j + 1, i) + gauss.at(j + 1, i + 1);
double gx = gradX.at(j, i);
double gy = gradY.at(j, i);
gradMag.at(j, i) = sqrt(gx*gx + gy * gy);
}
}
#그 다음 gradient를 계산하는 작업을 한다. (크기와 방향)
if (mag > th_low)
{
// 그래디언트 방향 계산 (4개 구역)
if (gx != .0)
{
ang = atan2(gy, gx) * 180 / PI;
if (((ang >= -22.5f) && (ang < 22.5f)) || (ang >= 157.5f) || (ang < -157.5f))
district = AREA0;
else if (((ang >= 22.5f) && (ang < 67.5f)) || ((ang >= -157.5f) && (ang < -112.5f)))
district = AREA45;
else if (((ang >= 67.5) && (ang < 112.5)) || ((ang >= -112.5) && (ang < -67.5)))
district = AREA90;
else
district = AREA135;
}
else
district = AREA90;
#위의 식처럼 비최대 억제를 구한다. 코드가 길어 다 올리는것은 효율이 떨어진다고 생각한다.
while (!strong_edges.empty())
{
Point p = strong_edges.back();
strong_edges.pop_back();
int x = p.x, y = p.y;
// 강한 엣지 주변의 약한 엣지는 최종 엣지(강한 엣지)로 설정
check_weak_edge(edgeimg, &strong_edges, x + 1, y);
check_weak_edge(edgeimg, &strong_edges, x + 1, y + 1);
check_weak_edge(edgeimg, &strong_edges, x, y + 1);
check_weak_edge(edgeimg, &strong_edges, x - 1, y + 1);
check_weak_edge(edgeimg, &strong_edges, x - 1, y);
check_weak_edge(edgeimg, &strong_edges, x - 1, y - 1);
check_weak_edge(edgeimg, &strong_edges, x, y - 1);
check_weak_edge(edgeimg, &strong_edges, x + 1, y - 1);
}
#히스테리시스 엣지 트래킹을 사용하여 약한 엣지와 강한 엣지를 파악한다.
허프변환 원검출
원 검출의 코드 또한 어마어마하다.
void CircleDetection(Mat* pSrc, int minradius, int maxradius, int radiusoffset, double ratio, vector* pvecCircle)
int ksize = 15;
int halfksize = ksize >> 1;
int minarea = (int)(PI*(minradius + maxradius)*ratio + .5);
for (int y = halfksize; y < h - halfksize; y++)
{
for (int x = halfksize; x < w - halfksize; x++)
{
int val = curmask.at(y, x);
bool bMax = true;
int nMax = 0;
if (val < minarea) continue;
for (int n = -halfksize; n <= halfksize; n++)
{
for (int m = -halfksize; m <= halfksize; m++)
{
if (n == 0 && m == 0) continue;
if (val < curmask.at(y + n, x + m))
{
bMax = false;
break;
}
}
if (bMax == false) break;
}
if (bMax)
{
nMax = curmask.at(y, x);
for (int n = -halfksize; n <= halfksize; n++)
{
for (int m = -halfksize; m <= halfksize; m++)
{
curmask.at(y + n, x + m) = 0;
}
}
curmask.at(y, x) = nMax;
}
}
}
#마스크를 통하여 원을 검출한다. 코드 부분을 삽입한 것.
for (int i = 0; i < circnt; i++)
{
if (34 <= pvecCircle->at(i)._radius && pvecCircle->at(i)._radius <= 37)
circle(resultcolor, pvecCircle->at(i)._center, pvecCircle->at(i)._radius, Scalar(0, 255, 255), 2);
else if (50.f <= pvecCircle->at(i)._radius && pvecCircle->at(i)._radius <= 53.f)
circle(resultcolor, pvecCircle->at(i)._center, pvecCircle->at(i)._radius, Scalar(0, 0, 255), 2);
else if (39.f <= pvecCircle->at(i)._radius && pvecCircle->at(i)._radius <= 43.f)
circle(resultcolor, pvecCircle->at(i)._center, pvecCircle->at(i)._radius, Scalar(255, 0, 255), 2);
}
#반복 루프를 돌면서 각각의 원사이즈에 맞게 색을 입히는 과정
케니 엣지를 통하여 엣지를 판별하고 허프 변환을 사용해서 원을 검출한 후,
색을 다르게 주어 끝내면 된다.
다양한 영상처리 라이브러리를 함수와해 놓았다.