使用 opencv 检测 W2 中的单个框 - python
Detecting individual boxes in W2 with opencv - python
我进行了广泛的研究,但找不到能够实现我需要的技术组合。
我有一种情况需要对数百个 W2 执行 OCR 以提取数据进行核对。 W2 的质量很差,因为它们是打印出来的,随后又被扫描回计算机。上述过程不在我的控制范围内;不幸的是,我必须使用现有的东西。
我去年能够成功执行此过程,但由于及时性是一个主要问题,我不得不强行执行它。为此,我手动指示要从中提取数据的坐标,然后一次仅对这些片段执行 OCR。今年,我想提出一个更动态的情况,因为坐标可能会发生变化,格式可能会发生变化等
我已经包含了一个示例,在下面擦洗了 W2。这个想法是让 W2 上的每个框都是它自己的矩形,并通过遍历所有矩形来提取数据。我尝试了几种边缘检测技术,但 none 提供了所需要的。我相信我还没有找到所需的预处理的正确组合。我已经尝试镜像一些数独谜题检测脚本。
这是我迄今为止尝试的结果,以及 python 代码,无论是与 OpenCV 2 还是 3 一起使用:
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)
如果您不遵循我的代码中的任何内容,请告诉我。这个概念最大的错误是
1:(如果你在主框线中有嘈杂的中断会把它分成单独的斑点)
2: idk 如果这是一个可以手写文本的东西,但是让字母重叠在框的边缘可能是不好的。
3:它绝对没有方向检查,(你可能真的想改进这个,因为我认为它不会太糟糕并且会给你更准确的手柄)。我的意思是,这取决于你的盒子与 xy 轴大致对齐,如果它们足够倾斜,它会给你所有盒子角的总偏移量(尽管它仍然应该找到它们)
我稍微调整了阈值设置点以使所有文本与边缘分离,如果有必要,您可以在开始打破主线之前将其拉得更低。此外,如果您担心换行,您可以将足够大的斑点添加到最终图像中。
基本上,第一步是摆弄阈值,使其处于最稳定的(可能是仍然保持连接框的最低值)截止值,以将文本和噪声与框分开。
其次找到最大的正斑点(应该是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;
}
呵呵,边缘检测不是唯一的方法。由于边缘足够厚(每个地方至少有一个像素),二值化允许您分割框内的区域。
通过简单的标准,您可以消除混乱,仅边界框即可为您提供相当好的分割。
我进行了广泛的研究,但找不到能够实现我需要的技术组合。
我有一种情况需要对数百个 W2 执行 OCR 以提取数据进行核对。 W2 的质量很差,因为它们是打印出来的,随后又被扫描回计算机。上述过程不在我的控制范围内;不幸的是,我必须使用现有的东西。
我去年能够成功执行此过程,但由于及时性是一个主要问题,我不得不强行执行它。为此,我手动指示要从中提取数据的坐标,然后一次仅对这些片段执行 OCR。今年,我想提出一个更动态的情况,因为坐标可能会发生变化,格式可能会发生变化等
我已经包含了一个示例,在下面擦洗了 W2。这个想法是让 W2 上的每个框都是它自己的矩形,并通过遍历所有矩形来提取数据。我尝试了几种边缘检测技术,但 none 提供了所需要的。我相信我还没有找到所需的预处理的正确组合。我已经尝试镜像一些数独谜题检测脚本。
这是我迄今为止尝试的结果,以及 python 代码,无论是与 OpenCV 2 还是 3 一起使用:
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)
如果您不遵循我的代码中的任何内容,请告诉我。这个概念最大的错误是
1:(如果你在主框线中有嘈杂的中断会把它分成单独的斑点)
2: idk 如果这是一个可以手写文本的东西,但是让字母重叠在框的边缘可能是不好的。
3:它绝对没有方向检查,(你可能真的想改进这个,因为我认为它不会太糟糕并且会给你更准确的手柄)。我的意思是,这取决于你的盒子与 xy 轴大致对齐,如果它们足够倾斜,它会给你所有盒子角的总偏移量(尽管它仍然应该找到它们)
我稍微调整了阈值设置点以使所有文本与边缘分离,如果有必要,您可以在开始打破主线之前将其拉得更低。此外,如果您担心换行,您可以将足够大的斑点添加到最终图像中。
基本上,第一步是摆弄阈值,使其处于最稳定的(可能是仍然保持连接框的最低值)截止值,以将文本和噪声与框分开。
其次找到最大的正斑点(应该是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;
}
呵呵,边缘检测不是唯一的方法。由于边缘足够厚(每个地方至少有一个像素),二值化允许您分割框内的区域。
通过简单的标准,您可以消除混乱,仅边界框即可为您提供相当好的分割。