1. 程式人生 > >實現text-detection-ctpn一路的坎坎坷坷

實現text-detection-ctpn一路的坎坎坷坷

輸出 oid __name__ eight vid 文字檢測 github rest lock

  小編在學習文字檢測,因為作者提供的caffe實現沒有訓練代碼(不過訓練代碼可以參考faster-rcnn的訓練代碼),所以我打算先使用tensorflow實現,主要是復現前輩的代碼,主要是對文字檢測模型進行訓練。

  代碼的GitHub地址:https://github.com/eragonruan/text-detection-ctpn

  主要寫一下自己實現的過程,因為原文給的步驟,小編沒有完全實現,所以首先打算解讀一下原文步驟,然後加上自己的理解,寫下自己可以實現的步驟。

文本檢測概述

  文本檢測可以看成特殊的目標檢測,但是它有別與通過目標檢測,在通用目標檢測中,每個目標都有定義好的邊界框,檢測出的bbox與當前目標的groundtruth重疊率大於0.5就表示該檢測結果正確,文本檢測中正確檢出需要覆蓋整個文本長度,且評判的標準不同於通用目標檢測,具體的評判方法參見(ICDAR 2017 RobustReading Competition).所以通用的目標檢測方法並不適用文本檢測。

1,參數設置

parameters

there are some parameters you may need to modify according to your requirement, you can find them in ctpn/text.yml

  • USE_GPU_NMS # whether to use nms implemented in cuda or not
  • DETECT_MODE # H represents horizontal mode, O represents oriented mode, default is H
  • checkpoints_path # the model I provided is in checkpoints/, if you train the model by yourself,it will be saved in output/

1.1 對其進行翻譯如下:

  根據我們的一些要求,我們可能需要修改一些參數,文件在ctpn/text.yml

  • USE_GPU_NMS 是否使用在cuda中實現的nms
  • DETECT_MODE H表示水平模式,O表示定向模式,默認為H
  • checkpoints_path 作者提供的模型在checkpoints/ 如果我們自己訓練模型,它將保存在 output/ 下面

自己訓練的模型在這個路徑下面:

 checkpoints_path: output/ctpn_end2end/voc_2007_trainval

下面展示一下小編訓練出來的模型:

技術分享圖片

2:環境設置

setup

  • requirements: python2.7, tensorflow1.3, cython0.24, opencv-python, easydict,(recommend to install Anaconda)
  • if you have a gpu device, build the library by
cd lib / utils 
chmod + x make.sh 
./make.sh

2.1 對其進行翻譯如下:

  需求的是python2.7 tensorflow1.3 cython0.24,opencv-python,easydict,(建議安裝Anaconda)

  (因為我有GPU)所以直接進行第三步,進入lib、utils,執行chmod+x給權限(在給權限之前,make.sh是灰色的(不可執行的文件),執行chmod+x make.sh 則變成綠色(可執行的文件))

技術分享圖片

技術分享圖片

3:準備數據

prepare data

  • First, download the pre-trained model of VGG net and put it in data/pretrain/VGG_imagenet.npy. you can download it from google drive or baidu yun.
  • Second, prepare the training data as referred in paper, or you can download the data I prepared from google drive or baidu yun. Or you can prepare your own data according to the following steps.
  • Modify the path and gt_path in prepare_training_data/split_label.py according to your dataset. And run
cd lib/prepare_training_data
python split_label.py

  • it will generate the prepared data in current folder, and then run
python ToVoc.py

  • to convert the prepared training data into voc format. It will generate a folder named TEXTVOC. move this folder to data/ and then run
cd ../../data
ln -s TEXTVOC VOCdevkit2007

3.1 對其進行翻譯

  •   首先,下載預先訓練的VGG網絡模型並將其放在data/pretrain/VGG_imagenet.npy.
  •   其次,準備論文提到的訓練數據。或者我們可以放置自己的數據
  •   根據我們的數據集修改prepare_training_data/split_label.py中的path和gt_path路徑。並執行下面操作。
cd lib/prepare_training_data
python split_label.py
  •   它將在當前文件夾中生成準備好的數據,然後運行下面代碼:
python ToVoc.py
  • 將準備好的訓練數據轉換為voc格式。它將生成一個名為TEXTVOC的文件夾。將此文件夾移動到數據/然後運行
cd ../../data 
ln -s TEXTVOC VOCdevkit2007

3.2 數據是否只有VOC2007?

  作者給的數據是預處理過的數據,

  我們下載了數據,VOCdevkit2007 只有1.06G,但是此數據可以訓練自己的模式,要是想訓練自己的數據,那麽需要自己標註數據,找自己的數據。

  作者使用的icdar17的multi lingual scene text dataset, 沒有用voc,只是用了他的數據格式,下面給出的數據是作者實現的源數據地址。

  gt_path的數據地址:http://rrc.cvc.uab.es/?com=contestant

技術分享圖片

  進入2017MLT 查看如下:

技術分享圖片

  然後我們可以發送郵件,註冊用戶,並激活,進入下載頁面:

技術分享圖片

  找到數據集並下載,因為這是國外網址,所以被墻了,小編沒有全部下載下來,就走到了這一步,目前沒有下一步(如果有人看到這篇博文,希望把下載的數據能分享給我,先在這裏道聲謝!!!):

技術分享圖片

3.3 存放數據

  作者訓練使用的是6000張圖片。使用train或者trainval是一樣的,因為用的都是這6000張圖片。可以檢查一下VOCdevkit2007/VOC2007/ImageSets/Main下面的train.txt和trainval.txt是否正確,是否是6000張圖片。你在用自己數據訓練的時候也要特別註意一點,數據的標註格式是不是和mlt這個數據集一致,因為split_label這個函數是針對mlt的標註格式來寫的,所以如果你原始數據標註格式如果和它不同,轉換之後可能會是錯的,那麽得到的用來訓練的數據集可能也不對。

  這是作者存放數據的路徑,我們修改路徑,並放數據(因為源數據沒有拿到,所以就數據存放也就做到這一步,沒有後續!!)。

技術分享圖片

對原始gt文件進一步處理的分析(也就是對txt標註數據進行進一步處理),生成對應的xml文件部分內容截圖如下:

技術分享圖片

對split_label的部分代碼截取如下:

for file in files:
    _, basename = os.path.split(file)
    if basename.lower().split(‘.‘)[-1] not in [‘jpg‘, ‘png‘]:
        continue
    stem, ext = os.path.splitext(basename)
    gt_file = os.path.join(gt_path, ‘gt_‘ + stem + ‘.txt‘)
    img_path = os.path.join(path, file)
    print(img_path)
    img = cv.imread(img_path)
    img_size = img.shape
    im_size_min = np.min(img_size[0:2])
    im_size_max = np.max(img_size[0:2])

    im_scale = float(600) / float(im_size_min)
    if np.round(im_scale * im_size_max) > 1200:
        im_scale = float(1200) / float(im_size_max)
    re_im = cv.resize(img, None, None, fx=im_scale, fy=im_scale, interpolation=cv.INTER_LINEAR)
    re_size = re_im.shape
    cv.imwrite(os.path.join(out_path, stem) + ‘.jpg‘, re_im)

    with open(gt_file, ‘r‘) as f:
        lines = f.readlines()
    for line in lines:
        splitted_line = line.strip().lower().split(‘,‘)
        pt_x = np.zeros((4, 1))
        pt_y = np.zeros((4, 1))
        pt_x[0, 0] = int(float(splitted_line[0]) / img_size[1] * re_size[1])
        pt_y[0, 0] = int(float(splitted_line[1]) / img_size[0] * re_size[0])
        pt_x[1, 0] = int(float(splitted_line[2]) / img_size[1] * re_size[1])
        pt_y[1, 0] = int(float(splitted_line[3]) / img_size[0] * re_size[0])
        pt_x[2, 0] = int(float(splitted_line[4]) / img_size[1] * re_size[1])
        pt_y[2, 0] = int(float(splitted_line[5]) / img_size[0] * re_size[0])
        pt_x[3, 0] = int(float(splitted_line[6]) / img_size[1] * re_size[1])
        pt_y[3, 0] = int(float(splitted_line[7]) / img_size[0] * re_size[0])

        ind_x = np.argsort(pt_x, axis=0)
        pt_x = pt_x[ind_x]
        pt_y = pt_y[ind_x]

        if pt_y[0] < pt_y[1]:
            pt1 = (pt_x[0], pt_y[0])
            pt3 = (pt_x[1], pt_y[1])
        else:
            pt1 = (pt_x[1], pt_y[1])
            pt3 = (pt_x[0], pt_y[0])

        if pt_y[2] < pt_y[3]:
            pt2 = (pt_x[2], pt_y[2])
            pt4 = (pt_x[3], pt_y[3])
        else:
            pt2 = (pt_x[3], pt_y[3])
            pt4 = (pt_x[2], pt_y[2])

        xmin = int(min(pt1[0], pt2[0]))
        ymin = int(min(pt1[1], pt2[1]))
        xmax = int(max(pt2[0], pt4[0]))
        ymax = int(max(pt3[1], pt4[1]))

        if xmin < 0:
            xmin = 0
        if xmax > re_size[1] - 1:
            xmax = re_size[1] - 1
        if ymin < 0:
            ymin = 0
        if ymax > re_size[0] - 1:
            ymax = re_size[0] - 1

        width = xmax - xmin
        height = ymax - ymin

        # reimplement
        step = 16.0
        x_left = []
        x_right = []
        x_left.append(xmin)
        x_left_start = int(math.ceil(xmin / 16.0) * 16.0)
        if x_left_start == xmin:
            x_left_start = xmin + 16
        for i in np.arange(x_left_start, xmax, 16):
            x_left.append(i)
        x_left = np.array(x_left)

        x_right.append(x_left_start - 1)
        for i in range(1, len(x_left) - 1):
            x_right.append(x_left[i] + 15)
        x_right.append(xmax)
        x_right = np.array(x_right)

        idx = np.where(x_left == x_right)
        x_left = np.delete(x_left, idx, axis=0)
        x_right = np.delete(x_right, idx, axis=0)

        if not os.path.exists(‘label_tmp‘):
            os.makedirs(‘label_tmp‘)
        with open(os.path.join(‘label_tmp‘, stem) + ‘.txt‘, ‘a‘) as f:
            for i in range(len(x_left)):
                f.writelines("text\t")
                f.writelines(str(int(x_left[i])))
                f.writelines("\t")
                f.writelines(str(int(ymin)))
                f.writelines("\t")
                f.writelines(str(int(x_right[i])))
                f.writelines("\t")
                f.writelines(str(int(ymax)))
                f.writelines("\n")

