OCR的字符重建和填充

5

我正在处理轮胎文本识别方面的工作。 为了使用OCR,我必须先获取清晰的二进制地图。

我已经处理了图像,但文本出现了断裂和不连续的边缘。 我尝试过在MATLAB中使用圆形盘和线元素进行标准腐蚀/膨胀,但并没有真正帮助。

Pr1- 有什么想法可以重建这些字符并填补字符之间的空隙吗?

Original Image_highres Original Image_lowRes canny edge detected

Pr2- 上面的图片分辨率更高,照明条件也很好。 但是,如果像下面的图像一样,照明条件很差,分辨率相对较低,处理的可行选项是什么?

enter image description here

尝试的解决方案:

S1:这是对Spektre共享的处理过的图像应用中值滤波器的结果。为了去除噪声,我应用了一个中值滤波器(5x5),随后使用线元素(5,11)进行图像膨胀。即使现在OCR(Matlab 2014b)只能识别一些字符。

无论如何,非常感谢迄今为止的建议。我仍然会等待看看是否有人可以提供不同的想法,也许可以打破常规 :)

enter image description here

以下是 Spektre 代码的 Matlab 实现结果(未包括笔画膨胀(使用角落进行归一化,顺序为1、2、3、4):

enter image description here

使用阈值tr0=400和tr1=180以及归一化的角点顺序1,3,2,4

在此输入图像描述

最好的问候

Wajahat


添加源图像而不进行过滤...可能会过滤掉太多信息。 - Spektre
当然。抱歉耽搁了。我刚刚才看到你的评论。 - Wajahat
2个回答

5

我稍微调整了一下你的输入

通过光照归一化和动态范围归一化,可以在一定程度上获得更好的结果,但仍然远离所需的效果。我想尝试锐化部分导数以增强字母与背景的对比,并在集成回来并重新着色掩码图像之前阈值处理小颗粒。如果有时间(不确定是何时,或许是明天),我会进行编辑(并评论/通知您)。

光照归一化

计算平均角落强度,并双线性地重新缩放强度以匹配平均颜色。

normalized lighting

如果您需要更复杂的内容,请参见:

边缘检测

通过xy的强度的部分导数 i...

  • i=|i(x,y)/dx|+|i(x,y)/dy|

然后通过treshold=13进行阈值处理。

edge detect

[注]

为了消除大部分噪点,在边缘检测之前我应用了平滑滤波。

[编辑1] 经过一些分析,我发现您的图像对于锐化集成来说边缘很差

这是图像中间线经过x方向第一次导数后的强度图示例

poor edges

如您所见,黑色区域很好,但白色区域几乎无法从背景噪声中识别出来。因此,您唯一的希望是像@Daniel的答案建议的那样使用最小最大滤波,并在黑色边缘区域上取更多权重(白色不可靠)。

min max

最小最大滤波强调黑色(蓝色掩码)和白色(红色掩码)区域。如果两个区域都可靠,则只需填充它们之间的空间,但在您的情况下这不是一个选项,相反,我会扩大这些区域(更多地加权蓝色掩码),并使用为此类三色输入定制的OCR对结果进行OCR。

您还可以拍摄2张具有不同光照位置和固定相机的图像,并将它们合并以覆盖所有可识别的黑色区域。

[编辑2] 上述方法的C++源代码

//---------------------------------------------------------------------------
typedef union { int dd; short int dw[2]; byte db[4]; } color;
picture pic0,pic1,pic2; // pic0 source image,pic1 normalized+min/max,pic2 enlarge filter
//---------------------------------------------------------------------------
void filter()
    {
    int sz=16;          // [pixels] square size for corner avg color computation (c00..c11)
    int fs0=5;          // blue [pixels] font thickness
    int fs1=2;          // red  [pixels] font thickness
    int tr0=320;        // blue min treshold
    int tr1=125;        // red  max treshold

    int x,y,c,cavg,cmin,cmax;
    pic1=pic0;          // copy source image
    pic1.rgb2i();       // convert to grayscale intensity

    for (x=0;x<5;x++) pic1.ui_smooth();
    cavg=pic1.ui_normalize();

    // min max filter
    cmin=pic1.p[0][0].dd; cmax=cmin;
    for (y=0;y<pic1.ys;y++)
     for (x=0;x<pic1.xs;x++)
        {
        c=pic1.p[y][x].dd;
        if (cmin>c) cmin=c;
        if (cmax<c) cmax=c;
        }
    // treshold min/max
    for (y=0;y<pic1.ys;y++)
     for (x=0;x<pic1.xs;x++)
        {
        c=pic1.p[y][x].dd;
             if (cmax-c<tr1) c=0x00FF0000; // red
        else if (c-cmin<tr0) c=0x000000FF; // blue
        else                 c=0x00000000; // black
        pic1.p[y][x].dd=c;
        }
    pic1.rgb_smooth();  // remove single dots

    // recolor image
    pic2=pic1; pic2.clear(0);
    pic2.bmp->Canvas->Pen  ->Color=clWhite;
    pic2.bmp->Canvas->Brush->Color=clWhite;
    for (y=0;y<pic1.ys;y++)
     for (x=0;x<pic1.xs;x++)
        {
        c=pic1.p[y][x].dd;
        if (c==0x00FF0000)
            {
            pic2.bmp->Canvas->Pen  ->Color=clRed;
            pic2.bmp->Canvas->Brush->Color=clRed;
            pic2.bmp->Canvas->Ellipse(x-fs1,y-fs1,x+fs1,y+fs1); // red
            }
        if (c==0x000000FF)
            {
            pic2.bmp->Canvas->Pen  ->Color=clBlue;
            pic2.bmp->Canvas->Brush->Color=clBlue;
            pic2.bmp->Canvas->Ellipse(x-fs0,y-fs0,x+fs0,y+fs0); // blue
            }
        }
    }
//---------------------------------------------------------------------------
int  picture::ui_normalize(int sz=32)
    {
    if (xs<sz) return 0;
    if (ys<sz) return 0;
    int x,y,c,c0,c1,c00,c01,c10,c11,cavg;

    // compute average intensity in corners
    for (c00=0,y=         0;y<     sz;y++) for (x=         0;x<     sz;x++) c00+=p[y][x].dd; c00/=sz*sz;
    for (c01=0,y=         0;y<     sz;y++) for (x=xs-sz;x<xs;x++) c01+=p[y][x].dd; c01/=sz*sz;
    for (c10=0,y=ys-sz;y<ys;y++) for (x=         0;x<     sz;x++) c10+=p[y][x].dd; c10/=sz*sz;
    for (c11=0,y=ys-sz;y<ys;y++) for (x=xs-sz;x<xs;x++) c11+=p[y][x].dd; c11/=sz*sz;
    cavg=(c00+c01+c10+c11)/4;

    // normalize lighting conditions
    for (y=0;y<ys;y++)
     for (x=0;x<xs;x++)
        {
        // avg color = bilinear interpolation of corners colors
        c0=c00+(((c01-c00)*x)/xs);
        c1=c10+(((c11-c10)*x)/xs);
        c =c0 +(((c1 -c0 )*y)/ys);
        // scale to avg color
        if (c) p[y][x].dd=(p[y][x].dd*cavg)/c;
        }
    // compute min max intensities
    for (c0=0,c1=0,y=0;y<ys;y++)
     for (x=0;x<xs;x++)
        {
        c=p[y][x].dd;
        if (c0>c) c0=c;
        if (c1<c) c1=c;
        }
    // maximize dynamic range <0,765>
    for (y=0;y<ys;y++)
     for (x=0;x<xs;x++)
      c=((p[y][x].dd-c0)*765)/(c1-c0);
    return cavg;
    }
//---------------------------------------------------------------------------
void picture::rgb_smooth()
    {
    color   *q0,*q1;
    int     x,y,i;
    color   c0,c1,c2;
    if ((xs<2)||(ys<2)) return;
    for (y=0;y<ys-1;y++)
        {
        q0=p[y  ];
        q1=p[y+1];
        for (x=0;x<xs-1;x++)
            {
            c0=q0[x];
            c1=q0[x+1];
            c2=q1[x];
            for (i=0;i<4;i++) q0[x].db[i]=WORD((WORD(c0.db[i])+WORD(c0.db[i])+WORD(c1.db[i])+WORD(c2.db[i]))>>2);
            }
        }
    }
//---------------------------------------------------------------------------

我使用自己的图片类来处理图片,其中一些成员包括:
  • xs,ys:图片的尺寸(以像素为单位)
  • p[y][x].dd:表示在(x,y)位置上的像素,类型为32位整数
  • clear(color):清除整个图片
  • resize(xs,ys):将图片调整为新的分辨率
  • bmp:VCL封装的GDI位图,可访问画布

我只添加了2个相关成员函数的源代码(不需要复制整个类)。

[编辑3] LQ 图片

我找到了最佳设置(代码相同):

int sz=32;          // [pixels] square size for corner avg color computation (c00..c11)
int fs0=2;          // blue [pixels] font thickness
int fs1=2;          // red  [pixels] font thickness
int tr0=52;         // blue min treshold
int tr1=0;          // red  max treshold

LQ example

由于光线条件,红色区域无法使用(已关闭)。


非常感谢您的回复。 - Wajahat
非常感谢回复。我对高分辨率图像有这样的结果,但这对于OCR来说仍然不足。为了使用OCR,我需要知道任何一种方法,只填充每个字符笔画宽度中的空白而不会使相邻字符合并到一起。我已经在您处理过的图像上应用了直径为5像素或线元素的圆形盘膜,但我仍然无法获得足够好的二进制地图供OCR使用。它确实识别出一些字符,但是大多数都是错误的。最好的问候 - Wajahat
感谢您的回复。我正在使用超过2个光源方向,但如果方向更倾斜(高度斜角),则阴影会在边缘图中引入很多伪影。我相信某种阴影去除算法可能会有所帮助。 - Wajahat
@Wajahat 我会在每张图像中只使用单个光源方向...并选择与角色接触的阴影边缘。然后在连接之后,您将获得字符边缘而不是阴影本身。 - Spektre
到目前为止使用的图像是高分辨率的。您有没有处理低分辨率图像的想法,比如我下面添加的这个?您能否检查一下对此图像的归一化/最小-最大滤波实现,看看是否有任何阈值组合可以产生良好的结果? - Wajahat
显示剩余10条评论

2
您可以先应用最大值滤波器(在新图像中为每个像素分配原始图像中相同像素周围区域的最大值),然后再应用最小值滤波器(在最大值图像中为每个像素分配其周围区域的最小值)。特别是如果您将邻域形状稍微调宽(例如,向右/左两到三个像素,向上/下一个像素),则应该能够得到一些字符(您的图像似乎主要显示水平方向的间隙)。
最佳邻域大小和形状取决于您的具体问题,因此您需要进行一些实验。您可能会通过此操作将字符粘在一起 - 如果它们与其他斑点相比过宽,您可能需要检测并拆分它们。
编辑:此外,二值化设置绝对是关键。尝试几种不同的二值化算法(Otsu、Sauvola等),看看哪种算法(以及哪些参数)最适合您。

嗨,丹尼尔,非常感谢您的建议。但是最大/最小过滤器与标准腐蚀和膨胀有什么不同呢? - Wajahat
我认为这只是同一个过滤器的不同名称。在我们公司里,术语好像更喜欢使用max/min(更短...?)。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接