OCR e Javascript - Resolvendo Captchas do Megaupload
Autor: Unknown | Publicado: 2011-08-09T13:53:00-03:00

OCR&nbsp;é um&nbsp;acrônimo&nbsp;para o&nbsp;inglês&nbsp;Optical Character Recognition, uma tecnologia para reconhecer caracteres a partir de um arquivo de imagem, ou&nbsp;mapa de bits. Através do OCR é possível digitalizar uma folha de texto impresso e obter um arquivo de texto editável&nbsp;[Wikipedia].&nbsp;Na verdade, não precisa ser necessariamente de um arquivo de imagem, OCR é também muito utilizado em dispositivos portáteis (tablets, celulares, PDAs etc.). Nos EUA, a tecnologia OCR é também utilizada pelos serviços postais, o software faz a leitura dos zip codes&nbsp;(correspondente ao nosso CEP) e redirecionada para um lugar apropriado para a entrega. Um uso desta tecnologia anunciado recentemente será feito em São Paulo, de acordo com o Olhar Digital, 500 câmeras de alta resolução serão instaladas nas rodovias da cidade e terão a função de identificar carros roubados ou que estejam com documentos irregulares, o OCR fará a leitura das placas dos carros e irá cruzar informações com um banco de dados. Fascinante!


Mas, há uma aplicação bastante peculiar que pode ser usada na internet: usar algoritmos de OCR para resolver captchas. O script que realiza esta façanha foi escrito por Shaun Friedle e está disponível para uso com a extensão GraseMonkey, apelidado de&nbsp;Megaupload auto-fill captcha greasemonkey script&nbsp;justamente por ter sido criado para resolver os captchas que existiam no Megaupload.



Um captcha qualquer
Mas como funciona isto? A primeira coisa que o algoritmo faz é a leitura da imagem, neste exemplo em particular, o autor do script utilizou a API do ainda-não-finalizado HTML5, particularmente a propriedade&nbsp;getImageData para extrair os dados da imagem e convertê-la em um array de pixels na escala de cinza. É o que a função abaixo faz:


function convert_grey(image_data){
  for (var x = 0; x &lt; image_data.width; x++){
    for (var y = 0; y &lt; image_data.height; y++){
      var i = x*4+y*4*image_data.width;
      var luma = Math.floor(image_data.data[i] * 299/1000 +
        image_data.data[i+1] * 587/1000 +
        image_data.data[i+2] * 114/1000);
      image_data.data[i] = luma;
      image_data.data[i+1] = luma;
      image_data.data[i+2] = luma;
      image_data.data[i+3] = 255;
    }
  }
}

Destaco esta função apenas para se ter uma noção geral de como é feita a conversão de cada bloco de pixel. Este script funciona apenas para os captchas do Megaupload, visto que eles possuem sempre a mesma fonte, apenas mudando as cores de cada letra. Aproveitando-se disso, agora é possível separar os três caracteres a partir de um filtro de cores:

filter(image_data[0], 105);
filter(image_data[1], 120);
filter(image_data[2], 135);

Os números 105, 120 e 135 indicam o número correspondente na escala de cinza que as letras possuem (a escala é de 0 a 255). A função filter pode ser conferida abaixo:

function filter(image_data, colour){
  for (var x = 0; x &lt; image_data.width; x++){
    for (var y = 0; y &lt; image_data.height; y++){
      var i = x*4+y*4*image_data.width;
      // Turn all the pixels of the certain colour to white
      if (image_data.data[i] == colour) {
        image_data.data[i] = 255;
        image_data.data[i+1] = 255;
        image_data.data[i+2] = 255;
      
      // Everything else to black
      } else {
        image_data.data[i] = 0;
        image_data.data[i+1] = 0;
        image_data.data[i+2] = 0;
      }
    }
  }
}
Após fazer a filtragem das letras, é preciso fazer alguns ajustes antes de aplicar a imagem à rede neural. Como o script é muito extenso, não o reproduzo aqui, mas basicamente os passos são: remover ruídos existentes e por último a imagem é cortada em um bloco 20x25, levando em conta as posições dos pixels da letra (o captcha não aparece com o mesmo alinhamento, as letras podem estar com um pouco de rotação ou desalinhadas verticalmente).  Após alguns outros ajustes geniais do autor,a matriz final contém apenas blocos contendo ou a cor branca ou a cor preta, o que já deixa claro que este array multidimensional pode ser facilmente utilizado como entrada em uma rede neural.

O uso de uma arquitetura RNA neste caso resolve muito bem o problema, visto que a rede consegue aprender o alfabeto (através do ajuste dos pesos) e depois identificar qual letra corresponde à imagem vista. E isto apesar do caractere estar um pouco rotacionado ou até mesmo deformado no final do tratamento da imagem. A arquitetura utilizada aqui consiste de 64 entradas booleanas (branco ou preto) &nbsp;e &nbsp;de 64 pesos pré-computados (também, através do ajuste dos pesos). Por fim, a rede retorna o valor que possui maior probabilidade (dentre os caracteres alfanuméricos possíveis).

As funções que definem um Neurônio estão transcritas abaixo:

function Neuron()
  {
    this.activation = 0;
    this.bias = -1;
    this.threshold = 0;
    this.weights = [];
  }

Neuron.prototype.feed = function(inputs)
  {
    this.activation = 0;
    for (var i = 0; i &lt; inputs.length; i++)
      {
        this.activation += inputs[i] * this.weights[i];
      }
    this.activation += this.bias*this.threshold;
  }

Neuron.prototype.output = function()
  {
    return 1/(1+Math.exp(-this.activation));
  }

A função de ativação usada é a sigmóide ou logística, já apresentada neste post.
As demais funções que definem a rede em si podem ser vistas abaixo e não tem muito segredo:

function NeuralNet()
  {
    this.h_layer = [];
    this.o_layer = [];
  }

NeuralNet.prototype.create_layer = function(weights)
  {
    var layer = [];

    for (var i = 0; i &lt; weights.length; i++)
      {
        var neuron = new Neuron;
        neuron.threshold = weights[i][0];
        neuron.weights = weights[i][1];

        layer.push(neuron);
      }

    return layer;
  }

NeuralNet.prototype.feed = function(inputs)
  {
    var h_outputs = [];
    
    for (var i = 0; i &lt; this.h_layer.length; i++)
      {
        this.h_layer[i].feed(inputs);
        h_outputs.push(this.h_layer[i].output());
      }

    for (var i = 0; i &lt; this.o_layer.length; i++)
      {
        this.o_layer[i].feed(h_outputs);
      }
  }

NeuralNet.prototype.output = function()
  {
    var output = [];

    for (var i = 0; i &lt; this.o_layer.length; i++)
      {
        output.push(this.o_layer[i].output());
      }

    return output;
  }

NeuralNet.prototype.test = function(inputs)
  {
    this.feed(inputs);
    return this.output();
  }

Para quem já conhece as teorias básicas para a construção de um perceptron não terá dificuldades em entender os últimos códigos postados aqui. A maior complexidade está no tratamento da imagem para inserir como entrada na rede, nada que um pouco de conhecimento em HTML5/JavaScript e uma dose de genialidade não resolva. A conclusão que se chega é que as novas tecnologias browser-side vão facilitar e agilizar muito o processamento de dados e é também fascinante perceber a imensidão de utilidades que as redes neurais proporcionam.

Você pode conferir a execução do algoritmo neste link.