processamento de imagem para melhorar a precisão do OCR do tesseract

145

Eu tenho usado o tesseract para converter documentos em texto. A qualidade dos documentos varia muito, e estou procurando dicas sobre que tipo de processamento de imagem pode melhorar os resultados. Percebi que o texto altamente pixelizado - por exemplo, o gerado por máquinas de fax - é especialmente difícil para o tesseract processar - presumivelmente todas essas bordas irregulares nos caracteres confundem os algoritmos de reconhecimento de forma.

Que tipo de técnicas de processamento de imagem melhoraria a precisão? Eu tenho usado um desfoque gaussiano para suavizar as imagens pixelizadas e vi algumas pequenas melhorias, mas espero que exista uma técnica mais específica que produza melhores resultados. Diga um filtro que foi ajustado para imagens em preto e branco, que suavizariam bordas irregulares, seguido de um filtro que aumentaria o contraste para tornar os caracteres mais distintos.

Alguma dica geral para quem é iniciante no processamento de imagens?

user364902
fonte

Respostas:

103
  1. corrigir DPI (se necessário) 300 DPI é mínimo
  2. corrigir tamanho do texto (por exemplo, 12 pt deve estar ok)
  3. tente consertar linhas de texto (distorcer e desanuviar texto)
  4. tente corrigir a iluminação da imagem (por exemplo, nenhuma parte escura da imagem)
  5. binarizar e remover o ruído da imagem

Não existe uma linha de comando universal que se encaixe em todos os casos (às vezes você precisa desfocar e aprimorar a imagem). Mas você pode tentar o TEXTCLEANER a partir dos scripts ImageMagick de Fred .

Se você não é fã da linha de comando, pode tentar usar o opensource scantailor.sourceforge.net ou o bookrestorer comercial .

user898678
fonte
6
E há um guia ilustrado de como fazer isso: code.google.com/p/tesseract-ocr/wiki/ImproveQuality
iljau
2
Observe que o script vinculado parece ser apenas para Linux.
Zoran Pavlovic
1
Isso não é verdade - este é um script bash. Se você instalou o bash e o ImageMagick, ele também será executado no Windows. Bash poderia instalado como parte de outro software útil por exemplo, git ou msys2 ...
user898678
6
@iljau Desde que mudou para o github. A página da wiki está em: github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
hometoast 4/16/16
2
Os documentos do Tesseract foram movidos novamente, para tesseract-ocr.github.io/tessdoc/ImproveQuality
Ninguém em
73

Não sou de modo algum um especialista em OCR. Mas nesta semana eu precisei converter texto de um jpg.

Comecei com um jpg colorido RGB de 445x747 pixels. Eu tentei imediatamente tesseract sobre isso, e o programa converteu quase nada. Eu entrei no GIMP e fiz o seguinte. imagem> modo> imagem em escala de cinza> imagem em escala> filtros de 1191x2000 pixels> aprimoramento> máscara de nitidez com valores de raio = 6,8, valor = 2,69, limite = 0 Em seguida, salvei como um novo jpg com 100% de qualidade.

O Tesseract conseguiu extrair todo o texto em um arquivo .txt

Gimp é seu amigo.

John
fonte
11
+1 Segui seus passos e obtive uma grande melhoria. Graças
onof
1
Também tenho a impressão de que o Tesseract funciona melhor se você converter a entrada em um arquivo TIFF e fornecer ao Tesseract o TIFF (em vez de solicitar ao Tesseract que faça a conversão para você). ImageMagick pode fazer a conversão para você. Esta é a minha impressão anedótica, mas não a testei com cuidado, por isso pode estar errada.
DW
+1 O filtro "máscara de nitidez" realmente fez o meu dia. Outro passo que me ajudou: usando a ferramenta "seleção difusa" selecionar o fundo, em seguida, pressione Del para wightening it out
Davide
Estou preso neste problema de processamento de imagem antes do reconhecimento de tesseract stackoverflow.com/questions/32473095/… Você pode me ajudar aqui?
Hussain
Não. Eu tentei torná-lo maior, e defini-lo em escala de cinza parece que nada me dá resultado positivo. Suspiro :( Verifique este alvo: freesms4us.com/…
gumuruh 14/03
30

Três pontos para melhorar a legibilidade da imagem: 1) Redimensione a imagem com altura e largura variáveis ​​(multiplique 0,5 e 1 e 2 com altura e largura da imagem). 2) Converta a imagem no formato de escala de cinza (preto e branco). 3) Remova os pixels de ruído e deixe mais claro (Filtre a imagem).

