霍夫變換是一種用於在圖像中查找直線、圓或其他簡單形狀的方法。最初的霍夫變換是直線變換,這是一種在二值圖像中搜索直線的方法。該變換可以進一步推廣到圓以及橢圓等情況。
霍夫直線變換的基本理論是,二值圖像中的任何點都可能是某條直線的一部分。如果通過斜率a和截距b對直線進行參數化,則原始圖像中的一個點將轉換為(a,b)平面中與通過該點的所有線相對應的點的軌跡。如果將輸入圖像中的每個非零像素轉換為輸出圖像中的這樣一組點並對所有這些貢獻求和,那麼出現在輸入(即(x,y)平面)圖像中的直線將顯示為在輸出(即(a,b)平面)圖像中的局部最大值。(a,b)平面通常稱為累加器平面。
截距截取形式實際上並不是代表所有通過點的線的最佳方式(即,由於線的密度隨斜率的不同而存在很大差異,並且可能的斜率從–∞到+∞)。這就是為什麼數值計算中使用的變換圖像的實際參數化有些不同的原因。通常將每條線表示為極坐標(ρ,θ)中的一個點,隱含的線是穿過指定點但垂直於從原點到該點的半徑的線。OpenCV的Hough變換算法返回(ρ,θ)平面中的局部最大值。
OpenCV支持三種不同的霍夫直線變換:標準霍夫變換SHT,多尺度霍夫變換MHT和漸進概率霍夫變換PPHT。PPHT是此算法的一種變體,之所以稱其為「概率」,是因為它只累加了一部分,而不是累加了累加器平面中的每個可能點。因為如果無論如何峰值都足夠高,那麼僅其中一小部分就足以找到它。這種結果是大大減少了計算時間。
標準和多尺度Hough變換都在函數HoughLines()中實現,區別在於使用或不使用最後兩個可選參數。
voidcv::HoughLines(
cv::InputArrayimage,
cv::OutputArraylines,
doublerho,
doubletheta,
intthreshold,
doublesrn = 0,
doublestn = 0
);
第一個參數是輸入圖像,它必須是8位圖像,但是輸入被視為二值信息(即所有非零像素都被視為等效)。第二個參數是找到的行將被存儲的位置,這是一個N×1兩通道浮點型數組(列數N將是返回的行數),對於每個找到的行包含rho(ρ)和theta(θ)值。參數rho和theta設置直線所需的解析度(即累加器平面的解析度)。rho的單位是像素,theta的單位是弧度。閾值是累加器平面中的值。該參數表明必須要返回的線的點數。
標準的Hough變換不使用參數srn和stn。它們用於多尺度霍夫變換(MHT)。對MHT,這兩個參數表明應針對這些線的參數計算出更高的解析度。MHT首先根據rho和theta參數給定的精度計算直線的位置,然後繼續分別以srn和stn的因子來細化這些結果,即rho的最終解析度是rho除以srn,並且theta的最終解析度為theta除以stn。
voidcv::HoughLinesP(
cv::InputArrayimage,
cv::OutputArraylines,
doublerho,
doubletheta,
intthreshold,
doubleminLineLength = 0,
doublemaxLineGap = 0
);
HoughLinesP()函數與HoughLines()非常相似,但有兩個重要區別。第一個是lines參數將是一個四通道數組,找到的是線段兩個端點的位置(x0,y0)和(x1,y1)。第二個區別是對於PPHT,minLineLength和maxLineGap參數設置將返回線段的最小長度,以及算法不將其連接到單個較長線段中所需的共線段之間的分隔。
霍夫圓變換與霍夫直線變換大致相似。但它必須用具有三個維度的累加器,一個用於x,一個用於y(圓心的位置),另一個用於圓半徑r。這意味著更高的內存需求和更慢的速度。在OpenCV中,通過使用一種稱為Hough梯度的方法避免了此問題。霍夫梯度的原理如下:首先,圖像進行邊緣檢測;接下來,對於邊緣圖像中的每個非零點,都要考慮局部梯度。使用該梯度,沿該斜率指示從-a處增加每個點。指定的最小到指定的最大距離在累加器中。同時,記錄這些非零像素中的每個像素在邊緣圖像中的位置。然後從該(二維)累加器中既高於某個給定閾值又大於其所有直接鄰域的那些點中選擇候選中心。這些候選中心按照其累加器值的降序排序,因此具有最多支持像素的中心會首先出現。接下來,對於每個中心,均考慮所有非零像素。這些像素根據它們與中心的距離排序。從最小的距離到最大的半徑,選擇一個最好由非零像素支持的半徑。如果中心有來自邊緣圖像中非零像素的足夠支持,並且距任何先前選擇的中心有足夠的距離,則保留中心。
這種實現方式使算法可以運行得更快,並且它有助於解決三維累加器稀疏的問題,否則將導致大量噪聲並使結果不穩定。但是有一些問題需要注意。首先,使用Sobel導數來計算局部梯度(以及隨之而來的假設可以認為它等同於局部切線)並不是一個數值穩定的命題。「大多數時候」可能是正確的,但是這會在輸出中產生一些噪聲。其次,針對每個候選中心考慮邊緣圖像中的整個非零像素集,因此,如果累加器閾值太低,該算法將花費很長時間運行。第三,由於每個中心僅選擇一個圓,因此,如果存在同心圓,則將僅獲得其中一個。
霍夫圓變換函數HoughCircles()具有與直線變換相似的參數。
voidcv::HoughCircles(
cv::InputArrayimage,
cv::OutputArraycircles,
intmethod,
doubledp,
doubleminDist,
doubleparam1 = 100,
doubleparam2 = 100,
intminRadius = 0,
intmaxRadius = 0
);
輸入圖像還是8位圖像。HoughCircles()和HoughLines()之間的一個重要區別是後者需要二值圖像。HoughCircles()函數內部自動調用Sobel(),因此可以提供更通用的灰度圖像。
結果數組將是矩陣數組或向量,具體取決於傳遞給HoughCircles()的內容。如果使用矩陣,它將是類型為F32C3的一維數組;這三個通道將用於編碼圓的位置及其半徑。如果使用向量,則其類型必須為vector <Vec3f>。方法參數必須始終設置HOUGH_GRADIENT。
參數dp是所用累加器圖像的解析度。此參數使我們可以創建解析度比輸入圖像低的累加器。因為沒有理由期望圖像中存在的圓自然落入與框的寬度或高度相同數量中。如果dp設置為1,則解析度將相同;否則,解析度將變為1。如果將其設置為較大的數字,則累加器解析度將減小該因子(在這種情況下為一半)。dp的值不能小於1。
參數minDist是兩個圓之間必須存在的最小距離,以便將它們視為不同的圓。
對於設置為HOUGH_GRADIENT的方法,接下來的兩個參數param1和param2分別是邊沿Canny閾值和累加器閾值。Canny邊緣檢測器本身實際上採用了兩個不同的閾值。當內部調用Canny()時,第一個(較高)閾值設置為參數param1的值傳遞到HoughCircles()中,並且第二個(較低的)閾值正好設置為該值的一半。參數param2是用於對累加器進行閾值處理的參數,它與Hough Lines()的閾值參數完全相似。
最後兩個參數是可以找到的圓的最小和最大半徑。這意味著這些是累加器為其表示的圓的半徑。例1顯示了一個使用HoughCircles()的示例程序。
例1:使用霍夫圓變換在圖像中查找直線和圓
#include<opencv2/opencv.hpp>#include<iostream>#include<math.h>usingnamespace cv;usingnamespace std;int main(intargc, char** argv){ Mat src; Mat thrImg; src = imread("E:/hough.bmp", 0); imshow("src", src); threshold(src, thrImg, 100, 255, 1); vector<Vec2f> lines; HoughLines(thrImg, lines, 1.1, CV_PI/180, 200); for (size_t i = 0; i < lines.size(); i++) { float rho = lines[i][0], theta = lines[i][1]; Point pt1, pt2; double a = cos(theta), b = sin(theta); double x0 = a*rho, y0 = b*rho; pt1.x = cvRound(x0 + 2000 * (-b)); pt1.y = cvRound(y0 + 2000 * (a)); pt2.x = cvRound(x0 - 2000 * (-b)); pt2.y = cvRound(y0 - 2000 * (a)); line(src, pt1, pt2, Scalar(0,0,255), 2); } vector<Vec3f> circles; HoughCircles(src, circles, cv::HOUGH_GRADIENT,2,100,80,100,30,60 ); for (size_t i = 0; i < circles.size(); ++i) {circle(src,Point(cvRound(circles[i][0]),cvRound(circles[i][1])),cvRound(circles[i][2]),Scalar(0, 0, 255),3); } imshow("Hough", src); waitKey(0); return 0;}值得反思的是,無論採用什麼技巧,都沒有繞過要求以三個自由度(x,y和r)來描述圓的要求。結果與之前的尋找直線相比,需要更多的內存和計算時間。考慮到這一點,最好在情況允許的範圍內緊緊地限制半徑參數,以控制計算時間。霍拉變換在1981年由Ballard擴展到任意形狀。
農曆2020年的最後一天,祝大家春節快樂。