토이 프로젝트를 진행하면서 기록을 해두고 싶어서 포스트잇 방식으로 씁니다.
관련 코드는 아래 링크를 참고하여 만들었습니다.
『OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝』
2022.05.07
OpenCV를 공부도 하고 글로도 정리하는데 머리속으로 잘 들어 오지 않아 간단한 토이 프로젝트를 해보기로 함.
OpenCV 관련 네이버 카페를 찾아보니 동전을 검출하여 자동으로 계산해 주는 프로젝트가 보여서 시작.
참고 네이버 카페
2022.05.09
대략적으로 개발 방향만 정함.
- USB 캠으로 실시간 화면 표시.
- 원검출로 동전 위치 검출
- 검출된 동전의 금액 검출
- 동전의 금액을 합쳐서 화면 표시
USB 1 프레임을 그레이스케일로 변환한 뒤 adaptiveThreshold 함수를 사용하니 아래와 같이 노이즈 발생하여 그레이스케일에서 블러를 적용한 뒤 adapttiveThreshold 사용.
블러 적용 전(좌측), 후(우측)
블러 적용 전(좌측), 후(우측)
블러의 경우 양방향 필터로 사용하였고, 사용해도 노이즈가 존재하긴 함. 우선 무시하고 원 검출 진행.
노이즈는 무시하고 허프 원 검출로 진행하려 했으나 노이즈때문에 원 검출이 제대로 안된다.
노이즈 제거를 위해 모폴로지 열기를 해봤으나 이미지만 더 이상해짐.
원본 영상이 너무 안좋은 것 같아 깔끔하게 하기 위해 흰색 종이에 동전을 올림.
이렇게 하니 형광등 반사도 적어지고 노이즈도 많이 줄음. (이래서 카페 글에 하얀 종이 위에 동전을 올렸구나..!)
왠지 원 검출이 잘 될거 같다. HoughCircles 함수로 원 검출.
노이즈 최소화
허프 원 검출
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
vector<Vec3f> circles;
double param1 = (double)g_iParam1;
double param2 = (double)g_iParam2;
HoughCircles(src_gray, circles, HOUGH_GRADIENT, g_Dp, g_minDist, param1, param2, g_min_radius, g_max_radius);
cvtColor(src_gray, dst, COLOR_GRAY2BGR);
for (Vec3f c : circles)
{
Point center(cvRound(c[0]), cvRound(c[1]));
int radius = cvRound(c[2]);
circle(dst, center, radius, Scalar(0, 0, 255), 2, LINE_AA);
}
원 검출은 잘 되는데 기분 탓인지 허프 원 검출은 뭔가 속도도 느린거 같고, 검출한 원의 크기가 늘었다 줄었다 하고있다.
뭔가 맘에 안들음.
다른 방식으로 원 검출을 시도해봐야겠다. findContours 사용하기 위해 THRESH_BINARY_INV으로 변경.
생각보다 너무 깔끔하게 잘 나온다. 시간이 늦어서 findContours는 나중에…
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
// blur 처리
Mat src_blur;
//medianBlur(src_gray, src_blur, 5);
bilateralFilter(src_gray, src_blur, -1, 10, 5);
//GaussianBlur(src_gray, src_blur, Size(), 3);
Mat dst_noise;
//threshold(src_blur, dst_noise, g_iThreshold, 255, THRESH_BINARY_INV);
int iSize = g_iThreshold;
if (iSize % 2 == 0)
iSize--;
if (iSize < 3)
iSize = 3;
adaptiveThreshold(src_blur, dst_noise, 255, ADAPTIVE_THRESH_GAUSSIAN_C, /*THRESH_BINARY*/THRESH_BINARY_INV, iSize, 10);
cvtColor(dst_noise, dst, COLOR_GRAY2BGR);
2022.05.10
findContour를 하기 전에 이진화 이미지를 조금 더 깔끔하게 하는 방법이 없을까 고민함.
05.09에 흰 종이를 댄것 처럼, 원본 영상에 수정을 하면 좋을 것 같아서, 히스토그램 평활화 적용.
평활화 전에 양방향 필터 적용. 결과적으로 깔끔하게 추출이 된다.
Mat frame;
bRead = video.read(frame);
if (!bRead)
break;
// 히스토그램 평활화
Mat frame_gray, frame_blur, frame_hist;
cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
bilateralFilter(frame_gray, frame_blur, -1, 10, 5); // 필터 적용
equalizeHist(frame_blur, frame_hist); // 평활화
현재까지 영상 처리 순서는
원본 영상 -> 이진화 -> 양방향 필터 -> 히스토그램 평활화 -> 이진화
그리고 findContour로 이진화 영상에서 객체를 검출.
area는 원의 면적이며, ratio는 0~1 사이의 실수값을 가지며, 1에 가까울 수록 원에 가깝다.
내 경우 area는 500 ~ 4000 정도의 값을 가지며, ratio는 0.02 ~ 0.9 정도의 값을 가짐
객체 검출을 위해 ratio 값은 0으로 해놓고, 작은 것들은 검출 되지 않게 150 이하 area는 객체로 검출 하지 않음
결과적으로는 맞게 한 것 같다.
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
vector<vector<Point>> contours;
findContours(src_gray, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
cvtColor(src_gray, dst, COLOR_GRAY2BGR);
for (vector<Point> &pts : contours)
{
vector<Point> approx;
approxPolyDP(pts, approx, arcLength(pts, true) * 0.02, true);
int vtc = (int)approx.size();
if (4 < vtc)
{
double len = arcLength(pts, true);
double area = contourArea(pts);
double ratio = 4. * CV_PI * area / (len * len);
double area_min = (double)g_area;
double ratio_min = (double)g_ratio * 0.1;
if (ratio_min < ratio && area_min < area)
{
Rect rc = boundingRect(pts);
output.push_back(rc);
rectangle(dst, rc, Scalar(0,0,255), 1);
}
}
}
퇴근후 찾아보니 다른 USB 캠이 있다. 실행 해보니 이전 영상보다 더 깔끔하고 좋음.
90도 위에서 찍을수 있는 카메라라서 더 장확하게 원으로 검출 된다.
검출된 원의 크기로 금액 계산이 가능할 것같다.
500원, 100원, 50원 10원 순서대로 검출
동전 금액별로 면적을 지정해서 화면에 총 금액을 표시한다.
dst = src.clone();
if (output.size() <= 0)
return false;
int sum_coin = 0;
for (int number = 0 ; number < output.size() ; number++)
{
vector<Point> pts = output.at(number);
Rect rc = boundingRect(pts);
rectangle(dst, rc, Scalar(0, 0, 255), 1);
int area = cvRound(contourArea(pts));
int coin = 0;
if (g_i100max <= area && area < g_i500max)
coin = 500;
else if (g_i50max <= area && area < g_i100max)
coin = 100;
else if (g_i10max <= area && area < g_i50max)
coin = 50;
else if (g_area <= area && area < g_i10max)
coin = 10;
else
coin = 0;
String strCoin = format("%d", coin);
putText(dst, strCoin, Point(rc.x, rc.y), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255));
sum_coin += coin;
}
String strSumCoin = format("%d", sum_coin);
putText(dst, strSumCoin, Point(50, 50), FONT_HERSHEY_SIMPLEX, 2, Scalar(0, 0, 0));
imshow("dst", dst);
실행해서 확인해본 결과 검출 잘 된다.
100원, 500원을 순서대로 놓음
초반에 정했던 개발 방향을 정리하자면 아래와 같다.
- USB 캠으로 실시간 화면 표시.
– 원본 영상에서 최대한 노이즈 제거, Threshold로 이진화 - 원검출로 동전 위치 검출
– contour로 객체 검출 - 검출된 동전의 금액 검출
– 검출된 객체의 면적으로 금액 검출 - 동전의 금액을 합쳐서 화면 표시
– 검출된 금액 및 합계 화면 표시
아무래도 3번이 객체의 면적으로 계산하다 보니 외화나 크기가 큰 10원같은 것들은 검출이 안된다.
1000루피아, 10원을 순서대로 놓음
진행하면서 느끼는 결론
- 원본 영상이 깔끔해야함
- 객체 검출 전 단계가 생각보다 손이 많이 감, 임계값 등은 조절할 수 있게 만들어야 편할 듯
- 생각보다 특정 상황에서 속도가 느림
- 객체 검출하는 것도 힘들고, 검출한게 무엇인지 판별하는 것도 힘들고.
객체 검출 방식 중 머신러닝과 딥러닝을 통해 검출하는 방식이 있다고 듣긴 했는데, 더 공부해서 해봐야겠다.