【CImg】霍夫變換——直線檢測


霍夫變換——直線檢測

 

考古debug,其實很久之前就解決的bug......一直忘記過來改文章....欸

=============================原文==================================   

此處膜拜大神(學到很多):http://blog.csdn.net/jia20003/article/details/7724530

這個博客更了很多圖像處理算法的底層實現解析,都很詳細易懂,先mark

 ========================我是分割線=============================

霍夫變換:CV中常用的識別幾何圖形的方法,其中最簡單的應用就是直線檢測

     主要原理是對於邊緣的每一個像素點(x0,y0),把可能經過它的所有直線y=kx+b,映射到k-b空間(即hough space),然后投票

     但是,對於與x軸垂直的直線,斜率不存在,無法表示,所以用參數方程表示,r = x * cos(theta) + y * sin (theta), 其中(x,y)表示某一個邊緣的像素點,r表示經過該點直線到原點的距離,theta表示r與x正軸的夾角。

     原理分析如下圖:(畫得..還挺chou...手殘)

       

      

     所以最終的霍夫空間可以用r-theta表示。

     對於每個邊緣點映射之后,在霍夫空間進行投票,每次有直線方程滿足(r, theta)點,此處的像素值+1: 

        

     最后可以得到一張這樣的hough-space圖像:

     

     某一個點越白(像素值越大)表示,越多的點經過這條直線,這就有可能是一條邊界直線

     過濾,求出局部極大值,可以得到幾條直線方程(四條單像素寬直線),然后就可以根據直線方向在原圖標定角點

       

 

以下為具體步驟以及實現

     1. 彩色圖像RBG->灰度圖Gray

        (opencv上需要注意顏色空間是RGB還是BGR,CImg中RGB分別對應0,1,2通道)

      2.       去噪(高斯核)

      3.       邊緣提取(梯度算子、拉普拉斯算子、canny; 此處實現用sobel) 

      4.       二值化(判斷此處是否為邊緣點,就看灰度值==255)

         5.       映射到霍夫空間(此處准備兩個容器,一個CImg用來展示hough-space概況,一個數組hough-space用來儲存voting的值,因為投票過程往往有某個極大值超過255,多達幾千,不能直接用灰度圖來記錄投票信息)

      6.        取局部極大值,設定閾值,過濾干擾直線

      7.        繪制直線、標定角點

 

實現

    1. 轉灰度

      可以用自帶API,或者自己寫

    2. 高斯去噪(采用了一個標准差為1的高斯核)

      

    3. sobel算子提取邊界

     sobel時梯度算子的一種

     

    4. 二值化(應該設置一個閾值,對不同的圖,不同的閾值,以便完整顯示邊界)

     在高斯去噪和邊界提取之后都需要二值化

     以下時同一張圖片的二值化(閾值分別為60、80、100、127),可見,保持較好的邊緣信息需要合適的閾值

   

    5. 映射到霍夫空間

     先在原圖構造一個x-y平面,一一對應各點的直線方程計算O(0,0)為事實上的原點,O‘(width/2,height/2)為構造平面的原點

     然后構造一個hough-space,其中縱軸表示theta的刻度,theta取值0~PI,分成500個刻度,r的最大值為max_length=sqrt((width/2)^2 + (height/2)^2),又r存在正負值,故而hough-space的橫軸需要2*max_length

       

      

    //霍夫空間,圖像初始化
    CImg<unsigned char> output(2 * max_length, hough_space, 1, 1);
    int** hough = new int*[500];
    for (int k = 0; k < hough_space; k ++)
        hough[k] = new int[2*max_length] ();
    output.fill(0);

    //檢測每一個點的所有可能直線方程,並記錄投票,以及最大值
    int max_hough = 0;
    for (int x = 0; x < width; x ++) {
        for (int y = 0; y < height; y ++) {
            int temp = (int)inputImage.atXYZC(x, y, 1, 0);
            if (temp == 0)continue;
            else {
                for (int degree = 0; degree < hough_space; degree ++) {
                    double r = (x - centerX) * cos(degree * hough_intervals) + (y - centerY) * sin(degree * hough_intervals);
                    r += max_length;
                    if (r < 0 || (r >= 2 * max_length))continue;
                    unsigned char temp = output.atXYZC((unsigned int)r, degree, 1, 0) + 1;
                    output.atXYZC((unsigned int)r, degree, 1, 0) = temp;
                    hough[degree][(int)r] ++;
                    if (max_hough < hough[degree][(int)r])max_hough = hough[degree][(int)r];
                }
            }
        }
    }
    cout << "max_hough = " << max_hough << endl;    

     6. 取局部極大值,設定閾值,過濾干擾直線(直線方程存儲在lines中)

