文本中的“河流”检测

信息处理 图像处理
2021-12-19 20:36:35

在 TeX stackexchange 上,我们一直在讨论如何在这个问题的段落中检测“河流” 。

在这种情况下,河流是由文本中的字间距意外对齐产生的空白带。由于这可能会分散读者的注意力,因此不良河流被认为是排版不良的症状。带有河流的文本的一个例子是这个,其中有两条对角线流动的河流。

在此处输入图像描述

人们对自动检测这些河流很感兴趣,以便可以避免它们(可能通过手动编辑文本)。Raphink 在 TeX 级别上取得了一些进展(它只知道字形位置和边界框),但我相信检测河流的最佳方法是进行一些图像处理(因为字形形状非常重要,TeX 不可用) . 我尝试了各种方法从上面的图像中提取河流,但我应用少量椭圆体模糊的简单想法似乎还不够好。我也尝试了一些氡气基于霍夫变换的过滤,但我也没有得到任何结果。河流对人眼/视网膜/大脑的特征检测电路非常明显,不知何故,我认为这可以转化为某种过滤操作,但我无法使其工作。有任何想法吗?

具体来说,我正在寻找一些可以检测上图中的 2 条河流的操作,但不会有太多其他误报检测。

编辑: endolith 问我为什么要采用基于图像处理的方法,因为在 TeX 中我们可以访问字形位置、间距等,并且使用检查实际文本的算法可能会更快、更可靠。我以另一种方式做事的原因是形状字形的多少会影响河流的显着程度,并且在文本级别很难考虑这种形状(这取决于字体、连字等)。有关字形形状如何重要的示例,请考虑以下两个示例,它们之间的区别在于我用几乎相同宽度的其他字形替换了一些字形,以便基于文本的分析会考虑他们同样好/坏。但是请注意,第一个示例中的河流比第二个示例中的河流要糟糕得多。

在此处输入图像描述

在此处输入图像描述

4个回答

这个我想多了,觉得下面的应该是比较稳定的。请注意,我将自己限制在形态学操作上,因为这些应该在任何标准图像处理库中都可用。

(1) 使用 nPix-by-1 掩码打开图像,其中 nPix 大约是字母之间的垂直距离

#% read image
img = rgb2gray('http://i.stack.imgur.com/4ShOW.png');

%# threshold and open with a rectangle
%# that is roughly letter sized
bwImg = img > 200; %# threshold of 200 is better than 128

opImg = imopen(bwImg,ones(13,1));

在此处输入图像描述

(2) 使用 1×mPix 蒙版打开图像,以消除任何太窄而不能成为河流的地方。

opImg = imopen(opImg,ones(1,5));

在此处输入图像描述

(3)去掉因段落间距或缩进造成的横向“江湖”。为此,我们删除所有为真的行,并使用我们知道不会影响我们之前找到的河流的 nPix-by-1 掩码打开。

要移除湖,我们可以使用比 nPix-by-nPix 稍大的开口遮罩。

在这一步,我们还可以丢弃所有太小而不能成为真正河流的东西,即面积小于 (nPix+2)*(mPix+2)*4 的所有东西(这将给我们大约 3 条线)。+2 之所以存在,是因为我们知道所有对象的高度至少为 nPix,宽度至少为 mPix,我们想要稍微高于它。

%# horizontal river: just look for rows that are all true
opImg(all(opImg,2),:) = false;
%# open with line spacing (nPix)
opImg = imopen(opImg,ones(13,1));

%# remove lakes with nPix+2
opImg = opImg & ~imopen(opImg,ones(15,15)); 

%# remove small fry
opImg = bwareaopen(opImg,7*15*4);

在此处输入图像描述

(4) 如果我们不仅对河流的长度感兴趣,而且对河流的宽度感兴趣,我们可以将距离变换与骨架相结合。

   dt = bwdist(~opImg);
   sk = bwmorph(opImg,'skel',inf);
   %# prune the skeleton a bit to remove branches
   sk = bwmorph(sk,'spur',7);

   riversWithWidth = dt.*sk;

