批量对多个 PDF 进行 OCR

批量对多个 PDF 进行 OCR

一年前这里已经讨论过这个问题了:

对许多 PDF 文件进行批量 OCR(尚未 OCR)?

有没有办法批量处理尚未 OCR 的 PDF?我认为,目前的情况是处理两个问题:

批量 OCR PDF

视窗

  • 杂技演员– 这是最直接的 OCR 引擎,可以批量执行 OCR。唯一的问题似乎是 1) 它不会跳过已经过 OCR 的文件 2) 尝试向它输入一堆 PDF(一些旧的)并观察它崩溃。它有点问题。它会在遇到每个错误时警告您(尽管您可以告诉软件不要通知。但同样,它会在某些类型的 PDF 上严重死机,因此您的里程可能会有所不同。

  • ABBYY FineReader(批量/扫描快照),奥姆尼帕奇– 这些肯定是人类已知的最糟糕的编程软件。如果你能找到如何充分自动化(无提示)批量 OCRPDF节省一样的名字那么请在这里发帖。看来我能找到的唯一解决方案在某些地方失败了——重命名、不是完全自动化等等。充其量,有一种方法可以做到这一点,但文档和编程太糟糕了,你永远都不会发现。

  • ABBYY FineReader 引擎ABBYY 识别服务器- 这些实际上更像是企业解决方案,您可能最好让 Acrobat 在文件夹上运行,然后尝试清除出现错误/导致程序崩溃的 PDF,而不是费力安装评估软件(假设您是简单的最终用户)。对于小用户来说,成本似乎没有竞争力。

  • ** Autobahn DX 工作站 ** 该产品的价格非常高,您可能要购买 6 份 Acrobat。这实际上不是最终用户解决方案。如果您是企业设置,那么这对您来说可能是值得的。

Linux

  • 手表OCR– 不再开发,并且基本上不可能在现代 Ubuntu 发行版上运行
  • pdfsandwich– 不再开发,基本上不可能在现代 Ubuntu 发行版上运行
  • ** ABBY LINUX OCR ** - 这应该是可编写脚本的,并且似乎有一些不错的结果:

http://www.splitbrain.org/blog/2010-06/15-linux_ocr_software_comparison

但是,与许多其他 ABBYY 产品一样,它们按页收费,因此,您最好尝试使用 Acrobat Batch OCR 来工作。

  • **Ocrad、GOCR、OCRopus、tesseract** – 这些可能有效,但也存在一些问题:
  1. 对于其中一些,OCR 效果不如 Acrobat 那么好(参见上面的链接)。
  2. 没有一个程序可以接收 PDF 文件并输出 PDF 文件。您必须先创建一个脚本并拆分 PDF,然后对每个程序运行这些程序,然后将文件重新组合为 PDF
  3. 一旦你这样做了,你可能会发现,就像我一样,(tesseract)创建了一个移位的 OCR 层。因此,如果你搜索单词“the”,你将获得单词旁边部分的突出显示。
  • 批量DjVu→ 转换为 PDF – 还没有研究过,但似乎是一个糟糕的迂回解决方案。

在线的

  • PDFcubed.com– 拜托,这实际上不是一个批量解决方案。
  • ABBYY 云 OCR- 不确定这是否真的是一个批量解决方案,无论如何,您都必须按页付费,而且这可能会非常昂贵。

云(更新于 2023 年——难以置信人们仍在关注这个)

如今,研究 AWS、Azure、GCP 来扩展您的 OCR,但更有可能的是会有一个托管解决方案可供您使用。


识别非 OCR PDF

这是一个稍微简单一点的问题,在 Linux 中很容易解决,但在 Windows 中就难得多了。我能够编写一个 perl 脚本来pdffont识别是否嵌入了字体,以确定哪些文件不是 OCR 的。


当前的“解决方案”

  1. 使用脚本来识别非 OCR 的 PDF(这样您就不必重新运行数千个 OCR 的 PDF)并将它们复制到临时目录(保留正确的目录树),然后在 Windows 上使用 Acrobat 运行这些 PDF,希望较小的批次不会崩溃。

  2. 使用相同的脚本,但让其中一个 linux ocr 工具正常工作,从而影响 ocr 质量。

我想我会尝试#1,我只是太担心Linux OCR工具的结果(我想没有人做过比较)并且如果Adobe实际上可以批量OCR目录而不会阻塞,那么将文件分开并再次将它们拼接在一起似乎是不必要的编码。

如果您想要一个完全免费的解决方案,您必须使用脚本来识别非 OCR pdf(或者只需重新运行 OCR pdf),然后使用其中一个 Linux 工具尝试对它们进行 OCR。Teseract 似乎具有最好的效果,但同样,其中一些工具在现代版本的 Ubuntu 中得不到很好的支持,但如果您可以设置它并修复我遇到的图像层与文本匹配层不匹配的问题(使用 tesseract),那么您将有一个非常可行的解决方案,并且再次是 Linux > Windows。


您有可行的解决方案吗?完全自动化,批量 OCR PDF,跳过已 OCR 的同名文件,使用高质量? 如果可以的话,我将非常感激您的意见。


Perl 脚本用于将非 OCRed 文件移动到临时目录。不能保证此脚本有效,可能需要重写,但如果有人使它有效(假设它无效)或效果更好,请告诉我,我会在此处发布更好的版本。


#!/usr/bin/perl

# move non-ocred files to a directory
# change variables below, you need a base dir (like /home/joe/), and a sourcedirectory and output
# direcotry (e.g books and tempdir)
# move all your pdfs to the sourcedirectory

use warnings;
use strict;

# need to install these modules with CPAN or your distros installer (e.g. apt-get)
use CAM::PDF;
use File::Find;
use File::Basename;
use File::Copy;

#use PDF::OCR2;
#$PDF::OCR2::CHECK_PDF   = 1;
#$PDF::OCR2::REPAIR_XREF = 1;

my $basedir = '/your/base/directory';
my $sourcedirectory  = $basedir.'/books/';
my @exts       = qw(.pdf);
my $count      = 0;
my $outputroot = $basedir.'/tempdir/';
open( WRITE, >>$basedir.'/errors.txt' );

#check file
#my $pdf = PDF::OCR2->new($basedir.'/tempfile.pdf');
#print $pdf->page(10)->text;



find(
    {
        wanted => \&process_file,

        #       no_chdir => 1
    },
    $sourcedirectory
);
close(WRITE);

sub process_file {
    #must be a file
    if ( -f $_ ) {
        my $file = $_;
        #must be a pdf
        my ( $dir, $name, $ext ) = fileparse( $_, @exts );
        if ( $ext eq '.pdf' ) {
            #check if pdf is ocred
            my $command = "pdffonts \'$file\'";
            my $output  = `$command`;
            if ( !( $output =~ /yes/ || $output =~ /no/ ) ) {
                #print "$file - Not OCRed\n";
                my $currentdir = $File::Find::dir;
                if ( $currentdir =~ /$sourcedirectory(.+)/ ) {
                    #if directory doesn't exist, create
                    unless(-d $outputroot.$1){
                    system("mkdir -p $outputroot$1");
                    }
                    #copy over file
                    my $fromfile = "$currentdir/$file";
                    my $tofile = "$outputroot$1/$file";
                    print "copy from: $fromfile\n";
                    print "copy to: $tofile\n";
                    copy($fromfile, $tofile) or die "Copy failed: $!";
#                       `touch $outputroot$1/\'$file\'`;
                }
            }

        }

    }
}

答案1

我也曾寻找过一种自动批量 OCR 多个 PDF 的方法,但运气不佳。最后,我想出了一个类似于您的可行解决方案,使用 Acrobat 和以下脚本:

  1. 将所有相关的 PDF 复制到特定目录。

  2. 删除已经包含文本的 PDF(假设它们已经是 OCRd 或已经是文本 - 我知道这不是理想的,但现在足够好了)。

  3. 使用自动热键自动运行 Acrobat,选择特定目录并对所有文档进行 OCR,并在其文件名后附加“-ocr”。

  4. 将 OCRd PDF 移回其原始位置,使用“-ocr.pdf”文件的存在来确定是否成功。

有点希思罗宾逊,但实际上效果很好。

答案2

我相信您需要意识到 ABBYY FineReader 是一种旨在提供快速、准确的开箱即用 OCR 的最终用户解决方案。

根据我的经验,OCR 项目每次都有明显不同的细节,并且无法为每种独特情况创建开箱即用的解决方案。但我可以向您推荐可以为您完成工作的更专业的工具:

我是上述云服务前端开发团队的一员,如有必要,可以提供更多信息。

考虑到在 PDF 中查找文本层,我无法给出任何建议,因为这项任务与我的专长 OCR 有点不同,所以我认为您使用外部脚本的方法非常合理。也许您会发现这个讨论很有帮助:http://forum.ocrsdk.com/questions/108/check-if-pdf-is-scanned-image-or-contains-text

答案3

2015 年初,我在 Windows 上使用 Nuance OmniPage Ultimate 成功实现了完全自动化的批量 OCR。不是免费的,标价 500 美元。使用随附的批处理程序“DocuDirect”。它有一个选项“在没有任何提示的情况下运行作业”,这似乎是对您原始问题的直接回答。

我使用 DocuDirect 为每个输入图像(即不可搜索)PDF 文件输出一个可搜索的 PDF 文件;可以指示它在输出文件夹中复制输入目录树以及原始输入文件名(几乎 - 见下文)。还使用多个核心。准确度是我评估的软件包中最好的。跳过受密码保护的文档(不停止作业,不显示对话框)。

注意事项 1:几乎原始文件名 - 后缀“.PDF”变为“.pdf”(即从大写变为小写),因为嘿,在 Windows 上它们都是一样的。(呃。)

注意事项 2:没有日志文件,因此诊断哪些文件在识别过程中失败(它们肯定会失败)的责任又回到了您身上。 DocuDirect 会很乐意产生乱码输出,例如整个页面都丢失了。我使用 PyPDF2 模块编写了一个 Python 脚本来实现粗略的验证:测试输出页数是否与输入页数匹配。见下文。

注意事项 3:模糊、不清晰的输入图像文件会导致 OmniPage 永远挂起,不使用任何 CPU;它永远无法恢复。这确实破坏了批处理,我没有找到任何解决方法。我也向 Nuance 报告了这个问题,但没有任何进展。

@Joe 说得对,该软件编程和文档编写得不好。我注意到OmniPage 拥有令人惊奇的字符识别魔法技术,但是其外壳(GUI 和批处理)就足以让你抓狂。

我赞同@Joe 和@Kiwi 的建议,使用脚本筛选出文件,以便仅向 OCR 包中呈现未受保护的图像文档。

我与 Nuance 的唯一关系就是作为一名不太满意的客户 - 我有一批未解决的支持单可以证明这一点 :)

