OpenCV開發筆記(四十四):紅胖子8分鐘帶你深入瞭解霍夫圓變換(圖文並茂+淺顯易懂+程序源碼)

若該文爲原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105575546
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究

目錄

前言

Demo

霍夫變換

概述

霍夫圓變換

概述

原理

1.圓的圖像二維空間可由笛卡爾座標系表示

2.三維曲線的形成原理

3.判斷圓的依據

霍夫梯度法原理

霍夫梯度法優點

霍夫梯度法缺點

霍夫圓變換函數原型

Demo源碼

工程模板:對應版本號v1.39.0


OpenCV開發專欄(點擊傳送門)

 

    OpenCV開發筆記(四十四):紅胖子8分鐘帶你深入瞭解霍夫圓變換(圖文並茂+淺顯易懂+程序源碼)

 

前言

      紅胖子來也!!!

去噪、邊緣檢測之後,就是特徵提取了,識別圖形的基本方法之一---霍夫變換,霍夫變換是圖像處理中的一種特徵提取技術,本篇章主要講解霍夫圓變換。

 

Demo

      前面2個Demo不怎麼準確,是因爲筆者對原圖進行了縮放,導致原本的圓形有點變形了,最後一個則是專門按照縮放後的分辨率,自己繪製的。

 

霍夫變換

概述

      霍夫變換(Hough Transform)是圖像處理中的一種特徵提取技術,改過程在一個參數空間中通過計算累計結果的局部最大值得到一個符合該特定形狀的集合作爲霍夫變換結果。

      經典的霍夫變換用來檢測圖像中的直線,後來霍夫變換擴展到任意形狀物體的識別,多爲圓和橢圓。

      霍夫變換運用兩個座標空間之間的變換將在一個空間中具有相同的形狀的曲線或直線映射到另一個座標控件的一個點上形成峯值,從而把檢測任何形狀的問題轉化爲統計峯值問題。

      OpenCV中的霍夫變換分爲兩大類型,線變換下又分三種類型,如下圖:

霍夫圓變換

概述

      霍夫圓變換,從名字就可以知道其實針對圓,顯而易見就是用來尋找圓的方法,此處特別注意,使用霍夫圓變換之前,肯定是需要對圖片進行預處理:降噪、邊緣檢測處理,霍夫圓變換隻尋找圓,只能識別邊緣二值圖像,所以輸入也就只能是二值化(單通道8位)的圖像了。

      霍夫圓變換會找出大量的圓,但是有些圓其實是無用的,與霍夫線變換一樣都可能會產生“噪聲”數據。

原理

1.圓的圖像二維空間可由笛卡爾座標系表示

  • 在笛卡爾座標系(霍夫圓變換採用的方式):可有圓心(a,b),半徑r表示;

在笛卡爾座標系中圓的方式:

公式得出:

所以在abr組成的三維座標系中,一個點可以唯一確定一個圓。

2.三維曲線的形成原理

在笛卡爾的xy座標系中經過某一點的所有圓映射到abr座標系中就是一條三維的曲線。

經過xy座標系中所有的非零像素點的所有圓就構成了abr座標系中很多條三維的曲線。

3.判斷圓的依據

在xy座標系中同一個圓上的所有點的圓方程是一樣的,它們映射到abr座標系中的是同一個點,所以在abr座標系中該點就應該有圓的總像素N0個曲線相交。通過判斷abr中每一點的相交(累積)數量,大於一定閾值的點就認爲是圓。

以上是標準霍夫圓變換實現算法,問題是它的累加面是一個三維的空間,意味着比霍夫線變換需要更多的計算消耗。

Opencv霍夫圓變換對標準霍夫圓變換做了運算上的優化。它採用的是“霍夫梯度法”。它的檢測思路是去遍歷累加所有非零點對應的圓心,對圓心進行考量。

圓心一定是在圓上的每個點的模向量上,即在垂直於該點並且經過該點的切線的垂直線上,這些圓上的模向量的交點就是圓心。

霍夫梯度法就是要去查找這些圓心,根據該“圓心”上模向量相交數量的多少,根據閾值進行最終的判斷。

