• Nenhum resultado encontrado

Estudo e implementação de um algoritmo de processamento de imagens com técnicas GPGPUa

N/A
N/A
Protected

Academic year: 2021

Share "Estudo e implementação de um algoritmo de processamento de imagens com técnicas GPGPUa"

Copied!
54
0
0

Texto

(1)

Estudo e implementac¸˜ao de algoritmos de

processamento de imagens com t´ecnicas GPGPU

Florian´opolis – SC Dezembro / 2008

(2)

Edison Gustavo Muenz

Estudo e implementac¸˜ao de algoritmos de

processamento de imagens com t´ecnicas GPGPU

Orientador:

Dr. rer.nat. Aldo von Wangenheim

BACHARELADO EM CIENCIAS DAˆ COMPUTAC¸ ˜AO

INE

CENTROTECNOLOGICO´

UNIVERSIDADEFEDERAL DE SANTA CATARINA

Florian´opolis – SC Dezembro / 2008

(3)

Prof. Dr. rer.nat. Aldo von Wangenheim

Departamento de Inform´atica e Estat´ıstica - INE - UFSC Orientador

Leandro Coser

Departamento de Inform´atica e Estat´ıstica - INE - UFSC Banca orientadora

Antonio Carlos Sobieranski

Departamento de Inform´atica e Estat´ıstica - INE - UFSC Banca orientadora

(4)

Agradecimentos

Agradec¸o ao grupo Cyclops, por seu suporte, estrutura e oportunidade de poder trabalhar com este tema. Especialmente aos membros de minha banca: Leandro Coser e Antˆonio Sobi-eranski, sem os quais eu nunca teria conseguido. Ao laborat´orio LaPiX, onde executei e fiz o TCC. Ao chefe deste laborat´orio: Eros Comunello, pela forc¸a, compreens˜ao e dedicac¸˜ao ao seu trabalho. A todos os outros membros do LaPiX: Adiel Mittmann, Vilson Heck J´unior, Rafael Floriani Bertoldi (Fogo), Guilherme Bertuol, S´ılvio e Al´ecio.

A todas as empresas que trabalhei durante a graduac¸˜ao que me propiciaram um crescimento pessoal e profissional, onde ainda hoje cultivo as amizades criadas nestas mesmas, como a Binara.

Aos meus amigos, sem os quais eu n˜ao conseguiria me divertir, rir e aproveitar a vida. Ao Kaio, cuja convivˆencia somente me acrescentou como ser humano e frequentador de festas. Ao Rafael, que posso chamar de “meio irm˜ao”, por todas discuss˜oes, alegrias e fatos vividos como companheiros.

`

A minha fam´ılia que suportou que eu pudesse madrugar neste per´ıodo elaborando o traba-lho. Tamb´em por compreenderem minhas necessidades neste periodo e em todas as outras fases da minha vida. Devo `a eles tudo que tenho e sou. Ao meu pai, Edison Muenz, cuja sabedo-ria, teimosia e alegria me d˜ao forc¸as para progredir e melhorar quem eu sou. Ao meu irm˜ao, Eduardo Augusto Muenz, que me ajudou e caminha para ser um grande companheiro meu. `A minha irm˜a, Ana Luiza Muenz, onde posso contar para muitas coisas. `A Sandra, onde pude contar muitas vezes com discuss˜oes e id´eias sobre meu futuro.

`

A minha namorada Sabrina que se mostrou companheira por boa parte do curso e com a qual aprendi muito sobre a vida, meu rumo profissional e o amor.

Agradec¸o a Deus, por todas as suas formas de representac¸˜ao que ele possui nos dando forc¸a e acreditar que podemos ser bons e melhores.

Aos colegas de curso que se destacaram na minha jornada pela UFSC, sendo companheiros de sala ou n˜ao, mas que mantiveram-se amigos e hoje s˜ao bons companheiros de vida. Ao Tiago Coelho, vulgo “Coelho”, onde tive muitas conversas, trocas de id´eia e festas.

(5)
(6)

Resumo

Algoritmos de processamento de imagens muitas vezes caracterizam-se por tomarem uma consider´avel quantidade de tempo para serem executados. Esta caracter´ıstica faz com que estes algoritmos necessitem de otimizac¸˜ao. No entanto, algumas horas n˜ao ´e mais possivel melhorar o tempo de resposta dos mesmos, devido `a limitac¸˜ao da CPU, sendo necess´ario buscar tecnologias alternativas para executar estes algoritmos.

As alternativas tomadas at´e ent˜ao consistiam em utilizar clusters de alto desempenho, no entanto, o alto custo destes inviabliza a utilizac¸˜ao desta categoria de algoritmos em m´aquinas dispon´ıveis comercialmente, como os PCs. Com a evoluc¸˜ao da computac¸˜ao gr´afica e a demanda por placas gr´aficas mais poderosas, ocorreu uma evoluc¸˜ao destas mesmas, onde caracter´ısticas ´uteis apareceram, viabilizando o seu uso para prop´ositos al´em da pura s´ıntese de gr´aficos, mas tamb´em para prop´ositos gerais.

Este trabalho de conclus˜ao de curso consiste em implementar um algoritmo de processa-mento digital de imagens utilizando as placas gr´aficas (GPUs) de forma a melhorar o desempe-nho dos mesmos.

Palavras-chave: GPU, GPGPU, Paralelismo, Processamento Digital de Imagens, Alta per-formance

(7)

Digital image processing algorithms are distinguishable among others because they take a long time to be executed. This charecteristic makes them good subjects to be optimized in order to obtain a better execution time. A better performance will be able to accomplish the needs of some applications, such as real time applications.

This problem was handled by using high performance clusters, however, these cluster have a very high price making these algorithms unusable on regular machines, such as the PCs. Because of the evolution of graphics, the demand for more powerful graphic cards has risen and they have become usable for other purposes that are beyond computer graphics.

This thesis is about implementing a digital image processing algorithm and a general pro-blem by using the new graphic cards technology (GPGPU) in a way to improve their perfor-mance if possible.

(8)

Sum´ario

Lista de Figuras Lista de Tabelas Introduc¸˜ao p. 12 1 Objetivos p. 14 1.1 Objetivos gerais . . . p. 14 1.2 Objetivos espec´ıficos . . . p. 14 2 Fundamentac¸˜ao te´orica p. 15

2.1 O que ´e PDI . . . p. 15 2.2 Imagem digital . . . p. 15 2.3 Sistemas de processamento digital de imagens . . . p. 16 2.4 Filtros . . . p. 17 2.4.1 Filtro de difus˜ao anisotr´opico . . . p. 20 2.5 GPU . . . p. 22 2.5.1 Stream processing . . . p. 23 2.5.2 Aplicac¸˜oes GPGPU . . . p. 25 2.6 Framework CUDA . . . p. 26 2.6.1 Motivac¸˜ao . . . p. 26 2.6.2 Arquitetura de execuc¸˜ao . . . p. 27 2.6.3 Uma extens˜ao da linguagem C . . . p. 30 2.6.4 Ponteiros . . . p. 32 2.6.5 Texturas . . . p. 33

(9)

3.1.1 Modelo em GPU . . . p. 41 3.1.2 Custo/tempo de implementac¸˜ao . . . p. 43 3.2 Filtro de difus˜ao anisotr´opico . . . p. 43 3.2.1 Mem´oria . . . p. 43 3.2.2 Estrat´egia de implementac¸˜ao . . . p. 44 3.2.3 Custo/tempo de implementac¸˜ao . . . p. 44

4 Resultados p. 45

4.1 Filtro de difus˜ao anisotr´opica . . . p. 45 4.1.1 Validac¸˜ao dos resultados . . . p. 45 4.1.2 Tempos de resposta . . . p. 47 4.1.3 An´alise com o CUDAProfiler . . . p. 48 4.1.4 Conclus˜ao . . . p. 48 4.2 Comparac¸˜ao N a N . . . p. 50 4.2.1 Tempos de resposta . . . p. 50 4.2.2 Validac¸˜ao dos resultados . . . p. 50 4.2.3 An´alise com o CUDAProfiler . . . p. 50

5 Conclus˜ao p. 52

(10)

Lista de Figuras

2.1 Etapas do PDI. Cada etapa ´e aplicada (se necess´ario) no sentido hor´ario

(re-tirado de ??) . . . p. 16 2.2 Filtro de m´edias (a) imagem original (b) ru´ıdo gaussiano (c) filtro de m´edias

3x3 . . . p. 18 2.3 Filtro de mediana (a) imagem original (b) ru´ıdo aleat´orio (salt and pepper)

(c) filtro de mediana 3x3 . . . p. 19 2.4 Filtro gaussiano (a) imagem original (b) ru´ıdo gaussiano (c) filtro gaussiano . p. 19 2.5 Remoc¸˜ao de ru´ıdo conservativa (a) imagem original (b) ru´ıdo aleat´orio (salt

and pepper) (c) remoc¸˜ao de ru´ıdo conservativa . . . p. 20 2.6 Implementac¸˜ao em 1 dimens˜ao do filtro de difus˜ao . . . p. 21 2.7 Configurac¸˜oes diferentes que o filtro de difus˜ao pode utilizar . . . p. 22 2.8 Execuc¸˜oes do filtro de difus˜ao com λ = 15 . . . p. 23 2.9 Execuc¸˜oes do filtro de difus˜ao com λ = 30 . . . p. 24 2.10 A GPU dedica mais transistores `as ALUs . . . p. 25 2.11 Arquitetura de execuc¸˜ao . . . p. 27 2.12 Modelo de mem´oria . . . p. 28 2.13 Acesso coerente `a mem´oria . . . p. 38 2.14 Exemplos de acesso n˜ao coerente `a mem´oria. Esquerda: Acesso interligado,

Direita: Enderec¸o inicial n˜ao alinhado . . . p. 39 4.1 Comparac¸˜ao do resultado entre CPU e GPU. λ = 15 e 90 iterac¸˜oes . . . p. 46 4.2 Diferenc¸as entre as imagens geradas por CPU e GPU . . . p. 47 4.3 Gr´afico de performance do filtro de difus˜ao comparando CPU e GPU . . . p. 48 4.4 Cuda profiler rodando o filtro de difus˜ao com 90 iterac¸˜oes . . . p. 49 4.5 Gr´afico dos sinais capturados pelo CUDAProfiler. ´E not´avel o alto valor de

(11)
(12)

Lista de Tabelas

2.1 Custo de acesso `a mem´oria . . . p. 36 4.1 M´edia das execuc¸˜oes do filtro de difus˜ao com 100 iterac¸˜oes (tempos em

mi-lisegundos) . . . p. 48 4.2 M´edia das execuc¸˜oes da comparac¸˜ao N a N . . . p. 50

