OCR字符重建和填充
character reconstruction and filling for OCR
我正在研究轮胎上的文字识别。
为了使用OCR,我必须先得到一个清晰的二值图。
我已经处理过图像,文本显示有破损和不连续的边缘。
我已经在 MATLAB 中尝试使用标准 erosion/dilation 圆盘和线元素,但它并没有真正帮助。
Pr1- 关于如何重构这些字符并填充字符笔画之间的空隙有什么想法吗?
Pr2- 上面的图像分辨率更高,照明良好。
但是,如果像下图这样光照不佳且分辨率相对较低,那么处理的可行方案是什么?
尝试过的解决方案:
S1:这是对Spektre分享的处理过的图像应用中值滤波后的结果。为了去除噪声,我应用了中值滤波器 (5x5),随后使用线元素 (5,11) 进行图像膨胀。即使现在 OCR (Matlab 2014b) 也只能识别部分字符
无论如何,非常感谢到目前为止的建议。我仍然会等着看是否有人可以提出不同的建议,也许是开箱即用的想法:)。
Matlab 执行以下 Spektre 代码中的步骤的结果(没有笔划扩张(按照 1,2,3,4 的顺序对角进行归一化:
并且阈值 tr0=400 和 tr1=180 以及归一化的角顺序 1,3,2,4
此致
瓦加哈特
您可以先应用最大过滤器(为新图像中的每个像素分配原始图像中同一像素周围邻域的最大值),然后应用最小过滤器(从最大邻域分配最小值-图片)。特别是如果你把邻域塑造得比它高一点(比如,right/left 2 或 3 个像素,1 个像素 top/bottom),你应该能够得到你的一些角色(你的图像似乎主要表现在水平方向上的间隙)。
最佳邻域大小和形状取决于您的具体问题,因此您必须进行一些试验。您可能会遇到通过此操作将字符粘合在一起的情况 - 您可能必须检测斑点并在它们与其他斑点相比太宽时将其拆分。
编辑:此外,二值化设置绝对是关键。尝试几种不同的二值化算法(Otsu、Sauvola 等),看看哪一种(以及哪些参数)最适合您。
你的输入我玩了一下
光照归一化+动态范围归一化有助于获得更好的结果,但离需要的效果还差得很远。我想尝试锐化部分推导以增强背景中的字母并在整合回来之前去除小凸起并重新着色以掩盖图像当我有时间时(不确定明天什么时候)我将编辑这个(和comment/notify你)
标准化光照
计算平均角强度并双线性重新调整强度以匹配平均颜色
如果您需要更复杂的东西,请参阅:
- OpenCV for OCR: How to compute thresholding levels for gray image OCR
边缘检测
x
和 y
...
的强度 i
的部分推导
i=|i(x,y)/dx|+|i(x,y)/dy|
然后被treshold=13
限制
[备注]
为了消除大部分噪声,我在边缘检测之前应用了平滑滤波
[edit1] 经过一些分析,我发现您的图像边缘较差,无法进行锐化整合
此处为图像中线 x 一阶推导后的强度图示例
如您所见,黑色区域很好,但白色区域几乎无法从背景噪音中辨认出来。所以你唯一的希望是使用 min max filtering as @Daniel answer suggested and take more weight on black edge regions (white is not reliable)
min max 过滤器强调黑色(蓝色蒙版)和白色(红色蒙版)区域。如果展位区域是可靠的,那么您只需在它们之间填充 space 但这不是您的选择,而是我会扩大区域(在蓝色蒙版上加权更多)并使用针对此类 3 定制的 OCR 对结果进行 OCR颜色输入。
- 您可以为此制作自己的自定义 OCR,请参阅 OCR and character similarity
你也可以用不同的光线位置和固定的相机拍摄2张图像并将它们组合起来从各个方向覆盖可识别的黑色区域
[edit2] 最后一个方法的 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);
}
}
}
//---------------------------------------------------------------------------
我使用自己的图片 class 作为图片,所以一些成员是:
xs,ys
图像大小(以像素为单位)
p[y][x].dd
是(x,y)
位置的像素,为32位整数类型
clear(color)
- 清除整个图像
resize(xs,ys)
- 将图像调整为新分辨率
bmp
- VCL 封装的 GDI 位图 Canvas 访问
我只为 2 个相关成员函数添加了源代码(无需在此处复制整个 class)
[edit3] 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
由于光照条件,红色区域无法使用(关闭)
我正在研究轮胎上的文字识别。 为了使用OCR,我必须先得到一个清晰的二值图。
我已经处理过图像,文本显示有破损和不连续的边缘。 我已经在 MATLAB 中尝试使用标准 erosion/dilation 圆盘和线元素,但它并没有真正帮助。
Pr1- 关于如何重构这些字符并填充字符笔画之间的空隙有什么想法吗?
Pr2- 上面的图像分辨率更高,照明良好。 但是,如果像下图这样光照不佳且分辨率相对较低,那么处理的可行方案是什么?
尝试过的解决方案:
S1:这是对Spektre分享的处理过的图像应用中值滤波后的结果。为了去除噪声,我应用了中值滤波器 (5x5),随后使用线元素 (5,11) 进行图像膨胀。即使现在 OCR (Matlab 2014b) 也只能识别部分字符
无论如何,非常感谢到目前为止的建议。我仍然会等着看是否有人可以提出不同的建议,也许是开箱即用的想法:)。
Matlab 执行以下 Spektre 代码中的步骤的结果(没有笔划扩张(按照 1,2,3,4 的顺序对角进行归一化:
并且阈值 tr0=400 和 tr1=180 以及归一化的角顺序 1,3,2,4
此致
瓦加哈特
您可以先应用最大过滤器(为新图像中的每个像素分配原始图像中同一像素周围邻域的最大值),然后应用最小过滤器(从最大邻域分配最小值-图片)。特别是如果你把邻域塑造得比它高一点(比如,right/left 2 或 3 个像素,1 个像素 top/bottom),你应该能够得到你的一些角色(你的图像似乎主要表现在水平方向上的间隙)。
最佳邻域大小和形状取决于您的具体问题,因此您必须进行一些试验。您可能会遇到通过此操作将字符粘合在一起的情况 - 您可能必须检测斑点并在它们与其他斑点相比太宽时将其拆分。
编辑:此外,二值化设置绝对是关键。尝试几种不同的二值化算法(Otsu、Sauvola 等),看看哪一种(以及哪些参数)最适合您。
你的输入我玩了一下
光照归一化+动态范围归一化有助于获得更好的结果,但离需要的效果还差得很远。我想尝试锐化部分推导以增强背景中的字母并在整合回来之前去除小凸起并重新着色以掩盖图像当我有时间时(不确定明天什么时候)我将编辑这个(和comment/notify你)
标准化光照
计算平均角强度并双线性重新调整强度以匹配平均颜色
如果您需要更复杂的东西,请参阅:
- OpenCV for OCR: How to compute thresholding levels for gray image OCR
边缘检测
x
和 y
...
i
的部分推导
i=|i(x,y)/dx|+|i(x,y)/dy|
然后被treshold=13
[备注]
为了消除大部分噪声,我在边缘检测之前应用了平滑滤波
[edit1] 经过一些分析,我发现您的图像边缘较差,无法进行锐化整合
此处为图像中线 x 一阶推导后的强度图示例
如您所见,黑色区域很好,但白色区域几乎无法从背景噪音中辨认出来。所以你唯一的希望是使用 min max filtering as @Daniel answer suggested and take more weight on black edge regions (white is not reliable)
min max 过滤器强调黑色(蓝色蒙版)和白色(红色蒙版)区域。如果展位区域是可靠的,那么您只需在它们之间填充 space 但这不是您的选择,而是我会扩大区域(在蓝色蒙版上加权更多)并使用针对此类 3 定制的 OCR 对结果进行 OCR颜色输入。
- 您可以为此制作自己的自定义 OCR,请参阅 OCR and character similarity
你也可以用不同的光线位置和固定的相机拍摄2张图像并将它们组合起来从各个方向覆盖可识别的黑色区域
[edit2] 最后一个方法的 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);
}
}
}
//---------------------------------------------------------------------------
我使用自己的图片 class 作为图片,所以一些成员是:
xs,ys
图像大小(以像素为单位)p[y][x].dd
是(x,y)
位置的像素,为32位整数类型clear(color)
- 清除整个图像resize(xs,ys)
- 将图像调整为新分辨率bmp
- VCL 封装的 GDI 位图 Canvas 访问
我只为 2 个相关成员函数添加了源代码(无需在此处复制整个 class)
[edit3] 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
由于光照条件,红色区域无法使用(关闭)