Hoje cada dia mais utilizamos dos recursos que a tecnologia nos proporciona para realizar tarefas que até então antes eram impossíveis aos olhos humanos, em todas as áreas de estudo. Neste artigo trataremos de uma técnica muito comum e nosso dia a dia, mas que muitas das vezes passa despercebida, a OCR.

Antes de começarmos a colocar a “mão na massa” e desenvolver nosso software temos que entender o que é OCR e quais sua aplicabilidade atualmente.

OCR é um acrônimo para Optical Character Recognition ou Reconhecimento Ótico de Caracteres em português. Esta tecnologia possibilita que caracteres em uma imagem ou mapa de bits sejam reconhecidos sem necessidade de intervenção humana, ou seja, ela é capaz de extrair informações a partir de uma imagem qualquer.

Um exemplo muito comum de aplicação do OCR são os radares ou conhecidos como “araras eletrônicas”, onde a câmera de alta sensibilidade captura uma foto da placa do carro que está irregular e envia para um sistema que utilizará o OCR para extrair os números e letras da imagem que foi recebida. Outro exemplo são os Correios, estes utilizam tal recurso para colocar as correspondências no seu destino correto baseado no CEP de destino, sem necessidade de intervenção humana.

Iniciando a preparação do ambiente

Neste artigo iremos trabalhar com a API Tesseract própria para reconhecimento de caracteres e provavelmente uma das mais conhecidas nesta área. O Tesseract trabalha em conjunto com a biblioteca Leptonica, que ajuda o mesmo a realizar a manipulação sobre imagens, realizando transformações, rotações e muitas outras operações que o Tesseract deixa por conta do Leptonica.

O Tesseract funciona nas seguintes plataformas: Linux, Windows e MacOS. Neste artigo trabalharemos com Linux, mais especificamente com a distribuição Ubuntu LTS 12.04 Precise.

Antes de começarmos a desenvolver qualquer coisa em Java, nós precisamos instalar e configurar o Tesseract no Linux, deixando o mesmo pronto para ser “invocado” pelo nosso programa em Java. Vamos iniciar nosso passo a passo de instalação, conforme mostra a Listagem 1.

Listagem 1. Instalando bibliotecas


  sudo apt-get install libpng-dev libjpeg-dev libtiff-dev zlib1g-dev
  sudo apt-get install gcc g++
  sudo apt-get install autoconf automake libtool checkinstall

Na Listagem 1 instalamos alguns recursos necessários e na Listagem 2 vamos instalar o Leptonica que explicarmos anteriormente.

Listagem 2. Instalando Leptonica


  wget http://www.leptonica.org/source/leptonica-1.69.tar.gz
  tar -zxvf leptonica-1.69.tar.gz
  cd leptonica-1.69
  ./configure
  make
  sudo checkinstall
  sudo ldconfig

Na Listagem 2 capturamos a versão 1.69 na biblioteca Leptonica e instalamos a mesma. O próximo passo é instalar o Tesseract, a biblioteca que utilizaremos para uso do OCR, conforme mostra a Listagem 3.

Listagem 3. Instalando Tesseract


  wget https://tesseract-ocr.googlecode.com/files/tesseract-ocr-3.02.02.tar.gz
  tar -zxvf tesseract-ocr-3.02.02.tar.gz
  cd tesseract-ocr
  ./autogen.sh
  ./configure
  make (this may take a while)
  sudo make install
  sudo ldconfig
  

A instalação dos três itens acima pode demorar um pouco devido à grande quantidade de configurações que serão realizadas. Após finalizar a Listagem 3 você terá todas as bibliotecas instaladas e configuradas no seu ambiente, porém você ainda precisa configurar as linguagens que irá utilizar caso contrário de nada adiantará todo nosso trabalho.

O Tesseract suporta muitas linguagens e de acordo com o padrão que você utilizará você pode optar pelas linguagens que precisar. Vamos instalar Inglês (Listagem 4) e Português (Listagem 5) para o nosso projeto.

Listagem 4. Instalando a linguagem Inglês


  wget http://tesseract-ocr.googlecode.com/files/tesseract-ocr-3.02.eng.tar.gz
  tar -zxvf tesseract-ocr-3.02.eng.tar.gz
  cd tesseract-ocr/tessdata/
  cp * /usr/share/tesseract-ocr/tessdata/

