Reconocimiento de caracteres con redes neuronales

AUTOR: Hugo Miguez CREADO: 2018-07-05 14:12:21

Utilizaremos el algoritmo de Perceptrón multicapas para reconocimiento de caracteres. Un ejemplo sencillo donde el usuario dibujará un número dentro de una casilla y la red neuronal reconocerá el mismo.

Por medio de muestras de imágenes con números dibujados a mano, alimentamos las muestras para que la red neuronal aprenda y pueda predecir que número ha dibujado el usuario.

Primer paso: Obtener los datos de las imágenes

La red neuronal necesita que los datos estén normalizados en un vector de números entre el 0 y 1. Tenienendo en cuenta ésto: necesitamos de cada imágen obtener esos datos. Lo que haremos es dividir la imagen en pequeños cuadros. (como puedes ver en la imagen siguiente). Cada cuadro ocupará una posición en el vector y el valor que indicaremos es que si ese cuadro esta pintado (aunque solo sea una pequeña zona) pondremos el valor 1 y en caso contrario 0.



Para el ejemplo hemos utilizado javascript y un objeto canvas para cargar la imagen. Emplear el objeto canvas es necesario para poder leer los byts del archivo de imagen y poder crear el vector de datos.

Puedes leer más sobre el perceptrón multicapas en: post/redes-neuronales-perceptron-multicapas

Las imágenes tienen un tamaño de 40px de ancho y 50px de alto.

Ejemplo funcional

Antes de continuar con el código te enseñamos el siguiente ejemplo para que puedas probarlo. Solo tienes que dibujar un número en el cuadro blanco que se encuentra aqui abajo. Cuando sueltes el ratón se ejecutará el script para predecir el número que has dibujado.



...

Númerotasa de acierto (0 al 1)Porcentaje
000
100
200
300
400
500

Ajustes de la red neuronal

Tasa aprendizajeError deseadoÉpocas

Muestras


Script para obtener los datos estructurados de la imagen

El siguiente script hace lo que hemos explicado al principio de la publicación. El script "ImageAnalize" analizará todas las imagenes que utilizamos para el aprendizaje de la red neuronal. Nos devolverá un array de con los objetos (array de datos) listo para enviarlos al objeto PerceptronMulticapas.

Funciones del script ImageAnalize:

  • init(size): debemos indicar la cantidad de cuadros que tendrá tanto horizontalmente como verticalmente.
  • get(canvasId, imgClass): le indicamos el id del canvas donde dibujará la imagen para obtener los datos y la clase de las imágenes. Nos devolverá una matríz de datos. el cual le pasaremos al perceptrón multicapas para crear la red neuronal y realizar el entrenamiento.
  • analize(ctx, w, h): esta función la llama la función get. su función es crear el vector con los datos de la imagen.
Puedes observar que en el script, la function get, cuando añade al vector los datos de las imágenes incluye dos datos más:
  • out: se debe indicar el resultado 0 si es incorrecto o 1 si es correcto. Es un array de un elemento ya que la clase PerceptronMulticapas asi lo requiere.
  • id: es un parametro que indica el valor que tiene dibujado en la imagen (data-value).

