自然場(chǎng)景下的文本檢測(cè)和識(shí)別 EAST text detector and recognition

自然場(chǎng)景下的文本檢測(cè)和識(shí)別 EAST text detector and recognition

最近在做巡檢機(jī)器人和儀表識(shí)別算法,巡檢機(jī)器人拍攝的照片除了指針儀表和狀態(tài)燈以外,還有一部分是數(shù)字顯示的儀表,這樣對(duì)儀表的數(shù)值的識(shí)別就需要后臺(tái)代碼具備檢測(cè)文本和識(shí)別的功能了.
另外,一些項(xiàng)目中也有對(duì)移動(dòng)的車(chē)廂或者罐子上的編號(hào)做識(shí)別處理,這樣一套算法就可以搞定這些問(wèn)題了.

儀表面板

鐵罐編號(hào)1

鐵罐編號(hào)2

1. EAST text detector 模型

自然場(chǎng)景下的文本檢測(cè)模型,參考了 Zhou et al.的在arxiv上的論文方法. 論文鏈接

  • 使用ResNet-50殘差網(wǎng)絡(luò)作為基礎(chǔ).
  • 使用dice loss 損失函數(shù).
  • 使用了 AdamW 優(yōu)化器.
模型定義如下
import keras
from keras import layers, Input, Model
import tensorflow as tf
from east.layers.base_net import resnet50
from east.layers.losses import balanced_cross_entropy, iou_loss, angle_loss
from east.layers.rbox import dist_to_box


def merge_block(f_pre, f_cur, out_channels, index):
    """
    east網(wǎng)絡(luò)特征合并塊
    :param f_pre:
    :param f_cur:
    :param out_channels:輸出通道數(shù)
    :param index:block index
    :return:
    """
    # 上采樣
    up_sample = layers.UpSampling2D(size=2, name="east_up_sample_f{}".format(index - 1))(f_pre)
    # 合并
    merge = layers.Concatenate(name='east_merge_{}'.format(index))([up_sample, f_cur])
    # 1*1 降維
    x = layers.Conv2D(out_channels, (1, 1), padding='same', name='east_reduce_channel_conv_{}'.format(index))(merge)
    x = layers.BatchNormalization(name='east_reduce_channel_bn_{}'.format(index))(x)
    x = layers.Activation(activation='relu', name='east_reduce_channel_relu_{}'.format(index))(x)
    # 3*3 提取特征
    x = layers.Conv2D(out_channels, (3, 3), padding='same', name='east_extract_feature_conv_{}'.format(index))(x)
    x = layers.BatchNormalization(name='east_extract_feature_bn_{}'.format(index))(x)
    x = layers.Activation(activation='relu', name='east_extract_feature_relu_{}'.format(index))(x)
    return x


def east(features):
    """
    east網(wǎng)絡(luò)頭
    :param features: 特征列表: f1, f2, f3, f4分別代表32,16,8,4倍下采樣的特征
    :return:
    """
    f1, f2, f3, f4 = features
    # 特征合并分支
    h2 = merge_block(f1, f2, 128, 2)
    h3 = merge_block(h2, f3, 64, 3)
    h4 = merge_block(h3, f4, 32, 4)
    # 提取g4特征
    x = layers.Conv2D(32, (3, 3), padding='same', name='east_g4_conv')(h4)
    x = layers.BatchNormalization(name='east_g4_bn')(x)
    x = layers.Activation(activation='relu', name='east_g4_relu')(x)

    # 預(yù)測(cè)得分
    predict_score = layers.Conv2D(1, (1, 1), name='predict_score_map')(x)
    # 預(yù)測(cè)距離
    predict_geo_dist = layers.Conv2D(4, (1, 1), activation='relu', name='predict_geo_dist')(x)  # 距離必須大于零
    # 預(yù)測(cè)角度
    predict_geo_angle = layers.Conv2D(1, (1, 1), name='predict_geo_angle')(x)

    return predict_score, predict_geo_dist, predict_geo_angle


def east_net(config, stage='train'):
    # 輸入
    h, w = list(config.IMAGE_SHAPE)[:2]
    h, w = h / 4, w / 4
    input_image = Input(shape=config.IMAGE_SHAPE, name='input_image')
    input_score_map = Input(shape=(h, w), name='input_score')
    input_geo_dist = Input(shape=(h, w, 4), name='input_geo_dist')  # rbox 4個(gè)邊距離
    input_geo_angle = Input(shape=(h, w), name='input_geo_angle')  # rbox 角度
    input_mask = Input(shape=(h, w), name='input_mask')
    input_image_meta = Input(shape=(12,), name='input_image_meta')

    # 網(wǎng)絡(luò)
    features = resnet50(input_image)
    predict_score, predict_geo_dist, predict_geo_angle = east(features)

    if stage == 'train':
        # 增加損失函數(shù)層
        score_loss = layers.Lambda(lambda x: balanced_cross_entropy(*x), name='score_loss')(
            [input_score_map, predict_score, input_mask])
        geo_dist_loss = layers.Lambda(lambda x: iou_loss(*x), name='dist_loss')(
            [input_geo_dist, predict_geo_dist, input_score_map, input_mask])
        geo_angle_loss = layers.Lambda(lambda x: angle_loss(*x), name='angle_loss')(
            [input_geo_angle, predict_geo_angle, input_score_map, input_mask])

        return Model(inputs=[input_image, input_score_map, input_geo_dist, input_geo_angle, input_mask],
                     outputs=[score_loss, geo_dist_loss, geo_angle_loss])
    else:
        # 距離和角度轉(zhuǎn)為頂點(diǎn)坐標(biāo)
        vertex = layers.Lambda(lambda x: dist_to_box(*x))([predict_geo_dist, predict_geo_angle])
        # dual image_meta
        image_meta = layers.Lambda(lambda x: tf.identity(x))(input_image_meta)  # 原樣返回
        predict_score = layers.Lambda(lambda x: tf.nn.sigmoid(x))(predict_score)  # logit轉(zhuǎn)為score
        return Model(inputs=[input_image, input_image_meta],
                     outputs=[predict_score, vertex, image_meta])


