quinta-feira, 16 de abril de 2009

Exibir imagem do OpenCV (IplImage)

Além do FOX Toolkit, eu utilizo a biblioteca OpenCV (Open Source Computer Vision Library), pois trabalho com Processamento de Imagens. Então surgiu a necessidade de juntar os dois.

O OpenCV até fornece alguns recursos de interface gráfica, mas são muito limitados: apenas janelas, trackbars e tratamento de eventos de mouse e teclado. A próxima figura mostra uma janela com uma trackbar.


Quebrando, então, a seqüência de tutoriais básicos, vou ensinar como exibir uma imagem do OpenCV (IplImage) em um aplicativo FOX.

Para isso, criarei um aplicativo que abre uma imagem pelo OpenCV, exibe e aplica o filtro da média. Não vou falar da criação da interface e do tratamento de eventos, apenas mostrar como se faz a "conversão" de IplImage para FXImage.

Na declaração da classe, eu apenas declaro os ponteiros para as imagens:

19 FXImageView *imageView;
20
21 FXImage *fxImage;
22 IplImage *iplImage;

No construtor, eu apenas atribuo nulo a ambas, já que o usuário é quem vai escolher a imagem.

32 fxImage = 0;
33 iplImage = 0;

A "conversão" é feita ao carregar a imagem:

52 long MainWindow::onCmdOpen(FXObject*, FXSelector, void*) {
53 FXFileDialog dialog(this, "Abrir imagem");
54
55 if (!dialog.execute(PLACEMENT_OWNER))
56 return 1;
57
58 if (fxImage)
59 delete fxImage;
60
61 if (iplImage)
62 cvReleaseImage(&iplImage);
63
64 FXString filename = dialog.getFilename();
65
66 IplImage *temp = cvLoadImage(filename.text());
67 iplImage = cvCreateImage(cvGetSize(temp), IPL_DEPTH_8U, 4);
68
69 cvCvtColor(temp, iplImage, CV_BGR2RGBA);
70
71 cvReleaseImage(&temp);
72
73 FXColor *colorData = reinterpret_cast<FXColor*> (iplImage->imageData);
74
75 fxImage = new FXImage(getApp(), colorData,
76 IMAGE_SHMP|IMAGE_SHMI|IMAGE_KEEP,
77 iplImage->width, iplImage->height);
78
79 fxImage->create();
80
81 imageView->setImage(fxImage);
82
83 return 1;
84 }

Aqui, eu crio uma imagem temporária só para carregar a imagem do arquivo. Neste caso, ele abre uma imagem colorida com três canais de 8 bits.

A representação interna do IplImage é um vetor de caracteres e os pixels são armazenados em BGR.

A representação interna do FXImage é um vetor de FXColor (equivalente a um inteiro sem sinal). Em cada inteiro são armazenados quatro canais (um byte para cada): RGBA.

Bom, aí já temos uma semelhança: ambos armazenam em um vetor. Isso que dizer que eu posso aproveitar o vetor de pixels do IplImage e jogar dentro do FXImage. Basta converter a representação interna.

Então, cria-se uma IplImage com quatro canais e profundidade de 8 bits sem sinal. Esta é a que foi declarada em nossa classe.

67 iplImage = cvCreateImage(cvGetSize(temp), IPL_DEPTH_8U, 4);

Basta então converter nossa imagem temporária de BGR para RGBA:

69 cvCvtColor(temp, iplImage, CV_BGR2RGBA);

Assim, já temos uma IplImage com representação interna parecida com a de FXImage. É preciso ainda converter de char* para FXColor*. Essa não é uma conversão padrão, portanto é preciso usar um operador especial para isso.

Eu acho até dolorido usá-lo, mas não vi outra alternativa.

73 FXColor *colorData = reinterpret_cast<FXColor*> (iplImage->imageData);

Com um vetor de pixels no formato interno do FXImage, é só criar nossa imagem:

75 fxImage = new FXImage(getApp(), colorData,
76 IMAGE_SHMP|IMAGE_SHMI|IMAGE_KEEP,
77 iplImage->width, iplImage->height);
78
79 fxImage->create();

Como a imagem foi criada depois do aplicativo, é preciso chamar create() manualmente.

Manipulando a imagem

Agora, temos um FXImage com o vetor de pixels compartilhado com o IplImage. Isso significa que qualquer operação executada sobre a imagem Ipl refletirá na imagem FX.

Isso é demonstrado aqui, onde eu aplico o filtro da média para suavizar a imagem:

88 long MainWindow::onCmdSmooth(FXObject*, FXSelector, void*) {
89 cvSmooth(iplImage, iplImage, CV_BLUR, 3, 3);
90
91 fxImage->render();
92
93 imageView->update();
94
95 return 1;
96 }

Os únicos detalhes é que é preciso chamar render() de FXImage e atualizar o imageView.

O render() é necessário porque há duas representações do FXImage: uma do lado do cliente, que neste caso é o vetor compartilhado entre as duas imagens, e o lado do servidor gráfico. render() atualiza o lado do servidor gráfico, de forma que seja exibida a imagem modificada.

E é preciso atualizar o imageView porque ele não detecta automaticamente alterações na imagem, então é preciso informar manualmente que ele deve se redesenhar.

Trabalhando com vídeos

O OpenCV, além de imagens estáticas, também trabalha com vídeos, que podem ser capturados de câmeras ou lidos de arquivos.

Cada quadro do vídeo é recuperado em um IplImage. Isso significa que dá para usar a técnica descrita acima. O problema é que a imagem que representa cada quadro não pode ser deletada nem modificada. Isso significa que para cada quadro do vídeo, é preciso fazer a conversão para uma outra imagem.

Seria mais simples se desse para modificar a imagem retornada, assim dava para compartilhar os pixels sem a necessidade de uma imagem extra.

Infelizmente, não tenho nenhum exemplo a mostrar com vídeos.

Resultado



Créditos

Imagem retirada do Eu Podia Tá Matando

---
Código-fonte deste tutorial.
Baixar vídeo.

Nenhum comentário: