Java 去除图像中的背景噪声,使OCR文本更清晰

Java 去除图像中的背景噪声,使OCR文本更清晰,java,c++,opencv,ocr,Java,C++,Opencv,Ocr,我已经编写了一个应用程序,它根据图像中的文本区域对图像进行分割,并根据我认为合适的方式提取这些区域。我试图做的是清理图像,以便OCR(Tesseract)给出准确的结果。我以下图为例: 在tesseract中运行此命令会得到一个非常不准确的结果。但是,清理图像(使用photoshop)以获得如下图像: 给出了我所期望的结果。第一个图像已通过以下方法运行,以将其清理到该点: public Mat cleanImage (Mat srcImage) { Core.normalize(s

我已经编写了一个应用程序,它根据图像中的文本区域对图像进行分割,并根据我认为合适的方式提取这些区域。我试图做的是清理图像,以便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;
}
我还能做些什么来清洁第一个图像,使其与第二个图像相似

编辑:这是通过
cleanImage
功能运行之前的原始图像


那张照片对你有帮助吗

生成该图像的算法很容易实现。我相信,如果你调整它的一些参数,你可以得到非常好的结果,这类图像

我用tesseract测试了所有图像:

  • 原始图像:未检测到任何内容
  • 已处理图像#1:未检测到任何内容
  • 已处理图像#2:12-14(精确匹配)
  • 我的处理图像:y'1'2-14/j

    • 我的回答基于以下假设。很可能他们中没有一个对你有利

      • 您可以在分割区域中为边界框高度设置阈值。然后您应该能够过滤掉其他组件
      • 你知道数字的平均笔划宽度。使用此信息可将数字连接到其他区域的可能性降至最低。您可以为此使用距离变换和形态学操作
      这是我提取数字的过程:

      • 对图像应用大津阈值
      • 以距离变换为例
      • 使用笔划宽度(=8)约束设置距离变换图像的阈值

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

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

      笔划宽度=8 笔划宽度=10

      编辑

      • 使用找到的数字轮廓的凸出部分准备掩模

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

      笔划宽度=8

      笔划宽度=10

      我的理论知识有点生疏了。正如我所记得的,你可以得到角色的自信水平。如果仍然碰巧将噪声区域检测为角色边界框,则可以使用此信息过滤掉噪声

      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);
      
      Mat im=imread(“aRh8C.png”,0);
      //应用大津阈值
      Mat bw;
      阈值(im,bw,0,255,CV_THRESH_BINARY_INV|CV_THRESH_OTSU);
      //以距离变换为例
      垫区;
      距离变换(bw、dist、CV_dist_L2、CV_dist_MASK_precision);
      Mat-dibw;
      //距离变换图像的阈值
      双SWTHRESH=8;//笔划宽度阈值
      阈值(dist、dibw、SWTHRESH/2255、CV_THRESH_二进制);
      Mat kernel=getStructuringElement(变形,大小(3,3));
      //如果数字仍然连接,则执行打开
      垫形;
      形态学(dibw,morph,CV_MOP_OPEN,kernel);
      dibw.转换器(dibw,CV_8U);
      //查找轮廓并过滤
      Mat cont;
      变形转换(续,CV_8U);
      Mat二进制;
      CVT颜色(dibw、二进制、CV_GRAY2BGR);
      常数双HTHRESH=im.rows*.5;//高度阈值
      矢量等值线;
      向量层次;
      矢量数字;//对应于数字轮廓的点
      findContours(cont,等高线,层次结构,CV_RETR_CCOMP,CV_CHAIN_APPROX_SIMPLE,点(0,0));
      对于(int idx=0;idx>=0;idx=hierarchy[idx][0])
      {
      Rect Rect=boundingRect(等高线[idx]);
      如果(矩形高度>HTHRESH)
      {
      //将此轮廓的点附加到数字点
      digits.insert(digits.end()、等高线[idx].begin()、等高线[idx].end());
      矩形(二进制,
      点(矩形x,矩形y),点(矩形x+矩形宽度-1,矩形y+矩形高度-1),
      标量(0,0,255),1);
      }
      }
      //以数字轮廓的凸面为例
      矢量数字外壳;
      convexHull(数字、数字外壳);
      //准备一个面具
      矢量数字区域;
      数字区域。推回(数字外壳);
      Mat digitsMask=Mat::零(im.rows、im.cols、CV_8U);
      绘制等高线(digitsMask、digitsRegion、0、标量(255、255、255),-1);
      //展开遮罩以包含我们在前面的形态学打开中丢失的任何信息
      形态学(digitsMask,digitsMask,CV_MOP_deplate,kernel);
      //复制区域以获得已清理的图像
      清洁垫=垫:零(im.rows、im.cols、CV_8U);
      dibw.copyTo(清洁,数字掩模);
      
      编辑

      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
      
      Mat im=Highgui.imread(“aRh8C.png”,0);
      //应用大津阈值
      Mat bw=新的Mat(im.size(),CvType.CV_8U);
      Imgproc.阈值(im,bw,0,255,Imgproc.THRESH_BINARY_INV|Imgproc.THRESH_OTSU);
      //以距离变换为例
      Mat dist=新的Mat(im.size(),CvType.CV_32F);
      Imgproc.distanceTransform(bw,dist,Imgproc.CV\u dist\u L2,Imgproc.CV\u dist\u MASK\u precision);
      //距离变换的阈值
      Mat dibw32f=新Mat(im.size(),CvType.CV_32F);
      最终双SWTHRESH=8.0;//笔划宽度阈值
      Imgproc.threshold(dist,dibw32f,SWTHRESH/2.0255,Imgproc.THRESH_二进制);
      Mat dibw8u=新Mat(im.size(),CvType.cv8U);
      dibw32f.convertTo(dibw8u,CvType.cv8u);
      Mat kernel=Imgproc.getStructuringElement(Imgproc.morp_RECT,新大小(3,3));
      //打开以移除杂散元件的连接
      Mat cont=新的Mat(im.size(),CvType.CV_8U);
      Imgproc.morphologyEx(dibw8u,cont,Imgproc.MORPH_OPEN,kernel);
      //根据边界框高度查找轮廓并进行过滤
      最终双精度HTHRESH=im.rows()*0.5;//边界框高度阈值
      列表等高线=新的ArrayList();
      列表位数=新建ArrayList();//可能的数字轮廓
      Imgproc.findContours(续,等高线,新垫(),Imgproc.RETR\u CCOMP,Imgproc.CHAIN\u近似值\u简单值);
      用于(int i)=