(13)

Introduc¸˜ao

Processamento digital de imagens ´e o processo do qual a partir de uma imagem de entrada aplica-se um processo para cada pixel e geram-se resultados, o que em geral pode ser uma imagem de sa´ıda. Este processo pode gerar imagens de diferentes formatos, aumentar e/ou diminuir a imagem. No entanto, uma caracter´ıstica marcante de alguns destes algoritmos, prin-cipalmente os de segmentac¸˜ao de imagens ´e a alta exigˆencia computacional para execuc¸˜ao. Isto pode impedir o seu uso em algumas aplicac¸˜oes como biometria, reconhecimento de padr˜oes, processamento de v´ıdeo, etc.

Clusteres ´e uma soluc¸˜ao que a comunidade cient´ıfica adotou para resolver tais problemas, aumentando o poder computacional dispon´ıvel, no entanto, estes s˜ao caros e impratic´aveis para muitas aplicac¸˜oes e usos que poderiam ser dados a este campo.

A evoluc¸˜ao das GPUs com o seu poder computacional e programabilidade pode ser uma soluc¸˜ao barata e eficiente para este problema. Elas conseguem obter tempos de resposta mui-tas vezes superior `as CPUs atuais grac¸as `a sua arquitetura, tendo em vista um modelo de computac¸˜ao em paralelo. As GPUs possuem uma grande quantidade de n´ucleos, com um grande n´umero de ALUs e pequenas caches.

Esta arquitetura diferenciada exige das aplicac¸˜oes que utilizam a GPU algumas premissas para executarem os algoritmos de forma eficiente, sendo a principal delas: independˆencia dos dados.

Alguns algoritmos de processamento de imagens est˜ao aptos de forma direta para serem executados na GPU, ou seja, s˜ao em essˆencia paralelos, j´a outros necessitam de uma adaptac¸˜ao para serem “traduzidos” para a GPU.

Esta traduc¸˜ao ´e um grande problema para alguns algoritmos, pois estes devem ser mode-lados de forma diferente para garantir uma independˆencia dos dados, sendo algumas vezes im-pratic´avel ou imposs´ıvel obter um modelo que consiga executar de forma eficiente o algoritmo. Algumas t´ecnicas para evitar algumas “insuficiˆencias” das GPUs est˜ao descritas em (BUCK, 2005).

Como apontado por (LUEBKE; HUMPHREYS, 2007) e (GHULOUM, 2007) uma das carˆencias das GPUs era a ausˆencia de linguagens de alto n´ıvel para a criac¸˜ao de programas para a GPU. Estes estavam restritos `as linguagens de Shaders, que eram basicamente utilizados em computac¸˜ao gr´afica para obter efeitos gr´aficos mais realistas, mas para prop´ositos gerais,

(14)

Introduc¸˜ao 13

eram deficientes por incluirem um overhead das APIs gr´aficas, como OpenGL e Directx. Isto torna o c´odigo sujo e dif´ıcil de depurar e debugar. Tamb´em ´e necess´ario trabalhar diretamente com texturas, o que n˜ao ´e natural para alguns problemas. Al´em disso, h´a tamb´em as limitac¸˜oes dos Shaders.

A NVIDIA ent˜ao disponibilizou o “framework” CUDA para permitir a criac¸˜ao de progra-mas para suas placas de v´ıdeo da s´erie 8000. Esta possui muitas caracter´ısticas ´uteis para a criac¸˜ao de aplicativos GPGPU, sem necessitar do overhead das apis gr´aficas, facilitando a es-crita dos algoritmos.

O framework CUDA ´e tamb´em chamado de ”C para GPUs”. Ele visa facilitar a implementac¸˜ao de algoritmos de prop´osito geral, pois possui estruturas na linguagem que exp˜oem o paralelismo da GPU sem uso das APIs gr´aficas. Por ser uma extens˜ao do C, os desenvolvedores est˜ao mais familiarizados com a linguagem, aumentando a produtividade.

(15)

1

Objetivos

1.1

Objetivos gerais

Estudar e implementar sob o paradigma de programac¸˜ao paralela, que ´e imposto pela programac¸˜ao GPGPU, algoritmos de processamento de imagens, elaborando estrat´egias e re-solvendo os problemas encontrados neste novo ambiente.

1.2

Objetivos espec´ıficos

• Implementar o filtro de difus˜ao anisotr´opico em GPU, utilizando a tecnologia CUDA. • Implementar um algoritmo onde a traduc¸˜ao para a GPU n˜ao seja direta, onde seja

ne-cess´ario repensar todo a estrat´egia de execuc¸˜ao e modelagem do algoritmo.

• Avaliar a performance dos algoritmos implementados em conjunto com a dificuldade de implementar os mesmos

• Efetuar um estudo comparativo de performance e qualidade de resultados nos ambientes especificados

(16)

15

2

Fundamentac¸˜ao te´orica

2.1

O que ´e PDI

PDI atende pela sigla Processamento Digital de Imagens e se caracteriza por aplicar algo-ritmos espec´ıficos a imagens digitalizadas.

O termo PDI surgiu quando a obtenc¸˜ao de imagens digitais a partir de equipamentos especi-ais surgiram. Este processo se revelou mespeci-ais simples que o processamento de imagens anal´ogicas devido `a representac¸˜ao da imagem digital, onde h´a uma quantidade finita de pontos, chamados pixels.

A aplicac¸˜ao de PDI ´e muito difundida em diversas ´areas. As ´areas m´edicas s˜ao um exemplo, onde o processamento pode ajudar a identificar patologias a partir das imagens geradas por tomografias; reconhecimento de sinais e padr˜oes, ajudando a identificar as placas de um carro por exemplo, auxiliando o piloto autom´atico de um carro ao identificar os elementos de uma estrada como placas, lombadas, carros; melhoria de fotos, aumentando/diminuindo contraste, corrigindo bordas, etc.

2.2

Imagem digital

O mundo real visto pelos nossos olhos ´e cont´ınuo, isto ´e, ele possui infinitos “pontos” que representam este mesmo espac¸o. Para capturar este espac¸o cont´ınuo, as cˆamera anal´ogicas utilizavam filmes onde a projec¸˜ao da imagem anal´ogica era impressa neste filme (o chamado “negativo”). Esta ´e uma representac¸˜ao anal´ogica da imagem, no entanto, para podermos aplicar t´ecnicas PDI nesta imagem, ela precisa ser convertida para o formato digital.

O formato digital ´e uma representac¸˜ao discreta da mesma imagem com um n´umero finito de pontos. Pode-se descrever ent˜ao este processo como uma func¸˜ao f (x, y) que mapeia um intervalo da imagem anal´ogica em uma representac¸˜ao discreta deste intervalo. O valor de cada ponto desta func¸˜ao f (x, y) ´e chamado de intensidade ou valor do pixel.

(17)

Segundo (GONZALEZ; WOODS, 2008), PDI est´a dividido em v´arias etapas. Cada uma com uma func¸˜ao diferente, no entanto, nem todos os algoritmos de processamento de imagens passam por todas as etapas.

Estas etapas est˜ao representadas no diagrama apontado pela figura 2.1

Figura 2.1: Etapas do PDI. Cada etapa ´e aplicada (se necess´ario) no sentido hor´ario (retirado de ??)

Aquisic¸˜ao O processo de aquisic¸˜ao consiste em obter uma imagem digital para processar os algoritmos desejados. Este processo em geral ´e a obtenc¸˜ao de uma representac¸˜ao discreta a partir de uma imagem anal´ogica com o equipamento correspondente (cˆameras digitais, raio-x, etc.)

Filtragem Este ´e o processo de correc¸˜ao de pequenas falhas que ocorreram no processo de aquisic¸˜ao. Consiste em deixar a imagem mais facilmente trat´avel para o algoritmo que ser´a aplicado. Filtros s˜ao muito ´uteis para processos de segmentac¸˜ao, por exemplo.

Restaurac¸˜ao Muito parecido com o processo de filtragem, pois tamb´em podem se utilizar filtros nesta etapa. A diferenc¸a est´a na validac¸˜ao dos resultados. Nesta etapa a qualidade ´e ava-liada atrav´es de m´etodos matem´aticos, em geral estat´ısticos. Na etapa de filtragem a qualidade ´e mensurada pelo observador que ir´a dizer se o resultado ´e “bom suficiente” para o algoritmo.

(18)

2.4 Filtros 17

Processamento de cores Esta etapa ´e a melhoria das cores para uma percepc¸˜ao mais aguc¸ada de alguns aspectos da imagem. Esta etapa ganhou muita importˆancia ap´os o apelo visual que as imagens na internet passaram a exigir.

