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 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