图像处理智能化的探索[二]:文字区块识别

0. 前言

在很久很久以前,我发过一篇关于用人脸识别实现智能裁剪图片的文章:原文链接。写完这篇文后,我畅想了一下所有内容相关业务实现全自动化运营的盛世图景……现在回想起来,当时的我真是太年轻了。殊不知有句老话说得好(?):自动化运营的大坑茫茫多,图片特别多啊!总之不经历种种跌倒,就无法认识到现实有多残酷(以及有多奇葩),我们只好擦干眼泪,期望用自己的肉身在地雷阵里探出一片通途。坑这么多,那么我们就一个个来填平吧!

1. 问题

缩略图是一篇新闻展现在用户面前举足轻重的一环,俗话说:有图有真相,突出的就是图片对于用户获取信息的重要性。在很多业务场景下,新闻列表里通常只有一个标题和一幅缩略图,因此可以说缩略图的质量高低很大程度决定了用户会否愿意点进详情页。在应用了人脸识别后的一段时间,我们观察到,纵然脸部残缺不全、人像不翼而飞的情形大幅减少,但另一些骨骼清奇的图片却浮上了水面。

比如这位:

此类图片内容常包含微博发文、统计报表和频道推广、赞助商广告等,不胜枚举。好吧,我承认那些常出现在娱乐频道头条的微博发文和聊天记录一类的图可能在某种程度上满足了吃瓜群众的窥私欲,时常能赚来大量点击,然而当它们出现在120X68或者更小的尺寸下时,就算拿放大镜也是看不清内容的。这样的缩略图几乎等于白板,无法起到吸引用户眼球的作用,更别提茫茫多千奇百怪的广告图了。至于这些图是怎么抓过来的,我管不了,我们可以做到的是在输出图片之前设立一个切面,告诉接口这幅图能不能用,这就够了。

2. 探索

地雷位置探明了,接下来就是着手排除了。我们观察到这类图片的共同点就是——文字多,我们要做的工作也就是识别图像的文字占地面积。

  • 均值计算

本着一切从简的思路,考虑到微博文字一类图片通常是白色背景,且文字占用的像素较少,我们第一个想到的方法就是将图像灰度化后计算均值和方差,这一点通过opencv可以很轻易地实现:

mean, std = cv2.meanStdDev(img)  

通过一些样本的统计,一般均值在200以上,方差在40以内,可以判定为图像泛白,文字过多的可能性很大。然而,一张一点也不白的广告图横空出世,狠狠地打了我的脸:

好吧,我们继续……

  • OCR

既然不能用色值来简单地归类,那么我们就得把关注点挪到文字本身。文字识别提得最多的就是OCR了,识别流程大致为图像预处理(灰度、降噪、二值化)-> 特征提取 -> 分类 -> 后处理(模型校正)。这块成熟的东西很多,比如Tesseract-OCR、chongdata等,但要不就是限制过多,要不就是对中文的识别效果很差,在图示那种复杂背景下出现较小文字的话基本无法识别。若是自己实现一套OCR,光特征提取和分类训练就很费时间。况且我们的需求只是过滤“文字多的图片”,而不是“识别出文字内容”,使用OCR也就有种杀鸡用牛刀的感觉了。不过在OCR的流程中,也有值得我们提取出来加以利用的环节,那便是图像预处理部分。在OCR中,这一环节从图像里分离出文字区域,用来为下一步:字符切分和特征提取做准备,但对我来说,走到这一步就够了。

  • 边缘检测

文字区块通常的特征是他们的边缘非常齐整,可以连成一个长矩形。如果能够找出这些矩形,那么我们只需要计算他们的相对面积或者数量,便可以确定这张图中的文字是否过多。幸运的是,OCR的预处理中刚好有一种方法能用来解决这个问题,那便是边缘检测。

图像中,物体的边缘通常表现为亮度或者像素灰度急剧变化,通过计算这些数值变化的导数(反映出变化的剧烈程度),即可在图像中检测出一系列高于某个阈值的像素集合,这就是我们通常看到的边缘,或者轮廓。OK,方法找到了,话不多说,上OpenCV。

4. 实践

我们拿到这样一幅娱乐频道新闻中常见的图。

4.1 图像降噪

首先,为了除去一些噪声数据的干扰,我们将图片灰度化处理,得到单通道图像,调用OpenCV的边缘检测方法。

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  
sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize=3)  

