文字检测是文字识别过程中的一个非常重要的环节,文字检测的主要目标是将图片中的文字区域位置检测出来,以便于进行后面的文字识别,只有找到了文本所在区域,才能对其内容进行识别。
文字检测的场景主要分为两种,一种是简单场景,另一种是复杂场景。其中,简单场景的文字检测较为简单,例如像书本扫描、屏幕截图、或者清晰度高、规整的照片等;而复杂场景,主要是指自然场景,情况比较复杂,例如像街边的广告牌、产品包装盒、设备上的说明、商标等等,存在着背景复杂、光线忽明忽暗、角度倾斜、扭曲变形、清晰度不足等各种情况,文字检测的难度更大。如下图:
本文将介绍简单场景、复杂场景中常用的文字检测方法,包括形态学操作、MSER+NMS、CTPN、SegLink、EAST等方法,并主要以ICDAR场景文字图片数据集介绍如何使用这些方法,如下图:
1、简单场景:形态学操作法
通过利用计算机视觉中的图像形态学操作,包括膨胀、腐蚀基本操作,即可实现简单场景的文字检测,例如检测屏幕截图中的文字区域位置,如下图:
其中,“膨胀”就是对图像中的高亮部分进行扩张,让白色区域变多;“腐蚀”就是图像中的高亮部分被蚕食,让黑色区域变多。通过膨胀、腐蚀的一系列操作,可将文字区域的轮廓突出,并消除掉一些边框线条,再通过查找轮廓的方法计算出文字区域的位置出来。主要的步骤如下:
- 读取图片,并转为灰度图
- 图片二值化,或先降噪后再二值化,以便简化处理
- 膨胀、腐蚀操作,突出轮廓、消除边框线条
- 查找轮廓,去除不符合文字特点的边框
- 返回文字检测的边框结果
通过OpenCV,便能轻松实现以上过程,核心代码如下:
- # -*- coding: utf-8 -*-
-
- import cv2
- import numpy as np
-
- # 读取图片
- imagePath = '/data/download/test1.jpg'
- img = cv2.imread(imagePath)
-
- # 转化成灰度图
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
-
- # 利用Sobel边缘检测生成二值图
- sobel = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize=3)
- # 二值化
- ret, binary = cv2.threshold(sobel, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)
-
- # 膨胀、腐蚀
- element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 9))
- element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (24, 6))
-
- # 膨胀一次,让轮廓突出
- dilation = cv2.dilate(binary, element2, iterations=1)
-
- # 腐蚀一次,去掉细节
- erosion = cv2.erode(dilation, element1, iterations=1)
-
- # 再次膨胀,让轮廓明显一些
- dilation2 = cv2.dilate(erosion, element2, iterations=2)
-
- # 查找轮廓和筛选文字区域
- region = []
- contours, hierarchy = cv2.findContours(dilation2, 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)
- print ("rect is: ")
- print (rect)
-
- # box是四个点的坐标
- box = cv2.boxPoints(rect)
- box = np.int0(box)
-
- # 计算高和宽
- height = abs(box[0][1] - box[2][1])
- width = abs(box[0][0] - box[2][0])
-
- # 根据文字特征,筛选那些太细的矩形,留下扁的
- if (height > width * 1.3):
- continue
-
- region.append(box)
-
- # 绘制轮廓
- for box in region:
- cv2.drawContours(img, [box], 0, (0, 255, 0), 2)
-
- cv2.imshow('img', img)
- cv2.waitKey(0)
- cv2.destroyAllWindows()
该图像处理过程如下图所示:
可以看到最终成功将图像中的文字区域检测出来了。
这种方法的特点是计算简单、处理起来非常快,但在文字检测中的应用场景非常有限,例如如果图片是拍照的,光线有明有暗或者角度有倾斜、纸张变形等,则该方法需要不断重新调整才能检测,而且效果也不会很好,如下图。例如上面介绍的代码是针对白底黑字的检测,如果是深色底白色字则需要重新调整代码,如果有需要,可再私信我交流。
2、简单场景:MSER+NMS检测法
MSER(Maximally Stable Extremal Regions,最大稳定极值区域)是一个较为流行的文字检测传统方法(相对于基于深度学习的AI文字检测而言),在传统OCR中应用较广,在某些场景下,又快又准。
MSER算法是在2002提出来的,主要是基于分水岭的思想进行检测。分水岭算法思想来源于地形学,将图像当作自然地貌,图像中每一个像素的灰度值表示该点的海拔高度,每一个局部极小值及区域称为集水盆地,两个集水盆地之间的边界则为分水岭,如下图:
MSER的处理过程是这样的,对一幅灰度图像取不同的阈值进行二值化处理,阈值从0至255递增,这个递增的过程就好比是一片土地上的水面不断上升,随着水位的不断上升,一些较低的区域就会逐渐被淹没,从天空鸟瞰,大地变为陆地、水域两部分,并且水域部分在不断扩大。在这个“漫水”的过程中,图像中的某些连通区域变化很小,甚至没有变化,则该区域就被称为最大稳定极值区域。在一幅有文字的图像上,文字区域由于颜色(灰度值)是一致的,因此在水平面(阈值)持续增长的过程中,一开始不会被“淹没”,直到阈值增加到文字本身的灰度值时才会被“淹没”。该算法可以用来粗略地定位出图像中的文字区域位置。
听起来这个处理过程似乎非常复杂,好在OpenCV中已内置了MSER的算法,可以直接调用,大大简化了处理过程。
检测效果如下图:
检测后的结果是存在各种不规则的检测框形状,通过对这些框的坐标作重新处理,变成一个个的矩形框。如下图:
核心代码如下:
- # 读取图片
- imagePath = '/data/download/test2.jpg'
- img = cv2.imread(imagePath)
-
- # 灰度化
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
- vis = img.copy()
- orig = img.copy()
-
- # 调用 MSER 算法
- mser = cv2.MSER_create()
- regions, _ = mser.detectRegions(gray) # 获取文本区域
- hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions] # 绘制文本区域
- cv2.polylines(img, hulls, 1, (0, 255, 0))
- cv2.imshow('img', img)
-
- # 将不规则检测框处理成矩形框
- keep = []
- for c in hulls:
- x, y, w, h = cv2.boundingRect(c)
- keep.append([x,