【问题标题】:OpenCV for OCR: How to compute thresholding levels for gray image OCROCR 的 OpenCV:如何计算灰度图像 OCR 的阈值级别
【发布时间】:2016-08-30 17:38:50
【问题描述】:

我正在尝试为 OCR 准备图像,到目前为止,这是我使用来自 Extracting text OpenCV 的信息所做的工作

从生成的图像中,我使用经过过滤的轮廓制作蒙版,如下所示:

//this is the mask of all the text
Mat maskF = Mat::zeros(rgb.rows, rgb.cols, CV_8UC1);
// CV_FILLED fills the connected components found - CV_FILLED to fill
drawContours(maskF, letters, -1, Scalar(255), CV_FILLED);
cv::imwrite("noise2-Mask.png", maskF);

生成的 img 很有希望:

考虑到这是我原来的img:

不幸的是,在其上运行 Tesseract 会产生一些问题,我认为您在单词上的字母之间看到的灰度级别会使 tesseract 感到困惑 - 所以,您在想,是的,让我们进行二进制转换,这只是错过了后半部分页面,所以我也尝试应用 Otsu 阈值,但文本变得像素化并且字符失去了形状。

我尝试了 OpenCV Adaptive Threshold OCR 的 CalcBlockMeanVariance,但无法编译(而且我不确定我是否完全理解)在

上编译阻塞
res=1.0-res;
res=Img+res;

无论如何,如果有人有任何建议,我将不胜感激!请注意,Tesseract 很少识别分数,但我正在编写一个新的训练集,希望能提高 reco 率)

