Breno Ferreira

Opniões e códigos de um desenvolvedor de software .NET.

Handwriting Recognition com HTML5 Canvas

leave a comment »

Olá pessoal,

Uma das grandes novidades do HTML5 é o elemento Canvas. Ele permite que uma determinada area da página seja utilizada para desenhar, renderizar imagens e gráficos. Algumas aplicações bem legais do elemento Canvas são o SketchPad, Cloth Simulation, e até jogos são possíveis. Se voce ainda não conhece como utilizar as APIs do Canvas, recomendo ir ao Mozilla Developer Network e ver como acessar as funcionalidades básicas.

Um uso bacana que pensei foi a possibilidade de fazer o reconhecimento de escrita utilizando o elemento Canvas. Isso seria bastante útil em dispositivos móveis com telas touch-screen como tablets e smartphones.

Como fazer?

Para fazermos o reconhecimento da escrita precisamos fazer duas coisas: guarder os dados de input do usuário, conforme ele vai “escrevendo”, em seguida, mandar esses dados para o servidor para realizar o processo de reconhecimento da escrita.

 Client-Side

Primeiramente definimos o elemento Canvas, e um botão que, quando clicado, irá enviar os dados para o servidor.

<canvas id="myCanvas"></canvas>

<button id="submitBtn">Submit</button>

Depois, definimos um objeto que irá ser responsável por desenhar os traços na tela.

board = {
    isDrawing: false,
    surface: null,

    setup: function () {
        var myCanvas = document.getElementById('myCanvas')
        myCanvas.width = window.innerWidth - 25;
        myCanvas.height = window.innerHeight - 25;
        this.surface = myCanvas.getContext('2d');
        this.surface.lineWidth = 5;
        this.surface.strokeStyle = '#0000ff';
    },

    beginDraw: function (x, y) {
        this.surface.beginPath();
        this.surface.moveTo(x, y);

        this.isDrawing = true;
    },

    draw: function (x, y) {
        this.surface.lineTo(x, y);
        this.surface.stroke();
    },

    endDraw: function () {
        this.isDrawing = false;
    }
};

Primeiramente, no método Setup() definimos algumas propriedades básicas do Canvas como altura e largura na tela, tamanho do traço a ser desenhado e sua cor, e guardamos uma referência para a superficie de desenho, chamando o método getContext(‘2d’).

Em seguida, temos as funções beginDraw(x, y), draw(x, y) e endDraw(). Essas funções serão chamadas quando o usuário começar a escrever alguma coisa, enquanto ele estiver escrevendo, e quando ele terminar de escrever, respectivamente. Os métodos são bem simples, na função beginDraw, desloco o contexto de desenho para o ponto XY onde o usuário começou a escrever e seto um flag indicando que o usuário está escrevendo. Na função draw, traço uma linha até o ponto XY, e no método endDraw, simplesmente seto o flag para false, indicando que o usuário terminou de escrever.

Em seguida, associamos os eventos de input do usuário e chamamos as funções do objeto definido acima.

var strokesHistory = {
    Strokes: []
};

var currentStroke = {
    Points: []
};

board.setup();

var myCanvas = document.getElementById('myCanvas');

myCanvas.onmousedown = myCanvas.ontouchstart = function (e) {
    var xy = getInputCoordinates(e);

    currentStroke.Points.push({ X: xy.x, Y: xy.y });

    board.beginDraw(xy.x, xy.y);
};

myCanvas.onmousemove = myCanvas.ontouchmove = function (e) {
    if (board.isDrawing === true) {
        var xy = getInputCoordinates(e);

        currentStroke.Points.push({ X: xy.x, Y: xy.y });
        board.draw(xy.x, xy.y);
    }
};

myCanvas.onmouseup = myCanvas.ontouchend = function (e) {
    strokesHistory.Strokes.push(currentStroke);
    currentStroke = { Points: [] };
    board.endDraw();
};

function getInputCoordinates(e) {
    var x, y;

    if (e.changedTouches) {
        var touchData = e.changedTouches[0];
        x = touchData.clientX;
        y = touchData.clientY;
        e.preventDefault();
    }
    else {
        x = e.clientX;
        y = e.clientY;
    }

    return { x: x, y: y };
}

Repare que definimos callbacks para ambos os eventos do mouse e também para os de touch. Também criamos um objeto, strokesHistory, que guardará os dados de input (coordenadas X e Y dos traços) do usuário, assim, podemos enviar os dados para o servidor depois. Por fim, criamos uma função, getInputCoordinates(e), que retorna a coordenada XY de onde o usuário está escrevendo. A partir desse momento, o usuário já consegue escrever livremente com o elemento Canvas.

Por fim, quando o usuário clicar no botão, iremos enviar os dados via POST para o servidor, formatados como JSON. Note que iremos simplesmente exibir um alert com o resultado do reconhecimento.

$('#submitBtn').click(function () {
    $.post('/api/recognition/recognize',
            { strokes: JSON.stringify(strokesHistory)},
            function (result) {
                alert(result);
            });
});

Server-Side

No lado do servidor, iremos criar uma WebAPI, novo recurso do ASP.NET MVC 4. Caso voce ainda não conheça como funciona essa nova feature, o Elemar Jr. tem um post no seu blog explicando bem sucintamente o que é o ASP.NET WebAPI e como funciona.

Iremos chamar nosso Controller de RecognitionController, e nele teremos uma Action que aceita o verbo POST.

public class RecognitionController : ApiController
{
    [HttpPost]
    public HttpResponseMessage<String> Recognize(string strokes)
    {
        var strokePointsData = JsonConvert.DeserializeObject<dynamic>(strokes);

        var strokeCollection = GetStrokeCollectionFromPoints(strokePointsData);

        var inkAnalyzer = new InkAnalyzer();

        inkAnalyzer.AddStrokes(strokeCollection);

        var analysisStatus = inkAnalyzer.Analyze();

        if (analysisStatus.Successful)
        {
            var recognizedString = inkAnalyzer.GetRecognizedString();
            return new HttpResponseMessage<string>(recognizedString);
        }
        else
            return new HttpResponseMessage<string>("Data not recognized");
    }
}

O processo de reconhecimento é bem simples. Utilizamos uma API do Windows Presentation Foundation (WPF) chamada InkAnalyser, que, dado como input dados (coordenadas) de traços escritos pelo usuário, ela devolve uma String reconhecida no processo de análise. Porém, como estamos enviando os dados formatados como JSON, precisamos converter para uma estrutura de dados requerida pelo InkAnalyser. O método abaixo irá converter os dados deserializados do JSON para um objeto do tipo StrokeCollection.

private StrokeCollection GetStrokeCollectionFromPoints(dynamic strokePoints)
{
    var strokeCollection = new StrokeCollection();

    foreach (var stroke in strokePoints.Strokes)
    {
        var points = new StylusPointCollection();

        foreach (var point in stroke.Points)
        {
            var x = (float)point.X;
            var y = (float)point.Y;

            points.Add(new StylusPoint(x, y));
        }

        strokeCollection.Add(new Stroke(points));
    }

    return strokeCollection;
}

Agora se executarmos a aplicação, poderemos escrever alguma palavra no canvas (seja com o mouse, ou em algum dispositivo touch-screen), e clicarmos no botão no canto inferior esquerdo e esperar o resultado do reconhecimento.

 

O código dessa aplicação está no Github.

Abraços

Breno

Written by Breno Ferreira

14/03/2012 at 20:01

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s