OCR Benchmark Pipeline
Comparando engines de OCR em documentos escaneados, avaliadas contra um gabarito, não só cronometradas
O problema
Comparar engines de OCR normalmente significa rodar cada uma manualmente e avaliar a saída no olho. Este pipeline automatiza isso: ele busca PDFs escaneados em um object storage, roda uma engine selecionada em cada página, e registra tempo, uso de recursos e, sempre que existe um gabarito, a precisão real contra ele (taxa de erro por caractere/palavra, extração de campos e localização), para que a escolha da engine seja baseada em números reais em vez de uma checagem pontual.
Arquitetura
Como funciona
Ingestão e conversão
PDFs são baixados de um object store MinIO, ou lidos do disco local em modo offline, e convertidos em imagens PNG, uma por página. Cada execução do pipeline é rastreada com um run ID, a engine usada e um status, então todo resultado remonta a uma execução específica.
OCR e benchmarking
A engine selecionada roda em cada página. O resultado de cada página é cronometrado, e seu score de confiança e a contagem de caracteres extraídos são registrados junto com o documento e o número da página. Quando existe um gabarito para aquela página, a mesma execução também calcula a taxa de erro por caractere/palavra e a precisão de extração de campos contra ele, automaticamente. Um monitor de recursos separado registra o uso de RAM e CPU da execução. Tudo isso é gravado no PostgreSQL como métricas estruturadas, enquanto a saída bruta do OCR vai para o MongoDB como JSON.
Armazenamento e API
O PostgreSQL guarda metadados de execução, estatísticas por documento (páginas, tempo decorrido, uso de CPU e memória) e métricas por página, indexadas por engine para permitir filtrar os resultados. O MongoDB guarda o JSON bruto do OCR de cada página. Um backend FastAPI serve os dois, e o dashboard em Streamlit usa isso para listar execuções e navegar pelo texto extraído de qualquer execução.
Precisão e gabarito
Tempo e confiança dizem o quão rápido uma engine rodou e o quanto ela alega ter certeza. Nenhum dos dois diz se ela leu o documento corretamente. Três métricas separadas fazem isso.
Taxa de erro por caractere e por palavra
CER/WER, calculadas com jiwer, medem a fidelidade do texto bruto: o quão próximo o texto extraído está de uma transcrição de referência, caractere por caractere e palavra por palavra.
Precisão de extração de campos
Uma checagem de nível mais alto e relevante para o negócio: a engine acertou os valores reais dos campos (Report ID, Date, Route...), não só os caracteres? Duas engines podem ter CER/WER quase idênticos e utilidade real muito diferente se o único erro de caractere cair dentro de uma data ou um ID.
Precisão de localização
Além de ler o valor certo, a engine também encontrou onde ele está na página? Medida como IoU (intersection-over-union) contra uma caixa de referência, o mesmo tipo de métrica que benchmarks reais de document-AI (FUNSD, CORD, DocVQA) usam.
De onde vem o gabarito
O gabarito não pode vir de ler o mesmo documento que você está tentando fazer OCR, isso seria circular. Para o documento de exemplo que acompanha o repositório, os rótulos são gerados de graça: o gerador desenha strings conhecidas na página com PIL, então a string "RPT-1000" existe em memória antes de qualquer pixel ser desenhado, e é escrita direto no arquivo de rótulo nesse mesmo momento. As engines de OCR então leem apenas os pixels, sem acesso a essa string original. Para documentos reais esse atalho não se aplica: os rótulos precisam vir de uma pessoa lendo o documento, ou de um sistema de registro já verificado, nunca de fazer OCR no documento de novo.

Resultados do benchmark
Os números abaixo vêm do único documento de exemplo que acompanha o repositório (2 páginas com gabarito, rodadas em Tesseract e EasyOCR). O PaddleOCR é verificado separadamente, explicado abaixo.
| Engine | CER | WER | Precisão de campos | IoU médio | Seg/página |
|---|---|---|---|---|---|
| Tesseract | 0,0025 | 0,0417 | 100% | 0,92 | 1,39 |
| EasyOCR | 0,0076 | 0,0417 | 60% | 0,64 | 18,82 |
Neste documento, o Tesseract é mais preciso e cerca de 13 vezes mais rápido que o EasyOCR: menor taxa de erro por caractere, todos os campos extraídos corretamente contra 3 de 5 campos do EasyOCR, caixas de localização mais precisas, e 1,4 segundos por página contra 18,8. Os erros do EasyOCR se concentraram em dois campos específicos (Inspector, Report ID), não distribuídos igualmente, o que o detalhamento por campo no dashboard deixa visível.
O PaddleOCR não está na tabela acima porque esta execução usou o stack do dashboard via Docker, onde o PaddleOCR atualmente não consegue rodar (ver Limitações). Rodado separadamente pelo caminho nativo, ele obteve CER/WER 0,0 e acertou os 5 campos e as 5 localizações no mesmo documento de exemplo.
Uma quarta pergunta, se a confiança auto-reportada de uma engine de fato acompanha sua precisão real, também é medida. Com apenas 2 páginas por engine, porém, essa correlação retorna estatisticamente indefinida. Seria preciso um conjunto de rótulos bem maior para significar algo.
Adicionando uma engine
Toda engine implementa a mesma interface (BaseOCREngine.predict), registrada por um pequeno dicionário de loaders, então as dependências de uma engine só são importadas quando ela é realmente usada. Adicionar uma é três passos: um arquivo de config, uma classe de engine, e o registro no loader. Nenhuma outra parte do pipeline, conversão, benchmarking, armazenamento, API ou dashboard, precisa mudar.
Testes
72 testes unitários e de integração cobrem os runtime helpers, o parsing de saída das duas engines, o cálculo de CER/WER e de precisão de campos, e os cálculos de localização por IoU, rodados com pytest. O CI roda ruff, black e mypy, além da suíte de testes completa, a cada push e pull request.
Limitações
- ▸Os números de benchmark acima vêm de um único documento sintético (2 páginas com gabarito por engine). Isso prova que a maquinaria de métricas funciona de ponta a ponta, não é uma avaliação estatisticamente significativa. Documentos escaneados reais (ruído, inclinação, baixo contraste) provavelmente ampliariam a diferença entre as engines.
- ▸O PaddleOCR está implementado e verificado como correto no caminho nativo, mas trava (segfault) dentro do container Docker em Apple Silicon: um bug não resolvido do upstream no wheel linux/arm64 do PaddlePaddle (PaddlePaddle/Paddle#76111), não é algo corrigível a partir deste repositório.
- ▸A calibração de confiança, se a confiança auto-reportada de uma engine acompanha sua precisão real, é medida mas fica estatisticamente indefinida com apenas 2 páginas com gabarito por engine. Precisaria de um conjunto de rótulos bem maior para significar algo.