在此处输入图像描述 (颜色对应于河流的宽度(尽管颜色条偏离了 2 倍)

现在,您可以通过计算每个连接组件中的像素数来获得河流的大致长度,并通过平均它们的像素值来获得平均宽度。


这是应用于第二张“无河流”图像的完全相同的分析:

在此处输入图像描述

在 Mathematica 中,使用腐蚀和霍夫变换:

(*Get Your Images*)
i = Import /@ {"http://i.stack.imgur.com/4ShOW.png", 
               "http://i.stack.imgur.com/5UQwb.png"};

(*Erode and binarize*)
i1 = Binarize /@ (Erosion[#, 2] & /@ i);

(*Hough transform*)
lines = ImageLines[#, .5, "Segmented" -> True] & /@ i1;

(*Ready, show them*)
Show[#[[1]],Graphics[{Thick,Orange, Line /@ #[[2]]}]] & /@ Transpose[{i, lines}]

在此处输入图像描述

编辑回答巫师先生的评论

如果你想摆脱水平线,只需做这样的事情(可能有人可以让它更简单):

Show[#[[1]], Graphics[{Thick, Orange, Line /@ #[[2]]}]] & /@ 
 Transpose[{i, Select[Flatten[#, 1], Chop@Last@(Subtract @@ #) != 0 &] & /@ lines}]

在此处输入图像描述

嗯...我猜氡变换不是那么容易提取。(Radon 变换基本上是在“通过它”边看的同时旋转图像。这是 CAT 扫描背后的原理。)图像的变换会产生这个正弦图,“河流”形成明亮的峰,这些峰被圈起来:

在此处输入图像描述

可以很清楚地看到旋转 70 度的那个,作为沿水平轴的切片图左侧的峰值:

在此处输入图像描述

特别是如果文本首先是高斯模糊的:

在此处输入图像描述

但我不确定如何可靠地从其余噪声中提取这些峰值。正弦图明亮的顶端和底端代表了文本水平行之间的“河流”,您显然并不关心。也许是一个加权函数与角度,强调更多的垂直线并最小化水平线?

一个简单的余弦加权函数在这张图片上效果很好:

在此处输入图像描述

找到 90 度的垂直河流,这是正弦图中的全局最大值:

在此处输入图像描述

在这张图片上找到 104 度的那个,虽然首先模糊使它更准确:

在此处输入图像描述 在此处输入图像描述

(SciPy 的radon()功能有点笨,或者我会将这个峰映射回原始图像,作为穿过河中的一条线。)

但是在模糊和加权之后,它没有在您的图像的正弦图中找到两个主要峰值中的任何一个:

在此处输入图像描述

他们在那里,但他们被加权函数中间峰值附近的东西所淹没。通过正确的加权和调整,这种方法可能奏效,但我不确定正确的调整是什么。它也可能取决于页面扫描的属性。也许权重需要从切片中的整体能量或其他东西中得出,例如归一化。

from pylab import *
from scipy.misc import radon
import Image

filename = 'rivers.png'
I = asarray(Image.open(filename).convert('L').rotate(90))

# Do the radon transform and display the result
a = radon(I, theta = mgrid[0:180])

# Remove offset
a = a - min(a.flat)

# Weight it to emphasize vertical lines
b = arange(shape(a)[1]) #
d = (0.5-0.5*cos(b*pi/90))*a

figure()
imshow(d.T)
gray()
show()

# Find the global maximum, plot it, print it
peak_x, peak_y = unravel_index(argmax(d),shape(d))
plot(peak_x, peak_y,'ro')
print len(d)- peak_x, 'pixels', peak_y, 'degrees'

我使用不同尺度的衍生特征(最高二阶)在像素上训练了一个判别分类器。

我的标签:

标签

训练图像预测:

在此处输入图像描述

对其他两张图像的预测:

在此处输入图像描述

在此处输入图像描述

我想这看起来很有希望,并且考虑到更多的训练数据和更智能的功能,可能会产生可用的结果。另一方面,我只花了几分钟就得到了这些结果。您可以使用开源软件ilastik自己重现结果。[免责声明:我是主要开发人员之一。]