SSD: Signle Shot Detector 用于自然场景文字检测
前言
之前我在 論文閱讀:SSD: Single Shot MultiBox Detector 中,講了這個最新的 Object Detection 算法。
既然 SSD 是用來檢測物體的,那么可不可以將 SSD 用來檢測自然場景圖像中的文字呢?答案肯定是可以的~
同時,受到浙大 solace_hyh 同學的 ssd-plate_detection 工作,這篇文章記錄我自己將 SSD 用于文字檢測的過程。
全部的代碼上傳到 Github 了:https://github.com/chenxinpeng/SSD_scene-text-detection,代碼質量不太高,還請高手指點 。^_^
準備與轉換數據集
ICDAR 2011 數據集訓練集共有 229 張圖像,我將其分為 159 張、70張圖像兩部分。前者用作訓練,后者用于訓練時進行測試。
下面就是要將這些圖像,轉換成 lmdb 格式,用于 caffe 訓練;將文字區域的標簽,轉換為 Pascal VOC 的 XML 格式。
將 ground truth 轉換為 Pascal VOC XML 文件
先將 ICDAR 2011 給定的 gt_**.txt 標簽文件轉換為 Pascal VOC XML 格式。
先看下原來的 gt_**.txt 格式,如下圖,有一張原始圖像:
下面是其 ground truth 文件:
158,128,412,182,"Footpath" 442,128,501,170,"To" 393,198,488,240,"and" 63,200,363,242,"Colchester" 71,271,383,313,"Greenstead"- 1
- 2
- 3
- 4
- 5
ground truth 文件格式為:xmin,?ymin,?xmax,?ymax,?label。同時,要注意,這里的坐標系是如下擺放:
將 ground truth 的 txt 文件轉換為 Pascal VOC 的 XML 格式的代碼如下:
#! /usr/bin/pythonimport os, sys import glob from PIL import Image# ICDAR 圖像存儲位置 src_img_dir = "/media/chenxp/Datadisk/ocr_dataset/ICDAR2011/train-textloc" # ICDAR 圖像的 ground truth 的 txt 文件存放位置 src_txt_dir = "/media/chenxp/Datadisk/ocr_dataset/ICDAR2011/train-textloc"img_Lists = glob.glob(src_img_dir + '/*.jpg')img_basenames = [] # e.g. 100.jpg for item in img_Lists:img_basenames.append(os.path.basename(item))img_names = [] # e.g. 100 for item in img_basenames:temp1, temp2 = os.path.splitext(item)img_names.append(temp1)for img in img_names:im = Image.open((src_img_dir + '/' + img + '.jpg'))width, height = im.size# open the crospronding txt filegt = open(src_txt_dir + '/gt_' + img + '.txt').read().splitlines()# write in xml fileos.mknod(src_txt_dir + '/' + img + '.xml')xml_file = open((src_txt_dir + '/' + img + '.xml'), 'w')xml_file.write('<annotation>\n')xml_file.write(' <folder>VOC2007</folder>\n')xml_file.write(' <filename>' + str(img) + '.jpg' + '</filename>\n')xml_file.write(' <size>\n')xml_file.write(' <width>' + str(width) + '</width>\n')xml_file.write(' <height>' + str(height) + '</height>\n')xml_file.write(' <depth>3</depth>\n')xml_file.write(' </size>\n')# write the region of text on xml filefor img_each_label in gt:spt = img_each_label.split(',')xml_file.write(' <object>\n')xml_file.write(' <name>text</name>\n')xml_file.write(' <pose>Unspecified</pose>\n')xml_file.write(' <truncated>0</truncated>\n')xml_file.write(' <difficult>0</difficult>\n')xml_file.write(' <bndbox>\n')xml_file.write(' <xmin>' + str(spt[0]) + '</xmin>\n')xml_file.write(' <ymin>' + str(spt[1]) + '</ymin>\n')xml_file.write(' <xmax>' + str(spt[2]) + '</xmax>\n')xml_file.write(' <ymax>' + str(spt[3]) + '</ymax>\n')xml_file.write(' </bndbox>\n')xml_file.write(' </object>\n')xml_file.write('</annotation>')- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
x上面代碼運行結果是得到如下的 XML 文件,同樣用上面的 100.jpg 圖像示例,其轉換結果如下:
<annotation><folder>VOC2007</folder><filename>100.jpg</filename><size><width>640</width><height>480</height><depth>3</depth></size><object>...... </annotation>- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
上面代碼生成的 XML 文件,與圖像文件存儲在一個地方。
生成訓練圖像與 XML 標簽的位置文件
這一步,按照 SSD 訓練的需求,將圖像位置,及其對應的 XML 文件位置寫入一個 txt 文件,供訓練時讀取,一個文件名稱叫做:trainval.txt 文件,另一個叫做:test.txt 文件。形式如下:
scenetext/JPEGImages/106.jpg scenetext/Annotations/106.xml scenetext/JPEGImages/203.jpg scenetext/Annotations/203.xml scenetext/JPEGImages/258.jpg scenetext/Annotations/258.xml scenetext/JPEGImages/122.jpg scenetext/Annotations/122.xml scenetext/JPEGImages/103.jpg scenetext/Annotations/103.xml scenetext/JPEGImages/213.jpg scenetext/Annotations/213.xml scenetext/JPEGImages/149.jpg scenetext/Annotations/149.xml ......- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
生成的代碼如下:
#! /usr/bin/pythonimport os, sys import globtrainval_dir = "/home/chenxp/data/VOCdevkit/scenetext/trainval" test_dir = "/home/chenxp/data/VOCdevkit/scenetext/test"trainval_img_lists = glob.glob(trainval_dir + '/*.jpg') trainval_img_names = [] for item in trainval_img_lists:temp1, temp2 = os.path.splitext(os.path.basename(item))trainval_img_names.append(temp1)test_img_lists = glob.glob(test_dir + '/*.jpg') test_img_names = [] for item in test_img_lists:temp1, temp2 = os.path.splitext(os.path.basename(item))test_img_names.append(temp1)dist_img_dir = "scenetext/JPEGImages" dist_anno_dir = "scenetext/Annotations"trainval_fd = open("/home/chenxp/caffe/data/scenetext/trainval.txt", 'w') test_fd = open("/home/chenxp/caffe/data/scenetext/test.txt", 'w')for item in trainval_img_names:trainval_fd.write(dist_img_dir + '/' + str(item) + '.jpg' + ' ' + dist_anno_dir + '/' + str(item) + '.xml\n')for item in test_img_names:test_fd.write(dist_img_dir + '/' + str(item) + '.jpg' + ' ' + dist_anno_dir + '/' + str(item) + '.xml\n')- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
生成 test name size 文本文件
這一步,SSD 還需要一個名叫:test_name_size.txt 的文件,里面記錄訓練圖像、測試圖像的圖像名稱、height、width。內容形式如下:
106 480 640 203 480 640 258 480 640 318 480 640 122 480 640 103 480 640 320 640 480 ......- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
生成這個文本文件的代碼如下:
#! /usr/bin/pythonimport os, sys import glob from PIL import Imageimg_dir = "/home/chenxp/data/VOCdevkit/scenetext/JPEGImages"img_lists = glob.glob(img_dir + '/*.jpg')test_name_size = open('/home/chenxp/caffe/data/scenetext/test_name_size.txt', 'w')for item in img_lists:img = Image.open(item)width, height = img.sizetemp1, temp2 = os.path.splitext(os.path.basename(item))test_name_size.write(temp1 + ' ' + str(height) + ' ' + str(width) + '\n')- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
準備標簽映射文件 labelmap
這個 prototxt 文件是記錄 label 與 name 之間的對應關系的,內容如下:
item {name: "none_of_the_above"label: 0display_name: "background" } item {name: "object"label: 1display_name: "text" }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
我的 prototxt 文件名稱,被我重命名為:labelmap_voc.prototxt
生成 lmdb 數據庫
準備好上述的幾個文本文件,將其放置在如下位置:
/home/chenxp/caffe/data/scenetext- 1
這時候,需要修改調用 SSD 源碼中提供的 create_data.sh 腳本文件(我將文件重命名為:create_data_scenetext.sh):
cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd ) root_dir=$cur_dir/../..cd $root_dirredo=1 data_root_dir="$HOME/data/VOCdevkit" dataset_name="scenetext" mapfile="$root_dir/data/$dataset_name/labelmap_voc_scenetext.prototxt" anno_type="detection" db="lmdb" min_dim=0 max_dim=0 width=0 height=0extra_cmd="--encode-type=jpg --encoded" if [ $redo ] thenextra_cmd="$extra_cmd --redo" fi for subset in test trainval dopython $root_dir/scripts/create_annoset.py --anno-type=$anno_type --label-map-file=$mapfile \--min-dim=$min_dim --max-dim=$max_dim --resize-width=$width --resize-height=$height \--check-label $extra_cmd $data_root_dir $root_dir/data/$dataset_name/$subset.txt \$data_root_dir/$dataset_name/$db/$dataset_name"_"$subset"_"$db examples/$dataset_name done- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
上面的 bash 腳本會自動將訓練的 ICDAR 2011 的圖像文件與對應 label 轉換為 lmdb 文件。轉換后的文件位置可參見上面腳本的內容,我的位置為:
/home/chenxp/caffe/examples/scenetext_trainval_lmdb /home/chenxp/caffe/examples/scenetext_test_lmdb- 1
- 2
訓練模型
將 SSD 用于自己的檢測任務,是需要 Fine-tuning a pretrained network 的。
具體的,需要加載 SSD 作者提供的 VGG_ILSVRC_16_layers_fc_reduced.caffemodel,在這個預訓練的模型上,繼續用我們的數據訓練。
下載下來后,放在如下位置下面:
/home/chenxp/caffe/models/VGGNet- 1
之后,修改作者提供的訓練 Python 代碼:ssd_pascal.py,這份代碼會自動創建訓練所需要的如下幾個文件:
- deploy.prototxt
- solver.prototxt
- trainval.prototxt
- test.prototxt
我們需要按照自己的情況,修改如下幾處地方:
# Modify the job name if you want. job_name = "SSD_{}".format(resize) # The name of the model. Modify it if you want. model_name = "VGG_VOC0712_{}".format(job_name)# Directory which stores the model .prototxt file. save_dir = "models/VGGNet/VOC0712/{}".format(job_name) # Directory which stores the snapshot of models. snapshot_dir = "models/VGGNet/VOC0712/{}".format(job_name) # Directory which stores the job script and log file. job_dir = "jobs/VGGNet/VOC0712/{}".format(job_name) # Directory which stores the detection results. output_result_dir = "{}/data/VOCdevkit/results/VOC2007/{}/Main".format(os.environ['HOME'], job_name)# model definition files. train_net_file = "{}/train.prototxt".format(save_dir) test_net_file = "{}/test.prototxt".format(save_dir) deploy_net_file = "{}/deploy.prototxt".format(save_dir) solver_file = "{}/solver.prototxt".format(save_dir) # snapshot prefix. snapshot_prefix = "{}/{}".format(snapshot_dir, model_name) # job script path. job_file = "{}/{}.sh".format(job_dir, model_name)# Stores the test image names and sizes. Created by data/VOC0712/create_list.sh name_size_file = "data/VOC0712/test_name_size.txt" # The pretrained model. We use the Fully convolutional reduced (atrous) VGGNet. pretrain_model = "models/VGGNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel" # Stores LabelMapItem. label_map_file = "data/VOC0712/labelmap_voc.prototxt"num_classes = 21num_test_image = 4952- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
我的訓練參數
其實還需要修改一些,如訓練時的參數。因為一開始若直接用作者 ssd_pascal.py 文件中的默認的 solver.prototxt 參數,會出現如下情況:
跑著跑著,loss 就變成 nan 了,發散了,不收斂。
我調試了一段時間,我的 solver.prototxt 參數設置如下,可保證收斂:
base_lr: 0.0001- 1
其余參數可看自己設置。學習率一定要小,原先的 0.001 就會發散。
訓練結束:
可以看見,最后的測試精度為 0.776573,感覺 SSD 效果還可以。
我自己訓練好的模型,上傳到云端了:鏈接:http://share.weiyun.com/1c544de66be06ea04774fd11e820a780 (密碼:ERid5Y)
這個需要在下一階段的測試中用到。
用訓練好的 model 進行 predict
SSD 的作者也給我們寫好了 predict 的代碼,我們只需要該參數就可以了。
用 jupyter notebook 打開 ~/caffe/examples/ssd_detect.ipynb 文件,這是作者為我們寫好的將訓練好的 caffemodel 用于檢測的文件。
指定好 caffemodel,deploy.txt,詳細的看我上傳的代碼吧。
測試幾張圖像,結果如下:
參考
總結
以上是生活随笔為你收集整理的SSD: Signle Shot Detector 用于自然场景文字检测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SSD 安装、训练、测试(ubuntu1
- 下一篇: OpenCV学习笔记(一)——OpenC