场景文字检测—CTPN原理与实现 - 知乎对于复杂场景的文字识别,首先要定位文字的位置,即文字检测。这一直是一个研究热点。 Detecting Text in Natural Image with Connectionist Text Proposal NetworkCTPN是在ECCV 2016提出的一种文字检测算法。CTPN…https://zhuanlan.zhihu.com/p/34757009
Ctpn检测的都是小框,宽度16像素(stride=16),在github上比较火的ocr项目chineseocr就是使用的ctpn的后处理方向,只不过把前面的fastrcnn换成了yolo。
?1.fine-scale提案
?????????我们寻找能够很好地泛化到所有级别的文本组件的文本的独特属性。 我们观察到,RPN 的单词检测很难准确预测单词的水平边,因为单词中的每个字符都是孤立的或分开的,这使得很难找到单词的开始和结束位置。 显然,文本行是一个序列,它是文本和通用对象之间的主要区别。 将文本行视为一系列 fine-scale 文本提案是很自然的,其中每个提案通常代表文本行的一小部分,例如,16 像素宽度的文本片段。 每个提案可能包括单个或多个笔划、一个字符的一部分、单个或多个字符等。我们认为,通过 x 其水平位置来预测每个提案的垂直位置会更准确,这可能是 更难预测。 与预测对象的 4 个坐标的 RPN 相比,这减少了搜索空间。 我们开发了一种垂直锚机制,可以同时预测每个 fine-scale 提议的文本/非文本分数和 y 轴位置。 检测一般的固定宽度文本建议也比识别孤立字符更可靠,孤立字符很容易与一个字符的一部分或多个字符混淆。 此外,在一系列固定宽度的文本建议中检测文本行也可以可靠地处理多尺度和多纵横比的文本。
???????? 为此,我们设计了如下的 fine-scale 文本提案。 我们的检测器密集地调查 conv5 中的每个空间位置。 文本提案被定义为具有 16 像素的固定宽度(在输入图像中)。 这等于在 conv5 映射中密集移动检测器,其中总步幅正好是 16 个像素。 然后我们设计 k 个垂直锚来预测每个提案的 y 坐标。? k 个锚点具有相同的水平位置,固定宽度为 16 像素,但它们的垂直位置在 k 个不同的高度上变化。 在我们的实验中,我们为每个提议使用十个锚点,k = 10,其高度在输入图像中从 11 到 273 像素(每次 0:7)不等。 显式垂直坐标由提案边界框的高度和 y 轴中心测量。
1.加了lstm
cnn学习的是感受野内的空间信息,lstm学习的是序列特征,对于文本序列检测,毕竟文字是连续的,需要lstm。不过看有人说把lstm换成7*7的conv也有效果。
3.side-refinment
?看了不少的开源方案,也有很多人没有实现它这个边缘的loss
贴一段上面的过程的代码:
class CTPN_Model(nn.Module):
def __init__(self):
super().__init__()
base_model = models.vgg16(pretrained=False)
layers = list(base_model.features)[:-1]
self.base_layers = nn.Sequential(*layers) # block5_conv3 output
self.rpn = BasicConv(512, 512, 3,1,1,bn=False)
self.brnn = nn.GRU(512,128, bidirectional=True, batch_first=True)
self.lstm_fc = BasicConv(256, 512,1,1,relu=True, bn=False)
self.rpn_class = BasicConv(512, 10*2, 1, 1, relu=False,bn=False)
self.rpn_regress = BasicConv(512, 10 * 2, 1, 1, relu=False, bn=False)
def forward(self, x):
x = self.base_layers(x)
# rpn
x = self.rpn(x)
x1 = x.permute(0,2,3,1).contiguous() # channels last
b = x1.size() # batch_size, h, w, c
x1 = x1.view(b[0]*b[1], b[2], b[3])
x2, _ = self.brnn(x1)
xsz = x.size()
x3 = x2.view(xsz[0], xsz[2], xsz[3], 256) # torch.Size([4, 20, 20, 256])
x3 = x3.permute(0,3,1,2).contiguous() # channels first
x3 = self.lstm_fc(x3)
x = x3
cls = self.rpn_class(x)
regr = self.rpn_regress(x)
cls = cls.permute(0,2,3,1).contiguous()
regr = regr.permute(0,2,3,1).contiguous()
cls = cls.view(cls.size(0), cls.size(1)*cls.size(2)*10, 2)
regr = regr.view(regr.size(0), regr.size(1)*regr.size(2)*10, 2)
return cls, regr
4.连框策略
? ? ? ? 看上面的代码,是先经过了vgg和rpn的特征提取,经过了lstm或者是gru,在经过了了lstm_fc之后,输出两个head,一个是cls,一个是回归的支路,回归只是竖向的中心y和h。一共十组anchor,这其中正负样本的分配策略基本和fasterrcnn是一致的,ctpn只是改动了anchor,加了lstm和回归的head。
? ? ? ? 连框策略还是比较复杂的。
|