Compress˜ao Compreende em utilizar formatos diferentes para armazenar as imagens. As imagens podem exigir grandes espac¸os de armazenamento se n˜ao forem comprimidas, dado o grande n´umero de pixels que estas possuem. Experimentos em PDI muitas vezes consistem em processar muitas imagens. Isto gera um n´umero muito grande de resultados e arquivos, que em geral s˜ao tamb´em imagens.

Segmentac¸˜ao A segmentac¸˜ao de imagens ´e a etapa que consiste em identificar os diferentes objetos existentes nas imagens de forma similar a um ser humano. Atrav´es disto ´e poss´ıvel extrair regi˜oes de interesse para an´alise automatizada ou visual. Esta ´e, em geral, a etapa mais complexa do processamento de imagens, dado que n˜ao existe uma t´ecnica j´a fundamentada para qualquer tipo de imagem; atualmente ´e necess´ario conhecimento pr´evio sobre o tipo de imagem que ser´a segmentada e o n´ıvel de detalhe desejado. Al´em disto, m´etodos de segmentac¸˜ao cos-tumam ser muito custosos computacionalmente.

Representac¸˜ao Geralmente feita ap´os o resultado de uma segmentac¸˜ao, consiste em obter os resultados segmentados e agrup´a-los da forma que for conveniente `a aplicac¸˜ao, criando uma “nova” representac¸˜ao da imagem que vai al´em de pixels agrupados por cor, mas estruturas de dados propriamente ditas que poder˜ao ter um significado semˆantico atribu´ıdo pela pr´oxima etapa.

Reconhecimento de objetos Identifica e d´a um sentido a cada objeto da imagem. A identificac¸˜ao de uma regi˜ao da imagem e denomin´a-la “carro” ´e um exemplo. Este processo ´e tamb´em um campo de estudo de IA.

2.4

Filtros

Filtros fazem parte da etapa de pr´e-processamento da imagem e s˜ao ´uteis para melhorar a imagem de entrada de forma a reduzir ou eliminar ru´ıdos, alterac¸˜oes na cor, detecc¸˜ao de bordas. Quase todos os algoritmos de segmentac¸˜ao necessitam de uma etapa de pr´e-processamento para obterem bons resultados. Pois os filtros podem suavizar alguns detalhes que n˜ao s˜ao im-portantes. H´a v´arios filtros conhecidos com diferentes utilidades.

(19)

Considerando que a imagem I ´e uma projec¸˜ao de uma cena C (sendo 3D, 2D, etc.). No dom´ınio espacial, cada mudanc¸a na posic¸˜ao em I, acarreta uma mudanc¸a do valor em S.

Cada filtro listado abaixo foi aplicado na situac¸˜ao em que ele ´e mais indicado, ou seja, aplicado ao tipo de ru´ıdo em que ele melhor desempenha.

Filtro de m´edias Este ´e o filtro mais utilizado para reduc¸˜ao de ru´ıdo de uma imagem. Ele calcula a m´edia de acordo com os seus vizinhos alterando o valor do pixel atual. A caracter´ıstica disto ´e eliminar pixels que n˜ao est˜ao relacionados com a sua vizinhanc¸a, logo eliminando ru´ıdos. A quantidade de pixels vizinhos levados em considerac¸˜ao ´e chamado de kernel. Geralmente utilizam-se matrizes 3 × 3, mas estas podem ter qualquer tamanho desejado.

Figura 2.2: Filtro de m´edias (a) imagem original (b) ru´ıdo gaussiano (c) filtro de m´edias 3x3

Filtro de mediana Filtro de reduc¸˜ao de ru´ıdo muito parecido com o de m´edias, mas ao inv´es de substituir pela m´edia dos vizinhos, ele substitui pela mediana. Este comportamento faz com que este filtro seja melhor que o filtro de m´edias porque pixels de ru´ıdo que n˜ao sejam parecidos com os seus vizinhos n˜ao afetem tanto o resultado final. No entanto, ele peca ao remover alguns tipos de ru´ıdo (como o ru´ıdo gaussiano por exemplo).

Desfocagem gaussiana Utilizado para remoc¸˜ao de ru´ıdos e detalhes de imagens. Para fazer o c´alculo do pixel atual ´e utilizada a equac¸˜ao de GAUSS de uma forma discretizada. Ele atribui a cada pixel uma “m´edia ponderada” dando um peso maior aos pixels centrais. Isto faz com que este filtro consiga remover de forma mais eficaz os ru´ıdos e preserva melhor as bordas que um filtro de m´edias.

(20)

2.4 Filtros 19

Figura 2.3: Filtro de mediana (a) imagem original (b) ru´ıdo aleat´orio (salt and pepper) (c) filtro de mediana 3x3

A equac¸˜ao de GAUSS em 2 dimens˜oes ´e a seguinte:

G(x, y) = 1 2πσ2e

− 1

2πσ 2 (2.1)

Figura 2.4: Filtro gaussiano (a) imagem original (b) ru´ıdo gaussiano (c) filtro gaussiano

Remoc¸˜ao de ru´ıdo conservativa T´ecnica de reduc¸˜ao de ru´ıdos utilizando o valor m´aximo e m´ınimo dos pixels vizinhos. Ele obt´em o valor de cada pixel vizinho, verifica os valores m´aximos e m´ınimos e coloca o valor do pixel atual dentro deste limite (se ele n˜ao estiver).

Este filtro se caracteriza por conseguir remover ru´ıdos com alta frequˆencia espacial, ou seja, est˜ao muito distribu´ıdos pela imagem. Ele consegue manter bem os detalhes da imagem.

Filtros de frequˆencia Os filtros de frequˆencia operam no dom´ınio de frequˆencia imagem. Os mais conhecidos s˜ao:

(21)

Figura 2.5: Remoc¸˜ao de ru´ıdo conservativa (a) imagem original (b) ru´ıdo aleat´orio (salt and pepper) (c) remoc¸˜ao de ru´ıdo conservativa

• Passa-alta - atenuas altas frequˆencias, o que resulta em acentuar as bordas

Para aplicar cada filtro a imagem ´e convertida para o dom´ınio da frequˆencia atrav´es da transformac¸˜ao de Fourier e depois convertida de volta para o dom´ınio espacial pela mesma transformac¸˜ao.

2.4.1

Filtro de difus˜ao anisotr´opico

O filtro de difus˜ao anisotr´opico ´e um filtro iterativo para simplificac¸˜ao da imagem (smo-othing) de forma a torn´a-la um pouco mais homogˆenea. ´E um filtro que preserva as bordas ao ser executado, portanto ´e util para a detecc¸˜ao de bordas de uma imagem.

Este filtro tamb´em pode ser usado para a remoc¸˜ao de ru´ıdos em imagens de resonˆancia magn´etica conforme apontado por (SERAMANI ZHOU JIAYIN, 2008).

O filtro de difus˜ao anisotr´opico foi proposto por Perona e Malik em (PERONA; MALIK, 1990).

Interpretac¸˜ao matem´atica do filtro de difus˜ao O processo de difus˜ao ´e definido como:

∂ tI(x,t) = div (c(x,t) 5 I(x,t)) (2.2)

A forc¸a da difus˜ao ´e controlada por c(x,t), a func¸˜ao de difusibilidade. O vetor x representa as coordenadas no espac¸o da imagem. A vari´avel t ´e o parˆametro espac¸o tempo. I(x,t) ´e a imagem.

(22)

2.4 Filtros 21

Para poder controlar a difus˜ao foram propostas duas func¸˜oes de difusibilidade para o termo c(x,t): c1(x,t) = exp − | 5 I(x,t)| κ 2! (2.3) c2(x,t) = 1 1 + |5I(x,t)| κ  , α > 0 (2.4)

Estas equac¸˜oes foram discretizadas por Perona e Malik para:

Ist+∆t = Ist+ ∆t |ηx|

gp∈ηx | 5 Is,pt | 5 It

s,p (2.5)

Caracter´ısticas

O filtro de difus˜ao pode ser controlado atrav´es do parˆametro λ . O valor de λ define a velocidade que a imagem ir´a sofrer difus˜ao.

O gradiente da imagem ´e a variac¸˜ao entre dois pontos espaciais da imagem. Uma carac-ter´ıstica muito importante do filtro de difus˜ao ´e que ele p´ara ao encontrar uma variac¸˜ao de gradiente muito alta. Isto faz com que as bordas sejam preservadas.

Dimens˜oes do filtro de difus˜ao

O filtro de difus˜ao pode ser utilizado em 1D, 2D ou 3D (podendo ser extendido a mais dimens˜oes). A implementac¸˜ao 1D do filtro de difus˜ao pode ser visualizado na imagem 2.6.

Figura 2.6: Implementac¸˜ao em 1 dimens˜ao do filtro de difus˜ao

A implementac¸˜ao para 2 dimens˜oes se extende em utilizar mais pixels adjacentes. H´a 3 variantes desta implementac¸˜ao: conectividade diagonal; conectividade horizontal e vertical; conectividade combinada ou conectividade total. Elas est˜ao ilustrados na figura a seguir:

A implementac¸˜ao adotada adotou a conectividade total, por aumentar a quantidade de operac¸˜oes aritm´eticas e a qualidade dos resultados.

(23)

Figura 2.7: Configurac¸˜oes diferentes que o filtro de difus˜ao pode utilizar ´

E percept´ıvel que a forma dos objetos ´e preservada e que a imagem passou por uma pr´e-segmentac¸˜ao. Os detalhes da imagem relacionados a ru´ıdos (como a textura do bolo) s˜ao sim-plificados. Este resultados podem ser interessantes para algoritmos de segmentac¸˜ao por cresci-mento de regi˜oes como o Mumford&Shah.

Ao aumentarmos a velocidade da difus˜ao (aumentando o lambda) os resultados se tornam mais “desfocados”:

2.5

GPU

A GPU (graphical processing unit) ´e o “processador” das placas gr´aficas. Estas placas s˜ao compostas de diversos componentes, no entanto, assim como nos pcs (onde a CPU ´e o componente “principal”), a GPU ´e o componente principal destas mesmas, onde ocorre o pro-cessamento de todas as instruc¸˜oes enviadas `a placa de v´ıdeo.

