PUCPR- Pontifícia Universidade Católica Do Paraná PPGIA- Programa de Pós-Graduação Em Informática Aplicada PROF. DR. JACQUES FACON Processo de Afinamento de Stentiford Resumo O algoritmo estudado para este trabalho é baseado no processo de afinamento de Stentiford. Este algoritmo será integrado ao programa PDImagem afim de se obter uma nova ferramenta para o processamento de imagens. Este algoritmo tem a finalidade de afinar os objetos contidos em uma imagem binária. O que se deseja com o afinamento é fazer com que os objetos da imagem fiquem apenas um pixel de largura e/ou altura no final de seu processamento, como se fosse um esqueleto deste objeto. Como acontece na maioria dos algoritmos de afinamento, o algoritmo de Stentiford se baseia na remoção de pixels por camadas. Várias iterações são feitas para remoção de cada camada. Estas iterações ocorrem até não existirem mais camadas a serem retiradas. O processo de remoção, como e qual pixel será removido, é definido através de algumas regras e máscaras. Palavras chaves: afinamento, Stentiford, processamento, imagem, binária, morfologia.
Introdução O objetivo deste estudo é o aprendizado de técnicas de processamento de imagens binárias, aprimorando, ao mesmo tempo, o software PDimagem. Este software está sendo desenvolvido para fins didáticos e servirá para futuros estudantes possam acrescentar novos métodos e aprender os que já estão implementados. O algoritmo de Stentiford foi estudado e em seguida implementado e adicionado ao PDimagem no Microsoft Visual C++ 5.0. Algoritmo de Stentiford O algoritmo de afinamento é descrito utilizando seis passos. Antes da descrição do algoritmo, serão descritos alguns conceitos necessários a este. Pixels: Um pixel (picture element) representa a menor parte de uma imagem. É um único ponto da imagem que, em nosso caso, poderá ser apenas branco (pixel apagado) ou preto (existe um pixel a ser considerado). O ponto branco tem valor igual a 1 e o preto igual a zero. Máscaras (moldes): Quatro máscaras M1, M2, M3 e M4 serão utilizadas para este processo: M1 M2 M3 M4 Onde o círculo branco representa um pixel branco, o círculo preto representa um círculo preto e o X representa que não importa a cor do pixel. Estas máscaras devem percorrer a imagem na seguinte ordem: M1 da esquerda para a direita e de cima para baixo; M2 de baixo para cima e da esquerda para a direita; M3 da direita para a esquerda e de baixo para cima; M4 de cima para baixo e da direita para a esquerda; Pixels Vizinhos (Neighbors): Um pixel tem oito pixels vizinhos. Eles serão numerados da seguinte forma:
N4 N3 N2 N5 N0 N1 N6 N7 N8 Obs.: N0 é o pixel analisado. Ponto Final (Endpoint): Um ponto final é um pixel preto que tem apenas um outro pixel preto como seu vizinho. Estes pixels não podem ser apagados. Número de Conectividade (Connectivity Number): Alguns pixels não podem ser apagados pois eles fazem a ligação entre duas partes de um objeto. Se eles forem apagados, o objeto se dividirá em outros objetos menores e isto não pode acontecer. O pixel analisado só poderá ser apagado se o seu número de conectividade (C n ) for igual a um. Para determinar o número de conectividade foram estudados dois métodos: 1. Através da seguinte fórmula: C S n = = k S N k 1,3,5,7 ( Nk Nk + 1 Nk + 2) 2. O número de conectividade C n é igual ao número de mudanças de preto para branco percorrendo os vizinhos na seguinte ordem: N 1, N 2, N 3, N 4, N 5, N 6, N 7, N 8, N 1. Agora, os passos do algoritmo: 1. Percorrer a imagem até encontrar um pixel que se encaixe na máscara M1. 2. Se este pixel não for um ponto final e se o seu número de conectividade = 1, marcar este ponto para que seja apagado mais tarde. 3. Repetir os passos 1 e 2 para todos os pixels que se encaixem na máscara M1. 4. Repetir os passo 1, 2 e 3 para cada uma das máscaras M2, M3 e M4, nesta ordem. 5. Se algum ponto estiver marcado para ser apagado, ele deve ser apagado mudando-o para a cor branca.
6. Se algum ponto foi apagado no passo 5, repetir todos os passos a partir do passo 1. Senão, o processo termina. Exemplos Conclusão O algoritmo descrito foi implementado e mostrou funcionar somente para algumas imagens. Para outras imagens, os objetos afinados apresentaram descontinuidade nos seu pixels, o que não pode acontecer. A causa mais provável desta descontinuidade é alguma falha ou imperfeição no processo que verifica o número de conectividade de um pixel. O algoritmo implementado não levou em consideração o fato das máscaras M1, M2, M3 e M4 terem que ser passadas em ordens diferentes pela imagem. Esta ordem não mostrou ser importante já que os pixels só serão realmente alterados em outra etapa (passo) do processo. Referência: [STEN-83] Stentiford F.W. and Mortimer R.G., Some New Heuristics for Thinning Binary Handprinted Characters for OCR, in Algorithms for Image Processing and Computer Vision, JR Parker- John Wiley &Sons, Inc, 1997.
Rotina Implementada O algoritmo foi implementado como um método da classe CpdiMorfoB e seu código é: int CPdiMorfoB::AfinamentoStentiford(CDC *pdc) bool ImagemAlterada = true; struct InfoPixels int x; int y; bool V[9]; // obs: cada pixel tem 8 Vizinhos, a entrada bool bapagapixel; // de índice 0 (V[0]) será ignorada ; int dx = (int) GetWidth() - 1; int dy = (int) GetHeight() - 1; HBITMAP HImaAux; CDC DcAux; DcAux.CreateCompatibleDC(NULL); // dx recebe largura da imagem original // dy recebe altura da imagem original // cria um CDC auxiliar HImaAux = CreateDIBitmap(pDC->m_hDC,lpBI,CBM_INIT,lpBits,lpbmi,DIB_RGB_COLORS); if((::selectobject(dcaux.m_hdc, HImaAux)) == NULL) return 0; while(imagemalterada) int x, y, n; UINT index = 0; DWORD qtpixels = 0; // conta o numero de pixels pretos na imagem for(y = 1; y < dy; y++) for(x = 1; x < dx; x++) if( DcAux.GetPixel(x, y) == (RGB(0,0,0)) ) qtpixels++; // aloca espaço necessário para guardar as informações de // todos os pixels pretos InfoPixels *Info = new InfoPixels[qtPixels]; // guarda as informações sobre cada pixel na estrutura Info for(y = 1; y < dy; y++) for(x = 1; x < dx; x++) if( DcAux.GetPixel(x, y) == (RGB(0,0,0)) ) Info[index].x = x; Info[index].y = y; Info[index].bApagaPixel = FALSE; if( DcAux.GetPixel(x+1, y) == (RGB(0,0,0)) ) Info[index].V[1] = false; Info[index].V[1] = true; if( DcAux.GetPixel(x+1, y-1) == (RGB(0,0,0)) ) Info[index].V[2] = false; Info[index].V[2] = true; if( DcAux.GetPixel(x, y-1) == (RGB(0,0,0)) )
Info[index].V[3] = false; Info[index].V[3] = true; if( DcAux.GetPixel(x-1, y-1) == (RGB(0,0,0)) ) Info[index].V[4] = false; Info[index].V[4] = true; if( DcAux.GetPixel(x-1, y) == (RGB(0,0,0)) ) Info[index].V[5] = false; Info[index].V[5] = true; if( DcAux.GetPixel(x-1, y+1) == (RGB(0,0,0)) ) Info[index].V[6] = false; Info[index].V[6] = true; if( DcAux.GetPixel(x, y+1) == (RGB(0,0,0)) ) Info[index].V[7] = false; Info[index].V[7] = true; if( DcAux.GetPixel(x+1, y+1) == (RGB(0,0,0)) ) Info[index].V[8] = false; Info[index].V[8] = true; index++; inferior é preto // faz operações para cada uma das máscaras for(int M = 1; M <= 4; M++) for(index = 0; index < qtpixels; index++) int NumPixelsVizinhos = 0, NumConect = 0; bool bpontoterm; bool status = false; == 1) && (Info[index].V[7] == 0) ) direito é preto == 1) && (Info[index].V[1] == 0) ) inferior é branco == 0) && (Info[index].V[7] == 1) ) direito é branco == 0) && (Info[index].V[1] == 1) ) switch( M ) case 1: // verifica se pixel superior é branco e se pixel if( (!Info[index].bApagaPixel) && (Info[index].V[3] break; status = true; case 2: // verifica se pixel esquerdo é branco e se pixel if( (!Info[index].bApagaPixel) && (Info[index].V[5] break; status = true; case 3: // verifica se pixel superior é preto e se pixel if( (!Info[index].bApagaPixel) && (Info[index].V[3] break; status = true; case 4: // verifica se pixel esquerdo é preto e se pixel if( (!Info[index].bApagaPixel) && (Info[index].V[5] break; status = true;
if( status ) // verifica se pixel é ponto terminal (Endpoint) for(n = 1; n <= 8 ; n++) if( Info[index].V[n] == 0 ) NumPixelsVizinhos++; if( NumPixelsVizinhos == 1 ) bpontoterm = true; bpontoterm = false; // verifica o numero de conectividade (Connectivity Number) /* NumConect = Info[index].V[1] - (Info[index].V[1] * Info[index].V[2] * Info[index].V[3]); NumConect += Info[index].V[3] - (Info[index].V[3] * Info[index].V[4] * Info[index].V[5]); NumConect += Info[index].V[5] - (Info[index].V[5] * Info[index].V[6] * Info[index].V[7]); NumConect += Info[index].V[7] - (Info[index].V[7] * Info[index].V[8] * Info[index].V[1]); */ for(int temp1 = 1;temp1 <= 8; temp1++) int temp2; if(temp1 == 8) temp2 = 1; temp2 = temp1+1; (Info[index].V[temp2] == 1) ) if( (Info[index].V[temp1] == 0) && NumConect++; // se o pixel não é terminal e seu número de // conectividade é 1, marca este pixel para // ser apagado if( (!bpontoterm) && (NumConect == 1) ) Info[index].bApagaPixel = true; ImagemAlterada = false; // apaga todos os pixels marcados for(index = 0; index < qtpixels; index++) if( Info[index].bApagaPixel ) DcAux.SetPixel( Info[index].x, Info[index].y, RGB(255,255,255) ); ImagemAlterada = true; delete Info; GetDIBits(DcAux.m_hDC, HImaAux, 0, (WORD) GetHeight(), lpbits, lpbmi, DIB_RGB_COLORS); // copia os bits do CDC auxiliar ReleaseDC(NULL,DcAux.m_hDC); DcAux.DeleteDC(); if(himaaux!= NULL) DeleteObject(HImaAux); return 1;