通用的 Blob 檢測方法包括:Laplacian of Gaussian(LoG), Difference of Gaussian(DoG), Derterminant of Hessian(DoH)。
opencv 提供了一種簡單的方法實現 Blob 檢測:SimpleBlobDetector。所謂 Blob,其實就是圖像上一些或亮或暗的小連通區域,該連通區域可以使用特定閾值提取出來。
當分析場景相對簡單,使用 SimpleBlobDetector 可以比較准確且高效的定位 Blob 區域,基本思路如下:
1 使用連續閾值對圖像進行閾值操作,其閾值參數范圍為 ,步長為 s;
2 使用 suzuki 算法(findContours)提取每個閾值下形成的區域,findContours 得到的每個邊界圍成的區域被認為是候選 Blob;
使用 findContours 檢測連通區域要比連通區域分析算法更加高效,同時,一次 findContours 可以區分內外邊界,外邊界對應亮區域,內邊界對應暗區域,但 opencv 2.4.10 源碼中並未對內外邊界區分對待;
3 對 Blob 區域進行篩查,篩查條件包括:
1 CV_PROP_RW bool filterByColor; 2 CV_PROP_RW uchar blobColor; 3 4 CV_PROP_RW bool filterByArea; 5 CV_PROP_RW float minArea, maxArea; 6 7 CV_PROP_RW bool filterByCircularity; 8 CV_PROP_RW float minCircularity, maxCircularity; 9 10 CV_PROP_RW bool filterByInertia; 11 CV_PROP_RW float minInertiaRatio, maxInertiaRatio; 12 13 CV_PROP_RW bool filterByConvexity; 14 CV_PROP_RW float minConvexity, maxConvexity;
1)filterByColor 表示提取亮區域或者暗區域。當 blobColor = 255 時,提取亮區域;當 blobColor = 0 時,提取暗區域;
2)filterByArea 表示是否限制 Blob 面積,其面積范圍為一個半開半閉區間 [minArea, maxArea);
3)filterByCircularity 表示是否限制 Blob 圓形度,圓形度公式為 。
當 Blob 為圓形時,。當 Blob 為一個無限長的橢圓,
。故圓形度范圍取值范圍為 (0, 1];
4)filterByConvexity 表示 Blob 面積與其凸包面積比,取值范圍為 (0, 1);
5)filterByInertia 表示 Blob 區域轉動慣量最小值與最大值比值,在 “二值圖像的幾何性質” 博客中,轉動慣量表示為:
,
,
,
,
由於 轉動慣量 E 為一個二次型函數,使用系數矩陣 的特征值與特征向量可描述轉動慣量特性,其最小值與最大值比為
。
通過以上分析可知,當 Blob 區域為圓形時,比值接近 1。當 Blob 區域為無限長橢圓時,比值接近 0,故取值范圍為 (0,1);
4 對於符合篩查條件的 Blob 區域,使用區域邊界到中心點距離中值點作為該 Blob 區域半徑;
5 將不同閾值下符合條件的候選 Blob 區域組合到一起,使用 minRepeatability 與 minDistBetweenBlobs 篩查出最終有效 Blob 區域;
以下給出 opencv 對候選 Blob 篩查的部分源碼:
1 for (size_t contourIdx = 0; contourIdx < contours.size(); contourIdx++) 2 { 3 4 Center center; 5 center.confidence = 1; // 將可信度置1 6 7 // 求輪廓所圍成區域零階矩,一階矩,二階矩,用於篩查條件計算 8 Moments moms = moments(Mat(contours[contourIdx])); 9 10 if (params.filterByArea) 11 { 12 // 零階矩表示區域面積 13 double area = moms.m00; 14 if (area < params.minArea || area >= params.maxArea) 15 continue; 16 } 17 18 if (params.filterByCircularity) 19 { 20 // 求區域面積與區域周長,並使用圓形度公式計算圓形度 21 double area = moms.m00; 22 double perimeter = arcLength(Mat(contours[contourIdx]), true); 23 double ratio = 4 * CV_PI * area / (perimeter * perimeter); 24 if (ratio < params.minCircularity || ratio >= params.maxCircularity) 25 continue; 26 } 27 28 if (params.filterByInertia) 29 { 30 // 使用二階矩求區域形狀 31 double denominator = sqrt(pow(2 * moms.mu11, 2) + pow(moms.mu20 - moms.mu02, 2)); 32 const double eps = 1e-2; 33 double ratio; 34 if (denominator > eps) 35 { 36 double cosmin = (moms.mu20 - moms.mu02) / denominator; 37 double sinmin = 2 * moms.mu11 / denominator; 38 double cosmax = -cosmin; 39 double sinmax = -sinmin; 40 41 double imin = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmin - moms.mu11 * sinmin; 42 double imax = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmax - moms.mu11 * sinmax; 43 ratio = imin / imax; 44 } 45 else 46 { 47 ratio = 1; 48 } 49 50 if (ratio < params.minInertiaRatio || ratio >= params.maxInertiaRatio) 51 continue; 52 53 center.confidence = ratio * ratio; 54 } 55 56 if (params.filterByConvexity) 57 { 58 // 求區域面積與凸包面積之比 59 vector < Point > hull; 60 convexHull(Mat(contours[contourIdx]), hull); 61 double area = contourArea(Mat(contours[contourIdx])); 62 double hullArea = contourArea(Mat(hull)); 63 double ratio = area / hullArea; 64 if (ratio < params.minConvexity || ratio >= params.maxConvexity) 65 continue; 66 } 67 68 center.location = Point2d(moms.m10 / moms.m00, moms.m01 / moms.m00); 69 70 if (params.filterByColor) 71 { 72 // 提取亮區域或者暗區域 73 if (binaryImage.at<uchar> (cvRound(center.location.y), cvRound(center.location.x)) != params.blobColor) 74 continue; 75 } 76 77 // 計算 Blob 半徑 78 { 79 vector<double> dists; 80 for (size_t pointIdx = 0; pointIdx < contours[contourIdx].size(); pointIdx++) 81 { 82 Point2d pt = contours[contourIdx][pointIdx]; 83 dists.push_back(norm(center.location - pt)); 84 } 85 std::sort(dists.begin(), dists.end()); 86 center.radius = (dists[(dists.size() - 1) / 2] + dists[dists.size() / 2]) / 2.; 87 } 88 89 centers.push_back(center); 90 91 }
參考資料 Learning OpenCV 3 Adrian Kaehler & Gary Bradski