3.4 參考知乎大神的準備數據如下:

  數據標註

  在標註數據的時候采用的是順時針方向,一次是左上角坐標點,右上角坐標點,右下角坐標點,左下角坐標點(即x1,y1,x2,y2,x3,y3,x4,y4),,這裏的標註方式與通用目標檢測的目標檢測方式一樣,這裏我標註的數據是生成到txt中,具體格式如下:

技術分享圖片

  x1,y1,x2,y2,x3,y3,x4,y4 分別是一個框的四個角點的x,y坐標。這是因為作者用的mlt訓練的,他的數據就是這麽標註的,如果你要用一些水平文本的數據集,標註是x,y,w,h的,也是可以的,修改一下split_label的代碼,或者寫個小腳本把x,y,w,h轉換成x1,y1,x2,y2,x3,y3,x4,y4就行。

  數據處理

  根據ctpn訓練數據的要求,需要對上述數據(txt標註數據)進行進一步的處理,生成對應的xml文件,具體格式參考pascal voc 具體的訓練數據截圖和生成的pascal voc格式如下圖:

技術分享圖片

  處理數據的時候執行下面代碼(和原文一致)

cd lib/prepare_training_data
python split_label.py
python ToVoc.py
cd ../../data
ln -s TEXTVOC VOCdevkit2007

  註意:這裏生成的數據會在當前目錄下,文件夾為TEXTVOC,需要將該文件夾移至/data目錄下,然後再做VOCdevikt2007的軟連接。

3.5 準備數據註意事項

  在原作者使用那6000張圖片的話,roidb和image_index都是6000,因為使用的train和trainval是一樣的,所以我們在使用自己數據訓練的時候也要特別註意一點,數據的標註格式是不是與mlt這個數據集一致,因為split_label這個函數是針對mlt的標註格式來寫的,所以我們原始數據標註格式如果和它不同,轉化之後可能會是錯的,那麽得來的用來訓練的數據集可能也不對。

  cache是為了加速數據讀取,所以不會每次重新生成,更換了數據集需要手動清理。

3.6 訓練數據的格式是什麽樣子,是否需要準備圖片?

  其實想了解自己準備圖片的格式,以及圖片中的文字區域的坐標是否需要手動標出,才能訓練。

  上面也說了訓練數據的格式是x1,y1,x2,y2,x3,y3,x4,y4 ,當然了自己標註比較麻煩,這裏我們可以直接使用一些公開的數據集,原作者使用的額是multi lingual scene texts dataset。

4:訓練

Simplely run

