오브젝트 분석을 위해서는 그레이 이미지로 변화한 후 2진화를 통해 바이너리 이미지로 만들어야 한다.
보통 바이너리 이미지는 이미지 헤더에 Index 0, 255 를 만들고 0과 1로 표현하지만, OpenCV에서는 헤더없이 0(Black)과 255(White) 로 표현한다.
2진화 방법의 Threshold 값으로는 크게 3가지가 있다.
1. (전체대상) 고정값
2. (전체대상) 자동으로 분리값 계산 후 분리
3. 전체를 작은 블럭으로 나눈 후 고정값이나, 평균, 자동으로 Threshold를 계산
그중 자동 2진화 방법인 OTSU 에 대해 알아보자.
일본인 大津(OOTSU) 박사에 의해 1979년 발표되었는데 간단히 설명하면 두 산봉오리(배경과 전경) 사이의 계곡을 찾는 원리이다.
double getOtsuThreshold(const cv::Mat& src) {
// 1. 히스토그램 생성 (0~255 밝기가 각각 몇 개인지 세기)
int hist[256] = {0};
for (int r = 0; r < src.rows; r++) {
const uchar* ptr = src.ptr<uchar>(r);
for (int c = 0; c < src.cols; c++) {
hist[ptr[c]]++;
}
}
// 2. Otsu 알고리즘 계산
double total = (double)src.rows * src.cols;
double sum = 0;
for (int i = 0; i < 256; i++) sum += i * hist[i];
double sumB = 0;
int wB = 0;
double varMax = 0;
double threshold = 0;
for (int i = 0; i < 256; i++) {
wB += hist[i]; // 배경의 픽셀 개수 가중치
if (wB == 0) continue;
int wF = total - wB; // 전경의 픽셀 개수 가중치
if (wF == 0) break;
sumB += (double)(i * hist[i]);
double mB = sumB / wB; // 배경 평균
double mF = (sum - sumB) / wF; // 전경 평균
// 클래스 간 분산(Between-class variance) 계산
double varBetween = (double)wB * (double)wF * (mB - mF) * (mB - mF);
if (varBetween > varMax) {
varMax = varBetween;
threshold = i;
}
}
return threshold;
}
OpenCV : cv::threshold(src, dst, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
아래 이미지를 보며 설명해 보자.
이미지의 오른쪽과 같이 히스토그램을 표현하면 가로축은 픽셀의 값(0~255), 세로축은 그 값들을 가지는 픽셀수가 된다.
배경 Foreground는 각 왼쪽(어두운색)이나 오른쪽(밝은색) 혹은 오르쪽이나 왼쪽으로 나뉠 수 있다.
히스토그램을 0 ⇒ 255 방향으로 이동하면서 누적픽셀수(wB)과 누적평균(mB) 을 구해간다. 그럼 그 시점에서의 반대편은 전체에서 wB와 mB를 뺀 나머지의 wF, mF가 된다.
이때 double varBetween = (double)wB * (double)wF * (mB - mF) * (mB - mF); 계산에 의해서 두 값들의 차가 벌어질 수록 값이 커지기 때문에 가장 많이 벌어진 지점이 Threshold 값으로 정해지는 것이다.
빨간바가 위치한 지점의 wB mB / wF mF의 계산 영역을 표시