@Joe:回答晚了,但可能仍然有意义。@SuperUser 社区:我希望您觉得这是主题相关的。

** 更新 ** 后续套件是 Nuance PowerPDF Advanced,标价仅为 150 美元。我使用它甚至取得了更好的效果,它同样准确,但更加稳定。

以下是 OCR 前/后树验证的 python 脚本。

'''
Script to validate OCR outputs against inputs.
Both input and output are PDF documents in a directory tree.
For each input document, checks for the corresponding output
document and its page count.

Requires PyPDF2 from https://pypi.python.org/pypi/PyPDF2
'''

from __future__ import print_function
from PyPDF2 import PdfFileReader
import getopt
import os
import stat
import sys

def get_pdf_page_count(filename):
    '''
    Gets number of pages in the named PDF file.
    Fails on an encrypted or invalid file, returns None.
    '''
    with open(filename, "rb") as pdf_file:
        page_count = None
        err = None
        try:
            # slurp the file
            pdf_obj = PdfFileReader(pdf_file)
            # extract properties
            page_count = pdf_obj.getNumPages()
            err = ""
        except Exception:
            # Invalid PDF.
            # Limit exception so we don't catch KeyboardInterrupt etc.
            err = str(sys.exc_info())
            # This should be rare
            print("Warning: failed on file %s: %s" % (filename, err), file=sys.stderr)
            return None

    return page_count