霍夫梯度法原理

  1. 首先對圖像進行邊緣檢測,比如用canny邊緣檢測;
  2. 對邊緣圖像中的每一個非零點,考慮其局部梯度,即用Sobel()函數計算x和y方向的Sobel一節導數得到梯度;
  3. 利用得到的梯度,有些率指定直線上的每一個點都在累加器中被累加,這裏的斜率是從一個指定的最值到指定的最大值的距離;
  4. 同時,標記邊緣圖像中每一個非0像素的位置;
  5. 從二維累加器中這些點中選擇候選的中心,這樣中心都大於給定閾值並且大於其所有近鄰。這些候選的中心按照累加值降序排列,一邊最支持像素的中心首先出現;
  6. 對每個中心,考慮所有的非0像素;
  7. 這下像素按照其與中心的距離排列,從到最大半徑的最小距離算起,選擇非0像素最支持的一條半徑;
  8. 如果一箇中心收到邊緣圖像非0像素最充分的支持,並且到前期被選擇的中心有足夠的距離,那麼它就會被保留下去;

霍夫梯度法優點

      算法高效,並能夠解決三維累加器中會產生許多噪聲並且使得結果不穩定的稀疏不穩定問題;

霍夫梯度法缺點

  • 使用Sobel導數來計算可能導致更多的噪聲;
  • 在邊緣圖像中整個非0像素都被當做候選中心,所以把累加器值設置偏低會導致點暴漲,以致於計算量較大,算法消耗時間過長;
  • 中心按照其關聯的累加器的升序排列的,並且如果新的中心過於接近之前已經接受的中心點的話,就不會保留該點,等於是會傾向於同心圓只保留最大半徑的圓;

霍夫圓變換函數原型

void HoughCircles( InputArray image,
                OutputArray circles,
                int method,
                double dp,
                double minDist,
                double param1 = 100,
                double param2 = 100,
                int minRadius = 0,
                int maxRadius = 0);
  • 參數一:InputArray類型的image,源圖像8位,單通道二進制圖像。可以將任意的原圖載入進來,並由函數修改成此格式後,再填這裏;
  • 參數二:OutputArray類型的cirlces,輸出圓向量,每個向量包括三個浮點型的元素——圓心橫座標,圓心縱座標和圓半徑;
  • 參數三:int類型的method,爲使用霍夫變換圓檢測的算法;

序號

枚舉

描述

1

HOUGH_STANDARD

0

CV_HOUGH_STANDARD - 傳統或標準 Hough 變換(SHT)。每一個線段由兩個浮點數 (ρ, θ) 表示,其中 ρ 是直線與原點 (0,0) 之間的距離,θ 線段與 x-軸之間的夾角。因此,矩陣類型必須是 CV_32FC2 type;

2

HOUGH_PROBABILISTIC

1

CV_HOUGH_PROBABILISTIC- 概率 Hough 變換(PPHT)。如果圖像包含一些長的線性分割,則效率更高。它返回線段分割而不是整個線段。每個分割用起點和終點來表示,所以矩陣(或創建的序列)類型是 CV_32SC4 type

3

HOUGH_MULTI_SCALE

2

傳統 Hough 變換的多尺度變種。線段的編碼方式與 CV_HOUGH_STANDARD 的一致

4

HOUGH_GRADIENT

3

基本上是 21HT

  • 參數四:double類型的dp,dp:尋找圓弧圓心的累計分辨率,這個參數允許創建一個比輸入圖像分辨率低的累加器。(這樣做是因爲有理由認爲圖像中存在的圓會自然降低到與圖像寬高相同數量的範疇)。如果dp設置爲1,則分辨率是相同的;如果設置爲更大的值(比如2),累加器的分辨率受此影響會變小(此情況下爲一半);dp的值不能比1小;
  • 參數五:double類型的minDist,精度,該參數是讓算法能明顯區分的兩個不同圓之間的最小距離;
  • 參數六:double類型的param1,默認爲100用於Canny的邊緣閥值上限,下限被置爲上限的一半
  • 參數七:double類型的param2,默認爲100,在霍夫梯度的情況下,它是檢測階段圓中心的累加器閾值。它越小,就越可以檢測到更多根本不存在的圓,而越多,能通過檢測的圓就是更加接近完美原型;
  • 參數八:int類型的minRadius,默認爲0,檢測圓的最小半徑;
  • 參數九:int類型的maxRadius,默認爲0,檢測圓的最大半徑,爲0時,最大爲像素矩陣大小;

 

Demo源碼

