segunda-feira, 28 de dezembro de 2009

IplImage e FXImage: revisitado

Anteriormente, falei aqui como utilizar uma imagem do OpenCV (IplImage) e uma do FOX (FXImage), ambas compartilhando o vetor de pixels. Confira aqui.

Pois bem. Fazendo daquela maneira, há certos problemas: ambos objetos devem permanecer vivos, e o dono dos pixels tem que ser a IplImage.

Isto pode não ser desejado. Por exemplo, vou citar o caso que aconteceu comigo e tive que matar um pouco a cabeça para resolver.

Muitas imagens que eu exibo (com o FOX Toolkit) precisam antes passar por algum processamento (com o OpenCV). Depois disso, eu não preciso mais da IplImage, apenas dos pixels.

Entretanto, seguir os passos explicados no outro tópico levava a um problema seriíssimo, que será explicado no decorrer deste tópico.

Compartilhando pixels: antigamente

Conforme eu havia explicado, criava-se primeiro a IplImage e depois a FXImage, utilizando o vetor de pixels, mais ou menos assim:


IplImage *iplImage = cvCreateImage(cvSize(200, 200), IPL_DEPTH_8U, 4);

FXImage *fxImage = new FXImage(getApp(), iplImage->imageData,
    IMAGE_KEEP|IMAGE_OWNED|IMAGE_SHMP|IMAGE_SHMI,
    iplImage->width, iplImage->height);


Lembrando que era necessário um cast horroroso com reinterpret_cast para transformar os pixels da IplImage de char* para FXColor.

Com isso, eu tenho que minha IplImage "doou" seus pixels para a FXImage, que tomou conta deles. Assim, a IplImage poderia ser dispensada (na verdade, somente o cabeçalho da imagem; o OpenCV permite isso) que a FXImage, quando for liberada, libera também o vetor de pixels.

O problema

Isso causa um problema que eu demorei muito para descobrir. E, depois de descoberto, vê-se que é uma coisa tão simples... é sempre um detalhezinho que cega a gente.

O grande problema é que, como o vetor de pixels é alocado pela IplImage, ele é alocado usando malloc. A FXImage, quando tenta liberar, tenta com delete. Aí não dá certo...

A "solução"

Eu criava as imagens independentemente, cada uma com seu vetor de pixels. Depois de processada, eu fazia uma chamada a memcpy para copiar os pixels. Uma tristeza considerando o contexto.

Compartilhando pixels: a solução de fato

Depois de quase desistir da idéia, eu descobri (dia desses...) o problema. E logo cheguei a uma solução elegante (e óbvia).

Basta criar primeiro a FXImage, que vai apenas "emprestar" os pixels para a IplImage. Assim:


FXImage *fxImage = new FXImage(getApp(), 0,
    IMAGE_KEEP|IMAGE_OWNED|IMAGE_SHMP|IMAGE_SHMI,
    200, 200);


Percebam que o segundo parâmetro, que seria o vetor de pixels, é nulo. Como os pixels são de responsabilidade da FXImage, ela mesma os cria (isso é informado pela opção IMAGE_OWNED).

É necessário também manter os pixels no lado do cliente (opção IMAGE_KEEP). Sem ela, os pixels são liberados depois da imagem criada, impossibilitando o compartilhamento.

Pois bem, agora vamos "emprestar" os pixels da FXImage para a IplImage:


IplImage *iplImage = cvCreateImageHeader(cvSize(200, 200),
    IPL_DEPTH_8U, 4);
  cvSetData(iplImage, fxImage->getData(), CV_AUTOSTEP);


cvCreateImageHeader() aloca todas as informações de uma imagem, com exceção do vetor de pixels. Com isso, a IplImage, que é temporária, apenas pega emprestado os pixels para trabalhar sobre eles. Ao terminar o processamento, podemos liberar o cabeçalho:


cvReleaseImageHeader(&iplImage);


Uma grande vantagem dessa abordagem é que não precisa nem converter a imagem, conforme eu havia feito anteriormente. A imagem já é criada no formato da FXImage.

Conclusão

Conforme havia dito, é uma coisa tão simples e óbvia que fica invisível.

Espero que essa informação seja útil para quem for usar essas duas bibliotecas juntas.

Abraço e até a próxima.

Nenhum comentário: