2016-12-14 32 views
4

我已經做了大量的研究,並且找不到能夠實現我所需要的技術的組合。用opencv檢測W2中的單個盒子 - python

我有一種情況,需要在數百個W2上執行OCR以提取對帳數據。 W2s的質量很差,因爲它們被打印並隨後掃描回計算機。上述過程不在我的控制範圍之內;不幸的是,我必須與我所得到的一起工作。

去年我能夠成功地完成這個過程,但由於時效性是一個主要問題,我不得不對其進行強制執行。我通過手動指示座標來從中提取數據,然後僅在這些段上一次執行OCR。今年,我想提出一個更加動態的情況,預計座標可能發生變化,格式可能發生變化等。

我已經在下面包括了一個樣本,已擦洗的W2。這個想法是W2上的每個盒子都是它自己的矩形,並通過迭代所有矩形來提取數據。我已經嘗試了幾種邊緣檢測技術,但沒有一種能夠提供完全所需的。我相信我還沒有找到所需的預處理的正確組合。我試圖鏡像一些Sudoku拼圖檢測腳本。

Example W2

下面是我迄今嘗試,用Python代碼,其可與OpenCV的2或3個可以使用是否沿結果:

Processed W2

import cv2 
import numpy as np 

img = cv2.imread(image_path_here) 

newx,newy = img.shape[1]/2,img.shape[0]/2 
img = cv2.resize(img,(newx,newy)) 
blur = cv2.GaussianBlur(img, (3,3),5) 
ret,thresh1 = cv2.threshold(blur,225,255,cv2.THRESH_BINARY) 

gray = cv2.cvtColor(thresh1,cv2.COLOR_BGR2GRAY) 

edges = cv2.Canny(gray,50,220,apertureSize = 3) 

minLineLength = 20 
maxLineGap = 50 
lines = cv2.HoughLinesP(edges,1,np.pi/180,100,minLineLength,maxLineGap) 

for x1,y1,x2,y2 in lines[0]: 
    cv2.line(img,(x1,y1),(x2,y2),(255,0,255),2) 

cv2.imshow('hough',img) 
cv2.waitKey(0) 
+0

的問題是,這與該參數的垂直線是幾乎未檢測到,試圖找到垂直線與'lines_v = cv2.HoughLinesP(邊緣,如圖1所示,np.pi,100,minLineLength,maxLineGap)'併爲此做出另一個循環。爲HoughLinesP函數嘗試不同的參數值,也許爲水平和垂直線設置不同的值。 – ebeneditos

回答

2

讓我知道你是否不遵守我的代碼中的任何內容。這一概念的最大缺點是

1:(如果你有在主箱線嘈雜中斷,將其分解爲不同的斑點)

2:IDK的,如果這是一個東西那裏可以手寫文本,但是有字母重疊的邊框可能會很糟糕。

3:它絕對沒有方向檢查,(你可能真的想改善這一點,因爲我認爲這不會太糟糕,並會給你更準確的把手)。我的意思是,它取決於你的盒子與xy軸大致對齊,如果它們有足夠的傾斜度,它會給你所有盒子角落的總偏移量(儘管它應該仍然可以找到它們)

I fiddled使用閾值設置點來獲取所有文本與邊緣分離的位置,如果需要,可以在開始中斷主線之前將其拉低。另外,如果您擔心換行符,可以將足夠大的斑點添加到最終圖像中。 Processing steps

Final result

基本上,第一步驟與所述閾擺弄得到它在從盒中分離文本和噪聲cuttoff值最穩定的(即仍保持連接盒可能的最低值)。

第二次找到最大的正面blob(應該是boxgrid)。如果你的盒子不能保持在一起,你可能想採取一些最高的斑點......儘管這會變得粘稠,所以嘗試獲得閾值,以便你可以把它作爲一個單一的斑點。

最後一步是讓矩形,要做到這一點,我只是尋找負斑點(忽略第一外側區域)。

這裏是代碼(抱歉,這是C++,但希望你理解的概念,並將它自己無論如何寫):

#include "opencv2/imgproc/imgproc.hpp" 
#include "opencv2/highgui/highgui.hpp" 
#include <iostream> 
#include <stdio.h> 
#include <opencv2/opencv.hpp> 

using namespace cv; 