O poder de processamento das GPUs atuais ´e imenso, se comparado ao processamento das CPUs modernas. Este poder chamou a atenc¸˜ao dos desenvolvedores e permitiu que os efeitos gr´aficos nunca vistos antes. Tamb´em fez com que olh´assemos para a GPU com olhos n˜ao somente voltados ao processamento de primitivas gr´aficas, mas como um processador de prop´osito geral.

O poder de processamento das GPUs vem do maior n´umero de transistores dedicados `a ALU, ou seja, mais poder de c´alculo. Isto pode ser visto na figura 2.10. No entanto, o menor n´umero de transistores exige que o c´odigo tenha uma estrat´egia e cuidados diferentes dos que s˜ao feitos na CPU.

As GPUs passaram a ter uma maior atenc¸˜ao dos desenvolvedores para prop´osito geral por-que passaram a possuir est´agios program´aveis (atualmente com 2 est´agios). Estes est´agios permitem que sejam inseridos algoritmos que ir˜ao rodar dentro da GPU tratando os dados da forma que for necess´ario, tendo como objetivo efeitos gr´afico e/ou algum outro prop´osito geral. Os 2 est´agios program´aveis da GPU s˜ao tamb´em chamados de shaders, estes s˜ao: vertex shader e pixel shader.

(24)

2.5 GPU 23

(a) Imagem original (b) λ = 15 com 30 iterac¸˜oes

(c) λ = 15 com 60 iterac¸˜oes (d) λ = 15 com 90 iterac¸˜oes

Figura 2.8: Execuc¸˜oes do filtro de difus˜ao com λ = 15

2.5.1

Stream processing

Este ´e o modelo de programac¸˜ao que deve ser seguido quando escrevem-se algoritmos para a GPU. Isto ocorre porque a GPU possui processadores que funcionam desta forma.

Este ´e um modelo de programac¸˜ao que incentiva o paralelismo, pois ele estabelece o modelo de execuc¸˜ao SIMD (nos modelos da GPU atuais, embora existam outros modelos para Stream processing(WIKIPEDIA, 200?)). Este modelo ´e a sigla para Same instruction, multiple data, ditando que o mesmo c´odigo ´e executado sobre diferentes dados.

Este modelo define o termo kernel, que ´e o c´odigo que ser´a executado em cada trecho dos dados. Este kernel executando sobre este setor de dados define um stream.

Um c´odigo executando em CPU sobre um conjunto de dados data ´e o seguinte:

void kernel() { ...

(25)

(a) Imagem original (b) λ = 30 com 30 iterac¸˜oes

(c) λ = 30 com 60 iterac¸˜oes (d) λ = 30 com 90 iterac¸˜oes

Figura 2.9: Execuc¸˜oes do filtro de difus˜ao com λ = 30

int result;

for (int i = 0; i < DATA_SIZE; ++i) { kernel(data[i], result);

}

No caso dessa execuc¸˜ao ser feita no modelo Stream processing este c´odigo seria o seguinte:

void kernel() { ....

}

result = apply_kernel(kernel, data, DATA_SIZE);

(26)

2.5 GPU 25

Figura 2.10: A GPU dedica mais transistores `as ALUs

2.5.2

Aplicac¸˜oes GPGPU

Existem v´arias particularidades ao escrevermos algortimos para a GPU, pois a arquitetura desta ´e diferente da arquitetura da CPU, introduzindo algumas dificuldades e limites. Por isto sempre que estes forem escritos, deve-se ter sempre em mente o funcionamento do pipeline gr´afico, seus limites e particularidades. Algumas destas caracter´ısticas s˜ao:

• Latˆencia do barramento entre CPU e GPU - Existe uma latˆencia para a troca de informac¸˜oes entre a CPU e a GPU que ´e limitado pelo slot AGP ou PCI-Express. Este tempo pode ser cr´ıtico quando h´a muita comunicac¸˜ao entre estes dois componentes. Portanto deve-se ter sempre em mente que ´e recomend´avel enviar instruc¸˜oes e dados suficientes, ou ent˜ao que ocupem um tempo que justifique o processamento ser feito na GPU. Caso contr´ario, pode ocorrer uma situac¸˜ao onde o resultado j´a poderia ter sido calculado pela CPU, mas a instruc¸˜ao ainda est´a trafegando pelo barramento at´e a GPU.

• Dificuldades de programac¸˜ao - Como a GPU possui uma arquitetura altamente especia-lizada, alguns comandos n˜ao s˜ao poss´ıveis de serem executados em alguns est´agios do pipeline, tornando alguns algoritmos de implementac¸˜ao trivial em CPU, dif´ıceis de se implementar em GPU. O algoritmo quicksort ´e um exemplo claro desta limitac¸˜ao, pois a operac¸˜ao scatter (escrita em algum enderec¸o de mem´oria) ´e limitado no vertex shader e desabilitado no pixel shader.

Soluc¸˜oes para estas dificuldades existem, atrav´es da busca de algoritmos alternativos e/ou uma implementac¸˜ao diferenciada, que aproveita melhor o paralelismo e consegue rodar sobre as limitac¸˜oes do hardware da GPU. Algumas t´ecnicas utilizadas para superar estas dificuldades foram descritas em (PHARR; FERNANDO, 2005)

(27)

(atrav´es de Shaders) s˜ao as limitac¸˜oes de gather e scatter.

A operac¸˜ao Gather ´e o ato de obter dados a partir de um enderec¸o de mem´oria aleat´orio. Por exemplo:

int dados = data[10];

Neste caso, estamos obtendo o dado a partir do enderec¸o 10 do conjunto de dados data. Esta operac¸˜ao possui suporte total no Pixel Shader, mas ´e limitada no Vertex Shader.

Scatter

A operac¸˜ao Scatter ´e o ato de escrever dados em um enderec¸o de mem´oria. Por exemplo:

data[10] = 5;

Aqui est´a se escrevendo o valor 5 no conjunto de dados data. Esta operac¸˜ao n˜ao ´e suportada no Pixel Shader e pode ser feita no Vertex Shader.

Isto apresenta limitac¸˜oes claras porque muitos algoritmos necessitam escrever em enderec¸os de mem´oria, tornando algumas vezes imposs´ıvel alguns algoritmos serem escritos pela maneira cl´assica de GPGPU (shaders).

2.6

Framework CUDA

O CUDA ´e um “framework” desenvolvido pela NVIDIA com o objetivo de facilitar a criac¸˜ao de aplicac¸˜oes para a GPU.

Ele pode ser interpretado como uma API com func¸˜oes e formas de escrever c´odigo direta-mente na GPU. Esta API ´e uma extens˜ao do C, onde o compilador da NVIDIA (nvcc) compila o c´odigo relativo ao CUDA e deixa o compilador padr˜ao da m´aquina (gcc, msvc) compilar o c´odigo C. A linguagem est´a descrita em (NVIDIA, 2008).

2.6.1

Motivac¸˜ao

A forma (cl´assica) de se escrever c´odigo para a GPU ´e atrav´es da programac¸˜ao dos shaders, rodando no vertex shader, pixel shader ou geometry shader (a partir do Shader model 4.0).

(28)

2.6 Framework CUDA 27

Este tipo de c´odigo ´e eficiente (no quesito praticidade) caso a aplicac¸˜ao n˜ao tenha o intuito de utilizar c´odigo GPGPU. No entanto, caso o c´odigo tenha um prop´osito geral (n˜ao diretamente relacionado ao uso de uma API 3D), h´a muito esforc¸o desnecess´ario, como:

• Necessidade da utilizac¸˜ao de APIs gr´aficas (como opengl e direct3d) • Dificuldade em traduzir problemas computacionais para a GPU • ausˆencia de scatter/gather

Este esforc¸o diminui com o CUDA, pois o c´odigo n˜ao ´e mais escrito em linguagem de sha-ders, a necessidade de utilizac¸˜ao de APIs gr´aficas desaparece, h´a suporte para scatter/gather em qualquer parte do c´odigo.

2.6.2

Arquitetura de execuc¸˜ao

Figura 2.11: Arquitetura de execuc¸˜ao

Kernels, Grids e Blocos de threads

O CUDA n˜ao foge ao modelo de programac¸˜ao de Streams(??) como h´a na programac¸˜ao de Shaders, pois a GPU ´e uma m´aquina com m´ultiplos processadores em paralelo, e ´e isto que a torna t˜ao eficiente computacionalmente.

(29)

Threads S˜ao a menor unidade de execuc¸˜ao do kernel, onde cada uma executa parte do c´odigo do kernel. Estas est˜ao organizadas em blocos, podendo estas compartilharem informac¸˜oes entre si atrav´es de uma mem´oria de r´apido acesso e sincronizarem sua execuc¸˜ao para coordenar o acesso `a mem´oria.

Blocos Consiste em um agrupamento de threads que ir˜ao executar em um dos multiprocessa-dores dispon´ıveis na GPU. Estes blocos s˜ao indepependentes entre si, ou seja, podem executar em uma ordem aleat´oria e n˜ao previamente conhecida. Um grande n´umero de blocos garante um grande paralelismo (mantendo os multiprocessadores ocupados).

Grids E o conjunto de todos os blocos que est´a executando um kernel. Quando um grid´ termina de executar significa que o kernel terminou sua execuc¸˜ao.

Mem´oria

Figura 2.12: Modelo de mem´oria

A comunicac¸˜ao entre a CPU e a GPU ´e feita atrav´es de m´etodos da api, como cudaMalloc() (aloca mem´oria na GPU) e cudaMemcpy() (transfere dados entre CPU e GPU). Estes m´etodos

(30)

2.6 Framework CUDA 29

s˜ao otimizados pelo compilador para utilizar o chip DMA (Direct Memory Access) aumentando a velocidade de acesso.

Uma thread possui acesso `a mem´oria da GPU atrav´es dos seguintes padr˜oes: • Read-write per-thread registers,