var ImageAnalize = function(){
    var sizeZone = 10;
    
    function init(size){
        sizeZone = size;
    }
    
    function get(canvasId, imgClass){
        var c = document.getElementById(canvasId);
        var ctx = c.getContext("2d");
        
        if(imgClass !== ""){
            var img = document.getElementsByClassName(imgClass);
            var w = c.width = img[0].width;
            var h = c.height = img[0].height;
            var out = [];
            
            for (var i = 0; i < img.length; i++) {
                ctx.drawImage(img[i], 0, 0);
                var inputs = analize(ctx, w, h);
                var salida = parseInt(img[i].getAttribute("data-value"));
                out.push({in: inputs, out:[0], id: salida});
            }
            
            return out;
        }else{
            var w = c.width;
            var h = c.height;
            return analize(ctx, w, h);
        }
    }
    
    function analize(ctx, w, h){
        var incX = w / sizeZone;
        var incY = h / sizeZone;
        
        var inputs = [];
        
        var colstart = 0;
        var colstartok = false;
        
        for (var s = 0; s < w; s+=incX){
            for (var n = 0; n < h; n+=incY){
                var imgData = ctx.getImageData(s, n, incX, incY);
                var oscuro = 0;
                for (var i = 0; i < imgData.data.length; i+=4){
                    var r = imgData.data[i];
                    var g = imgData.data[i+1];
                    var b = imgData.data[i+2];
                    if(r < 100 && g < 100 && b < 100){
                        oscuro=1;
                    }
                }
                inputs.push(oscuro);
                if(oscuro === 0 && !colstartok){
                    colstart++;
                }else{
                    colstartok = true;
                }
            }
        }
        
        var count = parseInt(colstart/sizeZone);
        var temp = [];
        
        for(var n=count*sizeZone; n < inputs.length;n++){
            temp.push(inputs[n]);
        }
        var inicio = temp.length;
        for(var n=inicio; n < inputs.length;n++){
            temp.push(0);
        }        
        return temp;  
    }
    
    return {
        init : init,
        get : get,
        analize : analize
    };
};

Script para el lienzo donde el usuario dibujará el número

El siguiente script permite que el usuario pueda dibujar dentro de un canvas

