首先

  首先,我想说明的是本文的主要任务在于文本的检测,而非识别。在文本检测中,我们只检测文本周围的边界框。但是,在文本识别中,我们实际上得到了框中所写的实际内容。两者其实是一个递进的关系。

前提准备

  在本文中,主要需要使用到一个基于tensorflow的模型,而此处我们需要基于OpenCV来调用该tensorflow模型,且OpenCV的版本不能过低(我之前实验的时候使用的版本是3.4.2.16,结果会出现问题,建议把版本提升到4以上。

大概流程

  整个流程大概有以下几步:
  (1):网络加载(使用cv2.dnn.readNet函数)
  (2):读取图像(这里需要创建一个斑点块(blob),将图像传输到网络中,此处可以使用cv2.dnn.blobFromImage函数来进行实现。其一共具有5个参数:

blob = cv.dnn.blobFromImage(frame, 1.0, (inpWidth, inpHeight), (123.68, 116.78, 103.94), True, False)
1.原始图像本身。
2.图像中像素值的缩放比,一般不变,所以填1。
3.设定网络参数,默认为320*320,需要在创建blob时指定它,最好和网络输入的大小一致。
4.训练时设定的模型均值,最后的结果需要减去这个设定的值。
5.判定是否需要交换R和B通道。这里必须为True,因为OpenCV使用BGR格式,Tensorflow使用RGB格式。
6.表示是否要裁剪图像并进行中心裁剪,此处并不需要,所以选择False。

  (3):之后我们将输入图像进行前向传播,会得到两个输出,一个是指定文本框的位置,另一个是指定检测到的框的置信度分数。两个输出层分别是:
  ·feature_fusion/concat_3
  ·feature_fusion/Conv_7/Sigmoid
  (4):处理输出。结果出来后会得到很多的矩形框,与物体检测一样,此处我们也需要进行非极大抑制,来避免最后呈现的图像中出现过多的矩形框。非极大抑制结束后,即可输出图像。

代码实现

  这里需要使用到一个模型,我放在我的百度网盘中了,点击前往,提取码为:1234
  最终整个代码部分如下所示:

import cv2
import math


def decode(scores, geometry, scoreThresh):
    detections = []
    confidences = []

    assert len(scores.shape) == 4, "Incorrect dimensions of scores"
    assert len(geometry.shape) == 4, "Incorrect dimensions of geometry"
    assert scores.shape[0] == 1, "Invalid dimensions of scores"
    assert geometry.shape[0] == 1, "Invalid dimensions of geometry"
    assert scores.shape[1] == 1, "Invalid dimensions of scores"
    assert geometry.shape[1] == 5, "Invalid dimensions of geometry"
    assert scores.shape[2] == geometry.shape[2], "Invalid dimensions of scores and geometry"
    assert scores.shape[3] == geometry.shape[3], "Invalid dimensions of scores and geometry"
    height = scores.shape[2]
    width = scores.shape[3]
    for y in range(0, height):
        # 提取数据
        scoresData = scores[0][0][y]
        x0_data = geometry[0][0][y]
        x1_data = geometry[0][1][y]
        x2_data = geometry[0][2][y]
        x3_data = geometry[0][3][y]
        anglesData = geometry[0][4][y]
        for x in range(0, width):
            score = scoresData[x]
            # 若低于阈值,直接跳过
            if(score < scoreThresh):
                continue
            # 否则计算偏移量
            offsetX = x * 4.0
            offsetY = y * 4.0
            angle = anglesData[x]
            # 计算对应角的cos和sin值
            cosA = math.cos(angle)
            sinA = math.sin(angle)
            h = x0_data[x] + x2_data[x]
            w = x1_data[x] + x3_data[x]

            offset = ([offsetX + cosA * x1_data[x] + sinA * x2_data[x], offsetY - sinA * x1_data[x] + cosA * x2_data[x]])
            # 寻找框取矩形的点
            p1 = (-sinA * h + offset[0], -cosA * h + offset[1])
            p3 = (-cosA * w + offset[0],  sinA * w + offset[1])
            center = (0.5*(p1[0]+p3[0]), 0.5*(p1[1]+p3[1]))
            detections.append((center, (w,h), -1*angle * 180.0 / math.pi))
            confidences.append(float(score))

    return [detections, confidences]


if __name__ == "__main__":
    # 参数设定
    confThreshold = 0.5
    nmsThreshold = 0.4
    inpWidth = 320
    inpHeight = 320
    # 模型相对地址
    model = "frozen_east_text_detection.pb"

    # 读取网络
    net = cv2.dnn.readNet(model)

    kWinName = "EAST"
    outputLayers = []
    # 输出层设置
    outputLayers.append("feature_fusion/Conv_7/Sigmoid")
    outputLayers.append("feature_fusion/concat_3")
    # 读取图片
    frame = cv2.imread("4.jpg")

    # 获得图片的宽和长
    height_ = frame.shape[0]
    width_ = frame.shape[1]
    # 获得图片与模型之间的缩放比
    rW = width_ / float(inpWidth)
    rH = height_ / float(inpHeight)

    # 构建图片中所需要的斑点区域(即:一片连续的具有相似属性的像素点集群)
    blob = cv2.dnn.blobFromImage(frame, 1.0, (inpWidth, inpHeight), (123.68, 116.78, 103.94), True, False)

    net.setInput(blob)
    # 前向传播
    output = net.forward(outputLayers)

    scores = output[0]
    geometry = output[1]
    [boxes, confidences] = decode(scores, geometry, confThreshold)
    # 进行非极大抑制
    indices = cv2.dnn.NMSBoxesRotated(boxes, confidences, confThreshold,nmsThreshold)
    for i in indices:
        # 得到旋转矩形的4个边角
        vertices = cv2.boxPoints(boxes[i[0]])
        # 根据比例缩放边界框坐标
        for j in range(4):
            vertices[j][0] *= rW
            vertices[j][1] *= rH
        # 这里由于cv2.line中的参数需要为int值,为此需要进行强制类型转换
        for j in range(4):
            p1 = (int(vertices[j][0]), int(vertices[j][1]))
            print(p1)
            p2 = (int(vertices[(j + 1) % 4][0]), int(vertices[(j + 1) % 4][1]))
            print(p2)
            cv2.line(frame, p1, p2, (0, 255, 0), 2, cv2.LINE_AA)
    # 显示界面
    cv2.imshow("result", frame)
    cv2.waitKey(0)

  最后代码的显示结果如下所示。

  可以看到的是,图片中的文字已经被很好的识别出来了。且整个检测时间也非常的短,具有很高的实时性。

总结

  在本文中,我们主要进行了文字的检测,识别部分在本文中并没有进行叙述,但该部分其实做起来并不是很困难,可以先对文字进行切片,之后使用例如PyTesseract库来进行实现。而在使用PyTesseract库之前,还可以先进行图像对齐,将有倾斜度的文字“掰直”,可能会有更好的效果。