• Read-write per-thread local memory, • Read-write per-block shared memory, • Read-write per-grid global memory, • Read-only per-grid constant memory, • Read-only per-grid texture memory.

Registradores Cada multiprocessador possui uma quantidade definida de registradores. Por-tanto, o n´umero de threads por bloco ´e limitado ao hardware onde o programa est´a sendo exe-cutado.

Mem´oria compartilhada O CUDA disponibiliza uma mem´oria compartilhada de alta ve-locidade de acesso ao programador. Esta mem´oria pode ser utilizada para sincronia das threads entre os blocos, diminuindo o n´umero de loads feitos da mem´oria principal da GPU, aumen-tando a velocidade de execuc¸˜ao do kernel. O tamanho desta mem´oria ´e limitado a 16kb por bloco.

Mem´oria de texturas A mem´oria de texturas possui um cache, de forma que uma leitura da textura resulta em um “miss” apenas se esta n˜ao estiver em cache, sendo ent˜ao lida da mem´oria global. O cache de texturas ´e otimizado para uma localidade 2D espacial, portanto threads de um mesmo bloco que lˆeem dados que est˜ao pr´oximos ir˜ao obter a melhor performance.

Mem´oria de constantes A mem´oria de constantes ´e muito r´apida e possui cache. Uma leitura da mem´oria de constantes resulta em uma leitura da mem´oria global apenas em caso de um “cache miss”. A performance desta mem´oria ´e garantida se as threads de um bloco efetuarem a leitura no mesmo enderec¸o; neste caso, a velocidade da mem´oria de constantes ´e igual a um registrador.

(31)

uma extens˜ao do C, a curva de aprendizado ´e menor devido `as similaridades com o C, que ´e uma linguagem conhecida.

H´a a introduc¸˜ao de alguns conceitos b´asicos:

• Host - Executa o c´odigo compilado e controla os dispositivos (devices). Seria a CPU. • Device - Executa c´odigo escrito especificamente para o dispositivo, a GPU.

• Func¸˜oes e tipos que caracterizam vetores como j´a parte da linguagem (p/ ex.: float2, int4). Este c´odigo ´e suportado tanto no host quanto no device.

Qualificadores para m´etodos device

• Implica que um m´etodo ser´a executado apenas no device. • Chamado apenas pelo pr´oprio device

global Implica no ponto de entrada para um kernel • Executado no device

• Chamado apenas pelo host

host

• Executado no host

• Chamado apenas pelo host ´

E equivalente declarar um m´etodo com este modificador e n˜ao declar´a-lo sem nenhum dos modificadores listados. No entanto, ´e poss´ıvel declarar um m´etodo com ambos os modificadores device e host que dizem que um m´etodo ser´a compilado para ser executado em ambos os ambientes.

(32)

2.6 Framework CUDA 31

Qualificadores para vari´aveis

device Especifica que uma vari´avel ser´a armazenada no device. Os modificadores a seguir definem onde a vari´avel ser´a alocada.

constant Uma vari´avel declarada com este modificador possui as seguintes caracter´ısticas • Reside no espac¸o de mem´oria de constantes

• Tem um tempo de vida igual `a vida da aplicac¸˜ao • ´E acess´ıvel a todas as threads do grid

shared Uma vari´avel declarada com este modificador possui as seguintes caracter´ısticas • Reside no espac¸o de compartilhado de thread de um bloco (mem´oria veloz mas pequena) • Tem um tempo de vida igual `a vida do bloco

• ´E acess´ıvel a todas as threads do bloco

Este tipo de vari´avel ´e suscet´ıvel `a sincronia de threads, portanto, para sincronizar os reads e writes utiliza-se o comando syncthreads(), garantindo que as escritas de outras threads ser˜ao vis´ıveis.

Se nenhum destes modificadores for especificado, a vari´avel ter´a a seguinte caracter´ıstica: • Reside no espac¸o de mem´oria global

• Tem um tempo de vida igual `a vida da aplicac¸˜ao • ´E acess´ıvel a todas as threads do grid

Chamando um kernel

Quando declaramos um m´etodo com o modificador global e queremos cham´a-lo para poder executar um kernel, este deve possuir um tipo de chamada especial, onde ´e especificado a dimens˜ao do grid que ir´a executar aquele kernel.

O formato desses parˆametros segue a forma <<< Dg, Db, Ns >>>, onde:

• Dg ´e do tipo dim3 e especifica a dimens˜ao e o tamanho do grid. Sendo Dg.x * Dg.y igual ao n´umero de blocos.

(33)

• Ns ´e do tipo size t e especifica o n´umero de bytes que ser˜ao alocados dinamicamente al´em dos bytes est´aticos. Esta mem´oria ´e utilizada por arrays declarados com o modificador extern. Este argumento ´e opcional e o seu valor padr˜ao ´e 0.

Por exemplo, se queremos chamar um kernel com 10 blocos, sendo 5 threads por bloco, podemos utilizar o seguinte programa C:

__global__ void kernel() { ... } void main() { kernel<<<10, 5>>>(); } ´

E v´alido lembrar que uma chamada de um kernel a partir do host ´e ass´ıncrono, ou seja, o kernel pode n˜ao terminar sua execuc¸˜ao antes que a pr´oxima instruc¸˜ao do c´odigo do host seja chamado. Para que haja uma sincronizac¸˜ao deste kernel ´e poss´ıvel chamar o m´etodo cudaThre-adSynchronize()que garante o t´ermino de execuc¸˜ao de todas as suas threads.

O compilador nvcc

Este ´e o compilador disponibilizado para poder compilar o c´odigo do CUDA. A diferenc¸a, ´e que este n˜ao ´e um compilador completo, ou seja, ele n˜ao se responsabiliza por c´odigo C, e sim apenas pelo c´odigo escrito em CUDA. Isto permite que o compilador utilizado para compilar o c´odigo C seja customizado, ou seja, pode ser o gcc, visual c++ compiler, etc.

Modo de emulac¸˜ao O nvcc permite que o c´odigo escrito em cuda (os kernels) sejam execu-tados em modo de emulac¸˜ao (na CPU) para debugar o c´odigo, permitindo chamadas a qualquer m´etodo de CPU dentro do kernel em si para debugac¸˜ao. M´etodos como printf() podem ser chamados dentro do kernel apenas em modo de emulac¸˜ao.

Para compilar em modo de emulac¸˜ao ´e passada a flag -deviceemu para o nvcc.

2.6.4

Ponteiros

Ponteiros no CUDA s˜ao muito similares a ponteiros em C. H´a algumas “f´ormulas b´asicas” para utiliz´a-los, sendo o que os distingue dos ponteiros em C.

(34)

2.6 Framework CUDA 33

´

E preciso diferenciar se um ponteiro est´a sendo alocado no host ou no device. Sendo os ponteiros do host os ponteiros C e os ponteiros device os ponteiros CUDA.

Os ponteiros em C s˜ao alocados atrav´es de malloc(), os ponteiros em CUDA s˜ao alocados com cudaMalloc().

Para “popular” os dados de cada ponteiro, em C geralmente utiliza-se um for sobre os dados populando conforme necess´ario. Em CUDA, os ponteiros possuem seus dados populados atrav´es de cudaMemcpy() que copia os dados de um ponteiro C para um ponteiro CUDA (o m´etodo cudaMemcpy() ´e an´alogo ao m´etodo memcpy()).

Um trecho de c´odigo para alocar um ponteiro CUDA e popular seus dados:

int * data = (int*)malloc(sizeof(int) * 10); for (int i = 0; i < 10; ++i)

data[i] = 5; int * d_data;

cudaMalloc((void**)&d_data, sizeof(int) * 10);

cudaMemcpy(d_data, data, sizeof(int) * 10, cudaMemcpyHostToDevice);

2.6.5

Texturas

O CUDA possui o tipo textura. Este tipo permite acesso `a mem´oria de texturas, que possui r´apido acesso e ´e otimizada para armazenar estes tipos de dados.

Para poder alocar mem´oria para as texturas, utiliza-se o comando cudaMallocArray() que ir´a alocar a imagem na mem´oria para texturas.

´

E poss´ıvel tamb´em utilizar a mem´oria global para alocar a textura, no entanto, isso geral-mente implica em perda de desempenho. Al´em disso, o CUDA n˜ao oferece suporte a algumas operac¸˜oes com as texturas (como filtros) quando utiliza-se este modo. Uma textura alocada na mem´oria global sofre das seguintes caracter´ısticas:

• Pode ter apenas uma dimens˜ao • N˜ao suporta filtro para as texturas

• Pode ser acessada apenas por valores inteiros e n˜ao normalizados (n˜ao estando no inter-valo [0,1))

(35)

conter˜ao dados pertinentes ao conte´udo da textura. As vari´aveis descritoras s˜ao:

• texture<Type, Dim, ReadMode> texRef; - Utilizada para declarar como a textura est´a sendo armazenada (tipo da vari´avel), os filtros sendo aplicados, etc.

