一、blob基礎
所謂Blob就是圖像中一組具有某些共同屬性(例如,灰度值)的連接像素。在上圖中,深色連接區域是斑點,斑點檢測的目的是識別並標記這些區域。OpenCV提供了一種方便的方法來檢測斑點並根據不同的特征對其進行過濾。在OpenCV 3中,使用SimpleBlobDetector :: create方法創建智能指針調用該算法。
Python
Python
# Setup SimpleBlobDetector parameters.
params
=
cv2.SimpleBlobDetector_Params()
# Change thresholds
params.minThreshold
=
10
;
params.maxThreshold
=
200
;
# Filter by Area.
params.filterByArea
=
True
params.minArea
=
1500
# Filter by Circularity
params.filterByCircularity
=
True
params.minCircularity
=
0
.
1
# Filter by Convexity
params.filterByConvexity
=
True
params.minConvexity
=
0
.
87
# Filter by Inertia
params.filterByInertia
=
True
params.minInertiaRatio
=
0
.
01
# Create a detector with the parameters
ver
=
(cv2.
__version__
).split(
'.'
)
if
int
(ver[
0
])
<
3
:
detector
=
cv2.SimpleBlobDetector(params)
else
:
detector
=
cv2.SimpleBlobDetector_create(params)
C++
// Setup SimpleBlobDetector parameters.
SimpleBlobDetector
:
:Params params;
// Change thresholds
params.minThreshold
=
10;
params.maxThreshold
=
200;
// Filter by Area.
params.filterByArea
=
true;
params.minArea
=
1500;
// Filter by Circularity
params.filterByCircularity
=
true;
params.minCircularity
=
0.
1;
// Filter by Convexity
params.filterByConvexity
=
true;
params.minConvexity
=
0.
87;
// Filter by Inertia
params.filterByInertia
=
true;
params.minInertiaRatio
=
0.
01;
#
if CV_MAJOR_VERSION
<
3
// If you are using OpenCV 2
// Set up detector with params
SimpleBlobDetector detector(params);
// You can use the detector this way
// detector.detect( im, keypoints);
#
else
// Set up detector with params
Ptr
<SimpleBlobDetector
> detector
= SimpleBlobDetector
:
:create(params);
// SimpleBlobDetector::create creates a smart pointer.
// So you need to use arrow ( ->) instead of dot ( . )
// detector->detect( im, keypoints);
#
endif
二、blob參數設置
在OpenCV中實現的叫做SimpleBlobDetector,它基於以下描述的相當簡單的算法,並且進一步由參數控制,具有以下步驟。
SimpleBlobDetector
:
:Params
:
:Params()
{
thresholdStep
=
10;
//二值化的閾值步長,即公式1的t
minThreshold
=
50;
//二值化的起始閾值,即公式1的T1
maxThreshold
=
220;
//二值化的終止閾值,即公式1的T2
//重復的最小次數,只有屬於灰度圖像斑點的那些二值圖像斑點數量大於該值時,該灰度圖像斑點才被認為是特征點
minRepeatability
=
2;
//最小的斑點距離,不同二值圖像的斑點間距離小於該值時,被認為是同一個位置的斑點,否則是不同位置上的斑點
minDistBetweenBlobs
=
10;
filterByColor
=
true;
//斑點顏色的限制變量
blobColor
=
0;
//表示只提取黑色斑點;如果該變量為255,表示只提取白色斑點
filterByArea
=
true;
//斑點面積的限制變量
minArea
=
25;
//斑點的最小面積
maxArea
=
5000;
//斑點的最大面積
filterByCircularity
=
false;
//斑點圓度的限制變量,默認是不限制
minCircularity
=
0.
8f;
//斑點的最小圓度
//斑點的最大圓度,所能表示的float類型的最大值
maxCircularity
= std
:
:numeric_limits
<
float
>
:
:max();
filterByInertia
=
true;
//斑點慣性率的限制變量
minInertiaRatio
=
0.
1f;
//斑點的最小慣性率
maxInertiaRatio
= std
:
:numeric_limits
<
float
>
:
:max();
//斑點的最大慣性率
filterByConvexity
=
true;
//斑點凸度的限制變量
minConvexity
=
0.
95f;
//斑點的最小凸度
maxConvexity
= std
:
:numeric_limits
<
float
>
:
:max();
//斑點的最大凸度
}
SimpleBlobDetector
:
:Params
:
:Params()
{
thresholdStep = 10; //二值化的閾值步長,即公式1的t
minThreshold = 50; //二值化的起始閾值,即公式1的T1
maxThreshold = 220; //二值化的終止閾值,即公式1的T2
//重復的最小次數,只有屬於灰度圖像斑點的那些二值圖像斑點數量大於該值時,該灰度圖像斑點才被認為是特征點
minRepeatability = 2;
//最小的斑點距離,不同二值圖像的斑點間距離小於該值時,被認為是同一個位置的斑點,否則是不同位置上的斑點
minDistBetweenBlobs = 10;
filterByColor = true; //斑點顏色的限制變量
blobColor = 0; //表示只提取黑色斑點;如果該變量為255,表示只提取白色斑點
filterByArea = true; //斑點面積的限制變量
minArea = 25; //斑點的最小面積
maxArea = 5000; //斑點的最大面積
filterByCircularity = false; //斑點圓度的限制變量,默認是不限制
minCircularity = 0. 8f; //斑點的最小圓度
//斑點的最大圓度,所能表示的float類型的最大值
maxCircularity = std : :numeric_limits < float > : :max();
filterByInertia = true; //斑點慣性率的限制變量
minInertiaRatio = 0. 1f; //斑點的最小慣性率
maxInertiaRatio = std : :numeric_limits < float > : :max(); //斑點的最大慣性率
filterByConvexity = true; //斑點凸度的限制變量
minConvexity = 0. 95f; //斑點的最小凸度
maxConvexity = std : :numeric_limits < float > : :max(); //斑點的最大凸度
}
{
thresholdStep = 10; //二值化的閾值步長,即公式1的t
minThreshold = 50; //二值化的起始閾值,即公式1的T1
maxThreshold = 220; //二值化的終止閾值,即公式1的T2
//重復的最小次數,只有屬於灰度圖像斑點的那些二值圖像斑點數量大於該值時,該灰度圖像斑點才被認為是特征點
minRepeatability = 2;
//最小的斑點距離,不同二值圖像的斑點間距離小於該值時,被認為是同一個位置的斑點,否則是不同位置上的斑點
minDistBetweenBlobs = 10;
filterByColor = true; //斑點顏色的限制變量
blobColor = 0; //表示只提取黑色斑點;如果該變量為255,表示只提取白色斑點
filterByArea = true; //斑點面積的限制變量
minArea = 25; //斑點的最小面積
maxArea = 5000; //斑點的最大面積
filterByCircularity = false; //斑點圓度的限制變量,默認是不限制
minCircularity = 0. 8f; //斑點的最小圓度
//斑點的最大圓度,所能表示的float類型的最大值
maxCircularity = std : :numeric_limits < float > : :max();
filterByInertia = true; //斑點慣性率的限制變量
minInertiaRatio = 0. 1f; //斑點的最小慣性率
maxInertiaRatio = std : :numeric_limits < float > : :max(); //斑點的最大慣性率
filterByConvexity = true; //斑點凸度的限制變量
minConvexity = 0. 95f; //斑點的最小凸度
maxConvexity = std : :numeric_limits < float > : :max(); //斑點的最大凸度
}
- 閾值:通過使用以minThreshold開始的閾值對源圖像進行閾值處理,將源圖像轉換為多個二進制圖像。這些閾值以thresholdStep遞增,直到maxThreshold。因此,第一個閾值為minThreshold,第二個閾值為minThreshold + thresholdStep,第三個閾值為minThreshold + 2 x thresholdStep,依此類推;
- 分組:在每個二進制圖像中,連接的白色像素被分組在一起。我們稱這些二進制blob;
- 合並:計算二進制圖像中二進制斑點的中心,並合並比minDistBetweenBlob更近的斑點;
- 中心和半徑計算:計算並返回新合並的Blob的中心和半徑。
並且可以進一步設置SimpleBlobDetector的參數來過濾所需的Blob類型。
- 按顏色:首先需要設置filterByColor =True。設置blobColor = 0可選擇較暗的blob,blobColor = 255可以選擇較淺的blob。
- 按大小:可以通過設置參數filterByArea = 1以及minArea和maxArea的適當值來基於大小過濾blob。例如。設置minArea = 100將濾除所有少於100個像素的斑點。
- 按圓度:這只是測量斑點距圓的距離。例如。正六邊形的圓度比正方形高。要按圓度過濾,請設置filterByCircularity =1。然后為minCircularity和maxCircularity設置適當的值。圓度定義為(
)。圓的為圓度為1,正方形的圓度為PI/4,依此類推。

