基于给定的单词群体自动构建来自OCR错误的可能单词的输出列表

时间:2012-12-11 16:35:37

标签: mysql perl ocr fuzzy-search

我想知道在Perl / MySQL中是否有可能根据给定的单词构建一个变体单词列表,该单词可能会出现常见的OCR错误(即8而不是b)?换句话说,如果我有一个单词列表,并且在该列表中是单词“Alphabet”,那么有没有办法扩展或构建一个新列表以包含我的原始单词加上“Alphabet”的OCR错误变体?所以在我的输出中,我可能会对Alphabet有以下变体:

Alphabet
A1phabet
Alpha8et
A1pha8et

当然,如果不是所有出现在OCR文本中的常见错误代码都是有用的。像8而不是b,或1而不是l。我不打算修复错误,因为在我的数据本身中我可能有OCR错误,但是想根据我给它作为输入的单词列表创建一个单词的变体列表作为我的输出。所以在我的数据中,我可能有Alpha8et,但是如果我对Alphabet进行简单的搜索,它就不会发现这个明显的错误。

我快速而肮脏的MySQL方法

Select * from   
(SELECT Word
FROM words
union all
-- Rule 1 (8 instead of b)
SELECT 
case
    when Word regexp 'b|B' = 1 
        then replace(replace(Word, 'B','8'),'b','8')
    end as Word
FROM words
union all
-- Rule 2 (1 instead of l)
SELECT 
case
    when Word regexp 'l|L' = 1 
        then replace(replace(Word, 'L','1'),'l','1')
    end as Word
FROM words) qry
where qry.Word is not null
order by qry.Word;

我认为必须有更自动化和更清洁的方法

2 个答案:

答案 0 :(得分:0)

实现这一目标的有效方法是使用bitap algorithm。 Perl有re::engine::TRE,绑定到libtre,它在regexp中实现模糊字符串匹配:

use strict;
use warnings qw(all);
use re::engine::TRE max_cost => 1;

# match "Perl"
if ("A pearl is a hard object produced..." =~ /\(Perl\)/i) {
    say $1; # find "pearl"
}

另外,还有agrep工具允许您从命令行使用libtre:

$ agrep -i -E 1 peArl *
fork.pl:#!/usr/bin/env perl
geo.pl:#!/usr/bin/env perl
leak.pl:#!/usr/local/bin/perl

当您需要将几个单词与OCRized文本匹配时,有两种不同的方法。

如果它足够小,你可以简单地用整个字典构建一个正则表达式:

/(Arakanese|Nelumbium|additionary|archarios|corbeil|golee|layer|reinstill\)/

可以通过构建trigram索引来优化大型字典查询。 Perl在内存中有String::Trigram。 一些RDBMS也有三元组索引扩展。 PostgreSQL风格的pg_trgm允许你编写这样的查询,即使对于真正的大字典也是如此:

SELECT DISTINCT street, similarity(street, word)
    FROM address_street
    JOIN (
        SELECT UNNEST(ARRAY['higienopolis','lapa','morumbi']) AS word
    ) AS t0 ON street % word;

(这个在约150K行的桌子上花了大约70ms)

答案 1 :(得分:0)

如果您有包含扫描(原始)版本和更正版本的扫描文本示例,则生成字符更正列表应该相对简单。从足够的文本中收集这些数据,然后按频率对其进行排序。确定修正必须经常发生的频率为“常见”,然后只留下列表中的常见修正。

将列表转换为正确字母键入的地图;该值是该字母常见错误扫描的数组。使用递归函数来获取单词并生成其所有变体。

这个例子在Ruby中显示了递归函数。收集可能的错误扫描由您决定:

VARIATIONS = {
  'l' => ['1'],
  'b' => ['8'],
}

def variations(word)
  return [''] if word.empty?
  first_character = word[0..0]
  remainder = word[1..-1]
  possible_first_characters =
    [first_character] | VARIATIONS.fetch(first_character, [])
  possible_remainders = variations(remainder)
  possible_first_characters.product(possible_remainders).map(&:join)
end

p variations('Alphabet')
# => ["Alphabet", "Alpha8et", "A1phabet", "A1pha8et"]

原始字词包含在变体列表中。如果您只想 错误扫描,请删除原始单词:

def misscans(word)
  variations(word) - [word]
end

p misscans('Alphabet') 
# => ["Alpha8et", "A1phabet", "A1pha8et"]

命令行程序的快速(和未经测试)版本将上述函数与此“主”函数耦合:

input_path, output_path = ARGV
File.open(input_path, 'r') do |infile|
  File.open(output_path, 'w') do |outfile|
    while word = infile.gets
      outfile.puts misscans(word)  
    end
  end
end