O objetivo dessa atividade é demonstrar, em código, como seria feito caso quiséssemos encontrar objetos em imagens e identificar quais destes contém buracos.

Introdução

Para o mundo de processamento de imagens e visão artificial, é muito comum e muito importante que haja a possibilidade de identificar que tipo de objeto e quantos objetos estão presentes em uma cena.

Peguemos a imagem a seguir:

320
Figura 1. Bolhas (Original)

De uma forma simplista e prática, diferentemente dos humanos, máquinas não conseguem processar (sem o uso de algoritmos voltados para isso) e diferenciar quais são os tipos de objetos existentes nessa imagem. Para nós pode parecer fácil saber que há uma quantidade "x" de objetos, dos quais, "y" contém furos (ou buracos) em seu interior, porém, para que uma máquina possa processar esse tipo de informação, faz-se necessário o uso de códigos e funções que faça uma varredura em busca do objetivo.

Desenvolvimento da atividade

Nesta atividade, a imagem assume apenas 2 valores de tom de cinza. O fundo com valor "0" (preto) e os objetos com valor "255" (branco). Assim, podemos rotular os objetos presentes na imagem com um valor de tom de cinza que difere cada um.

O papel da função FloodFill é exatamente este. Processar os valores e atribuir um novo valor de tom de cinza ao "aglomerado de pixel" que encontrar - o objeto.

Porém, torna-se falho caso haja um número de objetos superior a 255, uma vez que não haveria mais um valor de tom de cinza para associar a aquele objeto. Assim, faz-se necessário a atualização do algoritmo para uma quantidade maior de objetos (caso haja). Dessa forma, fora implementado um código que reconheça até 16581375 (255x255x255) objetos. Fizemos uso da ideia de que podemos associar em RGB, ou seja, ao invés de apenas 255 tons de cinza, agora temos 255 para o Red, 255 para o Green e 255 para o Blue.

É preciso retirar as "bolhas" que tocam a borda da imagem, uma vez que não podemos identificar se estas realmente são bolhas ou não, se tem buracos ou não. Dessa forma, o programa se baseará no seguinte fluxograma:

128
Figura 2. Fluxograma a ser implementado

Código no OpenCV

O código feito pode ser visto a seguir, com os devidos comentários:

Listagem 1. bolhas.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char** argv){
    Mat image;											//Imagem a ser modificada
	int width, height;									//Largura e altura da imagem
	int i,j;											//Variáveis auxiliares para percorrer a imagem
	CvPoint p;											//Ponto da imagem a ser usado no floodfill
	unsigned long int contador_bolhas = 0;				//Variável para contagem de bolhas. A contagem é feita pela mudança RGB,
														//sendo necessário ao menos 24 bits.
	unsigned long int contador_bolhas_com_buracos = 0;	//Variável para contagem de bolhas com buracos.
	unsigned long int contador_bolhas_sem_buracos = 0;	//Variável para contagem de bolhas sem buracos.
	Vec3b cor_bolha_com_buraco(255,0,0);				//Cor identificadora de bolhas com buracos (Azul)
	Vec3b branco(255,255,255);							//Cor das bolhas
	Vec3b preto (0,0,0);								//Cor de fundo da imagem
	Vec3b label;										//Cor para labelling


	//============== Aquisição da Imagem ========================================
	image = imread(argv[1],CV_LOAD_IMAGE_COLOR);		//Leitura da imagem
	if(!image.data){									//Teste de falha na leitura
        cout << "Imagem nao carregou corretamente.\n";
        return(-1);
	}

	width=image.size().width;							//Largura da imagem
    height=image.size().height;							//Altura da imagem

	//============== Remoção de bolhas que tocam a borda da imagem ==============

	//Remoção na lateral esquerda: índice j = 0 para primeira coluna, e índice i varre todas as linhas
	//Caso encontre um pixel branco, realiza o floodfill para a cor de fundo (preto)
	i = 0; j = 0;
	for(i = 0; i < height; i++){
		if(image.at<Vec3b>(i,j) == branco){
			p.x=j; p.y=i;
			floodFill(image,p,preto);
		}
	}

	//Remoção na lateral direita: índice j = largura - 1 para a última coluna, e índice i varre todas as linhas
	//Caso encontre um pixel branco, realiza o floodfill para a cor de fundo (preto)
	i = 0; j = width - 1;
	for(i = 0; i < height; i++){
		if(image.at<Vec3b>(i,j) == branco){
			p.x=j; p.y=i;
			floodFill(image,p,preto);
		}
	}

	//Remoção na linha superior: indice i = 0 para a primeira linha, e índice j varre todas as colunas
	//Caso encontre um pixel branco, realiza o floodfill para a cor de fundo (preto)
	i = 0; j = 0;
	for(j = 0; j < width; j++){
		if(image.at<Vec3b>(i,j) == branco){
			p.x=j; p.y=i;
			floodFill(image,p,preto);
	  }
	}

	//Remoção na linha inferior: índice i = altura - 1, e índice j varre todas as colunas
	//Caso encontre um pixel branco, realiza o floodfill para a cor de fundo (preto)
	i = height - 1; j = 0;
	for(j = 0; j < width; j++){
		if(image.at<Vec3b>(i,j) == branco){
			p.x=j; p.y=i;
			floodFill(image,p,preto);
	  }
	}


	//================ Alteração da cor de fundo da imagem ==========================
	p.x=0; p.y=0;										//O ponto 0,0 faz parte do fundo da imagem
	floodFill(image,p,Scalar(1,1,1)); 					//Após essa modificação, apenas os buracos estarão com cor preta (0,0,0)

	imwrite("1.png", image);
	//================ Identificar bolhas com buracos	===============================
	for(int i=0; i<height; i++){
    for(int j=0; j<width; j++){
      if(image.at<Vec3b>(i,j) == preto){				             //Caso seja encontrado um pixel pertencente a um buraco
				if(image.at<Vec3b>(i,j-1) == cor_bolha_com_buraco){} //E o pixel anterior mostrar que a bolha já foi identificada,
																	 //Não fazer nada
				else if(image.at<Vec3b>(i,j-1) == branco){			 //Caso o pixel anterior não tenha a cor identificadora, mas seja branco
					p.x=j - 1; p.y=i;								 //Estamos tratando de uma bolha com buraco
					floodFill(image,p,cor_bolha_com_buraco);		 //Então mudamos a bolha para uma cor identificadora
				}
			}
		}
	}

	//================ Alteração da cor de fundo da imagem ==========================
	p.x=0; p.y=0;													//O ponto 0,0 faz parte do fundo da imagem
	floodFill(image,p,preto); 										//Deixa a cor de fundo como preto novamente

	imwrite("2.png", image);
	//================ Realizar o labelling das bolhas ==============================
	for(int i=0; i<height; i++){
    for(int j=0; j<width; j++){
			p.x=j; p.y=i;
			if(image.at<Vec3b>(i,j) == branco){						//Caso seja encontrado uma bolha branca
				contador_bolhas++;									//Incrementa o contador para labelling
				contador_bolhas_sem_buracos++;						//Incrementa a contagem de bolhas sem buraco
				label[0] = contador_bolhas & 0x0000FF;				//Atribui à componente B, os 8 bits menos significativos do contador
				label[1] = (contador_bolhas & 0x00FF00) >> 8;		//Atribui à componente G, os bits 15-8  do contador
				label[2] = (contador_bolhas & 0xFF0000) >> 16;		//Atribui à componente R, os bits 23-16 do contador
				floodFill(image,p,label);							//Realiza o labelling da bolha
			}
			else if(image.at<Vec3b>(i,j) == cor_bolha_com_buraco){  //Caso seja encontrado uma bolha com a cor identificadora que possui buraco
				contador_bolhas++;									//Incrementa o contador para labelling
				contador_bolhas_com_buracos++;						//Incrementa a contagem de bolhas com buraco
				label[0] =  contador_bolhas & 0x0000FF;				//Atribui à componente B, os 8 bits menos significativos do contador
				label[1] = (contador_bolhas & 0x00FF00) >> 8;		//Atribui à componente G, os bits 15-8  do contador
				label[2] = (contador_bolhas & 0xFF0000) >> 16;		//Atribui à componente R, os bits 23-16 do contador
				floodFill(image,p,label);							//Realiza o labelling da bolha
			}
		}
	}

	//============= Exibe resultado ===================================================
	cout << "Número de bolhas sem buracos: " << contador_bolhas_sem_buracos << endl;
	cout << "Número de bolhas com buracos: " << contador_bolhas_com_buracos << endl;
	cout << "Número total de bolhas: " 		 << contador_bolhas 			<< endl;

	namedWindow("Labelling",CV_WINDOW_KEEPRATIO);					//Cria janela
    imshow("Labelling", image);										//Exibe imagem na janela criada
    imwrite("labeling-result.png", image);							//Gera arquivo com a imagem resultante
    waitKey();														//Espera teclado ser pressionado para finalizar o programa

	return 0;
}

Resultados

Foram observados as imagens processadas em diferentes momentos de execução do programa. O primeiro passo, que seria realizar a retirada das bolhas que tocam as bordas, apresentou o seguinte resultado:

320
Figura 3. Imagem sem objetos que tocam a borda

Após retirada dos objetos que tocam as bordas, a imagem está preparada para ser tratada, ou seja, contar quantas destas que restaram contém buracos. Mais uma vez, a imagem anterior é modificada, fazendo a identificação de bolhas com buracos através da cor azul:

320
Figura 4. Bolhas com buracos

Por fim, podemos perceber abaixo as imagens com o floodfill, ou seja, com cada objeto e sua "identificação".

320
Figura 5. Objetos rotulados

O código então retorna o resultado:

Número de bolhas sem buracos: 14

Número de bolhas com buracos: 7

Número total de bolhas: 21