//Attempts to find the largest connected group of points (assumed to be the interconnected boundaries of the textbox grid) 
Mat biggestComponent(Mat targetImage, int connectivity=8) 
{ 
    Mat inputImage; 
    inputImage = targetImage.clone(); 
    Mat finalImage;// = inputImage; 
    int greatestBlobSize=0; 
    std::cout<<"Top"<<std::endl; 
    std::cout<<inputImage.rows<<std::endl; 
    std::cout<<inputImage.cols<<std::endl; 

    for(int i=0;i<inputImage.cols;i++) 
    { 
     for(int ii=0;ii<inputImage.rows;ii++) 
     { 
      if(inputImage.at<uchar>(ii,i)!=0) 
      { 
       Mat lastImage; 
       lastImage = inputImage.clone(); 
       Rect* boundbox; 
       int blobSize = floodFill(inputImage, cv::Point(i,ii), Scalar(0),boundbox,Scalar(200),Scalar(255),connectivity); 

       if(greatestBlobSize<blobSize) 
       { 
        greatestBlobSize=blobSize; 
        std::cout<<blobSize<<std::endl; 
        Mat tempDif = lastImage-inputImage; 
        finalImage = tempDif.clone(); 
       } 
       //std::cout<<"Loop"<<std::endl; 
      } 
     } 
    } 
    return finalImage; 
} 

//Takes an image that only has outlines of boxes and gets handles for each textbox. 
//Returns a vector of points which represent the top left corners of the text boxes. 
std::vector<Rect> boxCorners(Mat processedImage, int connectivity=4) 
{ 
    std::vector<Rect> boxHandles; 

    Mat inputImage; 
    bool outerRegionFlag=true; 

    inputImage = processedImage.clone(); 

    std::cout<<inputImage.rows<<std::endl; 
    std::cout<<inputImage.cols<<std::endl; 

    for(int i=0;i<inputImage.cols;i++) 
    { 
     for(int ii=0;ii<inputImage.rows;ii++) 
     { 
      if(inputImage.at<uchar>(ii,i)==0) 
      { 
       Mat lastImage; 
       lastImage = inputImage.clone(); 
       Rect boundBox; 

       if(outerRegionFlag) //This is to floodfill the outer zone of the page 
       { 
        outerRegionFlag=false; 
        floodFill(inputImage, cv::Point(i,ii), Scalar(255),&boundBox,Scalar(0),Scalar(50),connectivity); 
       } 
       else 
       { 
        floodFill(inputImage, cv::Point(i,ii), Scalar(255),&boundBox,Scalar(0),Scalar(50),connectivity); 
        boxHandles.push_back(boundBox); 
       } 
      } 
     } 
    } 
    return boxHandles; 
} 

Mat drawTestBoxes(Mat originalImage, std::vector<Rect> boxes) 
{ 
    Mat outImage; 
    outImage = originalImage.clone(); 
    outImage = outImage*0; //really I am just being lazy, this should just be initialized with dimensions 

    for(int i=0;i<boxes.size();i++) 
    { 
     rectangle(outImage,boxes[i],Scalar(255)); 
    } 
    return outImage; 
} 

int main() { 

    Mat image; 
    Mat thresholded; 
    Mat processed; 

    image = imread("Images/W2.png", 1); 
    Mat channel[3]; 

    split(image, channel); 


    threshold(channel[0],thresholded,150,255,1); 

    std::cout<<"Coputing biggest object"<<std::endl; 
    processed = biggestComponent(thresholded); 

    std::vector<Rect> textBoxes = boxCorners(processed); 

    Mat finalBoxes = drawTestBoxes(image,textBoxes); 


    namedWindow("Original", WINDOW_AUTOSIZE); 
    imshow("Original", channel[0]); 

    namedWindow("Thresholded", WINDOW_AUTOSIZE); 
    imshow("Thresholded", thresholded); 

    namedWindow("Processed", WINDOW_AUTOSIZE); 
    imshow("Processed", processed); 

    namedWindow("Boxes", WINDOW_AUTOSIZE); 
    imshow("Boxes", finalBoxes); 



    std::cout<<"waiting for user input"<<std::endl; 

    waitKey(0); 

    return 0; 
} 
3

他,他,邊緣檢測是不是唯一的方式。由於邊緣足夠厚(至少有一個像素),二值化允許您單擊盒子內的區域。

通過簡單的標準,你可以擺脫混亂,只是邊界框給你一個相當不錯的分割。

enter image description here

+0

類似於Sneaky Polar Bear的方法。 –