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


