之前,写过一篇文章特殊字符语言包训练流程(新)记录了Tess4.0训练模型的流程。但是由于Tesseract的系统限制,Tess4.0无法自动训练中文正体和英文斜体的混合文本,因此,后来写了文章Tess4.0中英文正体斜体混合训练记录了中文正体和英文斜体的训练流程。然而,这篇文章里暴力地将所有中文正体放在一个页面,英文斜体放在另一个页面。因此,Tess4.0无法学习到中英文文本之间的顺序。为了解决中文正体和英文斜体之间的顺序识别问题,这篇文章记录了手动合并Tess4.0的输入数据的过程。手动合并的数据很好地融合了中文正体和英文斜体之间的顺序信息。这样,Tess4.0后期的LSTM网络结构可以很好地学习到中文正体和英文斜体之间的顺序信息。
Tess输入数据生成步骤
初步分析,Tess4.0的输入数据主要由tif和box两个文件组成,其中tif2文件是Tess待识别的图片,box文件中记录了每个字符在图片中的位置信息。现在我们假设所有的文本信息记录在txt文本中,下面阐述生成tif和box文本的流程。
txt文本信息转存到word文档
由于需要学习txt文本中的英文斜体信息(txt不支持正体和斜体同步出现的情况),我们需要将txt中的所有文本按顺序转存到word中,其中,中文设置为宋体正体,英文设置为Times new Roman斜体。其主要python代码如下:
document = Document()
for eachLine in content_lines.split('\n'): //content_lines读取的txt文本中的内容
p = document.add_paragraph() //所有数据保存在document对象中,读入数据以后保存到相应路径下
if eachLine == '':
continue
for each_word in eachLine:
if is_alphabet(each_word): //字母以Times New Roman斜体形式保存
run = p.add_run(each_word + ' ')
run.font.name = u'Times New Roman'
run.italic = True
else:
run = p.add_run(each_word + ' ') //其他字符保存宋体正体
if each_word == '_':
run.font.bold = True
run.italic = True
run.font.name = u'宋体'
run._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')
document.save('word_path')
word文档转图片
已经得到了中文正体和英文斜体混合的word文档,下面就是将其转化为图片信息。转为图片的目的是截取每个字符的box位置,由于字符之间的间距很小,截取box位置难度较大。为了提高字符的截取精度,在上一步骤转存word文档时,我们故意在每个字符后面添加一些空格,这样字符之间的间距增大,就降低了字符截取的难度。
在word转图片时,首先将word转存为pdf文档,然后利用下面的java代码转为tif图片:
public static void main (String[] args) {
// TODO Auto-generated method stub
String filePath = "math_data.pdf";
File file = new File(filePath);
try {
PDDocument doc = PDDocument.load(file);
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
for(int i=0;i<pageCount;i++){
BufferedImage image = renderer.renderImageWithDPI(i, 296);
String saveFilePath = "./test_data/";
saveFilePath += "image";
saveFilePath += "_";
saveFilePath += filePath;
saveFilePath += "_";
saveFilePath += i;
saveFilePath += ".tif";
System.out.println("saveFilePath = " + saveFilePath);
ImageIO.write(image, "tif", new File(saveFilePath));
}
}catch (IOException e) {
e.printStackTrace();
}
}
这些代码基本修改于网络上现成的程序,所以不同的数据处理步骤用了不同的语言(python, java, 后面的代码还有用到C++的)。
box文件提取与图像字符重组
在提取字符box位置时,主要思路是二值化,水平投影截取行,再垂直投影截取字符位置。然后将截取出来的字符重新排列位置,主要调整字符之间的距离,顺序不变,并调整相应的box位置 。代码流程框架见下:
CutWords cutWords = CutWords();
Mat srcImage = imread(path); // 获取原图
Mat srcImageBin;
srcImageBin = cutWords.horizonProjection(srcImage); // 二值化并且水平投影
cutWords.combineBlob(); // 融合行处理
list<Mat> lineImgs = cutWords.cutLine(srcImageBin); // 截取行
//cutWords.regionLines(srcImage); // 画出行截取的中间结果
cutWords.verticalProjection(lineImgs); // 对每行进行垂直投影,截取字符
cutWords.combineBox(); // 融合字符处理
//cutWords.regionWords(srcImage); // 画出字符截取结果
//waitKey(0);
srcImageBin = cutWords.adjustBoxLocation(srcImageBin, pageNum); // 调整字符位置和box文件
sprintf(saveBox, "E:/javaWorkspace/PdfConvertImage/test_box/boxdata_%d.txt", pageNum);
cutWords.writeBoxFile("E:/jiequ.txt", saveBox, pageNum, srcImageBin); // 画出box文件和新的图片
由代码注释可见主要的处理过程。
box文件后续处理
上一步骤的box文件生成的结果与Tesseract的输入文件存在一定的偏差,需要将具体的文本字符和box文件进行合并,并且添加图片中的行转换提示符。因此需要对box文件后续处理。
处理步骤如下:
- 将字符与box文件进行合并,上一步骤中只有box位置信息,但每个位置对应的字符并没有给出,这里需要将字符信息与位置信息对应;
- 每个字符之间需要插入一些转行提示符。
具体的处理代码如下:
tag = 0
for file_path in html_dir:
tag += 1
fopen = open('E:/数据/math_html/' + file_path, 'rb')
content_lines = fopen.read().decode('utf-8')
for eachLine in content_lines.split('\n'):
each_box_line = ''
if box_num < len(box_text)-1:
for each_word in eachLine: # 每行数据进行处理
print(box_text[box_num])
if each_word == ' ' or each_word == ' ' or each_word == ' ' or \
each_word == ' ' or each_word == '�' or each_word == '': # 排除文本中的特殊字符
continue
if box_num +1 == len(box_text)-1:
box_num += 1
break
box_line += each_word + ' ' + box_text[box_num] + '\r\n' # 合并字符和位置信息
if len(box_text[box_num+1].split(' ')) > 2: # 插入转行符
if box_text[box_num].split(' ')[1] != box_text[box_num+1].split(' ')[1]:
box_line += ' ' + ' ' + str(int(box_text[box_num].split(' ')[0])+60) + ' ' + \
box_text[box_num].split(' ')[1] + ' ' + str(int(box_text[box_num].split(' ')[2])+60) \
+ ' ' + box_text[box_num].split(' ')[3] + ' ' + box_text[box_num].split(' ')[4] + '\r\n'
box_num += 1
if len(box_text[box_num+1].split(' ')) > 2:
if box_text[box_num].split(' ')[1] != box_text[box_num+1].split(' ')[1]:
box_line += ' ' + ' ' + str(int(box_text[box_num].split(' ')[0])+60) + ' ' + \
box_text[box_num].split(' ')[1] + ' ' + str(int(box_text[box_num].split(' ')[2])+60) \
+ ' ' + box_text[box_num].split(' ')[3] + ' ' + box_text[box_num].split(' ')[4]+ '\r\n'
box_num += 1
验证box和tif的生成结果
tif和box文件都是由我们自己根据txt文档生成的,生成的结果如何验证很重要。这里我们给出验证数据质量的方法:
- 下载安装jTessBoxEditor工具,具体的工具使用方法可以查看日志:android中tesseract-ocr自定义字库的介绍。这里不再赘述;
- 提取box文档中的其中一页的box信息,提取方法如下:
updated_box_file = open('E:/javaWorkspace/PdfConvertImage/math_data.box', 'rb') text_content = updated_box_file.read().decode('utf-8').strip() box_page = 15 reault = '' for each_box in text_content.split('\r\n'): try: if int(each_box.split(' ')[5]) == box_page: reault += each_box.split(' ')[0] + ' ' + each_box.split(' ')[1] + ' ' + each_box.split(' ')[2] + ' ' \ + each_box.split(' ')[3] + ' ' + each_box.split(' ')[4] + ' 0' + '\r\n' except: print(each_box) open('E:/javaWorkspace/PdfConvertImage/test_data/image_math_data.pdf_'+str(box_page)+'.box', 'wb').write(reault.encode('utf-8'))
- 将对应的tif文件和box文件相同命名,并且放入同一目录下,用jTessBoxEditor加载图片,验证box信息是否正确。
合并tif和box文件
由于训练数据量要求比较大,这里我们生成了很多tif图片需要进一步合并,box文件也需要相应的处理。具体的处理方法可见Tess4.0中英文正体斜体混合训练文章中的合并中英文数据章节。这里直接给出合并box文件的代码:
box_file1 = open('E:/jTessBoxEditorFX/tesseract-ocr/temp_roman/chi_sim.SIMSUN_roman.exp0.box', 'rb').read().decode('utf-8')
box_file2 = open('E:/jTessBoxEditorFX/tesseract-ocr/temp_roman/math_data.box', 'rb').read().decode('utf-8')
box_content = box_file1 + '\n' // 第一个文件中的内容直接拷贝到box_content变量中
box1_page_num = 0
for line in box_file1.split('\n'): // 获取第一个文件中的页码数
if len(line) > 0:
page_num = int(line.split(' ')[len(line.split(' '))-1])
if page_num > box1_page_num:
box1_page_num = page_num
box1_page_num += 1
for line in box_file2.split('\r\n'): // 合并第二个文件
if len(line) > 0:
page_num1 = int(line.split(' ')[len(line.split(' '))-1])
sub_line = ''
for i in range(len(line.split(' '))-1):
sub_line += line.split(' ')[i] + ' '
box_content += sub_line + str(page_num1+box1_page_num) + '\n'
open('E:/jTessBoxEditorFX/tesseract-ocr/temp_roman/chi_sim.SIMSUN_roman.exp1.box', 'bw').write(box_content.encode('utf-8'))
print('merge success!')
训练Tesseract模型
在阅读本章节之前建议详细阅读Tess4.0中英文正体斜体混合训练这篇文章,这里的处理步骤原理与Tess4.0中英文正体斜体混合训练文章中的神经网络输入数据准备章节的处理步骤一致。更详细的训练模型的方法步骤可以参考文章特殊字符语言包训练流程(新)或者github中给出的详细教程。
tif和box文档生成的具体步骤
- 将所有待识别的数据以txt文档的格式放入./data/alltxt_input文件夹中;
- 运行./txt2word.py脚本,生成word_data.docx在./data/目录下;
- 将./word_data.docx文件手动打开,并存储为PDF格式保存在相同目录下;
- 运行./PdfConvertImage/src/testConvert.java文件,将pdf文档转存为tif文件,并存储在./data/img_tif/目录下;
- 运行./OCR_Process/OCRMain.cpp文件,截取box并调整字符图片位置,box文件存储在./data/txt_box/目录下,tif存储在./data/adjust_img_tif/目录下;
- 运行./boxfile_update.py脚本,合并字符与box信息。
这样,Tesseract的输入数据就已经生产好了。