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;
}
有关详细信息,请参阅链接的答案。切换到灰度<0,765> 并使用pic1.normalize(8,false,true); 后,输入图像的结果如下
二值化
我首先尝试了简单的范围阈值化,所以如果所有颜色通道值(R、G、B)都在<min,max> 范围内,则将其重新着色为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 会太大)。如您所见,结果或多或少可以接受。
如果这还不够,您可以将图像分割成多个部分。计算每个段的直方图(它应该是双峰的)然后找到两个最大值之间的颜色,这是你的阈值。问题是背景覆盖了更多的区域,因此墨峰相对较小,有时很难在线性尺度上发现完整的图像直方图:
当您为每个片段执行此操作时,效果会好很多(因为阈值周围的背景/文本颜色渗色会少得多),因此间隙会更加明显。也不要忘记忽略小间隙(直方图中缺少垂直线),因为它们仅与量化/编码/舍入有关(图像中并非所有灰色阴影都存在),因此您应该过滤掉小于强度的间隙替换它们具有最后一个和下一个有效直方图条目的平均值。