1 项目介绍
1.1 项目功能
(1)项目功能:英文手写识别,如输入数据为手写英文作文扫描图片,技术:OCR技术
(2)应用场景:
- 高考等应试教育英语作文电子阅卷
- 英文手写电子笔记的上传
1.2 评估指标
(1)模型评估指标:动态规划实现的字符串相似度算法,公式如下
2 数据集介绍
2.1 数据特征
数据集中存在的问题和难点
(1)数据集数量不够大,
(2)扫描得到的图像倾斜,文字区域无法定位,或文字区域无法精准定位
(3)图片中有很多噪声信息,如下划线
(4)图片中手写英文存在很多连笔,涂改等。
3 数据的预处理
3.1 数据增强
数据集的预处理工作,为提升模型的性能,做了数据预处理工作。
(1)图像旋转
(2)图像缩放
(3)对图像添加噪声
(4)对图像进行模糊
(5)将图像往x、y方向上按指定的数量移动图像像素
3.2 倾斜矫正
扫描版或拍着图片会存在图像倾斜的情况,将大大降低识别效果,因此需要对图像进行倾斜矫正预处理。
原理:
(1)首先对图像进行边缘轮廓检测
(2)对边缘轮廓检测后的图片进行霍夫曼倾斜矫正
霍夫曼倾斜矫正原理:通过识别图像中的直线,检测直线倾斜角度和直线的位置信息对图像进行旋转,实测效果佳,且边缘轮廓检测对霍夫曼倾斜矫正起到很好的辅助作用。
3.3 去横线
图像中的横线对文字识别有一定的影响,因此需要在识别前对图像进行横线去除工作,去除横线方法调用Ieptonical库
原理:
(1)首先旋转图片进行倾斜矫正,使得横线变水平,然后提取出水平横线调用函数班背景去掉,只留下横线
(2)接着将横线进行阈值处理,高于阈值的横线加黑,低于阈值的变白,将处理图片上的黑色横线翻转为白色
(3)步骤2原图的横线被去掉,但原图人物身体的部分也被擦除
(4)此时调用相关函数使横线图片与人物擦除的图片想结合,补出擦除的部分,得到较好的去横线的效果。
3.4 文本区域定位
英文行全页面自动定位算法,文本区域定位,在输入神经网络模型前需要做文本区域定位,基于MSER算法进行改进。
算法原理:MSER算法产生的局部文字区域杂乱,对MSER产生的边框又进行了下面的四步筛选,大大提升了问题区域定位的效果
(1)首先根据矩形的大小,将过大或过小的矩形筛除掉
(2)将大矩形和小矩形如果交叠部分大于设定的阈值,将小矩形筛除掉
(3)此步特殊之处在于并不筛除掉矩阵,而是按照规则取min_left_top_x min_left_y max_right_bottom_x max_right_bottom_y 将一个类的矩阵合并成一个大的矩形
(4)按照矩形边框的height > min_height weight > min_weight筛选出最后的边框。
4 网络结构
(1)神经网络结构:四层卷积层+四层池化层
(2)神经网络使用的是:双向LSTM
(3)结构分析
该神经网络使用的是双向递归神经网络tf.nn.bidirectional_dynamic_rnn()
双向的RNN,当cell使用LSTM时,便是双向LSTMD。单向的RNN只考虑上文的信息对下文信息的影响,双向RNN即考虑当前信息不仅受到上文的影响,同时也考虑下文的影响。
前向RNN和dynamic_rnn完全一致,后向RNN输入的序列经过了反转。
(4)优化算法
本神经网络使用的参数优化算法:AdamOptimizer。除了该算法还有Momentum优化算法
- Momentum优化算法计算梯度的指数加权平均,加快迭代速度
- Adam算法集成了momentum动量梯度下降法和RMSprop梯度下降法的优点
(5)损失函数
CTC损失函数(connectionist temporal classification)
CTC在神经网络中计算一种损失值,主要用于可以对没有对齐的数据进行自动补齐,即主要是用在没有事先对齐的序列化数据训练上,应用领域如:语音识别、OCR识别
(6)池化
池化过程使用最大池化max_pool.
原因:虽然最大池化和平均池化都对数据进行了下采样,但是最大池化做特征选择,选出了分类识别度更好的特征
- 最大池化:可以降低卷积层参数误差造成估计均值的偏移,更多的保留纹理信息
- 最大池化提供了非线性,这是最大池化效果更好的原因
(7)使用Dropout
使用Dropout简化训练的网络结构,控制过拟合出现的风险,并通过调整,得到了一个比较合适的dropout参数
(8)激活函数
使用relu函数。可以降低网络参数训练过程中梯度消失或者梯度爆炸的风险
(9)断点续训
保证函数在训练中断后继续进行训练。
5 OCR实现
ocr_generated.py
import os
import glob
import random
import numpy as np
from PIL import Image
from PIL import ImageFilter
#记录一个问题: tf.placeholder 报错InvalidArgumentError: You must feed a value for placeholder tensor 'inputs/x_input'
#chr函数: 将数字转化成字符
#ord函数: 将字符转化成数字
#characterNo字典:a-z, A-Z, 0-10, " .,?\'-:;!/\"<>&(+" 为key分别对应值是0-25,26-51,52-61,62...
#characters列表: 存储的是cahracterNo字典的key
#建立characterNo字典的意思是: 为了将之后手写体对应的txt文件中的句子转化成 数字编码便于存储和运算求距离
charactersNo={}
characters=[]
length=[]
for i in range(26):
charactersNo[chr(ord('a')+i)]=i
characters.append(chr(ord('a')+i))
for i in range(26):
charactersNo[chr(ord('A')+i)]=i+26
characters.append(chr(ord('A')+i))
for i in range(10):
charactersNo[chr(ord('0')+i)]=i+52
characters.append(chr(ord('0')+i))
punctuations=" .,?\'-:;!/\"<>&(+"
for p in punctuations:
charactersNo[p]=len(charactersNo)
characters.append(p)
def get_data():
#读取了train_img和train_txt文件夹下的所有文件的读取路径
#下面代码的作用是:
#Imgs:列表结构 存储的是手写的英文图片
#Y: 数组结构 存储的是图片对应的txt文件中句子,只不过存储的是字符转码后的数字
#length: 数组结构 存储的是图片对应的txt文件中句子含有字符的数量
imgFiles=glob.glob(os.path.join("train_img", "*"))
imgFiles.sort()
txtFiles=glob.glob(os.path.join("train_txt", "*"))
txtFiles.sort()
Imgs=[]
Y=[]
length=[]
for i in range(len(imgFiles)):
fin=open(txtFiles[i])
line=fin.readlines()
line=line[0]
fin.close()
y=np.asarray([0]*(len(line)))
succ=True
for j in range(len(line)):
if line[j] not in charactersNo:
succ=False
break
y[j]=charactersNo[line[j]]
if not succ:
continue
Y.append(y)
length.append(len(line))
im = Image.open(imgFiles[i])
width,height = im.size#1499,1386
im = im.convert("L")
Imgs.append(im)
#np.asarray()函数 和 np.array()函数: 将list等结构转化成数组
#区别是np.asarray()函数不是copy对象,而np.array()函数是copy对象
print("train:",len(Imgs),len(Y))
Y = np.asarray(Y)
length = np.asarray(length)
return Imgs, Y
ocr_forward.py
import tensorflow as tf
import os
import glob
import random
import numpy as np
from PIL import Image
from PIL import ImageFilter
import ocr_generated
conv1_filter=32
conv2_filter=64
conv3_filter=128
conv4_filter=256
def get_weight(shape, regularizer):
#参数w初始化,并且对w进行正则化处理,防止模型过拟合
w = tf.Variable(tf.truncated_normal((shape), stddev=0.1, dtype=tf.float32))
if regularizer != None: tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
return w
def get_bias(shape):
#参数b初始化
b = tf.Variable(tf.constant(0., shape=shape, dtype=tf.float32))
return b
def conv2d(x,w):
#卷积层函数tf.nn.conv2d
return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x, kernel_size):
#池化层函数,在池化层采用最大池化,有效的提取特征
return tf.nn.max_pool(x, ksize=kernel_size, strides=kernel_size, padding='VALID')
def forward(x, train, regularizer):
#前向传播中共使用了四层神经网络
#第一层卷积层和池化层实现
conv1_w = get_weight([3, 3, 1, conv1_filter], regularizer)
conv1_b = get_bias([conv1_filter])
conv1 = conv2d(x, conv1_w)
relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b))
pool1 = max_pool_2x2(relu1, [1,2,2,1])
#通过keep_prob参数控制drop_out函数对神经元的筛选
if train:
keep_prob = 0.6 #防止过拟合
else:
keep_prob = 1.0
#第二层卷积层和池化层实现
conv2_w = get_weight([5, 5, conv1_filter, conv2_filter], regularizer)
conv2_b = get_bias([conv2_filter])
conv2 = conv2d(tf.nn.dropout(pool1, keep_prob), conv2_w)
relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_b))
pool2 = max_pool_2x2(relu2, [1,2,1,1])
#第三层卷积层和池化层
conv3_w = get_weight([5, 5, conv2_filter, conv3_filter], regularizer)
conv3_b = get_bias([conv3_filter])
conv3 = conv2d(tf.nn.dropout(pool2, keep_prob), conv3_w)
relu3 = tf.nn.relu(tf.nn.bias_add(conv3, conv3_b))
pool3 = max_pool_2x2(relu3, [1,4,2,1])
#第四层卷积层和池化层
conv4_w = get_weight([5, 5, conv3_filter, conv4_filter], regularizer)
conv4_b = get_bias([conv4_filter])
conv4 = conv2d(tf.nn.dropout(pool3, keep_prob), conv4_w)
relu4 = tf.nn.relu(tf.nn.bias_add(conv4, conv4_b))
pool4 = max_pool_2x2(relu4, [1,7,1,1])
rnn_inputs=tf.reshape(tf.nn.dropout(pool4,keep_prob),[-1,256,conv4_filter])
num_hidden=512
num_classes=len(ocr_generated.charactersNo)+1
W = tf.Variable(tf.truncated_normal([num_hidden,num_classes],stddev=0.1), name="W")
b = tf.Variable(tf.constant(0., shape=[num_classes]), name="b")
#前向传播、反向传播,利用双向LSTM长时记忆循环网络
#seq_len = tf.placeholder(tf.int32, shape=[None])
#labels=tf.sparse_placeholder(tf.int32, shape=[None,2])
cell_fw = tf.nn.rnn_cell.LSTMCell(num_hidden>>1, state_is_tuple=True)
cell_bw = tf.nn.rnn_cell.LSTMCell(num_hidden>>1, state_is_tuple=True)
#outputs_fw_bw: (output_fw, output_bw) 是(output_fw, output_bw)的元组
outputs_fw_bw, _ = tf.nn.bidirectional_dynamic_rnn(cell_fw, cell_bw, rnn_inputs, dtype=tf.float32)
#tf.contat 连接前向和反向得到的结果,在指定维度上进行连接
outputs1 = tf.concat(outputs_fw_bw, 2)
shape = tf.shape(x)
batch_s, max_timesteps = shape[0], shape[1]
outputs = tf.reshape(outputs1, [-1, num_hidden])
#全连接层实现
logits0 = tf.matmul(tf.nn.dropout(outputs,keep_prob), W) + b
logits1 = tf.reshape(logits0, [batch_s, -1, num_classes])
logits = tf.transpose(logits1, (1, 0, 2))
y = tf.cast(logits, tf.float32)
return y
ocr_backward.py
import tensorflow as tf
import ocr_forward
import ocr_generated
import os
import glob
import random
import numpy as np
from PIL import Image
from PIL import ImageFilter
REGULARIZER = 0.0001
graphSize = (112,1024)
MODEL_SAVE_PATH = "./model/"
MODEL_NAME = "ocr_model"
def transform(im, flag=True):
'''
将传入的图片进行预处理:对图像进行图像缩放和数据增强
Args:
im : 传入的待处理的图片
Return:
graph : 返回经过预处理的图片
#random.uniform(a, b)随机产生[a, b)之间的一个浮点数
'''
graph=np.zeros(graphSize[1]*graphSize[0]*1).reshape(graphSize[0],graphSize[1],1)
deltaX=0
deltaY=0
ratio=1.464
if flag:
lowerRatio=max(1.269,im.size[1]*1.0/graphSize[0],im.size[0]*1.0/graphSize[1])
upperRatio=max(lowerRatio,2.0)
ratio=random.uniform(lowerRatio,upperRatio)
deltaX=random.randint(0,int(graphSize[0]-im.size[1]/ratio))
deltaY=random.randint(0,int(graphSize[1]-im.size[0]/ratio))
else:
ratio=max(1.464,im.size[1]*1.0/graphSize[0],im.size[0]*1.0/graphSize[1])
deltaX=int(graphSize[0]-im.size[1]/ratio)>>1
deltaY=int(graphSize[1]-im.size[0]/ratio)>>1
height=int(im.size[1]/ratio)
width=int(im.size[0]/ratio)
data = im.resize((width,height),Image.ANTIALIAS).getdata()
data = 1-np.asarray(data,dtype='float')/255.0
data = data.reshape(height,width)
graph[deltaX:deltaX+height,deltaY:deltaY+width,0]=data
return graph
def create_sparse(Y,dtype=np.int32):
'''
对txt文本转化出来的数字序列Y作进一步的处理
Args:
Y
Return:
indices: 数组Y下标索引构成的新数组
values: 下标索引对应的真实的数字码
shape
'''
indices = []
values = []
for i in range(len(Y)):
for j in range(len(Y[i])):
indices.append((i,j))
values.append(Y[i][j])
indices = np.asarray(indices, dtype=np.int64)
values = np.asarray(values, dtype=dtype)
shape = np.asarray([len(Y), np.asarray(indices).max(0)[1] + 1], dtype=np.int64) #[64,180]
return (indices, values, shape)
def backward():
x = tf.placeholder(tf.float32, shape=[None, graphSize[0], graphSize[1],1])
y = ocr_forward.forward(x, True, REGULARIZER)
#y_: 表示真实标签数据
#Y : 从文本中读取到的标签数据,训练时传给y_
#y : 神经网络预测的标签
global_step = tf.Variable(0, trainable=False)#全局步骤计数
seq_len = tf.placeholder(tf.int32, shape=[None])
y_ = tf.sparse_placeholder(tf.int32, shape=[None,2])
Imgs, Y = ocr_generated.get_data()
#损失函数使用的ctc_loss函数
loss = tf.nn.ctc_loss(y_, y, seq_len)
cost = tf.reduce_mean(loss)
#优化函数使用的是Adam算法
optimizer1 = tf.train.AdamOptimizer(learning_rate=0.0003).minimize(cost, global_step=global_step)
optimizer2 = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(cost, global_step=global_step)
width1_decoded, width1_log_prob=tf.nn.ctc_beam_search_decoder(y, seq_len, merge_repeated=False,beam_width=1)
decoded, log_prob = tf.nn.ctc_beam_search_decoder(y, seq_len, merge_repeated=False)
width1_acc = tf.reduce_mean(tf.edit_distance(tf.cast(width1_decoded[0], tf.int32), y_))
acc = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32), y_))
nBatchArray=np.arange(Y.shape[0])
epoch=100
batchSize=32
saver=tf.train.Saver(max_to_keep=1)
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess=tf.Session(config=config)
bestDevErr=100.0
with sess:
sess.run(tf.global_variables_initializer())
ckpt = tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
#saver.restore(sess, "model/model.ckpt")
#print(outputs.get_shape())
for ep in range(epoch):
np.random.shuffle(nBatchArray)
for i in range(0, Y.shape[0], batchSize):
batch_output = create_sparse(Y[nBatchArray[i:i+batchSize]])
X=[None]*min(Y.shape[0]-i,batchSize)
for j in range(len(X)):
X[j]=transform(Imgs[nBatchArray[i+j]])
feed_dict={x:X,seq_len :np.ones(min(Y.shape[0]-i,batchSize)) * 256, y_:batch_output}
if ep<50:
sess.run(optimizer1, feed_dict=feed_dict)
else:
sess.run(optimizer2, feed_dict=feed_dict)
print(ep,i,"loss:",tf.reduce_mean(loss.eval(feed_dict=feed_dict)).eval(),"err:",tf.reduce_mean(width1_acc.eval(feed_dict=feed_dict)).eval())
#saver.save(sess, "model/model.ckpt")
saver.save(sess, os.path.join(MODEL_SAVE_PATH, MODEL_NAME))
def main():
backward()
if __name__ == '__main__':
main()
ocr_test.py
import os
import glob
import random
import numpy as np
from PIL import Image
from PIL import ImageFilter
import ocr_forward
import tensorflow as tf
REGULARIZER = 0.0001
graphSize = (112,1024)
def transform(im,flag=True):
'''
对image做预处理,将其形状强制转化成(112, 1024, 1)的ndarray对象并返回
Args:
im = Image Object
Return:
graph = Ndarray Object
'''
graph=np.zeros(graphSize[1]*graphSize[0]*1).reshape(graphSize[0],graphSize[1],1)
deltaX=0
deltaY=0
ratio=1.464
if flag:
lowerRatio=max(1.269,im.size[1]*1.0/graphSize[0],im.size[0]*1.0/graphSize[1])
upperRatio=max(lowerRatio,1.659)
ratio=random.uniform(lowerRatio,upperRatio)
deltaX=random.randint(0,int(graphSize[0]-im.size[1]/ratio))
deltaY=random.randint(0,int(graphSize[1]-im.size[0]/ratio))
else:
ratio=max(1.464,im.size[1]*1.0/graphSize[0],im.size[0]*1.0/graphSize[1])
deltaX=int(graphSize[0]-im.size[1]/ratio)>>1
deltaY=int(graphSize[1]-im.size[0]/ratio)>>1
height=int(im.size[1]/ratio)
width=int(im.size[0]/ratio)
data = im.resize((width,height),Image.ANTIALIAS).getdata()
data = 1-np.asarray(data,dtype='float')/255.0
data = data.reshape(height,width)
graph[deltaX:deltaX+height,deltaY:deltaY+width,0]=data
return graph
def countMargin(v,minSum,direction=True):
'''
Args:
v = list
minSum = Int
Return:
v中比minSum小的项数
'''
if direction:
for i in range(len(v)):
if v[i]>minSum:
return i
return len(v)
for i in range(len(v)-1,-1,-1):
if v[i]>minSum:
return len(v)-i-1
return len(v)
def splitLine(seg,dataSum,h,maxHeight):
i=0
while i<len(seg)-1:
if seg[i+1]-seg[i]<maxHeight:
i+=1
continue
x=countMargin(dataSum[seg[i]:],3,True)
y=countMargin(dataSum[:seg[i+1]],3,False)
if seg[i+1]-seg[i]-x-y<maxHeight:
i+=1
continue
idx=dataSum[seg[i]+x+h:seg[i+1]-h-y].argmin()+h
if 0.33<=idx/(seg[i+1]-seg[i]-x-y)<=0.67:
seg.insert(i+1,dataSum[seg[i]+x+h:seg[i+1]-y-h].argmin()+seg[i]+x+h)
else:
i+=1
def getLine(im,data,upperbound=8,lowerbound=25,threshold=30,h=40,minHeight=35,maxHeight=120,beginX=20,endX=-20,beginY=125,endY=1100,merged=True):
'''
'''
dataSum=data[:,beginX:endX].sum(1) #dataSum是一个一维向量
lastPosition=beginY
seg=[]
flag=True
cnt=0
for i in range(beginY,endY):
if dataSum[i]<=lowerbound:
flag=True
if dataSum[i]<=upperbound:
cnt=0
continue
if flag:
cnt+=1
if cnt>=threshold:
lineNo=np.argmin(dataSum[lastPosition:i])+lastPosition if threshold<=i-beginY else beginY
if not merged or len(seg)==0 or lineNo-seg[-1]-countMargin(dataSum[seg[-1]:],5,True)-countMargin(dataSum[:lineNo],5,False)>minHeight:
seg.append(lineNo)
else:
avg1=dataSum[max(0,seg[-1]-1):seg[-1]+2]
avg1=avg1.sum()/avg1.shape[0]
avg2=dataSum[max(0,lineNo-1):lineNo+2]
avg2=avg2.sum()/avg2.shape[0]
if avg1>avg2:
seg[-1]=lineNo
lastPosition=i
flag=False
lineNo=np.argmin(dataSum[lastPosition:]>10)+lastPosition if threshold<i else beginY
if not merged or len(seg)==0 or lineNo-seg[-1]-countMargin(dataSum[seg[-1]:],10,True)-countMargin(dataSum[:lineNo],10,False)>minHeight:
seg.append(lineNo)
else:
avg1=dataSum[max(0,seg[-1]-1):seg[-1]+2]
avg1=avg1.sum()/avg1.shape[0]
avg2=dataSum[max(0,lineNo-1):lineNo+2]
avg2=avg2.sum()/avg2.shape[0]
if avg1>avg2:
seg[-1]=lineNo
splitLine(seg,dataSum,h,maxHeight)
results=[]
for i in range(0,len(seg)-1):
results.append(im.crop((0,seg[i]+countMargin(dataSum[seg[i]:],0),im.size[0],seg[i+1]-countMargin(dataSum[:seg[i+1]],0,False))))
return results
def calEditDistance(text1,text2):
dp=np.asarray([0]*(len(text1)+1)*(len(text2)+1)).reshape(len(text1)+1,len(text2)+1)
dp[0]=np.arange(len(text2)+1)
dp[:,0]=np.arange(len(text1)+1)
for i in range(1,len(text1)+1):
for j in range(1,len(text2)+1):
if text1[i-1]==text2[j-1]:
dp[i,j]=dp[i-1,j-1]
else:
dp[i,j]=min(dp[i,j-1],dp[i-1,j],dp[i-1,j-1])+1
return dp[-1,-1]
def test():
x = tf.placeholder(tf.float32, shape=[None, graphSize[0], graphSize[1], 1])
y = ocr_forward.forward(x, False, REGULARIZER)
seq_len = tf.placeholder(tf.int32, shape=[None])
labels=tf.sparse_placeholder(tf.int32, shape=[None,2])
loss = tf.nn.ctc_loss(labels, y, seq_len)
cost = tf.reduce_mean(loss)
width1_decoded, width1_log_prob=tf.nn.ctc_beam_search_decoder(y, seq_len, merge_repeated=False,beam_width=1)
decoded, log_prob = tf.nn.ctc_beam_search_decoder(y, seq_len, merge_repeated=False)
width1_acc = tf.reduce_mean(tf.edit_distance(tf.cast(width1_decoded[0], tf.int32), labels))
acc = tf.reduce_mean(tf.edit_distance(tf.cast(decoded[0], tf.int32), labels))
saver=tf.train.Saver(max_to_keep=1)
result=0
imgFiles=glob.glob(os.path.join("test_img","*"))
imgFiles.sort()
txtFiles=glob.glob(os.path.join("test_txt","*"))
txtFiles.sort()
for i in range(len(imgFiles)):
goldLines=[]
fin=open(txtFiles[i])
lines=fin.readlines()
fin.close()
for j in range(len(lines)):
goldLines.append(lines[j])
im = Image.open(imgFiles[i])
width, height = im.size
im = im.convert("L")
data = im.getdata()
data = 1-np.asarray(data,dtype='float')/255.0
data = data.reshape(height,width)
#getLine()将图片切割成一行一行的词条
Imgs = getLine(im,data)
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess=tf.Session(config=config)
with sess:
saver.restore(sess,"model/model.ckpt")
X=[None]*len(Imgs)
for j in range(len(Imgs)):
X[j]=transform(Imgs[j],False)
feed_dict={inputs:X,seq_len :np.ones(len(X)) * 256}
predict = decoded[0].eval(feed_dict=feed_dict)
j=0
predict_text=""
gold_text="".join(goldLines)
for k in range(predict.dense_shape[0]):
while j<len(predict.indices) and predict.indices[j][0]==k:
predict_text+=characters[predict.values[j]]
j+=1
predict_text+="\n"
predict_text=predict_text.rstrip("\n")
print("predict_text:")
print(predict_text)
fout=open("predict%s%s.txt"%(os.sep,txtFiles[i][txtFiles[i].find(os.sep)+1:txtFiles[i].rfind('.')]),'w')
fout.write(predict_text)
fout.close()
print("gold_text:")
print(gold_text)
cer=calEditDistance(predict_text,gold_text)*1.0/len(gold_text)
print("预测正确率: ", end='')
print(cer)
print()
result+=cer
print("test composition err:",result*1.0/len(imgFiles))
def main():
test()
if __name__ == '__main__':
main()