def compile(keras_model, config, loss_names=[]):
    """
    編譯模型,增加損失函數(shù)幔亥,L2正則化以
    :param keras_model:
    :param config:
    :param loss_names: 損失函數(shù)列表
    :return:
    """
    # 優(yōu)化目標(biāo)
    optimizer = keras.optimizers.SGD(
        lr=config.LEARNING_RATE, momentum=config.LEARNING_MOMENTUM,
        clipnorm=config.GRADIENT_CLIP_NORM)
    # 增加損失函數(shù)开缎,首先清除之前的宋列,防止重復(fù)
    keras_model._losses = []
    keras_model._per_input_losses = {}

    for name in loss_names:
        layer = keras_model.get_layer(name)
        if layer is None or layer.output in keras_model.losses:
            continue
        loss = (tf.reduce_mean(layer.output, keepdims=True)
                * config.LOSS_WEIGHTS.get(name, 1.))
        keras_model.add_loss(loss)

    # 增加L2正則化
    # 跳過(guò)批標(biāo)準(zhǔn)化層的 gamma 和 beta 權(quán)重
    reg_losses = [
        keras.regularizers.l2(config.WEIGHT_DECAY)(w) / tf.cast(tf.size(w), tf.float32)
        for w in keras_model.trainable_weights
        if 'gamma' not in w.name and 'beta' not in w.name]
    keras_model.add_loss(tf.add_n(reg_losses))

    # 編譯
    keras_model.compile(
        optimizer=optimizer,
        loss=[None] * len(keras_model.outputs))  # 使用虛擬損失

    # 為每個(gè)損失函數(shù)增加度量
    for name in loss_names:
        if name in keras_model.metrics_names:
            continue
        layer = keras_model.get_layer(name)
        if layer is None:
            continue
        keras_model.metrics_names.append(name)
        loss = (
                tf.reduce_mean(layer.output, keepdims=True)
                * config.LOSS_WEIGHTS.get(name, 1.))
        keras_model.metrics_tensors.append(loss)


def add_metrics(keras_model, metric_name_list, metric_tensor_list):
    """
    增加度量
    :param keras_model: 模型
    :param metric_name_list: 度量名稱(chēng)列表
    :param metric_tensor_list: 度量張量列表
    :return: 無(wú)
    """
    for name, tensor in zip(metric_name_list, metric_tensor_list):
        keras_model.metrics_names.append(name)
        keras_model.metrics_tensors.append(tf.reduce_mean(tensor, keepdims=True))

2. 文本識(shí)別

EAST text detector實(shí)現(xiàn)了文本定位和檢測(cè),下一步需要對(duì)檢測(cè)的文本做識(shí)別處理

將圖像中的文字轉(zhuǎn)化為真正的文本踩晶,就需要用到OCR的技術(shù)攘蔽。OCR領(lǐng)域最著名的孵稽、最主流的開(kāi)源實(shí)現(xiàn)是Tesseract-OCR句狼,鑒于本次識(shí)別的都是印刷體和簡(jiǎn)單的數(shù)字,直接采用google成熟的OCR識(shí)別工具集tesseract-ocr就可以了,尤其是當(dāng)Tesseract-OCR已經(jīng)升級(jí)到了4.0版本相速。和傳統(tǒng)的版本(3.x)比,4.0時(shí)代最突出的變化就是基于LSTM神經(jīng)網(wǎng)絡(luò)鲜锚。

3. 整合成端到端的代碼 end to end

把EAST text detector 和 tesseract-ocr整合到一套代碼中實(shí)現(xiàn)端到端的解決方案,實(shí)現(xiàn)圖片的文字檢測(cè),分割和識(shí)別輸出的一系列操作.

儀表面板

鐵罐編號(hào)1

鐵罐編號(hào)2
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末突诬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芜繁,更是在濱河造成了極大的恐慌旺隙,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骏令,死亡現(xiàn)場(chǎng)離奇詭異蔬捷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)榔袋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)周拐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人凰兑,你說(shuō)我怎么就攤上這事妥粟。” “怎么了吏够?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵勾给,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我锅知,道長(zhǎng)播急,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任售睹,我火速辦了婚禮桩警,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昌妹。我一直安慰自己捶枢,他們只是感情好沉噩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著柱蟀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蚜厉。 梳的紋絲不亂的頭發(fā)上长已,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音昼牛,去河邊找鬼术瓮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛贰健,可吹牛的內(nèi)容都是我干的胞四。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼伶椿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辜伟!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起脊另,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤导狡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后偎痛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體旱捧,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年踩麦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枚赡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谓谦,死狀恐怖贫橙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情反粥,我是刑警寧澤料皇,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站星压,受9級(jí)特大地震影響践剂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娜膘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一逊脯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竣贪,春花似錦军洼、人聲如沸巩螃。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)避乏。三九已至,卻和暖如春甘桑,著一層夾襖步出監(jiān)牢的瞬間拍皮,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工跑杭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留铆帽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓德谅,卻偏偏與公主長(zhǎng)得像爹橱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窄做,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容