![使用生成的 LaTeX 文件训练 Tesseract](https://linux22.com/image/301835/%E4%BD%BF%E7%94%A8%E7%94%9F%E6%88%90%E7%9A%84%20LaTeX%20%E6%96%87%E4%BB%B6%E8%AE%AD%E7%BB%83%20Tesseract.png)
我的问题
Tesseract 是一个可以训练的OCR。
我需要训练它考虑一种新的特定字体(ttf 字体)。
我发现这次讨论这表明可以使用 LaTeX 生成训练文件。但是,脚本文件不再可用。
因此我开始尝试创建自己的脚本文件,但现在我只能用 LaTeX 生成正确的文件。
Tesseract 训练集是如何制作的
为了制作 Tesseract 训练集,我需要生成两个文件:一个 .box 文件和一个图像文件(例如 .tif 或 .png)。
这两个文件都是从我从现有的 Tesseract 训练集中提取的训练文本生成的。该训练文本至少包含 4700 个字符。
以下是训练文本的示例:
« : : :R: : :» Re: û® 03 vue ë ’ Logiciels septembre ¢§ petite numérique ÈQ été Yû Windows “I” ^Z Commentaires forum privé
depuis Dernière Annuaire une vendeur ° # “au-delà ” est français FAQ «% moment car œ? | ` fois JuG 14 plus HP Culture
现在,我使用此文本生成一个 pdf 文件(使用 OpenOffice)。然后将此 pdf 转换为图像(使用 imagemagick)。
然后我使用 tesseract 生成一个 .box 文件。此 .box 文件应包含每个包含字符的框的坐标。
例子:
m 861 2621 893 2644 0
m 895 2617 921 2648 0
e 922 2621 938 2644 0
m 939 2622 961 2644 0
b 963 2621 978 2649 0
对应下图:
如您所见,这里的问题是se
和pt
字符被替换为m
并且它们位于同一个框中。
我应该有这个:
s 861 2621 877 2644 0
e 877 2621 893 2644 0
p 895 2617 908 2648 0
t 908 2617 921 2648 0
e 922 2621 938 2644 0
m 939 2622 961 2644 0
b 963 2621 978 2649 0
这与这里几乎正确的图像相对应:
如您所见,每个字母都在其自己的盒子内。
t
与此处底部相同,p
因为我编辑了 .box 文件并分割了包含两个字符的原始框。
我必须手动更正坐标才能得到这个:
s 861 2621 877 2644 0
e 877 2621 893 2644 0
p 895 2617 910 2644 0
t 910 2622 921 2648 0
e 922 2621 938 2644 0
m 939 2622 961 2644 0
b 963 2621 978 2649 0
与下图相匹配:
所以我必须纠正手动使用特定软件(jTessBoxEditor)对训练集(4700 多个字符!)进行处理,这个过程非常漫长且痛苦。
盒子文件格式
每行匹配一个字符 (文件必须采用 UTF-8 编码但是,使用另一种编码生成文件应该可以轻松转换为 UTF-8)
每行给出一个字符及其在图像文件中的位置,从左下角开始(0;0)。
e 922 2621 938 2644 0
给出包含 ae 字符的框的左下角坐标(x=922 和 y=2621)和右上角坐标(x=938 和 y=2644)。
每行的最后一个数字是页码(从 0 开始):这里是第一页(0)。
为什么不使用 LaTeX?
问题是 OpenOffice 仅生成 PDF(图像),我不知道如何解析 PDF 文件来获取每个字符的位置(也不知道是否可能)。
然后我想起 LaTeX 使用方框将每个字符放入页面中。而且 LaTeX“知道”每个方框在哪里。所以我找到了已经提到讨论并决定尝试一下。
我在讨论中看到他们使用了以下流程,因此我也尝试这样做:
a) 安装 MikTeX 和一些允许 latex 理解卡纳达语的软件包,我知道有一个叫做 itrans 的软件包可以处理这个。但是你需要使用拉丁音译为其提供输入。
b) 您准备训练文本(使用音译)并使用 itrans 和 latex 对其进行处理。在此阶段,您将获得一个 .dvi 文件,该文件以卡纳达语排版,并包含(以隐秘形式)有关文本字符框的所有信息。您可以使用我的 perl 脚本提取此信息
dvitype <file.dvi>| perl script1.pl > file.texbox
c) 你为 Tesseract 制作训练图像。你可以使用 dvips + ghostscript 以电子方式完成此操作
dvips -o <ps.file> <dvi.file>
gs -r300x300 -dNOPAUSE -sOutputFile=<file.tif> -sDEVICE=tiffg4 <ps.file>
或者通过在打印机上打印 dvi 文件,然后扫描它。
你跑
tesseract <file.tif> <file.txt> batch.nochop makebox
您将 file.txt 重命名为 file.box。
d) 使用我的第二个 perl 脚本生成用于训练 tesseract 的最终框文件
perl correlatebox.pl file.texbox file.box > result_file.box
不幸的是,.pl 文件现在丢失了...而且我不是 DVI 文件和字体文件方面的专家。
我尝试过
我的猜测是输出一个可解析的 DVI 文件(使用 dvitype)来获取盒子的坐标,然后从 DVI 生成图像。
但是当我使用时lualatex -output-format dvi training.helveticacomp.tex
,字体出现一些错误(HelveticaComp.ttf)。
但是当我使用时xelatex -no-pdf training.helveticacomp.tex
,我得到了一个 xdv 文件,我无法使用 dvitype 实用程序对其进行解析。
我在基本 TexLive 发行版上安装了缺少的组件:texware 中的 dvitype、dtl 中的 disdiv(用于反汇编 xdv 文件disdvi -x <xdv file>
)
我发现了有趣的信息:
我尝试跟随这个文件安装我的需要的字体(因为 lualatex 抱怨缺少字体)但我仍然遇到一些错误。我不确定是否要在我的 LaTeX 中安装一个全新的字体,因为这似乎很难,所以我还没有尝试。
这是我的 .tex 文件的一个示例:
\documentclass[fontsize=12,a4paper,headheight=0.5cm,headsepline,parskip=half-]{scrartcl}
\KOMAoptions{BCOR=0mm,DIV=40}
\usepackage{fontspec}
\setmainfont{Helvetica-Compressed}
\begin{document}
‘ kit Contacts Carte Type un forme ç~ avant BYW: EN monde 2001 qu'on plan image ZG 23 À+ niveau femmes
\end{document}
我的问题
我需要一些线索来使用 LaTeX 生成与 .png(或 .tif)文件相关联的正确的 .box 文件。
如果可能的话,这样就可以避免安装新的字体。
如能就如何解析 xdv 或 dvi 文件以获取 .box 以及如何正确管理我的字体提供任何帮助,我将不胜感激。
当然,任何其他产生相同输出(.box +图像文件)的解决方案都是受欢迎的。
我想这会引起任何想要训练 Tesseract 但又不想花费大量时间进行 .box 编辑的人的兴趣。
我的电脑
我在 Mac OS X (10.10) 上使用基本的 TexLive 安装。
答案1
LuaTeX
因此我研究了使用的节点处理回调的选项。最适合的是pre_output_filter
当页面准备好输出时调用的。我创建了名为 的简单包boxes
,它由两个文件组成:LaTeX 包boxes.sty
和 Lua 模块boxes.lua
。
盒子.sty:
\ProvidesPackage{boxes}
\RequirePackage{luacode}
\RequirePackage{kvoptions}
\DeclareStringOption[eng]{lang}
\DeclareStringOption[72]{resolution}
\DeclareStringOption[75pt]{startx}
\DeclareStringOption[67.5pt]{starty}
\ProcessKeyvalOptions*
\luaexec{%
main_language = "\boxes@lang"
resolution = tonumber("\boxes@resolution")
startx = tex.sp("\boxes@startx")
starty = tex.sp("\boxes@starty")
}
\begin{luacode*}
print("language", main_language)
local boxes = require "boxes"
boxes.resolution = resolution
boxes.startx = startx
boxes.starty = starty
--boxes.set_name()
luatexbase.add_to_callback("pre_output_filter",
function(head,info, size, pack, maxdpth)
local f = font.getfont(font.current())
local fontname = f.psname or f.fullname
local name = string.format("%s.%s.exp%i.box", main_language, fontname,0)
local glyphs = boxes.traverse(head)
if #glyphs > 0 then boxes.save(name, glyphs) end
return head
end, "Save node boxes")
\end{luacode*}
\endinput
这个文件很简单,需要注意的是包选项:
- lang:处理的语言
- 分辨率:我还没有找到应该以哪种分辨率保存框文件,我认为使其可配置是一个好主意。默认值为 72 ppi。
- startx 和 starty - 我找不到如何计算文本块左上角开头的方法,这实际上取决于使用的文档类或类似的包
geometry
,我希望有某种方法可以确定它,但我不知道如何。所以我们必须手动设置这些值,使用一些实验
现在lua模块boxes.lua
:
local boxes = {}
boxes.resolution = 300 --72
local pt = 2 ^ 16
local uchar = unicode.utf8.char
local total_height = tex.pageheight
local pagebox = tex.pdfpagebox
local baselineskip = tex.baselineskip.width
local function round(num, idp)
local mult = 10^(idp or 0)
return math.floor(num * mult + 0.5) / mult
end
local function make_dimensions(glyph, x, y)
local resolution = boxes.resolution
local bp = (2 ^ 16) / (resolution / 72.27)
local lx = round(x / bp)
local ly = round((total_height - (y + glyph.depth)) / bp)
local rx = round((x + glyph.width) / bp)
local ry = round((total_height - (y - glyph.height)) / bp)
return lx,ly, rx,ry
end
function boxes.traverse(head)
--local set = head.glue_set
--local sign = head.glue_sign
--local order = head.glue_order
local resolution = boxes.resolution
local bp = (2 ^ 16) / (resolution / 72.27)
local glyphs = {}
local i = 0
for n in node.traverse(head) do
print(n.id, n.subtype)
if n.id == 0 then
i = i + 1
local set = n.glue_set
local sign = n.glue_sign
local order = n.glue_order
local height = i * baselineskip
local nhead = n.head
-- y is distance from page top to the current baseline
local y = boxes.starty + height or tex.pdfvorigin + height - 4.5 * (2^16)
local x = boxes.startx or tex.pageleftoffset + 2.5 * (2^16)
for glyph in node.traverse_id(37, nhead) do
local w, h, d = node.dimensions(set, sign, order, nhead, glyph)
local glyph_x = x + w
local lx,ly, rx, ry = make_dimensions(glyph, glyph_x, y)
glyphs[#glyphs+1]={uchar(glyph.char), lx,ly,rx,ry}
end
end
end
return glyphs
end
function boxes.save(name, glyphs)
local f = io.open(name,"w")
for _, line in ipairs(glyphs) do
f:write(table.concat(line,", ").. "\n")
end
f:close()
end
return boxes
代码非常简单:在函数中boxes.traverse
我们处理行节点列表。当我们找到id
0 号节点(即水平线)时,我们用 增加行数和垂直位置\baselineskip
。只要文本简单且没有更高级的格式(这会导致垂直空间大于 baselineskip),这种方法就会有效。但出于这个特定目的,我们可以假设只使用没有格式化的纯文本。
然后我们处理节点的子列表glyph
并使用函数计算水平位置node.dimensions
:
local w, h, d = node.dimensions(set, sign, order, nhead, glyph)
set
,sign
并order
用于计算空间的大小,因为它具有可变的宽度,所以每行可能略有不同。这些值在父hlist
节点中设置。w
变量是从行首到当前字形的宽度。
然后我们用函数计算角色的尺寸
local function make_dimensions(glyph, x, y)
local resolution = boxes.resolution
local bp = (2 ^ 16) / (resolution / 72.27)
local lx = round(x / bp)
local ly = round((total_height - (y + glyph.depth)) / bp)
local rx = round((x + glyph.width) / bp)
local ry = round((total_height - (y - glyph.height)) / bp)
return lx, ly, rx, ry
end
变量x
和y
是左下角坐标。因为坐标系从TeX
左上角开始,但对于boxes
格式,它从左下角开始,所以所有垂直尺寸都必须镜像,只需y
从页面的总高度中减去计算值即可。最后,使用变量除法将计算出的尺寸插值到当前分辨率bp
。
\documentclass[fontsize=12,a4paper,%headheight=0.5cm,headsepline,
parskip=half-]{scrartcl}
%\documentclass{article}
\usepackage[resolution=300]{boxes}
%\KOMAoptions{BCOR=0mm,DIV=40}
\usepackage{fontspec}
%\setmainfont{Helvetica-Compressed}
\begin{document}
\typeout{\the\baselineskip}
‘ kit Contacts Carte Type un forme ç~ avant BYW: EN monde 2001 qu'on plan image ZG 23 À+ niveau femmes příliš žluťoučký kůň úpěl ďábelské ódy.
\end{document}
我们可以用盒子命令。它需要pbm
格式的图像,可以使用pdftoppm
命令创建:
lualatex sample
pdftoppm -mono -freetype yes -aa yes -r 300 sample.pdf > sample.pbm
tessboxes sample.pbm eng.LMRoman12-Regular.exp0.box > output.pbm