从图像中删除背景噪音,使OCR的文字更清晰

Zy0*_*y0n 25 c++ java ocr opencv

我编写了一个应用程序,根据文本区域对图像进行分割,并根据需要提取这些区域.我试图做的是清洁图像,以便OCR(Tesseract)给出准确的结果.我将以下图像作为示例:

在此输入图像描述

通过tesseract运行此结果会产生广泛不准确的结果.然而,清理图像(使用photoshop)获取图像如下:

在此输入图像描述

给出我期望的结果.第一个图像已经通过以下方法运行,以清除它到那一点:

 public Mat cleanImage (Mat srcImage) {
    Core.normalize(srcImage, srcImage, 0, 255, Core.NORM_MINMAX);
    Imgproc.threshold(srcImage, srcImage, 0, 255, Imgproc.THRESH_OTSU);
    Imgproc.erode(srcImage, srcImage, new Mat());
    Imgproc.dilate(srcImage, srcImage, new Mat(), new Point(0, 0), 9);
    return srcImage;
}
Run Code Online (Sandbox Code Playgroud)

我还能做些什么来清理第一张图像,使其类似于第二张图像?

编辑:这是在运行该cleanImage功能之前的原始图像.

在此输入图像描述

dha*_*hka 26

我的答案基于以下假设.在你的情况下,它们可能都没有.

  • 您可以在分段区域中对边界框高度施加阈值.然后你应该能够过滤掉其他组件.
  • 您知道数字的平均笔画宽度.使用此信息可以最大限度地减少数字连接到其他区域的可能性.您可以使用距离变换和形态学操作.

这是我提取数字的过程:

  • 将Otsu阈值应用于图像 大津
  • 采取距离变换 DIST
  • 使用笔画宽度(= 8)约束对距离变换图像进行阈值处理 SW2

  • 应用形态学操作断开连接 ws2op

  • 过滤边界框高度并猜测数字的位置

stroke-width = 8 BB stroke-width = 10 BB2

编辑

  • 使用找到的数字轮廓的凸包制备面罩 面具

  • 使用蒙版将数字区域复制到干净的图像

stroke-width = 8 CL1

stroke-width = 10 CL2

我的Tesseract知识有点生疏.我记得你可以获得角色的置信度.如果仍然将嘈杂区域检测为字符边界框,则可以使用此信息过滤掉噪声.

C++代码

Mat im = imread("aRh8C.png", 0);
// apply Otsu threshold
Mat bw;
threshold(im, bw, 0, 255, CV_THRESH_BINARY_INV | CV_THRESH_OTSU);
// take the distance transform
Mat dist;
distanceTransform(bw, dist, CV_DIST_L2, CV_DIST_MASK_PRECISE);
Mat dibw;
// threshold the distance transformed image
double SWTHRESH = 8;    // stroke width threshold
threshold(dist, dibw, SWTHRESH/2, 255, CV_THRESH_BINARY);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
// perform opening, in case digits are still connected
Mat morph;
morphologyEx(dibw, morph, CV_MOP_OPEN, kernel);
dibw.convertTo(dibw, CV_8U);
// find contours and filter
Mat cont;
morph.convertTo(cont, CV_8U);

Mat binary;
cvtColor(dibw, binary, CV_GRAY2BGR);

const double HTHRESH = im.rows * .5;    // height threshold
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
vector<Point> digits; // points corresponding to digit contours

findContours(cont, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, Point(0, 0));
for(int idx = 0; idx >= 0; idx = hierarchy[idx][0])
{
    Rect rect = boundingRect(contours[idx]);
    if (rect.height > HTHRESH)
    {
        // append the points of this contour to digit points
        digits.insert(digits.end(), contours[idx].begin(), contours[idx].end());

        rectangle(binary, 
            Point(rect.x, rect.y), Point(rect.x + rect.width - 1, rect.y + rect.height - 1),
            Scalar(0, 0, 255), 1);
    }
}

// take the convexhull of the digit contours
vector<Point> digitsHull;
convexHull(digits, digitsHull);
// prepare a mask
vector<vector<Point>> digitsRegion;
digitsRegion.push_back(digitsHull);
Mat digitsMask = Mat::zeros(im.rows, im.cols, CV_8U);
drawContours(digitsMask, digitsRegion, 0, Scalar(255, 255, 255), -1);
// expand the mask to include any information we lost in earlier morphological opening
morphologyEx(digitsMask, digitsMask, CV_MOP_DILATE, kernel);
// copy the region to get a cleaned image
Mat cleaned = Mat::zeros(im.rows, im.cols, CV_8U);
dibw.copyTo(cleaned, digitsMask);
Run Code Online (Sandbox Code Playgroud)

编辑

Java代码

