文章目录
- 1 概述
- 2 流程解读
- 2.1 生成文字mask
- 2.2 plane2xyz的bug
- 2.3 文字上色
- 2.4 图像融合
- 参考资料
1 概述
SynthText是OCR领域生成数据集非常经典,且至今看来无人超越的方法。整体可以分为三个大的步骤,分别是生成文字的mask,这里用到了图像的分割信息来选取区域,图像的深度信息来拟合该区域的3D平面,单应性变换来旋转平面;文字上色,用于选取适合背景色的文字颜色;图像融合,用来了泊松图像融合。
整体思路是非常棒的,挑不出毛病。目前有些文本贴图不真实,主要原因是分割的区域不是同一个平面内的区域,加之图像的深度信息也不准,导致了3D平面拟合不准,文字贴上去也就不准了。
2 流程解读
2.1 生成文字mask
这部分需要用到图像的分割信息和深度信息,示意图如图2-1所示。
在当前的场景当中,最好的分割结果是可以把同一个物体的不同平面分割出来;次好的结果是把同一个平面分割出来,可以是不同物体。比如一座房子,不同的墙最好可以单独分开,两座平行的房子上平行的墙,分割在一起也是可以的。对于曲面,整个分割成一个instance就可以了,这个可以通过无法拟合成一个平面来删除。
最好的深度信息,就是准确的深度信息。
xyz = su.DepthCamera.depth2xyz(depth)
这步就是将深度信息转化为空间3D坐标。
regions = TextRegions.get_regions(xyz,seg,area,label)
会对分割区域进行筛选,得到可用于分割的区域。
筛选分为两步:
(1)regions = TextRegions.filter(seg,area,label)
筛除面积过小,或者长宽比极端的区域
(2)regions = TextRegions.filter_depth(xyz,seg,regions)
筛除区域内的点不能构成一个平面的区域,也就是曲面或者多个面,这里会得到平面的拟合结果
筛选后的区域示意图如下图2-2所示。
regions = self.filter_for_placement(xyz,seg,regions)
对图2-2中的区域进行warp的变换,并获取warp变换所使用的单应性变换矩阵。
这部分有些难理解,我这里举个例子。比如我们有原图2-3。
我们选取的区域为图2-4,也就是床下面那块黑影。
该区域的部分截取下来为图2-5。
该区域拟合的平面为图2-6。
将图2-6中的平面绕中点转正,也就是转到平面的法向量对准我们,即法向量与Y轴平行。记录下这个变换过程所用到的单应性变换矩阵及其逆变换矩阵。转正后的图片如下图2-7所示。
其mask如图2-8所示。
我们这里得到的单应性变换矩阵就是图2-4变为图2-8的矩阵。接下来只要往图2-8上贴上文字,然后整体变回图2-4就可以了。
这部分直接关系到了文字贴上图片能否有背景在同一个空间的效果。有许多的深度信息和分割信息是不准的,如果可以优化的话,可以有更好的效果。拿自动驾驶的数据集去当背景图应该是个不错的选择。
2.2 plane2xyz的bug
在将预估出来的平面转正的时候,发现转出来的z的值总是不同的,这和我们的理解是由偏差了。查了半天问题,发现是place2xyz代码中的两个系数写反了。修正后的代码为
@staticmethod
def plane2xyz(center, ij, plane):"""converts image pixel indices to xyz on the PLANE.center : 2-tupleij : nx2 int arrayplane : 4-tuplereturn nx3 array."""ij = np.atleast_2d(ij)n = ij.shape[0]ij = ij.astype('float')xy_ray = (ij-center[None,:]) / DepthCamera.fz = -plane[3]/(xy_ray.dot(plane[:2])+plane[2])# z = -plane[2]/(xy_ray.dot(plane[:2])+plane[3])xyz = np.c_[xy_ray, np.ones(n)] * z[:,None]return xyz
接下来解释一下为何如此。如下图2-9,我们的目的是将左边图像平面上的点,转换到右侧的实际平面上,中间的原点是小孔,原点到左侧图像的距离为焦距。实际平面就是代码中的plane
,它有四个值,分别是a,b,c,da, b, c, da,b,c,d,表示平面ax+by+cz+d=0ax+by+cz+d=0ax+by+cz+d=0。
图像上点(xi,yi)(x_i, y_i)(xi,yi)经过原点的直线可以表示为
x−0xi−0=y−0yi−0=z−0zi−0(2-1)\frac{x-0}{x_i - 0} = \frac{y-0}{y_i - 0} = \frac{z-0}{z_i - 0} \tag{2-1} xi−0x−0=yi−0y−0=zi−0z−0(2-1)
于是可以有
x=xizzi,y=yizzi,zi=f(2-2)x = \frac{x_iz}{z_i}, y = \frac{y_iz}{z_i}, z_i=f \tag{2-2} x=zixiz,y=ziyiz,zi=f(2-2)
将式(2−2)(2-2)(2−2)代入ax+by+cz+d=0ax+by+cz+d=0ax+by+cz+d=0,计算直线与planar scene的交点可以得到
z=−daxif+byif+c(2-3)z = -\frac{d}{a\frac{x_i}{f} + b\frac{y_i}{f} + c} \tag{2-3} z=−afxi+bfyi+cd(2-3)
代码中plane[2]
就是ccc,plane[3]
就是ddd,所有原来是反的。
代码中的xy_ray
就是xif\frac{x_i}{f}fxi和yif\frac{y_i}{f}fyi。要求xxx和yyy,再利用式(2−2)(2-2)(2−2)就可以了,这也就有了
xyz = np.c_[xy_ray, np.ones(n)] * z[:,None]
把这行代码改了,有奇效!
2.3 文字上色
我们往图2-8上贴文字的时候,需要确定文字的字体和颜色,字体随机即可,文字的颜色是需要根据背景色来确定的。
render_res = self.text_renderer.render_sample(font,collision_mask)
就是贴文字的过程。这部分的位置选取,字体选取,文字尺寸什么的就不说了,说一下这个文字颜色的选取。
选取颜色的过程在 def color_text(self, text_arr, h, bg_arr)
函数当中,这里依赖于一个文件 colors_new.cp
,这个文件中存储的是shape为 (4941, 12)
的array,为表示方便称为colors
。colors[:, :6]
表示颜色A的RGB值和各通道方差,colors[:, 6:12]
表示颜色B的RGB值和各通道方差。表示背景色为A时,文字颜色可以用B;背景色为B时,文字颜色可以用A。
在寻找最匹配的背景色的时候,使用了LAB颜色空间,也就是将所有的RGB值转为到了LAB空间,然后取候选区域的所有LAB值的平均作为背景色LAB色,然后根据LAB色的距离来选取最合适的RGB值。方差就是取值的时候,加上方差,来得到多样性的作用。
这部分是可以优化的,背景色直接取LAB平均也太暴力了,万一颜色比较花,或者有极端值,就会影响很大。可以改成聚类或者mmcq的方式来获取主体色作为背景色。
2.4 图像融合
直到上述部分,文字仍旧还是和背景分离的,得到带有文本的图像和贴图的区域之后,使用了泊松融合的方式来自然融合两图,这里不推荐使用cv2.seamlessClone
,因为cv2.seamlessClone
暴露的参数太少了,作者自己写的blit_images
就非常好,scale_grad
这个参数可以用来调整前景的梯度,也就是前景贴到背景上的明显程度。
比如有前景图和背景图,如图2-10所示。
使用不同的 scale_grad
参数和 cv2.seamlessClone
的效果如下图2-11所示,cv2.seamlessClone
甚至丢失掉了边界。
将文本贴到warp过的区域之后,就可以使用单应性变换逆矩阵,把warp的区域还原回去,得到最终的效果图。
这么好的效果,其实是挑选过结果的,实际情况下,还是有很多都是效果没那么出色的。怪就怪分割和深度信息不准。
参考资料
[1] Synthetic Data for Text Localisation in Natural Images
[2] https://github.com/ankush-me/SynthText