Skip to main content
Balneário Camboriú - SC +55 (47) 99725 1117
Siga-nos:

Este artigo demonstra como programar a extração de fronteiras internas e externas, e esqueletização de imagens binárias apresentadas em Morfologia Matemática – Esqueletização de imagem e Morfologia Matemática – Extração de Fronteiras / Detecção de Bordas. A programação foi realizada na linguagem javascript, utilizando a biblioteca opencv.js e as imagens produzidas com a IDE OpenCV-Flow.

OpenCV

A biblioteca opencv.js disponibiliza diversas funcionalidade prontas para o processamento de imagens e visão computacional. Para utilizá-la, basta incluí-la no script da página conforme descrito em Using OpenCV.js.

Extração de Fronteira Interna

A fronteira interna, como apresentado no outro post citado, é o contorno da imagem binarizada. O cálculo consiste em subtrair da imagem o resultado da erosão da própria imagem, por um elemento estruturante.

A primeira etapa que iremos realizar é criar o núcleo de operação com o formato de quadrado. O elemento estruturante deve ser uma imagem binarizada (preto e branco). Como não temos imagens binárias no OpenCV, criaremos uma imagem em tons de cinza.

Na função nucleoFormatoCruz abaixo, é criada uma matriz de tamanho 3×3, com um canal de cor do tipo uint8, com valores entre 0 e 255:

function nucleoFormatoCruz() {
  return new cv.matFromArray(3, 3, cv.CV_8UC1, [
    0, 1, 0,
    1, 1, 1,
    0, 1, 0
  ]);
}

Com o núcleo criado, basta realiza a erosão da imagem binarizada e depois subtrair a imagem original pelo resultado desta operação. A função abaixo realiza este processo com a biblioteca OpenCV.js:

function extrairFronteiraInterna(imagem, imagemSaida, nucleo) {
  let imgTemporaria = new cv.Mat(imagem.rows, imagem.cols, imagem.type(), new cv.Scalar(0));

  // Realiza a erosão com biblioteca opencv.
  cv.erode(imagem, imgTemporaria, nucleo);
  
  // Realiza a subtração da imagem pela mesma erodida
  cv.subtract(imagem, imgTemporaria, imagemSaida);
}

A primeira operação realizada com a função cv.erode, recebe uma imagem de entrada, uma imagem que receberá o resultado da operação de erosão e o núcleo para a operação.

A segunda e última operação realizada é a subtração com a função cv.subtract, que recebe uma imagem de entrada, uma segunda imagem com os valores das subtrações a serem realizadas, e por fim uma imagem de saída que receberá o resultado desta operação.

É possível simplificar esta função conforme abaixo:

function extrairFronteiraInterna(imagem, imagemSaida, nucleo) {
  // Realiza a erosão com biblioteca opencv.
  cv.erode(imagem, imagemSaida, nucleo);
  
  // Realiza a subtração da imagem pela mesma erodida
  cv.subtract(imagem, imagemSaida, imagemSaida);
}

Note que, foi informada a variável imagemSaida como segundo e terceiro parâmetro, da segunda operação. Foi realizado isto, pois a variável imagemSaida contém o resultado da erosão, que é preciso para subtrair os valores e para não precisarmos criar uma terceira variável, simplificando a função. Não há problemas em utilizar a mesma variável na operação cv.subtract, pois ela é uma operação não convolucional, que realiza apenas operações que utilizam um único pixel de cada vez.

Extração de Fronteiras Externas

A fronteira externa, na imagem binarizada, cria uma camada como uma vestimenta que cobre toda a imagem. O cálculo consiste em dilatar a imagem por um elemento estruturante e subtrair pela imagem original. De certa forma, o inverso da operação para extração da fronteira interna.

A primeira etapa para realizar esta operação, também consiste em criar um núcleo para a operação de dilatação. Utilizaremos o mesmo da seção anterior. Com o núcleo criado basta realizar as operações conforme a função abaixo:

function extrairFronteiraExterna(imagem, imagemSaida, nucleo) {
  let imgTemporaria = new cv.Mat(imagem.rows, imagem.cols, imagem.type(), new cv.Scalar(0));

  // Realiza a erosão com biblioteca opencv.
  cv.dilate(imagem, imgTemporaria, nucleo);
  
  // Realiza a subtração da imagem pela mesma erodida
  cv.subtract(imgTemporaria, imagem, imagemSaida);
}

Observe que para extração da fronteira externa, foi trocada a operação de erosão pela dilatação com a função cv.dilate, e alterada a ordem dos parâmetros durante a subtração com a função cv.subtract.

Esqueletização de Imagem Binarizada

Pare realizar a esqueletização com morfologia, conforme apresentado em Morfologia Matemática – Esqueletização de imagem, basta realizar quatro operações, dentro de um laço de repetição, e realizar a inicialização de algumas variáveis para este processamento.

Observe na função abaixo, que nas primeiras linhas, antes do laço while, foram inicializados variáveis para o processamento da esqueletização, criando uma imagem temporária chamada clone, com as mesmas informações da imagem original e um núcleo que é utilizado para reduzir a estrutura da imagem original.

function extrairEsqueleto(imagem, imagemSaida) {
  // Inicializa as matrizes
  const erosao = new cv.Mat(imagem.rows, imagem.cols, src.type());
  const abertura = new cv.Mat(imagem.rows, imagem.cols, src.type());
  const subtracao = new cv.Mat(imagem.rows, imagem.cols, src.type());

  // Clona a imagem original
  let clone = imagem.clone();
  // Cria o núcleo(3x3) em formato de cruz
  const nucleo = cv.getStructuringElement(cv.MORPH_CROSS, new cv.Size(3, 3), new cv.Point(-1, -1));

  while (cv.countNonZero(clone) !== 0) {
    cv.erode(clone, erosao, nucleo); 
    cv.dilate(erosao, abertura, nucleo);

    cv.subtract(clone, abertura, subtracao);

    cv.bitwise_or(imagemSaida, subtracao, imagemSaida);

    clone = erosao.clone();
    GCStore.add(clone);
  }

  // Deleta as variáveis temporárias
  delete erosao;
  delete abertura;
  delete subtracao;
  delete clone;
}

Após a inicialização das variáveis, é realizado um laço que verifica se a imagem clone está vazia (só com valores zero), caso não esteja, o laço fica realizando as seguintes operações:

  • As duas primeiras operações realizadas, dentro do laço, consistem em realizar uma erosão, seguida de uma dilatação.
    • Como resultado é criado uma abertura da imagem, pelo elemento estruturante em formato cruz;
    • Esta abertura remove os pontos das extremidades da imagem clone.
  • A terceiraça operação, realiza a subtração da imagem clone pela imagem de abertura.
    • Como resultado, na matriz subtração, estão apenas os pontos com as extremidades da imagem clone.
  • Por fim, os pontos da extremidades, são salvos na imagem de saída.

O processo é repetido até que a imagem clone seja zerada, durante o processo de erosão.

Note que, em cada laço realizado, é coletado os pontos da extremidades da imagem clone e salvo na imagem de saída, e em cada laço a imagem é diminuída de tamanho. Este processo resulta na criação do esqueleto da imagem.

Cuidados

Caso você vá utilizar a biblioteca opencv, saiba que as funções morfológicas dela também realizam morfologia matemática em imagens em tons de cinza e coloridas, que possuem resultados muito diferentes das operações morfológicas em imagens binarizadas. Lembre-se de converter a imagem em preto e branco, para realizar os experimentos aqui apresentados.

Considerações

As funções programadas neste post possuem caráter de estudo, afim de entender as etapas e os processos morfológicos, pois não foram pensadas em questões como desempenho nestes exemplos. Caso você precise utilizar este tipo de operação, é recomendado que utilize uma biblioteca preparada para isso, como o OpenCV ou similar.

O código aqui apresentado esta disponível no link abaixo:

Source code: https://github.com/visaocomputacionalexemplos/morfologia/blob/main/javascript/base/esqueletizacao.html

Nenhum comentário ainda!

Seu endereço de e-mail não será publicado