Mat im = Highgui.imread("aRh8C.png", 0);
// apply Otsu threshold
Mat bw = new Mat(im.size(), CvType.CV_8U);
Imgproc.threshold(im, bw, 0, 255, Imgproc.THRESH_BINARY_INV | Imgproc.THRESH_OTSU);
// take the distance transform
Mat dist = new Mat(im.size(), CvType.CV_32F);
Imgproc.distanceTransform(bw, dist, Imgproc.CV_DIST_L2, Imgproc.CV_DIST_MASK_PRECISE);
// threshold the distance transform
Mat dibw32f = new Mat(im.size(), CvType.CV_32F);
final double SWTHRESH = 8.0;    // stroke width threshold
Imgproc.threshold(dist, dibw32f, SWTHRESH/2.0, 255, Imgproc.THRESH_BINARY);
Mat dibw8u = new Mat(im.size(), CvType.CV_8U);
dibw32f.convertTo(dibw8u, CvType.CV_8U);

Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
// open to remove connections to stray elements
Mat cont = new Mat(im.size(), CvType.CV_8U);
Imgproc.morphologyEx(dibw8u, cont, Imgproc.MORPH_OPEN, kernel);
// find contours and filter based on bounding-box height
final double HTHRESH = im.rows() * 0.5; // bounding-box height threshold
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
List<Point> digits = new ArrayList<Point>();    // contours of the possible digits
Imgproc.findContours(cont, contours, new Mat(), Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
    if (Imgproc.boundingRect(contours.get(i)).height > HTHRESH)
    {
        // this contour passed the bounding-box height threshold. add it to digits
        digits.addAll(contours.get(i).toList());
    }   
}
// find the convexhull of the digit contours
MatOfInt digitsHullIdx = new MatOfInt();
MatOfPoint hullPoints = new MatOfPoint();
hullPoints.fromList(digits);
Imgproc.convexHull(hullPoints, digitsHullIdx);
// convert hull index to hull points
List<Point> digitsHullPointsList = new ArrayList<Point>();
List<Point> points = hullPoints.toList();
for (Integer i: digitsHullIdx.toList())
{
    digitsHullPointsList.add(points.get(i));
}
MatOfPoint digitsHullPoints = new MatOfPoint();
digitsHullPoints.fromList(digitsHullPointsList);
// create the mask for digits
List<MatOfPoint> digitRegions = new ArrayList<MatOfPoint>();
digitRegions.add(digitsHullPoints);
Mat digitsMask = Mat.zeros(im.size(), CvType.CV_8U);
Imgproc.drawContours(digitsMask, digitRegions, 0, new Scalar(255, 255, 255), -1);
// dilate the mask to capture any info we lost in earlier opening
Imgproc.morphologyEx(digitsMask, digitsMask, Imgproc.MORPH_DILATE, kernel);
// cleaned image ready for OCR
Mat cleaned = Mat.zeros(im.size(), CvType.CV_8U);
dibw8u.copyTo(cleaned, digitsMask);
// feed cleaned to Tesseract
Run Code Online (Sandbox Code Playgroud)


Haz*_*lah 5

我认为你需要在预处理部分做更多的工作,以便在调用tesseract之前尽可能地清楚地准备图像.

我的想法是:

1-从图像中提取轮廓并在图像中找到轮廓(选中此项)和

2-每个轮廓都有宽度,高度和面积,因此您可以根据宽度,高度和面积过滤轮廓(选中此项此项),此外,您可以使用轮廓分析代码的某些部分来过滤轮廓和您可以使用模板轮廓匹配删除与"字母或数字"轮廓不相似的轮廓.

3-过滤轮廓后,您可以检查此图像中字母和数字的位置,因此您可能需要使用一些文本检测方法,例如此处

4-如果要删除非文本区域以及图像中不好的轮廓,现在需要的所有内容

5-现在您可以创建binirization方法,或者您可以使用tesseract方法对图像进行binirization,然后在图像上调用OCR.

当然这些是执行此操作的最佳步骤,您可以使用其中一些,这对您来说已经足够了.

其他想法:

  • 您可以使用不同的方法来做到这一点,最好的想法是找到一种方法来使用不同的方法检测数字和字符位置,如模板匹配或基于功能的HOG.

  • 您可能首先对图像进行二值化并获得二进制图像,然后您需要对水平和垂直应用线条结构的开放,这将帮助您在此之后检测边缘并对图像进行分割然后进行OCR .

  • 检测图像中的所有轮廓后,您还可以使用Hough transformation检测到任何类型的线路和自定义曲线像这样的一个,并以这种方式则可以检测到被一字排开,所以你可以分割的图像字符和后做OCR那.

更简单的方法:

1-进行双重化 在此输入图像描述

2-分离轮廓的一些形态学操作:

在此输入图像描述

3-反转图像中的颜色(这可能在步骤2之前)

在此输入图像描述

4-查找图像中的所有轮廓

在此输入图像描述

5-删除宽度大于其高度的所有轮廓,删除非常小的轮廓,非常大的轮廓和非矩形轮廓

在此输入图像描述

注意:您可以使用文本检测方法(或使用HOG或边缘检测)代替步骤4和5

6-找到包含图像中所有剩余轮廓的大矩形

在此输入图像描述

7-您可以进行一些额外的预处理以增强tesseract的输入,然后您可以立即调用OCR.(我建议你裁剪图像并将其作为OCR的输入[我的意思是裁剪黄色矩形,不要将整个图像作为输入只是黄色矩形,这也会增强结果])