Listagem 5. Instalando a linguagem Português


  wget http://tesseract-ocr.googlecode.com/files/tesseract-ocr-3.02.por.tar.gz
  tar -zxvf tesseract-ocr-3.02.por.tar.gz
  cd tesseract-ocr/tessdata/
  cp * /usr/share/tesseract-ocr/tessdata/

A instalação das linguagens é simples. Você baixa o arquivo “.tar.gz”, extrai no local que desejar e copia todo conteúdo do diretório tessdata para o /usr/share/tesseract-ocr/tessdata/ que é onde está instalado o tesseract e suas linguagens. O mesmo processo é feito para todas as outras linguagens desejadas.

Com isso temos todo nosso ambiente pronto para começar ao desenvolvimento da nossa aplicação.

Iniciando desenvolvimento da aplicação

A API tesseract não foi desenvolvida em Java e para termos acesso aos seus recursos precisamos fazer uso do JNA que funciona como um Wrapper que nos possibilita usar os métodos desta API em Java. Para isso você precisa fazer o importa de algumas bibliotecas para que possamos usar os métodos da classe Tesseract. São elas:

  • ghost4j-0.5.1
  • jai_imageio
  • jna-4.1.0
  • jnuit-4.10
  • log4j-1.2.17
  • tess4j

Todas as bibliotecas estão disponíveis na opção código-fonte deste artigo. Após fazer o download das bibliotecas mencionadas acima, coloque estas no classpath da sua aplicação para que você possa fazer o import nas suas classes, como faremos mais adiante.

Vamos começar mostrando a interface da nossa aplicação, conforme mostra a Figura 1.

Interface

Figura 1. Interface

Nossa interface é simples mas funcional: Temos um JtextField logo abaixo do label 'Imagem' onde será mostrado o caminho da imagem que foi carregado através do botão 'Carregar Imagem', e no nosso JtextArea 'Texto' será mostrado o texto que foi extraído da imagem.

Para que você possa construir uma interface idêntica à que mostramos na Figura 1, disponibilizamos na Listagem 6 o nosso arquivo “.form” que pode ser usado no Netbeans.

Listagem 6. Arquivo .form da nossa interface


  <?xml version="1.0" encoding="UTF-8" ?>
   
  <Form version="1.3" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
    <Properties>
      <Property name="defaultCloseOperation" type="int" value="3"/>
    </Properties>
    <SyntheticProperties>
      <SyntheticProperty name="formSizePolicy" type="int" value="1"/>
    </SyntheticProperties>
    <AuxValues>
      <AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
      <AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
      <AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
      <AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
      <AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
    </AuxValues>
   
    <Layout>
      <DimensionLayout dim="0">
        <Group type="103" groupAlignment="0" attributes="0">
            <Group type="102" attributes="0">
                <EmptySpace max="-2" attributes="0"/>
                <Group type="103" groupAlignment="0" max="-2" attributes="0">
                    <Component id="jButtonSelecionarImagem" alignment="0" min="-2" max="-2" attributes="0"/>
                    <Component id="jTextFieldImagem" alignment="0" pref="327" max="32767" attributes="1"/>
                    <Component id="jLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
                    <Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
                    <Component id="jScrollPane1" alignment="0" max="32767" attributes="1"/>
                </Group>
                <EmptySpace pref="24" max="32767" attributes="0"/>
            </Group>
        </Group>
      </DimensionLayout>
      <DimensionLayout dim="1">
        <Group type="103" groupAlignment="0" attributes="0">
            <Group type="102" alignment="0" attributes="0">
                <EmptySpace max="-2" attributes="0"/>
                <Component id="jLabel1" min="-2" max="-2" attributes="0"/>
                <EmptySpace max="-2" attributes="0"/>
                <Component id="jTextFieldImagem" min="-2" max="-2" attributes="0"/>
                <EmptySpace min="-2" pref="20" max="-2" attributes="0"/>
                <Component id="jLabel2" min="-2" max="-2" attributes="0"/>
                <EmptySpace max="-2" attributes="0"/>
                <Component id="jScrollPane1" min="-2" max="-2" attributes="0"/>
                <EmptySpace pref="35" max="32767" attributes="0"/>
                <Component id="jButtonSelecionarImagem" min="-2" max="-2" attributes="0"/>
                <EmptySpace max="-2" attributes="0"/>
            </Group>
        </Group>
      </DimensionLayout>
    </Layout>
    <SubComponents>
      <Component class="javax.swing.JLabel" name="jLabel1">
        <Properties>
          <Property name="text" type="java.lang.String" value="Imagem"/>
        </Properties>
      </Component>
      <Component class="javax.swing.JTextField" name="jTextFieldImagem">
        <Properties>
          <Property name="enabled" type="boolean" value="false"/>
        </Properties>
      </Component>
      <Component class="javax.swing.JButton" name="jButtonSelecionarImagem">
        <Properties>
          <Property name="text" type="java.lang.String" value="Carregar Imagem"/>
        </Properties>
        <Events>
          <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="jButtonSelecionarImagemActionPerformed"/>
        </Events>
      </Component>
      <Component class="javax.swing.JLabel" name="jLabel2">
        <Properties>
          <Property name="text" type="java.lang.String" value="Texto"/>
        </Properties>
      </Component>
      <Container class="javax.swing.JScrollPane" name="jScrollPane1">
        <AuxValues>
          <AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
        </AuxValues>
   
        <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
        <SubComponents>
          <Component class="javax.swing.JTextArea" name="jTextAreaOCR">
            <Properties>
              <Property name="columns" type="int" value="20"/>
              <Property name="rows" type="int" value="5"/>
            </Properties>
          </Component>
        </SubComponents>
      </Container>
    </SubComponents>
  </Form>