//輸出直線軌跡
    CImg<unsigned char> output1(width, height, 1, 1);
    output1.fill(0);

    //設置閾值
    int threshold = int(max_hough * value);
    cout << "threshold = " << threshold << endl;
    int count = 0;
    vector<pair<int, int> > lines;
    //遍歷hough空間,找到所有比閾值大的點
    for (int row = 0; row < hough_space; row ++) {
        for (int col = 0; col < 2 * max_length; col ++) {
            bool newLines = true;
            int temp = hough[row][col];
            if (hough[row][col] > threshold) {
                for (int k = 0; k < lines.size(); k ++) {
                    //判斷極值
                    if ((abs(lines[k].first - row) < 15 || abs((500 - lines[k].first) + row) < 5) && abs(lines[k].second - col) < 300) {
                        if (hough[row][col] > hough[lines[k].first][lines[k].second]) {
                            lines[k].first = row;
                            lines[k].second = col;
                        }
                        newLines = false;
                    }
                }
                if (newLines) {
                    lines.push_back(make_pair(row, col));
                    //cout << "push " << row << " "<< col << endl;
                }
            }
        }
    }

 

     7. 繪制直線、標定角點(角點信息存儲在node中)

      因為有的直線斜率K可能不存在,所以我判斷兩條直線相較的條件是在draw lines的時候,看一下某像素點是不是已經被標記直線,若是,則說明有直線與當前直線相交,記錄交點(但是這種方法不是很好,最后討論優缺點)

 1 //角點
 2     vector<pair<int, int> > node;
 3 
 4     //draw lines
 5     for (int k = 0; k < lines.size(); k ++) {
 6         int row = lines[k].first;
 7         int col = lines[k].second;
 8         //cout << "line " << k << " = " << row << " " << col << endl;
 9         double dy = sin(row * hough_intervals);
10         double dx = cos(row * hough_intervals);
11         if ((row <= hough_space / 4 ) || (row >= 3 * hough_space / 4)) {
12             for (int sRow = 0; sRow < height; ++sRow) {
13                 int sCol;
14                 if (row == 0 || row == 500)sCol =  (int)(col - max_length) + centerX;
15                 sCol = (int)((col - max_length - ((sRow - centerY) * dy)) / dx) + centerX;
16                 if (sCol < width && sCol >= 0) {
17                     if((int)output1.atXYZC(sCol, sRow, 1, 0) == 255)node.push_back(make_pair(sCol, sRow));
18                     else output1.atXYZC(sCol, sRow, 1, 0) = (unsigned char)255;
19                 }
20             }
21         }
22         else {
23             for (int sCol= 0; sCol < width; ++sCol) {
24                 int sRow;
25                 if(row == 250)sRow = (int)(col - max_length) + centerY;
26                 sRow = (int)((col - max_length - ((sCol - centerX) * dx)) / dy) + centerY;
27                 if (sRow < height && sRow >= 0) {
28                     if((int)output1.atXYZC(sCol, sRow, 1, 0) == 255)node.push_back(make_pair(sCol, sRow));
29                     else output1.atXYZC(sCol, sRow, 1, 0) = (unsigned char)255;
30                 }
31             }
32         }
33     }
34 
35     //在原圖上標記
36     CImg<unsigned char> output2(scrImage);
37 
38     //標記
39     for (int k = 0; k < lines.size(); k ++) {
40         unsigned int w = output2.width();
41         unsigned int h = output2.height();
42 
43         int range = 50;
44 
45         cout << "node x = " << node[k].first << " " << "   y = " << node[k].second << endl;
46 
47         for (int c = -range; c < range; c ++) {
48             for (int r = -range; r < range; r ++) {
49                 int distance = (int)sqrt(c * c + r * r + 0.0);
50                 if (node[k].first>= range && node[k].first < width - range && node[k].second >= range && node[k].second < height - range) {
51                     if (distance <= 50 && node[k].first + c >= 0 && node[k].first + c < width && node[k].second + r >= 0 && node[k].second + r < height) {
52                         output2.atXYZC(node[k].first + c, node[k].second + r, 1, 0) = (unsigned char)(255);
53                         output2.atXYZC(node[k].first + c, node[k].second + r, 1, 1) = (unsigned char)(0);
54                         output2.atXYZC(node[k].first + c, node[k].second + r, 1, 2) = (unsigned char)(255);
55                     }
56                 }
57             }
58         }
59     }

 

分析

   幾幅圖像的實驗結果如下:

   去噪、提取邊緣、二值化之后(圖1\2\3\4)

   

   依次為圖1\2\3\4的霍夫空間表示

     

   

   

     

   分別為圖1\2\3\4的邊界直線繪制,可知四張圖的邊界都可以檢測到

   

   在原圖上標定交點

   

 

   可以發現,四張圖中,只有圖2的角點沒有標好,其余三張圖的邊界直線都有斜率K不存在的情況,所以,我的標定方法適用,當直線的斜率存在時,就很可能出現一下情況:(紅藍分別表示兩條直線的像素點,可以看到雖然它們相交,但是在像素表示上並無交點,這時候需要多加一個判斷,是否需要用直線方程y=kx+b來直接求出交點)

   


免責聲明!

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



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