void OpenCVManager::testHoughCircles()
{
    QString fileName1 =
            "E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/17.jpg";
    cv::Mat srcMat = cv::imread(fileName1.toStdString());
    int width = 400;
    int height = 300;

    cv::resize(srcMat, srcMat, cv::Size(width, height));
    cv::Mat colorMat = srcMat.clone();

    cv::String windowName = _windowTitle.toStdString();
    cvui::init(windowName);

    cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
                                srcMat.type());

    cv::cvtColor(srcMat, srcMat, CV_BGR2GRAY);

    int threshold1 = 200;
    int threshold2 = 100;
    int apertureSize = 1;

    int dp = 10;            // 默認1像素
    int minDist = 10;       // 默認1°
    int minRadius = 0;
    int maxRadius = 0;

    while(true)
    {
        qDebug() << __FILE__ << __LINE__;
        windowMat = cv::Scalar(0, 0, 0);

        cv::Mat mat;
        cv::Mat dstMat;
        cv::Mat grayMat;

        // 轉換爲灰度圖像
        // 原圖先copy到左邊
        cv::Mat leftMat = windowMat(cv::Range(0, srcMat.rows),
                                    cv::Range(0, srcMat.cols));
        cv::cvtColor(srcMat, grayMat, CV_GRAY2BGR);
        cv::addWeighted(leftMat, 0.0f, grayMat, 1.0f, 0.0f, leftMat);

        {
            cvui::printf(windowMat,
                         width * 1 + 100,
                         height * 0 + 20,
                         "threshold1");
            cvui::trackbar(windowMat,
                           width * 1 + 100,
                           height * 0 + 50,
                           200,
                           &threshold1,
                           0,
                           255);
            cvui::printf(windowMat,
                         width * 1 + 100,
                         height * 0 + 100, "threshold2");
            cvui::trackbar(windowMat,
                           width * 1 + 100,
                           srcMat.cols * 0 + 130,
                           200,
                           &threshold2,
                           0,
                           255);

            qDebug() << __FILE__ << __LINE__;
            cv::Canny(srcMat, dstMat, threshold1, threshold2, apertureSize * 2 + 1);
            // copy
            mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));

            cv::cvtColor(dstMat, grayMat, CV_GRAY2BGR);
            cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);

            cvui::printf(windowMat,
                         width * 1 + 100,
                         height * 1 + 20 - 80,
                         "dp = value / 10");
            cvui::trackbar(windowMat,
                           width * 1 + 100,
                           height * 1 + 50 - 80,
                           200,
                           &dp,
                           1,
                           1000);
            cvui::printf(windowMat,
                         width * 1 + 100,
                         height * 1 + 100 - 80,
                         "minDist = value / 2");
            cvui::trackbar(windowMat,
                           width * 1 + 100,
                           height * 1 + 130 - 80,
                           200,
                           &minDist,
                           1,
                           720);
            cvui::printf(windowMat,
                         width * 1 + 100,
                         height * 1 + 180 - 80,
                         "minRadius");
            cvui::trackbar(windowMat,
                           width * 1 + 100,
                           height * 1 + 210 - 80,
                           200,
                           &minRadius,
                           0,
                           100);
            cvui::printf(windowMat,
                         width * 1 + 100,
                         height * 1 + 260 - 80,
                         "maxRadius");
            cvui::trackbar(windowMat,
                           width * 1 + 100,
                           height * 1 + 290 - 80,
                           200,
                           &maxRadius,
                           0,
                           1000);
            // 邊緣檢測後,進行霍夫圓檢測
            std::vector<cv::Vec3f> circles;
            cv::HoughCircles(dstMat,
                             circles,
                             cv::HOUGH_GRADIENT,
                             dp / 10.0f,
                             minDist / 10.0f,
                             200,
                             100,
                             minRadius,
                             maxRadius);
            // 在圖中繪製出每條線段
            dstMat = colorMat.clone();
            for(int index = 0; index < circles.size(); index++)
            {
                cv::Point center(cvRound(circles[index][0]),
                                 cvRound(circles[index][1]));
                int radius = cvRound(circles[index][2]);
                // 繪製圓心
                cv::circle(dstMat, center, 3, cv::Scalar(255, 255, 255));
                // 繪製圓
                cv::circle(dstMat, center, radius, cv::Scalar(0, 0, 255));
            }
            // copy
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                            cv::Range(srcMat.cols * 1, srcMat.cols * 2));
            cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);


            // 在圖中繪製出每條線段
            for(int index = 0; index < circles.size(); index++)
            {
                cv::Point center(cvRound(circles[index][0]),
                                 cvRound(circles[index][1]));
                int radius = cvRound(circles[index][2]);
                // 繪製圓心
                cv::circle(grayMat, center, 3, cv::Scalar(255, 255, 255));
                // 繪製圓
                cv::circle(grayMat, center, radius, cv::Scalar(0, 0, 255));
            }
            // copy
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));
            cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
        }
        // 更新
        cvui::update();
        // 顯示
        cv::imshow(windowName, windowMat);
        // esc鍵退出
        if(cv::waitKey(25) == 27)
        {
            break;
        }
    }
}

 

工程模板:對應版本號v1.39.0

      對應版本號v1.39.0

 

原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105575546

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章