• cudaChannelFormatDesc - Cont´em uma descric¸˜ao sobre a textura, em geral ´e criado com o m´etodo cudaCreateChannelDesc<Type>(); onde Type ´e o tipo da vari´avel que ir´a conter cada pixel da textura (ex.: float, char, etc.)

Ent˜ao, na utilizac¸˜ao das texturas, ´e necess´ario criar uma vari´avel que ir´a conter a informac¸˜ao de cada pixel da textura e ser armazenada no lado do host. Como por exemplo:

float * h texture;

Esta vari´avel ´e utilizada da mesma forma que utiliza-se para carregar texturas com OpenGL. A pr´oxima vari´avel ´e correspondente esta, mas ser´a alocada no lado do device, ou seja, ser´a enviada `a GPU. ´E declarada da mesma forma, no entanto, sua alocac¸˜ao ´e feita pelo m´etodo cudaMalloc(). Por exemplo:

float * d_texture;

cudaMalloc( (void**) &d_texture, imageW * imageH * sizeof(float));

A pr´oxima vari´avel ´e do tipo cudaArray e ser´a ela que ir´a transportar a informac¸˜ao para a GPU atrav´es da mem´oria dedicada a texturas. O c´odigo abaixo mostra como ela deve ser usada: cudaArray * cu_array;

cudaMallocArray( &cu_array, &channelDesc, imageW, imageH );

cudaMemcpyToArray( cu_array, 0, 0, h_data, imageW * imageH * sizeof(float), cudaMemcpyHostToDevice);

cudaBindTextureToArray( texImage, cu_array, channelDesc);

Note que as vari´aveis texImage e channelDesc s˜ao os descritores citados anteriormente.

Invocac¸˜ao do kernel com texturas

Para invocar um kernel que utiliza texturas, os passos da sec¸˜ao anterior s˜ao necess´arios para a declarac¸˜ao das vari´aveis para a utilizac¸˜ao das texturas. O kernel deve ent˜ao possuir um

(36)

2.6 Framework CUDA 35

parˆametro onde ele poder´a escrever o resultado do seu processamento. Ela ter´a o mesmo tipo que a textura que armazena a textura no lado do host, mas o ponteiro passado para o kernel ser´a o da vari´avel que ´e armazenada no lado do device. Veja o c´odigo abaixo:

float * d_texture;

cudaMalloc( (void**) &d_texture, imageW * imageH * sizeof(float)); ...

__global__ void grayscale(float * textura, int w, int h); //declara¸c~ao do kernel ...

grayscale<<<grid, threads>>>(d_texture, imageW, imageH); //invoca¸c~ao do kernel

Obtenc¸˜ao dos resultados

Para obter os resultados da invocac¸˜ao do kernel, ´e necess´ario copiar o resultado para uma vari´avel local (no lado do host), isto ´e feito da seguinte forma:

float * resultado = (image_t *)malloc(sizeof(float) * imageH * imageW); cudaMemcpy( resultado, cu_array, sizeof(float) * imageH * imageW,

cudaMemcpyDeviceToHost);

O resultado da computac¸˜ao feita pelo kernel pode ser manipulada da forma desejada atrav´es da vari´avel resultado.

Sa´ıda para v´arias texturas

Foram feitos experimentos com a escrita do kernel em mais de uma textura, isto pode ser feito utilizando:

• Um cudaArray para cada textura

• Uma vari´avel armazenada no dispositivo (sendo alocada com cudaMalloc()) para cada textura

• Utilizac¸˜ao dos mesmo descritores da textura (se a sa´ıda desejada for do mesmo tipo que a textura de entrada)

2.6.6

Performance

Para obter uma boa performance utilizando o CUDA ´e necess´ario que haja uma atenc¸˜ao especial na hora de se codificar os kernels. Caso n˜ao haja esta atenc¸˜ao, ´e muito poss´ıvel que o c´odigo produzido seja muito mais lento que o produzido em CPU.

(37)

• Minimizar o n´umero de func¸˜oes com baixo “throughput”

• Maximizar o uso da banda da mem´oria atrav´es de acessos coerentes e utilizando cada tipo de mem´oria corretamente

• Utilizar um n´umero grande de instruc¸˜oes aritm´eticas em cada thread

• Manter um grande n´umero de threads ativas em cada multiprocessador sem que elas fi-quem esperando por um acesso `a mem´oria. Isto tamb´em ´e chamado de “latency hiding”.

Instruc¸˜oes de controle de fluxo

Utilizar instruc¸˜oes if, switch, do, for, while podem impactar na performance de um kernel se estas n˜ao forem minimizadas (o que nem sempre ´e poss´ıvel para a corretude do problema). Estas instruc¸˜oes podem diminuir a performance porque em alguns casos ´e necess´ario seriali-zar ambos caminhos de execuc¸˜ao, ou seja, em uma instruc¸˜ao if ambos caminhos de execuc¸˜ao precisam ser executados (Neste caso, as threads divergem, e apenas ap´os a execuc¸˜ao dos cami-nhos que as threads convertem para o mesmo caminho de execuc¸˜ao. Isto ´e um processo custoso porque h´a instruc¸˜oes in´uteis que foram executadas.

´

E v´alido lembrar que as threads de um bloco divergem se elas tomarem decis˜oes de fluxo diferentes entre si.

Acesso `a mem´oria

Executar leituras da mem´oria pode ser muito custoso, portanto, este tempo deve ser “escon-dido” e possibilitar com que haja outras operac¸˜oes executando enquanto aquela thread aguarda pela palavra sendo carregada. Os custos de acesso `a mem´oria est˜ao descritos na tabela 2.1.

Tipo de mem´oria Caracter´ıstica Custo (ciclos)

Registrador Hardware dedicado 1

Mem´oria compartilhada Hardware dedicado 1

Mem´oria local DRAM, sem cache 400-600

Mem´oria global DRAM, sem cache 400-600

Mem´oria de constantes DRAM com cache 1-100, dependendo da localidade do cache Mem´oria de texturas DRAM, com cache 1-100, dependendo da localidade do cache

(38)

2.6 Framework CUDA 37

Como os dados de entrada est˜ao sempre localizados na mem´oria global, os acessos a esta devem ser minimizados e seus dados colocados em alguma mem´oria mais r´apida. Um programa t´ıpico caracteriza-se nas seguintes etapas:

1. Carregar os dados da mem´oria global na mem´oria compartilhada

2. Sincronizar as threads do bloco para garantir que as threads n˜ao ir˜ao ler dados incorretos 3. Processar os dados na mem´oria compartilhada

4. Sincronizar as threads do bloco para garantir que as threads n˜ao ir˜ao escrever dados in-corretos

5. Escrever os resultados da mem´oria compartilhada na mem´oria global

O acesso coerente `a mem´oria ´e muito importante para uma boa performance. Ele ajuda a di-minuir o custo de acesso diminuindo a quantidade de ciclos necess´arios, pois menos instruc¸˜oes s˜ao feitas para efetuar as operac¸˜oes de leitura, carregando mais palavras por instruc¸˜ao.

Um acesso coerente `a mem´oria se caracteriza em fazer com que as threads acessem blocos cont´ıguos da mem´oria de uma forma “padronizada”. Um exemplo de acesso coerente `a mem´oria pode ser visto na figura 2.13. Esta figura mostra que todas as threads est˜ao carregando N bytes cont´ıguos da mem´oria e que o enderec¸o inicial est´a alinhado `a thread.

J´a na figura 2.14 o acesso n˜ao ´e coerente.

Os requerimentos para obter um acesso coerente `a mem´oria foi relaxado nas ´ultima gerac¸˜oes de GPUs da NVIDIA. A partir da s´erie 9, que pussuem computabilidade 1.3, os acessos podem ser interligados dentro de um bloco de threads, mas sequenciais em relac¸˜ao aos blocos.

Estas “computabilidades” s˜ao n´umeros dados `as diferentes GPUs da NVIDIA. Quanto maior o numero, maior a quantidade de operac¸˜oes diferentes e suporte a mais recursos as GPUs t´em (n˜ao necessariamente s˜ao mais r´apidas).

(39)
(40)

2.6 Framework CUDA 39

Figura 2.14: Exemplos de acesso n˜ao coerente `a mem´oria. Esquerda: Acesso interligado, Di-reita: Enderec¸o inicial n˜ao alinhado

(41)

3

Desenvolvimento

Os algoritmos implementados foram todos feitos utilizando a tecnologia CUDA em con-junto com C++ e C. Isto devido `a linguagem CUDA ser facilmente utilizada a partir destas outras linguagens.

N˜ao foi utilizado nenhum framework para cuda, como o cudpp, pois o objetivo era utilizar a tecnologia para estudar o seu uso e problemas enfrentados.

3.1

Comparac¸˜ao de N a N elementos

Este ´e um problema cl´assico, muito estudado na literatura, e consiste em:

Dado um conjunto de dados, comparar cada elemento deste array com cada um dos outros elementos deste array, verificando quais elementos s˜ao iguais entre si.

Este algoritmo foi escolhido por ser uma sub-etapa do algoritmo de segmentac¸˜ao de ima-gens Mumford&Shah, e que sua implementac¸˜ao ´e muito interessante dado sua natureza paralela, mas que exige uma reinterpretac¸˜ao do problema.

A implementac¸˜ao deste algoritmo em CPU ´e simples:

Listing 3.1: Pseudo c´odigo de comparac¸˜ao N a N em CPU

1 T[tamanhoConjunto] dadosEntrada; // elementos de entrada do tipo T (qualquer tipo ) 2 for ( i = 0; i < tamanhoConjunto; ++i) {

3 for ( j = i+1; j < tamanhoConjunto; ++j) {

4 if (dadosEntrada[ i ] == dadosEntrada[ j ]) {

5 // aqui encontramos 2 elementos iguais

6 }

7 }

8 }

