傳統 Canny/Sobel 需均勻光照,陰影干擾大。本專案採用 大津法:計算灰度直方圖,遍歷閾值最大化類間方差,獲得最佳二值化分割點,誤差控制在 1% 以內。
int Otsu(IplImage* src) {
float hist[256] = {0};
int h = src->height, w = src->width;
// 統計灰度分布
for (int y = 0; y < h; y++) {
uchar* p = (uchar*)src->imageData + y * src->widthStep;
for (int x = 0; x < w; x++) hist[p[x]]++;
}
// 歸一化
int sz = h * w;
for (int i = 0; i < 256; i++) hist[i] /= sz;
// 遍歷閾值最大化類間方差
float avg = 0; for (int i = 0; i < 256; i++) avg += i * hist[i];
float maxVar = 0; int thresh = 0;
float w = 0, u = 0;
for (int i = 0; i < 256; i++) {
w += hist[i];
u += i * hist[i];
float t = avg * w - u;
float var = t * t / (w * (1 - w));
if (var > maxVar) { maxVar = var; thresh = i; }
}
return thresh;
}
void ProcessImage() {
IplImage* bin = cvCreateImage(cvGetSize(gray), 8, 1);
int thresh = Otsu(gray);
cvThreshold(gray, bin, thresh, 255, CV_THRESH_BINARY);
FillInternalContours(bin, filterThresh); // 面積過濾填充
cvNamedWindow("Result"); cvShowImage("Result", bin);
// 面積統計
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contours = 0;
cvFindContours(bin, storage, &contours, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE);
cvDrawContours(bin, contours, CV_RGB(255,255,255), CV_RGB(255,255,255), 2, CV_FILLED, 8);
double maxArea = 0, secMax = 0;
for (CvSeq* c = contours; c; c = c->h_next) {
for (CvSeq* ic = c->v_next; ic; ic = ic->h_next) {
double a = fabs(cvContourArea(ic, CV_WHOLE_SEQ));
if (a <= filterThresh) // 小噪聲填充白色
cvDrawContours(bin, ic, CV_RGB(255,255,255), CV_RGB(255,255,255), 0, CV_FILLED, 8);
if (a > maxArea) { secMax = maxArea; maxArea = a; }
else if (a > secMax) secMax = a;
}
}
// 顯示結果
SetDlgItemText(IDC_EDIT1, FormatArea(maxArea));
SetDlgItemText(IDC_EDIT2, FormatArea(secMax));
cvReleaseMemStorage(&storage);
cvReleaseImage(&bin);
}
filterThresh:Combo Box 選擇,建議 5000(視圖片分辨率調整)完整代碼已在上下兩篇覆蓋,可直接構建葉片面積測量工具。