Levenshtein menos reconhecimento de caracteres Ă  distĂąncia

Recentemente, a tarefa de reconhecimento de caracteres em programas aplicativos nĂŁo Ă© particularmente difĂ­cil - vocĂȘ pode usar muitas bibliotecas OCR prontas, muitas das quais estĂŁo quase perfeitas. PorĂ©m, Ă s vezes, pode surgir uma tarefa para desenvolver seu prĂłprio algoritmo de reconhecimento sem o uso de bibliotecas OCR "sofisticadas" de terceiros.


Essa Ă© precisamente a tarefa que surgiu durante o meu trabalho, e hĂĄ vĂĄrias razĂ”es pelas quais Ă© melhor nĂŁo usar bibliotecas prontas: o projeto fechado, com sua certificação adicional, uma certa restrição no nĂșmero de linhas de cĂłdigo e no tamanho das bibliotecas conectadas, ainda mais porque vocĂȘ precisa reconhecer o suficiente na ĂĄrea de assunto conjunto de caracteres especĂ­fico.


O algoritmo de reconhecimento Ă© simples e, Ă© claro, nĂŁo finge ser o mais preciso, rĂĄpido e eficaz, mas lida bem com sua pequena tarefa.


Suponha que tenhamos entrada na forma de imagens digitalizadas de documentos de forma estruturada. Esses documentos tĂȘm um cĂłdigo especial de um caractere localizado no canto superior esquerdo. Nossa tarefa Ă© reconhecer esse sĂ­mbolo e executar algumas açÔes, por exemplo, classificar o documento de origem de acordo com as regras fornecidas.



O diagrama do algoritmo Ă© o seguinte:




Como sabemos com antecedĂȘncia onde nosso sĂ­mbolo estĂĄ localizado, cortar uma determinada ĂĄrea nĂŁo Ă© difĂ­cil. Para remover todas as “irregularidades” das bordas do sĂ­mbolo, traduzimos a imagem em monocromĂĄtico (preto e branco).



short width = 45, height = 40, offsetTop = -10, offsetLeft = -70;
BufferedImage image = ImageIO.read(file);
BufferedImage symbol = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
Graphics2D g = symbol.createGraphics();
g.drawImage(image, offsetLeft, offsetTop, null);

Em seguida, vocĂȘ precisa converter o fragmento resultante “pixel por pixel” em uma sequĂȘncia binĂĄria, ou seja, uma sequĂȘncia em que, por exemplo, '*' corresponde a um pixel preto e um espaço em branco.



short whiteBg = -1;
StringBuilder binaryString = new StringBuilder();  
for (short y = 1; y < height; y++)
   for (short x = 1; x < width; x++) {
       int rgb = symbol.getRGB(x, y);
       binaryString.append(rgb == whiteBg ? " " : "*");
   }

Em seguida, vocĂȘ precisa encontrar a distĂąncia mĂ­nima de Levenshtein entre a string recebida e as strings de referĂȘncia prĂ©-preparadas (na verdade, vocĂȘ pode usar qualquer mĂ©todo de comparação de strings).

int min = 1000000;
char findSymbol = "";
for (Map.Entry<Character, String> entry : originalMap.entrySet()) {
     int levenshtein = levenshtein(binaryString.toString(), entry.getValue());
     if (levenshtein < min) {
             min = levenshtein;
             findSymbol = entry.getKey();
     }
}

A função de encontrar a distùncia de Levenshtein é implementada como um método de acordo com o algoritmo padrão:

public static int levenshtein(String targetStr, String sourceStr) {
        int m = targetStr.length(), n = sourceStr.length();
        int[][] delta = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++)
            delta[i][0] = i;
        for (int j = 1; j <= n; j++)
            delta[0][j] = j;
        for (int j = 1; j <= n; j++)
            for (int i = 1; i <= m; i++) {
                if (targetStr.charAt(i - 1) == sourceStr.charAt(j - 1))
                    delta[i][j] = delta[i - 1][j - 1];
                else
                    delta[i][j] = Math.min(delta[i - 1][j] + 1,
                            Math.min(delta[i][j - 1] + 1, delta[i - 1][j - 1] + 1));
            }
        return delta[m][n];
    }

O findSymbol resultante serĂĄ nosso caractere reconhecido.

Esse algoritmo pode ser otimizado para melhorar o desempenho e complementado com vĂĄrias verificaçÔes para melhorar a eficiĂȘncia do reconhecimento. Muitos indicadores dependem da ĂĄrea de assunto especĂ­fica do problema que estĂĄ sendo resolvido (nĂșmero de caracteres, variedade de fontes, qualidade de imagem etc.)

Foi verificado de forma prĂĄtica que o mĂ©todo lida qualitativamente mesmo com questĂ”es problemĂĄticas como "semelhança" de caracteres, por exemplo, "L" <-> "P", "5" <-> "S", "O" <-> "0". Como, por exemplo, a distĂąncia entre as cadeias binĂĄrias “L” e “P” serĂĄ sempre maior do que entre a “L” reconhecida e a cadeia de referĂȘncia “L”, mesmo com algumas “irregularidades”.

Em geral, se vocĂȘ precisar resolver um problema semelhante (por exemplo, reconhecimento de cartas de jogar), com vĂĄrias restriçÔes ao uso de neurĂŽnios e outras soluçÔes prontas, poderĂĄ usar e modificar esse mĂ©todo com segurança.

All Articles