圓是基本圖形的一種,更為重要的是,自然情況下采集的圖像,很少大量存在“圓”;但凡存在的,大都是人工的,那么就必然代表特定的意義,從而方便定位、分割和識別。
OpenCV現有代碼中能夠直接“找圓”,主要有2個,一個是“HoughCircle ”,另一個是“BlobDetector ”,此外基本的輪廓分析也能夠用於圓的尋找。但是這些基礎的方法,涉及到的參數比較多,一方面我們需要深入理解、一方面需要融合運用,才能夠有效提高識別准確率。因此結合實踐,整理相關內容如下:
1. “找圓”在圖像處理中的價值和應用案例;
2. 深入理解“HoughCircle ”的參數設置和優缺點;
3. 深入理解 “BlobDetector”的參數設置和應用實踐;
4. 進一步理解”閾值-輪廓-分割“的分割方法和在“找圓”上的運用;
5. 融合目前技術,提出”找圓算法鏈“,提高識別准確率。
6. 對圓度、凸性、慣性比等基礎知識的進一步認識。
希望能夠為圖像處理工程師、愛好者提供一些啟發。
二、有效“找圓”的方法
OpenCV現有代碼中,設計“找圓”算法的,主要有2個,一個是“HoughCircle ”,另一
個是“
BlobDetector
”,此外基本的輪廓分析也能夠用於圓的尋找。
2.1HoughCircle
霍夫圓變換

代碼:
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat img, gray;
if( argc != 2 || !(img=imread(argv[1], 1)).data)
return -1;
cvtColor(img, gray, COLOR_BGR2GRAY);
// smooth it, otherwise a lot of false circles may be detected
GaussianBlur( gray, gray, Size(9, 9), 2, 2 );
vector<Vec3f> circles;
HoughCircles(gray, circles, HOUGH_GRADIENT,2, gray.rows/4, 200, 100 );
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// draw the circle center
circle( img, center, 3, Scalar(0,255,0), -1, 8, 0 );
// draw the circle outline
circle( img, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
namedWindow( "circles", 1 );
imshow( "circles", img );
waitKey(0);
return 0;
}
-
特別需要注意的是,目前版本出現新參數“HOUGH_GRADIENT_ALT”,在
默認參數下比以前有很大程度的精度提升:
代碼:
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <math.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat img, gray;
if( argc != 2 || !(img=imread(argv[1], 1)).data)
return -1;
cvtColor(img, gray, COLOR_BGR2GRAY);
// smooth it, otherwise a lot of false circles may be detected
GaussianBlur( gray, gray, Size(9, 9), 2, 2 );
vector<Vec3f> circles;
HoughCircles(gray, circles, HOUGH_GRADIENT,2, gray.rows/4, 200, 100 );
for( size_t i = 0; i < circles.size(); i++ )
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
// draw the circle center
circle( img, center, 3, Scalar(0,255,0), -1, 8, 0 );
// draw the circle outline
circle( img, center, radius, Scalar(0,0,255), 3, 8, 0 );
}
namedWindow( "circles", 1 );
imshow( "circles", img );
waitKey(0);
return 0;
}
- 特別需要注意的是,目前版本出現新參數“HOUGH_GRADIENT_ALT”,在 默認參數下比以前有很大程度的精度提升:
但是HoughCircle的缺點也是顯而易見的,簡單來說,在默認參數下,它非常容易丟目標。
2.2BlobDetector
所謂Blob就是圖像中一組具有某些共同屬性(例如,灰度值)的連接像素。OpenCV提供了一種方便的方法來檢測斑點並根據不同的特征對其進行過濾。
// 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
cv::Ptr<cv::SimpleBlobDetector> detector = cv::SimpleBlobDetector::create(params);
vector<KeyPoint> keypoints;
detector->detect(screw1, keypoints);
#
endif
在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();
//斑點的最大凸度
}
- 閾值:通過使用以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。
按凸性(左低右高)
按慣性比(左低右高)

// 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
cv::Ptr<cv::SimpleBlobDetector> detector = cv::SimpleBlobDetector::create(params);
vector<KeyPoint> keypoints;
detector->detect(screw1, keypoints);
#
endif
在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。
按凸性(左低右高)
按慣性比(左低右高)



按凸性(左低右高) | 按慣性比(左低右高) |
![]() |
![]() |
這里的基礎知識可能比較復雜,關鍵是默認參數下,識別的效果應該說出奇的好。
cv
:
:Ptr
<cv
:
:SimpleBlobDetector
> detector
= cv
:
:SimpleBlobDetector
:
:create();
但是存在的主要問題是由於blob分析的配置參數太多,優化起來存在困難。同時對於某些情況,明顯識別錯誤。
2.3 基本輪廓分析
更普通的情況下,我們還是需要從輪廓分析開始,通過上面提出的“圓度”來尋找圓,主要是用來“查漏補缺”,或者是用於特殊情況的查找。
三、算法融合、協作增效
在前面已經詳細分析3種主要算法的基礎上,本文的重點創造一個“算法鏈”找到的目標有效地融合起來,並且進一步橫向分析研究算法間的關系,希望多少能夠給關注這個方向、有類似需求的創作者一些思考。
算法流程
首先對於自然圖片,通過blod detection獲得准確的半徑;
而后基於准確的半徑,分別調用HoughCircle以查漏補缺;
最后,以上獲得的結果,需要進行融合篩選。這個方法,我在“鋼管識別”項目上得到了突出的成功應用,最終能夠實現非常高的准確識別。主要是基於以下幾點:
1、blobdetector能夠找到准確的圓的半徑,但是會找錯、找漏;
2、HoughCircle在有“准確的圓的半徑”的加持下,能夠很大程度上提高准確識別效率;
3、目標物體是有“固有特征”的,比如這里需要尋找的鋼管,他們的“半徑”基本上是一致的。
如果對於這個項目感興趣,可以在51cto課程上搜索,感謝閱讀至此,希望有所幫助。