邊緣提取與直線檢測

計算機中的邊緣算法主要是依靠梯度差來計算,常見的有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位的類型來儲存。spa

而後咱們將兩個方向上的值相加:code

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

相加後的像素值區間在0~1020之間,沒法顯示,咱們要將其歸一化,即每一個值都除以最大值,而後乘以255.ci

這裏咱們要將大的值(邊緣)是用黑色表示,因此在歸一化的時候要使用-255:it

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

最後一行的意義是將數據轉化爲8位,將每一個像素點的值X作以下運算:table

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;
    }

結果以下:

相關文章
相關標籤/搜索