这里我们使用的是Sobel算子,用来计算图像灰度函数的近似梯度。此外还有Canny算子、Laplacian算子等,各自的特性可查阅相关资料。

dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])  

其中,参数dx和dy分别表示x和y方向上的差分阶数,取1, 0表示只检测x方向上的边缘(因为我们要检测的文本大多是横向的)。检测完后的图像如下:

从图中可以看到,除了文字,还有一写其他的边缘包含在内(照片、景物等),接下来我们要做的就是去除这部分的干扰。这个时候,万能的OpenCV又站了出来,他表示:我认识一对好基友——膨胀和腐蚀,他们就是干这个的。

4.2 膨胀与腐蚀

膨胀(dilation)和腐蚀(erosion)是两种形态学运算方法,原理说来话长,简单表示他们的效果就是:膨胀会让图像的高亮区域变大,腐蚀会让图像的高亮区域变小,具体可阅读这篇博文:http://blog.csdn.net/xia316104/article/details/44748217,写得非常详细。通过膨胀与腐蚀,可以达到分割相连文字区域、去除噪声边缘的目的。

进行膨胀和腐蚀操作前,我们首先将图像二值化(即非黑:0即白:255的存储方式),进一步降噪,然后进行第一次膨胀。

c1 = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 8))  
c2 = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 6))

ret, bimg = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)  
dilation = cv2.dilate(bimg, c2, iterations=1)  

其中c1、c2中的参数20X8、20x6分别是腐蚀和膨胀的核,表示与图像区域做卷积的面积大小。效果如图:

可以看到经过膨胀后,文字区块已经被连成了一个矩形。但烦人的是,下部有一些竖直的边缘线也连到了一起。这时我们就要用到腐蚀了。

erosion = cv2.erode(dilation, c1, iterations=1)  
img_edge = cv2.dilate(erosion, c2, iterations=1)  

通过腐蚀,亮部的文字不会受到很大影响,而图像中参差不齐的边缘就遭了秧。此后我们再进行第二次膨胀,让文字边缘更加清晰整齐,这个过程有点类似PS抠图中调色阶的操作。最后,边缘结果如图所示:

4.3 筛选文字区域

完成上两步预处理后,我们现在可以正式开始着手筛选文字区域了。首先,我们根据边缘的连线得出所有的轮廓:

contours, hierarchy = cv2.findContours(img_edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)  

好吧,还是有一些图片中的轮廓混进来了。通过观察,我们发现这些轮廓相比文字区块是很不规则的,有句话叫以己之长攻彼之弱,我们就用文字区块这个特点来排除掉不规则的轮廓。利用OpenCV的minAreaRect方法,我们可以得到一块区域的像素点集中包含的最小面积的矩形。其中文字区块包含的矩形通常连成一片,相较其他轮廓更细长。因此我们通过内含矩形的长宽比则可以筛选出文字区块:

# 记录文字区块数量
area_text_num = 0  
region = []

