『OCR_recognition』chineseocr


一、Chineseocr 识别流程

step 1: 文字方向检测

  • 包含:VGG16 的一个四分类算法(0,90,180,270),小角度的检测,estimate_skew_angle
  • 该步骤可以跳过,因为算法在一定角度范围内具有鲁棒性

step 2: 文本检测

  1. 用 yolo 检测出含有文本框的区域 text_proposals;
    Note:图像输入固定压缩到 608*608,框的宽度固定为 8,高度 11~283 一共 9 个 anchors,框没框住文字无所谓,主要框的高度与文字的高度 overlap 超过一定的阈值即可定义为正样本
  2. text_proposals 通过文本线构造算法进行同行合并;
    Note:文本线构造法就是左右搜索 3 个框,看高度的 overlap 如果大于一定的阈值就将其连接在一起,当然还有一些细节操作,需要看代码逻辑,总之这一步就是将同一行的 proposals 连接一起
  3. 同一行的框的中心拟合一条直线,然后通过 xmin,xmax 再加上直线参数获取框的四个点(带有旋转性质的),boxes;
  4. 对 boxes 进行 y 排序,相当于将文本行从上到下进行排序;
  5. 根据 boxes 的每一行一个个的输入给 CRNN。

step 3: 文本识别

  • 根据 box 旋转 ROI 区域图像,进入 CNN 前,图像高度压缩到 32,输出大小为(batch,w,512),因为高度固定为 32,经过 CNN 中 5 次下采样之后特征图的高度变为 1,接下来 CNN 的输出要输入到 BLSTM 中,LSTM 输出的维度是 w,也就是对应于原图每 8 个像素就有一个预测,因为横向只下采样了 3 次,也就是相当于每一个 proposal 都有一个预测,这样肯定会有重复的预测,采用 CTC 进行去重生成真实的标签序列

二、Darknet 提取 text_proposals

作者通过对 yolo 进行方法改进提取 text_proposals

step 1: 首先第一个改进,是将宽度限制在 8,其余还是一样,一共九个 achors,输出三层,每层 3 个 anchors 预测,不同于 yolo 原文输入固定 resize 到 416*416,这里是固定在 608*608,分类为 2,判断是文字与否。

# 文字检测引擎 
pwd = os.getcwd()
opencvFlag = 'keras' # keras,opencv,darknet,模型性能 keras>darknet>opencv
IMGSIZE = (608,608)  # yolo3 输入图像尺寸
# keras 版本anchors
keras_anchors = '8,11, 8,16, 8,23, 8,33, 8,48, 8,97, 8,139, 8,198, 8,283'
class_names = ['none','text',]
kerasTextModel=os.path.join(pwd, "models", "text.h5") 	# keras版本模型权重文件

############## darknet yolo  ##############
darknetRoot = os.path.join(os.path.curdir,"darknet") 	# yolo 安装目录
yoloCfg     = os.path.join(pwd,"models","text.cfg")
yoloWeights = os.path.join(pwd,"models","text.weights")
yoloData    = os.path.join(pwd,"models","text.data")

step 2: 接下来根据 anchors 总数,制造后处理过程,yolo 会在三个输出特征图上做预测,将 anchors_num/3=9/3,每层上预测 3 种尺度的 anchors。

def yolo_text(num_classes,anchors,train=False):
    imgInput = Input(shape=(None,None,3))
    darknet = Model(imgInput, darknet_body(imgInput))
    num_anchors = len(anchors)//3
    x, y1 = make_last_layers(darknet.output, 512, num_anchors*(num_classes+5))

    x = compose(DarknetConv2D_BN_Leaky(256, (1,1)),
    			UpSampling2D(2))(x)
    x = Concatenate()([x,darknet.layers[152].output])
    x, y2 = make_last_layers(x, 256, num_anchors*(num_classes+5))

    x = compose(DarknetConv2D_BN_Leaky(128, (1,1)),
    			UpSampling2D(2))(x)
    x = Concatenate()([x,darknet.layers[92].output])
    x, y3 = make_last_layers(x, 128, num_anchors*(num_classes+5))
    
    out = [y1,y2,y3]
    if train:
        num_anchors = len(anchors)
        y_true = [Input(shape=(None, None,num_anchors//3, num_classes+5)) for l in range(3)]
        loss = Lambda(yolo_loss,output_shape=(4,),name='loss',
        			  arguments={
    
    'anchors': anchors,
        			   			 'num_classes': num_classes, 
        			   			 'ignore_thresh': 0.5,})(out+y_true)
        
        def get_loss(loss,index):
            return loss[index]
        
    
        lossName = ['class_loss','xy_loss','wh_loss','confidence_loss']   
        lossList = [Lambda(get_loss,output_shape=(1,),name=lossName[i],arguments={
    
    'index':i})(loss) for i in range(4)]
        textModel = Model([imgInput, *y_true], lossList)
        return textModel
        
    else:
        textModel = Model([imgInput],out)
        return textModel

step 3: 接着,加载预训练模型

textModel.load_weights('models/text.h5')  # 加载预训练模型权重

step 4: 接着,读取并增强数据

trainLoad = data_generator(jpgPath[:num_train], anchors, num_classes,splitW)
testLoad  = data_generator(jpgPath[num_train:], anchors, num_classes,splitW)

step 5: 从 label 当中得到带旋转尺度的框后,按照行宽为 8 分割 boxes

def get_box_spilt(boxes,im,sizeW,SizeH,splitW=8,isRoate=False,rorateDegree=0):
    """ isRoate:是否旋转box """
    size = sizeW,SizeH
    if isRoate:
        # 旋转box
        im,boxes = get_rorate(boxes,im,degree=rorateDegree)
    # 采用 padding 的方式不改变比例的压缩图像
    newIm,f  = letterbox_image(im, size)
    # 图像压缩后。boxes 也要做相应的压缩
    newBoxes = resize_box(boxes,f)
    #按照行 8 分割 box,一直分割覆盖包含最后
    newBoxes = sum(box_split(newBoxes,splitW),[])
    newBoxes = [box+[1] for box in newBoxes]
    return newBoxes,newIm
    
def box_split(boxes,splitW = 15):
    newBoxes = []
    for box in boxes:
        w = box['w']
        h = box['h']
        cx = box['cx']
        cy=box['cy']
        angle = box['angle']
        x1,y1,x2,y2,x3,y3,x4,y4 = xy_rotate_box(cx,cy,w,h,angle)
        splitBoxes =[]
        i = 1
        tanAngle = tan(-angle)
        
        while True:
            flag = 0 if i==1 else 1
            xmin = x1+(i-1)*splitW
            ymin = y1-tanAngle*splitW*i
            xmax = x1+i*splitW
            ymax = y4-(i-1)*tanAngle*splitW +flag*tanAngle*(x4-x1)
            if xmax>max(x2,x3) and xmin>max(x2,x3):
                break
            splitBoxes.append([int(xmin),int(ymin),int(xmax),int(ymax)])
            i+=1
        
        newBoxes.append(splitBoxes)
    return newBoxes

step 6: 确定优化器

adam = tf.keras.optimizers.Adam(lr=0.0005)

step 7: loss 损失设置

def yolo_loss(args, anchors, num_classes, ignore_thresh=.5):
	# 详细看代码
	pass

猜你喜欢

转载自blog.csdn.net/libo1004/article/details/111722863
OCR