Agora iremos começar a construção do nosso formulário passo a passo, como mostra a Listagem 7.

Listagem 7. FtesseractApp - início do processamento


  //1
  private void jButtonSelecionarImagemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonSelecionarImagemActionPerformed
         iniciarProcessamento();
      }//GEN-LAST:event_jButtonSelecionarImagemActionPerformed
      
        //2
         public void iniciarProcessamento() {
               try {
                      JFileChooser file = new JFileChooser();
                      file.setFileSelectionMode(JFileChooser.FILES_ONLY);
                      int i = file.showSaveDialog(this);
                      if (i != 1) {
                             File arquivo = file.getSelectedFile();
                             jTextFieldImagem.setText(arquivo.getAbsolutePath());
                             Tesseract tess = Tesseract.getInstance();
                             tess.setLanguage("eng");
   
                             jTextAreaOCR.setText(tess.doOCR(arquivo));
   
                      }
               } catch (TesseractException e) {
                      JOptionPane.showMessageDialog(this, "Erro ao processar OCR");
                      e.printStackTrace();
               }
         }

Acima mostramos os dois métodos principais para funcionamento do nosso programa. O método jButtonSelecionarImagemActionPerformed() é acionado por um listener que está no nosso botão, este método aciona o método iniciarProcessamento() que fará todo o processo OCR que desejamos.

No método iniciarProcessamento() nós precisamos definir um bloco try-catch pois o método “doOCR()” pode disparar uma exceção TesseractException. Logo no início temos a criação de um JfileChooser com o selectionMode definido para FILES_ONLY, este nos garante que o usuário apenas escolherá arquivos e não diretórios. Nossa variável 'i' recebe o retorno do método showSaveDialog() que será diferente de 1 quando o usuário selecionar algum arquivo e clicar em salvar, ou seja, não clicar em cancelar.

Dentro da condição onde 'i' for diferente de 1 nós temos a criação de um objeto do tipo Tesseract, através do método Tesseract.getInstance(). É importante notar que é utilizado aqui o padrão de projeto Singleton, que garante apenas uma instância do objeto Tesseract por contexto. Com o objeto em mãos, nós definimos a linguagem que vamos utilizar através do método tess.setLanguage(“eng”), mas poderíamos definir português também da seguinte forma: tess.setLanguage(“por”). Por fim, executamos o método doOCR() passando por parâmetro um objeto do tipo File que contém nossa imagem, esse método retorna uma String que são os caracteres reconhecidos na imagem.

Para finalizar este artigo, iremos postar a classe FtesseractApp completa na Listagem 8.