def validate_pdf_pair(verbose, img_file, txt_file):
    '''
    Checks for existence and size of target PDF file;
    number of pages should match source PDF file.
    Returns True on match, else False.
    '''
    #if verbose: 
    #    print("Image PDF is %s" % img_file)
    #    print("Text PDF is %s" % txt_file)

    # Get source and target page counts
    img_pages = get_pdf_page_count(img_file)
    txt_pages = get_pdf_page_count(txt_file)
    if img_pages is None:
        # Bogus PDF, skip.
        print("Warning: failed to get page count for %s" % img_file, file=sys.stderr)
        return None
    if txt_pages is None:
        # Bogus PDF, skip.
        print("Warning: failed to get page count for %s" % txt_file, file=sys.stderr)
        return None

    retval = True
    if img_pages != txt_pages:
        retval = False
        print("Mismatch page count: %d in source %s, %d in target %s" % (img_pages, img_file, txt_pages, txt_file), file=sys.stderr)

    return retval


def validate_ocr_output(verbose, process_count, total_count, img_dir, txt_dir):
    '''
    Walks a tree of files to compare against output tree, calling self recursively.
    Returns a tuple with PDF file counts (matched, non-matched).
    '''
    # Iterate over the this directory
    match = 0
    nonmatch = 0
    for dirent in os.listdir(img_dir):
        src_path = os.path.join(img_dir, dirent)
        tgt_path = os.path.join(txt_dir, dirent)
        if os.path.isdir(src_path):
            if verbose: print("Found source dir %s" % src_path)
            # check target
            if os.path.isdir(tgt_path):
                # Ok to process
                (sub_match, sub_nonmatch) = validate_ocr_output(verbose, process_count + match + nonmatch, total_count, 
                                         src_path, tgt_path)
                match += sub_match
                nonmatch += sub_nonmatch
            else:
                # Target is missing!?
                print("Fatal: target dir not found: %s" % tgt_path, file=sys.stderr)

        elif os.path.isfile(src_path):
            # it's a plain file
            if src_path.lower().endswith(".pdf"):
                # check target
                # HACK: OmniPage changes upper-case PDF suffix to pdf;
                # of course not visible in Windohs with the case-insensitive 
                # file system, but it's a problem on linux.
                if not os.path.isfile(tgt_path):
                    # Flip lower to upper and VV
                    if tgt_path.endswith(".PDF"):
                        # use a slice
                        tgt_path = tgt_path[:-4] + ".pdf"
                    elif tgt_path.endswith(".pdf"):
                        tgt_path = tgt_path[:-4] + ".PDF"
                # hopefully it will be found now!
                if os.path.isfile(tgt_path):
                    # Ok to process
                    sub_match = validate_pdf_pair(verbose, src_path, tgt_path)
                    if sub_match:
                        match += 1
                    else:
                        nonmatch += 1
                    if verbose: print("File %d vs %d matches: %s" % (process_count + match + nonmatch, total_count, sub_match))

                else:
                    # Target is missing!?
                    print("Fatal: target file not found: %s" % tgt_path, file=sys.stderr)
                    nonmatch += 1

        else:
            # This should never happen
            print("Warning: not a directory nor file: %s" % src_path, file=sys.stderr)
    return (match, nonmatch)