- 按凸性:凸度定義為(斑點的面積/凸包的面積)。現在,形狀的“凸包”是最緊密的凸形,它完全包圍了該形狀,用不嚴謹的話來講,給定二維平面上的點集,凸包就是將最外層的點連接起來構成的凸多邊形,它能包含點集中所有的點。直觀感受上,凸性越高則里面“奇怪的部分”越少。要按凸度過濾,需設置filterByConvexity = true,minConvexity、maxConvexity應該屬於[0,1],而且maxConvexity> minConvexity。
- 按慣性比:這個詞匯比較抽象。我們需要知道Ratio可以衡量形狀的伸長程度。簡單來說。對於圓,此值是1,對於橢圓,它在0到1之間,對於直線,它是0。按慣性比過濾,設置filterByInertia = true,並設置minInertiaRatio、maxInertiaRatio同樣屬於[0,1]並且maxConvexity> minConvexity。
按凸性(左低右高)
按慣性比(左低右高)



按凸性(左低右高) | 按慣性比(左低右高) |
![]() |
![]() |
三、OpenCV的blob代碼解析
在它的函數定義部分(feature2d.hpp),詳細地說明了該部分代碼的使用方法。
實現部分代碼,來源於

單文件構成,結構比較簡單,主要函數集中於detect和
findBlobs
,其他的皆為配合函數。
主要的一個數據結構,
包含了中心的位置、半徑和確定性。
struct CV_EXPORTS Center
{
Point2d location;
double radius;
double confidence;
};
2.1 findblob函數實現
findblob的主要過程是尋找到當前圖片的輪廓,而后根據參數中的相關定義進行篩選。其中值得注意的地方。
std::vector < std::vector<Point> > contours;
findContours(binaryImage, contours, RETR_LIST, CHAIN_APPROX_NONE);
2.1.1在findContours的過程中,使用的是
RETR_LIST 和
CHAIN_APPROX_NONE
,我們來看下圖
RETR_LIST
的方法是將所有的輪廓全部以鏈表的形式串聯起來(反過來說,將丟失輪廓間的樹狀結構)。
2.1.2注意
輪廓遍歷的大循環。進入循環后將根據參數中的每一個單項進行逐條篩選。
for (size_t contourIdx = 0; contourIdx < contours.size(); contourIdx++)
{……
2.1.3
“面積”篩選
通過
獲得moms.m00
來獲得面積。
Moments moms
= moments(contours[contourIdx]);
if (params.filterByArea)
{
double area
= moms.m00;
if (area
< params.minArea
|| area
>
= params.maxArea)
continue;
}
這個地方調用了moments(),該函數用於計算中心矩。
設f(x,y)是一幅數字圖像,
,我們把像素的坐標看成是一個二維隨機變量(X, Y),那么一副灰度圖可以用二維灰度圖密度函數來表示,因此可以用矩來描述灰度圖像的特征。
對於二值圖像的來說,零階矩M00等於它的面積,同時一階矩計算質心/重心。OpenCV中是這樣實現
Moments moments(InputArray array,
bool binaryImage
=
false )
class Moments
{
public
:
Moments();
Moments(
double m00,
double m10,
double m01,
double m20,
double m11,
double m02,
double m30,
double m21,
double m12,
double m03 );
Moments(
const CvMoments
& moments );
operator CvMoments()
const;
}
參數說明
- 輸入參數:array是一幅單通道,8-bits的圖像,或一個二維浮點數組(Point of Point2f)。binaryImage用來指示輸出圖像是否為一幅二值圖像,如果是二值圖像,則圖像中所有非0像素看作為1進行計算。
- 輸出參數:moments是一個類:
2.1.3
“圓度”篩選時通過來計算圓度公式,此外自帶函數
arcLength
獲得輪廓的周長。
double perimeter
= arcLength(contours[contourIdx],
true);
double ratio
=
4
* CV_PI
* area
/ (perimeter
* perimeter);

2.1.3“顏色”篩選,只判斷“圓心”的顏色。
if (params.filterByColor)
{
if (binaryImage.at
<uchar
> (cvRound(center.location.y), cvRound(center.location.x))
!= params.blobColor)
continue;
}
這個使用方法值得商榷,在實際使用過程中不采納。
2.1.4
"凸性"篩選,
凸圖像在0-1之間取值。
if (params.filterByConvexity)
{
std
:
:vector
< Point
> hull;
convexHull(contours[contourIdx], hull);
double area
= contourArea(contours[contourIdx]);
double hullArea
= contourArea(hull);
if (fabs(hullArea)
< DBL_EPSILON)
continue;
double ratio
= area
/ hullArea;
if (ratio
< params.minConvexity
|| ratio
>
= params.maxConvexity)
continue;
}
……
我們可以用凸度來表示斑點凹凸的程度,凸度V的定義為:
2.1.5“慣性比”篩選,簡單的來說,就是輪廓“扁”的程度。對於圓,此值是1,對於橢圓,它在0到1之間,對於直線,它是0。基本上就是取值在0-1之間,越扁越小。
if (params.filterByInertia)
{
double denominator
= std
:
:sqrt(std
:
:pow(
2
* moms.mu11,
2)
+ std
:
:pow(moms.mu20
- moms.mu02,
2));
const
double eps
=
1e
-
2;
double ratio;
if (denominator
> eps)
{
double cosmin
= (moms.mu20
- moms.mu02)
/ denominator;
double sinmin
=
2
* moms.mu11
/ denominator;
double cosmax
=
-cosmin;
double sinmax
=
-sinmin;
double imin
=
0.
5
* (moms.mu20
+ moms.mu02)
-
0.
5
* (moms.mu20
- moms.mu02)
* cosmin
- moms.mu11
* sinmin;
double imax
=
0.
5
* (moms.mu20
+ moms.mu02)
-
0.
5
* (moms.mu20
- moms.mu02)
* cosmax
- moms.mu11
* sinmax;
ratio
= imin
/ imax;
}
else
{
ratio
=
1;
}
if (ratio
< params.minInertiaRatio
|| ratio
>
= params.maxInertiaRatio)
continue;
center.confidence
= ratio
* ratio;
}
……
二階中心矩稱為慣性矩。如果僅考慮二階中心矩的話,則圖像完全等同於一個具有確定的大小、方向和離心率,以圖像質心為中心且具有恆定輻射度的橢圓。圖像的協方差矩陣為:

該矩陣的兩個特征值λ1和λ2對應於圖像強度(即橢圓)的主軸和次軸:

而圖像的方向角度θ為:

圖像的慣性率I為:
這個函數定義和代碼略有不同,沒有進一步研究。
2.1.6特別注意“半徑”的計算方法
//compute blob radius
{
std
:
:vector
<
double
> dists;
for (size_t pointIdx
=
0; pointIdx
< contours[contourIdx].size(); pointIdx
++)
{
Point2d pt
= contours[contourIdx][pointIdx];
dists.push_back(norm(center.location
- pt));
}
std
:
:sort(dists.begin(), dists.end());
center.radius
= (dists[(dists.size()
-
1)
/
2]
+ dists[dists.size()
/
2])
/
2.;
}
采用的是排序取中間值的方法,值得借鑒。
2.2 detect函數實現
2.2.1
對於圖像通道的判斷,值得借鑒。
Mat grayscaleImage;
if (image.channels()
==
3
|| image.channels()
==
4)
cvtColor(image, grayscaleImage, COLOR_BGR2GRAY);
else
grayscaleImage
= image.getMat();
if (grayscaleImage.type()
!= CV_8UC1) {
CV_Error(Error
:
:StsUnsupportedFormat,
"Blob detector only supports 8-bit images!");
}
2.2.2最外層的循環采用的是遍歷閾值的方法,該方法非常值得借鑒。
for (
double thresh
= params.minThreshold; thresh
< params.maxThreshold; thresh
+= params.thresholdStep)
{
Mat binarizedImage;
threshold(grayscaleImage, binarizedImage, thresh,
255, THRESH_BINARY);……
2.2.3
通過距離判斷新找到的圓是否為新圓,這段絕對是值得復用的。
for (
double thresh
= params.minThreshold; thresh
< params.maxThreshold; thresh
+= params.thresholdStep)
{
Mat binarizedImage;
threshold(grayscaleImage, binarizedImage, thresh,
255, THRESH_BINARY);
std
:
:vector
< Center
> curCenters;
findBlobs(grayscaleImage, binarizedImage, curCenters);
std
:
:vector
< std
:
:vector
<Center
>
> newCenters;
for (size_t i
=
0; i
< curCenters.size(); i
++)
{
bool isNew
=
true;
for (size_t j
=
0; j
< centers.size(); j
++)
{
double dist
= norm(centers[j][ centers[j].size()
/
2 ].location
- curCenters[i].location);
isNew
= dist
>
= params.minDistBetweenBlobs
&& dist
>
= centers[j][ centers[j].size()
/
2 ].radius
&& dist
>
= curCenters[i].radius;
if (
!isNew)
{
centers[j].push_back(curCenters[i]);
size_t k
= centers[j].size()
-
1;
while( k
>
0
&& curCenters[i].radius
< centers[j][k
-
1].radius )
{
centers[j][k]
= centers[j][k
-
1];
k
--;
}
centers[j][k]
= curCenters[i];
break;
}
}
if (isNew)
newCenters.push_back(std
:
:vector
<Center
> (
1, curCenters[i]));
}
std
:
:copy(newCenters.begin(), newCenters.end(), std
:
:back_inserter(centers));
}
三、一些聯想
1、面積篩選這塊,那個面積函數是什么意思?
面積函數是專門有實現的。
double cv::contourArea( InputArray _contour, bool oriented )
{
CV_INSTRUMENT_REGION();
Mat contour = _contour.getMat();
int npoints = contour.checkVector(2);
int depth = contour.depth();
CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_32S));
if( npoints == 0 )
return 0.;
double a00 = 0;
bool is_float = depth == CV_32F;
const Point* ptsi = contour.ptr<Point>();
const Point2f* ptsf = contour.ptr<Point2f>();
Point2f prev = is_float ? ptsf[npoints-1] : Point2f((float)ptsi[npoints-1].x, (float)ptsi[npoints-1].y);
for( int i = 0; i < npoints; i++ )
{
Point2f p = is_float ? ptsf[i] : Point2f((float)ptsi[i].x, (float)ptsi[i].y);
a00 += (double)prev.x * p.y - (double)prev.y * p.x;
prev = p;
}
a00 *= 0.5;
if( !oriented )
a00 = fabs(a00);
return a00;
}
但是據說,
double
area
=
moms
.
m00
;
也行,這個是為什么?
進入看看,並且刪除多余的東西:
cv::Moments cv::moments( InputArray _src, bool binary )
{
CV_INSTRUMENT_REGION();
const int TILE_SIZE = 32;
MomentsInTileFunc func = 0;
uchar nzbuf[TILE_SIZE*TILE_SIZE];
Moments m;
int type = _src.type(), depth = CV_MAT_DEPTH(type), cn = CV_MAT_CN(type);
Size size = _src.size();
if( size.width <= 0 || size.height <= 0 )
return m;
#ifdef HAVE_OPENCL
CV_OCL_RUN_(type == CV_8UC1 && _src.isUMat(), ocl_moments(_src, m, binary), m);
#endif
Mat mat = _src.getMat();
if( mat.checkVector(2) >= 0 && (depth == CV_32F || depth == CV_32S))
return contourMoments(mat);
if( cn > 1 )
CV_Error( CV_StsBadArg, "Invalid image type (must be single-channel)" );
CV_IPP_RUN(!binary, ipp_moments(mat, m), m);
if( binary || depth == CV_8U )
func = momentsInTile<uchar, int, int>;
else if( depth == CV_16U )
func = momentsInTile<ushort, int, int64>;
else if( depth == CV_16S )
func = momentsInTile<short, int, int64>;
else if( depth == CV_32F )
func = momentsInTile<float, double, double>;
else if( depth == CV_64F )
func = momentsInTile<double, double, double>;
else
CV_Error( CV_StsUnsupportedFormat, "" );
Mat src0(mat);
for( int y = 0; y < size.height; y += TILE_SIZE )
{
Size tileSize;
tileSize.height = std::min(TILE_SIZE, size.height - y);
for( int x = 0; x < size.width; x += TILE_SIZE )
{
tileSize.width = std::min(TILE_SIZE, size.width - x);
Mat src(src0, cv::Rect(x, y, tileSize.width, tileSize.height));
if( binary )
{
cv::Mat tmp(tileSize, CV_8U, nzbuf);
cv::compare( src, 0, tmp, CV_CMP_NE );
src = tmp;
}
double mom[10];
func( src, mom );
if(binary)
{
double s = 1./255;
for( int k = 0; k < 10; k++ )
mom[k] *= s;
}
double xm = x * mom[0], ym = y * mom[0];
// accumulate moments computed in each tile
// + m00 ( = m00' )
m.m00 += mom[0];
// + m10 ( = m10' + x*m00' )
m.m10 += mom[1] + xm;
// + m01 ( = m01' + y*m00' )
m.m01 += mom[2] + ym;
// + m20 ( = m20' + 2*x*m10' + x*x*m00' )
m.m20 += mom[3] + x * (mom[1] * 2 + xm);
// + m11 ( = m11' + x*m01' + y*m10' + x*y*m00' )
m.m11 += mom[4] + x * (mom[2] + ym) + y * mom[1];
// + m02 ( = m02' + 2*y*m01' + y*y*m00' )
m.m02 += mom[5] + y * (mom[2] * 2 + ym);
// + m30 ( = m30' + 3*x*m20' + 3*x*x*m10' + x*x*x*m00' )
m.m30 += mom[6] + x * (3. * mom[3] + x * (3. * mom[1] + xm));
// + m21 ( = m21' + x*(2*m11' + 2*y*m10' + x*m01' + x*y*m00') + y*m20')
m.m21 += mom[7] + x * (2 * (mom[4] + y * mom[1]) + x * (mom[2] + ym)) + y * mom[3];
// + m12 ( = m12' + y*(2*m11' + 2*x*m01' + y*m10' + x*y*m00') + x*m02')
m.m12 += mom[8] + y * (2 * (mom[4] + x * mom[2]) + y * (mom[1] + xm)) + x * mom[5];
// + m03 ( = m03' + 3*y*m02' + 3*y*y*m01' + y*y*y*m00' )
m.m03 += mom[9] + y * (3. * mom[5] + y * (3. * mom[2] + ym));
}
}
completeMomentState( &m );
return m;
}
static void momentsInTile( const Mat& img, double* moments )
{
Size size = img.size();
int x, y;
MT mom[10] = {0,0,0,0,0,0,0,0,0,0};
MomentsInTile_SIMD<T, WT, MT> vop;
for( y = 0; y < size.height; y++ )
{
const T* ptr = img.ptr<T>(y);
WT x0 = 0, x1 = 0, x2 = 0;
MT x3 = 0;
x = vop(ptr, size.width, x0, x1, x2, x3);
for( ; x < size.width; x++ )
{
WT p = ptr[x];
WT xp = x * p, xxp;
x0 += p;
x1 += xp;
xxp = xp * x;
x2 += xxp;
x3 += xxp * x;
}
WT py = y * x0, sy = y*y;
mom[9] += ((MT)py) * sy; // m03
mom[8] += ((MT)x1) * sy; // m12
mom[7] += ((MT)x2) * y; // m21
mom[6] += x3; // m30
mom[5] += x0 * sy; // m02
mom[4] += x1 * y; // m11
mom[3] += x2; // m20
mom[2] += py; // m01
mom[1] += x1; // m10
mom[0] += x0; // m00
}
for( x = 0; x < 10; x++ )
moments[x] = (double)mom[x];
}
從結果來比較
Moments moms = moments(contours[contourIdx]); double area = moms.m00; |
double area = contourArea(contours[contourIdx]); |
![]() |
![]() |
是一個東西,這樣的話就更應該優選contourArea,因為更具有可解釋性。但是在這里,使用m00卻是有道理的:
因為moms不僅在一個地方被使用,那么這次就算就是非常值的。
2、凸度的話,從結果圖片上再繼續分析;
凸度來表示斑點凹凸的程度,其定義為:
![]() |
簡單來說,比如看這張圖,area(hull)>>area(contours),這個值越大,則表明原圖這個尖子越多,可以表明是越復雜,越可能存在缺口,越不像一個平滑、規整的圖像。
3、慣性里面有一個confidence是什么意思?
if (params.filterByInertia)
{
double denominator = std::sqrt(std::pow(2 * moms.mu11, 2) + std::pow(moms.mu20 - moms.mu02, 2));
const double eps = 1e-2;
double ratio;
if (denominator > eps)
{
double cosmin = (moms.mu20 - moms.mu02) / denominator;
double sinmin = 2 * moms.mu11 / denominator;
double cosmax = -cosmin;
double sinmax = -sinmin;
double imin = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmin - moms.mu11 * sinmin;
double imax = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmax - moms.mu11 * sinmax;
ratio = imin / imax;
}
else
{
ratio = 1;
}
if (ratio < params.minInertiaRatio || ratio >= params.maxInertiaRatio)
continue;
center.confidence = ratio * ratio;
也就是相當於:
center
.
confidence =
imin
/
imax * (
imin
/
imax)
這個來自於這里的解釋:
偏心率是指某一橢圓軌道與理想圓形的偏離程度,長橢圓軌道的偏心率高,而近於圓形的軌道的偏心率低。圓形的偏心率等於0,橢圓的偏心率介於0和1之間,而偏心率等於1表示的是拋物線。直接計算斑點的偏心率較為復雜,但利用圖像矩的概念計算圖形的慣性率,再由慣性率計算偏心率較為方便。偏心率E和慣性率I之間的關系為:

偏心率:
偏心率(離心率)
偏心率(Eccentricity)是用來描述圓錐曲線軌道形狀的數學量。對於圓錐曲線(二次曲線)的(不完整)統一定義:到定點(焦點)的距離與到定直線(准線)的距離的商是常數e(離心率)的點的軌跡。
當e>1時,為雙曲線的一支;當e=1時,為拋物線;當0<e<1時,為橢圓;當e=0時,為一點
對於橢圓,偏心率即為兩焦點間的距離(焦距,2c)和長軸長度(2a)的比值,即e=c/a。偏心率反映的是某一橢圓軌道與理想圓環的偏離程度,長橢圓軌道“偏心率”高,而近於圓形的軌道“偏心率”低。
在橢圓的標准方程 (x/a)^2+(y/b)^2=1 中,如果a>b>0焦點在X軸上,這時,a代表長軸、b代表短軸、 c代表兩焦點距離的一半,有關系式 c^2=a^2-b^2,即e^2=1-(b/a)^2。因此橢圓偏心率0<e<1,短軸與長軸比值(b/a)越小,e越接近於1,橢圓也就越扁平。
4、默認參數不判斷圓度,但仍體現出良好的圓的篩選。
默認情況下,是不判斷圓度的,但是仍然體現出了良好的對圓的篩選。
/*
* SimpleBlobDetector
*/
SimpleBlobDetector::Params::Params()
{
thresholdStep = 10;
minThreshold = 50;
maxThreshold = 220;
minRepeatability = 2;
minDistBetweenBlobs = 10;
filterByColor = true;
blobColor = 0;
filterByArea = true;
minArea = 25;
maxArea = 5000;
filterByCircularity = false;
minCircularity = 0.8f;
maxCircularity = std::numeric_limits<float>::max();
filterByInertia = true;
//minInertiaRatio = 0.6;
minInertiaRatio = 0.1f;
maxInertiaRatio = std::numeric_limits<float>::max();
filterByConvexity = true;
//minConvexity = 0.8;
minConvexity = 0.95f;
maxConvexity = std::numeric_limits<float>::max();
}
但是它的
Convexity
很高,一般來說,如果要達到這么高的
Convexity
,那么肯定是一個封閉圖形;反而也可以直接使用圓度來進行判斷,但是得到的結果要少一些。
5、blob和contours的區別、對比
blob和contours是高、低配關系。可以通過代碼非常明顯地看出,blob調用了contours方法,但僅僅是一種方法;blob在輪廓篩選這塊更成熟;但contours還有一個重要的信息,那就是“輪廓間關系”。
將來在使用上,應該推廣blob方法,但是可能不僅僅是調用其函數,還是需要將其內容掰開來具體研究分析;對於有“輪廓間
關系
”的情況,應該積極主動使用contours分析。
感謝閱讀至此,希望有所幫助。