2009-09-22 56 views
8

我正在尋找一種算法來從邊緣檢測器的輸出中剪除短線段。從下面的圖像(和鏈接)中可以看出,檢測到的幾條小邊不是「長」線。理想情況下,我希望四邊形的四邊在加工後出現,但如果有幾條流浪線,這不會有什麼大不了的......任何建議?修剪邊緣檢測器輸出的短線段?

Example

Image Link

+0

你只想找矩形? – tom10 2009-09-22 20:32:19

回答

5

找到邊緣預過程與開放靠近操作(或兩者)上的圖像之前,即,侵蝕隨後通過擴張,或擴張隨後侵蝕。這應該刪除較小的對象,但留下較大的對象大致相同。

我看了網上的例子,我能找到的最好是this PDF第41頁。

+0

看看示例圖片。矩形的邊緣輪廓只有1像素薄!如果你先腐蝕,你會完全失去矩形和小邊緣。如果你先放大,你可能會縮小你的大矩形中的一些空隙,但這是一個不同的問題,並不能真正幫助你擺脫小的邊緣。 – 2009-09-22 23:00:18

+0

@Levy - 不,如我在我的回答中明確指出的那樣,圖像應該在查找邊界之前關閉。當然,這不應該應用於邊緣(而是應用於計算邊緣的對象)。 – tom10 2009-09-22 23:07:32

+0

@ tom10 - 感謝您的建議,我切換到了開放操作(替換高斯濾波器),並且我得到了更好的Canny邊緣檢測輸出(關閉操作也導致更好的性能)。我曾想過使用侵蝕/擴張,但是我想在邊緣檢測之後使用,這不適用於細線。 – user21293 2009-09-23 02:53:21

4

我懷疑這可以用一個簡單的本地操作來完成。看看你想要保留的矩形 - 有幾個間隙,因此執行本地操作以消除短線段可能會嚴重降低所需輸出的質量。

在後果我會嘗試縮小差距,配合多邊形,或類似的東西來檢測矩形作爲重要的內容,然後在第二個步驟丟棄剩餘不重要的內容。可能是Hough transform可能有所幫助。

UPDATE

我只是用一個內核Hough變換與樣品圖像用這個sample application和有四個漂亮的線條適合您的矩形。

+1

+1用於提示Hough變換。只需找到變換空間中最強的四個峯值,那就是你的四邊形。 – erickson 2009-09-22 20:07:16

2

霍夫變換可能是一個非常昂貴的操作。

可在的情況下工作良好的替代方案是以下內容:

  1. 運行2個數學形態學運算稱爲接近具有水平和垂直線的給定長度的確定的圖像(http://homepages.inf.ed.ac.uk/rbf/HIPR2/close.htm)(從測試)結構元素分別。這一點是關閉大矩形中的所有空白。

  2. 運行連接組件分析。如果您已經有效地完成了形態學,那麼大矩形將作爲一個連通的組件出現。然後,它只會繼續遍歷所有連接的組件並挑選出應該是大矩形的最有可能的候選。

+0

這是我會這樣做的方式。如果目標是找到大矩形的位置,請選擇最大的組件。如果目標只是去除短邊(噪聲),那麼刪除所有足夠小的組件,或者除了最大的組件外。 – 2009-09-22 23:04:14

2

也許發現連接的組件,然後用除去部件小於X像素(經驗確定),隨後沿水平/垂直線的矩形中重新連接的間隙擴張

1

這是可能遵循兩個主要技術:基於

  1. 矢量操作:您的像素羣島地圖爲簇(BLOB,維諾區,等等)。然後應用一些啓發式算法來修正這些段,比如Teh-Chin鏈逼近算法,並且對矢量元素(起點,終點,長度,方向等)進行修剪。

  2. 基於設置的操作:對數據進行集羣(如上所述)。對於每個聚類,通過查找僅顯示1個有意義的特徵值的聚類(或者如果您查找「胖」段,可能類似於橢圓),計算主要組件並檢測來自圓圈或任何其他形狀的線。檢查與特徵值相關的特徵向量以獲取有關斑點方向的信息,並進行選擇。

兩種方式都可以用OpenCV的輕鬆探索(前者,的確,根據「輪廓分析」交易算法的類別下降)。

4

如果有人在此線程上執行操作,OpenCV 2.x會帶來一個名爲squares.cpp的示例,該示例基本上可以完成此任務。

我作了輕微修改的應用,以提高四邊形的檢測

enter image description here

代碼

#include "highgui.h" 
#include "cv.h" 

#include <iostream> 
#include <math.h> 
#include <string.h> 

using namespace cv; 
using namespace std; 

void help() 
{ 
     cout << 
     "\nA program using pyramid scaling, Canny, contours, contour simpification and\n" 
     "memory storage (it's got it all folks) to find\n" 
     "squares in a list of images pic1-6.png\n" 
     "Returns sequence of squares detected on the image.\n" 
     "the sequence is stored in the specified memory storage\n" 
     "Call:\n" 
     "./squares\n" 
    "Using OpenCV version %s\n" << CV_VERSION << "\n" << endl; 
} 

int thresh = 70, N = 2; 
const char* wndname = "Square Detection Demonized"; 

// helper function: 
// finds a cosine of angle between vectors 
// from pt0->pt1 and from pt0->pt2 
double angle(Point pt1, Point pt2, Point pt0) 
{ 
    double dx1 = pt1.x - pt0.x; 
    double dy1 = pt1.y - pt0.y; 
    double dx2 = pt2.x - pt0.x; 
    double dy2 = pt2.y - pt0.y; 
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10); 
} 