(42)

3.1 Comparac¸˜ao de N a N elementos 41

• ID: corresponde ao ID ´unico deste objeto, sendo que nenhum outro objeto cont´em este ID

• valor: o valor que ser´a comparado. Um objeto ser´a igual a outro caso este campo seja igual

O que se deseja ent˜ao ´e invalidar os IDs dos objetos que contiverem um outro objeto igual. Um objeto ser´a igual a outro se o campo valor for igual. Preservar o objeto com o menor ID dos objetos iguais. Por exemplo, os dados de entrada:

A = { (1, 1) , (2, 1), (3, 1) , (4, 2), (5, 2), (6, 3) }

deve retornar:

R = { (1, 1) , (-1), (-1) , (4, 2), (-1), (6, 3) }

A quantidade de comparac¸˜oes feitas ´e de:

c= n∗ (n − 1)

2 (3.1)

O que revela que a complexidade do algoritmo ´e O(n2). As comparac¸˜oes feitas no exemplo acima s˜ao: (1,2), (1,3), (1,4), (1,5), (1,6), (2,3), (2,4), (2,5), (2,6), (3,4), (3,5), (3,6), (4,5), (4,6), (5,6)

3.1.1

Modelo em GPU

As comparac¸˜oes feitas podem ser colocadas em uma matriz triangular:

− 2 3 4 5 6 1 (1, 2) (1, 3) (1, 4) (1, 5) (1, 6) 2 x (2, 3) (2, 4) (2, 5) (2, 6) 3 x x (3, 4) (3, 5) (3, 6) 4 x x x (4, 5) (4, 6) 5 x x x x (5, 6)

Todos os campos marcados com x s˜ao comparac¸˜oes in´uteis, portanto somente a matriz triangular superior ´e importante neste caso.

(43)

Esta matriz ent˜ao ´e convertida para uma matriz c ×cn, onde c ´e o n´umero de comparac¸˜oes calculado previamente. Agrupa-se ent˜ao, similar ao processo de GAUSS para soma, cada linha com a ´ultima linha marcada. Por exemplo, a linha 1 agrupa-se com a ´ultima linha, a linha 2 agrupa-se com a pen´ultima linha, e assim por diante. A matriz resultante deste exemplo ´e:

(1, 2) (1, 3) (1, 4) (1, 5) (1, 6) (5,6) (2, 3) (2, 4) (2, 5) (2, 6) (4,5) (4,6) (3, 4) (3, 5) (3, 6) (5,6) x x

Note que neste caso temos uma matriz retangular, mas muito menos campos x, onde threads s˜ao desperdic¸adas. Aqui ´e simples fazer a indexac¸˜ao de onde cada thread deve operar.

Cada thread ir´a ent˜ao trabalhar com: (sendo x e y os ´ındices das threads e n o n´umero de dados de entrada). O valor (x,y) onde cada thread trabalha ´e dado por:

(i, j) = (

(x, x + y) se esquerda da divis˜ao(n˜ao negrito); (n − x − 2, y − 1) se esquerda da divis˜ao(negrito). Para saber se um valor est´a a direita da divis˜ao isto ´e feito por:

x+ y + 1 < n → Esquerda da divis˜ao; x+ y + 1 ≥ n → Direita da divis˜ao (negrito).

O kernel ´e lanc¸ado com b blocos, calculado por:

B= d n

larguraBlocoe ∗ d (nc) blocks.ye;

, onde: c = n´umero de comparac¸˜oes e n = n´umero de elementos de entrada.

A dimens˜ao de cada bloco ´e 16 × 16 para uma execuc¸˜ao eficiente, como explicado em (??). Como cada bloco ir´a possuir um ID e cada thread possui o seu id local (dentro do bloco), ´e necess´ario que seja calculado um ID Global para cada thread. Isto ´e calculado da seguinte forma:

numberO f BlocksPerRow= d n

(44)

3.2 Filtro de difus˜ao anisotr´opico 43

x= blockId ∗ larguraBloco n



∗ larguraBloco + threadId.y

y= ((blockId ∗ larguraBloco) % (numberO f BlocksPerRow ∗ larguraBloco)) + threadId.x

As threads s˜ao ent˜ao lanc¸adas e cada uma carrega o valor (i, j + 1) da mem´oria e compara a chave valor; caso elas sejam iguais, escreve em [ j + 1] o valor −1.

3.1.2

Custo/tempo de implementac¸˜ao

Como a descric¸˜ao deste algoritmo ´e relativamente mais complicada, este levou mais tempo para ser implementado dado v´arios bugs e particularidades encontradas.

3.2

Filtro de difus˜ao anisotr´opico

Este algoritmo por se tratar de um filtro, sua implementac¸˜ao n˜ao teve nenhuma “traduc¸˜ao” complicada. Os filtros em geral se caracterizam por alterar o pixel atual levando em considerac¸ao o valor dos pixels adjacentes a ele. Isto ´e muito simples e r´apido de se aplicar na GPU.

O algoritmo do filtro de difus˜ao ´e implementado da seguinte forma:

repetir n itera¸c~oes

para cada pixel fazer

pixel atual = opera¸cao de difus~ao com pixels adjacentes fim

fim

3.2.1

Mem´oria

Para enviar a imagem para a GPU foi utilizada a mem´oria de texturas para representar a imagem. Isto ajudou muito na performance do filtro, pois a mem´oria de texturas n˜ao sofre penalidades de performance para acessos `a mem´oria n˜ao coerentes.

(45)

apenas assumiu-se que cada thread iria processar cada pixel. O acesso `a mem´oria de texturas ´e apenas de 8 leituras (pixels adjacentes) e 1 escrita para cada pixel.

Como a mem´oria de texturas possui exigˆencias mais brandas para obter uma alta velocidade de acesso, esta revelou-se at´e 5 vezes mais r´apida que o mesmo kernel utilizando a mem´oria global.

3.2.3

Custo/tempo de implementac¸˜ao

O tempo gasto para este algoritmo foi relativamente curto, no entanto, a maior parte do tempo foi gasta aprendendo a nova API que estava em estudo.

(46)

45

4

Resultados

Resultados sobre a qualidade e tempos de resposta obtidos para cada um dos algoritmos implementados.

O CUDA Profiler ´e um aplicativo criado pela NVIDIA para verificar algumas informac¸˜oes sobre a execuc¸˜ao dos kernels na GPU (p/ ex.: acessos coerentes `a mem´oria, n˜ao coerentes, escritas, etc.). Estar informac¸˜oes s˜ao ´uteis para guiar o programador de forma a melhorar e analisar o desempenho do programa. Os resultados podem ser utilizados para apontar se uma estrat´egia de implementac¸˜ao diferente deve ser utilizada.

4.1

Filtro de difus˜ao anisotr´opica

O filtro de difus˜ao anisotr´opica foi um ´otimo caso de utilizac¸˜ao da GPU devido `a sua natu-reza paralela (independˆencia dos dados) e alto n´umero de operac¸˜oes aritm´eticas.

4.1.1

Validac¸˜ao dos resultados

Para validar os resultados foi implementado o mesmo filtro em CPU, que serve como implementac¸˜ao base. Rodou-se ent˜ao ambos algoritmos com os mesmos parˆametros gerando duas imagens diferentes, uma para CPU e outra para GPU.

A figura 4.1.1 mostra imagens lado a lado para que seja feita uma inspec¸˜ao visual entre CPU e GPU. Um n´umero elevado de iterac¸˜oes foi utilizado para acentuar a diferenc¸a entre as imagens.

Para inspecionar melhor a diferenc¸a entre os resultados, foi utilizado o utilit´ario Image-magick(IMAGEMAGICK, 2008) para criar imagens que mostrem de forma visual as diferenc¸a entre as imagens. Ser´a utilizada a primeira imagem da figura 4.1.1 para comparac¸˜ao entre cpu e gpu.

A imagem (a) da figura 4.1.1 mostra em vermelho se o pixel (x, y) da imagem em CPU ´e diferente do pixel (x, y) da imagem em GPU. Para salientar melhor a quantidade de pixels diferentes ´e feita uma m´ascara, onde em branco estar˜ao os pixels diferentes e em preto os pixels

(47)

(a) Imagem original (b) FDA em CPU (c) FDA em GPU

(d) Imagem original (e) FDA em CPU (f) FDA em GPU

(g) Imagem original (h) FDA em CPU (i) FDA em GPU

Figura 4.1: Comparac¸˜ao do resultado entre CPU e GPU. λ = 15 e 90 iterac¸˜oes que n˜ao s˜ao diferentes, como mostrado na imagem (b) da figura 4.1.1.

H´a diferenc¸a entre as imagens, mas a quantidade ´e pequena, como pode ser visto por inspec¸˜ao visual nas imagens anteriores. As imagens (c) e (d) da figura 4.1.1 mostram em quan-tidade o quanto estas imagens s˜ao diferentes. A primeira ´e uma simples diferenc¸a entre cada pixel, calculado pela equac¸˜ao 4.1 e a ´ultima ´e a primeira imagem com contraste acentuado para que seja mais f´acil visualizar a imagem.

(48)

4.1 Filtro de difus˜ao anisotr´opica 47

(a) Diferenc¸a entre CPU e GPU. Pixels alterados est˜ao em ver-melho

(b) M´ascara aplicada sobre a imagem de diferenc¸a. Os pixels em branco s˜ao pixels diferentes entre as imagens

(c) Imagem de diferenc¸a en-tre CPU e GPU aplicando a equac¸˜ao 4.1

(d) Imagem de diferenc¸a en-tre CPU e GPU aplicando a equac¸˜ao 4.1 com contraste acen-tuado para melhor visualizac¸˜ao

Figura 4.2: Diferenc¸as entre as imagens geradas por CPU e GPU

4.1.2

Tempos de resposta

O filtro de difus˜ao apresentou tempos de resposta muito bons quando comparados `a implementac¸˜ao em CPU. Os n´umero comprovam que a GPU ´e superior quando h´a muitas instruc¸˜oes aritm´eticas para serem executadas com independˆencia dos dados.

Para medir os tempos foram executados ambos algoritmos 500 vezes. A configurac¸˜ao de cada foi de 100 iterac¸˜oes e λ = 15. O tempo medido em CPU ´e somente a execuc¸˜ao do filtro de difus˜ao, excluindo o tempo de carregar e salvar a imagem. Na GPU, as medic¸˜oes est˜ao divididas em duas:

• GPU processamento - apenas o tempo de processamento do kernel ´e medido

• GPU overhead - o tempo de processamento do kernel ´e medido e tamb´em o tempo de transferˆencia dos dados para a GPU.

(49)

512x512 199535,87 685,37 735,53 271

1024x1024 807530,07 2688,18 2774,56 291

2048x2048 3205504,2 10118,28 10349,07 310

Tabela 4.1: M´edia das execuc¸˜oes do filtro de difus˜ao com 100 iterac¸˜oes (tempos em milisegun-dos)

Figura 4.3: Gr´afico de performance do filtro de difus˜ao comparando CPU e GPU

4.1.3

An´alise com o CUDAProfiler

Ao rodar o CUDAProfiler sobre o filtro de difus˜ao com 90 iterac¸˜oes, obtemos os resultados exibidos na figura 4.1.3. Cada linha ´e uma execuc¸˜ao do kernel, portanto h´a 90 linhas de resulta-dos. Estes resultados chamam a atenc¸˜ao pela coluna gst incoherent, que mostra a quantidade de writesincoerentes feitos pelo kernel. Isto ´e evidenciado ao plotarmos o gr´afico de cada coluna, como mostrado na figura 4.1.3.

O valor alto apontado pela coluna gst incoherent mostra que h´a muitos writes em mem´oria n˜ao coerentes. Como explicado no cap´ıtulo 2.6.6, os writes devem ser coerentes para aumentar a velocidade do acesso `a mem´oria. Isto pode estar impedindo que o algoritmo rode em maiores velocidades.

4.1.4

Conclus˜ao

A qualidade das imagens obtidas e a otimizac¸˜ao do tempo de resposta mostram que a implementac¸˜ao do filtro de difus˜ao foi um sucesso. Talvez possa se melhorar ainda mais o tempo de resposta devido aos resultados exibidos no CUDAProfiler.

(50)

4.1 Filtro de difus˜ao anisotr´opica 49

Figura 4.4: Cuda profiler rodando o filtro de difus˜ao com 90 iterac¸˜oes

Figura 4.5: Gr´afico dos sinais capturados pelo CUDAProfiler. E not´avel o alto valor de´ gst incoherent.

(51)

O problema de comparac¸˜ao N a N exige uma certa ast´ucia na hora de repens´a-lo para a GPU, pois as threads podem ser desperdic¸adas. ´E tamb´em necess´ario prestar atenc¸˜ao especial no acesso coerente da mem´oria.

Este problema, da maneira como foi implementado, serve como um “contra-exemplo” para o argumento “GPUs s˜ao mais r´apidas, independente do problema”. Isto ´e mostrado nos tempos de resposta obtidos. No entanto, ´e poss´ıvel que este problema possa ser acelerado atrav´es de um uso inteligente da mem´oria compartilhada e utilizar acesso coerente `a mem´oria, mas a quantidade de operac¸˜oes aritm´eticas por thread ´e baixa e a quantidade de acessos `a mem´oria ´e alta, o que pode inviabilizar este problema para a GPU.

4.2.1

Tempos de resposta

Os tempos de resposta para a GPU foram maiores que a CPU, o que ´e esperado, j´a que a implementac¸˜ao n˜ao se preocupou muito em ter um acesso estritamente coerente `a mem´oria e, pela natureza do problema, a quantidade de operac¸˜oes aritm´eticas ´e muito baixa.

Cada m´etodo foi executado 500 vezes com 500, 1000 e 1500 elementos. A m´edia das execuc¸˜oes est´a na tabela 4.2. Esta tabela gera o gr´afico da figura 4.2.1.

- Tempo m´edio (ms)

N´umero de elementos CPU GPU

500 1,97 64,65

1000 10,21 65,34

1500 21,86 67,43

Tabela 4.2: M´edia das execuc¸˜oes da comparac¸˜ao N a N

4.2.2

Validac¸˜ao dos resultados

A validac¸˜ao dos resultados para este problema ´e uma simples checagem entre o array gerado pela CPU e pela GPU. Se cada elemento gerado pela GPU for igual ao array gerado pela CPU, o resultado est´a correto.

4.2.3

An´alise com o CUDAProfiler

A figura 4.2.3 exibe os resultados de rodar a comparac¸˜ao N a N com 1500 elementos de entrada. ´E poss´ıvel notar que as colunas gld incoherent e gld coherent n˜ao aparecem, pois todos os seus valores s˜ao 0. As colunas gst coherent e gst incoherent apresentam valores parecidos,

(52)

4.2 Comparac¸˜ao N a N 51

Figura 4.6: Gr´afico de performance da comparac¸˜ao N a N com 1500 elementos

o que chega a ser esperado, j´a que a matriz possui a divis˜ao, o que torna muito dif´ıcil conseguir tornar os writes coerentes.

Figura 4.7: Cuda profiler rodando a comparac¸˜ao N a N com 1500 elementos

(53)

5

Conclus˜ao

Para v´arias aplicac¸˜oes ´e muito v´alido criar uma implementac¸˜ao em GPU pois o ganho em performance pode ser muito grande, como visto com o filtro de difus˜ao. No entanto, ´e necess´ario um estudo pr´evio sobre o algoritmo e colocar na balanc¸a se ´e vi´avel.

Hoje, devido `a falta de amadurecimento por parte da tecnologia, que ´e nova e promissora, e dos desenvolvedores, por se trabalhar com um paradigma de programac¸˜ao diferente do con-vencional, o custo de implementar um algoritmo j´a conhecido ser´a maior do que se fosse feito pelos m´etodos tradicionais.

(54)

53

Referˆencias Bibliogr´aficas

BUCK, I. Gpu gems 2 - programming techniques for high-performance graphics and general-purpose computation. In: . [S.l.]: Addison Wesley, 2005. cap. Taking the plunge into GPU computing, p. 509–519.

LUEBKE, D.; HUMPHREYS, G. How gpus work. Computer, IEEE Computer Society, Los Alamitos, CA, USA, v. 40, n. 2, p. 96–100, 2007. ISSN 0018-9162.

GHULOUM, A. The Problem(s) with GPGPU. Out 2007. Acessado em 19/11/2007. Dispon´ıvel em: <http://blogs.intel.com/research/2007/10/the problem with gpgpu.html>.

GONZALEZ, R. C.; WOODS, R. E. Digital Image Processing. 3rd. ed. Upper Saddle River, NJ, USA: Prentice Hall, 2008. ISBN 9780131687288.

SERAMANI ZHOU JIAYIN, C. K. L. N. A. S. Denoising of mr images using non linear anisotropic diffusion filtering as a preprocessing step. International Journal of BioSciences and Technology, v. 1, n. 1, 2008.

PERONA, P.; MALIK, J. Scale-space and edge detection using anisotropic diffusion. Pattern Analysis and Machine Intelligence, IEEE Transactions on, v. 12, n. 7, p. 629–639, Jul 1990. ISSN 0162-8828.

WIKIPEDIA. Stream processing. [S.l.], 200? Acessado em 28/08/2007. Dispon´ıvel em: <http://en.wikipedia.org/wiki/Stream processing>.

PHARR, M.; FERNANDO, R. GPU Gems 2 : Programming Techniques for High-Performance Graphics and General-Purpose Computation. [S.l.]: Addison-Wesley Professional, 2005. Hardcover. ISBN 0321335597.

NVIDIA. NVIDIA CUDA Programming Guide 2.0. [S.l.: s.n.], 2008.

IMAGEMAGICK. Comparing – IM v6 Examples. [S.l.], 2008. Acessado em 29/10/2008. Dispon´ıvel em: <http://www.imagemagick.org/Usage/compare/>.

Referências

Documentos relacionados

Os testes de desequilíbrio de resistência DC dentro de um par e de desequilíbrio de resistência DC entre pares se tornarão uma preocupação ainda maior à medida que mais

ano, permitida uma recondução consecutiva e eleita com os respectivos suplentes por seus pares. A eleição do CCPGG será solicitada ao Departamento de Geografia e ao corpo

3 O presente artigo tem como objetivo expor as melhorias nas praticas e ferramentas de recrutamento e seleção, visando explorar o capital intelectual para

(grifos nossos). b) Em observância ao princípio da impessoalidade, a Administração não pode atuar com vistas a prejudicar ou beneficiar pessoas determinadas, vez que é

Se você vai para o mundo da fantasia e não está consciente de que está lá, você está se alienando da realidade (fugindo da realidade), você não está no aqui e

Todavia, nos substratos de ambos os solos sem adição de matéria orgânica (Figura 4 A e 5 A), constatou-se a presença do herbicida na maior profundidade da coluna

Este trabalho busca reconhecer as fragilidades e potencialidades do uso de produtos de sensoriamento remoto derivados do Satélite de Recursos Terrestres Sino-Brasileiro

13 Além dos monômeros resinosos e dos fotoiniciadores, as partículas de carga também são fundamentais às propriedades mecânicas dos cimentos resinosos, pois