Consulte o código abaixo:

//Resize
  public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
        {

                Bitmap temp = (Bitmap)bmp;

                Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);

                double nWidthFactor = (double)temp.Width / (double)newWidth;
                double nHeightFactor = (double)temp.Height / (double)newHeight;

                double fx, fy, nx, ny;
                int cx, cy, fr_x, fr_y;
                Color color1 = new Color();
                Color color2 = new Color();
                Color color3 = new Color();
                Color color4 = new Color();
                byte nRed, nGreen, nBlue;

                byte bp1, bp2;

                for (int x = 0; x < bmap.Width; ++x)
                {
                    for (int y = 0; y < bmap.Height; ++y)
                    {

                        fr_x = (int)Math.Floor(x * nWidthFactor);
                        fr_y = (int)Math.Floor(y * nHeightFactor);
                        cx = fr_x + 1;
                        if (cx >= temp.Width) cx = fr_x;
                        cy = fr_y + 1;
                        if (cy >= temp.Height) cy = fr_y;
                        fx = x * nWidthFactor - fr_x;
                        fy = y * nHeightFactor - fr_y;
                        nx = 1.0 - fx;
                        ny = 1.0 - fy;

                        color1 = temp.GetPixel(fr_x, fr_y);
                        color2 = temp.GetPixel(cx, fr_y);
                        color3 = temp.GetPixel(fr_x, cy);
                        color4 = temp.GetPixel(cx, cy);

                        // Blue
                        bp1 = (byte)(nx * color1.B + fx * color2.B);

                        bp2 = (byte)(nx * color3.B + fx * color4.B);

                        nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        // Green
                        bp1 = (byte)(nx * color1.G + fx * color2.G);

                        bp2 = (byte)(nx * color3.G + fx * color4.G);

                        nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        // Red
                        bp1 = (byte)(nx * color1.R + fx * color2.R);

                        bp2 = (byte)(nx * color3.R + fx * color4.R);

                        nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));

                        bmap.SetPixel(x, y, System.Drawing.Color.FromArgb
                (255, nRed, nGreen, nBlue));
                    }
                }



                bmap = SetGrayscale(bmap);
                bmap = RemoveNoise(bmap);

                return bmap;

        }


//SetGrayscale
  public Bitmap SetGrayscale(Bitmap img)
        {

            Bitmap temp = (Bitmap)img;
            Bitmap bmap = (Bitmap)temp.Clone();
            Color c;
            for (int i = 0; i < bmap.Width; i++)
            {
                for (int j = 0; j < bmap.Height; j++)
                {
                    c = bmap.GetPixel(i, j);
                    byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);

                    bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
                }
            }
            return (Bitmap)bmap.Clone();

        }
//RemoveNoise
   public Bitmap RemoveNoise(Bitmap bmap)
        {

            for (var x = 0; x < bmap.Width; x++)
            {
                for (var y = 0; y < bmap.Height; y++)
                {
                    var pixel = bmap.GetPixel(x, y);
                    if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
                        bmap.SetPixel(x, y, Color.Black);
                    else if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
                        bmap.SetPixel(x, y, Color.White);
                }
            }

            return bmap;
        }

IMAGEM DE ENTRADA
IMAGEM DE ENTRADA

IMAGEM DE SAÍDA IMAGEM DE SAÍDA

Sathyaraj Palanisamy
fonte
Sim. Precisamos passar o parâmetro necessário para o método Resize, ele pré-processará o redimensionamento, as operações SetGrayscale e RemoveNoise e retornará a imagem de saída com melhor legibilidade.
Sathyaraj Palanisamy
Tentei essa abordagem em um conjunto de arquivos e comparado com o resultado inicial. Em alguns casos limitados, obtém melhores resultados, principalmente com uma ligeira diminuição na qualidade do texto de saída. Portanto, não parece uma solução universal.
Bryn
Isso realmente funcionou muito bem para mim. Certamente, fornece um ponto de partida para o pré-processamento de imagens que remove a quantidade de bobagens que você recebe do Tesseract.
ses 31/01
22