# 根据边缘连接得到所有轮廓
contours, hierarchy = cv2.findContours(img_edge, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

for i in range(len(contours)):  
    cnt = contours[i]
    area = cv2.contourArea(cnt)

    # 筛掉面积过小的区块
    if area < 1000:
        continue

    # 得到最小矩形区域,转换为顶点坐标形式(矩形可能会有角度)
    rect = cv2.minAreaRect(cnt)
    box = cv2.cv.BoxPoints(rect)
    box = np.asarray(box)
    box = box.astype(int)

    x0 = box[0][0] if box[0][0] > 0 else 0
    x1 = box[2][0] if box[2][0] > 0 else 0
    y0 = box[0][1] if box[0][1] > 0 else 0
    y1 = box[2][1] if box[2][1] > 0 else 0
    height = abs(y0 - y1)
    width = abs(x0 - x1)

    # 筛掉不够“扁”的的区块,它们更有可能不是文字
    if height > width * 0.3:
        continue
    area_text_num += height * width
    region.append(box)

return region, area_text_num  

筛选之后结果如下:

有了结果之后,我们再根据业务的具体情况来设定“文字过多”的阈值,根据简单的统计后,我目前界定的标准是文字区块面积占图片的10%,或者图片区块数量大于3。

4.4 边缘调整

在白色背景上,识别结果十分完美,那么我们是否可以开始坐下来喝杯茶,开始憧憬图片智能化处理的盛世图景了呢……等等,残酷的现实告诉我,永远不要觉得前面的道路是平坦的……这不,一个测试用例弹了出来,差点吓掉了我手中的茶杯。它是这样的:

这种文字出现在背景里的图多了,岂不是很尴尬……于是我只得对之前的方法加入了一些调整。这些非主体的文字(如球场赞助商广告、球衣背号)相比广告文字更为模糊,因此我不假思索借用了检测图片模糊量的神器——Laplacian函数来助我临门一脚。在前面的代码中,加入如下片段:

for i in range(len(contours)):  
    cnt = contours[i]
    area = cv2.contourArea(cnt)

    # 筛掉面积过小的区块
    if area < 1000:
        continue

    # 得到最小矩形区域,转换为顶点坐标形式(矩形可能会有角度)
    rect = cv2.minAreaRect(cnt)
    box = cv2.cv.BoxPoints(rect)
    box = np.asarray(box)
    box = box.astype(int)

    # 过滤掉过于模糊的区块
    lap = cv2.Laplacian(gray[box[1][1]:box[0][1], box[0][0]:box[3][0]], cv2.CV_64F)
    if lap is None or lap.var() < TEXT_LAPLACIAN_THRESHOLD:
        continue

    # Code...

文字主体区域一般都很清晰(嗯,不清晰做个啥广告),因此边缘也会比较多,正是Laplacian算子的用武之地。至于模糊量过滤的阈值多少,也需要根据实际情况来调整。

5. 尾声

只是为了解决文字图过滤的问题,便费了好些周章,可见图片智能化处理的前路漫漫呐。我们只能且行且止,不断填平路上的坑,这样后人才能走在一片坦途之上。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android群英传

贝塞尔Loading——化学风暴

761
来自专栏大数据文摘

数学之美:两点之间最快的路径

1869
来自专栏新智元

【ACL2016 终极盘点】终身成就奖得主:我还没玩深度学习

【新智元导读】在德国柏林召开的计算机语言顶级会议ACL2016将于当地时间明天(8月12日)闭幕。今天,大会公布了最佳论文,一篇关于词态学的论文获此殊荣。此外,...

3587
来自专栏理论坞

制作长投影icon的一点注意事项

先放一下最终的效果图,这个图是今天在UI中国的一个临摹!相信我,我也不是什么大神,只是每天想做点东西,思考些东西,多练多积累,只看是没用的,只有做才行(其实这个...

1103
来自专栏AI科技评论

学界丨这届机器学习论文评比,搞怪无厘头我只服 Reddit

你知道吗?Reddit 上的 Machine Learning 小组上目前已经聚集了 85,613 位关注者了,而在 2016 年底的时候,@Mandratha...

3705
来自专栏PPV课数据科学社区

【经典】数据科学家教你用数据模型来恋爱

男生和女生分别是来自不同星球的科学事实已经众所周知的了.男生们总是认为,女生们都是迷一样的生物,他们的情感状态浮动似乎是以秒单位在变化的,难以理解,更勿论预测了...

2574
来自专栏量子位

怎样用GAN生成各种胖吉猫?谷歌大脑程序员教你撩妹神技

Facebook聊天框里出道的灰色短毛猫Pusheen,是柔软的微胖界宠儿,中文名字叫胖吉。

1462
来自专栏算法+

简易 bokeh 图像散景效果算法实现

bokeh百度百科的解释 摄影镜头光圈大小和拍摄距离决定了拍摄时的景深,相对于焦点位置,焦点前与焦点后的被拍摄物体会显得模糊,这个模糊区域被称为焦外。 焦外...

4285
来自专栏奇点大数据

破解PRISMA(中)

对PRISMA的研究已经持续了一周了。虽然还没有完全破解PRISMA最终各种滤镜算法的奥妙,但是确实得到了不少宝贵的信息。 喏,比如上次,我们就找到了这篇著名的...

3546
来自专栏大数据挖掘DT机器学习

【趣味】数据挖掘(6)——借水浒传故事,释决策树思路

决策树 (又称判定树,Decision Tree)是硕、博士生数据挖掘课程要点和难点,教学实践表明,这一章需要数学基础知识多,难得有趣。明知是难点,偏向难...

3575

扫码关注云+社区

领取腾讯云代金券