文字检测是文字识别过程当中的一个很是重要的环节,文字检测的主要目标是将图片中的文字区域位置检测出来,以便于进行后面的文字识别,只有找到了文本所在区域,才能对其内容进行识别。python
文字检测的场景主要分为两种,一种是简单场景,另外一种是复杂场景。其中,简单场景的文字检测较为简单,例如像书本扫描、屏幕截图、或者清晰度高、规整的照片等;而复杂场景,主要是指天然场景,状况比较复杂,例如像街边的广告牌、产品包装盒、设备上的说明、商标等等,存在着背景复杂、光线忽明忽暗、角度倾斜、扭曲变形、清晰度不足等各类状况,文字检测的难度更大。以下图:git
本文将介绍简单场景、复杂场景中经常使用的文字检测方法,包括形态学操做、MSER+NMS、CTPN、SegLink、EAST等方法,并主要以ICDAR场景文字图片数据集介绍如何使用这些方法,以下图:github
一、简单场景:形态学操做法web
经过利用计算机视觉中的图像形态学操做,包括膨胀、腐蚀基本操做,便可实现简单场景的文字检测,例如检测屏幕截图中的文字区域位置,以下图:算法
其中,“膨胀”就是对图像中的高亮部分进行扩张,让白色区域变多;“腐蚀”就是图像中的高亮部分被蚕食,让黑色区域变多。经过膨胀、腐蚀的一系列操做,可将文字区域的轮廓突出,并消除掉一些边框线条,再经过查找轮廓的方法计算出文字区域的位置出来。主要的步骤以下:数组
经过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()
该图像处理过程以下图所示:bash
能够看到最终成功将图像中的文字区域检测出来了。微信
这种方法的特色是计算简单、处理起来很是快,但在文字检测中的应用场景很是有限,例如若是图片是拍照的,光线有明有暗或者角度有倾斜、纸张变形等,则该方法须要不断从新调整才能检测,并且效果也不会很好,以下图。例如上面介绍的代码是针对白底黑字的检测,若是是深色底白色字则须要从新调整代码,若是有须要,可再私信我交流。网络
二、简单场景: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, y, x + w, y + h]) cv2.rectangle(vis, (x, y), (x + w, y + h), (255, 255, 0), 1) cv2.imshow("hulls", vis)
从上图能够看出,检测框有不少是重叠的,大框里面有小框,框与框之间有交叉,有些框只是圈出了汉字的偏旁或者某些笔划,而咱们指望是能圈出文字的外边框,这样便于后续的文字识别。为了处理这些不少重叠的大小框,通常会采用NMS方法(Non Maximum Suppression,非极大值抑制),也就是抑制非极大值的元素,即抑制不是最大尺寸的框,至关于去除大框中包含的小框,达到去除重复区域,找到最佳检测位置的目的。
NMS算法的主要流程以下:
通过以上步骤,最后剩下的就是不包含重叠部分的文本检测框了。核心代码以下:
# NMS 方法(Non Maximum Suppression,非极大值抑制) def nms(boxes, overlapThresh): if len(boxes) == 0: return [] if boxes.dtype.kind == "i": boxes = boxes.astype("float") pick = [] # 取四个坐标数组 x1 = boxes[:, 0] y1 = boxes[:, 1] x2 = boxes[:, 2] y2 = boxes[:, 3] # 计算面积数组 area = (x2 - x1 + 1) * (y2 - y1 + 1) # 按得分排序(如没有置信度得分,可按坐标从小到大排序,如右下角坐标) idxs = np.argsort(y2) # 开始遍历,并删除重复的框 while len(idxs) > 0: # 将最右下方的框放入pick数组 last = len(idxs) - 1 i = idxs[last] pick.append(i) # 找剩下的其他框中最大坐标和最小坐标 xx1 = np.maximum(x1[i], x1[idxs[:last]]) yy1 = np.maximum(y1[i], y1[idxs[:last]]) xx2 = np.minimum(x2[i], x2[idxs[:last]]) yy2 = np.minimum(y2[i], y2[idxs[:last]]) # 计算重叠面积占对应框的比例,即 IoU w = np.maximum(0, xx2 - xx1 + 1) h = np.maximum(0, yy2 - yy1 + 1) overlap = (w * h) / area[idxs[:last]] # 若是 IoU 大于指定阈值,则删除 idxs = np.delete(idxs, np.concatenate(([last], np.where(overlap > overlapThresh)[0]))) return boxes[pick].astype("int")
经NMS处理后的检测结果以下图:
从上图能够看出,经MSER+NMS后,已能较好地将文字区域检测、圈出来。
MSER+NMS检测方法在传统的OCR应用中使用普遍,检测速度也很是快,能知足必定的文字识别场景。但当在复杂的天然场景中,特别是有复杂背景的,其检测效果也不尽人意,会将一些无关的因素也检测出来,以下图:
【重点来了】
接下来要介绍的方法,就主要是基于深度学习的AI文字检测法,可应用于复杂的天然场景中。
三、复杂场景:CTPN检测法
CTPN(Detecting Text in Natural Image with Connectionist Text Proposal Network,基于链接预选框网络的文本检测)是基于卷积神经网络和循环神经网络的文本检测方法,其基本作法是生成一系列适当尺寸的文本proposals(预选框)进行文本行的检测,示意图以下,具体的技术原理请见以前的文章(文章:大话文本检测经典模型:CTPN)
CTPN检测法能适应较为复杂的天然场景,是目前深度学习中做文字检测的经常使用方法之一。CTPN的原做者提供该算法的源代码(https://github.com/tianzhi0549/CTPN),是基于caffe深度学习框架的。你们对tensorflow可能会更加熟悉,因而有人在github上提供了tensorflow版本的CTPN程序(https://github.com/eragonruan/text-detection-ctpn),下面介绍如何使用该程序进行文字检测。
(1)下载源代码和模型
① 首先,将tensorflow版本的CTPN程序源代码下载下来,可直接下载成zip压缩包或者git克隆
git clone https://github.com/eragonruan/text-detection-ctpn.git
② 接下来,进行编译安装,执行如下命令
cd utils/bbox chmod +x make.sh ./make.sh
③ 下载预训练好的模型,下载地址为 https://pan.baidu.com/s/1BNHt_9fiqRPGmEXPaxaFXw ,下载后的压缩文件为checkpoints_mlt.zip,新建目录text-detection-ctpn,将解压后将 checkpoints_mlt 文件夹放到text-detection-ctpn 目录中
(2)CTPN文本检测能力测试
将图片放到data/demo目录(默认有自带测试图片,如要检测本身的图片,则将本身的图片放于data/demo目录下),而后执行如下命令,就能使用CTPN进行文字检测
python ./main/demo.py
检测后的结果存放于 data/res 目录中,检测结果由图片和检测框位置、置信度分数信息两种文件组成,以下图所示:
打开文件后,以下图所示,可见已较好地将文字检测出来:
再打开其它图片,可看到检测结果以下,检测效果还不错,以下图:
(3)CTPN文本检测能力封装
经过对main/demo.py的程序稍微进行改造,就能将CTPN检测能力封装后提供给其它程序调用了,核心代码以下:
# 基于 CTPN 的文字检测方法 # 输入:图片 # 返回:文本框位置和置信度分数 def text_detect(image): with tf.get_default_graph().as_default(): # 模型参数定义 input_image = tf.placeholder(tf.float32, shape=[None, None, None, 3], name='input_image') input_im_info = tf.placeholder(tf.float32, shape=[None, 3], name='input_im_info') global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0), trainable=False) bbox_pred, cls_pred, cls_prob = model.model(input_image) variable_averages = tf.train.ExponentialMovingAverage(0.997, global_step) saver = tf.train.Saver(variable_averages.variables_to_restore()) with tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) as sess: # 加载模型 ckpt_state = tf.train.get_checkpoint_state(checkpoint_dir) model_path = os.path.join(checkpoint_dir, os.path.basename(ckpt_state.model_checkpoint_path)) saver.restore(sess, model_path) # 预测文本框位置 img = image h, w, c = img.shape im_info = np.array([h, w, c]).reshape([1, 3]) bbox_pred_val, cls_prob_val = sess.run([bbox_pred, cls_prob], feed_dict={input_image: [img], input_im_info: im_info}) textsegs, _ = proposal_layer(cls_prob_val, bbox_pred_val, im_info) scores = textsegs[:, 0] textsegs = textsegs[:, 1:5] textdetector = TextDetector(DETECT_MODE='H') boxes = textdetector.detect(textsegs, scores[:, np.newaxis], img.shape[:2]) boxes = np.array(boxes, dtype=np.int) return boxes,scores
从以上的检测结果来看,CTPN检测法在复杂的天然场景下具备较好的检测效果。
四、复杂场景:SegLink检测法
虽然CTPN在天然场景下的文字检测效果还不错,但CTPN的检测效果是基于水平方向的,对于非水平的文本检测效果并很差。在天然场景中,有不少的文本信息都是带有必定的旋转、倾斜角度的,例如街道上的广告牌。接下来介绍的SegLink检测法可以实现对旋转文本的多角度检测,该模型主要是对经过Segment(切片)、Link(连接)实现对文本的检测,示意图以下,具体的技术原理请见以前的文章(文章:大话文本检测经典模型:SegLink)
下面介绍如何使用SegLink来检测文本。
(1)下载源代码和模型
① 首先,在github上下载tensorflow版本的SegLink源代码(https://github.com/dengdan/seglink),可直接下载成zip压缩包或者git克隆
git clone https://github.com/dengdan/seglink.git
② 下载pylib,下载路径为https://github.com/dengdan/pylib/tree/f7f5c5503fbb3d9593e6ac3bbf0b8508f53ee1cf ,解压后将src里面的util文件放到pylib目录下面,而后添加到环境变量,在test_seglink.py的前面加上
import sys sys.path.append('/data/PycharmProjects/tensorflow/ocr/seglink/util')
或者在当前窗口执行如下命令,或在 /etc/profile,~/.bashrc 文件中添加如下命令
export PYTHONPATH=xx:$PYTHONPATH
③ 下载预训练好的模型(基于SynthText、IC15数据集),做者提供了两个预训练好的模型seglink-384(基于384x384的图片)、seglink-512(基于512x512的图片),下载地址为 https://pan.baidu.com/s/1slqaYux
④ 安装依赖包
conda install -c cachemeorg setproctitle #或如下命令 #pip install setproctitle
⑤ 若是python是使用了python3的,则须要进行如下修改(使用python 2.x的,请忽略)
⑥ 修改./tf_extended/seglink.py,第808行,opencv3没有cv.BoxPoints() 函数,修改以下:
# points = cv2.cv.BoxPoints(bbox) #opencv2.4.9 points = cv2.boxPoints(bbox) #opencv3.1.0
(2)SegLink检测文本测试(文本框坐标)
经过运行如下命令进行测试
./scripts/test.sh 0 GPU_ID CKPT_PATH DATASET_DIR
该命令由三个参数组成,第1个表示GPU,第2个表示模型路径,第3个表示数据目录。例如咱们使用刚才下载的seglink-384预训练模型,将要检测的图片放到指定的目录后进行测试(可以使用本身的图片,或使用场景文字图片数据集ICDAR2015进行测试,下载地址为http://rrc.cvc.uab.es/?ch=4&com=downloads),那么执行的脚本以下:
./scripts/test.sh 0 ./models/seglink-512/model.ckpt-217867 ./dataset/ICDAR2015 /ch4_test_images
检测后,生成了图片检测出来的文本框位置(8个坐标点),并存放在txt文件中,以下图:
从这些检测的文本框位置结果来看,并不显性化,不知道在图片中的实际检测效果如何。
(3)SegLink检测文本测试(结果显性化)
为了能显性化地展示出文本检测 的图片结果,可经过如下命令进行展现,格式为
python visualize_detection_result.py \ --image=检测的图片所在目录 --det=通过test_seglink.py检测输出的文本框位置坐标 --output=指定将文本框位置绘制到图片上的输出目录
该命令由三个参数组成,第一个表示输入的图像,第二个表示输出检测结果的文本信息,第三个表示输出检测结果的图像
① 在visualize_detection_result.py添加环境变量
import sys sys.path.append('/data/PycharmProjects/tensorflow/ocr/seglink/util')
② 若是python是使用了python3的,则对visualize_detection_result.py第65行,print后面加上括号
对刚才输出的检测结果信息进行可视化展现,调用的命令以下(以ICDAR2015测试图片集为例,如要使用本身的照片,请替换图片目录):
python visualize_detection_result.py \ --image=./dataset/ICDAR2015/ ch4_test_images/ \ --det=./models/seglink-512/model.ckpt-217867/test/icdar2015_test/model.ckpt-217867/seg_link_conf_th_0.800000_0.500000/txt \ --output=./dataset/output
执行后,可看到直接输出了检测后的结果图片,以下图:
打开其它图片,检测效果以下:
从上面的检测结果来看,可较好地检测出天然场景中的文字,特别是其中还有一些带有必定倾斜或旋转角度的文字,也能检测出来。
(4)SegLink文本检测能力封装
为了方便在其它程序中调用SegLink的检测能力,在test_seglink.py, visualize_detection_result.py代码的基础上进行封装改造,就能将SegLink的检测能力进行封装提供给其它程序调用,核心代码以下:
# 基于 SegLink 的文字检测方法 # 输入:图片 # 返回:文本框位置 def text_detect(img): with tf.name_scope('eval'): with tf.variable_scope(tf.get_variable_scope(),reuse=True): # 模型参数 image = tf.placeholder(dtype=tf.int32, shape=[None, None, 3]) image_shape = tf.placeholder(dtype=tf.int32, shape=[3, ]) # 预处理图片 processed_image, _, _, _, _ = ssd_vgg_preprocessing.preprocess_image(image, None, None, None, None, out_shape=config.image_shape, data_format=config.data_format, is_training=False) b_image = tf.expand_dims(processed_image, axis=0) b_shape = tf.expand_dims(image_shape, axis=0) # 预测文本框 net = seglink_symbol.SegLinkNet(inputs=b_image, data_format=config.data_format) bboxes_pred = seglink.tf_seglink_to_bbox(net.seg_scores, net.link_scores, net.seg_offsets, image_shape=b_shape, seg_conf_threshold=config.seg_conf_threshold, link_conf_threshold=config.link_conf_threshold) sess_config = tf.ConfigProto(log_device_placement=False, allow_soft_placement=True) sess_config.gpu_options.allow_growth = True saver = tf.train.Saver() if util.io.is_dir(checkpoint_dir): checkpoint = util.tf.get_latest_ckpt(checkpoint_dir) else: checkpoint = checkpoint_dir with tf.Session(config=sess_config) as sess: # 加载模型 saver.restore(sess, checkpoint) # 预测文本框 image_data = img image_bboxes = sess.run([bboxes_pred], feed_dict={image: image_data, image_shape: image_data.shape}) bboxes = image_bboxes[0] return bboxes
五、复杂场景:EAST检测法
CTPN检测法、SegLink检测法是经过先预测proposals(预选框)、segment(切片),而后再回归、合并等方式实现对文本的检测,中间过程比较冗长。而接下来介绍的EAST检测法,则将中间过程缩减为只有FCN(全卷积网络)、NMS(非极大值抑制)两个阶段,并且输出结果支持文本行、单词的多个角度检测,既高效准确,又能适应多种天然应用场景,以下图所示,具体的技术原理请见以前的文章(文章:大话文本检测经典模型:EAST)
下面介绍如何使用EAST来检测文本。
(1)下载源代码和模型
① 首先在github上下载EAST的源代码(https://github.com/argman/EAST),可直接下载成zip压缩包或者git克隆
git clone https://github.com/argman/EAST.git
② 在百度网盘上下载预先训练好的模型文件(基于ICDAR 201三、ICDAR 2015数据集训练),下载地址为http://pan.baidu.com/s/1jHWDrYQ
③ 安装shapely依赖包,执行如下命令
conda install shapely # 或执行如下命令 # pip install shapely
(2)EAST检测文本测试(demo页面)
进入EAST-master目录,而后执行如下命令,可启动demo页面
python run_demo_server.py –checkpoint_path model/east_icdar2015_resnet_v1_50_rbox/
页面默认会加载输出的结果图片,首次加载时没有结果输出,因此会提示404,这不影响后面的使用。
执行命令后,便可启动web服务,在浏览器中输入http://localhost:8769,打开demo页面,以下图:
点击“选择文件”选择待检测的图片,点击“Submit”提交进行检测,检测后将在页面上返回显示检测后的图片,随机挑选了其中三张图片,检测效果以下图:
做者还很贴心地提供在在线的demo页面,让用户可直接进行体验使用,使用方式跟上面的demo页面同样,网站连接为http://east.zxytim.com/
(3)EAST检测文本测试(批量检测)
可经过命令行调用一批图片批量检测文本,仍是以刚才的ICDAR图片数据集进行检测(若是要检测本身的图片,请替换数据目录),命令以下:
python eval.py –test_data_path=/data/work/tensorflow/model/seglink/ICDAR2015/ch4_test_images/ --checkpoint_path=model/east_icdar2015_resnet_v1_50_rbox/ --output_dir=/tmp/east
执行该命令后,将会批量读取图片进行检测,并输出检测结果,包括图片中检测到的文本框位置、检测结果框住文本后的图片,以下图所示:
从上图也能够看出,EAST也能较好地检测出天然场景的文字,对其中一些带有旋转角度的文字也可准确地检测出来。
(4)EAST文本检测能力封装
为了方便将EAST提供给其它代码调用,经过对eval.py进行修改,封装EAST文本检测的方法,可直接供其它代码调用,代码以下:
# 基于 EAST 的文字检测方法 # 输入:图片 # 返回:文本框位置相关信息 def text_detect(img): # 模型路径 checkpoint_path='/data/PycharmProjects/tensorflow/ocr/east/model/east_icdar2015_resnet_v1_50_rbox/' # 模型参数 input_images = tf.placeholder(tf.float32, shape=[None, None, None, 3], name='input_images') global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0), trainable=False) f_score, f_geometry = model.model(input_images, is_training=False) variable_averages = tf.train.ExponentialMovingAverage(0.997, global_step) saver = tf.train.Saver(variable_averages.variables_to_restore()) sess = tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) # 加载模型 ckpt_state = tf.train.get_checkpoint_state(checkpoint_path) model_path = os.path.join(checkpoint_path, os.path.basename(ckpt_state.model_checkpoint_path)) saver.restore(sess, model_path) # 预测文本框 im_resized, (ratio_h, ratio_w) = resize_image(img) score, geometry = sess.run( [f_score, f_geometry], feed_dict={input_images: [im_resized[:,:,::-1]]}) boxes,_ = detect(score_map=score, geo_map=geometry, timer=collections.OrderedDict([('net', 0),('restore', 0),('nms', 0)])) if boxes is not None: scores = boxes[:,8].reshape(-1) boxes = boxes[:, :8].reshape((-1, 4, 2)) boxes[:, :, 0] /= ratio_w boxes[:, :, 1] /= ratio_h text_lines = [] if boxes is not None: text_lines = [] for box, score in zip(boxes, scores): box = sort_poly(box.astype(np.int32)) if np.linalg.norm(box[0] - box[1]) < 5 or np.linalg.norm(box[3]-box[0]) < 5: continue tl = collections.OrderedDict(zip( ['x0', 'y0', 'x1', 'y1', 'x2', 'y2', 'x3', 'y3'], map(float, box.flatten()))) tl['score'] = float(score) text_lines.append(tl) ret = { 'text_lines': text_lines, } return ret
为方便介绍,以上CTPN、SegLink、EAST的文本检测能力封装时,将加载模型、文本框预测、图片绘制文本框等代码写在一块儿,而在实际生产使用中,通常是将其分开,在后台启动OCR服务能力时预先加载模型,而后提供核心的文本检测、识别能力,而输出结果是否将文本框绘制到图片上,则视具体需求场景而定。在生产环境中,如何更加有效地封装AI能力,可再私信进行交流。
欢迎关注本人的微信公众号“大数据与人工智能Lab”(BigdataAILab),获取 完整源代码
推荐相关阅读
一、AI 实战系列
二、大话深度学习系列
三、AI 杂谈
四、大数据超详细系列