霍夫變換與距離之和最小擬合圓方法對比
1、**霍夫變換** 其實霍夫變換理論和opencv中HoughCircles的實現是有根本的不同的,霍夫變換基於畫素對自己所屬於的直線或曲線引數方程引數進行投票,最終得票高的就是大概率在影象中存在的曲線。而HoughCircles則是根據畫素(可能的圓周邊緣)的梯度、邊緣、邊緣方向資訊進行圓心定位,然後結合圓周資訊和其他約束(如半徑大小)進行最終圓的位置的確定。
圖形可以用一些引數進行表示,標準霍夫變換的原理就是把影象空間轉換成引數空間(即霍夫空間),例如霍夫變換的直線檢測就是在距離-角度空間內進行檢測。圓可以表示成:
其中a和b表示圓心座標,r表示圓半徑,因此霍夫變換的圓檢測就是在這三個引數組成的三維空間內進行檢測。
原則上,霍夫變換可以檢測任何形狀。但複雜的形狀需要的引數就多,霍夫空間的維數就多,因此在程式實現上所需的記憶體空間以及執行效率上都不利於把標準霍夫變換應用於實際複雜圖形的檢測中。所以一些改進的霍夫變換就相繼提出,它們的基本原理就是儘可能減小霍夫空間的維數。
HoughCircles函式實現了圓形檢測,它使用的演算法也是改進的霍夫變換——2-1霍夫變換(21HT)。也就是把霍夫變換分為兩個階段,從而減小了霍夫空間的維數。第一階段用於檢測圓心,第二階段從圓心推匯出圓半徑。檢測圓心的原理是圓心是它所在圓周所有法線的交匯處,因此只要找到這個交點,即可確定圓心,該方法所用的霍夫空間與影象空間的性質相同,因此它僅僅是二維空間。檢測圓半徑的方法是從圓心到圓周上的任意一點的距離(即半徑)是相同,只要確定一個閾值,只要相同距離的數量大於該閾值,我們就認為該距離就是該圓心所對應的圓半徑,該方法只需要計算半徑直方圖,不使用霍夫空間。圓心和圓半徑都得到了,那麼通過公式1一個圓形就得到了。從上面的分析可以看出,2-1霍夫變換把標準霍夫變換的三維霍夫空間縮小為二維霍夫空間,因此無論在記憶體的使用上還是在執行效率上,2-1霍夫變換都遠遠優於標準霍夫變換。但該演算法有一個不足之處就是由於圓半徑的檢測完全取決於圓心的檢測,因此如果圓心檢測出現偏差,那麼圓半徑的檢測肯定也是錯誤的。2-1霍夫變換的具體步驟為:
第一階段:檢測圓心
1.1、對輸入影象邊緣檢測;
1.2、計算圖形的梯度,並確定圓周線,其中圓周的梯度就是它的法線;
1.3、在二維霍夫空間內,繪出所有圖形的梯度直線,某座標點上累加和的值越大,說明在該點上直線相交的次數越多,也就是越有可能是圓心;
1.4、在霍夫空間的4鄰域內進行非最大值抑制;
1.5、設定一個閾值,霍夫空間內累加和大於該閾值的點就對應於圓心。
第二階段:檢測圓半徑
2.1、計算某一個圓心到所有圓周線的距離,這些距離中就有該圓心所對應的圓的半徑的值,這些半徑值當然是相等的,並且這些圓半徑的數量要遠遠大於其他距離值相等的數量;
2.2、設定兩個閾值,定義為最大半徑和最小半徑,保留距離在這兩個半徑之間的值,這意味著我們檢測的圓不能太大,也不能太小;
2.3、對保留下來的距離進行排序;
2.4、找到距離相同的那些值,並計算相同值的數量;
2.5、設定一個閾值,只有相同值的數量大於該閾值,才認為該值是該圓心對應的圓半徑;
2.6、對每一個圓心,完成上面的2.1~2.5步驟,得到所有的圓半徑。
HoughCircles函式的原型為:
void HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0,int maxRadius=0 )
image為輸入影象,要求是灰度影象
circles為輸出圓向量,每個向量包括三個浮點型的元素——圓心橫座標,圓心縱座標和圓半徑
method為使用霍夫變換圓檢測的演算法,Opencv2.4.9只實現了2-1霍夫變換,它的引數是CV_HOUGH_GRADIENT
dp為第一階段所使用的霍夫空間的解析度,dp=1時表示霍夫空間與輸入影象空間的大小一致,dp=2時霍夫空間是輸入影象空間的一半,以此類推
minDist為圓心之間的最小距離,如果檢測到的兩個圓心之間距離小於該值,則認為它們是同一個圓心
param1為邊緣檢測時使用Canny運算元的高閾值
param2為步驟1.5和步驟2.5中所共有的閾值
minRadius和maxRadius為所檢測到的圓半徑的最小值和最大值。 霍夫變換適合檢測同一幅影象上多個圓的情況,但是擬合的不是很準確,同時在對單個圓經行擬合時,可能擬合出多個圓。
#include "stdafx.h" #include "ArcDetect.h" #include<iostream> #include <stdio.h> #include<opencv2/core/core.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include <opencv2/opencv.hpp> #include<fstream> #include "cxcore.hpp" #include "cv.hpp" #include <map> #include <cmath> #define pi 3.14159265; #ifdef _DEBUG #pragma comment(lib,"opencv_world320d.lib") #else #pragma comment(lib,"opencv_world320.lib") #endif
using namespace cv; using namespace std;
//=======函式實現=====================================================================
//=======呼叫函式=====================================================================
int main() {
Mat src_color = imread("C:\\Users\\wang\\Desktop\\4.jpg");//讀取原彩色圖 imshow("原圖-彩色", src_color);
//宣告一個三通道影象,畫素值全為0,用來將霍夫變換檢測出的圓畫在上面 Mat dst(src_color.size(), src_color.type()); dst = Scalar::all(0);
Mat src_gray;//彩色影象轉化成灰度圖 cvtColor(src_color, src_gray, COLOR_BGR2GRAY); imshow("原圖-灰度", src_gray); imwrite("src_gray.png", src_gray);
//對灰度影象進行雙邊濾波 Mat BilateralFilterImg; bilateralFilter(src_gray, BilateralFilterImg, kvalue, kvalue * 2, kvalue / 2); vector<Vec3f> circles;//宣告一個向量,儲存檢測出的圓的圓心座標和半徑 HoughCircles(BilateralFilterImg, circles, CV_HOUGH_GRADIENT, 1.5,50, 130,36, 10, 30);//霍夫變換檢測圓 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]); circle(dst, center, 0, Scalar(0, 255, 0), -1, 8, 0); circle(dst, center, radius, Scalar(0, 0, 255), 1, 18, 0); cout << cvRound(circles[i][0]) << "\t" << cvRound(circles[i][1]) << "\t" << cvRound(circles[i][2]) << endl;//在控制檯輸出圓心座標和半徑 } imshow("特徵提取", dst); imwrite("dst.png", dst); waitKey(); }
原圖
擬合後圖
**2、最小距離** 這種方法對誤差符合正態分佈的資料點很有效。但是在機器視覺應用中經常會碰到一些干擾點。這些干擾點多數時候是偏向某一個方向的。這時要是用最小二乘法擬合,擬合出的圓會偏很多。因此,有必要研究更有效的擬合算法。
這裡介紹一個我常用的擬合算法,根據資料點到圓的距離絕對值的和來確定圓的引數,也就是下面這個式子:
最小距離法,適合擬合影象上只有單個圓的影象,抗干擾能力強,擬合準確。
#include "stdafx.h" #include "ArcDetect.h" #include<iostream> #include <stdio.h> #include<opencv2/core/core.hpp> #include<opencv2/imgproc/imgproc.hpp> #include<opencv2/highgui/highgui.hpp> #include <opencv2/opencv.hpp> #include<fstream> #include "cxcore.hpp" #include "cv.hpp" #include <map> #include <cmath> #define pi 3.14159265; #ifdef _DEBUG #pragma comment(lib,"opencv_world320d.lib") #else #pragma comment(lib,"opencv_world320.lib") #endif
using namespace cv; using namespace std;
//=======函式實現=====================================================================
//=======呼叫函式=====================================================================
const int kvalue = 15; int main() {
Mat src_color = imread("C:\\Users\\wang\\Desktop\\5.png");//讀取原彩色圖 imshow("原圖-彩色", src_color);
//宣告一個三通道影象,畫素值全為0,用來將霍夫變換檢測出的圓畫在上面 Mat dst(src_color.size(), src_color.type()); dst = Scalar::all(0);
Mat src_gray;//彩色影象轉化成灰度圖 cvtColor(src_color, src_gray, COLOR_BGR2GRAY); imshow("原圖-灰度", src_gray); imwrite("src_gray.png", src_gray);
//對灰度影象進行雙邊濾波 Mat BilateralFilterImg; bilateralFilter(src_gray, BilateralFilterImg, kvalue, kvalue * 2, kvalue / 2); Mat EdgeImg; Canny(BilateralFilterImg, EdgeImg,200, 220); vector<vector<Point>>contours; Mat IfOffsetRoi; double maxareaRoi = 0; int maxindex = 0; cv::findContours(EdgeImg, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); for (int index = 0; index < contours.size(); index++) {
double tmparea = contours[index].size() ; if (tmparea > maxareaRoi) { maxareaRoi = tmparea; maxindex = index;//記錄最大輪廓的索引號 } } Mat image2 = src_color.clone(); Mat RoiLocate1 = Mat::zeros(src_gray.rows, src_gray.cols, CV_8UC1); cv::drawContours(RoiLocate1, contours, maxindex, Scalar(255), CV_FILLED);
std::vector<cv::Point2f> points; cv::Point2f center; double radius; for (int i = 0;i < contours[maxindex].size();i++) { points.push_back(contours[maxindex].at(i)); } circleLeastFit(points, center, radius);
circle(src_color, center, 0, Scalar(0, 255, 0), -1, 8, 0); circle(src_color, center, radius, Scalar(0, 0, 255), 1, 18, 0);
imshow("特徵提取", dst); imwrite("dst.png", dst);
waitKey();
}
原圖
擬合後的圖