def count_pdfs_listdir(verbose, src_dir):
    '''
    Counts PDF files in a tree using os.listdir, os.stat and recursion.
    Not nearly as elegant as os.walk, but hopefully very fast on
    large trees; I don't need the whole list in memory.
    '''
    count = 0
    for dirent in os.listdir(src_dir):
        src_path = os.path.join(src_dir, dirent)
        # stat the entry just once
        mode = os.stat(src_path)[stat.ST_MODE]
        if stat.S_ISDIR(mode):
            # It's a directory, recurse into it
            count += count_pdfs_listdir(verbose, src_path)
        elif stat.S_ISREG(mode):
            # It's a file, count it
            if src_path.lower().endswith('.pdf'):
                count += 1
        else:
            # Unknown entry, print an error
            print("Warning: not a directory nor file: %s" % src_path, file=sys.stderr)
    return count

def main(args):
    '''
    Parses command-line arguments and processes the named dirs.
    '''
    try:
        opts, args = getopt.getopt(args, "vi:o:")
    except getopt.GetoptError:
        usage()
    # default values
    verbose = False
    in_dir = None
    out_dir = None
    for opt, optarg in opts:
        if opt in ("-i"):
            in_dir = optarg
        elif opt in ("-o"):
            out_dir = optarg
        elif opt in ("-v"):
            verbose = True
        else:
            usage()
    # validate args
    if in_dir is None or out_dir is None: usage()
    if not os.path.isdir(in_dir):
        print("Not found or not a directory: %s" % input, file=sys.stderr)
        usage()
    if not os.path.isdir(out_dir):
        print("Not found or not a directory: %s" % out_dir, file=sys.stderr)
        usage()
    if verbose: 
        print("Validating input %s -> output %s" % (in_dir, out_dir))
    # get to work
    print("Counting files in %s" % in_dir)
    count = count_pdfs_listdir(verbose, in_dir)
    print("PDF input file count is %d" % count)
    (match,nomatch) = validate_ocr_output(verbose=verbose, process_count=0, total_count=count, img_dir=in_dir, txt_dir=out_dir) 
    print("Results are: %d matches, %d mismatches" % (match, nomatch))

def usage():
    print('Usage: validate_ocr_output.py [options] -i input-dir -o output-dir')
    print('    Compares pre-OCR and post-OCR directory trees')
    print('    Options: -v = be verbose')
    sys.exit()

# Pass all params after program name to our main
if __name__ == "__main__":
    main(sys.argv[1:])

答案4

开启MacLinux

parallel --tag -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf

这里

相关内容