【问题讨论】:

    标签: image-processing tesseract opencv3.0


    【解决方案1】:
    1. Enhancing dynamic range and normalizing illumination

      关键是首先将背景标准化为无缝颜色。有很多方法可以做到这一点。这是我为您的图片所做的尝试:

      为图像创建纸张/墨水单元表(与链接答案中的方式相同)。因此,您选择的网格单元尺寸足够大,可以从背景中区分字符特征。对于您的图像,我选择 8x8 像素。因此,将图像分成正方形并计算每个正方形的平均颜色和绝对颜色差异。然后标记饱和的(小的 abs 差异)并根据平均颜色将它们设置为纸或墨水单元,并与整个图像的平均颜色进行比较。

      现在只需处理所有图像行,并为每个像素获取左右纸单元。并在这些值之间进行线性插值。这应该会引导您获得该像素的实际背景颜色,因此只需从图像中减去它即可。

      我的 C++ 实现如下所示:

      color picture::normalize(int sz,bool _recolor,bool _sbstract)
          {
          struct _cell { color col; int a[4],da,_paper; _cell(){}; _cell(_cell& x){ *this=x; }; ~_cell(){}; _cell* operator = (const _cell *x) { *this=*x; return this; }; /*_cell* operator = (const _cell &x) { ...copy... return this; };*/ };
          int i,x,y,tx,ty,txs,tys,a0[4],a1[4],n,dmax;
          int x0,x1,y0,y1,q[4][4][2],qx[4],qy[4];
          color c;
          _cell **tab;
          // allocate grid table
          txs=xs/sz; tys=ys/sz; n=sz*sz; c.dd=0;
          if ((txs<2)||(tys<2)) return c;
          tab=new _cell*[tys]; for (ty=0;ty<tys;ty++) tab[ty]=new _cell[txs];
          // compute grid table
          for (y0=0,y1=sz,ty=0;ty<tys;ty++,y0=y1,y1+=sz)
           for (x0=0,x1=sz,tx=0;tx<txs;tx++,x0=x1,x1+=sz)
              {
              for (i=0;i<4;i++) a0[i]=0;
              for (y=y0;y<y1;y++)
               for (x=x0;x<x1;x++)
                  {
                  dec_color(a1,p[y][x],pf);
                  for (i=0;i<4;i++) a0[i]+=a1[i];
                  }
              for (i=0;i<4;i++) tab[ty][tx].a[i]=a0[i]/n;
              enc_color(tab[ty][tx].a,tab[ty][tx].col,pf);
      
              tab[ty][tx].da=0;
              for (i=0;i<4;i++) a0[i]=tab[ty][tx].a[i];
              for (y=y0;y<y1;y++)
               for (x=x0;x<x1;x++)
                  {
                  dec_color(a1,p[y][x],pf);
                  for (i=0;i<4;i++) tab[ty][tx].da+=abs(a1[i]-a0[i]);
                  }
              tab[ty][tx].da/=n;
              }
          // compute max safe delta dmax = avg(delta)
          for (dmax=0,ty=0;ty<tys;ty++)
           for (tx=0;tx<txs;tx++)
            dmax+=tab[ty][tx].da;
             dmax/=(txs*tys);
      
          // select paper cells and compute avg paper color
          for (i=0;i<4;i++) a0[i]=0; x0=0;
          for (ty=0;ty<tys;ty++)
           for (tx=0;tx<txs;tx++)
            if (tab[ty][tx].da<=dmax)
              {
              tab[ty][tx]._paper=1;
              for (i=0;i<4;i++) a0[i]+=tab[ty][tx].a[i]; x0++;
              }
            else tab[ty][tx]._paper=0;
          if (x0) for (i=0;i<4;i++) a0[i]/=x0;
          enc_color(a0,c,pf);
          // remove saturated ink cells from paper (small .da but wrong .a[])
          for (ty=1;ty<tys-1;ty++)
           for (tx=1;tx<txs-1;tx++)
            if (tab[ty][tx]._paper==1)
             if ((tab[ty][tx-1]._paper==0)
               ||(tab[ty][tx+1]._paper==0)
               ||(tab[ty-1][tx]._paper==0)
               ||(tab[ty+1][tx]._paper==0))
              {
              x=0; for (i=0;i<4;i++) x+=abs(tab[ty][tx].a[i]-a0[i]);
              if (x>dmax) tab[ty][tx]._paper=2;
              }
          for (ty=0;ty<tys;ty++)
           for (tx=0;tx<txs;tx++)
            if (tab[ty][tx]._paper==2)
             tab[ty][tx]._paper=0;
      
          // piecewise linear interpolation H-lines
          int ty0,ty1,tx0,tx1,d;
          if (_sbstract) for (i=0;i<4;i++) a0[i]=0;
          for (y=0;y<ys;y++)
              {
              ty=y/sz; if (ty>=tys) ty=tys-1;
              // first paper cell
              for (tx=0;(tx<txs)&&(!tab[ty][tx]._paper);tx++); tx1=tx;
              if (tx>=txs) continue; // no paper cell found
              for (;tx<txs;)
                  {
                  // fnext paper cell
                  for (tx++;(tx<txs)&&(!tab[ty][tx]._paper);tx++);
                  if (tx<txs)
                      {
                      tx0=tx1; x0=tx0*sz;
                      tx1=tx;  x1=tx1*sz;
                      d=x1-x0;
                      }
                  else x1=xs;
      
                  // interpolate
                  for (x=x0;x<x1;x++)
                      {
                      dec_color(a1,p[y][x],pf);
                      for (i=0;i<4;i++) a1[i]-=tab[ty][tx0].a[i]+(((tab[ty][tx1].a[i]-tab[ty][tx0].a[i])*(x-x0))/d)-a0[i];
                      if (pf==_pf_s   ) for (i=0;i<1;i++) clamp_s32(a1[i]);
                      if (pf==_pf_u   ) for (i=0;i<1;i++) clamp_u32(a1[i]);
                      if (pf==_pf_ss  ) for (i=0;i<2;i++) clamp_s16(a1[i]);
                      if (pf==_pf_uu  ) for (i=0;i<2;i++) clamp_u16(a1[i]);
                      if (pf==_pf_rgba) for (i=0;i<4;i++) clamp_u8 (a1[i]);
                      enc_color(a1,p[y][x],pf);
                      }
                  }
              }
      
          // recolor paper cells with avg color (remove noise)
          if (_recolor)
           for (y0=0,y1=sz,ty=0;ty<tys;ty++,y0=y1,y1+=sz)
            for (x0=0,x1=sz,tx=0;tx<txs;tx++,x0=x1,x1+=sz)
             if (tab[ty][tx]._paper)
              for (y=y0;y<y1;y++)
               for (x=x0;x<x1;x++)
                p[y][x]=c;
      
          // free grid table
          for (ty=0;ty<tys;ty++) delete[] tab[ty]; delete[] tab;
          return c;
          }
      

      有关详细信息,请参阅链接的答案。切换到灰度&lt;0,765&gt; 并使用pic1.normalize(8,false,true); 后,输入图像的结果如下

    2. 二值化

      我首先尝试了简单的范围阈值化,所以如果所有颜色通道值(R、G、B)都在&lt;min,max&gt; 范围内,则将其重新着色为c1,否则为c0

      void picture::treshold_AND(int min,int max,int c0,int c1) // all channels tresholding: c1 <min,max>, c0 (-inf,min)+(max,+inf)
          {
          int x,y,i,a[4],e;
          for (y=0;y<ys;y++)
           for (x=0;x<xs;x++)
              {
              dec_color(a,p[y][x],pf);
              for (e=1,i=0;i<3;i++) if ((a[i]<min)||(a[i]>max)){ e=0; break; }
              if (e) for (i=0;i<4;i++) a[i]=c1;
               else  for (i=0;i<4;i++) a[i]=c0;
              enc_color(a,p[y][x],pf);
              }
          }
      

      在应用 pic1.treshold_AND(0,127,765,0); 并转换回 RGBA 后,我得到了这个结果:

      灰色噪声是由于 JPEG 压缩造成的(PNG 会太大)。如您所见,结果或多或少可以接受。

      如果这还不够,您可以将图像分割成多个部分。计算每个段的直方图(它应该是双峰的)然后找到两个最大值之间的颜色,这是你的阈值。问题是背景覆盖了更多的区域,因此墨峰相对较小,有时很难在线性尺度上发现完整的图像直方图:

      当您为每个片段执行此操作时,效果会好很多(因为阈值周围的背景/文本颜色渗色会少得多),因此间隙会更加明显。也不要忘记忽略小间隙(直方图中缺少垂直线),因为它们仅与量化/编码/舍入有关(图像中并非所有灰色阴影都存在),因此您应该过滤掉小于强度的间隙替换它们具有最后一个和下一个有效直方图条目的平均值。

    【讨论】:

    • 我会试试这个 - 还没有找到将其添加到我的 obj-C 项目的方法,但会进行研究以了解如何将方法添加到图片对象。
    • 图片只是垫子吗?
    • @Xav 如果您使用的是第 3 方图像库,直接弄乱它不是一个好主意。您可以在图像类之外编写自定义函数,以更改作为操作数的图像。是的,我的图像是二维数组/像素矩阵,其中像素是 32 位无符号整数 (DWORD),支持编码 _pf_rgba (4x8bit uint),_pf_u(1x32bit uint), _pf_s(1x32bit int), and more dec_color/enc_color 只需在每个通道的基础上将其解包/打包到 DWORD[4] 数组,以使代码通用对于任何像素格式。您可以忽略所有这些,因为您只有灰度。
    • @Xav 因此,如果您的图像编码为 1 通道灰度,所有 3 和 4 迭代 fors 都可以消失。此外,如果您计算展位水平和垂直线并使用展位的平均值或使用三次插值,或者首先对表格中的间隙进行插值,您也可以大大改善这一点。但正如您所见,即使是简单的分段线性插值也很好。 PS。该代码用于双三次插值,因此还有许多未使用的变量,例如q[4][4][4] 等。(我忘了擦除)
    • @Spektre,我认为这是一个很好的解决方案。顺便问一下,你能分享一个能得到相同结果的python项目吗?
    【解决方案2】:

    您可以尝试使用腐蚀擦除所有文本,然后从灰度图像中减去结果,或者使用顶帽变换来统一背景以进行全局阈值处理! Here你可以找到示例代码!为什么不使用现有的adaptiveThreshold 函数呢?

    【讨论】:

      【解决方案3】:

      我尝试最大化一个通道的最小和最大颜色之间的距离,然后反转颜色。 (见代码)

      img1 = cv2.imread('img.png')
      img_reverted = img1.copy()
      
      for i in range(3):
        tmp = (img1[:, :, i] - img1[:, :, i].min()) / img1[:, :, i].max()
        tmp = tmp * 255
        tmp = -1 * tmp + 255
        img_reverted[:, :, i] = tmp.astype('uint8');
      

      结果非常好。 (见图片)

      原图:

      还原图像:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-04-03
        • 1970-01-01
        • 2020-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-18
        相关资源
        最近更新 更多