Pular para o conteúdo principal
O objetivo da classificação de documentos é atribuir os documentos a diferentes categorias predefinidas. Isso é muito útil ao lidar com um fluxo de documentos que inclui vários tipos de documento e quando é necessário identificar o tipo de cada um. Por exemplo, você pode querer separar contratos, faturas e recibos em pastas diferentes ou renomeá-los de acordo com seus tipos. Isso pode ser feito automaticamente com um sistema pré-treinado. Uma das principais características da classificação de documentos é que os tipos de documentos a serem diferenciados são conhecidos de antemão. O ABBYY FineReader Engine pode classificar documentos com base no conteúdo, nas características da imagem ou levando em consideração tanto as características do texto reconhecido quanto as da imagem. Vamos analisar o processo em detalhes. Ele consiste em duas etapas principais:
  1. Criação de um banco de dados de classificação
Para cada categoria, escolha vários documentos ou páginas típicos. Eles serão usados para criar o banco de dados de classificação.
  1. Classificação de documentos
O banco de dados criado na etapa anterior pode ser usado para classificar documentos. Os documentos de entrada são enviados para um sistema de classificação pré-treinado, que usa o banco de dados de classificação para determinar a categoria. Talvez também seja necessário classificar documentos de acordo com alguns de seus atributos, como autor ou valor do código de barras. Este artigo não aborda esse tipo de classificação. Se você quiser classificar documentos de acordo com seus atributos, deverá implementar seus próprios algoritmos, que podem usar os cenários de extração de texto, reconhecimento em nível de campo ou reconhecimento de código de barras para extração de dados. O procedimento descrito abaixo também é ilustrado pela ferramenta de demonstração Classification para Windows e pelo exemplo de código Classification para Linux e macOS.

Implementação do cenário

Os exemplos de código fornecidos neste tópico são específicos do Windows.
A seguir, apresentamos uma descrição detalhada do método recomendado para usar o ABBYY FineReader Engine na classificação de documentos.
Para começar a trabalhar com o ABBYY FineReader Engine, você precisa criar o objeto Engine. O objeto Engine é o objeto principal na hierarquia de objetos do ABBYY FineReader Engine e fornece várias configurações globais, alguns métodos de processamento e métodos para criar outros objetos.Para criar o objeto Engine, você pode usar a função InitializeEngine. Veja também outras formas de carregar o objeto Engine (Win).

C#

