Este artigo trata de como programar as operações morfológicas binárias apresentadas no post Morfologia Matemática para Processamento de Imagens. 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.
Utilizaremos a classe Mat do OpenCV para realizar a manipulação da imagem, com esta classe conseguimos construir de forma fácil imagens (coloridas e binárias) e manipular seus pixels.
Erosão
A erosão como apresentado no outro post citado, consiste em testar se o elemento estruturante (núcleo) se encaixa na imagem de origem, gerando uma nova imagem de destino, onde cada teste realizado com sucesso, é identificado na imagem de destino com o valor 1 e identificado com o valor 0 caso a estrutura não tenha sido localizada.
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 binárizada (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, [ 1, 1, 1, 1, 1, 1, 1, 1, 1 ]); }
Além do núcleo das operações, vamos precisar da posição central das operações, sendo que no OpenCV é utilizada a classe cv.Point para indicar posições específicas, com o objetivo de seguir o padrão, nosso código também ira utilizá-la. Para inicializar a classe, é preciso apenas informar as posições das coordenadas x e y, conforme abaixo:
const centro = new cv.Point(1, 1);
Agora que temos nosso núcleo e sua posição, nossa função de erosão foi programada com as seguintes etapas:
- Percorre todas as posições dos pixels da imagem;
- Para cara pixel da imagem, percorre todos os elementos do elemento estruturante e testa a estrutura na imagem conforme as etapas:
- Verifica-se se o elemento do núcleo possui valor e:
- Se o elemento possuir valor, verifica a posição correspondente na imagem também possui valor e:
- Se o pixel possuir valor, verifica o próximo elemento;
- Se o pixel não possuir, marca o teste como negado;
- Se o elemento possuir valor, verifica a posição correspondente na imagem também possui valor e:
- Se o elemento não possui valor, verifica o próximo elemento;
- Ao finalizar todos os testes dos elementos, do núcleo na região do pixel, e caso nenhum for negado, marca na imagem de destino o valor 255, do contrário marca como 0;
- Verifica-se se o elemento do núcleo possui valor e:
function erosao(nucleo, centro, imagem, imagemSaida) { //Percorre a imagem for (let x = centro.x; x < imagem.cols; x++) { for (let y = centro.y; y < imagem.rows; y++) { let hasNucleo = true; //Percorre o elemento estruturante (núcleo) for (let j = 0; j < nucleo.cols; j++) { for (let k = 0; k < nucleo.rows; k++) { //Verifica-se se o elemento do núcleo deve ser checado const nucleoTemValor = nucleo.ucharPtr(k, j)[0] > 0; if (nucleoTemValor) { const col = x + j - centro.x; const row = y + k - centro.y; //Verifica-se se a imagem tem valor na mesma posição do núcleo const imagemTemValor = imagem.ucharPtr(row, col)[0] > 0; if (!imagemTemValor) { hasNucleo = false; break; } } } } imagemSaida.ucharPtr(y, x)[0] = hasNucleo ? 255 : 0; } } }
Dilatação
A dilatação consiste em testar cada elemento da imagem de origem e verificar se possui valor 1, caso exista na imagem de destino, então é adicionado os valores do elemento estruturante a partir da posição central do elemento estruturante.
A programação da dilatação é menos complexa que a da erosão, nossa função realiza apenas as seguintes etapas:
- Percorre todas as posições dos pixeis da imagem;
- Para cara pixel da imagem:
- Verifica-se se o pixel possui valor e:
- Caso possuir, percorre os elementos do núcleo e os projeta na imagem de destino;
- Caso não possuir, não realiza nenhuma operação;
- Verifica-se se o pixel possui valor e:
function dilatacao(nucleo, centro, imagem, imagemSaida) { //Percorre a imagem for (let x = centro.x; x < imagem.cols; x++) { for (let y = centro.y; y < imagem.rows; y++) { //Verifica-se se o pixel da imagem possui valor positivo const pixelComValor = imagem.ucharPtr(y, x)[0] > 0; if (pixelComValor) { //Percorre o elemento estruturante (núcleo) for (let j = 0; j < nucleo.cols; j++) { for (let k = 0; k < nucleo.rows; k++) { //Verifica-se se o elemento do núcleo tem valor positivo const nucleoTemValor = nucleo.ucharPtr(k, j)[0] > 0; if (nucleoTemValor) { const col = x + j - centro.x; const row = y + k - centro.y; imagemSaida.ucharPtr(row, col)[0] = 255; } } } } } } }
Abertura
A abertura de uma imagem A, por um elemento estruturante B, é simplesmente a operação de erosão de A por B, seguida da dilatação de A por B. Como a abertura é apenas o encadeamento de duas operações, nosso código de exemplo faz apenas isto.
function abertura(nucleo, centro, imagem, imagemSaida) { let imgTemporaria = new cv.Mat( imagem.rows, imagem.cols, imagem.type(), new cv.Scalar(0)); erosao(nucleo, centro, imagem, imgTemporaria); dilatacao(nucleo, centro, imgTemporaria, imagemSaida); imgTemporaria.delete(); }
Fechamento
O fechamento de uma imagem A, por um elemento estruturante B, é simplesmente a operação de dilatação de A por B, seguida da erosão de A por B. Como o fechamento também é apenas o encadeamento de duas operações, nosso código de exemplo faz apenas isto também.
function fechamento(nucleo, centro, imagem, imagemSaida) { let imgTemporaria = new cv.Mat( imagem.rows, imagem.cols, imagem.type(), new cv.Scalar(0)); dilatacao(nucleo, centro, imagem, imgTemporaria); erosao(nucleo, centro, imgTemporaria, imagemSaida); imgTemporaria.delete(); }
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/morfologia.html
Nenhum comentário ainda!