python ./ctpn/train_net.py
  • you can modify some hyper parameters in ctpn/text.yml, or just used the parameters I set.
  • The model I provided in checkpoints is trained on GTX1070 for 50k iters.
  • If you are using cuda nms, it takes about 0.2s per iter. So it will takes about 2.5 hours to finished 50k iterations.

4.1:對其進行翻譯

簡單的運行

  你可以在ctpn/text.yml中修改一些參數,或者只使用作者設置的參數

  作者提供的模型在GTX1070上訓練了50K iters

  如果我們正在使用cuda nms ,它每次約需要0.2秒,因此完成50k叠代需要大約2.5小時

當然,我們可以指定在那塊顯卡上運行,比如我這裏指定選擇第一塊顯卡上訓練,訓練的命令如下:

CUDA_VISIBLE_DEVICES="0" python ./ctpn/train_net.py

4.2 成功運行截圖!!!

技術分享圖片

4.3:執行訓練代碼報的一個錯誤如下

AttributeError: module ‘tensorflow.python.ops.gen_logging_ops‘ has no attribute ‘_image_summary‘

  tensroflow 新版本相較於一些老版本更改了一些函數和變量類型。可以到 \lib\fast_rcnn\train.py 內嘗試把 build_image_summary(self) 函數整體替換為以下語句:

def build_image_summary(self):
    # A simple graph for write image summary
    log_image_data = tf.placeholder(tf.uint8, [None, None, 3])
    log_image_name = tf.placeholder(tf.string)
    from tensorflow.python.ops import gen_logging_ops
    from tensorflow.python.framework import ops as _ops
    log_image = tf.summary.image(str(log_image_name), 
        tf.expand_dims(log_image_data, 0), max_outputs=1)
    _ops.add_to_collection(_ops.GraphKeys.SUMMARIES, log_image)
    return log_image, log_image_data, log_image_name

  也就是把原文中那句替換成下面這句:

log_image = tf.summary.image(str(log_image_name),
 tf.expand_dims(log_image_data, 0), max_outputs=1)

4.4 在訓練時候,訓練集擴展了2倍,目的是什麽?

  在訓練時候,訓練集擴展了2倍,圖片倍翻轉了,這樣做的目的是擴展訓練集。

5:部分代碼解析

5.1 train_net.py的代碼解析

import os.path
import pprint
import sys

#os.getcwd()返回當前工作目錄  sys.path.append()用於將前面的工作目錄添加到搜索路徑中
sys.path.append(os.getcwd())
from lib.fast_rcnn.train import get_training_roidb, train_net
from lib.fast_rcnn.config import cfg_from_file, get_output_dir, get_log_dir
from lib.datasets.factory import get_imdb
from lib.networks.factory import get_network
from lib.fast_rcnn.config import cfg

if __name__ == ‘__main__‘:
    #存放訓練參數
    cfg_from_file(‘ctpn/text.yml‘)
    print(‘Using config:‘)
    # pprint函數的pprint模塊下的方法是一種標準的格式化輸出方式。
    # pprint(object, stream=None, indent=1, width=80, depth=None, *, compact=False)
    # 這裏是將訓練的參數格式化顯示出來
    pprint.pprint(cfg)
    # 讀取VOC中的數據集
    imdb = get_imdb(‘voc_2007_trainval‘)
    print(‘Loaded dataset `{:s}` for training‘.format(imdb.name))
    # 獲得感興趣區域的數據集
    roidb = get_training_roidb(imdb)

    # 返回程序運行結果存放的文件夾的路徑
    output_dir = get_output_dir(imdb, None)
    # 返回程序運行時中間過程產生的文件。
    log_dir = get_log_dir(imdb)
    print(‘Output will be saved to `{:s}`‘.format(output_dir))
    print(‘Logs will be saved to `{:s}`‘.format(log_dir))

    device_name = ‘/gpu:0‘
    print(device_name)

    # 獲取VGG網絡結構 定義網絡結構
    network = get_network(‘VGGnet_train‘)

    train_net(network, imdb, roidb,
              output_dir=output_dir,
              log_dir=log_dir,
              pretrained_model=‘data/pretrain/VGG_imagenet.npy‘,
              max_iters=int(cfg.TRAIN.max_steps),restore=bool(int(cfg.TRAIN.restore)))
#采用VGG_Net 輸入訓練圖片的數據集,感興趣區域的數據集等開始訓練。。

參考文獻:https://zhuanlan.zhihu.com/p/37363942

http://slade-ruan.me/2017/10/22/text-detection-ctpn/

實現text-detection-ctpn一路的坎坎坷坷