public class EngineLoader : IDisposable
{
    public EngineLoader()
    {
        // Inicialize estas variáveis com o caminho completo para FREngine.dll, seu ID do projeto do cliente,
        // e, se aplicável, o caminho para o arquivo de token da Licença Online e a senha da Licença Online
        string enginePath = "";
        string customerProjectId = "";
        string licensePath = "";
        string licensePassword = "";
        // Carrega a biblioteca FREngine.dll
        dllHandle = LoadLibraryEx(enginePath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
           
        try
        {
            if (dllHandle == IntPtr.Zero)
            {
                throw new Exception("Can't load " + enginePath);
            }
            IntPtr initializeEnginePtr = GetProcAddress(dllHandle, "InitializeEngine");
            if (initializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("Can't find InitializeEngine function");
            }
            IntPtr deinitializeEnginePtr = GetProcAddress(dllHandle, "DeinitializeEngine");
            if (deinitializeEnginePtr == IntPtr.Zero)
            {
                throw new Exception("Can't find DeinitializeEngine function");
            }
            IntPtr dllCanUnloadNowPtr = GetProcAddress(dllHandle, "DllCanUnloadNow");
            if (dllCanUnloadNowPtr == IntPtr.Zero)
            {
                throw new Exception("Can't find DllCanUnloadNow function");
            }
            // Converte os ponteiros em delegates
            initializeEngine = (InitializeEngine)Marshal.GetDelegateForFunctionPointer(
                initializeEnginePtr, typeof(InitializeEngine));
            deinitializeEngine = (DeinitializeEngine)Marshal.GetDelegateForFunctionPointer(
                deinitializeEnginePtr, typeof(DeinitializeEngine));
            dllCanUnloadNow = (DllCanUnloadNow)Marshal.GetDelegateForFunctionPointer(
                dllCanUnloadNowPtr, typeof(DllCanUnloadNow));
            // Chama a função InitializeEngine 
            // passando o caminho para o arquivo da Licença Online e a senha da Licença Online
            int hresult = initializeEngine(customerProjectId, licensePath, licensePassword, 
                "", "", false, ref engine);
            Marshal.ThrowExceptionForHR(hresult);
        }
        catch (Exception)
        {
            // Libera a biblioteca FREngine.dll
            engine = null;
            // Exclui todos os objetos antes da chamada a FreeLibrary
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            FreeLibrary(dllHandle);
            dllHandle = IntPtr.Zero;
            initializeEngine = null;
            deinitializeEngine = null;
            dllCanUnloadNow = null;
            throw;
        }
    }
    // Funções de Kernel32.dll
    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
    private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
    // Funções de FREngine.dll
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private delegate int InitializeEngine(string customerProjectId, string licensePath, 
        string licensePassword, string tempFolder, string dataFolder, bool isSharedCPUCoresMode, 
        ref FREngine.IEngine engine);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DeinitializeEngine();
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DllCanUnloadNow();
    // Variáveis privadas
    private FREngine.IEngine engine = null;
    // Handle para FREngine.dll
    private IntPtr dllHandle = IntPtr.Zero;
    private InitializeEngine initializeEngine = null;
    private DeinitializeEngine deinitializeEngine = null;
    private DllCanUnloadNow dllCanUnloadNow = null;
}
Crie um objeto ClassificationEngine, que serve como fábrica para outros objetos da API de classificação. Use o método CreateClassificationEngine do objeto Engine.

C#

FREngine.IEngine engine;
FREngine.IClassificationEngine classEngine = engine.CreateClassificationEngine();
Os métodos de treinamento e classificação trabalham com um tipo especial de objeto criado a partir de um documento ou de uma página: ClassificationObject, que contém todas as informações relevantes para a classificação.Para preparar um documento para uso em um cenário de classificação, faça o seguinte:
  1. Carregue as imagens para processamento. Há várias formas de fazer isso: por exemplo, você pode criar o objeto FRDocument com o método CreateFRDocument do objeto Engine e, em seguida, adicionar imagens ao objeto FRDocument criado a partir de um arquivo usando o método AddImageFile.
  2. Se você for treinar ou usar um classificador do tipo que leva em conta recursos de texto (CT_Combined, CT_Text), primeiro reconheça o documento usando qualquer método conveniente. Usaremos os métodos Analyze e Recognize do objeto FRDocument. A síntese de documentos não é necessária para a classificação.
Embora o processamento paralelo não seja compatível com a classificação em si, ele pode ser necessário para o reconhecimento preparatório dos documentos no Windows e no Linux. Se o número de documentos que você pretende classificar for grande, recomendamos usar o Batch Processor ou outros métodos de processamento paralelo descritos em Parallel Processing with ABBYY FineReader Engine.
  1. Use o método CreateObjectFromDocument do objeto ClassificationEngine para criar um ClassificationObject contendo as informações da primeira página do documento. Se precisar usar outra página do documento, chame o método CreateObjectFromPage.
  2. A propriedade Description do ClassificationObject fica vazia por padrão. Especifique essa propriedade se precisar de uma descrição relevante.
Às vezes, pode acontecer de o documento ou a página reconhecida ainda assim não conter texto reconhecido (por exemplo, se uma página vazia tiver sido usada por engano). Nesse caso, o ClassificationObject não poderá ser usado com classificadores que exigem recursos de texto. Você pode usar a propriedade SuitableClassifiers para fazer uma verificação adicional.

C#

// Criar o objeto FRDocument
FREngine.IFRDocument frDocument = engine.CreateFRDocument();
// Adicionar as imagens
frDocument.AddImageFile( "C:\\MyImage.tif", null, null );
// Opcional: analisar e reconhecer o documento
frDocument.Analyze( null, null, null );
frDocument.Recognize( null, null );
// Criar o objeto de classificação
FREngine.IClassificationObject clObject = classEngine.CreateObjectFromDocument( frDocument );
// Vamos colocar na descrição a categoria à qual o objeto pertence
clObject.Description = "CategoryA_Object1";
Para treinar um classificador capaz de distinguir vários tipos de documentos, você precisa de um conjunto de dados categorizado que contenha amostras de cada tipo. Use o objeto TrainingData para popular e gerenciar esse conjunto de dados:
  1. Crie um objeto vazio com o método CreateTrainingData do objeto ClassificationEngine.
  2. Acesse a coleção de categorias por meio da propriedade Categories.
  3. Use o método AddNew do objeto Categories várias vezes para adicionar uma categoria para cada tipo de documento que você pretende classificar. O método requer uma string com o rótulo da categoria como parâmetro de entrada. O rótulo será retornado pelos métodos de classificação, portanto deve ser exclusivo no conjunto de categorias.
  4. Para cada objeto Category recém-adicionado, abra a coleção de objetos de classificação usando a propriedade Objects. Com o método IClassificationObjects::Add, adicione os objetos de classificação correspondentes a essa categoria.
    Nenhuma categoria pode ficar vazia. Por motivos óbvios, pelo menos duas categorias são necessárias para o treinamento.
  5. Agora que você configurou o conjunto de dados de treinamento, talvez queira salvá-lo em um arquivo no disco para uso posterior, por exemplo, caso a precisão do modelo treinado se mostre insatisfatória e você queira adicionar ou corrigir alguns dados para melhorar a qualidade. O objeto TrainingData fornece o método SaveToFile.

C#

FREngine.ITrainingData trainingData = classEngine.CreateTrainingData();
FREngine.ICategories categories = trainingData.Categories;
// Adicione a primeira categoria
FREngine.ICategory category = categories.AddNew( "CategoryA" );
// Adicione os objetos de classificação preparados na etapa 3
category.Objects.Add( clObject ); // repita para todos os objetos desta categoria
...
// Repita para todas as categorias
...
// Depois que todas as categorias tiverem sido adicionadas, salve o conjunto de dados de treinamento
trainingData.SaveToFile( "C:\\trainingData.dat" );
A funcionalidade de treinamento de modelos é fornecida pelo objeto Trainer. Use o método CreateTrainer do objeto ClassificationEngine para criá-lo.Ele contém todas as configurações para o tipo de classificador e o procedimento de treinamento, em dois subobjetos TrainingParams e ValidationParams. Decida quais configurações você precisa e altere as Properties correspondentes:
  • O tipo de classificador (ITrainingParams::ClassifierType). Essa configuração determina quais características do documento são consideradas na atribuição de uma categoria: características da imagem, conteúdo do texto reconhecido ou ambos. Para selecionar um tipo que use o conteúdo do texto, você precisa garantir que todos os objetos de classificação no conjunto de dados de treinamento tenham sido criados com base em documentos previamente reconhecidos.
  • O modo de treinamento (ITrainingParams::TrainingMode). Essa configuração determina se o processo de treinamento deve priorizar alta precisão (quantos dos elementos selecionados estão corretos), alta cobertura (quantos dos elementos corretos são selecionados) ou um equilíbrio entre as duas.
  • Se a validação cruzada k-fold deve ser utilizada (IValidationParams::ShouldPerformValidation). Recomendamos usar a validação cruzada quando sua amostra de treinamento não for muito grande, pois isso permite treinar vários modelos em diferentes partições da mesma amostra e selecionar o melhor. Se você tiver um grande volume de dados categorizados, pode ser melhor desativar a validação, treinar o modelo com toda a amostra de treinamento e, em seguida, usar os métodos de classificação (Etapa 6) para testar o modelo em outra amostra, calculando as métricas de desempenho por conta própria.
  • Os parâmetros da validação cruzada k-fold: o número de partes em que a amostra de treinamento é dividida (IValidationParams::FoldsCount) e o número de iterações (IValidationParams::RepeatCount). Observe que, em cada iteração, o número mínimo necessário de objetos no conjunto de treinamento é 4 para o classificador de texto e 8 para o classificador combinado. Certifique-se de que sua amostra de treinamento contenha objetos suficientes.
Agora você está pronto para treinar um modelo. Passe o objeto TrainingData configurado na Etapa 4 para o método TrainModel do objeto Trainer. Ele retorna uma coleção TrainingResults que, com a funcionalidade atualmente disponível, contém apenas um TrainingResult. Se você optou por realizar validação cruzada, revise as pontuações de desempenho no subobjeto ValidationResult.
O treinamento e a classificação de modelos serão realizados em modo sequencial no Linux e no Windows, independentemente do valor de IMultiProcessingParams::MultiProcessingMode.
A propriedade ITrainingResult::Model fornece acesso ao modelo de classificação treinado. Você pode salvá-lo em um arquivo com o auxílio do método SaveToFile ou utilizá-lo diretamente para classificar documentos (prossiga para a Etapa 6).

C#

// Criar o objeto trainer e configurar os parâmetros
FREngine.ITrainer trainer = classEngine.CreateTrainer();
trainer.TrainingParams.ClassifierType = (int)FREngine.ClassifierTypeEnum.CT_Image; // o classificador usará apenas os recursos de imagem
// As demais configurações serão mantidas como padrão e o modelo será treinado diretamente
FREngine.ITrainingResults results = trainer.TrainModel ( trainingData );
// Verificar a pontuação F1 do modelo
double F1 = results[0].ValidationResult.FMeasure;
// Recuperar o modelo de classificação
FREngine.IModel model = results[0].Model;
// Salvar o modelo para uso posterior
model.SaveToFile( "C:\\model.dat" );
Para usar o modelo treinado para classificação:
  1. Se o modelo não estiver carregado no momento, chame o método CreateModelFromFile do objeto ClassificationEngine para carregá-lo de um arquivo no disco.
  2. Prepare os objetos de classificação com base nos documentos que você precisa classificar, conforme descrito na Etapa 3.
  3. Para cada objeto de classificação, chame o método Classify do objeto Model, usando o ClassificationObject como parâmetro de entrada. O método retorna uma coleção de objetos ClassificationResult, cada um contendo o rótulo da categoria e a probabilidade dessa categoria. Os resultados são ordenados por probabilidade, da maior para a menor. Recupere o resultado e verifique se o nível de probabilidade é aceitável para você.
    Se o classificador não conseguir atribuir uma categoria, será retornado null em vez da coleção de resultados.
O treinamento do modelo e a classificação serão executados em modo sequencial no Linux e no Windows, independentemente do valor de IMultiProcessingParams::MultiProcessingMode.

C#

// Abrir o modelo treinado
FREngine.IModel model = classEngine.CreateModelFromFile( "C:\\model.dat" );
// Classificar o objeto
FREngine.IClassificationResults classResults = model.Classify( clObject );
// Acessar o melhor resultado e sua probabilidade
string label = classResults[0].CategoryLabel;
double probability = classResults[0].Probability;
Após concluir seu trabalho com o ABBYY FineReader Engine, você precisa descarregar o objeto Engine. Para isso, use a função exportada DeinitializeEngine.

C#

public class EngineLoader : IDisposable
{
    // Descarregar o FineReader Engine
    public void Dispose()
    {
        if (engine == null)
        {
            // O objeto Engine não foi carregado
            return;
        }
        engine = null;
        // Excluindo todos os objetos antes da chamada a FreeLibrary
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        int hresult = deinitializeEngine();
 
        hresult = dllCanUnloadNow();
        if (hresult == 0)
        {
            FreeLibrary(dllHandle);
        }
        dllHandle = IntPtr.Zero;
        initializeEngine = null;
        deinitializeEngine = null;
        dllCanUnloadNow = null;
        // lançando exceção após a limpeza
        Marshal.ThrowExceptionForHR(hresult);
    }
    // Funções de Kernel32.dll
    [DllImport("kernel32.dll")]
    private static extern IntPtr LoadLibraryEx(string dllToLoad, IntPtr reserved, uint flags);
    private const uint LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008;
    [DllImport("kernel32.dll")]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);
    [DllImport("kernel32.dll")]
    private static extern bool FreeLibrary(IntPtr hModule);
    // Funções de FREngine.dll
    [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)]
    private delegate int InitializeEngine( string customerProjectId, string LicensePath, string LicensePassword, , , , ref FREngine.IEngine engine);
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DeinitializeEngine();
    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    private delegate int DllCanUnloadNow();
    // variáveis privadas
    private FREngine.IEngine engine = null;
    // handle de FREngine.dll
    private IntPtr dllHandle = IntPtr.Zero;
    private InitializeEngine initializeEngine = null;
    private DeinitializeEngine deinitializeEngine = null;
    private DllCanUnloadNow dllCanUnloadNow = null;
}

Recursos necessários

Você pode usar o arquivo FREngineDistribution.csv para criar automaticamente uma lista dos arquivos necessários para o funcionamento do seu aplicativo. Para processar com esse cenário, selecione na coluna 5 (RequiredByModule) os seguintes valores: Core Core.Resources Opening Opening, Processing Processing Processing.Classification Processing.Classification.NaturalLanguages Processing.OCR Processing.OCR, Processing.ICR Processing.OCR.NaturalLanguages Processing.OCR.NaturalLanguages, Processing.ICR.NaturalLanguages Se você modificar o cenário padrão, ajuste os módulos necessários de acordo. Você também precisa especificar os idiomas da interface, os idiomas de reconhecimento e quaisquer recursos adicionais usados pelo seu aplicativo (como, por exemplo, Opening.PDF se precisar abrir arquivos PDF, ou Processing.OCR.CJK se precisar reconhecer textos em idiomas CJK). Consulte Como trabalhar com o arquivo FREngineDistribution.csv para mais detalhes.

Otimização adicional

Você pode encontrar mais informações sobre como configurar as várias etapas de processamento nestes artigos:

Veja também

Implementação de cenários básicos de uso