邊緣提取與直線檢測


計算機中的邊緣算法主要是依靠梯度差來計算,常見的有sobel算子,lapacian算子等,在實現方法上都大同小異,OpenCV中對這類函數都有封裝,使用起來很方便:

1.Sobel算子的邊緣檢測

我們先找一張灰度圖像,這里用一張照片,取在HSV色域的V通道:

sobel算子有兩個方向:

-1 -2 -1
0 0 0
1 2 1

 

 

-1 0 1
-2 0 2
-1 0 1

 

 

分別用來檢測水平方向與豎直方向上的邊緣,

cv::Sobel(image, sobelX, CV_16S, 1, 0);//1,0代表水平方向
cv::Sobel(image, sobelY, CV_16S, 0, 1);//0,1代表豎直方向

因為計算后的像素值區間在-510~510之間,所以這邊要使用16位的類型來儲存。

然后我們將兩個方向上的值相加:

sobel = abs(sobelX) + abs(sobelY);

相加后的像素值區間在0~1020之間,無法顯示,我們要將其歸一化,即每個值都除以最大值,然后乘以255.

這里我們要將大的值(邊緣)是用黑色表示,所以在歸一化的時候要使用-255:

 double sobmin, sobmax; 
 cv::minMaxLoc(sobel, &sobmin, &sobmax);  //這個方法可以返回最大值
 cv::Mat sobelImage;
 sobel.convertTo(sobelImage, CV_8U, -255. / sobmax, 255);

最后一行的意義是將數據轉化為8位,將每個像素點的值X做如下運算:

X=X*(-255/sobmax)+255;從而將值域轉化為0~255之間:

結果如下:

這張圖中,越黑的位置代表其邊緣的強度越強,如果我們要忽略弱邊緣,則需要在這張圖上加一個閾值:

 

cv::threshold(sobelImage, sobelThresholded, 190, 255, cv::THRESH_BINARY)

上面這張圖中,大於190的部分將被轉換為白色,小於190的地方會被轉換為黑色:

選取閾值后,會發現盡管一些不相關的邊緣被抹去了,但是有些我們希望保留的邊緣(杯子右側)同樣被忽略,這種情況下Canny在1986年提出了一種策略模式來優化邊緣提取:

2.Canny算法的優化邊緣提取

OpenCV中可以直接運用canny算法,canny算法實際上是將sobel算子應用兩次,取不同於閾值,一個是低閾值,低閾值要包含像素全部的重要邊緣,高閾值要盡量將全部的非重要邊緣去除。

cv::Mat contours;
    cv::Canny(image, contours, 110,110);

這里可以教大家一個小技巧,在試閾值的時候,將兩個閾值調到一樣的值,然后看效果,比如先都調到110,這是低閾值:

低閾值包含了全部重要邊緣,然后設置高閾值,我們以行李箱中的紋路全部消失為界,同時調到380:

 

下面執行110到380的閾值,比較下原圖和最終的邊緣提取:

 3.霍夫變換的直線檢測

 霍夫變換是一個靠點的數量來判斷空間中特定形狀的存在的,其中最簡單的形狀就是直線,在實現霍夫變換檢測直線之前,我們先復習一下直線在空間中的表示。

任意直線在空間中都可以表示為y=kx+b的形式,但是作為斜率的k在空間中的取值為0到正無窮。在直線接近垂直於y軸的時候,難以表示,為了更好的表示是空間中的直線,Hough變換中,通常用極坐標表示空間內的直線:

空間內任意一條直線都可以用原點到其的距離r與這條垂直線和x軸的夾角θ表示,記為(r,θ)。

這時直線上任意一點(x,y),可以表示為:

r=xcos(θ)+ysin(θ)

也就是說,對於一個(x,y)來說,我們可以在(r,θ)空間中畫出很多條函數,表示通過這一點的所有直線,但是對於特定直線上的每個點,他們必定都通過(r,θ)這個點。

如果某一個(r,θ)出現的過多我們可以認定為w空間中很多點都在這條直線上,在圖像內這是一條直線。

 

在程序中,我們一般用一個2為數組Hough[n][360]來表示空間內的所有直線,其中n為圖像對角線的長度。

對於邊緣檢測中的所有像素點(x,y)進行一個360度的遍歷,最終如果某一個n值出現多過一個閾值,則判定為一條直線。

OpenCV中使用Hough變換很方便,一般只需兩步就可以得到一個結果直線的矩陣:

cv::Mat contours;
cv::Canny(image, contours,110,380);
vector<cv::Vec2f> lines;
cv::HoughLines(contours, lines, 1, PI / 180, 60);

Hough中的五個參數分別是:邊緣檢測結果,輸出結果矩陣,半徑步長,角度步長,閾值(多少個點算直線);

然后我們需要把這個結果矩陣畫到原來的圖上:

 這里我們需要特別注意一點,在畫線的過程中我們是以直線方程與坐標軸相交的兩個點來確定直線的,這時我們要用到cvPoint來定位這兩個點,平常我們定位圖像中坐標的時候是先行,在列,而cvPoint中是先列,再行。

因此對於 cv:Point pt1 (20,0)來說,pt1指的是圖像的第20列,0行這個點,而對於指令image.at<cv::vec3b>(20,0),來說,指的則是20行第0列這個點。

vector<cv::Vec2f>::const_iterator it = lines.begin();  //初始化迭代器遍歷所有直線
    while (it != lines.end()){
        float rho = (*it)[0];                                //rho訪問半徑
        float theta = (*it)[1];                                //theta方訪問角度

        if (theta<PI / 4. || theta>3.*PI / 4.){                //近似垂直線
            cv::Point pt1(rho / cos(theta), 0);                //計算其與圖像上方的交點
            cv::Point pt2((rho - image1.rows*sin(theta)) / cos(theta), image1.rows);//下方交點
            cv::line(image1, pt1, pt2, cv::Scalar(255), 1);//圖像,2個點,顏色
        }
        else{
            cv::Point pt1(0, rho / sin(theta)); //左方交點
            cv::Point pt2(image1.cols, (rho - image1.cols*cos(theta)) / sin(theta));//右方交點
            cv::line(image1, pt1, pt2, cv::Scalar(255), 1);
        }
        ++it;
    }

結果如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM