Analizando código fuente JavaScript para decodificar Captchas

captcha 

Siguiendo el hilo del post creado por nuestro colega tuxotron, vamos a ver cómo funciona un programa en JavaScript para pasar el test de un Captcha, en concreto para Megaupload.

Nuestro interés está por encima de pasar o no el test del Megaupload, radica en la compleja estructura del programa y el objetivo final del mismo, que no es otro que emular al ser humano a la hora de analizar una imágen.

Shaun Friedle  es el creador de este código. Utiliza el HTML 5 Canvas getImageData API para obtener los pixeles de la imágen. El script también incluye la implementación de una red neuronal escrita en JavaScript puro. Los datos extraidos de la imágen se envían a la red neuronal para adivinar exactamente qué caracteres se han usado, usando técnicas de OCR.

El código fuente completo puedes encontrarlo aquí:

http://userscripts.org/scripts/review/38736

Analizándolo un poco:

1. El Captcha es copiado al Canvas y convertido en escala de grises:

function convert_grey(image_data){
  for (var x = 0; x < image_data.width; x++){
    for (var y = 0; y < 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;
    }
  }
}

2. Luego se separa en tres matrices de pixeles diferentes por cada caracter:

filter(image_data[0], 105);
filter(image_data[1], 120);
filter(image_data[2], 135);
function filter(image_data, colour){
  for (var x = 0; x < image_data.width; x++){
    for (var y = 0; y < 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;
      }
    }
  }
}

3. Luego se eliminan todos los pixeles que no podemos analizar (posiblemente sean basura), esto se consigue buscando pixeles blancos que están rodeados por otros pixeles negros:

var i = x*4+y*4*image_data.width;
var above = x*4+(y-1)*4*image_data.width;
var below = x*4+(y+1)*4*image_data.width; 
 
if (image_data.data[i] == 255 &&
  image_data.data[above] == 0 &&
  image_data.data[below] == 0)  {
  image_data.data[i] = 0;
  image_data.data[i+1] = 0;
  image_data.data[i+2] = 0;
}

4. Aquí empieza lo bueno, ahora tenemos que alimentar la red neuronal pero aún tenemos que hacer pasar la información por un nuevo filtro, analizar los bordes. Para ello el código crea un rectángulo usando los pixeles situados en los extremos, convirtiendo la imágen en una matriz de 20x25.  Al final de este punto tenemos dicha matriz que contiene un dibujo en blanco y negro:

cropped_canvas.getContext("2d").fillRect(0, 0, 20, 25);
var edges = find_edges(image_data[i]);
cropped_canvas.getContext("2d").drawImage(canvas, edges[0], 
  edges[1], edges[2]-edges[0], edges[3]-edges[1], 0, 0,
  edges[2]-edges[0], edges[3]-edges[1]); 
image_data[i] = cropped_canvas.getContext("2d").getImageData(0, 0,
  cropped_canvas.width, cropped_canvas.height);

5. Ahora es cuando se alimenta la red neuronal con los datos obtenidos. Esta red analiza la información y reduce aún más los datos, ya que extraerá sólo 64 estados seleccionados según el criterio programado. Una vez seleccionados los puntos, la red se dedica a compararla con las letras del alfabeto una a una, hasta que consigue una que se aproxime a los valores que buscamos. En cada comparación se le asigna una puntuación a cada letra en función del éxito obtenido, por ejemplo "A" 80%, "B" 45%, etc ...

 Realmente espectacular todo lo que podemos aprender de este código.

Noticia original:

http://ejohn.org/blog/ocr-and-neural-nets-in-javascript/