Listagem 8. Classe FtesseractApp completa


  package br.com.dev.gui;
   
  import java.io.File;
   
  import javax.swing.JFileChooser;
  import javax.swing.JOptionPane;
   
  import net.sourceforge.tess4j.Tesseract;
  import net.sourceforge.tess4j.TesseractException;
   
  public class FTesseractApp extends javax.swing.JFrame {
      
      /** Creates new form FTesseractApp */
      public FTesseractApp() {
          initComponents();
      }
      
      /** This method is called from within the constructor to
       * initialize the form.
       * WARNING: Do NOT modify this code. The content of this method is
       * always regenerated by the Form Editor.
       */
      // <editor-fold defaultstate="collapsed" desc=" Código Gerado ">//GEN-BEGIN:initComponents
      private void initComponents() {
          jLabel1 = new javax.swing.JLabel();
          jTextFieldImagem = new javax.swing.JTextField();
          jButtonSelecionarImagem = new javax.swing.JButton();
          jLabel2 = new javax.swing.JLabel();
          jScrollPane1 = new javax.swing.JScrollPane();
          jTextAreaOCR = new javax.swing.JTextArea();
   
          setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
          jLabel1.setText("Imagem");
   
          jTextFieldImagem.setEnabled(false);
   
          jButtonSelecionarImagem.setText("Carregar Imagem");
          jButtonSelecionarImagem.addActionListener(new java.awt.event.ActionListener() {
              public void actionPerformed(java.awt.event.ActionEvent evt) {
                  jButtonSelecionarImagemActionPerformed(evt);
              }
          });
   
          jLabel2.setText("Texto");
   
          jTextAreaOCR.setColumns(20);
          jTextAreaOCR.setRows(5);
          jScrollPane1.setViewportView(jTextAreaOCR);
   
          javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
          getContentPane().setLayout(layout);
          layout.setHorizontalGroup(
              layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(layout.createSequentialGroup()
                  .addContainerGap()
                  .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                      .addComponent(jButtonSelecionarImagem)
                      .addComponent(jTextFieldImagem, javax.swing.GroupLayout.DEFAULT_SIZE, 327, Short.MAX_VALUE)
                      .addComponent(jLabel1)
                      .addComponent(jLabel2)
                      .addComponent(jScrollPane1))
                  .addContainerGap(24, Short.MAX_VALUE))
          );
          layout.setVerticalGroup(
              layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
              .addGroup(layout.createSequentialGroup()
                  .addContainerGap()
                  .addComponent(jLabel1)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(jTextFieldImagem, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                  .addGap(20, 20, 20)
                  .addComponent(jLabel2)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                  .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 35, Short.MAX_VALUE)
                  .addComponent(jButtonSelecionarImagem)
                  .addContainerGap())
          );
          pack();
      }// </editor-fold>//GEN-END:initComponents
   
      private void jButtonSelecionarImagemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonSelecionarImagemActionPerformed
         iniciarProcessamento();
      }//GEN-LAST:event_jButtonSelecionarImagemActionPerformed
      
         public void iniciarProcessamento() {
               try {
                      JFileChooser file = new JFileChooser();
                      file.setFileSelectionMode(JFileChooser.FILES_ONLY);
                      int i = file.showSaveDialog(this);
                      if (i != 1) {
                             File arquivo = file.getSelectedFile();
                             jTextFieldImagem.setText(arquivo.getAbsolutePath());
                             Tesseract tess = Tesseract.getInstance();
                             tess.setLanguage("eng");
   
                             jTextAreaOCR.setText(tess.doOCR(arquivo));
   
                      }
               } catch (TesseractException e) {
                      JOptionPane.showMessageDialog(this, "Erro ao processar OCR");
                      e.printStackTrace();
               }
         }
      
      /**
       * @param args the command line arguments
       */
      public static void main(String args[]) {
          java.awt.EventQueue.invokeLater(new Runnable() {
              public void run() {
                  new FTesseractApp().setVisible(true);
              }
          });
      }
      
      // Declaração de variáveis - não modifique//GEN-BEGIN:variables
      private javax.swing.JButton jButtonSelecionarImagem;
      private javax.swing.JLabel jLabel1;
      private javax.swing.JLabel jLabel2;
      private javax.swing.JScrollPane jScrollPane1;
      private javax.swing.JTextArea jTextAreaOCR;
      private javax.swing.JTextField jTextFieldImagem;
      // Fim da declaração de variáveis//GEN-END:variables
  }

Este artigo teve como principal função mostrar como usar a técnica de OCR utilizando a API Tesseract. Existem outras bibliotecas pagas para realizar tal processo, optamos pela Tesseract por tratar-se de uma biblioteca Free e com código OpenSource, possibilitando maior aprendizado e manipulação da mesma.

A aplicação de tal recurso é muito abrangente, como explicamos logo no início deste artigo, você poderá adicionar tais funcionalidades como módulo a um sistema já existente ou desenvolver um sistema complexo e poderoso de reconhecimento de caracteres. Perceba que há alguns problemas no retorno do texto realizado pelo Tesseract, isso porque ele não é 100% preciso, deixando a você o trabalho de fazer um tratamento mais minucioso do texto retornado.