var Lienzo = function(){
    var canvasId = "";
    var canvas, ctx, flag = false,
        prevX = 0,
        currX = 0,
        prevY = 0,
        currY = 0,
        dot_flag = false;

    var x = "#000", y = 5;
    
    function init(cid){
        canvasId = cid;
        
        canvas = document.getElementById(canvasId);
        ctx = canvas.getContext("2d");
                
        canvas.addEventListener("mousemove", function (e) {
            findxy('move', e);
        }, false);
        canvas.addEventListener("mousedown", function (e) {
            clear();
            findxy('down', e);
        }, false);
        canvas.addEventListener("mouseup", function (e) {
            findxy('up', e);
        }, false);
        
        canvas.addEventListener("mouseout", function (e) {
            findxy('out', e);
        }, false);
    }
    
    function clear(){
        canvas = document.getElementById(canvasId);
        ctx.fillStyle = "white";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
    
    function draw() {
        ctx.beginPath();
        ctx.moveTo(prevX, prevY);
        ctx.lineTo(currX, currY);
        ctx.strokeStyle = x;
        ctx.lineWidth = y;
        ctx.stroke();
        ctx.closePath();
    }

    function findxy(res, e) {
        var position = getPosition(canvas);
        
        if (res === 'down') {
            prevX = currX;
            prevY = currY;
            currX = e.clientX - position.left;
            currY = e.clientY - position.top;

            flag = true;
            dot_flag = true;
            if (dot_flag) {
                ctx.beginPath();
                ctx.fillStyle = x;
                ctx.fillRect(currX, currY, 2, 2);
                ctx.closePath();
                dot_flag = false;
            }
        }
        if (res === 'up' || res === "out") {
            flag = false;
        }
        if (res === 'move') {
            if (flag) {
                prevX = currX;
                prevY = currY;
                currX = e.clientX - position.left;
                currY = e.clientY - position.top;
                draw();
            }
        }
    }
    
    function getPosition(element){
        var top = 0, left = 0;
        do {
            top += element.offsetTop  || 0;
            left += element.offsetLeft || 0;
            element = element.offsetParent;
        } while(element);
        
        var doc = document.documentElement;
        left -= (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
        top -= (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
        
        return {
            top: top,
            left: left
        };
    }
    
    function generateThumbnail(callBack) {
        var thumbnailImage = new Image();

        thumbnailImage.onload = function() {
            var thumbnailCanvas = document.getElementById(canvasId);
            var ctx = thumbnailCanvas.getContext("2d");
            
            ctx.drawImage(thumbnailImage, 0, 0);
            
            callBack(true);
        };
        thumbnailImage.src = document.getElementById(canvasId).toDataURL();
    }
    
    return {
        init : init,
        clear : clear,
        draw : draw,
        findxy : findxy,
        getPosition: getPosition,
        generateThumbnail : generateThumbnail
    };
};

Código principal

El script principal creamos los objetos PerceptronMulticapas (nuestra red neuronal) y ImageAnalize. El objeto lienzo lo hemos creado dentro de la función reset (evitamos hasta que no cargue todas las imagenes el usuario pueda dibujar en el lienzo).

var pm = new PerceptronMulticapas();
var analize = new ImageAnalize();

//vector con las entradadas para realizar el entrenamiento de la red neuronal
var inputs = [];

$(document).ready(function(){
    var total = 0;
    var count = 0;

    $('.scream').each(function(){
        total++;
    });

    //llamamos a la función inicialize() solo cuando se carguen todas las imágenes de muestra, en caso contrarío el script se quedará bloqueado.
    $('.scream').one("load", function() {
        count++;
        if(count === total){
            inicialize();
        }
    }).each(function() {
        if(this.complete) {
            $(this).load();

        }
    });
});

//función que inicializa el objeto ImageAnalize, se le indica la cantidad de cuatros en que se dividirá la imagen en ancho y alto. retornará los datos de las imagenes para enviar a la red neuronal. Además creará el lienzo donde el usuario podrá dibujar y poder llamar a la función predic.
function inicialize(){
//cantidad de cuadros a analizar de las imagenes. Cuanto más cuadros más precisa es la detección var framesCount = 10; //inicializamos el objeto indicando la cantidad de cuadros a analizar analize.init(framesCount); //obtenemos las entradas de las imagenes para enviar al perceptron y que este aprenda de estos datos inputs = analize.get("myCanvas", "scream"); var lienzo = new Lienzo(); lienzo.init("perceptronPaint"); document.getElementById("perceptronPaint").onmouseup = function (e){ lienzo.generateThumbnail(function(){ var input = analize.get("perceptronPaint", "", 0); $('#perceptron_status').html("Procesando ..."); setTimeout(function(){ predic(input); },100); }); }; } //esta función empieza entrena la red y obtiene la predicción para cada número (0 al 5) function predic(values){ var tempNumber = 0; var tempMaxValue = 0; var tasa_aprendizaje = parseFloat($('#perceptron_tasa_aprendizaje').val()); var error_deseado = parseFloat($('#perceptron_error_deseado').val()); var epocas = parseInt($('#perceptron_epocas').val()); var out = []; var i = 0; for(var i=0; i < 6; i++){ for(var n=0; n < inputs.length; n++){ if(inputs[n].id !== i){ inputs[n].out[0] = 0; }else{ inputs[n].out[0] = 1; } if(inputs[n].main === 1 || (inputs[n].main === 0 && inputs[n].id === i)){ out.push(inputs[n]); } } pm.init(inputs, tasa_aprendizaje, error_deseado, -0.25, 0.25, epocas); var val = pm.predic(values); $("#result_" + i).html(val); $("#acierto_" + i).html(parseInt(val*100) + " %"); } for(var i=0; i < 9; i++){ var v = parseFloat($("#result_" + i).html()); $("#result_" + i).parents("tr").removeClass("table-success"); if(v > tempMaxValue){ tempMaxValue = v; tempNumber = i; } } $("#result_" + tempNumber).parents("tr").addClass("table-success"); $('#perceptron_status').html("Proceso completado"); }

Código html

        <h3>Dibujar un número en el recuadro blanco</h3>
        <div class="backcanvas"><canvas id="perceptronPaint" width="40" height="50"></canvas> <span id="perceptron_status"></span></div>
        <hr/>
        
        <table class="table">
            <thead>
                <tr>
                    <td>Número</td>>
                    <td>tasa de acierto (0 al 1)</td>
                    <td>Porcentaje</td>>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>0</td>
                    <td><span id="result_0">0</span></td>
                    <td><span id="acierto_0">0</span></td>
                </tr>
                <tr>
                    <td>1</td>
                    <td><span id="result_1">0</span></td>
                    <td><span id="acierto_1">0</span></td>
                </tr>
                <tr>
                    <td>2</td>
                    <td><span id="result_2">0</span></td>
                    <td><span id="acierto_2">0</span></td>
                </tr>
                <tr>
                    <td>3</td>>
                    <td><span id="result_3">0</span></td>
                    <td><span id="acierto_3">0</span></td>
                </tr>
                <tr>
                    <td>4</td>
                    <td><span id="result_4">0</span></td>
                    <td><span id="acierto_4">0</span></td>
                </tr>
                <tr>
                    <td>5</td>
                    <td><span id="result_5">0</span></td>
                    <td><span id="acierto_5">0</span></td>
                </tr>
            </tbody>
        </table>
        
        <h3>Ajustes de la red neuronal</h3>
        <table class="table">
            <thead>
                <tr>
                    <td>Tasa aprendizaje</td>
                    <td>Error deseado</td>
                    <td>Épocas</td>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><input type="text" class="form-control" id="perceptron_tasa_aprendizaje" value="0.2" /></td>
                    <td><input type="text" class="form-control" id="perceptron_error_deseado" value="0.05" /></td>
                    <td><input type="text" class="form-control" id="perceptron_epocas" value="50000" /></td>
                </tr>
            </tbody>
        </table>
        
        <h3>Muestras</h3>
        <table class="table">
            <tbody>
                <tr>
                    <td><img src="img/cero_1.jpg" class="scream" data-value="0" /></td>
                    <td><img src="img/uno_1.jpg" class="scream" data-value="1" /></td>
                    <td><img src="img/dos_1.jpg" class="scream" data-value="2" /></td>
                    <td><img src="img/tres_1.jpg" class="scream" data-value="3" /></td>
                    <td><img src="img/cuatro_1.jpg" class="scream" data-value="4" /></td>
                    <td><img src="img/cinco_1.jpg" class="scream" data-value="5" /></td>
                </tr>
                <tr>
                    <td><img src="img/cero_2.jpg" class="scream" data-value="0" /></td>
                    <td><img src="img/uno_2.jpg" class="scream" data-value="1" /></td>
                    <td><img src="img/dos_2.jpg" class="scream" data-value="2" /></td>
                    <td><img src="img/tres_2.jpg" class="scream" data-value="3" /></td>
                    <td><img src="img/cuatro_2.jpg" class="scream" data-value="4" /></td>
                    <td><img src="img/cinco_2.jpg" class="scream" data-value="5" /></td>
                </tr>
                <tr>
                    <td><img src="img/cero_3.jpg" class="scream" data-value="0" /></td>
                    <td><img src="img/uno_3.jpg" class="scream" data-value="1" /></td>
                    <td><img src="img/dos_3.jpg" class="scream" data-value="2" /></td>
                    <td><img src="img/tres_3.jpg" class="scream" data-value="3" /></td>
                    <td><img src="img/cuatro_3.jpg" class="scream" data-value="4" /></td>
                    <td><img src="img/cinco_3.jpg" class="scream" data-value="5" /></td>
                </tr>
            </tbody>
        </table>
        
        <canvas id="myCanvas" style="display:none;"></canvas>

Descargar el ejemplo completo

Puedes descargar el ejemplo completo y probarlo en tu navegador web. Pero es necesario disponer de un servidor local instalado en tu equipo y alojarlo dentro de un dominio virtual. Ya que el objeto canvas para cargar una imagen y analizar sus datos requiere sea de esta forma, en caso contrario dara un error en el script.

Descargar ejemplo


Comentar

Para poder realizar comentarios. Primero debes inicar sesión o crear una cuenta.