Título do Projeto - Pós-Graduação em Ciência da Computação
Transcrição
Título do Projeto - Pós-Graduação em Ciência da Computação
Universidade Federal do ABC (UFABC) Centro de Matemática, Computação e Cognição (CMCC) Curso de Pós-Graduação em Ciência da Computação Dissertação de Mestrado Alicia Isolina Pretel Jesus Processamento e Estilização de Dados RGB-Z em Tempo Real Santo André, SP Março, 2014 Curso de Pós-Graduação em Ciência da Computação Dissertação de Mestrado Alicia Isolina Pretel Jesus Processamento e Estilização de Dados RGB-Z em Tempo Real Trabalho apresentado como requisito parcial para obtenção do título de Mestre em Ciência da Computação Orientador: João Paulo Gois Co-orientador: Harlen Costa Batagelo Santo André, SP Março, 2014 Este exemplar foi revisado e alterado em relação à versão original, de acordo com as observações levantadas pela banca no dia da defesa, sob responsabilidade única do autor e com a anuência de seu orientador. Santo André, 27 de Junhho de 2014. Assinatura do autor: ____________________________________________ Assinatura do orientador: _______________________________________ Centro de Matemática, Computação e Cognição (CMCC) Curso de Pós-Graduação em Ciência da Computação Processamento e Estilização de Dados RGB-Z em Tempo Real Alicia Isolina Pretel Jesus Março de 2014 BANCA EXAMINADORA: • Prof. Dr. João Paulo Gois (Presidente) (CMCC) Universidade Federal do ABC - UFABC • Prof. Dr. Helton Hideraldo Bíscaro (EACH) Universidade de São Paulo - USP • Prof. Dr. André Guilherme Ribeiro Balan (CMCC) Universidade Federal do ABC - UFABC • Prof. Dr. Jesús Pascual Mena Chalco (Suplente) (CMCC) Universidade Federal do ABC - UFABC • Prof. Dr. Mario Augusto de Souza Liziér (Suplente) (GAPIS) Universidade Federal de São Carlos - UFSCAR Este trabalho contou com o auxílio financeiro das seguintes entidades: • Universidade Federal do ABC - UFABC (bolsa de mestrado, institucional), de fevereiro/2012 a janeiro/2013; • Coordenação de Aperfeiçoamento de Pessoal de Nível Superior - CAPES (bolsa de mestrado, demanda social), de fevereiro/2013 a janeiro/2014. Agradecimentos A Deus, pelo amor, pela saúde e por todas as coisas. Agradeço profundamente a meu esposo Jhon Franko Jorge Velarde, que é meu grande amor, obrigada pela motivação, compreensão, apoio e carinho que sempre me deu nos momentos mais difíceis e por todos os momentos que passamos juntos. Agradeço a minha família, pela força e por sempre depositarem suas esperanças em mim. Agradeço ao meu orientador e ao meu co-orientador, pela ajuda e colaboração com o meu trabalho, pelas conversas e conselhos ao longo do período do mestrado. A todos os professores pelas novas experiências, pelos desafios que me foram apresentados e pelos conhecimentos adquiridos. A meus amigos Lídia Rodrigues, Marcel Dias por sempre me motivar e torcer por mim. Agradeço a CAPES, e UFABC pelos financiamentos, em bolsas, para o desenvolvimento desta pesquisa. E por fim, agradeço a todos que um dia acreditaram em mim. Resumo O desenvolvimento tecnológico de dispositivos de captura 3D nos últimos anos permitiram que os usuários acessassem dados 3D de forma fácil e com baixo custo. Neste trabalho estamos interessados no processamento de dados de câmeras que produzem seqüências de imagens (canais RGB) e as informações de profundidade dos objetos que compõem a cena (canal Z) simultaneamente. Atualmente o dispositivo mais popular para a produção deste tipo de informação é o Microsoft Kinect, originalmente usado para rastreamento de movimentos em aplicações de jogos. A informação de profundidade, juntamente com as imagens permite a produção de muitos efeitos visuais de re-iluminação, abstração, segmentação de fundo, bem como a modelagem da geometria da cena. No entanto, o sensor de profundidade tende a gerar dados ruidosos, onde filtros multidimensionais para estabilizar os quadros de vídeo são necessários. Nesse sentido, este trabalho desenvolve e avalia um conjunto de ferramentas para o processamento de vídeos RGB-Z, desde filtros para estabilização de vídeos até efeitos gráficos (renderings não-fotorrealísticos). Para tal, um framework que captura e processa os dados RGB-Z interativamente foi proposto. A implementação deste framework explora programação em GPU com o OpenGL Shading Language (GLSL). Palavras–chave: dados RGB-Z, filtros de mapas de profundidade, processamento de vídeos RGB-Z, Scanners 3D, shaders. Abstract The technological development of 3D capture devices in recent years has enabled users to easily access 3D data easily an in a low cost. In this work we are interested in processing data from cameras that produce sequences of images (RGB-channels) and the depth information of objects that compose the scene (Z-channel) simultaneously. Currently the most popular device for producing this type of information is the Microsoft Kinect, originally used for tracking movements in game applications. The depth information coupled with the images allow the production of many visual effects of relighting, abstraction, background segmentation as well as geometry modeling from the scene. However, the depth sensor tends to generate noisy data, where multidimensional filters to stabilize the frames of the video are required. In that sense this work developed and evaluated a set of tools for video processing in RGB-Z, from filters to video stabilization to the graphical effects (based on non-photorealistic rendering). To this aim, an interactive framework that captures and processes RGB-Z data interactively was presented. The implementation of this framework explores GPU programming with OpenGL Shading Language (GLSL). Keywords: RGB-Z data, filter depth maps, RGB-Z video processing, 3D Scanners, shaders. Sumário 1 2 3 Introdução 1 1.1 Motivação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Objetivos e Contribuições desta Dissertação . . . . . . . . . . . . . . . . . . . 4 1.3 Trabalhos Relacionados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4 Organização do Trabalho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Processamento de Vídeo RGB-Z 11 2.1 Captura dos Dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2 Preenchimento em Multirresolução . . . . . . . . . . . . . . . . . . . . . . . . 13 2.3 Filtro Espaço-Temporal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.4 Efeitos sobre os Vídeos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 O framework Proposto 29 3.1 Sistemas Gráficos com GPU, OpenGL e Shaders . . . . . . . . . . . . . . . . 30 3.2 Componentes do Sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.2.1 O Microsoft Kinect . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.2.2 Ferramentas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.3 Arquitetura do Sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 3.4 Ambiente de Teste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4 5 6 Desenvolvimento do Framework 43 4.1 Descrição de Classes e Funções . . . . . . . . . . . . . . . . . . . . . . . . . 44 4.2 Relações das Classe nos Processos do Framework . . . . . . . . . . . . . . . . 50 4.3 Interação dos Shaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 4.4 Implementação de Shaders em GLSL . . . . . . . . . . . . . . . . . . . . . . 57 4.4.1 Shaders do Preenchimento em Multirresolução . . . . . . . . . . . . . 57 4.4.2 Shaders do Filtro Espaço-Temporal . . . . . . . . . . . . . . . . . . . 67 4.4.3 Shaders para Nuvem de Pontos e Wireframe . . . . . . . . . . . . . . . 72 4.4.4 Phong Shading para Dados do Kinect . . . . . . . . . . . . . . . . . . 76 4.4.5 Cartoon Shading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 4.4.6 Carregador de Arquivos . . . . . . . . . . . . . . . . . . . . . . . . . 87 Resultados e Discussão 93 5.1 Utilização do Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 5.2 Resultados e Discussão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 5.2.1 Parâmetros Testados para cada Fase do Processamento . . . . . . . . . 101 5.2.2 Desempenho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 5.2.3 Limitações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 5.2.4 Discussões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 5.2.5 Contribuções . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Conclusões e Trabalhos Futuros 107 6.1 Conclusões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 6.2 Trabalhos Futuros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 Referências Bibliográficas 110 Lista de Figuras 1.1 Processamento de dados RGB-Z . . . . . . . . . . . . . . . . . . . . . . . . . 2 1.2 Protótipo de câmera para dados RGB-Z . . . . . . . . . . . . . . . . . . . . . 6 1.3 Aplicações para dados RGB-Z . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1 Pipeline proposto para processamento de vídeo RGB-Z . . . . . . . . . . . . . 11 2.2 Reprojeção do mapa de profundidade às coordenadas de câmera de cor . . . . . 14 2.3 O Filtro Bilateral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4 Amostras em sub-resolução da imagem . . . . . . . . . . . . . . . . . . . . . 17 2.5 Preenchimento em multirresolução . . . . . . . . . . . . . . . . . . . . . . . . 19 2.6 Filtragem espacial e espaço-temporal . . . . . . . . . . . . . . . . . . . . . . . 21 2.7 Exemplo de Reiluminação . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.8 Exemplo de Segmentação I . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.9 Exemplo de Segmentação II . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.10 Exemplo de Abstração . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.11 Exemplo de Rendering Stroke-Based . . . . . . . . . . . . . . . . . . . . . . . 26 2.12 Exemplo de Rendering Estereoscópico . . . . . . . . . . . . . . . . . . . . . . 27 2.13 Cartoon Shading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.1 O pipeline estendido de shaders . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.2 O Kinect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.3 O padrão infravermelho . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3.4 Modelo geométrico do Kinect . . . . . . . . . . . . . . . . . . . . . . . . . . 34 3.5 Dados Kinect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.6 Arquitetura do sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 3.7 O framework Qt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 4.1 Diagrama de interação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 4.2 Interação dos shaders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 4.3 Fluxograma do rendering de um método . . . . . . . . . . . . . . . . . . . . . 54 4.4 Fluxograma de visualização da nuvem de pontos . . . . . . . . . . . . . . . . 55 4.5 Fluxograma de visualização da malha como wireframe . . . . . . . . . . . . . 56 4.6 Fluxograma de visualização da superfície . . . . . . . . . . . . . . . . . . . . 56 4.7 Faces triangulares com um ponto comum da malha. . . . . . . . . . . . . . . . 74 4.8 Cálculo do vetor normal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.9 Uso do carregador de arquivos de shaders . . . . . . . . . . . . . . . . . . . . 89 5.1 Interface gráfica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 5.2 Grupos de componentes da interface gráfica . . . . . . . . . . . . . . . . . . . 95 5.3 Interação com o mouse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 5.4 Barra de menus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 5.5 Opções de visualização da malha . . . . . . . . . . . . . . . . . . . . . . . . . 97 5.6 Processos de preenchimento de buracos . . . . . . . . . . . . . . . . . . . . . 98 5.7 Processos de filtragem espaço-temporal . . . . . . . . . . . . . . . . . . . . . 99 5.8 Processo de efeito especial . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 5.9 Carregador de arquivos shaders . . . . . . . . . . . . . . . . . . . . . . . . . . 99 5.10 Visualização com o mapa de profundidade filtrado . . . . . . . . . . . . . . . . 100 5.11 Ativação do método fluxo óptico . . . . . . . . . . . . . . . . . . . . . . . . . 100 5.12 Posição da luz na cena . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 6.1 Projeto Kinect Fun House Mirror . . . . . . . . . . . . . . . . . . . . . . . . . 109 Lista de Tabelas 3.1 Especificações técnicas do hardware . . . . . . . . . . . . . . . . . . . . . . . 40 3.2 Bibliotecas e Ferramentas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.1 Níveis da Componente Difusa . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Lista de Siglas Sigla Descrição API Application Programming Interface BF Bilateral Filter CBF Cross Bilateral Filter DBF Dual Bilateral Filter FBO Frame Buffer Object GLSL OpenGL Shading Language GPU Graphics Processing Unit GPGPU General-Purpose Computing on Graphics Processing Units JBU Joint Bilateral Upsampling NPR Non-Photorealistic Rendering OpenCV Open Computer Vision OpenGL Open Graphics Library OpenNI Open Natural Interaction PCL Point Cloud Library TBO Texture Buffer Object VAO Vertex Array Object VBO Vertex Buffer Object Capítulo 1 Introdução O Microsoft Kinect é um dispositivo compito de sensores para a captura de movimentos para jogos do console Microsoft Xbox 360. Contudo, a utilização do Microsoft Kinect tem extrapolado o uso em jogos, uma vez que ele possui preço acessível e diversas APIs para o desenvolvimento de aplicações computacionais. Isto significa que novos desafios surgiram além do que era comumente usado em pesquisas de de nuvens de pontos [11, 13, 30] onde os dados eram obtidos principalmente por scanners inacessíveis a usuários domésticos [25]. Através do uso do Microsoft Kinect, foram propostas diversas aplicações, entre elas reconstrução 3D em tempo-real [32], desenvolvimento de ambientes de Realidade Mista [20, 18] e o processamento de vídeos em RGB-Z1 [36]. Em particular, Richardt et al. apresentaram uma ferramenta de processamento de dados RGB-Z e um equipamento formado por uma câmera RGB e um sensor de profundidade time-of-flight [36]. Os autores propuseram filtros para a geração de imagens com coerência espaço-temporal e geometria simultaneamente. Na Figura 1.1 são apresentados os dados iniciais em (a) onde nota-se ruídos, em (b) a aplicação do filtro com coerência espaço-temporal, em (c) imagem original da cena e em (d) um rendering sobre a cena do modelo reconstruído. 1 Na bibliografia especializada, RGB-Z é também chamada de RGB-D. Capítulo 1. Introdução 2 (a) (b) (c) (d) Figura 1.1: Processamento de dados RGB-Z: (a) mapa de profundidade 3D original (com ruído); (b) mapa de profundidade 3D filtrado; (c) Vídeo RGB original; (d) Vídeo RGB com rendering stroke-based (Fonte [36]). 3 1.1. Motivação Motivado pelo trabalho de Richardt et al [36], propusemos desenvolver uma ferramenta computacional que obtém, processa e cria efeitos visuais interativamente a partir de dados RGB-Z. Utilizamos os métodos de preenchimento e filtragem propostos por Richardt e colaboradores, porém buscamos considerar outros aspectos para garantir a interatividade da aplicação desenvolvida. A ferramenta aqui proposta é multiplataforma, desenvolvida com a Linguagem C++, com o Framework Qt2 e com a linguagem de shaders do OpenGL (GLSL). Além disso, a interface gráfica da aplicação foi desenvolvida de modo que garantisse que os dados capturados em tempo real via o Microsoft Kinect fossem já processados e aplicados os efeitos especiais, ao passo que, na interface proposta por Richardt, o vídeo RGB-Z era gravado e carregado posteriormente para então se gerar os efeitos. A ferramenta aqui proposta apresenta outras funcionalidades, como a possibilidade de carregar os shaders em tempo de execução da aplicação, sem a necessidade de recompilar todo o código-fonte. Este recurso permite executar efeitos on-line. Por outro lado, a ferramenta proposta ainda possui algumas limitações, como a impossibilidade de gravação de resultados para reprodução posterior e nem a configuração de parâmetros das técnicas de processamento. 1.1 Motivação Embora há muita ênfase na Fotografia Computacional, existe um crescente interesse em desenvolver técnicas para processamento de vídeos, em especial para vídeos dotados de informação de profundidade (RGB-Z). Isto é devido ao avanço tecnológico de algumas câmeras com sensores especiais que além de capturar a informação de cor, capturam descontinuidades de profundidade em uma imagem, permitindo assim a criação de alguns dos efeitos mais atrativos na estilização do vídeo [36]. Além disso, existem ferramentas que fornecem uma interface para obter os dados em tempo real do Microsoft Kinect, tal como a Biblioteca Point Cloud Library 2 http://www.qt-project.org/ Capítulo 1. Introdução 4 — PCL, um projeto open source para o processamento de nuvens de pontos. As principais tarefas em processamento vídeos são a elaboração de filtros espaço-temporal, métodos de super- amostragem e os efeitos especiais, por exemplo, rendering não foto-realístico, técnicas de re-iluminação, múltiplas visualizações, efeitos artísticos, mosaico, entre outras[15]. Contudo a implementação destas tarefas são dificultadas pelos seguintes problemas: P1. Obtenção de dados com o uso do Microsoft Kinect: o sensor de profundidade tende a gerar dados ruidosos e de baixa resolução. É por este fato que filtros multidimensionais (na intensidade, no espaço e no tempo) para estabilização dos frames do vídeo se fazem necessários; P2. Processamento em tempo real: desenvolver filtros e efeitos gráficos no vídeo com coerência espaço-temporal têm alto custo computacional. Desta forma, métodos interativos costumam explorar estruturas de dados sofisticadas e paralelismo. Desta forma se motiva a presente trabalho, enfatizando três características distintas: M1. Explorar os dados em RGB e de profundidade do Microsoft Kinect em conjunto com a biblioteca PCL; M2. Gerar vídeos RGB-Z com coerência espaço-temporal; M3. Desenvolver efeitos sobre imagens e vídeos RGB-Z explorando a linguagem GLSL [41]. 1.2 Objetivos e Contribuições desta Dissertação Embora neste projeto exploramos o método proposto por Richardt et al. [36] para a criação e manipulação de vídeo RGB-Z, o desenvolvimento de um Framework que satisfaz esses requisitos não é trivial. O Framework tem como finalidade receber dados diretamente do Kinect e processá-los em tempo real. O objetivo principal do Framework é explorar correspondências entre mapas de cor e de profundidade capturadas de modo a gerar vídeo RGB-Z coerente 5 1.3. Trabalhos Relacionados em tempo real, preenchendo áreas com oclusão e removendo ruídos. O resultado do vídeo é usado para o processo de rendering com diversos efeitos que exploram imagens e a geometria intrínseca da profundidade. O Framework desenvolvido no presente trabalho apresenta uma ferramenta interativa e multiplataforma. Ele foi desenvolvido em uma arquitetura que integra o uso de bibliotecas especializadas com o Qt (usando a Linguagem C++). Essa arquitetura é flexível o suficiente para permitir que novas pesquisas sejam englobadas e, simultaneamente, impor determinados padrões que permitam a integração de seus componentes. Modelos padrões são criados para a intercomunicação de dados entre CPU e GPU em todo o processo, esses modelos são definidos sobre o uso de estruturas da linguagem de shaders GLSL. 1.3 Trabalhos Relacionados Richardt et al. [36] apresentam um protótipo de hardware para capturar dinamicamente a imagem colorida e geometria da cena (mapas de profundidade), constituído por uma câmera de vídeo RGB e uma câmera de infravermelho (Infrared) – IR Time-of-Flight – ToF (Figura 1.2). Nas câmeras ToF a profundidade da cena é obtida através do tempo que leva um raio de luz emitido tocar um objeto e retornar para o detector da câmera. Existem câmeras com diferentes princípios para capturar mapas de profundidade, como aquelas baseadas em um sistema de luz estruturada. Essa técnica consiste na iluminação da cena feita pelo emissor de IR com uma faixa de luz conhecida e, na observação da mesma cena pela câmera de IR (posicionada em um ângulo conhecido em relação ao emissor de IR). O mapa de profundidade é obtido a partir da triangulação entre a câmera e o emissorde IR do equipamento [22]. Câmeras como o Microsoft Kinect, que possuem esta tecnologia, estão tendo muito impacto em diversas aplicações. Especificamente, o Microsoft Kinect tem sido aplicado no problema de Reconstrução 3D [32], Realidade Mista [18], [20] e Processamento de vídeos RGB-Z [36], [29]. A Figura 1.3 apresenta algumas aplicações nestas áreas. Dentre as aplicações mais populares temos o Ki- Capítulo 1. Introdução 6 Figura 1.2: Protótipo de câmera para dados RGB-Z: câmera de vídeo RGB e câmera de infravermelho Time-of-Flight (Fonte [36]). nectFusion [32] Projeto de Reconstrução 3D em tempo real que cria uma estrutura volumétrica 3D e uma malha de polígonos que define a cena capturada, mesclado a partir de um conjunto de frames dos mapas de profundidade do Kinect. O modelo 3D resultante pode ser aplicado em Realidade Mista [18]. Já Knecht et al. [20] apresentam uma maneira atrativa de mostrar cenários de Realidade Mista, misturando objetos reais e virtuais considerando iluminação indiretamente entre eles (b). No processamento de vídeos RGB-Z, Richardt et al. [36] apresentam efeitos especiais nos vídeos tais como reiluminação, abstração, rendering stroke-based (Figura 1.1-(d)), segmentação e rendering estereoscópico 3D. Lucio et al. [29] mostram que à segmentação pode ser aplicado para a filtragem em certas regiões da cena, simulando o efeito especial de foco da câmera Figura 1.3-(c). São poucos os trabalhos que processam dados RGB-Z e que disponibilizaram seu códigofonte. Dentre alguns trabalhos podemos citar o projeto KinectFusion (Figura 1.3-(a)) e o projeto de Richardt e colaboradores. O KinectFusion, desenvolvido pela Microsoft Research, está disponível em uma versão open source chamado KinFu [16] que faz parte da biblioteca PCL. KinFu permite reconstruir uma cena em 3D e visualizar ela como malha ou como superfície densa com cores da textura RGB da mesma cena. Embora o KinFu mantenha alguns dos algoritmos de KinectFusion em CUDA (utilizando programação em GPGPU [19], permite fazer a ligação com eles e escrever código novo em C++. Já o projeto de Richardt et al. [36] processa 7 1.3. Trabalhos Relacionados (a) (b) (c) Figura 1.3: Aplicações para dados RGB-Z: (a) Reconstrução 3D e mapeamento de textura (Fonte [16]); (b) Realidade Mista, combinando luz e objetos virtuais com a cena real (Fonte [20]); (c) Efeito especial em vídeo RGB-Z, foco da câmera (Fonte [29]). vídeos RGB-Z, filtrando o mapa de profundidade e aplica efeitos no vídeo de cor. Os autores também disponibilizaram seu código-fonte, um conjunto de vídeos obtidos com o protótipo de câmera RGB-Z que eles criaram. Esse projeto foi implementado em C# com programação na GPU utilizando GLSL, mas apenas pode ser executado na plataforma Windows. Aplicações que usam como entradas dados RGB-Z, ou seja mapas de cor e de profundidade, visam como objetivo principal melhorar a qualidade do mapa de profundidade, além da execução em tempo real. As práticas nesse processamento são de superresolução (upsampling) e eliminação de ruído, assim a maioria dessas aplicações estão baseadas em uma extensão do Joint Bilateral Upsampling – JBU. Nesse sentido, Garcia et al. [10] verifica que no uso comum de JBU [23], na fusão de imagens coloridas e mapas de profundidade, são gerados artefatos de textura que mostram falsas profundidades. Com a finalidade de evitar esses artefatos os autores propõem o método Pixel Weighted Average Strategy – PWAS. Aqui os autores adicionam na aplicação do JBU uma estrutura chamada Mapa de Credibilidade – MC, a qual é representada pela utilização de um kernel Gaussiano sobre os gradientes das profundidades vizinhas a um pixel. Com isto, o MC atribui maior peso para as verdadeiras transições de profundidade e rejeita a influência das bordas de cor. Do mesmo modo, Min et al. [31] apresentam o método Weighted Mode Filtering –WMF. Capítulo 1. Introdução 8 Com base no cálculo de um Joint Histogram – JH para cada vizinhança de um pixel. A criação do JH está baseada no JBU e adiciona mais um peso definido por uma função Gaussiana de Erro (que é o desvio padrão da diferença de profundidade entre o pixel e cada um de seus vizinhos). Em seguida o resultado é usado para a contagem de cada valor de disparidade correspondente no mapa de profundidade. Também, para prevenir efeitos de aliasing os autores propõem aplicar seu método em escalas, eles referem-se ao método pelo nome de Multiscale Color Measure, e estende este método para conseguir consistência temporal, com uma estimativa simples de fluxo óptico [9] entre frames vizinhos. De maneira diferente às outras mencionadas, Vijayanagar et al. [40] utilizam um filtro multirresolução de difusão anisotrópica não-linear para fazer todo o processo de melhora dos mapas de profundidade. Os algoritmos que eles propõem exploram o conceito de pirâmides da imagem para refinar progressivamente o mapa de profundidade em diferentes resoluções. Os autores desenvolvem e implementam os algoritmos com programação em GPGPU utilizando OpenCL. Finalmente, o método de Richardt e colaboradores apresenta técnicas de preenchimento de buracos e filtragem em mapas de profundidade. A primeira técnica preenche áreas sem informação em vários níveis de resolução aplicando um JBU adaptado. A segunda técnica aplica um filtro espaço-temporal explorando coincidências das bordas de cor e de profundidade, a técnica considera dados do frame atual com dados filtrados no frame anterior, encontrando correspondências entre frames usando fluxo óptico. Em suma consegue-se dois objetivos: remoção considerável do ruído e superresolução do mapa de profundidade. Detalhes da técnicas de Richardt et al. [36] serão abordadas no Capítulo 2. 1.4 Organização do Trabalho O restante desta dissertação está organizada da seguinte forma: no Capítulo 2 são apresentadas as técnicas para o processamento de vídeo RGB-Z. No Capítulo 3 é apresentada a integração 9 1.4. Organização do Trabalho dos componentes do Framework assim como detalhes técnicos da arquitetura e de implementação. No Capítulo 4 é apresentado o desenvolvimento do Framework através da descrição de classes e funções bem como as suas relações. Além disto, é descrita implementação e interação dos shaders do processamento. No Capítulo 5 é apresentada a utilização do framework, resultados e discussões. Finalmente, no Capítulo 6 são apresentadas as conclusões do trabalho, além de sugestões para desenvolvimentos futuros. Capítulo 2 Processamento de Vídeo RGB-Z O objetivo deste capítulo é apresentar em detalhes as etapas do processamento de Vídeo RGBZ. Um vídeo RGB-Z é a combinação de vídeos de cor e de profundidade de uma cena. Consideramos a geração de um vídeo RGB-Z compreende a captura dos dados, a melhoria dos mapas de profundidade e a aplicação de um efeito especial. Vídeo Color Captura de Dados Preenchimento de Buracos Filtro Espaço-Temporal Efeito Especial Vídeo RGBZ Rendering Vídeo Profundidade Figura 2.1: Pipeline proposto para processamento de vídeo RGB-Z. Para as etapas de preenchimento e filtragem serão descritas as técnicas propostas por Richardt et al. [36]. Os métodos usados por eles buscam, no fim do processo, uma geometria suficientemente plausível em tempo real e adequados para aplicação de um rendering. Para alcançar esse objetivo foi proposto o pipeline que é mostrado na Figura 2.1. Na etapa de Captura de Dados (Seção 2.1), os vídeos de cor e de profundidades são capturados e transferidos como dados de entrada para o sistema. Também são preparados para processamento posterior. Na etapa de Preenchimento de Buracos (Seção 2.2), as áreas ocultas (sem informação) geradas Capítulo 2. Processamento de Vídeo RGB-Z 12 pela diferença de distância entre as câmeras do dispositivo são preenchidas. Em seguida, na etapa de Filtro Espaço-Temporal (Seção 2.3) o ruído é removido mantendo correspondências entre sequências de frames que resultam em vídeos coerentes e estáveis ao longo do tempo. Finalmente, na etapa de Efeito Especial (Seção 2.4) é aplicado um tipo especial de rendering utilizando dados da cena (cor e geometria melhorada nas etapas anteriores). 2.1 Captura dos Dados A recuperação da geometria 3D dos objetos do mundo real é um dos problemas clássicos em Computação Gráfica e Visão Computacional. Os dispositivos para capturar essa geometria são comumente conhecidos como scanners 3D. Eles muitas vezes são equipados com luzes, projetores ou sensores. O resultado final dessa captura é uma descrição da geometria em termos de distância, profundidade, disparidade ou nuvem de pontos da superfície. A importância na captura de dados da geometria, ao contrário de uma imagem simples, é que esses dados fornecem acesso a muitas propriedades da superfície, úteis nos diferentes tipos de processamento, tais como vetores, normais e curvaturas. No presente trabalho, a aquisição dos mapas de profundidade é feita a partir do Kinect. Este equipamento contém uma câmera RGB, uma câmera e um emissor IR1 , sendo que os mapas de profundidade são obtidos a partir da triangulação de luz estruturada projetada (o suporte teórico e técnico do Kinect são explicados na Seção 3.2.1). Porém, as câmeras RGB e IR têm posições diferentes sobre o Kinect (um espaçamento de 2,5 cm entre elas), ou seja, para um pixel na imagem RGB existe uma posição diferente do mesmo pixel no mapa de profundidade, e por isso as imagens não estão alinhadas. Para corrigir esta diferença e alinhar vídeo de profundidade à projeção da câmera de cor é necessário usar o método de reprojeção [38]. Entretanto, esse método precisa dos dados da calibragem da câmera. No trabalho foram utilizados os dados alinhados fornecidos pela biblioteca PCL. Por conseguinte o método 1 A luz IR faz parte da porção invisível do espectro eletromagnético que está adjacente aos comprimentos de onda longos, por isso é luz não visível pelo olho humano. 13 2.2. Preenchimento em Multirresolução de reprojeção não será tratado. Para que os dados estejam prontos para a aplicação dos algoritmos do processamento, eles são transformados em um formato padrão como é descrito na Seção 3.3. 2.2 Preenchimento em Multirresolução Tanto as câmeras ToF [21] quanto as da luz estruturada, mesmo admitindo que elas sejam baseadas em tecnologias diferentes, geram imagens de profundidade que mostram áreas com oclusão de informação (em relação ao Kinect os motivos são descritos na Seção 3.2.1). Para esta etapa Richardt et al. [36] propuseram preenchimento de buracos em multirresolução. Essa etapa compreende um passo preliminar que é invalidar geometria nas bordas da profundidade para que logo seja aplicada a técnica de preenchimento de buracos. Os autores verificam que as câmeras ToF introduzem os chamados flying pixels que são descontinuidades de profundidade que flutuam em distâncias intermediárias e precisam ser removidos. Esses flying pixels são causados por imprecisões na estimativa de profundidade próprias do sensor, quando a área de um pixel cobre as superfícies a diferentes distâncias. No caso do Kinect, após reprojetar o mapa de profundidade às coordenadas de câmera de cor, justamente nas bordas onde existe diferença de profundidade, os dados resultantes apresentam texturas que nem sempre pertencem às suas respectivas profundidades. Tais texturas também precisam ser removidas (Figura 2.2). Por tal motivo, aplica-se limiarização à magnitude do gradiente de profundidade, utilizando o clássico Operador de Sobel [33]. O Operador de Sobel tem por finalidade detectar bordas em uma imagem. Tecnicamente, é um operador de diferenciação discreta em que o cálculo é uma aproximação do gradiente da função intensidade da imagem i. O Operador de Sobel é baseado na convolução da imagem com um kernel de tamanho 3 × 3. Esse kernel é separável porque aproxima as derivadas na Capítulo 2. Processamento de Vídeo RGB-Z 14 Figura 2.2: Reprojeção do mapa de profundidade às coordenadas de câmera de cor. Note-se que a pessoa e a parede têm profundidades diferentes, mas nas bordas do rosto existem texturas da parede. direção horizontal Gx e vertical Gy : ⎤ ⎡ ⎥⎥⎥ ⎢⎢⎢ +1 0 −1 ⎥⎥⎥ ⎢⎢⎢ ⎥⎥⎥ ⎢⎢⎢ Gx = ⎢⎢⎢⎢+2 0 −2⎥⎥⎥⎥ * i ⎥⎥⎥ ⎢⎢⎢ ⎥⎥ ⎢⎢⎣ +1 0 −1⎦ ∧ ⎤ ⎡ ⎥⎥⎥ ⎢⎢⎢ +1 +2 +1 ⎢⎢⎢ ⎥⎥⎥ ⎥⎥⎥ ⎢⎢⎢ ⎥⎥⎥ * i, Gy = ⎢⎢⎢⎢ 0 0 0 ⎥⎥⎥ ⎢⎢⎢ ⎥⎥⎥ ⎢⎢⎣ +1 +2 +1⎦ onde * denota a operação de convolução bidimensional e i denota a função intensidade da imagem. As derivadas podem então ser combinadas para encontrar a magnitude absoluta do gradiente em cada ponto além da sua orientação. A magnitude do gradiente é dada por G = √︁ G2x + G2y . O limiar usado está no intervalo T = [0,1; 0,2]. Maiores valores levam a maior detecção de bordas. Por outro lado, o núcleo da estratégia para o preenchimento de buracos é baseado em uma extensão do Filtro Bilateral (Bilateral Filter – BF). O BF é uma técnica de suavização nãolinear que preserva bordas [39] difere do Filtro Gaussiano porque considera que dois pixels 15 2.2. Preenchimento em Multirresolução são próximos um ao outro não só se ocupam posições espacialmente próximas, mas também se eles têm alguma similaridade na escala fotométrica (intensidade), como podemos observar na Figura 2.3. Portanto os pesos do seu kernel são modificados em função da semelhança da intensidade entre os pixels, dando assim maior peso para pixels pertencentes a regiões espacialmente próximas, e reduzindo o efeito de blurring das bordas onde descontinuidades fotométricas estão presentes. Figura 2.3: O Filtro Bilateral suaviza uma imagem de entrada preservando suas bordas. Cada pixel é substituído por uma média ponderada dos seus vizinhos, onde cada um é ponderado por um componente espacial que penaliza pixels distantes e um componente fotométrico que penaliza pixels com uma intensidade diferente. A combinação de ambas componentes assegura que apenas os pixels nas proximidades semelhantes contribuem para o resultado final (Adaptado de [6]). Especificamente para um pixel x em uma imagem colorida i o valor do BF é dado por: fBF (x) = 1 ∑︁ wc (x, y) · w s (x,y) · i(y), Kx y∈N (2.1) x Kx = ∑︁ wc (x, y) · w s (x,y), (2.2) y∈Nx onde Nx é o conjunto de pixels vizinhos y no kernel centrado em x, e os pesos de cor e espaço são: (︁ )︁ wc (x, y) = exp −‖i(x) − i(y)‖2 /2σ2c , (2.3) Capítulo 2. Processamento de Vídeo RGB-Z (︁ )︁ w s (x, y) = exp −‖x − y‖2 /2σ2s . 16 (2.4) Adams et al. [1] introduzem reformulam o BF representando o valor do pixel i(y) como uma quantidade homogênea, i.e. (r,g,b,1) em vez de (r,g,b). Desta forma, o propósito é aplicar a filtragem tanto na coordenada homogênea quanto nas demais, de modo que o resultado final seja dividido pelo valor da coordenada homogênea. Por conseguinte esta notação elimina a divisão normal pela soma dos pesos, e ela irá ser assumida a partir deste ponto. Assim: fBF (x) = ∑︁ wc (x, y) · w s (x,y) · i(y). (2.5) y∈Nx Uma variação do BF, conhecida por Cross Bilateral Filter2 – CBF, foi introduzida por Eisemann e Durand [7] e Petschnigg et al. [35]. O CBF melhora a qualidade de uma imagem i usando uma segunda imagem ĩ da mesma cena. O filtro na intensidade da Equação 2.5 é aplicado em ĩ tentando combinar altas frequências de uma imagem e baixas frequências da outra. Portanto o peso de cor é substituído por: (︁ )︁ wc̃ (x, y) = exp −‖ĩ(x) − ĩ(y)‖2 /2σ2c . (2.6) A partir dos filtros anteriores, Kopf et al. [23] propuseram o Joint Bilateral Upsampling – JBU para o problema de super-resolução, afirmando que informações adicionais estão disponíveis na forma da imagem original de entrada em alta resolução. Dada uma imagem em alta resolução ĩ, e uma imagem em baixa resolução S calculada para uma versão reduzida da imagem, é proposto um método que aplica um filtro bilateral durante o processo de Upsampling. O propósito é aplicar um filtro espacial para a solução em baixa resolução enquanto um filtro semelhante é aplicado no espaço da intensidade da imagem original; para o caso do projeto o 2 O Cross Bilateral Filter também é conhecido como Joint Bilateral Filter mas neste trabalho somente o denominaremos por CBF. 17 2.2. Preenchimento em Multirresolução JBU é definido como: f JBU (x) = ∑︁ wc (x, y) · w s (x↓ ,y↓ ) · d(y↓ ), (2.7) y↓ ∈Nx↓ onde x e y denotam coordenadas de pixels na imagem de alta resolução, e x↓ e y↓ denotam as coordenadas correspondentes em uma solução subamostrada do mapa de profundidade d(y↓ ). Figura 2.4: Pirâmide de um conjunto hierárquico de amostras em sub-resolução da imagem. A técnica de preenchimento de buracos em multirresolução é justamente baseada no JBU, adaptado em diferentes escalas da imagem de cor e do mapa de profundidade (Figura 2.4). Para cada nível de resolução (da menor para a maior) calcula-se o CBF para preencher o mapa profundidade nesse nível. Após esta etapa, sobre esse resultado é aplicado o upsampling no nível seguinte e assim sucessivamente até alcançar o nível 0. O Algoritmo 1 descreve este método, onde os dados de entrada são o mapa de profundidade d e imagem de cor i, as duas de mesma resolução. No algoritmo se definem as variáveis n e g, nível de menor resolução (o nível de maior resolução é 0) e fator de sub-resolução, respectivamente. Também no mapa Capítulo 2. Processamento de Vídeo RGB-Z 18 de profundidade toda região com dados sem informação é considerada inválida. Em primeiro lugar, as bordas do mapa de profundidade são invalidadas aplicando limiarização ao Filtro de Sobel (Linha 2). Em segundo lugar, do nível 0 até o n são calculadas as amostras em sub-resolução de d e i aplicando downsampling (Linhas 3 e 4). Em seguida, no último nível de resolução n é aplicado o CBF para preencher dn apenas em pixels inválidos utilizando os dados da imagem de cor (Linha 5). O resultado é d̃n . Logo após, do nível k = n até 1 (Linha 7 até 11), é gerada uma solução upsample uk−1 para o seguinte nível superior de resolução (Linha 8). Para isto utiliza-se d̃k e o mapa de profundidade do nível seguinte dk−1 . No processo de upsampling os dados são preenchidos conforme dk−1 , exceto quando nele existe dados inválidos. Nesse caso é preenchido com o correspondente dado do d̃k de acordo com o fator g. Ainda o resultado uk−1 contém dados escassos espalhados nas regiões inválidas. Após isso, apenas nos pixels inválidos de dk−1 é aplicado o CBF utilizando dados de uk−1 e ik−1 (Linha 9). Finalmente a saída de processo no nível 0 é o mapa de profundidade dR preenchido e sem buracos (Linha 12). Algoritmo 1 Preenchimento em multirresolução 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: procedure Preenchimento(d,i) Calcular d0 com kernel limiarização do gradiente de d. Downsample d0 : ∀k ∈ {1,...,n} ∧ p ∈ dk calcular dkp = dk−1 p.k.g Downsample i0 : ∀k ∈ {1,...,n} ∧ p ∈ ik calcular ikp = ik−1 p.k.g d̃np ← CBF (dnp ,in ), . ∀p ∈ {q ∈ dn |dnq = INV ALID} k←n while k ≥ 1 do k uk−1 . ∀p ∈ {q ∈ dk−1 |dk−1 q = INV ALID} p.(k−1).g ← Upsample d̃p , k−1 k−1 k−1 k−1 k−1 d̃p.(k−1).g ← CBF (Up.(k−1).g ,I ), . ∀p ∈ {q ∈ d |dq = INV ALID} k ←k−1 end while dR ← d̃0 return dR end procedure A Figura 2.5 mostra graficamente a técnica descrita acima. As regiões amarelas representam dados inválidos. Nela o menor nível de resolução é 2. Nesse nível é feito um simples CBF. 19 2.2. Preenchimento em Multirresolução Para os seguintes níveis são aplicados os processos upsampling e CBF. Como já foi explicado, depois de ser aplicado o processo de upsampling ainda há dados escassos espalhados na região inválida. Esse resultado junto com a imagem de cor desse nível é utilizado no processo CBF apenas nos dados inválidos do mapa de profundidade do mesmo nível. No nível 0, depois de aplicar os processos, é obtido o mapa de profundidade preenchido. imagem cor mapa de profundidade preenchida (upsampling com dados escassos) mapa de profundidade i0 df0 du0 d0 i1 df1 du1 d1 i2 df2 Nível k=0 Nível k=1 d2 Nível k=2 Figura 2.5: Preenchimento em multirresolução, com três níveis de escala, em pixels inválidos (cor amarela). O início do processo é no nível 2, onde aplica-se CBF. Para os outros níveis são aplicados upsampling e CBF. A saída do nível 0 é um mapa de profundidade preenchido (Adaptado de [36]). Esta abordagem pode ser implementada na GPU. Ela requer um parâmetro σ s ≤ 10 como tamanho do kernel de convolução. O σ s é um desvio-padrão da distribuição Gaussiana o qual quanto maior é seu valor, maior é o tempo para processar a imagem. No entanto, para um método simples de CBF [35, 7] é necessário um kernel com σ s > 25 para preencher buracos grandes com uma única resolução da imagem, o que torna muito lento o processo, impossível para processamento em tempo real (característica principal que procura-se em todos os Capítulo 2. Processamento de Vídeo RGB-Z 20 métodos usados no Processamento de Vídeo RGB-Z), mesmo que seja executada na GPU. Adicionalmente, para reduzir a influência do ruído na imagem de cor, ela pode ser primeiramente filtrada usando BF com parâmetros σ s = 3 e σc = 0,1. Porém, aplicar esse filtro exige um tempo extra de cálculo no processo completo. 2.3 Filtro Espaço-Temporal Bennett et al. [4] introduziram o Dual Bilateral Filter – DBF como uma outra variante do BF e do CBF. Tanto o CBF como o DBF precisam de duas imagens como entrada. Contudo, o DBF modifica a obtenção do peso da intensidade, que neste caso é representado pelo espectro visível (RGB) e pelo espectro infravermelho (IR), o último representado no mapa de profundidade d. A câmera IR capta mais bordas, mas não tem as cores de uma câmera padrão RGB. Neste contexto, a dupla força do DBF faz com que as propriedades de ruído das imagens de entrada sejam contabilizadas separadamente, através dos parâmetros independentes σc e σd (Equações 2.9 e 2.10, respectivamente). Assim o DBF é um filtro espacial que preserva as bordas na imagem colorida i(x) e o mapa de profundidade d(x). A profundidade filtrada espacialmente em um pixel x num intervalo de tempo t é dada por: f s (x, t) = ∑︁ wc (x, y) · wd (x, y) · w s (x, y) · d(y, t), (2.8) y∈Nx onde Nx é o conjunto de pixels do kernel com raio 2σ s centrado em x, e os pesos de cor, distância e espaço são: (︁ )︁ wc (x, y) = exp −‖i(x, t) − i(y, t)‖2 /2σ2c , (2.9) (︁ )︁ wd (x, y) = exp − | d(x, t) − d(y, t) |2 /2σ2d , (2.10) )︁ (︁ w s (x, y) = exp −‖x − y‖2 /2σ2s . (2.11) 21 2.3. Filtro Espaço-Temporal O trabalho de Richardt et al. [36] aplica esse filtro no mapa de profundidade. Neste caso o resultado mostra-se na Figura 2.6-(b), comprovando que o ruído é suavizado, mas ainda não é eliminado completamente. Isto se deve ao fato de não lidar com ruído residual de baixa frequência que ainda é visível. Os valores dos parâmetros do filtro usados pelos autores são σc ∈ [0,05; 0,1], σd ∈ [0,075; 0,1] e σ s ∈ [4; 8]. (a) sem filtragem (b) filtragem espacial (c) filtragem espaço-temporal Figura 2.6: Comparação dos filtros espacial e espaço-temporal (Fonte [36]). Richardt e colaboradores assumem que o ruído de um frame em um intervalo de tempo é independente de outro, mas pode ser reduzido pela média de frames anteriores. Contudo, se pixels de frames anteriores forem simplesmente reutilizados, distorções visíveis podem aparecer. O que significa que qualquer movimento da câmera ou alterações na cena pode resultar em ghosting trails [17] seguindo os objetos. Esse efeito pode ser compensado através do fluxo de movimento, calculando os deslocamentos dos pixels entre osframes. Assim, considerando dois frames sucessivos com movimento significativo entre eles (deslocamentos dos pixels), temos que para o processo de filtragem espaço-temporal do frame atual, o filtro deve ser compensado com esse movimento significativo em relação ao frame prévio, além de considerar filtragem espacial. Com isto, esse filtro é mais efetivo na eliminação de ruído espacial e temporal. Portanto, para estabilizar cada frame de um mapa de profundidade no intervalo de tempo t, o filtro Capítulo 2. Processamento de Vídeo RGB-Z 22 espaço-temporal no pixel x̄ é dado por: fS T (x, t) = (1 − ϕ) · fS (x, t) + ϕ · fT (x, t), (2.12) onde o filtro espaço-temporal é compensado no movimento por fT propagando as distâncias filtradas de um frame prévio no intervalo de tempo t − 1 ao frame atual no intervalo de tempo t. No filtro temporal, x̄ em t − 1 denota a localização do movimento compensado de x no t. Então fT é calculado como: fT (x, t) = ∑︁ w (x, y, x̄, ȳ) · fS T (ȳ, t − 1). (2.13) y∈Nx A Equação 2.13 visa reduzir as oscilações provocadas pelo ruído independente do tempo. Para isto combina pesos com o mapa de profundidade filtrado em t − 1 em localizações do movimento compensado ȳ de todos os pixels y do kernel. Os pesos do filtro são dados por: w (x, y, x̄, ȳ) = wc (x, ȳ) · wd (x, ȳ) · w s (x̄, ȳ) · w f (y, ȳ). (2.14) A Equação 2.14 avalia a similaridade do pixel de movimento compensado ȳ com o pixel central x, em termos de distância, cor e espaço. No entanto, o peso espacial w s não penaliza a distância de x, mas sim sua localização de movimento compensado x̄. O peso de fluxo w f é definido na Equação 2.15. O objetivo desse peso é reduzir a influência de dados antigos em áreas de movimento rápido como eles tendem a ser não confiáveis. Com σ f ∈ [4; 5], temos: (︁ )︁ w f (y, ȳ) = exp −‖y − ȳ‖2 /2σ2f . (2.15) Além disso, para prevenir a amplificação do ruído nas áreas de movimento rápido, o filtro espacial fS é aumentado redefinindo o peso da intensidade como: (︁ )︁ wc (x, y) = exp −gc · ‖i(x, t) − i(y, t)‖2 /2σ2c , (2.16) 23 2.4. Efeitos sobre os Vídeos [︁ ]︁1 usando gc = 2 − ‖ȳ − y‖/σ f onde [m]ba fixa m ao intervalo [a,b]. O resultado é observado 0 nas áreas de movimento rápido da cena, onde se incrementa a importância da filtragem espacial e as distâncias são suavizadas por meio das bordas da imagem de cor. Em relação a encontrar os valores de x̄ e ȳ no intervalo de tempo t − 1, que são as localizações do movimento compensado de x e y no intervalo de tempo t, se utiliza Fluxo Óptico, responsável por calcular essas correspondências de pixels entre frames. Neste caso, são utilizados 2 frames consecutivos das imagens de cor. Esse processo é implementado na GPU como no trabalho de Eisemann et al. [8]. Finalmente, o filtro fS T é a soma do filtro espacial fS com o filtro temporal fT equilibrado pelo parâmetro ϕ. Esse parâmetro define pesos cuja soma é 1. O maior peso é dado para o filtro no frame atual, fS , e ϕ ∈ [0,01; 0,1]. Da mesma forma, o filtro espaço-temporal serve para aumentar a resolução dos mapas de profundidade (como é o caso dos mapas de profundidade de baixa resolução obtidos com câmeras ToF). Porém no presente trabalho não precisaremos aplicar este processo porque o Kinect fornece mapas de profundidade e de cor com mesma resolução (Seção 3.2.1). 2.4 Efeitos sobre os Vídeos Logo após a etapa de filtragem, é obtido um mapa de profundidade de alta qualidade. Ele está pronto para, junto com a imagem de cor, ser usado na aplicação de um efeito especial no vídeo RGB-Z. Muitos algoritmos de estilização da imagem precisam de informação útil para identificar os limites e formas de objetos na cena. Uma imagem de cor pode apresentar efeitos naturais de sombra e de iluminação do ambiente. Por exemplo, no problema fundamental da segmentação automática de objetos [5], torna-se complexo e difícil para o computador saber na imagem quais bordas representam limites e quais são causadas por descontinuidades internas de textura. Nesse sentido, as descontinuidades de profundidade desempenham um papel fundamental pois Capítulo 2. Processamento de Vídeo RGB-Z 24 fornecem informações sobre a geometria da cena e propriedades como normais, gradientes e curvaturas. Esses dados aliados aos vídeos de cor podem criar efeitos atrativos. Muitos destes efeitos podem ser gerados por um certo número de estilos expressivos pertencentes ao rendering não-fotorrealístico – NPR (Non-Photorealistic Rendering). NPR explora diferentes estilos artísticos mudando a informação visual [24] que em muitos casos a saída do rendering reduz a quantidade de informações que devem ser percebidas pelos usuários. NPR pode ser aplicado à fotografia, vídeo e geometria (objetos 3D). Dentre as técnicas ou efeitos de NPR que podem ser aplicados usando a geometria da cena e imagens de cor, ou seja, vídeo RGB-Z, temos: • Reiluminação: com o mapa de profundidade pode-se calcular a normal para cada pixel pertencente à cena. Usando essa normal bem como a posição 3D de uma fonte de luz, é possível modificar a intensidade do pixel em função do ângulo entre elas. O modelo de iluminação Phong [2] é o mais popular método de tonalização de malhas poligonais (Figura 2.7); Figura 2.7: À esquerda imagem original, as seguintes imagens mostram efeitos de Reiluminação com diferente posição da fonte de luz (Fonte [27]). • Segmentação: como dito anteriormente as descontinuidades de profundidade fornecem informações úteis para a identificação de limites do objeto. Essas informações combinadas com informações RGB tornam possível a segmentação automática de objetos 25 2.4. Efeitos sobre os Vídeos produzindo uma silhueta precisa de eles. Alguns exemplos deste efeito são apresentados nas Figuras 2.8, 2.9 e 1.3-(c); Figura 2.8: À esquerda, mapa de profundidade do objeto. À direita, segmentação do objeto (Fonte [26]). Figura 2.9: À esquerda, imagem de cor da cena. No centro, mapa de profundidade. À direita segmentação em capas (Fonte [5]). • Abstração: técnica que explora a geometria da cena e, coloca linhas nas formas da geometria que são significativas (Figura 2.10); • Stroke-Based Rendering: é um efeito que distribui strokes (traços) que cobrem a área dos objetos na imagem. No entanto, em vez de orientar esses strokes ao longo de gradientes da imagem, eles são orientados às curvaturas principais do mapa de profundidade que permitem alinhá-los às características geométricas significativas (Figura 2.11); • Rendering Estereoscópico 3D: a idéia para a esse efeito é sintetizar duas vistas, um para cada olho. Os vídeos RGB-Z podem servir para aplicar rendering estereoscópico para Capítulo 2. Processamento de Vídeo RGB-Z 26 Figura 2.10: À esquerda imagem original, à direita abstração (Fonte [36]). Figura 2.11: À esquerda, imagem original. À direita, Rendering Stroke-Based (Fonte [28]). 27 2.4. Efeitos sobre os Vídeos melhorar a percepção de profundidade da cena. Um exemplo deste efeito é mostrado na Figura 2.12; Figura 2.12: Exemplo de Rendering Estereoscópico (Fonte [34]). • Cartoon Rendering: ou efeito Cartoon Shading, é uma técnica que pretende imitar o estilo de sombreamento frequentemente usado em desenho animados. Existem muitos métodos que são usados para produzir este efeito. Wolff [41] mostra um método que envolve uma pequena modificação no método de tonalização Phong. O efeito básico é ter grandes áreas de cor constante com transições nítidas entre elas, ou seja, a cor é restrita a poucos níveis (Figura 2.13). Isso simula a maneira que um artista pode dar sombra em um objeto usando pinceladas de uma caneta ou pincel. Além disso, o efeito inclui contornos pretos ao redor das silhuetas e ao longo de outras bordas de alguma forma existente na cena. Capítulo 2. Processamento de Vídeo RGB-Z 28 Figura 2.13: À esquerda, tonalização Cartoon Shading com dois níveis. À direita, Phong Shading. Capítulo 3 O framework Proposto Dadas as características da pesquisa em testar e comparar algoritmos de filtragem e estilização nas imagens dos vídeos RGB-Z em tempo real, onde os dados são obtidos a partir de uma câmera scanner 3D, é útil ter um sistema que auxilie a configuração e a execução de experimentos nos gráficos. Com a finalidade de atender essa situação, neste trabalho foi proposto um framework que proverá uma infraestrutura capaz de integrar seus componentes e executar diferentes configurações de experimentos. Em detalhes esse framework contém: • um mecanismo de integração entre dispositivo de entrada, bibliotecas externas e uma interface gráfica intuitiva; • a disponibilidade do código fonte em uma linguagem baseada em C++ e uso de shaders em GLSL; • um ambiente de execução multi-plataforma com resultados gerados em tempo real. Este capítulo tem como objetivo descrever a integração dos componentes do sistema, assim como detalhes técnicos e de implementação do framework. O restante deste capítulo está organizado da seguinte maneira: na Seção 3.1 é apresentada uma visão geral de um Sistema Gráfico com GPU, OpenGL e shaders. Na Seção 3.2 são descritas as componentes de integração do Capítulo 3. O framework Proposto 30 sistema tais como dispositivos de entrada, bibliotecas externas, ferramentas e linguagem de programação usada. Na Seção 3.3 é apresentada a arquitetura do sistema e, finalmente, na Seção 3.4 é descrito o ambiente de teste do sistema. 3.1 Sistemas Gráficos com GPU, OpenGL e Shaders O desenvolvimento da Computação Gráfica tem sido impulsionado pelas necessidades de avanços em hardware e software. Desde o ano 2000 destacam-se progressos importantes como por exemplo, conseguir o fotorrealismo em tempo real com o uso de pipelines programáveis na Graphics Processing Unit – GPU. Tal objetivo pode ser possível com a combinação de fatores tais como conceitos modernos de Computação Gráfica, uso de APIs gráficas e programação na GPU. Os sistemas gráficos modernos enviam da CPU para GPU informações da cena em forma debuffers de atributos de vértices e buffers de índices aos vértices. Na GPU é feito o processo de rendering. Esse processo é suportado em uma arquitetura pipeline para gráficos raster, a qual armazena os pixels de saída no frame buffer [2]. A arquitetura do pipeline para sistemas gráficos inclui, entre outros, um processador de vértices, geometria e fragmentos. O pipeline habilita aplicar operações com vértices, primitivas e pixels. As GPUs evoluíram e são caracterizadas por módulos de propósito específicos voltados para operações gráficas e com alto grau de paralelismo. Assim, as arquiteturas das GPUs já estão modeladas para dar suporte ao pipeline definido na Figura 3.1. Uma aplicação na GPU pode ser programada o processador de vértice, geometria e fragmento. Dentre AS funcionalidades que permitem um grande poder computacional na GPU, temos as múltiplas unidades de processos matemáticos, acesso rápido à memória integrada, execução de um programa por cada fragmento/vértice e tarefas paralelas de alto desempenho. A GPU pode estar localizada na placa principal ou em uma placa gráfica. Atualmente, as principais fabricantes de GPUs no mercado são a NVIDIA e a ATI. 31 3.1. Sistemas Gráficos com GPU, OpenGL e Shaders Vertex Shader Primitive Assembly Tessellation Control Shader Tessellation Primitive Generator Tessellation Evaluation Shader Primitive Assembly Geometry Shader Primitive Assembly and Rasterization Fragment Shader Figura 3.1: Pipeline estendido de shaders suportado por OpenGL, em versões posteriores à 4.0 (Fonte [41]). Por outro lado OpenGL1 é uma API gráfica independente da plataforma e de fácil uso, que fornece uma interface de software que divide o trabalho para cada comando OpenGL entre a CPU e a GPU. As versões modernas estão baseadas na utilização de shaders programáveis para realizar o rendering na GPU, e estão sendo estruturadas em torno do pipeline para sistemas gráficos. Já para versões mais modernas do OpenGL foram adicionadas a esse pipeline o Geometry e Tessellator Shader como se mostra na Figura 3.1. Uma das opções para se escrever shaders é pela linguagem GLSL, que tem uma especificação separada do OpenGL, embora as funções de interface com aplicação para shaders façam parte da API OpenGL. Os shaders podem executar na GPU algoritmos relacionados com a iluminação e efeitos de tonalização de uma imagem 3D. No entanto, os shaders também são capazes de realizar animação [3], tessellation [41], e até cálculo de propósito geral (GPGPU)2 . O pipeline da Figura 3.1 mostra as fases programáveis que são encapsuladas em shaders.O pipeline estendido de shaders adiciona o Tessellation Control Shader – TCS, Tessellation Evaluation Shader – TES e Geometry Shader – GS. Basicamente GS é projetado para executar uma vez para cada primitiva. Ele tem acesso a todos os vértices da primitiva, bem como os valores de todas as variáveis de entrada associados com cada vértice. O uso de TCS e TES apresenta recursos para alterar a topologia e a geometria da malha. Por último, OpenGL fornece estruturas que gerenciam o armazenamento dos dados na GPU dentre os mais importantes e usados neste trabalho estão aqueles que manipulam objetos 1 O desenvolvimento é controlada por Kronos Group http://www.khronos.org/ Tais como dinâmica de fluidos, dinâmica molecular, criptografia, e assim por diante, que muitas vezes usam APIs especializadas, como CUDA ou OpenCL. 2 Capítulo 3. O framework Proposto 32 buffer denominados Vertex Buffer Object – VBO, contêineres de objetos vértices denominados Vertex Array Object – VAO, texturas unidimensional denominadasTexture Buffer Object – TBO, rendering de imagens denominados Frame Buffer Object – FBO. A última estrutura é interessante porque permite aplicar render-to-texture, que serve para escrever os resultados de um à memória de modo que, em seguida, pode ser utilizado como entrada em outras passagens pelo pipeline gráfico. 3.2 Componentes do Sistema Nesta seção vamos apresentar as componentes do sistema. Primeiramente apresentaremos o hardware utilizado, Kinect. Em seguida apresentaremos a arquitetura dos softwares que representam a estrutura de base que irá apoiar o desenvolvimento e funcionamento do framework. Figura 3.2: O Microsoft Kinect. 3.2.1 O Microsoft Kinect No presente trabalho, o dispositivo de entrada para a aquisição das imagens de cor e dos mapas de profundidade é feita a partir do Microsoft Kinect. Kinect foi apresentado em novembro de 2010, como um acessório para console Xbox 360, desenvolvido pela empresa PrimeSense em colaboração com a Microsoft. Em fevereiro de 2012, uma versão para Windows foi lançada. Os dados adquiridos têm naturezas diferentes e complementares, combinando geometria com atributos visuais. Por esta razão, o Kinect é uma ferramenta flexível que pode ser usada em aplicações de diversas áreas, tais como: Computação Gráfica, Processamento de Imagens, 33 3.2. Componentes do Sistema Visão Computacional e Interação Humano-Computador. A plataforma Kinect abrange tecnologias como uma câmera RGB, sensor de profundidade em 3D, microfone e uma inclinação motorizada (Figura 3.2). O sensor de profundidade da câmera IR do Kinect está baseada na tecnologia de luz estruturada. A técnica para capturar os mapas de profundidade é suportada por um modelo geométrico. Figura 3.3: O padrão infravermelho é organizado como matriz 3 × 3 de pontos. Pode-se ver que cada parte é diferenciada pela intensidade e têm um ponto de registro centralizado. Modelo geométrico: O mapa de profundidade é obtido a partir da triangulação feita entre o emissor IR e a câmera receptora do equipamento, como mostrado na Figura 3.4. Essa técnica consiste na iluminação da cena feita pelo emissor, com uma faixa de luz conhecida que é o padrão infravermelho (Figura 3.3), e na observação da mesma pela câmera posicionada em um ângulo conhecido em relação ao emissor IR. A posição tridimensional dos pontos da cena é determinada a partir da interseção entre a direção da visão da câmera e a direção da luz produzida pelo emissor. Aquisição dos dados: Dentre os dados que o Kinect provê temos a imagem RGB, mapa de profundidade e a imagem IR (mostrados na Figura 3.5). Mas no presente trabalho utilizamos apenas as imagens RGB e os mapas de profundidade. Sensor de Profundidade: O sensor de profundidade 3D consiste em um projetor de laser infravermelho, que capta profundidade em qualquer condição de iluminação, Capítulo 3. O framework Proposto 34 X R, T Z Y X CIR 2.5 cm CRGB CProjetor b=7.5 cm Figura 3.4: Modelo Geométrico do Kinect: C IR , CRGB , C Pro jetor são as câmeras IR, RGB e emissor IR. A localização X é determinada a partir da interseção entre a direção da visão da C IR e a direção da luz produzida pelo emissor da C Pro jetor . R,T são os parâmetros de Rotação e Translação usados para projetar dados da visão da C IR à visão da CRGB (Adaptado de [38]). fornecendo informações detalhadas sobre o meio ambiente. Juntos, o projetor e o sensor criam um mapa de profundidade baseado no modelo geométrico. Esse sensor tem um limite prático de distância variando de 0.8 a 3.5 metros. Contudo, a qualidade da medição de profundidade é afetada se o laser é projetado fora de um quarto; ou seja, o sensor IR do Kinect se torna não confiável na presença de luz solar porque a luz IR do sol se sobrepõe à luz IR ou pulso emitido a partir do sensor. A câmera IR opera em 30 Hz proporcionando mapas de profundidade de 640 × 480 pixels com uma precisão de 11 bits. Neste caso, os dados de profundidade podem ser considerados como imagens com um único canal, ou seja, podem ser convertidos em imagens monocromáticas. Câmera RGB: Ela produz vídeo de cor a 30 Hz, também com uma resolução máxima de 640 × 480 pixels, com 32-bits, ou seja, 8-bits por canal RGB. 35 3.2. Componentes do Sistema (a) (b) (c) Figura 3.5: Dados gerados pelo Kinect: (a) Imagem RGB; (b) Mapa de profundidade; (c) Imagem IR. As câmeras de luz estruturada têm a vantagem de prover mapas de profundidade densos com alta precisão em relação às câmeras ToF e às utilizadas para visão estéreo [38]. Em compensação, as imagens de profundidade obtidas por tal sistema têm as desvantagens de ruído e regiões sem dados (em uma imagem de profundidade são refletidas em cor preto). Isto é causado por várias razões: curta distância entre o objeto e o sensor, falta de reflexão de luz de alguns objetos, superfícies especulares, superfícies refinadas como o cabelo humano, a orientação da superfície frente ao eixo principal da câmera IR, pois as normais sofrem desvios e a medição se torna pouco confiável perto de descontinuidades de profundidade e disparidade entre o projetor eo sensor. 3.2.2 Ferramentas Abaixo estão descritas as ferramentas e tecnologias utilizadas no framework. O framework Qt O framework Qt permite o fácil desenvolvimento de aplicações gráficas profissionais multiplataforma usando C++. Qt fornece módulos compatíveis com OpenGL e GLSL, ou seja, proporciona um conjunto completo de recursos para programação em GPU [12]. Com o Qt, matrizes, vetores, objetos de buffer de vértices, texturas, shaders e componentes de interface Capítulo 3. O framework Proposto 36 do usuário são integradas por classes no paradigma orientado a objetos e intercomunicam-se pelo mecanismo Qt de signal/slots. As bibliotecas Qt e ferramentas estão reunidas no Qt SDK. Qt também fornece uma IDE, denominada Qt Creator. Ela é uma solução completa para o desenvolvimento UI (User Interface). Disponível para Windows, Linux e Mac OS X, Qt é open source e disponível sob licenças diferentes, incluindo GNU LGPL 2.1, GNU GPL 3.0 e uma licença para desenvolvimento comercial. Bibliotecas Dentre as bibliotecas escolhidas neste trabalho e que suportam e fornecem funcionalidades para o desenvolvimento de aplicações para o Kinect, estão o PCL, OpenNI e OpenCV. PCL. Point Cloud Library 3 – PCL é um projeto open source em grande escala para imagens 2D/3D e para o processamento de nuvens de pontos. Ele é liberado sob a licença BSD e é livre para uso comercial e de pesquisa. PCL é destinada para ser uma biblioteca multiplataforma. Para a aquisição de dados o PCL fornece interface para a API OpenNI. PCL é integrado com o Visualization Toolkit – VTK [37]. PCL contém algoritmos para filtragem, reconstrução da superfície, registro de movimentos da câmera, segmentação, extração de características, registro, busca por vizinhanças, manipulação de entrada e saída entre outras. OPENNI. Open Natural Interaction4 – OpenNI, é um framework open source multi-linguagem e multi-plataforma cujo objetivo principal é oferecer um padrão que permita a comunicação com sensores visuais e de áudio. A API do OpenNI elimina a dependência entre os sensores e middleware, permitindo que os aplicativos sejam escritos sem esforço adicional em cima dos módulos diferentes de middleware. A OpenNI permite também que as aplicações sejam escritas independentemente do tipo de sensor e dos fornecedores de 3 4 http://pointclouds.org/ http://www.openni.org/ 37 3.3. Arquitetura do Sistema middleware, o que torna possível a utilização de muitos dispositivos de captura disponíveis5 publicamente, como PrimeSense PSDK, Microsoft Kinect, Asus XtionPro. OPENCV. A Open Computer Vision6 – OpenCV é uma biblioteca open source livre sob a licença BSD e multi-plataforma. OpenCV é uma biblioteca bem estabelecida para Visão Computacional 2D, mas também coloca em seus algoritmos métodos para dados 3D. 3.3 Arquitetura do Sistema A arquitetura é a estrutura base da implementação do sistema, representado no modelo de estrutura de fluxo de dados que define componentes que interagem uns aos outros. O sistema foi subdividido de forma que cada grupo de funcionalidades possuísse um modulo específico que as implementasse. A Figura 3.6 mostra o diagrama componente–conector do sistema. O componente DepthProcessing é responsável por lidar com todas as operações que são feitas com os mapas de profundidade. Esse componente carrega um mapa de profundidade a partir do componente PCL que trabalha internamente com a biblioteca OpenNI para capturar dados do Kinect. PCL fornece os dados em arrays tipo uchar com formato de 16 e 32 bits para mapas de profundidade e imagens de cor, respectivamente. A biblioteca OpenCV é responsável pela conversão dos dados originais em formato IplImage para manipulação e processamento das imagens. A descrição das bibliotecas é abordada na Seção 3.2.2. O mesmo componente também é responsável pela melhora do mapa de profundidade com a aplicação de processos na GPU como preenchimento de buracos e filtragem, usando as técnicas de limiarização de Sobel, downsampling, upsampling, filtro Joint Bilateral, fluxo óptico e filtro espaço-temporal. Cada processo na GPU usa um Programa Shader dividido em Vertex Shader e Fragment Shader para realizar as respectivas tarefas de transformações e rendering. Esses conceitos são descritos na Seção 3.1. A saída de cada processo permanece na memória da GPU e está pronta para ser utilizada como entrada na próxima etapa do processo. 5 6 http://pointclouds.org/documentation/tutorials/openni_grabber.php http://opencv.org/ Capítulo 3. O framework Proposto Figura 3.6: Visão geral da arquitetura do sistema. 38 39 3.4. Ambiente de Teste O componente RGB-ZProcessing é responsável por processar e manipular dados RGB-Z (vídeos RGB e mapas de profundidade filtrados). Os dados de saída em forma de texturas do componente anterior DepthProcessing, armazenadas na GPU, são utilizados para aplicar os processos de efeitos especiais, também na GPU, usando um Program Shader para cada um deles. Este componente é responsável pela estilização dos vídeos, por exemplo, da aplicação do efeito Cartoon Shading. O componente View permite visualizar um vídeo RGB-Z com algum efeito especial capturado pelo RGB-ZProcessing. Esse componente utiliza a interface provida pelo QGLWidget, que é uma classe do Qt que proporciona suporte para rendering de gráficos OpenGL [12]. Essa classe, por sua vez, se comunica com componentes da UI através do conector signal and slot, também do Qt. Um signal é emitido quando um evento associado a algum objeto emissor é acionado, por exemplo, quando um botão na UI é pressionado e liberado. Um slot, por sua vez, é um método de um objeto receptor que é chamado em resposta a um signal particular. 3.4 Ambiente de Teste Esta seção descreve o ambiente computacional e os requisitos do framework. Neste trabalho desenvolvemos a aplicação no framework Qt usando o IDE Qt Creator (Figura 3.7), a API gráfica de OpenGL e C++ como linguagem de programação. A aplicação foi desenvolvida no sistema operacional Ubuntu 12.04 LTS. Os testes também foram feitos neste sistema operacional. Isto não exclui que a aplicação seja multi-plataforma, já que seus componentes são compatíveis em qualquer plataforma. O código na GPU foi desenvolvido em GLSL e testado com a placa gráfica NVIDIA GeForce GT 540M de arquitetura Fermi GF108. A Tabela 3.1 sumariza as principais características da GPU, da CPU e da câmera. As bibliotecas e ferramentas necessárias para o framework são listadas na Tabela 3.2 com suas respectivas versões. Capítulo 3. O framework Proposto 40 Tabela 3.1: Especificações técnicas do hardware. Hardware Câmera CPU GPU Componente Dispositivo Processador Memória Placa Gráfica OpenGL Renderer OpenGL GLSL Version Shader Memória Shader Processors Especificação Técnica Kinect XBOX 360 Intel Core i5-2430M, 2.4 GHz x 4 4GB de memória RAM NVIDIA GeForce GT 540M GeForce GT 540M/PCIe/SSE2 4.3 4.3 NVIDIA via Cg compiler 5.0 1024 MB de memória RAM 96 Figura 3.7: O framework Qt utilizado como suporte para o desenvolvimento do sistema. 41 3.4. Ambiente de Teste Tabela 3.2: Bibliotecas e Ferramentas do framework com suas respectivas versões. Bibliotecas/Ferramentas Qt Framework IDE Qt Creator PCL OpenCV OpenNI Glew Boost Flann Eigen Versão 5.0.1 2.6.2 1.6 2.3.1-7 1.3.2.1-4 1.6.0 1.48 1.7.1 3.0.5 Capítulo 4 Desenvolvimento do Framework O objetivo da criação do framework é construir um sistema computacional capaz de atender as necessidades exigidas para o processamento de vídeos RGB-Z, assim como desenvolver componentes reutilizáveis do sistema. Uma das grandes vantagens do desenvolvimento de software baseado em uma arquitetura é a possibilidade da criação de um esqueleto para o sistema, a fim de estender cada um de seus componentes. A aplicação está em conformidade com a arquitetura projetada na Seção 3.3. Outro ponto importante que diz respeito ao desenvolvimento do projeto é a integração do Kinect com as bibliotecas e ferramentas usadas neste trabalho (Seção 3.4). A integração destes componentes (com a definição de um conjunto de processos e funções) está apoiada no propósito do processamento de vídeo RGB-Z, no qual, todos eles se complementam e criam um ambiente de execução apropriado para o sistema, ao mesmo tempo que mantém o processamento em tempo real. Portanto, o projeto envolve o estudo de conceitos específicos de processamento de imagens como preenchimento multirresolução de buracos, filtragem espaçotemporal e efeito não-fotorrealístico, bem como o estudo de métodos de programação que fornecem suporte para aplicações na GPU. O desenvolvimento do framework abrange as etapas do pipeline indicado na Figura 2.1. Capítulo 4. Desenvolvimento do Framework 4.1 44 Descrição de Classes e Funções As classes e funções são representadas no diagrama de interação mostrado na Figura 4.1. MainWindow É a classe responsável pela inicialização do sistema. Ela serve como ponte entre a interface gráfica e as classes principais do sistema. Esta classe inicializa e executa o processo de aquisição de dados do Kinect. Em seguida, envia esses dados para serem processados em uma classe com suporte de funcionalidades OpenGL (para cada janela da interface gráfica como ilustrado na Figura 5.1). MainWindow aplica as seguintes funções: OpenNIGrabber(): Instancia o objeto grabber da classe PCL:OpenNIGrabber com os parâmetros deviceID que é o identificador de dispositivo. modeI e modeD são os modos de captura dos dados de imagem e profundidade, respectivamente; OpenNIViewer(): Instancia o objeto onv_rgba da classe OpenNIViewer com o parâmetro grabber, que é o gravador configurado para capturar e armazenar os dados da cena; Thread(): Pertence a classe boost::thread() que herda as propriedades de uma linha de execução. Nela é atribuído o parâmetro função onv_rgba::run() que captura e armazena os dados em background; setDataONV_Rgba(): Envia os dados obtidos em onv_rgba para a classe KinectView onde serão processadas. Os dados enviados como parâmetros são ptsPcl (nuvem de pontos com propriedades de cor e de profundidade para cada ponto da cena), rgbImg (a imagem de cor) e depthImg (a imagem de profundidade); setNumWidgetView(): Envia para a classe KinectView o parâmetro numWidget, que é o número de janela (widget) da interface gráfica onde serão apresentados os processamentos sobre os dados. 45 4.1. Descrição de Classes e Funções :GUI :MainWindow :PCL::OpenNiGrabber :OpenNiViewer :KinectView :HandleMethod 1: OpenNiGrabber(deviceID, modeI, modeD) grabber Fwk::User 2: OpenNiViewer(grabber) onv_rgba 3: Thread(onv_rgba::run) 4: setDataONV_Rgba(ptsPcl, rgbImg, depthImg) 5: setNumWidgetView(numWidget) 6: setMethod(typeMethod) 10: InitMethod(typeMethod, paramConfig) 7: createShaders() 8: createVBOs() 9: createTextureOutput() 11: setDataONV_Rbga(ptsPcl, rgbImg, depthImg) 12: ApplyMethod() 13: getTextureOutputMethod() TextureOutput 14: DrawGL(TextureOutput) DisplayGraphicsWidget(numWidget) Figura 4.1: Diagrama de interação da visualização de um método do framework. Capítulo 4. Desenvolvimento do Framework 46 PCL::OpenNIGrabber É uma classe da biblioteca PCL. Fornece uma interface de captura genérica para um acesso fácil e conveniente para diferentes dispositivos e seus drivers. OpenNIViewer É a classe que inicializa a conexão do grabber com o dispositivo usando funções callback para cada tipo de dados a serem obtidos, e, posteriormente executa o registro deles. Assim, fornece dados da imagem de cor (tipo boost::shared_ptr<openni_wrapper ::Image>), com um formato de 32 bits, sendo 8 bits para cada canal, e de profundidade (tipo boost:: shared_ptr<openni_wrapper::DepthImage>), com um formato de 16 bits para um canal em ponto flutuante. Esses dados são obtidos em arranjos que são transformados em imagens (tipo IplImage), usando a biblioteca OpenCV. O grabber também proporciona dados no espaço 3D como nuvem de pontos (tipo pcl::PointCloud<pcl::PointXYZRGBA>:: ConstPtr), onde cada ponto da profundidade está alinhado à sua respectiva cor. KinectView É uma extensão da classe QGLWidget que proporciona suporte para rendering de gráficos OpenGL. QGLWidget fornece métodos virtuais que devem ser implementadas para executar tarefas OpenGL comuns. Três deles são os mais utilizados e implementados na aplicação: initializeGL(), resizeGL() e paintGL(). initializeGL() é chamado sempre que o widget é atribuído a um novo contexto OpenGL. Destina-se a conter o código de inicialização OpenGL que vem antes da primeira chamada para os outros métodos. resizeGL() manipula o redimensionamento da janela OpenGL. paintGL() aplica o rendering da cena OpenGL sempre que o widget precisa ser redesenhado. Ele é atualizado em cada intervalo de tempo. Antes de fazer rendering a classe recebe o typeMethod, tipo de método a ser processado, e os dados obtidos do Kinect prontos para serem usados. Assim temos: InitMethod(): Inicializa um dos métodos que pertencem à classe HandleMethod. Para isso, envia o parâmetro typeMethod e os dados de configuração do método envolvidos no parâmetro paramConfig; 47 4.1. Descrição de Classes e Funções SetDataONV_Rgba(): Envia para a classe HandleMethod os dados da cena que o método precisa. Esses são ptsPcl (nuvem de pontos), rgbImg (imagem de cor) e depthImg (imagem de profundidade). Os dados são enviados em cada intervalo de tempo; ApplyMethod(): Aplica o método escolhido no processamento. Cada método pertence a classe HandleMethod. Nesta função, cada método está habilitado para executar o renderto-texture; getTexturaOutPutMethod(): Com este método a classe KinectView obtém a textura de saída da função anterior; DrawGL(): Em cada intervalo de tempo, esta função aplica o rendering final para que a cena seja exibida na tela. As tarefas OpenGL necessárias para o rendering (rendering-to-texture ou rendering na tela) são mostradas na Listagem 4.28 na seguinte ordem: • Transformações model-view: Qt fornece um conjunto de classes de vetores e matrizes para trabalhar com transformações geométricas e configurações da câmera. Na classe KinectView declaramos dois objetos tipo QMatrix4x4. Eles são modelViewMatrix e ProjectionMatrix. Primeiro cada objeto matriz é inicializado para a matriz identidade. Em seguida, à direita, multiplica-se pela matriz correspondente a um método específico. Os métodos de ProjectionMatrix indicam o tipo de projeção na tela. Os métodos de modelViewMatrix criam uma matriz de visualização, além de aplicar transformações geométricas (rotação, translação e escala); • Binding [41] do programa shader: tarefa do tipo QGLShaderProgram; • Carregamento dos dados uniform para a GPU: o conteúdo das estruturas definidas (vetores e matrizes) na CPU podem ser vinculadas a atributos do shader utilizando métodos da classe QGLShaderProgram, declarados como tipos de dados nativos vec2, vec3, vec4, mat2, mat3, mat4. (Listagem 4.24 , Linhas 7 e 8); Capítulo 4. Desenvolvimento do Framework 48 • Binding dos objetos buffer e texturas para a GPU: a textura é atribuída a uma variável shader, que pode ser do tipo sampler2D ou, como o exemplo samplerBuffer (Listagem 4.24 na Linha 10) onde passamos dados usando TBO, segundo a unidade de textura correspondente. Finalmente, vinculamos a textura ao shader passando como parâmetro o identificador de textura que lhe foi atribuído quando criada. Cada objeto buffer VBO é igualmente vinculado e, atribui-se a eles os atributos correspondentes do programa shader. Após isso, chama-se o comando nativo OpenGL adequado (glDrawElements() ou glDrawArrays()) para desenhar a malha ou textura; • Liberação dos buffer objects e shader programs: o método DrawGL() é encerrado. O framework Qt também fornece métodos virtuais na classe QGLWidget que são chamados em resposta a eventos do mouse. Esses métodos manipulam um trackball virtual definido na classe Trackball [12] e uma mudança de escala. Para que isso seja posível, a classe KinectView implementa os métodos mouseMove(), mousePress(), mouseRelease() e wheelEvent(). HandleMethod É a classe que representa cada método no processamento de vídeos RGB-Z. A classe define as estruturas e o programa shader para cada método específico. A classe recebe os dados prontos do Kinect através da classe Kinectview. HandleMethod aplica as seguintes funções: CreateVBOs(): permite definir objetos buffer exigidos pelo método, como mostrado na Listagem 4.27. O conceito de VBO permite a manipulação de atributos dos vértices na GPU tais como posição, normais, texturas, cores, índices, etc. O Qt fornece a classe QOpenGLBuffer para lidar com objetos buffer; CreateShaders(): permite definir o programa shader exigido pelo método. O Qt fornece a classe QGLShaderProgram para manipulá-lo. Essa classe provê métodos para compilar o código fonte em GLSL como um tipo específico da classe QGLShader (Vertex, 49 4.1. Descrição de Classes e Funções Geometry e Fragment) e adiciona-lo para esse programa shader, como mostrado na Listagem 4.1, Linhas 1 à 20; CreateTextureOutput(): executa duas sub-funções, a criação de texturas e de objetos FBO (mostrados na Listagem 4.1). Para criar uma textura definimos três características principais: tipo (por exemplo GL_TEXTURE_2D), tamanho (por exemplo 640×480) e formato (por exemplo GL_RGBA). Uma textura pode ser utilizada de duas maneiras, como entrada ou saída no rendering de um programa shader. Quando a saída do rendering é em uma textura (ou seja fora da tela), o método é conhecido como render-to-texture e precisa definir um objeto FBO. O FBO permite ao usuário criar um framebuffer para fazer rendering fora da tela. As duas sub-funções retornam um identificador com os quais são vinculadas a um shader na aplicação do método. Quando a classe Kinectview executa a função ApplyMethod(), está sendo aplicado um método que, por sua vez, pode executar outros sub-métodos. Isso depende do tipo de método. Esses sub-métodos também pertencem à mesma classe HandleMethod. GUI Representa a interface gráfica do usuário. Ela possui um ou vários widgets fornecidos pela classe QGLWidget. Cada widget é exibido em uma janela na tela contendo o rendering final da classe Kinectview. Neste caso, representado pelo método DisplayGraphicsWidget segundo o número de janela refletido no parâmetro numWidget. User Representa o usuário que pode escolher o método a ser aplicado. Esta operação é feita através de mecanismos signal/slots do framework Qt, que intercomunicam componentes da UI com a classe Kinectview. O usuário aplica a função slot setMethod(), introduzindo o parâmetro typeMethod, em resposta a um signal particular quando pressiona e libera um botão na UI. Capítulo 4. Desenvolvimento do Framework 4.2 50 Relações das Classe nos Processos do Framework Em seguida serão descritas as relações das classes nos processos do framework. Captura dos dados Este processo tem a tarefa de capturar os dados do Kinect. A classe Mainwindow é responsável pela inicialização, configuração e execução dessa tarefa. Ela define uma interface de suporte para a captura usando a classe PCL::OpenNIGrabber. Em seguida, inicia-se a conexão e o registro dos dados usando a classe OpenNIViewer. O registro é executado em background em uma linha de execução pertencente à classe Thread. Assim, os dados do Kinect estão prontos para serem processados, como imagem de cor, imagem de profundidade e mapa de profundidade. Logo, os dados são passados para a classe kinectview onde é aplicado o rendering de cada um deles. Finalmente, a saída é exibida na tela usando a classe GUI. Preenchimento em multirresolução Este processo tem a tarefa de preencher regiões sem informação do mapa de profundidade. O processo segue os passos que foram explicados na Seção 2.2. A classe User faz o pedido da aplicação do processo, enviando o tipo de método para a classe kinectview. Esta classe chama o método Fill_In da classe HandleMethod enviando os parâmetros de configuração dos dados do Kinect. Esse método utiliza outros submétodos que pertencem à mesma classe HandleMethod. São eles, Sobel, Downsampling, Upsampling e CrossBilateralFilter. Em primeiro lugar, o método Fill_In cria estruturas e shaders. Em seguida, aplica o método Sobel, cuja saída é uma textura. Após isso, segundo o algoritmo de preenchimento em multirresolução, o método Downsampling é primeiramente aplicado nas texturas do Sobel e da imagem de cor e, suas saídas servem para aplicar o método de forma iterativa (de acordo com o número de níveis de resolução que é passado como parâmetro). O próximo passo é a aplicação dos métodos Upsampling e CrossBilateralFilter sobre as texturas resultantes do método Downsampling em cada nível. A saída final é uma textura que retorna à classe kinectview para fazer o rendering na 51 4.3. Interação dos Shaders tela usando a classe GUI. Filtro espaço-temporal Este processo tem a tarefa de filtrar o mapa de profundidade nas di- mensões de espaço e tempo. Depois da classe User fazer o pedido para aplicar esse processo, a classe Kinectview chama o método SpatioTemporalFilter. Em seguida envia-lhe parâmetros e dados respectivos. Esse método invoca os sub-métodos Fill_In e OpticalFlow. Primeiramente, o método Fill_In é aplicado como foi descrito no processo anterior. Logo após, aplica o método OpticalFlow utilizando como dados as texturas da imagem de cor, do frame atual bem como do frame anterior. Tanto as texturas resultantes desses métodos quanto as texturas da imagem de cor, do frame atual e do frame anterior, são dados usados para aplicar o método SpatioTemporalFilter, adicionando, como um dado a mais seu resultado a partir do segundo intervalo de tempo da execução do método (ou seja, o método utiliza cinco texturas como entradas). Finalmente, como todo método que pertence à classe HandleMethod, a textura de saída é enviada para a classe Kinectview, a qual faz rendering dela na tela usando a classe GUI. Efeitos sobre os vídeos Este processo tem a tarefa de aplicar um efeito especial em uma imagem ou em uma malha. Da maneira semelhante aos processos anteriores, as classes User, Kinectview e GUI desempenham o mesmo papel. Aqui é chamado o método Efeito que, por sua vez, invoca o sub-método SpatioTemporalFilter. A textura resultante desse submétodo é usada para extrair a informação exata da profundidade da cena, a fim de executar um efeito especial específico na imagem de cor ou na malha. 4.3 Interação dos Shaders No desenvolvimento do presente trabalho, todos os algoritmos de processamento e de rendering são executados na GPU por meio de programas shaders. Como dito anteriormente, na Seção 3.1, os shaders são arquivos de texto encapsulados em programas shaders em uma apli- Capítulo 4. Desenvolvimento do Framework 52 cação na CPU, tornando possível a leitura, compilação, link e execução dos mesmos na GPU. Em relação aos processos definidos na seção anterior, apresentamos na Figura 4.2 a interação dos shaders representada em um fluxograma. Ele nos dá uma visão geral de como os shaders estão conectados com relação à entrada e saída de dados em cada método. O fluxograma mostra de modo claro quais são os dados de entrada e saída de um método. Igualmente mostra os arquivos shaders usados em cada método. De maneira simplificada, a linha vertical tracejada separa as principais funcionalidades da CPU e GPU no sistema. Na CPU os dados são capturados, preparados e enviados para GPU. A GPU recebe esses dados e os processa de acordo com o método. A saída de cada método é armazenada na memória da GPU e, em seguida, convertida em dado de entrada para outro método na mesma GPU. O resultado é então mostrado na tela. O bloco (A) do fluxograma reúne todos os métodos utilizados no processo de preenchimento. O processo tem dois níveis de sub-resolução. Para cada nível de resolução são aplicados os métodos Upsampling e CrossBilateralFilter segundo o Algoritmo 1. O bloco (B) reúne os métodos para o processo de filtragem. O dado PreviousColor é a textura da imagem de cor do frame no intervalo de tempo prévio. O dado PreviousSTDepth é a saída do método SpatioTemporalFilter aplicado no frame do intervalo de tempo anterior. O bloco (C) aplica apenas um método para o processo de efeito especial. Com o objetivo de representar o uso de texturas como dados de entrada ou de saída de um método, o fluxograma da Figura 4.3 mostra um fluxo padrão para todos os métodos em relação ao uso dessas texturas. Na aplicação na CPU, o processo de criação de textura retorna um identificador de textura, que é utilizado para ser enlaçado com um FBO (que permite fazer render-to-texture). Em seguida é aplicado o método na GPU. A textura resultante é armazenada na memória dela, pronta para ser utilizada por qualquer outro método. Assim, por exemplo para exibir a textura na tela, é necessário enviar como dado de entrada o identificador dessa textura para a GPU no programa shader que realizará o rendering. Por outro lado, o Framework oferece também tipos diferentes de visualização dos dados. 53 4.3. Interação dos Shaders CPU GPU Begin Kinect Color Depth A Sobel: vSobel.glsl fSobel.glsl DepthSobel DownSampling 1: DownDepth 1 DownSampling 2: vPosVertices.glsl fDownSampled.glsl DownColor 1 vPosVertices.glsl fDownSampled.glsl Cross Bilateral Filter: vPosVertices.glsl fCrossBilateralFilter.glsl CBDepth 1 DownDepth 2 DownColor 2 Cross Bilateral Filter 1: Cross Bilateral Filter 2: vPosVertices.glsl fCrossBilateralFilter.glsl vPosVertices.glsl fCrossBilateralFilter.glsl UpSampling: UpSampling 1: UpDepth 1 UpDepth vPosVertices.glsl fUpSampled.glsl CBDepth 2 vPosVertices.glsl fUpSampled.glsl Previous Color CBDepth B Optical Flow: vPosVertices.glsl Rgb2gray.glsl Linear1D5Filter.glsl Reduce.glsl Derivates1D5.glsl SecondDerivates1D5.glsl CalculateTensor.glsl Phi3.glsl JacobiAndUpdateUV.glsl Scale.glsl ScaleToResult.glsl Spatio Temporal Filter: OFImage vPosVertices.glsl fSpatioTemporalFilter.glsl Previous STDepth STDepth C Effect Image: Display EffectImage vEffectColorMap.glsl fCartoonShadingColorMap.glsl End Figura 4.2: Interação dos shaders, mostra entradas e saídas de dados para cada método das fases no todo o processamento. Cada fase é representada em blocos, A: preenchimento, B: filtragem e C: efeito especial. Cada método contém os arquivos shader em GLSL. Capítulo 4. Desenvolvimento do Framework CPU 54 GPU Begin Create Texture IDTexture Create FBO and bind to Texture Method Program Shader Applying the Method Display Method? No Memory GPU Yes Binding IDTexture End Rendering to Texture Rendering Program Shader Texture Display Figura 4.3: Fluxograma do rendering de um método: rendering-to-texture ou rendering na tela. Elas estão implementadas em shaders de acordo com as características que apresentam, ou seja, dependendo se os dados são originais do Kinect (com ruído e buracos) ou processados (filtrados). Os seguintes fluxogramas representam três métodos de visualização, como nuvem de pontos, malha como wireframe e superfície da malha com aplicação da iluminação. Para cada um desses métodos são considerados dados de entrada e programa shader específicos. No caso de visualizar os dados filtrados é preciso mudar apenas o Vertex Shader em relação aos dados originais. Em particular, para fazer rendering de uma nuvem de pontos, o fluxograma da Figura 4.4-a apresenta o caso com dados originais do Kinect, no qual destacam vértices e cores encapsulados em objetos VBO (vboVertex e vboColor, respectivamente). Devido à natureza 55 4.3. Interação dos Shaders CPU CPU GPU GPU Begin Begin vboVertex Color Texture vboColors vboTexCoords Memory GPU STDepth Texture Raw Point Cloud Program Shader: vboVertex Others vPointCloudCol.glsl fPointCloudCol.glsl Full Point Cloud Program Shader: Others vPointCloudDepthFilt.glsl fPointCloudCol.glsl End Display End Display (a) (b) Figura 4.4: Fluxograma de visualização da nuvem de pontos. Em (a) dados originais e em (b) dados filtrados. dos dados, nem todos os pontos contêm profundidade e cor. Já no segundo caso, mostrado na Figura 4.4-b, a profundidade de todos os pontos é atualizada com o mapa de profundidade filtrado e armazenado na memória da GPU (textura STDepth). A textura Color é enviada para GPU, a qual obtém a cor de cada ponto. As coordenadas de textura, encapsuladas em um VBO (vboTexCoord), trabalham com as localizações dos pontos nas texturas na GPU. Além desses dados, são enviados dados de projeção e transformações de coordenadas, representado no dado Others. O programa shader é composto por Vertex e Fragment Shader. Para a visualização da malha como wireframe, a Figura 4.5-a mostra no caso de visualizar os dados originais são enviados para a GPU, além dos vértices, os índices encapsulados em um VBO (vboIndex). Os índices indicam como estão distribuídos os vértices para formar uma face de um objeto, neste caso a cena capturada. A Figura 4.7 mostra a malha formada por faces triangulares. Para os dados filtrados, a Figura 4.5-b mostra, como entradas, os mesmos índices além de vboVertex, vboTexCoord, STDepth e Others, utilizados do mesmo modo que na visualização de nuvem de pontos com dados filtrados. O programa shader para conseguir o wireframe é composto por Vertex, Geometry e Fragment Shader. Capítulo 4. Desenvolvimento do Framework CPU 56 CPU GPU GPU Begin Begin vboVertex vboIndex vboIndex vboTexCoords Memory GPU STDepth Texture Raw Wireframe Program Shader: Others End vboVertex vWireframe.glsl gWireframe.glsl fWireframe.glsl Display Full Wireframe Program Shader: Others End vWireframeDepthFilt.glsl gWireframe.glsl fWireframe.glsl Display (b) (a) Figura 4.5: Fluxograma de visualização da malha como wireframe. Em (a) dados originais e em (b) dados filtrados. CPU CPU GPU GPU Begin Begin vboVertex vboIndex vboIndex vboTexCoords vboTexCoords vboVertex Memory GPU STDepth Texture Full Phong Program Shader: Others TBO vPhongDepthFilt.glsl gPhong.glsl fPhong.glsl Raw Phong Program Shader: Others End vPhong.glsl gPhong.glsl fPhong.glsl Display End (a) Display (b) Figura 4.6: Fluxograma de visualização da superfície da malha com aplicação de iluminação. Em (a) dados originais e em (b) dados filtrados. Finalmente, visualiza-se a superfície da malha com aplicação da iluminação de Phong (descrita na Seção 4.4.4). No caso dos dados originais, a Figura 4.6-a mostra que, além de vértices e índices (como no método anterior), é enviada para GPU a estrutura TBO. Ele armazena os 57 4.4. Implementação de Shaders em GLSL dados de profundidade em forma de uma textura unidimensional com a finalidade de calcular as normais de cada vértice na GPU. Para isto, é preciso definir coordenadas de textura em vboTexCoord. Em Others é adicionado dados da luz (posição, direção, cor, propriedades) e a matriz inversa da matriz de transformações de coordenadas, que será aplicada para transformar as normais dos vértices nas coordenadas de olho (sistema de coordenadas do olho, ou da câmera, é aquele em que todos os objetos da cena serão desenhados). A Figura 4.6-b mostra a variação que, ao invés de usar TBO para encontrar as normais na GPU, utiliza a textura STDepth armazenada na memória da GPU. Todos os outros dados de entrada são utilizados da mesma forma que no método anterior. Aqui, também, o programa shader é composto por Vertex, Geometry e Fragment Shader. 4.4 Implementação de Shaders em GLSL Nesta seção são descritos detalhes técnicos da implementação do Framework. Os shaders usados para o processamento de vídeo RGB-Z e sua interação são mostrados no fluxograma da Figura 4.2. No caso dos processos de preenchimento e filtragem, foram adaptados os shaders implementados por Richardt et al. [36], de acordo com o objetivo do presente trabalho. Além disso, os shaders para o fluxo óptico são implementados conforme o trabalho de Eisemann et al. [8]. A implementação de cada processo ocorre conforme a descrição apresentada na Seção 4.2. 4.4.1 Shaders do Preenchimento em Multirresolução O método de Preenchimento em Multirresolução é denominado Fill_In, ele pertence à classe HandleMethod e utiliza os sub-métodos Sobel, Downsampling, Upsampling e CrossBilateralFilter. Como é representado na Figura 4.1, todos os métodos criam objetos FBO, texturas e shaders com um formato padrão. Um exemplo de todos eles é mostrado na Listagem 4.1. Capítulo 4. Desenvolvimento do Framework 1 2 3 58 GLboolean Fill_In :: CreateShaders ( QGLShaderProgram * shaderP , const QString & vertPath ,const QString & fragPath ,const QString & geomPath ) { bool result ; 4 result =shaderP -> addShaderFromSourceFile ( vertPath ); if (! result ) qWarning () << shaderP ->log (); result =shaderP -> addShaderFromSourceFile ( fragPath ); if (! result ) qWarning () << shaderP ->log (); if ( geometryShaderPath != "") { result =shaderP -> addShaderFromSourceFile ( geomPath ); if ( ! result ) qWarning () << shaderP ->log (); } result =shaderP ->link (); if (! result ) qWarning () <<"Could not link shader program :"<<shaderP ->log (); return result ; 5 6 7 8 9 10 11 12 13 14 15 16 17 } 18 19 20 21 22 23 24 25 26 27 28 29 GLuint Fill_In :: CreateTextureDataf ( GLuint w, GLuint h) { GLuint texID = 0; glGenTextures (1, &texID); glBindTexture ( GL_TEXTURE_2D , texID); glTexImage2D ( GL_TEXTURE_2D , 0,GL_RGBA16F , w, h, 0,GL_RGBA , GL_FLOAT , 0); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_NEAREST ); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_NEAREST ); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE ); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE ); glBindTexture ( GL_TEXTURE_2D ,0); 30 return texID; 31 32 } 33 34 35 36 37 38 39 40 41 42 43 44 GLuint Fill_In :: CreateFBO ( GLuint texIDs ) { GLuint fboID = 0; glGenFramebuffers (1, &fboID); glBindFramebuffer ( GL_FRAMEBUFFER , fboID); GLenum Db [1] = { GL_COLOR_ATTACHMENT0 }; glFramebufferTexture2D ( GL_FRAMEBUFFER ,Db[0], GL_TEXTURE_2D ,texIDs ,0); glDrawBuffers (1, DrawBuffers ); // "1" is the size of DrawBuffers // Always check that our framebuffer is ok if( glCheckFramebufferStatus ( GL_FRAMEBUFFER ) != GL_FRAMEBUFFER_COMPLETE ) std ::cout <<" GL_FRAMEBUFFER_COMPLETE failed , CANNOT use FBO"; 45 return fboID; 46 47 } Listagem 4.1: CreateFBO(). Fill_In.cpp: métodos CreateShaders(), CreateTextureDataf() e 59 4.4. Implementação de Shaders em GLSL Também na Listagem 4.2 temos a criação de estruturas VBO para o método Sobel. Esse método é o que inicia o processamento sobre os dados da profundidade da cena. Ele é aplicado com a função ApplySobel(), que é uma extensão do método paintGL. Aqui, todos os objetos criados, VBO e shaders, são enlaçados à GPU para fazer, neste caso, o render-to texture definido na Listagem 4.3. Na função ApplySobel(), a profundidade é enviada em duas texturas. A primeira com valores RGB entre 0 e 1 (Linha 18), que representam valores de cor, e a segunda com os valores RGB reais da profundidade (Linha 20). 1 2 3 4 void Sobel :: createVBOs () { glGenVertexArrays (1, &VAO); glBindVertexArray (VAO); 5 numVertices indices vertices texCoords int k 6 7 8 9 10 = = = = = 480*640; new unsigned int[ numVertices ]; new QVector4D [ numVertices ]; new QVector2D [ numVertices ]; 0; 11 for ( unsigned int i = 0; i < 480; i++) for ( unsigned int j = 0; j < 640; j++) { indices [k] = k; vertices [k] = QVector4D (j,i ,0 ,1.0); texCoords [k] = QVector2D (j,i); k++; } 12 13 14 15 16 17 18 19 20 vboVertices = createObjetoBuffer ( QOpenGLBuffer :: VertexBuffer , QOpenGLBuffer :: DynamicDraw ,vertices , numVertices * sizeof ( QVector4D )); deleteVector ( vertices ); 21 22 23 24 vboTexCoords = createObjetoBuffer ( QOpenGLBuffer :: VertexBuffer , QOpenGLBuffer :: StaticDraw ,texCoords , numVertices * sizeof ( QVector2D )); deleteVector ( texCoords ); 25 26 27 28 vboIndices = createObjetoBuffer ( QOpenGLBuffer :: IndexBuffer , QOpenGLBuffer :: StaticDraw ,indices , numVertices * sizeof ( unsigned int)); deleteVector ( indices ); 29 30 31 32 glBindVertexArray (0); 33 34 } Listagem 4.2: Sobel.cpp: método CreateVBOs(). Capítulo 4. Desenvolvimento do Framework 60 As Listagem 4.4 e 4.5 mostram como os dados são convertidos às texturas de imagem e de profundidade, respectivamente. 1 2 3 void Sobel :: ApplySobel () { glBindFramebuffer ( GL_FRAMEBUFFER , FBOSobel ); 4 modelViewMatrix . setToIdentity (); projectionMatrix . setToIdentity (); projectionMatrix .ortho (0, 640 ,480 , 0, -1.0f, 1.0f); if ( ! SobelShaderProgram ->bind () ) { qWarning () << "Could not bind shader program to context "; return ; } SobelShaderProgram -> setUniformValue (" modelViewMatrix ",modelViewMatrix ); SobelShaderProgram -> setUniformValue (" projectionMatrix ",projectionMatrix ); SobelShaderProgram -> setUniformValue (" texDepthMap ", 0); SobelShaderProgram -> setUniformValue (" texDepth ", 1); SobelShaderProgram -> setUniformValue (" sobelThreshold ", 0.10f); glActiveTexture ( GL_TEXTURE0 ); _texImg = createTextureImage (depthimg , context (),_texImg ); glActiveTexture ( GL_TEXTURE1 ); _textura = SetTexturaDepth (); glBindTexture ( GL_TEXTURE_2D , _textura ); 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 vboVertices ->bind (); SobelShaderProgram -> setAttributeBuffer (" vPosition ", GL_FLOAT , 0, 4); SobelShaderProgram -> enableAttributeArray (" vPosition "); vboTexCoords ->bind (); SobelShaderProgram -> setAttributeBuffer (" vTexCoord ", GL_FLOAT , 0, 2); SobelShaderProgram -> enableAttributeArray (" vTexCoord "); vboIndices ->bind (); glDrawElements (GL_POINTS , numVertices , GL_UNSIGNED_INT , 0); 23 24 25 26 27 28 29 30 31 vboIndices -> release (); vboTexCoords -> release (); vboVertices -> release (); SobelShaderProgram -> release (); 32 33 34 35 36 } Listagem 4.3: Sobel.cpp: método ApplySobel(). 61 1 2 3 4 4.4. Implementação de Shaders em GLSL GLuint Sobel :: createTextureImage ( IplImage * img , QGLContext *glContext , GLuint texImage ) { GLuint texImg = 0; QImage mImage ; 5 if( img -> nChannels == 3) mImage = QImage (( const unsigned char *)(img -> imageData ), img ->width , img ->height ,img -> widthStep / sizeof (uchar),QImage :: Format_RGB888 ); 6 7 8 9 else if( img -> nChannels == 1) mImage = QImage (( const unsigned char *)(img -> imageData ), img ->width , img ->height ,img -> widthStep / sizeof (uchar), QImage :: Format_Indexed8 ); 10 11 12 13 else 14 return 0; 15 16 mImage = QGLWidget :: convertToGLFormat ( mImage ); 17 18 if( texImage > 0) glContext -> deleteTexture ( texImage ); 19 20 21 texImg = glContext -> bindTexture ( mImage ); glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ); glTexParameteri ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ); 22 23 24 25 return texImg ; 26 27 } Listagem 4.4: Sobel.cpp: método CreateTextureImage(). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 GLuint Sobel :: CreateTexturaDepth () { float * vert = new float [640*480]; int k = 0, p = 0; for ( int i = 479; i >-1; i--) { for ( unsigned int j = 0; j < 640; j++) { k = i*640+j; float value = cloud -> points .at(k).z; if( isnan(value) ) value = 0.0; vert[p*640+ j] = value; } p++; } if( _textura > 0) { deleteTexture ( _textura ); _textura = 0; } _textura = setTextureDatafDepth (640 , 480, vert); return _textura ; } Capítulo 4. Desenvolvimento do Framework 62 23 24 25 26 27 28 GLuint Sobel :: SetTextureDatafDepth ( GLuint w, GLuint h, float * data) { GLuint texID = 0; glGenTextures (1, &texID); glBindTexture ( GL_TEXTURE_2D , texID); 29 glTexImage2D ( GL_TEXTURE_2D , 0,GL_RGBA16F , w, h, 0, GL_DEPTH_COMPONENT , GL_FLOAT , data); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_NEAREST ); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_NEAREST ); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , GL_CLAMP_TO_EDGE ); glTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , GL_CLAMP_TO_EDGE ); 30 31 32 33 34 35 return texID; 36 37 } Listagem 4.5: Sobel.cpp: método CreateTextureDepth e SetTextureDatafDepth(). O Vertex Shader para o Sobel (Listagem 4.6) recebe os dados da CPU e para cada vértice faz o cálculo da seu gradiente na função getSobel (Linha 22), que chamaremos de valor sobel, como foi explicado na Seção 2.2. A cor para cada vértice é obtida da textura da imagem chamada texDepthMap (Linha 38) e a profundidade é obtida da textura de profundidade chamada texDepth, que logo é armazenada na propriedade a do vértice (Linha 39). O Fragment Shader (Listagem 4.7) recebe a cor correspondente para cada pixel além do valor sobel, o qual é avaliado para encontrar a borda da profundidade; os pixels com valor sobel maiores ao valor de limiarização são bordas e são invalidados (Linha 12). Quando o método Fill_In é aplicado, ele executa todos os sub-métodos correspondentes ao processo de preenchimento. A Listagem 4.8 mostra a entrada e saída de texturas na função ApplyFillIn(), conforme a interação dos shaders exibidos na Figura 4.2-A. A Listagem 4.9 mostra o shader para o método Downsampling, a Listagem 4.10 mostra o shader para o método Upsampling. Por último, a Listagem 4.11 mostra o shader para o método CrossBilateralFilter. 63 1 2 3 4 5 6 4.4. Implementação de Shaders em GLSL in vec4 in vec2 uniform uniform uniform uniform vPosition ; vTexCoord ; mat4 modelViewMatrix ; mat4 projectionMatrix ; sampler2D texDepthMap ; sampler2D texDepth ; 7 8 9 out float sobel; out vec4 color; 10 11 12 13 14 15 16 17 18 19 20 // returns the coordinates of the current vertex vec3 getVertex ( sampler2D coordinates , vec2 position ) { // texel coordinate from texture coordinate ivec2 texelpos = ivec2( position ); // clamp texel coordinate to valid range texelpos =clamp(texelpos ,ivec2 (0) ,textureSize ( coordinates ,0) -ivec2 (1)); // fetch vertex coordinates , in world coordinates return texelFetch ( coordinates , texelpos , 0).xyz; } 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 float getSobel () { float [9] z; vec3 vertexCoord = getVertex (texDepth , vTexCoord ); // look up depth of the 8 neighbours vec2 tSize = vec2 (1) / textureSize2D (texDepth , 0); z[0] = texture2D (texDepth , ( vTexCoord .xy + vec2 (-1.0, -1.0)) * tSize).x; z[1] = texture2D (texDepth , ( vTexCoord .xy + vec2( 0.0, -1.0)) * tSize).x; z[2] = texture2D (texDepth , ( vTexCoord .xy + vec2( 1.0, -1.0)) * tSize).x; z[3] = texture2D (texDepth , ( vTexCoord .xy + vec2 (-1.0, 0.0)) * tSize).x; z[4] = vertexCoord .x; z[5] = texture2D (texDepth , ( vTexCoord .xy + vec2( 1.0, 0.0)) * tSize).x; z[6] = texture2D (texDepth , ( vTexCoord .xy + vec2 (-1.0, 1.0)) * tSize).x; z[7] = texture2D (texDepth , ( vTexCoord .xy + vec2( 0.0, 1.0)) * tSize).x; z[8] = texture2D (texDepth , ( vTexCoord .xy + vec2( 1.0, 1.0)) * tSize).x; 37 color.rgb= texture2D ( texDepthMap ,( vTexCoord .xy+vec2 (0.0 ,0.0))*tSize).rgb; color.a= vertexCoord .r; // compute gradient magnitude return sqrt(pow(z[0] + 2 * z[1] + z[2] - z[6] - 2 * z[7] - z[8], 2) + pow(z[0] + 2 * z[3] + z[6] - z[2] - 2 * z[5] - z[8], 2)); 38 39 40 41 42 43 } 44 45 46 47 48 49 50 void main () { sobel = getSobel (); vec4 eyePosition = modelViewMatrix * vPosition ; gl_Position = projectionMatrix * eyePosition ; } Listagem 4.6: vSobel.glsl. Capítulo 4. Desenvolvimento do Framework 1 out vec4 fragColor ; 2 3 4 in float sobel; in vec4 color; 5 6 uniform float sobelThreshold ; 7 8 9 10 void main () { fragColor = color; 11 if( sobel >sobelThreshold ) fragColor = vec4 (0.0 ,0.0 ,0.0 ,0.0); 12 13 14 } Listagem 4.7: fSobel.glsl. 1 2 3 Gluint Fill_In :: ApplyFillIn () { DepthSobel = applySobel (); 4 DownDepth1 DownColor1 DownDepth2 DownColor2 5 6 7 8 = = = = applyDownSampled (fboDown1 ,320 ,240 , DepthSobel ); applyDownSampled (fboDown2 ,320 ,240 , Color); applyDownSampled (fboDown3 ,160 ,120 , DownDepth1 ); applyDownSampled (fboDown4 ,160 ,120 , DownColor1 ); 9 CBDepth2 = applyCrossBF (fboCB2 ,160 ,120 , DownColor2 ,DownDepth2 , DownDepth2 ); UpDepth1 = applyUpSampled ( fboUP1 , 320, 240, CBDepth2 , DownDepth1 ); CBDepth1 = applyCrossBF (fboCB1 , 320, 240, DownColor1 ,UpDepth1 , DownDepth1 ); UpDepth = applyUpSampled ( fboUP , 640, 480, CBDepth1 , DepthSobel ); CBDepth = applyFiltroBilateral (fboCB , 640, 480, Color ,UpDepth , DepthSobel ); 10 11 12 13 14 15 return CBDepth ; 16 17 } Listagem 4.8: Fill_In.cpp: método ApplyFillIn(). 64 65 1 2 3 4.4. Implementação de Shaders em GLSL out vec4 fragColor ; uniform float factor ; uniform sampler2D texColorMap ; 4 5 6 7 8 9 10 11 12 13 void main () { // texel to look up vec2 texel = factor * gl_FragCoord .xy ; // read pixel value ( equivalent to texelFetch (input , ivec2(texel), 0)) vec4 pixel= texture2D ( texColorMap ,texel /( vec2( textureSize2D ( texColorMap ,0)))); fragColor = pixel; } Listagem 4.9: fDownSampled.glsl (Adaptado de [36]). 1 2 3 4 out vec4 fragColor ; uniform float factor ; uniform sampler2D texLow ; uniform sampler2D texHigh ; 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void main () { ivec2 loResTexel = ivec2( gl_FragCoord .xy) / int( factor ); ivec2 hiResTexel = ivec2( gl_FragCoord .xy); // read pixel values ( equivalent to texelFetch (... , ivec2(texel), 0)) vec4 loResPixel = texture2D (texLow ,( vec2( loResTexel )+0.5) /( vec2( textureSize2D (texLow ,0)))) ; vec4 hiResPixel = texture2D (texHigh , gl_FragCoord .xy/( vec2( textureSize2D (texHigh ,0)))); // per default , use existing hi -res value fragColor = hiResPixel ; // use new samples only in invalid areas if( hiResPixel .a == 0.0 && loResPixel .a != 0.0) if( hiResTexel .x == factor * loResTexel .x ) if( hiResTexel .y == factor * loResTexel .y ) fragColor = loResPixel ; } Listagem 4.10: fUpSampled.glsl (Adaptado de [36]). 1 out vec4 fragColor ; 2 3 4 5 6 7 uniform uniform uniform uniform uniform sampler2D texColor ; sampler2D texPoints ; sampler2D texRawPoints ; float spatialSigma ; float rangeSigma ; 8 9 10 11 void main () { // assuming every texture has the same size Capítulo 4. Desenvolvimento do Framework vec2 size = vec2( textureSize2D (texColor , 0)); // precompute 1 / sigma ^2 float iSigmaS = 1.0 / (2.0 * spatialSigma * spatialSigma ); float iSigmaR = 1.0 / (2.0 * rangeSigma * rangeSigma ); // truncate Gaussian after 2 sigma int radius = int(ceil (2.0 * spatialSigma )); // colour andceil coordinate at the central pixel vec3 centreColour = texture2D (texColor , gl_FragCoord .xy / size).rgb; vec4 centrePoint = texture2D (texPoints , gl_FragCoord .xy / size); // determine point validity validPoint = texture2D ( texRawPoints , gl_FragCoord .xy / size).a; // default result : unfiltered point fragColor = centrePoint ; // only fill in invalid pixels if( validPoint == 0.0) { // homogeneous accumulator of depth , i.e. (wz , w) vec2 acc1 = vec2 (0.0); vec2 acc2 = vec2 (0.0); 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 for(int dy = -radius ; dy <= radius ; dy ++) { for(int dx = -radius ; dx <= radius ; dx ++) { vec2 coords = ( gl_FragCoord .xy + vec2(dx , dy)) / size; // colour values at the current pixel vec3 currentColour = texture2D (texColor , coords ).rgb; vec4 currentPoint = texture2D (texPoints , coords ); // only accumulate valid points if( currentPoint .a != 0.0) { // compute pixel weight , using exp(a) * exp(b) = exp(a + b); float w = 0.0; // spatial weight vec2 deltaS = vec2(dx , dy); w += iSigmaS * dot(deltaS , deltaS ); // range weight vec3 deltaR = currentColour - centreColour ; w += iSigmaR * dot(deltaR , deltaR ); 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 acc1 = (exp(-w) * vec2( currentPoint .r, 1.0)) + acc1; acc2 = (exp(-w) * vec2( currentPoint .a, 1.0)) + acc2; 52 53 } 54 } 55 } if(acc1.y > 0.0) fragColor = vec4(acc1.xxx / acc1.yyy ,acc2.x / acc2.y); 56 57 58 } 59 60 } Listagem 4.11: fCrossBilateralFilter.glsl (Adaptado de [36]). 66 67 4.4. Implementação de Shaders em GLSL 4.4.2 Shaders do Filtro Espaço-Temporal O método para o processo do Filtro Espaço-Temporal é denominado SpatioTemporalFilter, ele pertence à classe HandleMethod, e aplica os sub-métodos Fill_In e OpticalFlow. A Listagem 4.12 mostra sua aplicação e também os dados de entrada conforme a Figura 4.2-B. A função getSTF() (Linha 17) mostra que a textura texFiltEspTempAnt (textura filtrada no intervalo de tempo anterior) ainda é enviada no segundo intervalo de tempo, depois de ser executada (Linha 39). No primeiro intervalo de tempo é aplicado apenas o filtro espacial. Portanto, o peso para o frame no primeiro intervalo de tempo é 0 (Linha 53). Na função ApplySTF(), também pode ser escolhido a opção de aplicar o método OpticalFlow com o parâmetro enableOpticalFlow (Linha 3). Segundo a Equação 2.13, o Filtro Temporal considera operações no frame do intervalo de tempo anterior. Com a aplicação do fluxo óptico no frame anterior consegue-se obter a ubicação certa do pixel e seu kernel analisado no frame do intervalo de tempo atual. Caso não seja aplicado o método OpticalFlow, o cálculo do Filtro Espaço-Temporal se reduz a uma simples média de dois frames vizinhos. Consequentemente, quando algum objeto da cena está em movimento, a saída da malha mostra artefatos como na Figura 5.11. No Fragment Shader (Listagem 4.13), o parâmetro enableFlow indica habilitar o uso do fluxo óptico. Se ele é falso, o cálculo do pixel central (Linha 52) e de seus vizinhos (Linha 77) no frame, no intervalo de tempo anterior muda, afetando o cálculo da filtragem nesse frame, como mostrado nas Linhas 108 a 138. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Gluint SpatioTemporalFilter :: ApplySTF () { if( enableOpticalFlow ) { if( ! PreviusColor ) { PreviusColor = cvCreateImage ( cvSize (640 , 480) , IPL_DEPTH_8U , 3); memcpy ( PreviusColor ->imageData , Color ->imageData , 640*480*3 ); } OFImage = applyOpticalFow (); memcpy ( PreviusColor ->imageData , Color ->imageData , 640*480*3 ); } STDepth = getSTF (Color , PreviusColo ,OFImage ,CBDepth , PreviusSTDepth ); previousFrameFST ++; Capítulo 4. Desenvolvimento do Framework 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 return STDepth } GLuint SpatioTemporalFilter :: getSTF ( GLuint texColor , GLuint texColorAnt , GLuint texOptFlow , GLuint texFillIn , GLuint texFiltEspTempAnt ) { glBindFramebuffer ( GL_FRAMEBUFFER , fboSTF ); modelViewMatrix . setToIdentity (); projectionMatrix . setToIdentity (); projectionMatrix .ortho (0, 640 ,480 , 0, -1.0f, 1.0f); if ( ! mFiltEspTempProgram ->bind () ) { qWarning () << "Could not bind shader program to context "; return ; } glActiveTexture ( GL_TEXTURE0 ); glBindTexture ( GL_TEXTURE_2D , texColor ); glActiveTexture ( GL_TEXTURE1 ); glBindTexture ( GL_TEXTURE_2D , texColorAnt ); glActiveTexture ( GL_TEXTURE2 ); glBindTexture ( GL_TEXTURE_2D , texOptFlow ); glActiveTexture ( GL_TEXTURE3 ); glBindTexture ( GL_TEXTURE_2D , texFillIn ); if( previousFrameFST > 0 ) { glActiveTexture ( GL_TEXTURE4 ); glBindTexture ( GL_TEXTURE_2D , texFiltEspTempAnt ); } mFiltEspTempProgram -> setUniformValue ( " currentVideo ", 0 ); mFiltEspTempProgram -> setUniformValue ( " previousVideo ", 1 ); mFiltEspTempProgram -> setUniformValue ( " opticalFlow ", 2 ); mFiltEspTempProgram -> setUniformValue ( " currentDistance ", 3 ); mFiltEspTempProgram -> setUniformValue ( " previousDistance ", 4 ); mFiltEspTempProgram -> setUniformValue ( " colourSigma ", 0.1f ); mFiltEspTempProgram -> setUniformValue ( " distanceSigma ", 0.1f ); mFiltEspTempProgram -> setUniformValue ( " flowSigma ", 5.0f ); mFiltEspTempProgram -> setUniformValue ( " spatialSigma ", 5.0f ); if( previousFrameFST > 0) mFiltEspTempProgram -> setUniformValue ( " falloffWeight ", 0.1f ); else mFiltEspTempProgram -> setUniformValue ( " falloffWeight ", 0.0f ); if( enableOpticalFlow ) mFiltEspTempProgram -> setUniformValue (" enableFlow ", TRUE); else mFiltEspTempProgram -> setUniformValue (" enableFlow ", FALSE); vboVertices ->bind (); mFiltEspTempProgram -> setAttributeBuffer (" vPosition ", GL_FLOAT , 0, 4); mFiltEspTempProgram -> enableAttributeArray (" vPosition "); glDrawArrays ( GL_TRIANGLE_FAN , 0, 4 ); vboVertices -> release (); mFiltEspTempProgram -> release (); 64 return STDepth ; 65 66 68 } Listagem 4.12: SpatioTemporalFilter.cpp: funções ApplySTF() e GetSTF(). 69 1 2 3 4 5 6 7 8 9 10 11 12 13 14 4.4. Implementação de Shaders em GLSL out vec4 fragColor ; // input textures uniform sampler2D currentVideo ; uniform sampler2D previousVideo ; uniform sampler2D opticalFlow ; uniform sampler2D currentDistance ; uniform sampler2D previousDistance ; // filter parameters uniform float colourSigma ; uniform float distanceSigma ; uniform float flowSigma ; uniform float spatialSigma ; uniform float falloffWeight ; uniform bool enableFlow ; 15 16 17 18 19 void main () { // assuming every texture has the same size vec2 size = textureSize2D ( currentVideo , 0 ); 20 21 22 23 24 25 // precompute float iSigmaS float iSigmaC float iSigmaD float iSigmaF 1 = = = = / sigma ^2 1.0 / ( 2.0 * spatialSigma * spatialSigma ); 1.0 / ( 2.0 * colourSigma * colourSigma ); 1.0 / ( 2.0 * distanceSigma * distanceSigma ); 0.0; 26 27 28 if( enableFlow ) iSigmaF = 1.0 / ( 2.0 * flowSigma * flowSigma ); 29 30 31 // truncate Gaussian after 2 sigma int radius = int(ceil (2.0 * spatialSigma )); 32 33 34 35 36 // homogeneous accumulator of values , i.e. (w * value , w) vec2 acc = vec2 (0.0); vec2 acc1 = vec2 (0.0); vec2 acc2 = vec2 (0.0); 37 38 39 40 41 42 43 // colour and distance values at the central pixel vec3 centreC = texture2D ( currentVideo , gl_FragCoord .xy / size).rgb; vec4 centreD = texture2D ( currentDistance , gl_FragCoord .xy / size); // motion - compensated centre of kernel in previous frame vec4 centreFlow ; vec2 previousCentreCoords ; 44 45 46 47 48 49 50 51 52 53 if( enableFlow ) { centreFlow = texture2D ( opticalFlow , gl_FragCoord .xy / size); previousCentreCoords = vec2( gl_FragCoord .x- centreFlow .x, gl_FragCoord .y+ centreFlow .y)/size; } else previousCentreCoords = vec2( gl_FragCoord .x, gl_FragCoord .y) / size; Capítulo 4. Desenvolvimento do Framework 54 55 56 57 58 59 60 61 62 63 64 65 66 // loop over rectangular 2D kernel of pixels centred around gl_FragCoord .xy for(float yi = gl_FragCoord .y - radius ;yi <= gl_FragCoord .y + radius ;yi ++) { for(float xi = gl_FragCoord .x - radius ;xi <= gl_FragCoord .x + radius ;xi ++) { // ignore out of range pixels if(xi >= 0 && xi < size.x && yi >= 0 && yi < size.y) { // ---- current frame --------------------------------------// distance of kernel pixel in current frame vec2 currentCoords = vec2(xi , yi) / size; float currentD = texture2D ( currentDistance , currentCoords ).a; float currentDr = texture2D ( currentDistance , currentCoords ).r; 67 68 69 70 71 72 73 74 75 76 77 // optical flow at the kernel pixel vec4 flows = vec4 (0.0 ,0.0 ,0.0 ,0.0); vec2 previousCoords ; if( enableFlow ) { flows = texture2D ( opticalFlow , currentCoords ); previousCoords = vec2(xi - flows.x, yi + flows.y) / size; } else previousCoords = vec2(xi , yi) / size; 78 79 80 81 82 83 84 85 86 87 88 89 90 // ignore pixels with invalid depth if( currentD != 0.0f) { // colour of kernel pixel in current frame vec3 currentC = texture2D ( currentVideo , currentCoords ).rgb; // compute pixel weight , using exp(a) * exp(b) = exp(a + b); float w = 0.0f; // spatial weight vec2 deltaS = vec2(xi , yi) - gl_FragCoord .xy; w += iSigmaS * dot(deltaS , deltaS ); // colour weight vec3 deltaC = currentC - centreC ; 91 if( enableFlow ) w+= clamp (2- length (flows.zw)/flowSigma ,0.0 ,1.0)* iSigmaC *dot(deltaC , deltaC ); else w += iSigmaC * dot(deltaC , deltaC ); 92 93 94 95 96 // depthMap distance weight ( ignore if central pixel invalid ) float deltaD1 = currentDr - centreD .r; if( centreD .a != 0.0f) w += iSigmaD * ( deltaD1 * deltaD1 ); acc1 += exp(-w) * (1.0 - falloffWeight ) * vec2(currentDr , 1.0); // depth distance weight float deltaD2 = currentD - centreD .a; if( centreD .a != 0.0f) w += iSigmaD * ( deltaD2 * deltaD2 ); acc2 += exp(-w) * (1.0 - falloffWeight ) * vec2(currentD , 1); 97 98 99 100 101 102 103 104 105 70 } 71 4.4. Implementação de Shaders em GLSL 106 // ---- previous frame ------------------------------------------if( falloffWeight == 0.0) continue ; if( previousCoords .x < 0 || previousCoords .x >= 1) continue ; if( previousCoords .y < 0 || previousCoords .y >= 1) continue ; // distance of kernel pixel in current frame float previousD = texture2D ( previousDistance , previousCoords ).a; float previousDr = texture2D ( previousDistance , previousCoords ).r; // ignore pixels with invalid depth if( previousD != 0.0f) { // colour of kernel pixel in previous frame vec3 previousC = texture2D ( previousVideo , previousCoords ).rgb; // compute pixel weight , using exp(a) * exp(b) = exp(a + b); float w = 0.0f; // spatial weight // vec2 deltaS = vec2(xi , yi) - gl_FragCoord .xy; vec2 deltaS = size * ( previousCoords - previousCentreCoords ); w += iSigmaS * dot(deltaS , deltaS ); // flow weight if( enableFlow ) w += iSigmaF * dot(flows.xy , flows.xy); // colour weight vec3 deltaC = previousC - centreC ; w += iSigmaC * dot(deltaC , deltaC ); // DepthMap distance weight ( ignore if central pixel invalid ) float deltaD1 = previousDr - centreD .r; if( centreD .a != 0.0f) w += iSigmaD * ( deltaD1 * deltaD1 ); acc1 += exp(-w) * falloffWeight * vec2(previousDr , 1); // distance float deltaD2 = currentD - centreD .a; if( centreD .a != 0.0f) w += iSigmaD * ( deltaD2 * deltaD2 ); acc2 += exp(-w) * falloffWeight * vec2(currentD , 1); } } 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 } 141 } 142 } // write back filtered point if(acc1.y <= 0.0) // just return black if there were no valid data samples in the kernel fragColor = vec4 (0.0 , 0.0, 0.0, 0.0); else fragColor = vec4( acc1.xxx/acc1.yyy , acc2.x/acc2.y ); 143 144 145 146 147 148 149 150 } Listagem 4.13: fSpatioTemporalFilter.glsl (Adaptado de [36]). Por outro lado, todos os métodos dos processos de preenchimento e filtragem são aplicados como render-to-texture. Para que eles sejam mostrados na tela, são enviados à GPU seus respectivos identificadores de textura, como descrito no fluxograma da Figura 4.3. Usando os Capítulo 4. Desenvolvimento do Framework 72 shaders das Listagens 4.14 e 4.15, os resultados de qualquer método podem ser mostrados na tela. 1 2 in vec4 vPosition ; in vec2 vTexCoord ; 3 4 5 uniform mat4 modelViewMatrix ; uniform mat4 projectionMatrix ; 6 7 out vec2 fTexCoord ; 8 9 10 11 12 13 14 void main( void ) { fTexCoord = vTexCoord ; vec4 eyePosition = modelViewMatrix * vPosition ; gl_Position = projectionMatrix * eyePosition ; } Listagem 4.14: vTexturaToScreen.glsl. 1 out vec4 fragColor ; 2 3 4 in vec2 fTexCoord ; uniform sampler2D texColorMap ; 5 6 7 8 9 10 void main( void ) { fragColor = texture2D ( texColorMap , fTexCoord ); fragColor .a = 1.0; } Listagem 4.15: fTexturaToScreen.glsl. 4.4.3 Shaders para Nuvem de Pontos e Wireframe As distâncias originais de profundidade da cena, armazenadas na coordenada z, são mostradas como nuvem de pontos usando o Vertex e Fragment Shader das Listagens 4.16 e 4.17 respectivamente. As propriedades dos vértices como posição e cor são enviadas para GPU tal como descrito no fluxograma da Figura 4.4. Para o caso das profundidades filtradas, a Listagem 4.18 mostra que as posições dos vértices são atualizadas na coordenada z (Linha 16), recuperando e copiando da textura de profundidade filtrada texDeptthFilt o valor a. A cor é recuperada da textura da imagem de cor texColor para cada vértice (Linha 17). 73 1 2 4.4. Implementação de Shaders em GLSL in vec4 vPosition ; in vec4 vColor ; 3 4 5 uniform mat4 modelViewMatrix ; uniform mat4 projectionMatrix ; 6 7 out vec4 color; 8 9 10 11 12 13 14 void main( void ) { color = vColor ; vec4 eyePosition = modelViewMatrix * vPosition ; gl_Position = projectionMatrix * eyePosition ; } Listagem 4.16: vPointCloudCol.glsl. 1 out vec4 fragColor ; 2 3 in vec4 color 4 5 6 7 8 void main( void ) { fragColor = color; } Listagem 4.17: fPointCloudCol.glsl. 1 2 3 in vec4 vPosition ; in vec2 vTexCoord ; out vec4 color; 4 5 6 7 8 uniform uniform uniform uniform mat4 modelViewMatrix ; mat4 projectionMatrix ; sampler2D texDeptthFilt ; sampler2D texColor ; 9 10 11 12 13 14 15 16 17 18 19 void main () { vec4 vertexCoord = vPosition ; vec2 tSize = vec2 (1) / textureSize2D ( texDepthFilt , 0); ivec2 texelpos = ivec2( vTexCoord ); texelpos =clamp(texelpos ,ivec2 (0) ,textureSize ( texDepthFilt ,0) -ivec2 (1)); vertexCoord .z = texelFetch ( texDeptthFilt , texelpos , 0).a; vColor .rgb = texture2D (texColor ,( vTexCoord .xy+vec2 (0.0 ,0.0))*tSize).rgb; vColor .a = 1.0; color = vColor ; 20 vec4 eyePosition = modelViewMatrix * vertexCoord ; gl_Position = projectionMatrix * eyePosition ; 21 22 23 } Listagem 4.18: vPointCloudDepthFilt.glsl. Capítulo 4. Desenvolvimento do Framework 74 Outro método de visualização da cena consiste em mostrar a malha em forma de wireframe. Primeiro se define a estrutura de malha usando a nuvem de pontos adquirida. A Figura 4.7 mostra como se encontram localizados esses pontos e como se define a estrutura da malha. Assim, para realizar a visualização, são usadas faces triangulares formadas através da definição de índices como apresentado na Listagem 4.19, na função createVBOs(), Linhas 14 a 16. Figura 4.7: Estrutura da malha com faces triangulares. O ponto vermelho é o ponto comum entre as faces triangulares vizinhas, os índices i,j indicam a posição desse ponto. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 void Wireframe :: createVBOs () { glGenVertexArrays ( 1, &VAO ); glBindVertexArray ( VAO ); numVertices = 480*640; numIndices = ((640*2) -2) *(480 -1) *3; int k = 0; indices = new unsigned int [ numIndices ]; vertices = new QVector4D [ nVertices ];// vertices used later -not removed for ( unsigned int i = 0; i < 480; i++) for ( unsigned int j = 0; j < 640; j++) if( i <479 && j <639) { indices [k] = i*640+j; indices [k+1] = (i+1) *640+j; indices [k+2] = i *640+( j+1); indices [k+3] = i *640+( j+1); indices [k+4] = (i+1) *640+j; indices [k+5] = (i+1) *640+( j+1); k=k+6; } vboVertices = createObjetoBuffer ( QOpenGLBuffer :: VertexBuffer , QOpenGLBuffer :: DynamicDraw , vertices , numVertices * sizeof ( QVector4D ) ); deleteVector ( vertices ); vboIndices = createObjetoBuffer ( QOpenGLBuffer :: IndexBuffer , QOpenGLBuffer :: StaticDraw , indices , numIndices * sizeof ( unsigned int) ); deleteVector ( indices ); glBindVertexArray (0); } Listagem 4.19: Wireframe.cpp: método CreateVBOs(). 75 4.4. Implementação de Shaders em GLSL A implementação do wireframe é feita em shaders, onde é aproveitado o rendering padrão das faces de uma malha para desenhar as linhas do wireframe. Para isso, no Geometry Shader, é utilizado a primitiva TRIANGLE_STRIP como mostra a Listagem 4.21. Nesse shader é recuperada a posição do vértice de cada primitiva de entrada (a primitiva é especificada na Linha 1). Além disso são calculadas as transformações de vista e projeção. Aqui adicionamos o atributo weight para especificar um peso da borda em cada um desses vértices. Assim, quando esses dados são enviados ao Fragment Shader (Listagem 4.22), os pixels próximos às bordas de uma face poligonal da malha são pintados. 1 in vec4 vPosition ; 2 3 4 5 6 void main () { gl_Position = vPosition ; } Listagem 4.20: vWireframe.glsl. 1 2 layout ( triangles ) in; layout ( triangle_strip , max_vertices = 3) out; 3 4 5 uniform mat4 modelViewMatrix ; uniform mat4 projectionMatrix ; 6 7 out vec3 weight ; 8 9 10 11 12 13 void main () { weight = vec3 (1.0 , 0.0, 0.0); gl_Position = projectionMatrix * modelViewMatrix * gl_in [0]. gl_Position ; EmitVertex (); 14 weight = vec3 (0.0 , 1.0, 0.0); gl_Position = projectionMatrix * modelViewMatrix * gl_in [1]. gl_Position ; EmitVertex (); 15 16 17 18 weight = vec3 (0.0 , 0.0, 1.0); gl_Position = projectionMatrix * modelViewMatrix * gl_in [2]. gl_Position ; EmitVertex (); 19 20 21 22 EndPrimitive (); 23 24 } Listagem 4.21: gWireframe.glsl. Capítulo 4. Desenvolvimento do Framework 1 76 out vec4 fragColor ; 2 3 in vec3 weight ; 4 5 6 float DISCARD_AT = 0.1; float BACKGROUND_AT = 0.05; 7 8 9 10 11 12 13 14 void main () { vec3 color = vec3 (0.9 ,0.6 ,0.3); vec3 BACKGROUND = vec3 (0.0 ,0.0 ,0.0); float mindist = min( weight .r, min( weight .g, weight .b)); if ( mindist > DISCARD_AT ) discard ; vec3 intensity = mindist > BACKGROUND_AT ? BACKGROUND : color; 15 fragColor = vec4(intensity , 1.0); 16 17 } Listagem 4.22: fWireframe.glsl. Para o caso das profundidades filtradas, a entrada dos dados mudam como na Figura 4.5. A Listagem 4.23 mostra que as posições dos vértices são atualizadas na coordenada z da mesma forma como foi descrito para o caso da nuvem de pontos. 1 2 in vec4 vPosition ; in vec2 vTexCoord ; 3 4 uniform sampler2D texColorMap ; 5 6 7 8 9 10 11 12 void main () { vec4 vertexCoord = vPosition ; vec2 texelSize = vec2 (1) / textureSize2D ( texColorMap , 0); ivec2 texelpos = ivec2( vTexCoord + 0.5); texelpos = clamp(texelpos ,ivec2 (0) ,textureSize ( texColorMap ,0) -ivec2 (1)); vertexCoord .z = texelFetch ( texColorMap , texelpos , 0).a; 13 gl_Position = vertexCoord ; 14 15 } Listagem 4.23: vWireframeDepthFilt.glsl. 4.4.4 Phong Shading para Dados do Kinect Um importante método de tonalização de malhas poligonais é o Phong Shading. Esse método usa o modelo de iluminação de Phong [2], o qual é compatível com o modelo do pipeline 77 4.4. Implementação de Shaders em GLSL que ilumina cada polígono localmente durante a rasterização. O Phong Shading requer que o processamento seja aplicado a cada fragmento (por isso o nome “per fragment shading”) e portanto deve ser implementado através do fragment shader. O cálculo do modelo modificado do Phong Shading com o vetor halfway h, utilizado neste trabalho, está na Equação 4.1. Esta considera uma fonte de luz I longe da superfície da cena: (︀ )︀ I = kd Ld max (l · n, 0) + k s L s max (n · h) , 0 + ka La . (4.1) I é composto pela adição de três componentes: quantidade de luz difusa kd Ld max (l · n, 0), se l e n são normalizados, quantidade de luz especular k s L s max ((n · h) , 0) e quantidade de luz ambiente ka La . Ld , Ls , La são as intensidades de luz de cada tipo. kd , ks , ka são coeficientes de absorção que determinam quanto de cada tipo de luz é refletida. é o coeficiente de brilho na componente de luz especular. Na implementação, para aplicar o método, são necessários os dados das propriedades dos materiais e das luzes, bem como as normais. Esses dados devem ser calculados pela aplicação e servirão de entradas para os shaders. n n2 n1 n3 n4 Figura 4.8: Cálculo do vetor normal, n é obtido das normais vizinhas ao vértice (Fonte [2]). Neste trabalho, devido a mudança constante da geometria da cena, o cálculo das normais é feito na GPU, especificamente no Vertex Shader. Primeiro definimos a estrutura de malha da mesma forma como o wireframe representado na Figura 4.7. A Figura 4.8 mostra as faces que estarão envolvidas no cálculo da normal para um determinado ponto, de acordo com a Capítulo 4. Desenvolvimento do Framework 78 Equação 4.2. n= n1 + n2 + n3 + n4 . | n1 + n2 + n3 + n4 | (4.2) Assim, o Vertex Shader, mostrado na Listagem 4.24, recebe e processa atributos do vértice (luz, normal e observador). Tanto a luz como as normais são convertidas à coordenadas de olho. As normais são convertidas através da transformação com a matriz normalMatrix (Linha 90). A luz é convertida através da subtração dela com a posição do olho, Linha 91, já que a luz tem uma localização finita (essa característica é definida com LightPosition.w = 1.0). Por último, o vetor do observador tem direção oposta às coordenadas de olho porque o observador está na origem (Linha 92). A estrutura OpenGL TBO é usada para enviar os dados da nuvem de pontos, da CPU à GPU, como um arranjo no parâmetro uniform samplerBuffer (Linha 10). Logo, os dados são enviados ao geometry shader (Listagem 4.25). Neste trabalho, ainda não são criados novos elementos na geometria e o shader passa apenas os dados de cada triângulo ao Fragment Shader (Listagem 4.26). Aqui, por padrão, as cores dos vértices são interpoladas ao longo do objeto. Da mesma maneira, as normais serão interpolados para cada fragmento. Os dados de luz e materiais são dados enviados pela aplicação. O vetor halfway é calculado através da normalização da soma entre os vetores de luz e observador (Linha 19). Finalmente, as componentes de luz ambiente, difusa e especular são calculadas conforme à Equação 4.1. Para aplicar o Phong Shading são definidas estruturas VBO (Listagem 4.27) na CPU. Após isso, na função DrawGL(), extensão do método paintGL, essas estruturas são enviadas para a GPU fazer o rendering (Listagem 4.28). 79 1 2 3 4 5 6 7 8 9 10 4.4. Implementação de Shaders em GLSL in vec4 vPosition ; in vec2 vTexCoord ; out vec3 fN; out vec3 fE; out vec3 fL; uniform mat3 normalMatrix ; uniform mat4 modelViewMatrix ; uniform mat4 projectionMatrix ; uniform vec4 LightPosition ; uniform samplerBuffer tboSampler ; 11 12 13 14 15 vec3 genNormales () { int i = int( vTexCoord .x); int j = int( vTexCoord .y); 16 17 18 19 20 21 22 23 24 ivec2 indices [7]; indices [0] = ivec2(i-1,j); indices [1] = ivec2(i-1,j+1); indices [2] = ivec2(i,j -1); indices [3] = ivec2(i,j); indices [4] = ivec2(i,j+1); indices [5] = ivec2(i+1,j -1); indices [6] = ivec2(i+1,j); 25 26 27 28 29 30 31 32 ivec3 faces [6]; faces [0] = ivec3 (0 ,2 ,3); faces [1] = ivec3 (1 ,0 ,3); faces [2] = ivec3 (4 ,1 ,3); faces [3] = ivec3 (3 ,2 ,5); faces [4] = ivec3 (6 ,3 ,5); faces [5] = ivec3 (4 ,3 ,6); 33 34 vec3 normal = vec3 (0.0 ,0.0 ,0.0); 35 36 37 38 39 40 for (int { vec3 vec3 vec3 k = 0; k < 6; k++) v1 = vec3 (0.0 ,0.0 ,0.0); v2 = vec3 (0.0 ,0.0 ,0.0); v3 = vec3 (0.0 ,0.0 ,0.0); 41 42 43 i = indices [faces[k].x].x; j = indices [faces[k].x].y; 44 45 46 47 48 49 50 51 if( (i >= 0 && i < 480) && (j >= 0 && j < 640) ) { int coord = int(i*640+j); v1 = texelFetch (tboSampler , coord).xyz; } else continue ; 52 53 i = indices [faces[k].y].x; Capítulo 4. Desenvolvimento do Framework 80 j = indices [faces[k].y].y; 54 55 if( (i >= 0 && i < 480) && (j >= 0 && j < 640) ) { int coord = int(i*640+j); v2 = texelFetch (tboSampler , coord).xyz; } else continue ; 56 57 58 59 60 61 62 63 i = indices [faces[k].z].x; j = indices [faces[k].z].y; 64 65 66 if( (i >= 0 && i < 480) && (j >= 0 && j < 640) ) { int coord = int(i*640+j); v3 = texelFetch (tboSampler , coord).xyz; } else continue ; 67 68 69 70 71 72 73 74 vec3 faceNormal = cross(v2 -v1 ,v3 -v1); 75 76 if (!( isnan( faceNormal .x)|| isnan( faceNormal .y)|| isnan( faceNormal .z))) normal += faceNormal ; 77 78 } normal = normalize ( normal ); 79 80 81 return normal ; 82 83 } 84 85 86 87 88 void main(void) { vec4 eyePosition = modelViewMatrix vec3 vNormal = genNormales (); * vPosition ; 89 fN = normalMatrix * vNormal ; fL = LightPosition .xyz - eyePosition .xyz; fE = -eyePosition .xyz; 90 91 92 93 gl_Position = projectionMatrix * modelViewMatrix * vPosition ; 94 95 } Listagem 4.24: vPhong.glsl. 81 1 2 4.4. Implementação de Shaders em GLSL layout ( triangles ) in; layout ( triangle_strip , max_vertices = 3) out; 3 4 5 6 out vec3 GNormal ; out vec3 GPosition ; out vec3 GLuz; 7 8 9 10 in vec3 fN []; in vec3 fE []; in vec3 fL []; 11 12 13 14 15 16 17 18 void main () { GNormal = fN [0]; GPosition = fE [0]; GLuz = fL [0]; gl_Position = gl_in [0]. gl_Position ; EmitVertex (); 19 GNormal = fN [1]; GPosition = fE [1]; GLuz = fL [1]; gl_Position = gl_in [1]. gl_Position ; EmitVertex (); 20 21 22 23 24 25 GNormal = fN [2]; GPosition = fE [2]; GLuz = fL [2]; gl_Position = gl_in [2]. gl_Position ; EmitVertex (); 26 27 28 29 30 31 EndPrimitive (); 32 33 } Listagem 4.25: gPhong.glsl. 1 2 3 4 5 6 7 8 9 10 11 out vec4 fragColor ; in vec3 GNormal ; in vec3 GPosition ; in vec3 GLuz; struct MaterialInfo { vec4 ambientProduct ; vec4 diffuseProduct ; vec4 specularProduct ; float shininess ; }; uniform MaterialInfo M; // // // // Ambient reflectivity Diffuse reflectivity Specular reflectivity Specular shininess factor 12 13 14 15 16 17 void main () { // I = Kd*Id*(L.N) + Ks*Is*(N.H)^e + Ka*Ia vec3 N = normalize ( GNormal ); vec3 E = normalize ( GPosition ); Capítulo 4. Desenvolvimento do Framework 82 vec3 L = normalize (GLuz); vec3 H = normalize (L + E); float NdotL = dot(N, L); float kd = max(NdotL ,0.0); float ks = (NdotL < 0.0) ? 0.0 :pow(max(dot(N,H) ,0.0) ,M. shininess ); vec4 ambient = M. ambientProduct ; vec4 diffuse = kd * M. diffuseProduct ; vec4 specular = ks * M. specularProduct ; 18 19 20 21 22 23 24 25 26 fragColor = ambient + diffuse + specular ; fragColor .a = 1.0; 27 28 29 } Listagem 4.26: fPhong.glsl. 1 2 3 4 5 6 7 8 9 10 11 12 void Phong :: createVBOs () { glGenVertexArrays ( 1, &VAO ); glBindVertexArray ( VAO ); numVertices = 480*640; numIndices = numVertices ; numFaces = ((640*2) -2) *(480 -1); numIndices = numFaces *3; int k = 0; indices = new unsigned int [ numIndices ]; vertices = new QVector4D [ nVertices ]; texCoords = new QVector2D [ nVertices ]; 13 for ( unsigned int i = 0; i < 480; i++) for ( unsigned int j = 0; j < 640; j++) { if( i <479 && j <639) { indices [k] = i*640+j; indices [k+1] = (i+1) *640+j; indices [k+2] = i *640+( j+1); indices [k+3] = i *640+( j+1); indices [k+4] = (i+1) *640+j; indices [k+5] = (i+1) *640+( j+1); k=k+6; } texCoords [i*640+j] = QVector2D (i,j); } // vertices used later - not removed vboVertices = createObjetoBuffer ( QOpenGLBuffer :: VertexBuffer , QOpenGLBuffer :: DynamicDraw , vertices , numVertices * sizeof ( QVector4D ) ); vboTexCoords = createObjetoBuffer ( QOpenGLBuffer :: VertexBuffer , QOpenGLBuffer :: StaticDraw , texCoords , numVertices * sizeof ( QVector2D ) ); deleteVector ( texCoords ); vboIndices = createObjetoBuffer ( QOpenGLBuffer :: IndexBuffer , QOpenGLBuffer :: StaticDraw , indices , numIndices * sizeof ( unsigned int) ); deleteVector ( indices ); glBindVertexArray (0); 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 } Listagem 4.27: Phong.cpp: método CreateVBOs(). 83 1 2 3 4 5 6 7 8 9 10 4.4. Implementação de Shaders em GLSL void Phong :: DrawGL () { modelViewMatrix . setToIdentity (); modelViewMatrix . lookAt ( camera .eye , camera .at , camera .up); modelViewMatrix .scale(zoom , zoom , 1.0); modelViewMatrix . rotate ( trackBall . getRotation ()); modelViewMatrix . translate (0.0 ,0.0 , - dz); modelViewMatrix . rotate (90 ,0.0 , 0.0 ,1.0); projectionMatrix . setToIdentity (); projectionMatrix .ortho ( -0.50f, 0.50 , -0.50f, 0.50f, 0.0, 200.0f); 11 if ( ! shaderProgram ->bind () ) { qWarning () << "Could not bind shader program to context "; return ; } normalMatrix = modelViewMatrix . normalMatrix (); 12 13 14 15 16 17 shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( shaderProgram -> setUniformValue ( 18 19 20 21 22 23 24 25 26 27 " modelViewMatrix ", " projectionMatrix ", " nWidgetView ", " normalMatrix ", " LightPosition ", "M. ambientProduct ", "M. diffuseProduct ", "M. specularProduct ", "M. shininess ", " tboSampler ", modelViewMatrix ); projectionMatrix ); nWidgetView ); normalMatrix ); LightPosition ); ambientProduct ); diffuseProduct ); specularProduct ); 100.0f ); 0 ); 28 setTextureBufferData (); // passing array vertices as TBO glActiveTexture ( GL_TEXTURE0 ); glBindTexture ( GL_TEXTURE_BUFFER , _texImg ); 29 30 31 32 vboVertices ->bind (); getPontos (); // update vertices with methods map ()/unmap () 33 34 35 shaderProgram -> setAttributeBuffer (" vPosition ", GL_FLOAT , 0, 4); shaderProgram -> enableAttributeArray (" vPosition "); 36 37 38 vboTexCoords ->bind (); shaderProgram -> setAttributeBuffer ( " vTexCoord ", GL_FLOAT , 0, 2 ); shaderProgram -> enableAttributeArray ( " vTexCoord " ); 39 40 41 42 vboIndices ->bind (); glBindVertexArray (VAO); glDrawElements ( GL_TRIANGLES , numFaces *3, GL_UNSIGNED_INT , 0 ); 43 44 45 46 vboTexCoords -> release (); vboVertices -> release (); vboIndices -> release (); shaderProgram -> release (); 47 48 49 50 51 } Listagem 4.28: Phong.cpp: método DrawGL(). Capítulo 4. Desenvolvimento do Framework 4.4.5 84 Cartoon Shading No Cartoon Shading, estilo típico de desenho animado, também é aplicado o “per fragment shading”. Como o Phong Shading, ele inclui parte dos passos do sombreamento descrito na Seção anterior, mas este caso envolve apenas as componentes de luz ambiente e difusa. A ideia é quantizar o termo cosseno da componente difusa da Equação 4.1. Em outras palavras, o valor do produto escalar l · n, que é usado normalmente na componente difusa, está restrito a um número fixo de valores possíveis. Por exemplo, a Tabela 4.1 ilustra o conceito de quatro níveis. Tabela 4.1: Níveis da Componente Difusa. Valor utilizado para cada ângulo entre a fonte de luz l e o vetor normal da superfície n. l·n Entre 1,00 e 0,75 Entre 0,75 e 0,50 Entre 0,50 e 0,25 Entre 0,25 e 0,00 Valor Usado 0,75 0,50 0,25 0,00 Desta forma, ao restringir o valor do termo cosseno, a tonalização apresenta descontinuidades fortes a partir de um nível para outro, simulando os traços de caneta nos desenhos de animação à mão livre. Este efeito, além de reduzir o número de cores que são usadas, inclui contornos pretos ao redor das silhuetas e ao longo de outras bordas de alguma forma existente na cena. A técnica usada para detecção de bordas foi descrita na Seção 2.2. Ela pode ser aplicada na imagem de cor ou na profundidade devido ao fato que estamos trabalhando com dados RGB-Z. Para isso, no Vertex Shader (Listagem 4.29) é calculado a cor de cada vértice (Linha 22), os vetores luz (Linha 30) e normal (Linha 31). Todos eles, juntamente com a coordenada de textura, são passados para o Fragment Shader (Listagem 4.30). Aqui é definido o número de níveis, levels, da componente difusa (Linha 12). Primeiro é calculado normalmente o termo difuso (Linha 44). O valor dessa saída é entre 0 e 1. A linha seguinte visa quantizar este componente, multiplicando o valor anterior por levels e passando o resultado à função 85 4.4. Implementação de Shaders em GLSL floor() (arrendondá-lo a um número para baixo em direção a zero); assim, esse resultado será um número inteiro entre 0 e levels−1. Imediatamente, esse valor é dividido por levels (multiplicando por scaleFactor), dimensioná-lo novamente para ficar entre 0 e 1. Portanto, o resultado é um valor que pode ser um dos níveis de valores possíveis espaçadas entre 0 e 1 como é mostrado na Tabela 4.1 com levels = 4. Este resultado é então multiplicado por M.diffuseProduct, que é o termo de refletividade difusa (Linha 47). Finalmente, os componentes difusos e ambientes são combinados a fim de se obter a cor final para o fragmento, sem deixar de multiplicá-la pelo resultado da função sobel(), que obtém os contornos pretos ao redor das bordas na imagem de cor. 1 2 in vec4 vPosition ; in vec2 vTexCoord ; 3 4 5 6 7 8 9 uniform uniform uniform uniform uniform uniform mat4 modelViewMatrix ; mat4 projectionMatrix ; mat3 normalMatrix ; vec4 LightPosition ; sampler2D texNormalMap ; sampler2D texColorMap ; 10 11 12 13 14 out out out out vec3 vec3 vec4 vec2 fN; fL; color; fTexCoord ; 15 16 17 18 void main () { vec2 tSize = vec2 (1) / textureSize2D ( texColorMap , 0); 19 ivec2 texelpos = ivec2( vTexCoord ); texelpos =clamp(texelpos ,ivec2 (0) ,textureSize ( texColorMap ,0) -ivec2 (1)); color.rgb= texture2D ( texColorMap ,( vTexCoord .xy+vec2 (0.0 ,0.0))*tSize).rgb; color.a = 1.0; 20 21 22 23 24 fTexCoord = vTexCoord ; 25 26 vec3 vNormal = texelFetch ( texNormalMap , texelpos , 0).rgb; 27 28 vec4 eyePosition = modelViewMatrix * vPosition ; fN = normalMatrix * vNormal ; fL = LightPosition .xyz - eyePosition .xyz; gl_Position = projectionMatrix * eyePosition ; 29 30 31 32 33 } Listagem 4.29: vEffectColorMap.glsl. Capítulo 4. Desenvolvimento do Framework 1 2 3 4 5 6 7 8 9 10 11 12 13 86 out vec4 fragColor ; in vec3 fN; in vec3 fL; in vec4 color; in vec2 fTexCoord ; struct MaterialInfo { vec4 ambientProduct ; // Ambient reflectivity vec4 diffuseProduct ; // Diffuse reflectivity }; uniform MaterialInfo M; uniform sampler2D texColorMap ; const float levels = 4.0; const float scaleFactor = 1.0 / levels ; 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 vec3 sobel () { vec3 t1 ,t2 ,t3 ,t4 ,t5 ,t6 ,t7 ,t8 ,t9; // look up image of the 8 neighbours vec2 texelSize = vec2 (1) / textureSize2D ( texColorMap , 0); t1 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 ( -1.0 , -1.0))* texelSize ).rgb; t2 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 (0.0 , -1.0))* texelSize ).rgb; t3 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 (1.0 , -1.0))* texelSize ).rgb; t4 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 ( -1.0 ,0.0))* texelSize ).rgb; t5 = texture2D ( texColorMap , fTexCoord .xy * texelSize ).rgb; t6 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 (1.0 ,0.0))* texelSize ).rgb; t7 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 ( -1.0 ,1.0))* texelSize ).rgb; t8 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 (0.0 ,1.0))* texelSize ).rgb; t9 = texture2D ( texColorMap ,( fTexCoord .xy+vec2 (1.0 ,1.0))* texelSize ).rgb; vec3 xx = t1 + 2.0* t2 + t3 - t7 - 2.0* t8 - t9; vec3 yy = t1 - t3 + 2.0* t4 - 2.0* t6 + t7 - t9; vec3 rr = sqrt(xx * xx + yy * yy); float y = (rr.r + rr.g + rr.b) / 3.0; if (y > 0.25) return vec3 (0.0 , 0.0 , 0.0); else return vec3 (1.0 , 1.0 , 1.0); } 38 39 40 41 42 43 44 45 46 47 48 49 50 51 void main () { vec3 N = normalize (fN); vec3 L = normalize (fL); float NdotL = dot(N, L); float kd = max(NdotL ,0.0); float f = floor(kd* levels ); vec4 diffuse = M. diffuseProduct * f * scaleFactor ; vec4 ambient = color ; fragColor = ambient + diffuse ; fragColor .a = 1.0; fragColor .rgb = fragColor .rgb * sobel (); } Listagem 4.30: fCartoonShading.glsl. 87 4.4. Implementação de Shaders em GLSL 4.4.6 Carregador de Arquivos Nesta seção são apresentadas as considerações a serem tomadas para a utilização do carregador de arquivos shaders a fim de aplicar um efeito online, bem como a execução de um exemplo simples. O efeito pode ser aplicado sobre a imagem de cor ou sobre a malha. Para qualquer um dos dois pode-se usar os mesmos dados enviados da CPU para GPU. Dados enviados da CPU à GPU Como é mostrado na Listagem 4.31, além dos dados habituais de posição e sombreamento, os shaders são habilitados para trabalhar com três texturas. Essas texturas são de tipo uniform sampler2D. Elas têm as seguintes características: • texColorMap, textura da imagem de cor; • texDepthMap, textura do mapa de profundidade. Ela armazena tons de cinza do pixel com valores entre 0 e 1 na propriedade rgb. Na propriedade a são armazenadas as distâncias reais de profundidade filtrada (mesmo para valores superiores a 1); • texNormalMap, textura de normais dos vértices previamente calculados, onde o vetor normal vec3 é armazenado na propriedade rgb da textura. Também, na propriedade a são armazenadas as distâncias reais de profundidade filtrada (a fim de ter disponíveis todos os dados da profundidade em uma única textura); No caso de aplicar um efeito na malha, deve ser considerado no Vertex Shader a atualização da coordenada z da posição do vértice. Na Listagem 4.32, Linha 7, a textura texNormalMap é atualizada na propriedade a. É também apresentado a obtenção do vetor normal na Linha 11. Para acessar aos dados de textura podem ser usadas as funções texelFetch ou texture2D, tomando cuidado com índices fora do limite e a mudança dos tipos de índices de int para float. Capítulo 4. Desenvolvimento do Framework 1 2 88 in vec4 vPosition ; in vec2 vTexCoord ; 3 4 5 6 7 uniform uniform uniform uniform mat4 mat4 mat3 vec4 modelViewMatrix ; projectionMatrix ; normalMatrix ; LightPosition ; 8 9 10 11 12 13 14 15 struct MaterialInfo { vec4 ambientProduct ; vec4 diffuseProduct ; vec4 specularProduct ; float shininess ; }; uniform MaterialInfo M; // // // // Ambient reflectivity Diffuse reflectivity Specular reflectivity Specular shininess factor 16 17 18 19 uniform sampler2D texNormalMap ; uniform sampler2D texColorMap ; uniform sampler2D texDepthMap ; Listagem 4.31: dadosShader.glsl. 1 2 3 void main () { vec4 vertexCoord = vPosition ; 4 ivec2 texelpos = ivec2( vTexCoord ); texelpos =clamp(texelpos , ivec2 (0) ,textureSize ( texNormalMap , 0)-ivec2 (1)); vertexCoord .z = texelFetch ( texNormalMap , texelpos , 0).a; 5 6 7 8 fTexCoord = vTexCoord ; 9 10 vec3 vNormal = texelFetch ( texNormalMap , texelpos , 0).rgb; 11 12 vec4 eyePosition = modelViewMatrix * vertexCoord ; fN = normalMatrix * vNormal ; fL = LightPosition .xyz - eyePosition .xyz; fE = -eyePosition .xyz; gl_Position = projectionMatrix * eyePosition ; 13 14 15 16 17 18 } Listagem 4.32: vEffectDepthMapFilt3D.glsl: método main() para trabalhar com a malha. 89 4.4. Implementação de Shaders em GLSL Execução de um exemplo No painel de Load Shaders (um painel reúne componentes da interface gráfica que executam tarefas para realizar uma função do sistema) são carregados os arquivos Vertex Shader e Fragment Shader do efeito. Para o exemplo foi usado o efeito Background Subtraction [36]. Esses arquivos são anexados, compilados e executados em tempo real. A saída do efeito é mostrada na Figura 4.9. Figura 4.9: Uso do carregador de arquivos de shaders com a execução do efeito Background Subtraction. No caso do Vertex Shader mostrado na Listagem 4.33, foi obtida a cor do vértice usando a textura texColorMap (Linha 16), que, junto com a coordenada de textura (Linha 19), são passadas como parâmetros para o Fragment Shader (Listagem 4.34). Aqui foi utilizada a textura texNormalMap para fazer cálculos sobre os dados filtrados da profundidade da cena. Capítulo 4. Desenvolvimento do Framework 1 2 90 in vec4 vPosition ; in vec2 vTexCoord ; 3 4 5 6 uniform mat4 modelViewMatrix ; uniform mat4 projectionMatrix ; uniform sampler2D texColorMap ; 7 8 9 out vec4 color; out vec2 fTexCoord ; 10 11 12 13 14 15 16 17 void main () { vec2 tSize = vec2 (1) / textureSize2D ( texColorMap , 0); ivec2 texelpos = ivec2( vTexCoord ); texelpos =clamp(texelpos ,ivec2 (0) ,textureSize ( texColorMap ,0) -ivec2 (1)); color.rgb= texture2D ( texColorMap ,( vTexCoord .xy+vec2 (0.0 ,0.0))*tSize).rgb; color.a = 1.0; 18 fTexCoord = vTexCoord ; 19 20 vec4 eyePosition = modelViewMatrix * vPosition ; gl_Position = projectionMatrix * eyePosition ; 21 22 23 } Listagem 4.33: vBackgroundSubtraction.glsl. 91 1 4.4. Implementação de Shaders em GLSL out vec4 fragColor ; 2 3 4 in vec4 color; in vec2 fTexCoord ; 5 6 uniform sampler2D texNormalMap ; 7 8 9 10 11 12 13 14 15 16 vec3 getVertex ( sampler2D coordinates , vec2 position ) { // texel coordinate from texture coordinate ivec2 texelpos = ivec2( position + 0.5); // clamp texel coordinate to valid range texelpos =clamp(texelpos ,ivec2 (0) ,textureSize ( coordinates , 0)-ivec2 (1)); // fetch vertex coordinates , in world coordinates return texelFetch ( coordinates , texelpos , 0).aaa; } 17 18 19 20 21 22 void main (void) { vec3 backgroundColour = vec3 (1, 0, 0); vec4 backgroundPlane = vec4 (0, 0, 1, 1); float planeThreshold = 1.5; 23 vec3 shaded = color.xyz; vec2 pos = fTexCoord .xy - 1; // matte: 0 is background , 1 is foreground float matte = 0.0; matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte += dot(vec4( getVertex ( texNormalMap , pos backgroundPlane ) > planeThreshold ? 0.0 : matte = clamp(matte , 0.0, 1.0); 24 25 26 27 28 29 30 31 32 33 34 35 36 37 + vec2 (-1,-1)), 1.0 / 16.0; + vec2 (-1, 0)), 2.0 / 16.0; + vec2 (-1, 1)), 1.0 / 16.0; + vec2( 0,-1)), 2.0 / 16.0; + vec2( 0, 0)), 4.0 / 16.0; + vec2( 0, 1)), 2.0 / 16.0; + vec2( 1,-1)), 1.0 / 16.0; + vec2( 1, 0)), 2.0 / 16.0; + vec2( 1, 1)), 1.0 / 16.0; 1.0) , 1.0) , 1.0) , 1.0) , 1.0) , 1.0) , 1.0) , 1.0) , 1.0) , 38 fragColor = vec4(mix( backgroundColour , shaded , vec3(matte)), 1.0); 39 40 } Listagem 4.34: fBackgroundSubtraction.glsl. Capítulo 5 Resultados e Discussão Este capítulo está organizado da seguinte forma: na Seção 5.1 são apresentados exemplos da utilização do Framework para o processamento de vídeos RGB-Z e na Seção 5.2 são mostrados e discutidos resultados de desempenho do Framework, limitações e contribuções. 5.1 Utilização do Framework O Framework fornece uma interface gráfica para interagir com o usuário. Essa ferramenta apresenta componentes que possibilitam ao usuário aplicar os diferentes processos do pipeline proposto, bem como recursos e operações de visualização. Na Figura 5.1 é mostrada a interface gráfica. Ela captura e apresenta em cada intervalo de tempo os dados de imagem de cor, mapa de profundidade e nuvem de pontos. Os componentes da interface gráfica são agrupados segundo suas funcionalidades. Esses grupos estão representados na Figura 5.2 e são: A. Painéis Widget que são as janelas de visualização; B. MenuBar com o conjunto de items; C. ToolBar contendo botões para aplicar cada tarefa do processamento; Capítulo 5. Resultados e Discussão 94 Figura 5.1: Interface Gráfica: as janelas inferiores mostram vídeo de cor e vídeo de profundidade. A janela superior mostra a nuvem de pontos, resultante do alinhamento entre imagem de cor e mapa de profundidade. D. Painéis de opções associados aos processos e operações de visualização. Na janela superior da interface gráfica (Figura 5.2-A) são exibidos os resultados de cada processo. Os resultados podem ser apresentados como vídeos (do mesmo modo que os vídeos das janelas inferiores são mostrados) ou, como no exemplo da janela superior da figura, uma malha dinâmica. Além disso, no caso das visualizações da malha, a janela permite interagir com o mouse (trackball virtual e mudança de escala). Um exemplo é mostrado na Figura 5.3. Por outro lado, a barra de menu (Figura 5.2-B) provê funcionalidades para interagir com o sistema, tais como opções de arquivo, diferentes formas de apresentar a malha da cena, métodos do processamento de vídeos RGB-Z; preenchimento de buracos, filtragem espaçotemporal e efeito especial. Essas opções são exibidas na Figura 5.4. 95 5.1. Utilização do Framework Figura 5.2: Grupos de componentes da Interface Gráfica. A: janelas de visualização, B: barra de menu, C: barra de tarefas e D: painéis de opções. (a) (b) Figura 5.3: Interação com o mouse. (a): rotação (trackball), (b): escala. Tais menus estão presentes na barra de tarefas (Figura 5.2-C) para facilitar ao usuário na hora de aplicar as diferentes tarefas, como visualização da malha (Figura 5.5); nuvem de pontos com cor da imagem, nuvem de pontos sem cores, malha como wireframe e superfície da malha Capítulo 5. Resultados e Discussão 96 Figura 5.4: Barra de Menus com todas as opções de arquivo (File), visualização (View), preenchimento (Fill-In), filtragem (Filtering) e efeito especial (Effects). com aplicação de iluminação. Da mesma forma, o usuário tem a possibilidade de aplicar quase todos os métodos do pipeline descrito na Seção 2. Para a fase de Preenchimento de Buracos (Figura 5.6) pode-se aplicar os métodos como detecção de bordas no mapa de profundidade, downsampling recursivo (na imagem de cor e do mapa de profundidade até um nível 4), e finalmente, preenchimento das regiões sem informação no mapa de profundidade. Já para a fase de Filtragem Espaço-Temporal (Figura 5.7), o usuário pode aplicar os métodos de Fluxo Óptico na imagem de cor e o Filtro Espaço-Temporal no mapa de profundidade. Por último, um outro processo que pode ser aplicado é Efeito Especial sobre a imagem de cor (Figura 5.8) ou sobre a malha (Figura 5.9). Dentre os painéis de opções (Figura 5.2-D), a aplicação provê um painel carregador de arquivos shader para aplicar um efeito online (detalhes de implementação na Seção 4.4.6). O Framework suporta anexar, compilar e executar em tempo real Vertex, Fragment e Geometry Shader. Como requisito mínimo para aplicar o efeito são necessários os dois primeiros shaders. A Figura 5.9 mostra o carregador de arquivos e a aplicação do efeito na malha. Por outro lado, 97 5.1. Utilização do Framework Figura 5.5: Opções de visualização da malha. A: nuvem de pontos com cor da imagem (Col PointCloud), B: nuvem de pontos sem cores (PointCloud), C: malha (Wireframe) e D: superfície da malha com aplicação da iluminação (Surface). o painel superior de opções dispõe duas funções, a primeira é relacionada a operações de visualização, como mostrado na Figura 5.10. Quando o CheckBox desta opção é selecionado, habilita todas as operações sobre a malha que utilizam os dados do mapa de profundidade filtrado. As operações que estão incluídas são as visualizações da malha e o efeito especial na malha. A segunda opção é associada ao processo de filtragem espaço-temporal. Quando o CheckBox desta opção é selecionado, habilita aplicar o filtro considerando correspondências de movimento (Fluxo Óptico) entre um frame do intervalo de tempo atual a um frame do intervalo de tempo anterior. O resultado pode ser observado na superfície iluminada da malha (Figura 5.11). Um último painel de opções da interface gráfica é aquele que permite mudar a posição da luz na cena. O painel mostra a posição padrão da luz. Ela pode ser mudada pelo usuário interativamente (Figura 5.12). Esta operação pode ser configurada na superfície Capítulo 5. Resultados e Discussão 98 iluminada e nos efeitos, tanto na imagem como na malha. Figura 5.6: Processos de Preenchimento de Buracos. A,B,C,D: Dois níveis de redução de resolução tanto para o mapa de profundidade como para a imagem de cor (Downsampling), E: detecção de bordas no mapa de profundidade (Sobel) e F: preenchimento de buracos no mapa de profundidade (Fill-In). 99 5.1. Utilização do Framework Figura 5.7: Processos de filtragem espaço-temporal. A: fluxo óptico na imagem de cor (Optical Flow) e B: filtragem espaço-temporal no mapa de profundidade (Filter ET). Figura 5.8: Processo de efeito especial não-fotorrealísticos sobre a imagem de cor (Cartoon Effect). Figura 5.9: Carregador de arquivos de shaders e execução do efeito na malha. Capítulo 5. Resultados e Discussão 100 Figura 5.10: Visualização da nuvem de pontos colorida, A: com mapa de profundidade sem filtrar e B: com mapa de profundidade filtrado. Figura 5.11: Ativação do método fluxo óptico. A: movimento do objeto com fluxo óptico e B: movimento do objeto sem fluxo óptico. Figura 5.12: Posição da luz na cena. A: Posição padrão e B:posição editada pelo usuário. 101 5.2. Resultados e Discussão 5.2 5.2.1 Resultados e Discussão Parâmetros Testados para cada Fase do Processamento O Framework proposto desenvolvido neste trabalho, apresenta uma ferramenta capaz de capturar dados da câmera do Microsoft Kinect, preencher regiões sem informação no mapa de profundidade, filtrar e suavizar o mapa de profundidade. Por último, executa um efeito especial na imagem de cor ou na malha. Na fase de preenchimento é possível remover informação inválida nas bordas com profundidades diferentes, aplicando o Operador de Sobel usando limiarização T = 0,1. Igualmente, é possível o preenchimento de buracos na geometria da cena usando número de níveis de resolução n = 3, fator de sub-resolução g = 2, tamanho do kernel de convolução e parâmetro espacial σ s = 10, parâmetro de cor σc = 0,1. O resultado se mostra na Figura 5.6-F. Na fase de filtragem é obtido um mapa de profundidade com muito menos ruído e suavizado (em relação ao mapa de profundidade de entrada), usando fluxo óptico para manter as correspondências entre frames nas áreas com movimentos rápidos. O fluxo óptico foi implementado em GPU como no trabalho de Eisemann et al. [8], e é aplicado com os parâmetros nível de sub-resolução CoarseLevel = 6, parâmetros do método iterativo para soluções de sistemas lineares de Jacobi como número de iterações Iterations = 40 e valor de transformação Alpha = 8. Para o filtro espaço-temporal, foi considerado apenas os dados de um frame anterior em conjunto com os do frame atual, aqui foram usados parâmetro espacial σ s = 5, além de parâmetro de cor σc = 0,05, parâmetro de distância σd = 0,1, parâmetro de fluxo σc = 5, e parâmetro de equilíbrio entre o filtro espacial e o filtro temporal ϕ = 0,1. O resultado se mostra na Figura 5.7-B. Na fase de efeito especial foi incorporado e testado o efeito não-fotorrealístico Cartoon mostrado na Figura 5.8. Para isso são utilizadas duas componentes de tonalização. A componente ambiente, que toma os valores da imagem por cada pixel, e a componente difusa com valor rgba = (0.14, 0.14, 0.01, 1.0) com 4 níveis de quantização. Capítulo 5. Resultados e Discussão 5.2.2 102 Desempenho O Framework captura os dados do Kinect com uma taxa original de 30 FPS (frames per second) e com uma resolução de 640 × 480 pixels, tanto para a imagem de cor como para o mapa de profundidade, o qual é mantido ao final do processo. O Framework é implementado e executado em um ambiente de teste com características mínimas descritas na Secção 3.4. Especificamente, a placa em que testamos a nossa aplicação possui as seguintes características: • Placa Gráfica NVIDIA GeForce GT 540M • Shader Processors: 96 • Texture Fill Rate (billion/sec): 10.8 • Memory Bandwidth (GB/sec): 28.8 Para efeito de comparação, o estado-da-arte em placas gráficas é: • GeForce GTX TITAN Z • Shader Processors: 5760 • Texture Fill Rate (billion/sec): 338 • Memory Bandwidth (GB/sec): 672 Isto representa 60 vezes mais shader processors (CUDA cores) e uma taxa de preenchimento de texels 31 vezes maior que a GPU utilizada em nossos testes. A execução dos algoritmos de rendering na GPU têm vantagens sobre a CPU. Por exemplo, ao implementar a iluminação da cena com Phong Shading (Figura 5.5-D) temos que, um fator importante para conseguir esta tonalização é a determinação das normais para cada um dos vértices da superfície usando a informação 3D das nuvens de pontos. Esse cálculo pode ser feito apenas uma vez na CPU quando os objetos são estáticos, em taxas superiores a 60 103 5.2. Resultados e Discussão FPS. Contudo, para o caso onde a cena muda constantemente, o processo deve ser feito na GPU porque o processamento na CPU torna-se muito custoso (detalhes de implementação do algoritmo Phong Shading podem ser encontrados na Seção 4.4.4). Assim, o rendering Phong Shading com dados originais na GPU é obtido em uma taxa de 24 FPS. Em contraste, na CPU obtemos uma taxa de 5 FPS. O desempenho do sistema, segundo a nova taxa de exibição após a execução de cada fases de processamento e rendering, são dados em 16 FPS para o preenchimento, 6 FPS para o filtro, 6 FPS para aplicar o efeito e 6 FPS também para o rendering Phong Shading com o mapa de profundidade filtrado. 5.2.3 Limitações A qualidade do rendering do vídeo RGB-Z, que depende dos dados de entrada, são submetidas às limitações da câmera descritas na Seção 3.2.1. O sensor de profundidade da câmera não captura profundidades corretamente depois de 3,5 metros. Por exemplo, uma parede lisa fora desse limite obtém valores de profundidades errados e, na hora de suavizar os dados, ainda mantêm-se algumas flutuações de profundidade quando elas deveriam ter todas o mesmo valor. Profundidades inconsistentes se obtêm igualmente quando o laser é projetado fora de um quarto fechado. Para distâncias menores a 0,5 metros o sensor não captura dados. Esse e outros motivos mencionados na seção previamente referida geram regiões sem informação. Essas áreas podem conter oclusões muito grandes, afetando o processo de preenchimento, discutido na seção seguinte. Por outro lado, como o processo do Framework precisa da combinação da imagem de cor com o mapa de profundidade, o resultado é afetado quando as condições de luz mudam drasticamente (efeitos de sombra salientes ou cenas sem luz). Além dessas limitações, a aplicação precisa de um processador gráfico (com suporte para shaders programáveis) de alto desempenho capaz de oferecer aceleração nas operações gráficas de rendering; considerando características da placa como geração e gama, tipo e barramento Capítulo 5. Resultados e Discussão 104 de memória. Esses requisitos permitem executar a aplicação além de conseguir maior taxa de exibição dos vídeos RGB-Z. 5.2.4 Discussões A qualidade do mapa de profundidade após aplicar todo o processo é significativamente melhorada como pode ser observado na Figura 5.7-B. Contudo, para alguns casos é limitado, criando erros e artefatos no rendering de saída. No caso dos dados apresentarem oclusões grandes, às vezes o processo não consegue preencher todos esses buracos. Isso afeta a filtragem pois o filtro suaviza os dados usando informação do frame anterior e atual, o que resulta em buracos maiores até que a cena desaparece. Nestes casos, na fase de preenchimento pode-se adicionar um nível de escala maior com n = 3 e com um fator de sub-resolução g = 3. A combinação do mapa de profundidade com a imagem de cor no processo de filtragem cria alguns pontos dispersos entre bordas vizinhas de diferentes profundidades na malha (Figura 5.10-B). Isso pode acontecer devido a um peso baixo para a profundidade no filtro por não ter suficiente informação da geometria nessas áreas (ao contrário da informação de cor). No final isso resulta em uma média das profundidades vizinhas. Uma outra questão importante a ser destacada nesta combinação dos dados de cor e profundidade, onde é explorada coincidência das bordas entre eles, é que na malha resultante aparecem algumas bordas produto do copiado de texturas de cor no mapa de profundidade. Isto acontece porque o Cross Bilateral Filter – CBF conta com a suposição de que mudanças drásticas de cor com bordas de profundidade coincidem. Nesse sentido, quando há mudanças drásticas na textura e, ao contrário, na geometria as mudanças são muito suaves, pode-se introduzir este artefato (como mostra-se no peito da pessoa na Figura 5.12-A). Por outro lado, usa-se fluxo óptico para encontrar correspondências entre frames. Embora ele tenta manter geometria coerente em movimentos leves, falha quando esses movimentos são muito rápidos resultando em artefatos “blurring”. Isso devido às profundidades erradas que a 105 5.2. Resultados e Discussão câmera proporciona nestes casos ou, também, por regiões na imagem com pouca informação. O efeito Cartoon Shading aplicado em um vídeo em tempo real tornaria ele em um Video Cartooning segundo o estudo feito pelo Kyprianidis et al. [24], onde eles exploram diferentes estilos artísticos não-fotorrealísticos. Finalmente, quanto ao desempenho de visualização dos processos, cerca de 60% do processo total é gasto pelo método de preenchimentos em multirresolução e cerca de 40% para o método de filtragem espaço-temporal. O alto consumo de tempo na fase de preenchimento é devido ao tamanho do kernel de convolução e parâmetro espacial σ s = 10 para o algoritmo JBU, e também, porque a fase é recursiva conforme aos níveis de resolução. Por outro lado, o método de filtragem é aplicado apenas uma vez e usa um tamanho do kernel σ s = 5. A aplicação do efeito especial e da tonalizacão da malha mantém a mesma taxa de visualização porque os dados que precisam são mantidos na GPU e o aceso a eles é muito rápido. Contudo, o resultado de 6 FPS torna a reprodução do vídeo lento. 5.2.5 Contribuções O trabalho do Richardt et al. [36] inspirou o desenvolvimento do presente projeto. Nele é usado técnicas de preenchimento e filtragem para mapas de profundidade propostas por eles, contudo, apresentamos contribuições em alguns aspectos: • O presente sistema foi desenvolvido na linguagem C++ e pode ser executado nos sistemas operacionais Linux e Windows; • O Framework é baseado em uma arquitetura modular que permite sua expansão. No desenvolvimento são criadas estruturas e algoritmos que permitem a integração de seus componentes; • O presente trabalho pode servir de base para futuros desenvolvimentos nesta área. Proporciona um mapeamento do fluxo dos dados da CPU para GPU bem como a interação dos shaders utilizados; Capítulo 5. Resultados e Discussão 106 • Uma vantagem de nossa ferramenta interativa é permitir aplicar e visualizar, de modo fácil, quase todos os métodos do processamento, além de executá-los em tempo real; • A ferramenta provê um painel carregador de arquivos shaders. Com ele o Framework suporta anexar, compilar e executar um efeito online, sendo isso uma outra vantagem que oferece nossa aplicação. Capítulo 6 Conclusões e Trabalhos Futuros Neste capítulo são apresentadas as conclusões do trabalho feito nesta dissertação, e tratamos dos possíveis trabalhos futuros. 6.1 Conclusões O presente trabalho, motivado pela exploração de dados de cor e da geometria da cena, bem como, de processá-los em conjunto para conseguir estilizar vídeos RGB-Z em tempo real, projeta e desenvolve um framework para dar suporte a esses requisitos. Esses dados da cena são fornecidos pelos sensores do Microsoft Kinect. Para conseguir isso, foi realizado um estudo que envolve métodos para o processamento de vídeos RGB-Z seguindo o pipeline proposto. Esses métodos exigiram a aplicação de conceitos tais como detecção de bordas, Filtro Bilateral e suas variações JBU, CBF e DBF, downsampling, upsampling, fluxo óptico, técnicas de iluminação e sombreamento, entre outros. Da mesma forma, foram empregadas técnicas de programação que fornecem suporte para métodos de rendering na GPU usando GLSL. Além da manipulação das bibliotecas PCL, OpenGL, e também do framework Qt para o desenvolvimento do sistema. O framework desenvolvido provê uma ferramenta simples e muito intuitiva para executar Capítulo 6. Conclusões e Trabalhos Futuros 108 os processos. Esse framework está baseado em uma arquitetura que tem o objetivo de alcançar a qualidade de adaptação, porque conta com componentes que particionam todo o processo em tarefas específicas, todas elas encapsuladas em classes que promovem o reúso e alteração. No final do processamento a geometria resultante é aceitável. A qualidade do mapa de profundidade filtrado é importante porque afeta a projeção da imagem de cor sobre ele. Existem algoritmos para preencher e suavizar o mapa de profundidade com resultados mais confiáveis, mas em contraste mais lentos, portanto a necessidade de execução em tempo real do sistema impede o uso desses métodos. Finalmente, a aplicação aumenta o valor da sua usabilidade em relação à interação com o usuário e o teste de novos efeitos, pois fornece suporte para anexar, compilar e executar shaders de efeitos especiais em tempo real. Até provê a possibilidade de aumentar a geometria da malha porque permite trabalhar com Geometry Shader. Embora esse shader tenha sido usado neste trabalho apenas para adicionar atributos ou passar dados entre shaders, a aplicação está habilitada para usá-lo. 6.2 Trabalhos Futuros Com a finalidade de avaliação do comportamento dos métodos no uso de seus parâmetros, contempla-se algumas implementações na melhora da ferramenta, tais como adicionar um painel de configuração para cada método. Nesse sentido, por exemplo, seria dado maior flexibilidade no método de preenchimento de buracos, pois nos casos de oclusões muito grandes, o usuário poderia avaliar a quantidade de níveis de escala e fator de sub-resolução para obter melhores resultados nessa fase, bem como evitar resultados não desejáveis. Uma outra implementação em relação à ferramenta é desenvolver um componente que armazene e grave os resultados para sua reprodução posterior. Uma proposta para melhorar a malha inicial que tem áreas sem informação, é usar Geometry Shader para aumentar a geometria, criando novos vértices perto destas regiões e também 109 6.2. Trabalhos Futuros eliminando geometria inválida em uma vizinhança determinada. Além disto, no trabalho, o uso de Geometry Shader permite à aplicação o rendering da malha como wireframe. Uma sugestão para melhorar a estilização do efeito cartoon é suavizar as bordas da imagem aplicando uma técnica de Antialising assim como adicionar algoritmos de cálculo de sombras. Finalmente, em relação ao desenvolvimento de futuros efeitos especiais usando o mapa de profundidade preenchido e filtros. Um deles é a segmentação. Com ela pode-se reconhecer objetos da cena e ainda mais reconhecer silhuetas humanas. Também pode ser aplicado o efeito mosaico 6.1 em um objeto determinado da cena como apresenta o trabalho de Goto [14]. Figura 6.1: Efeitos do projeto Kinect Fun House Mirror (Fonte [14]). O efeito faz reconhecimento das silhuetas humanas e segmentação de alguma delas. A silhueta segmentada pode aparecer com o efeito mosaico na cena. Referências Bibliográficas [1] A. Adams, N. Gelfand, J. Dolson, and M. Levoy. Gaussian kd-trees for fast highdimensional filtering. ACM Trans. Graph., 28(3):1–12, 2009. 2.2 [2] E. Angel and D. Shreiner. Interactive Computer Graphics: A Top-Down Approach With Shader-Based Opengl. Pearson international edition. ADDISON WESLEY Publishing Company Incorporated, 2012. 2.4, 3.1, 4.4.4, 4.8 [3] A. Beane. 3D Animation Essentials. ITPro collection. Wiley, 2012. 3.1 [4] E. P. Bennett, J. L. Mason, and L. McMillan. Multispectral bilateral video fusion. IEEE Transactions on Image Processing, 16(5):1185–1194, 2007. 2.3 [5] M. J. Dahan, N. Chen, A. Shamir, and D. Cohen-Or. Combining color and depth for enhanced image segmentation and retargeting. The Visual Computer, pages 1181–1193, 2012. 2.4, 2.9 [6] F. Durand and J. Dorsey. Fast bilateral filtering for the display of high-dynamic-range images. ACM Trans. Graph., 21(3):257–266, July 2002. 2.3 [7] E. Eisemann and F. Durand. Flash photography enhancement via intrinsic relighting. ACM Trans. Graph., 23(3):673–678, Aug. 2004. 2.2, 2.2 [8] M. Eisemann, B. de Decker, M. A. Magnor, P. Bekaert, E. de Aguiar, N. Ahmed, C. The- 111 Referências Bibliográficas obalt, and A. Sellent. Floating textures. Comput. Graph. Forum, 27(2):409–418, 2008. 2.3, 4.4, 5.2.1 [9] D. J. Fleet and Y. Weiss. Optical flow estimation, 2005. 1.3 [10] F. Garcia, B. Mirbach, B. E. Ottersten, F. Grandidier, and A. Cuesta. Pixel weighted average strategy for depth sensor data fusion. In Proceedings of the International Conference on Image Processing, ICIP 2010, September 26-29, Hong Kong, China, pages 2805–2808. IEEE, 2010. 1.3 [11] J. P. Gois. Mínimos-Quadrados e Aproximação de Superfície de Pontos: Novas Perspectivas e Aplicações. PhD thesis, Instituto de Ciências Matemáticas e de Computação – Universidade de São Paulo, 2008. 1 [12] J. P. Gois and H. C. Batagelo. Interactive graphics applications with opengl shading language and qt. In L. Y. M. Walter, editor, SIBGRAPI 2012 (XXV Conference on Graphics, Patterns and Images Tutorials (SIBGRAPI-T)), Ouro Preto, MG, Brazil, august 2012. 3.2.2, 3.3, 4.1 [13] J. P. Gois and G. C. Buscaglia. Resampling strategies for deforming mls surfaces. Computer Graphics Forum, 29(6):1969–1980, 2010. 1 [14] B. Gotow. Kinect fun house mirror. http://blog.foundry376.com/category/portfolio/compu tational-art/, 2012. [Acessado 07-Agosto-2013]. 6.2, 6.1 [15] B. Hayes. Computational Photography. Sigma Xi, The Scientific Research Society, 2008. 1.1 [16] F. Heredia and R. Favier. Using kinfu large scale to generate a textu- red mesh. http://pointclouds.org/documentation/tutorials/using_kinfu_large_scale.php, 2012. [Acessado 07-Agosto-2013]. 1.3 Referências Bibliográficas 112 [17] R. Herzog, E. Eisemann, K. Myszkowski, and H.-P. Seidel. Spatio-temporal upsampling on the gpu. In Proceedings of the 2010 ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games, I3D ’10, pages 91–98, New York, NY, USA, 2010. ACM. 2.3 [18] S. Izadi, D. Kim, O. Hilliges, D. Molyneaux, R. Newcombe, P. Kohli, J. Shotton, S. Hodges, D. Freeman, A. Davison, and A. Fitzgibbon. Kinectfusion: real-time 3d reconstruction and interaction using a moving depth camera. In Proceedings of the 24th annual ACM symposium on User interface software and technology, UIST ’11, pages 559–568, New York, NY, USA, 2011. ACM. 1, 1.3 [19] H. Kim, R. Vuduc, S. Baghsorkhi, and J. Choi. Performance Analysis and Tuning for General Purpose Graphics Processing Units (GPGPU). Synthesis Lectures on Computer Architecture. Morgan & Claypool Publishers, 2012. 1.3 [20] M. Knecht, C. Traxler, O. Mattausch, and M. Wimmer. Reciprocal shading for mixed reality. Computers and Graphics, 2012. 1, 1.3, 1.3 [21] A. Kolb, E. Barth, R. Koch, and R. Larsen. Time-of-flight cameras in computer graphics, 2009. 2.2 [22] K. Konolige and P. Mihelich. Kinect calibration: Technical ros wiki, http://www.ros.org/wiki/kinect_calibration/technical/, 2010. 1.3 [23] J. Kopf, M. F. Cohen, D. Lischinski, and M. Uyttendaele. Joint bilateral upsampling. In ACM SIGGRAPH 2007 papers, SIGGRAPH ’07, New York, NY, USA, 2007. ACM. 1.3, 2.2 [24] J. E. Kyprianidis, J. Collomosse, T. Wang, and T. Isenberg. State of the "art: A taxonomy of artistic stylization techniques for images and video. IEEE Transactions on Visualization and Computer Graphics, 19(5):866–885, May 2013. 2.4, 5.2.4 113 Referências Bibliográficas [25] M. Levoy, K. Pulli, B. Curless, S. Rusinkiewicz, D. Koller, L. Pereira, M. Ginzton, S. Anderson, J. Davis, J. Ginsberg, J. Shade, and D. Fulk. The digital michelangelo project: 3d scanning of large statues. In Proceedings of the 27th annual conference on Computer graphics and interactive techniques, SIGGRAPH ’00, pages 131–144, New York, NY, USA, 2000. ACM Press/Addison-Wesley Publishing Co. 1 [26] H. Liu, M. Philipose, and M.-T. Sun. Automatic objects segmentation with rgb-d cameras. Journal of Visual Communication and Image Representation, 2013. 2.8 [27] J. Lopez-Moreno, J. Jimenez, S. Hadap, K. Anjyo, E. Reinhard, and D. Gutierrez. Nonphotorealistic, depth-based image editing. Computers and Graphics, 35(1):99 – 111, 2011. Extended Papers from Non-Photorealistic Animation and Rendering (NPAR) 2010. 2.7 [28] J. Lu, P. V. Sander, and A. Finkelstein. Interactive painterly stylization of images, videos and 3d animations. In Proceedings of the 2010 ACM SIGGRAPH symposium on Interactive 3D Graphics and Games, pages 127–134, New York, USA, 2010. ACM. 2.11 [29] D. Lucio, L. Cruz, and L. Velho. Rgbd camera effects. In SIBGRAPI - Workshop on Interactive Visualization, Rio de Janeiro, Brasil, 2012. 1.3, 1.3 [30] I. Macêdo, J. P. Gois, and L. Velho. Hermite radial basis functions implicits. Computer Graphics Forum, 30(1):27–42, 2011. 1 [31] D. Min, J. Lu, and M. N. Do. Depth video enhancement based on weighted mode filtering. Trans. Img. Proc., 21(3):1176–1190, Mar. 2012. 1.3 [32] R. A. Newcombe, S. Izadi, O. Hilliges, D. Molyneaux, D. Kim, A. J. Davison, P. Kohli, J. Shotton, S. Hodges, and A. Fitzgibbon. Kinectfusion: Real-time dense surface mapping and tracking. In Proceedings of the 2011 10th IEEE International Symposium on Mixed and Augmented Reality, ISMAR ’11, pages 127–136, Washington, DC, USA, 2011. IEEE Computer Society. 1, 1.3 Referências Bibliográficas 114 [33] M. Nixon and A. Aguado. Feature Extraction & Image Processing. Feature Extraction and Image Processing Series. Elsevier Science, 2008. 2.2 [34] L. Northam, P. Asente, and C. S. Kaplan. Consistent stylization and painterly rendering of stereoscopic 3d images. In Proceedings of the Symposium on Non-Photorealistic Animation and Rendering, pages 47–56, Aire-la-Ville, Switzerland, 2012. Eurographics Association. 2.12 [35] G. Petschnigg, R. Szeliski, M. Agrawala, M. Cohen, H. Hoppe, and K. Toyama. Digital photography with flash and no-flash image pairs. ACM Trans. Graph., 23(3):664–672, Aug. 2004. 2.2, 2.2 [36] C. Richardt, C. Stoll, N. A. Dodgson, H.-P. Seidel, and C. Theobalt. Coherent Spatio Temporal Filtering, Upsampling and Rendering of RGBZ Videos. Computer Graphics Forum (Proceedings of Eurographics), 2012. 1, 1.1, 1, 1.1, 1.2, 1.3, 1.3, 1.2, 1.3, 2, 2.2, 2.5, 2.3, 2.6, 2.10, 4.4, 14, 23, 69, 152, 4.4.6, 5.2.5 [37] W. Schroeder, K. Martin, and B. Lorensen. Visualization Toolkit: An Object-Oriented Approach to 3D Graphics, 4th Edition. Kitware, 4th edition, Dec. 2006. 3.2.2 [38] J. Smisek and T. Pajdla. 3D Camera Calibration. PhD thesis, Czech Technical University in Prague, 2011. 2.1, 3.4, 3.2.1 [39] C. Tomasi and R. Manduchi. Bilateral filtering for gray and color images. In Proceedings of the Sixth International Conference on Computer Vision, ICCV ’98, pages 839–, Washington, DC, USA, 1998. IEEE Computer Society. 2.2 [40] K. Vijayanagar, M. Loghman, and J. Kim. Real-time refinement of kinect depth maps using multi-resolution anisotropic diffusion. Mobile Networks and Applications, pages 1–12, 2013. 1.3 115 Referências Bibliográficas [41] D. Wolff. OpenGL 4.0 Shading Language Cookbook. Packt Publishing, 2011. 1.1, 2.4, 3.1, 4.1
Documentos relacionados
Métodos para Rendering de Superfície
Métodos de Rendering de Superfície Maioria das APIs grácas reduz o processamento usando algoritmos de scan-line As intensidades são calculadas nos vértices e interpoladas nas posições restantes do...
Leia mais