Como regra geral, geralmente aplico as seguintes técnicas de pré-processamento de imagem usando a biblioteca OpenCV:

  1. Redimensionar a imagem (é recomendado se você estiver trabalhando com imagens com um DPI inferior a 300 dpi):

    img = cv2.resize(img, None, fx=1.2, fy=1.2, interpolation=cv2.INTER_CUBIC)
    
  2. Convertendo imagem em escala de cinza:

    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
  3. Aplicando dilatação e erosão para remover o ruído (você pode brincar com o tamanho do kernel, dependendo do seu conjunto de dados):

    kernel = np.ones((1, 1), np.uint8)
    img = cv2.dilate(img, kernel, iterations=1)
    img = cv2.erode(img, kernel, iterations=1)
    
  4. A aplicação de desfoque, que pode ser feita usando uma das seguintes linhas (cada uma com seus prós e contras, no entanto, o desfoque médio e o filtro bilateral geralmente apresentam desempenho melhor que o desfoque gaussiano.):

    cv2.threshold(cv2.GaussianBlur(img, (5, 5), 0), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.threshold(cv2.bilateralFilter(img, 5, 75, 75), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.threshold(cv2.medianBlur(img, 3), 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
    
    cv2.adaptiveThreshold(cv2.GaussianBlur(img, (5, 5), 0), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    
    cv2.adaptiveThreshold(cv2.bilateralFilter(img, 9, 75, 75), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    
    cv2.adaptiveThreshold(cv2.medianBlur(img, 3), 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 2)
    

Recentemente, escrevi um guia bastante simples para o Tesseract, mas ele deve permitir que você escreva seu primeiro script de OCR e elimine alguns obstáculos que experimentei quando as coisas estavam menos claras do que gostaria na documentação.

Caso você queira vê-los, aqui estou compartilhando os links com você:

bkaankuguoglu
fonte
por que convertemos a imagem em escala de cinza? Para ser mais específico, eu já vi no processo de detecção de imagem, a imagem é primeiro convertida em escala de cinza, depois sobel-> MSER -> SWT. você poderia elaborar isso? Eu sou novo no campo IP.
OnePunchMan
Quanto ao meu entendimento, depende do algoritmo, alguns talvez não precisem ser convertidos. Pense nos pixels como alguns valores de cores armazenados digitalmente - no caso de RGB, vermelho, verde e azul -. Quando um pixel é convertido para a escala P / B, seu algoritmo precisa funcionar apenas com 2 dimensões, em vez de 3. Isso traz vantagens óbvias na velocidade ao executar o algoritmo em pixels, um por um. Além disso, alguns também podem dizer que é mais fácil remover o ruído e detectar as bordas de uma imagem quando ela é convertida em escala de cinza.
bkaankuguoglu
Obrigado pela resposta. E sobre o seu blog, você pode escrever um sobre COMO CONSTRUIR OCR A PARTIR DE USAR TESSERACT para scripts não romanos. Tenho pesquisado em todos os lugares, tudo o que está disponível corretamente não está claro.
OnePunchMan 13/06/19
16

Isso é um pouco atrás, mas ainda pode ser útil.

Minha experiência mostra que redimensionar a imagem na memória antes de passar para o tesseract às vezes ajuda.

Experimente diferentes modos de interpolação. A postagem https://stackoverflow.com/a/4756906/146003 me ajudou muito.

Atmocreations
fonte
15

O que foi extremamente útil para mim dessa maneira são os códigos-fonte do projeto Capture2Text. http://sourceforge.net/projects/capture2text/files/Capture2Text/ .

BTW: Parabéns ao seu autor por compartilhar um algoritmo tão meticuloso.

Preste atenção especial ao arquivo Capture2Text \ SourceCode \ leptonica_util \ leptonica_util.c - essa é a essência da pré-processamento de imagem para este utilitário.

Se você executar os binários, poderá verificar a transformação da imagem antes / depois do processo na pasta Capture2Text \ Output \.

A solução mencionada PS usa o Tesseract para OCR e Leptonica para pré-processamento.

Homem sábio
fonte
1
Obrigado pela ferramenta Capture2Text. Resolve perfeitamente todos os problemas de OCR do meu projeto!
Lê Quang Duy
12

Versão Java para o código de Sathyaraj acima:

// Resize
public Bitmap resize(Bitmap img, int newWidth, int newHeight) {
    Bitmap bmap = img.copy(img.getConfig(), true);

    double nWidthFactor = (double) img.getWidth() / (double) newWidth;
    double nHeightFactor = (double) img.getHeight() / (double) newHeight;

    double fx, fy, nx, ny;
    int cx, cy, fr_x, fr_y;
    int color1;
    int color2;
    int color3;
    int color4;
    byte nRed, nGreen, nBlue;

    byte bp1, bp2;

    for (int x = 0; x < bmap.getWidth(); ++x) {
        for (int y = 0; y < bmap.getHeight(); ++y) {

            fr_x = (int) Math.floor(x * nWidthFactor);
            fr_y = (int) Math.floor(y * nHeightFactor);
            cx = fr_x + 1;
            if (cx >= img.getWidth())
                cx = fr_x;
            cy = fr_y + 1;
            if (cy >= img.getHeight())
                cy = fr_y;
            fx = x * nWidthFactor - fr_x;
            fy = y * nHeightFactor - fr_y;
            nx = 1.0 - fx;
            ny = 1.0 - fy;

            color1 = img.getPixel(fr_x, fr_y);
            color2 = img.getPixel(cx, fr_y);
            color3 = img.getPixel(fr_x, cy);
            color4 = img.getPixel(cx, cy);

            // Blue
            bp1 = (byte) (nx * Color.blue(color1) + fx * Color.blue(color2));
            bp2 = (byte) (nx * Color.blue(color3) + fx * Color.blue(color4));
            nBlue = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            // Green
            bp1 = (byte) (nx * Color.green(color1) + fx * Color.green(color2));
            bp2 = (byte) (nx * Color.green(color3) + fx * Color.green(color4));
            nGreen = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            // Red
            bp1 = (byte) (nx * Color.red(color1) + fx * Color.red(color2));
            bp2 = (byte) (nx * Color.red(color3) + fx * Color.red(color4));
            nRed = (byte) (ny * (double) (bp1) + fy * (double) (bp2));

            bmap.setPixel(x, y, Color.argb(255, nRed, nGreen, nBlue));
        }
    }

    bmap = setGrayscale(bmap);
    bmap = removeNoise(bmap);

    return bmap;
}

// SetGrayscale
private Bitmap setGrayscale(Bitmap img) {
    Bitmap bmap = img.copy(img.getConfig(), true);
    int c;
    for (int i = 0; i < bmap.getWidth(); i++) {
        for (int j = 0; j < bmap.getHeight(); j++) {
            c = bmap.getPixel(i, j);
            byte gray = (byte) (.299 * Color.red(c) + .587 * Color.green(c)
                    + .114 * Color.blue(c));

            bmap.setPixel(i, j, Color.argb(255, gray, gray, gray));
        }
    }
    return bmap;
}

// RemoveNoise
private Bitmap removeNoise(Bitmap bmap) {
    for (int x = 0; x < bmap.getWidth(); x++) {
        for (int y = 0; y < bmap.getHeight(); y++) {
            int pixel = bmap.getPixel(x, y);
            if (Color.red(pixel) < 162 && Color.green(pixel) < 162 && Color.blue(pixel) < 162) {
                bmap.setPixel(x, y, Color.BLACK);
            }
        }
    }
    for (int x = 0; x < bmap.getWidth(); x++) {
        for (int y = 0; y < bmap.getHeight(); y++) {
            int pixel = bmap.getPixel(x, y);
            if (Color.red(pixel) > 162 && Color.green(pixel) > 162 && Color.blue(pixel) > 162) {
                bmap.setPixel(x, y, Color.WHITE);
            }
        }
    }
    return bmap;
}
Fábio Silva
fonte
Qual é a sua aula para Bitmap? O bitmap não é encontrado em Java (está no Android nativamente).
Estamos Borg
Este método throughs uma excepção: Causada por: Java.lang.IllegalArgumentException: y deve ser <bitmap.height ()
Nativ
9

A documentação do Tesseract contém alguns bons detalhes sobre como melhorar a qualidade do OCR através das etapas de processamento de imagem.

Até certo ponto, o Tesseract os aplica automaticamente. Também é possível solicitar ao Tesseract que grave uma imagem intermediária para inspeção, ou seja, verifique como o processamento interno da imagem funciona (procure tessedit_write_imagesna referência acima).

Mais importante, o novo sistema de rede neural no Tesseract 4 produz resultados OCR muito melhores - em geral e especialmente para imagens com algum ruído. É ativado com --oem 1, por exemplo, como em:

$ tesseract --oem 1 -l deu page.png result pdf

(este exemplo seleciona o idioma alemão)

Portanto, faz sentido testar primeiro a que distância você chega com o novo modo Tesseract LSTM antes de aplicar algumas etapas personalizadas de processamento de imagem de pré-processamento.

maxschlepzig
fonte
6

O limiar adaptativo é importante se a iluminação for irregular na imagem. Meu pré-processamento usando o GraphicsMagic é mencionado neste post: https://groups.google.com/forum/#!topic/tesseract-ocr/jONGSChLRv4

O GraphicsMagic também possui o recurso -lat para o limiar adaptativo de tempo linear, que tentarei em breve.

Outro método de limiar usando o OpenCV é descrito aqui: http://docs.opencv.org/trunk/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html

rleir
fonte
2
O link OpenCV é alterado. Na documentação OpenCV é OpenCV-Python Tutoriais> Processamento de Imagem em OpenCV> Limiarização Imagem
richk
2

Fiz isso para obter bons resultados de uma imagem que não possui texto muito pequeno.

  1. Aplique desfoque à imagem original.
  2. Aplique o limiar adaptável.
  3. Aplique o efeito Nitidez.

E se ainda não obtiver bons resultados, redimensione a imagem para 150% ou 200%.

Hamza Iqbal
fonte
2

A leitura de texto de documentos de imagem usando qualquer mecanismo de OCR tem muitos problemas para obter uma boa precisão. Não há solução fixa para todos os casos, mas aqui estão algumas coisas que devem ser consideradas para melhorar os resultados do OCR.

1) Presença de ruído devido à baixa qualidade da imagem / elementos / blobs indesejados na região de segundo plano. Isso requer algumas operações de pré-processamento, como remoção de ruído, que podem ser feitas facilmente usando o filtro gaussiano ou métodos normais de filtro mediano. Estes também estão disponíveis no OpenCV.

2) Orientação incorreta da imagem: devido à orientação incorreta, o mecanismo OCR falha ao segmentar as linhas e palavras na imagem corretamente, o que fornece a pior precisão.

3) Presença de linhas: ao fazer a segmentação de palavras ou linhas, o mecanismo de OCR às vezes também tenta mesclar as palavras e as linhas, processando conteúdo incorreto e, portanto, fornecendo resultados incorretos. Existem outras questões também, mas essas são as básicas.

Esse aplicativo pós- OCR é um exemplo de caso em que alguns pré-processamento e pós-processamento de imagem no resultado do OCR podem ser aplicados para obter melhor precisão do OCR.

flamelite
fonte
1

O reconhecimento de texto depende de vários fatores para produzir uma saída de boa qualidade. A saída de OCR depende muito da qualidade da imagem de entrada. É por isso que todo mecanismo de OCR fornece diretrizes sobre a qualidade da imagem de entrada e seu tamanho. Essas diretrizes ajudam o mecanismo de OCR a produzir resultados precisos.

Eu escrevi um artigo detalhado sobre o processamento de imagens em python. Por favor, siga o link abaixo para obter mais explicações. Também foi adicionado o código-fonte python para implementar esses processos.

Por favor, escreva um comentário se você tiver uma sugestão ou uma idéia melhor sobre este tópico para melhorá-lo.

https://medium.com/cashify-engineering/improve-accuracy-of-ocr-using-image-preprocessing-8df29ec3a033

Brijesh Gupta
fonte
2
Adicione uma resposta aqui como um resumo do seu blog. Assim, mesmo que o link esteja inoperante, a resposta não será inútil.
Nithin 29/10/19
0

você pode reduzir o ruído e aplicar limiar, mas pode brincar com a configuração do OCR alterando os valores --psm e --oem

tente: --psm 5 --oem 2

você também pode consultar o link a seguir para obter mais detalhes aqui

sameer maurya
fonte