我們已經得到了感興趣的輪廓,下一步就是要對輪廓進行選擇,有一些輪廓是需要——有一些是不需要的,是噪音。通過判斷一個輪廓是否為圓,在很多情況下可以幫助我們來做這至關重要的一步。
簡單的情況,比如下圖的啤酒瓶缺口檢測:

由於瓶口是有缺陷的,造成最大外輪廓不閉合——這顯然和“圓”差距很遠,那反過來說,那些差距比較小的輪廓可能就是沒有缺陷的。
再來看比較復雜的情況,我們來“數鋼管”。下圖是connection效果,我們發現了很多輪廓,但是只有一部分更接近圓——這些可能就是我們需要尋找的目標。

至關重要的一步就是建立數學模型。2017年左右為解決實際問題,我建立了模型一:
基於圓的定義:
“平面上到定點的距離等於定長的所有點組成的圖形叫做圓.定點稱為圓心,定長稱為半徑.”。那么通過判斷當前輪廓到一個定點的距離是否為定長,就可以得到當前輪廓是否更像圓。這個定點就可以采用外接圓圓心。這里的度量方式可以是方差。
//根據輪廓點和圓心計算方差 參考代碼
float
ComputeVariance(std
:
:
vector
<
cv
:
:
Point
>
theContour,Point2f theCenter)
{
int
a[
65535
],n;
float
aver,s;
float
sum
=
0
,e
=
0
;
n
=
theContour.size();
for
(
int
i
=
0
;i
<
n;i
++
)
{
a[i]
=
GetDistance(theContour[i],theCenter);
sum
+=
a[i];
}
aver
=
sum
/
n;
for
(
int
i
=
0
;i
<
n;i
++
)
e
+=
(a[i]
-
aver)
*
(a[i]
-
aver);
e
/=
n
-
1
;
s
=
sqrt(e);
return
e;
}
模型一使用了3年左右,也解決了很多問題,特別對於簡單問題來說,效果是很好的。但是它有一個明顯的缺點,就是關於這個結果方差的度量,每次都需要不斷試驗才能夠得出一個較好的值。2020年我遇到了前面的“數鋼管”問題,出現了新的困難,比如下圖:

當輪廓為長條形的時候(上圖紅圈),會錯誤地識別為圓。這種長條型輪廓可以抽象為下圖紅圈:

這種情況的方差可能不是很大,雖然直觀理解其它,它很不是一個圓。
經過一段時間思索和尋找,建立模型二:
基於“圓度”的定義:設平面上一個封閉圖形(內部無空洞)的面積為S,它的周長為C,則定義該圖形的圓度為:
4 * PI * S
Afa = ------------
C * C
正圓的afa為1,輪廓的afa值越接近1,輪廓越解決圓
4 * PI * S
Afa = ------------
C * C
正圓的afa為1,輪廓的afa值越接近1,輪廓越解決圓
//afa參考代碼
double s
= cv
:
:contourArea(contours_test[i]);
//輪廓面積
double c
= cv
:
:arcLength(contours_test[i],
true);
//輪廓周長
float afa
=
4
* PI
*s
/ (c
*c);
//afa計算
afa
= abs(afa
-
1);
比較不同afa閾值情況下,對此輪廓篩選結果
afa(越小越好) | 結果 |
無 | ![]() |
0.5 | ![]() |
0.3 | ![]() |
0.4 | ![]() |
從結果上可以明顯看出,afa方法很好地過濾掉了噪音。
感謝閱讀至此,希望有所幫助。!
參考資料:
P.S
connection效果參考代碼,來自GOCVHelper,在Github上可以找到,說明文件:
//尋找並繪制出彩色聯通區域
vector
<VP
> connection2(Mat src, Mat
& draw) {
RNG rng(
12345);
draw
= Mat
:
:zeros(src.rows, src.cols, CV_8UC3);
vector
<VP
>contours;
findContours(src.clone(), contours, RETR_LIST,CHAIN_APPROX_SIMPLE);
//由於給大的區域着色會覆蓋小的區域,所以首先進行排序操作
//冒泡排序,由小到大排序
VP vptmp;
for (
int i
=
1; i
< contours.size(); i
++) {
for (
int j
= contours.size()
-
1; j
>
= i; j
--) {
if (contourArea(contours[j])
< contourArea(contours[j
-
1]))
{
vptmp
= contours[j
-
1];
contours[j
-
1]
= contours[j];
contours[j]
= vptmp;
}
}
}
//打印結果
for (
int i
= contours.size()
-
1; i
>
=
0; i
--) {
Scalar color
= Scalar(rng.uniform(
0,
255), rng.uniform(
0,
255), rng.uniform(
0,
255));
drawContours(draw, contours, i, color,
-
1);
}
return contours;
}