// returns sequence of squares detected on the image. 
// the sequence is stored in the specified memory storage 
void findSquares(const Mat& image, vector<vector<Point> >& squares) 
{ 
    squares.clear(); 

    Mat pyr, timg, gray0(image.size(), CV_8U), gray; 

    // karlphillip: dilate the image so this technique can detect the white square, 
    Mat out(image); 
    dilate(out, out, Mat(), Point(-1,-1)); 
    // then blur it so that the ocean/sea become one big segment to avoid detecting them as 2 big squares. 
    medianBlur(out, out, 3); 

    // down-scale and upscale the image to filter out the noise 
    pyrDown(out, pyr, Size(out.cols/2, out.rows/2)); 
    pyrUp(pyr, timg, out.size()); 
    vector<vector<Point> > contours; 

    // find squares only in the first color plane 
    for(int c = 0; c < 1; c++) // was: c < 3 
    { 
     int ch[] = {c, 0}; 
     mixChannels(&timg, 1, &gray0, 1, ch, 1); 

     // try several threshold levels 
     for(int l = 0; l < N; l++) 
     { 
      // hack: use Canny instead of zero threshold level. 
      // Canny helps to catch squares with gradient shading 
      if(l == 0) 
      { 
       // apply Canny. Take the upper threshold from slider 
       // and set the lower to 0 (which forces edges merging) 
       Canny(gray0, gray, 0, thresh, 5); 
       // dilate canny output to remove potential 
       // holes between edge segments 
       dilate(gray, gray, Mat(), Point(-1,-1)); 
      } 
      else 
      { 
       // apply threshold if l!=0: 
       //  tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0 
       gray = gray0 >= (l+1)*255/N; 
      } 

      // find contours and store them all as a list 
      findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); 

      vector<Point> approx; 

      // test each contour 
      for(size_t i = 0; i < contours.size(); i++) 
      { 
       // approximate contour with accuracy proportional 
       // to the contour perimeter 
       approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true); 

       // square contours should have 4 vertices after approximation 
       // relatively large area (to filter out noisy contours) 
       // and be convex. 
       // Note: absolute value of an area is used because 
       // area may be positive or negative - in accordance with the 
       // contour orientation 
       if(approx.size() == 4 && 
        fabs(contourArea(Mat(approx))) > 1000 && 
        isContourConvex(Mat(approx))) 
       { 
        double maxCosine = 0; 

        for(int j = 2; j < 5; j++) 
        { 
         // find the maximum cosine of the angle between joint edges 
         double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1])); 
         maxCosine = MAX(maxCosine, cosine); 
        } 

        // if cosines of all angles are small 
        // (all angles are ~90 degree) then write quandrange 
        // vertices to resultant sequence 
        if(maxCosine < 0.3) 
         squares.push_back(approx); 
       } 
      } 
     } 
    } 
} 


// the function draws all the squares in the image 
void drawSquares(Mat& image, const vector<vector<Point> >& squares) 
{ 
    for(size_t i = 1; i < squares.size(); i++) 
    { 
     const Point* p = &squares[i][0]; 
     int n = (int)squares[i].size(); 
     polylines(image, &p, &n, 1, true, Scalar(0,255,0), 3, CV_AA); 
    } 

    imshow(wndname, image); 
} 


int main(int argc, char** argv) 
{ 
    if (argc < 2) 
    { 
     cout << "Usage: ./program <file>" << endl; 
     return -1; 
    } 

    static const char* names[] = { argv[1], 0 }; 

    help(); 
    namedWindow(wndname, 1); 
    vector<vector<Point> > squares; 

    for(int i = 0; names[i] != 0; i++) 
    { 
     Mat image = imread(names[i], 1); 
     if(image.empty()) 
     { 
      cout << "Couldn't load " << names[i] << endl; 
      continue; 
     } 

     findSquares(image, squares); 
     drawSquares(image, squares); 
     imwrite("out.jpg", image); 

     int c = waitKey(); 
     if((char)c == 27) 
      break; 
    } 

    return 0; 
} 
0

這裏是繼一個簡單的形態學濾波溶液@ Tom10的線條:

解在MATLAB:

se1 = strel('line',5,180);   % linear horizontal structuring element 
se2 = strel('line',5,90);    % linear vertical structuring element 
I = rgb2gray(imread('test.jpg'))>80; % threshold (since i had a grayscale version of the image) 
Idil = imdilate(imdilate(I,se1),se2); % dilate contours so that they connect 
Idil_area = bwareaopen(Idil,1200); % area filter them to remove the small components 

的想法是基本上連接水平方向的輪廓通過的區域打開過濾器,使一個大的部件和過濾以後,得到的矩形。

結果:

Dilating directionally the contours (90 and 180)

Area opening using bwareaopen, This may need some tuning but otherwise its simple and robust filter