UNIVERSIDADE ESTADUAL PAULISTA "JÚLIO DE MESQUITA FILHO" CAMPUS DE SÃO JOÃO DA BOA VISTA MOISÉS CARVALHO ALVES Estimativa de Profundidade em Imagens RGB de Regiões Anatômicas para a Geração de Modelos Tridimensionais Digitais São João da Boa Vista 2024 Moisés Carvalho Alves Estimativa de Profundidade em Imagens RGB de Regiões Anatômicas para a Geração de Modelos Tridimensionais Digitais Trabalho de Graduação apresentado ao Conselho de Curso de Graduação em Engenharia Eletrônica e de Telecomunicações do Campus de São João da Boa Vista, Universidade Estatual Paulista, como parte dos requisitos para obtenção do diploma de Graduação em Engenharia Eletrônica e de Teleco- municações . Orientador: Profº Dr. Marlon Rodrigues Garcia São João da Boa Vista 2024 A474e Alves, Moisés Carvalho Estimativa de profundidade em imagens RGB de regiões anatômicas para a geração de modelos tridimensionais digitais / Moisés Carvalho Alves. -- São João da Boa Vista, 2024 47 p. : il., tabs., fotos Trabalho de conclusão de curso (Bacharelado - Engenharia de Telecomunicações) - Universidade Estadual Paulista (UNESP), Faculdade de Engenharia, São João da Boa Vista Orientador: Marlon Rodrigues Garcia 1. Inteligência artificial. 2. Método de Monte Carlo. 3. Modelagem tridimensional. I. Título. Sistema de geração automática de fichas catalográficas da Unesp. Dados fornecidos pelo autor(a). UNIVERSIDADE ESTADUAL PAULISTA “JÚLIO DE MESQUITA FILHO” FACULDADE DE ENGENHARIA - CÂMPUS DE SÃO JOÃO DA BOA VISTA GRADUAÇÃO EM ENGENHARIA ELETRÔNICA E DE TELECOMUNICAÇÕES TRABALHO DE CONCLUSÃO DE CURSO ESTIMATIVA DE PROFUNDIDADE EM IMAGENS RGB DE REGIÕES ANATÔMICAS PARA A GERAÇÃO DE MODELOS TRIDIMENSIONAIS DIGITAIS Aluno: Moisés Carvalho Alves Orientador: Prof. Dr. Marlon Rodrigues Garcia Banca Examinadora: - Marlon Rodrigues Garcia (Orientador) - Plínio Santini Dester (Examinador) - Lilian Tan Moriyama (Examinadora) Os formulários de avaliação e a ata da defesa, na qual consta a aprovação do trabalho, devidamente assinados pela banca encontram-se no prontuário eletrônico do aluno. São João da Boa Vista, 05 de dezembro de 2024 “Não acho que quem ganhar ou quem perder, nem quem ganhar nem perder, vai ganhar ou perder. Vai todo mundo perder.“ (Dilma Rousseff) RESUMO Simular com exatidão a interação da luz com os tecidos biológicos é de alta importância para aplicações médicas. Contudo, obter essa interação por meio da Equação de Transporte Radiativo é bastante complexo. Para isto, o método de Monte Carlo se prova bastante útil, uma vez que por ele é possível resolver a equação de forma estatística, e portanto diminuindo a complexidade do problema. Para utilizar este método, é necessário um modelo tridimensional digital de alta precisão, que por sua vez não é algo acessível, principalmente em ambientes clínicos. Este trabalho portanto visa criar modelos tridimensionais precisos com apenas uma imagem bidimensional, de forma acessível para aplicações médicas. Para tal, processamos a imagem bidimensional por um algoritmo de inteligência artificial, capaz de nos retornar a profundidade para cada pixel da imagem. Com essa informação, criamos uma nuvem de pontos e enfim processamos ela, de modo a criar um modelo tridimensional. Para se obter esse modelo, foram criados vários métodos de geração de malha, além de também a geração de uma grade de voxels, para então ter a comparação destes métodos. Desta forma, foi possível notar as diferentes dificuldades para cada um dos métodos, assim como o quão complexo computacionalmente cada atividade é. Conclui-se então que foi possível utilizar desta metodologia para criar um modelo acessível e preciso dado o contexto, de forma a auxiliar os profissionais da área. PALAVRAS-CHAVE: Equação de Transporte Radiativo. Método de Monte Carlo. Modelo tridimen- sional. Nuvem de pontos. Inteligência artificial. ABSTRACT Simulating accurately the interaction of light with biological tissues is of great importance for me- dical applications. However, obtaining this interaction through the Radiative Transport Equation is quite complex. To address this, the Monte Carlo method proves to be very useful, as it allows the equation to be solved statistically, thus reducing the complexity of the problem. To use this method, a highly accurate 3D digital model is required, which is not easily accessible, particularly in clinical environments. Therefore, this work aims to create precise 3D models using just a 2D image, making it more accessible for medical applications. For this, we process the 2D image using an artificial intelligence algorithm capable of returning the depth for each pixel in the image. With this information, we create a point cloud and then process it to generate a 3D model. To obtain this model, various mesh generation methods were used, along with the creation of a voxel grid, in order to compare these methods. This way, we were able to identify the different challenges each method presents, as well as the computational complexity of each activity. The conclusion is that it was possible to use this methodology to create an accessible and precise model, given the context, to assist professionals in the field. KEYWORDS: Radiative Transport Equation. Monte Carlo Method. Three-dimensional model. Point cloud. Artificial intelligence. LISTA DE ILUSTRAÇÕES Figura 1 Fluxograma das etapas seguidas pelo projeto, para a obtenção de phantoms digitais 3D, tanto por voxels quanto por malha. . . . . . . . . . . . . . . . . . . 14 Figura 2 Representação de uma nuvem de pontos aleatória. . . . . . . . . . . . . . . . . 16 Figura 3 Imagem ilustrativa mostrando as camadas da pele. . . . . . . . . . . . . . . . . 17 Figura 4 Representação de uma voxel grid aleatória. . . . . . . . . . . . . . . . . . . . . 18 Figura 5 Representação de uma malha aleatória. . . . . . . . . . . . . . . . . . . . . . . 19 Figura 6 Representação 2D da reconstrução por Poisson. . . . . . . . . . . . . . . . . . 20 Figura 7 Representação 2D do BPA sobre uma nuvem de pontos. . . . . . . . . . . . . . 21 Figura 8 Imagem RGB da mão de um voluntário. . . . . . . . . . . . . . . . . . . . . . 22 Figura 9 Profundidade predita pela rede neural profunda DPT-Large 512 do MiDaS versão 3.1. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Figura 10 Nuvem de pontos gerada após combinação das imagens. . . . . . . . . . . . . 24 Figura 11 Nuvem de pontos, vista da parte posterior do dorso da mão, após a redução do número de pontos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Figura 12 Nuvem de pontos do mapa de profundidade mostrado na Figura 8, para uma vista posterior da parte dorsal da mão, após a redução do número de pontos e a reorientação das normais. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Figura 13 Nuvem de pontos do mapa de profundidade da Figura 8, após a reorientação de suas normais, em uma, para uma vista posterior da parte dorsal da mão. . . . . 26 Figura 14 Nuvem de pontos do mapa de profundidade para uma vista posterior da parte dorsal da mão mostrando-se a camada gerada em uma dada distância. . . . . . 27 Figura 15 Nuvem de pontos do mapa de profundidade para uma vista posterior da parte dorsal da mão preenchida até o deslocamento. . . . . . . . . . . . . . . . . . . 27 Figura 16 Grade de voxels para a nuvem de entrada na vista posterior da parte dorsal da mão. 28 Figura 17 Grade de voxels para múltiplas camadas simultâneas na vista posterior da parte dorsal da mão. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Figura 18 Vista posterior da parte dorsal da mão na malha por reconstrução de Poisson. . 30 Figura 19 Vista posterior da parte dorsal da mão na malha filtrada por reconstrução de Poisson. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Figura 20 Malhas por reconstrução de Poisson com diferentes parâmetros de entrada para (a) depth com valor de 5, e (b) depth com valor de 7. . . . . . . . . . . . . 31 Figura 21 Vista posterior da parte dorsal da mão na malha por BPA com um raio grande. . 31 Figura 22 Vista posterior da parte dorsal da mão da malha por BPA com um raio pequeno. 32 Figura 23 Vista posterior da parte dorsal da mão na malha e suas camadas inferiores por reconstrução de Poisson. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Figura 24 Aglomeração dos pontos quando a camada inferior tem um deslocamento muito grande com a reconstrução por Poisson para a vista posterior do dorso da mão apenas da camada inferior. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Figura 25 Malhas para a vista posterior do dorso da mão por reconstrução de Poisson dos pontos aglomerados filtrados. . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 LISTA DE TABELAS Tabela 1 – Exemplo de uma matriz de entrada para o Open3D. . . . . . . . . . . . . . . . . 16 LISTA DE ABREVIATURAS E SIGLAS 3D Tridimensional 2D Bidimensional ETR Equação de Transporte Radiativo MCX Monte Carlo eXtreme RGB Red, Green, Blue (vermelho, verde e azul) BPA Ball-Pivoting Algorithm (Algoritmo de pivotamento de bola) LiDaR Detecção e alcance de luz (Light Detection and Ranging) LISTA DE SÍMBOLOS ρ Densidade de carga r Raio da bola ϕ Intensidade do campo N Vetor normal ∇ Operador vetorial nabla SUMÁRIO 1 INTRODUÇÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 1.1 Justificativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 1.2 Objetivo Geral . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 1.3 Objetivos Específicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2 METODOLOGIA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.1 Aquisição da Imagem 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2 Predição da Imagem de Profundidade . . . . . . . . . . . . . . . . . . . . . . . . 15 2.3 Geração da Nuvem de Pontos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.4 Geração das Camadas Inferiores . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.5 Geração da Voxel-Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2.6 Geração da Malha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.6.1 Reconstrução pelo Método de Poisson . . . . . . . . . . . . . . . . . . . . . . 20 2.6.2 Ball-Pivoting Algorithm (BPA) . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3 RESULTADOS E DISCUSSÃO . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.1 Imagem 2D Capturada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.2 Predição de Profundidade . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.3 Nuvem de Pontos e Camadas Inferiores . . . . . . . . . . . . . . . . . . . . . . . 24 3.3.1 Camadas Inferiores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 3.4 Geração da Grade de Voxels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.5 Geração das Malhas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 4 CONCLUSÃO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 REFERÊNCIAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 APÊNDICE A – CÓDIGO UTILIZADO PARA MANIPULAR A NUVEM DE PONTOS E GERAR O MODELO 3D . . . . . . . . . . 38 11 1 INTRODUÇÃO Com o avanço da tecnologia médica, há uma crescente demanda por ferramentas que facilitem diagnósticos e tratamentos precisos. Esse aumento é especialmente notável em procedimentos que utilizam luz, como lasers em terapias e diagnósticos, que se tornaram fundamentais em diversas áreas da medicina, incluindo oncologia, dermatologia e oftalmologia. A precisão desses procedimentos não se limita apenas ao uso de equipamentos sofisticados; ela depende diretamente da capacidade de simular com exatidão a interação da luz com os tecidos biológicos. A Equação de Transporte Radiativo (ETR) (CHANDRASEKHAR, 2013) por sua vez é quem descreve a propagação de radiação em meios dispersivos e absorventes, levando em conta tanto a absorção quanto a dispersão da luz. Contudo, resolver a ETR de forma analítica pode ser extremamente desafiador, especialmente em estruturas biológicas complexas. Para superar essa dificuldade, métodos de simulação, como o método de Monte Carlo (GENTLE, 2009), são amplamente utilizados. Este método é uma técnica estatística que permite modelar a interação da luz com os tecidos biológicos por meio de simulações baseadas em amostragens aleatórias, possibilitando a previsão de como a luz irá se propagar em diferentes condições. Desta forma, o Monte Carlo desempenha um papel fundamental tanto no diagnóstico quanto no desenvolvimento ou aperfeiçoamento de novos protocolos de tratamento. No contexto de diagnósticos, ele é importante para simular como a luz irá propagar em tecidos biológicos, possibilitando o cálculo preciso das medidas necessárias para métodos de diagnóstico óptico. Para tratamentos, o Monte Carlo se destaca por sua capacidade de modelar a interação da luz com diferentes tipos de tecidos, permitindo o ajuste de parâmetros de tratamento, incluindo a fluência óptica, que se refere à dose de luz a ser entregue ao tecido alvo. Essa simulação é crucial para garantir que os tratamentos sejam tanto seguros quanto eficazes, atendendo às necessidades de diferentes tipos de pele e condições biológicas. A capacidade de prever a resposta dos tecidos à luz torna o Monte Carlo uma ferramenta indispensável no desenvolvimento de terapias baseadas em laser e outras aplicações clínicas que utilizam a luz como parte do tratamento. Para essa finalidade, o Monte Carlo eXtreme (MCX) (FANG; BOAS, 2009) é uma ferramenta bastante valiosa, sendo um software capaz de realizar simulações utilizando o método de Monte Carlo. Para utilizar o MCX de forma eficaz, é essencial ter um modelo tridimensional preciso que represente a complexidade dos tecidos biológicos, que são os chamados phantoms digitais. A etapa de modelagem tridimensional (3D) é crucial, pois é quem delimita o domínio das simulações, fazendo com que levem em consideração ou não a heterogeneidade dos tecidos, que podem variar em densidade, composição celular e propriedades ópticas. Esses modelos tridimensionais não apenas facilitam a simulação mais realista das interações luz-tecido, mas também são essenciais para garantir a segurança e a eficácia dos tratamentos. Por exemplo, em terapias fotodinâmicas, como o tratamento do câncer de pele, um modelo detalhado permite prever como a luz penetra nos tecidos e interage com células cancerígenas, possibilitando a criação de protocolos de tratamento mais adequados às características específicas de cada paciente. 12 Entretanto, a criação e manipulação de modelos 3D apresentam desafios significativos. Técnicas convencionais de modelagem, que muitas vezes utilizam imagens RGB-D ou LiDAR, podem ser custosas e requerer equipamentos sofisticados, tornando-as impraticáveis em ambientes clínicos cotidianos, como consultórios e ambulatórios. Portanto, há uma necessidade crescente de uma abordagem mais acessível e eficiente que utilize imagens monoculares, já disponíveis ou facilmente adquiridas com dispositivos comuns, como smartphones. Uma das principais limitações ao utilizar imagens monoculares para a criação de modelos tridi- mensionais é a ausência da informação de profundidade. Sem um modelo com essa informação, as simulações de transporte de fótons, como as realizadas pelos métodos de Monte Carlo, não terão a informação de profundidade, resultando em diagnósticos ou planejamentos de tratamento que podem não ser precisos. A obtenção dessa característica a partir de imagens monoculares representa um desafio técnico significativo, pois envolve estimar a distância e a geometria dos tecidos a partir de dados bidimensionais, que muitas vezes não capturam a complexidade geométrica necessária para se desenvolver um modelo 3D. Com os avanços recentes em inteligência artificial, esse desafio está sendo superado de forma cada vez mais eficiente. Algoritmos complexos, como os empregados no projeto MiDaS (RANFTL et al., 2020), têm a capacidade de gerar informações de profundidade a partir de imagens monoculares, utilizando técnicas de deep learning para estimar a profundidade de maneira precisa. Além disso, o crescente suporte para a modelagem tridimensional utilizando bibliotecas Python, como o Open3D (ZHOU; PARK; KOLTUN, 2018), tem facilitado a implementação de algoritmos de reconstrução 3D, permitindo que desenvolvedores e pesquisadores criem modelos tridimensionais de forma mais rápida e acessível. A integração dessas técnicas não apenas aprimora os tratamentos já existentes, mas também demo- cratiza o acesso a procedimentos avançados, tornando essas tecnologias mais viáveis e acessíveis para uma variedade de clínicas e hospitais. Ao oferecer uma compreensão mais aprofundada da interação entre luz e tecidos biológicos, essas inovações não apenas aumentam a segurança dos tratamentos, mas também reforçam o compromisso com a excelência no cuidado ao paciente, permitindo que cada indivíduo receba terapias com um maior grau de personalização. 1.1 JUSTIFICATIVA A crescente demanda por diagnósticos e tratamentos médicos precisos, especialmente em proce- dimentos que envolvem o uso de luz, destaca a necessidade de ferramentas que sejam não apenas eficazes, mas também acessíveis a um número maior de profissionais da saúde e pacientes. Nesse contexto, a adoção de métodos que aproveitam imagens monoculares, facilmente capturadas por dispo- sitivos comuns, como smartphones, se torna uma solução inovadora e viável. Essa abordagem não só democratiza o acesso a tecnologias avançadas, mas também promove um cuidado mais personalizado e seguro, adaptando-se às necessidades individuais de cada paciente. 13 1.2 OBJETIVO GERAL O objetivo geral deste trabalho é desenvolver um método automatizado para a criação de modelos tridimensionais a partir de imagens monoculares (imagens obtidas através de uma única câmera), com vistas à geração de phantoms digitais para simulações Monte Carlo da interação luz-tecidos biológicos. Primeiramente, objetivou-se utilizar redes neurais artificiais para estimar o mapa de profundidade das imagens monoscópicas. Com a informação de profundidade, o objetivo seguinte foi a obtenção da nuvem de pontos por meio da biblioteca Open3D. A nuvem de pontos permitiu a criação de uma voxel-grid e uma mesh a partir dela. 1.3 OBJETIVOS ESPECÍFICOS Como objetivos específicos, o presente trabalho abordou os seguintes tópicos: 1. Obtenção de familiaridade com a linguagem de programação Python; 2. Utilização de modelos de inteligência artificial para a estimação da profundidade em imagens monoculares; 3. Familiarização com o processamento de imagens 3D digitais; 4. Aprendizado das abordagens para modelagem 3D, com foco em voxelização e geração de malhas; 5. Desenvolvimento de modelos 3D a partir de nuvens de pontos; 6. Implementação de visualizações detalhadas para análise e interpretação dos modelos 3D; 7. Avaliação comparativa de diferentes técnicas de modelagem 3D para otimização da representação tridimensional. 14 2 METODOLOGIA O processo metodológico deste trabalho envolve várias etapas, desde a aquisição da imagem bidimensional (2D) até a geração de um modelo 3D final para ser utilizado em simulações de Monte Carlo. Na Figura 1 se mostra um fluxograma das etapas metodológicas desenvolvidas no presente projeto. Figura 1 – Fluxograma das etapas seguidas pelo projeto, para a obtenção de phantoms digitais 3D, tanto por voxels quanto por malha. Fonte: Produção do próprio autor. 2.1 AQUISIÇÃO DA IMAGEM 2D A imagem 2D foi adquirida com um smartphone modelo S20 FE 5g (Samsung, Yeongtong-gu, Coreia do Sul), no formato JPG, com o esquema de cores do tipo RGB, em um ambiente interno com iluminação natural. A foto foi capturada a uma distância de aproximadamente 30 cm do objeto de interesse, sem a necessidade de ângulos específicos ou alinhamentos predeterminados. Como a proposta deste trabalho visa simular condições reais de uma clínica, a foto foi capturada sem um aparato técnico específico, refletindo como as medidas podem ser tomadas em ambientes reais. As regiões da imagem que estiverem fora de foco podem não ter sua profundidade predita correta- mente pelos algoritmos de inteligência artificial. Essa questão é particularmente relevante, uma vez que a imagem selecionada será processada para gerar um mapa de profundidade, que é extremamente sensível à qualidade e clareza da imagem original. Portanto, o único critério de seleção da imagem foi a sua nitidez, assegurando-se que a imagem não estivesse desfocada. A verificação foi realizada pela 15 simples inspeção da imagem, sem a utilização de algoritmos de pré-processamento antes de submetê-la ao modelo de inteligência artificial. 2.2 PREDIÇÃO DA IMAGEM DE PROFUNDIDADE Para transformar uma imagem 2D em uma nuvem de pontos 3D, é fundamental obter a profundidade de cada pixel, o que permite reconstruir a posição relativa dos elementos no espaço tridimensional. Para esse fim, utilizamos um dos modelos disponíveis no repositório do projeto MiDaS, em sua versão 3.1, um projeto que desenvolve modelos de redes neurais artificiais profundas, baseadas em transformadores 1. Essa abordagem foi escolhida pela sua alta precisão e versatilidade na estimativa de profundidade. Esse modelo foi treinado principalmente com conjuntos de dados contendo objetos inanimados, como móveis e ambientes estruturados, e não com tecidos biológicos. Essa diferença no treinamento pode limitar a precisão do modelo quando aplicado ao foco deste projeto, que é estimar a profundidade em imagens de regiões anatômicas. Em particular, a versão do modelo usada foi a DPT-Large 512, obtida de um repositório oficial no GitHub, configurada para gerar mapas de profundidade de cada pixel da imagem 2D com alto nível de detalhes. Esse modelo não é o mais avançado do repositório oficial, porém, ele é avançado o suficiente para gerar bons resultados, mas sem ser tão pesado a ponto de diminuir o tempo de resposta. A entrada utilizada no MiDaS consiste em imagens 2D no formato .jpg, com um esquema de cores RGB, enquanto a saída é gerada como uma imagem monocromática, também em .jpg, onde cada pixel possui uma tonalidade de cinza representando a profundidade estimada. A resolução da imagem de saída é mantida igual à resolução da imagem de entrada, o que permite um mapeamento direto entre os pixels originais e seus respectivos valores de profundidade. 2.3 GERAÇÃO DA NUVEM DE PONTOS Uma nuvem de pontos é uma coleção de pontos no espaço tridimensional que representa a forma e a superfície de um objeto ou cena. Cada ponto na nuvem é definido por suas coordenadas tridimensionais (x, y, z) e, em alguns casos, pode incluir informações adicionais, como sua cor ou o seu vetor normal. As nuvens de pontos são dados não estruturados, onde não há uma conexão explícita entre os pontos, o que implica uma maior flexibilidade na manipulação dos pontos. Além disso, temos que cada nuvem de pontos tem sua própria densidade e distribuição, onde nuvens densas detalham mais a superfície do objeto, enquanto que nuvens esparsas detalham pouco essa mesma superfície. Em geral, uma nuvem de pontos não é nada mais do que uma representação tridimensional de um objeto e, a partir desta representação, é possível processar os dados para obter um modelo 3D de um tecido biológico, por exemplo. A Figura 2 exemplifica como uma nuvem de pontos se comporta para pontos em posições aleatórias, apenas para a ilustração do método. Cada ponto na nuvem possui três coordenadas, que definem sua localização em um espaço tridimensional. As coordenadas x e y podem ser extraídas da imagem 2D original, porém, como a 1 Transformadores (Transformers) é um tipo de rede neural que aprende o contexto e a relação dos componentes para que com base nesse aprendizado seja possível transformar um dado de entrada em uma saída coerente ao dado contexto. 16 Figura 2 – Representação de uma nuvem de pontos aleatória. Fonte: Produção do próprio autor. mesma não possui uma terceira dimensão, utilizaremos o mapa de profundidade para representar a coordenada z. Para o processamento das nuvens de pontos, utilizamos a biblioteca Open3D, que oferece ferra- mentas específicas para essa finalidade, além de permitir a visualização de todas as etapas do processo. Assim, além de associar a profundidade com cada coordenada bidimensional, é necessário parametrizar os dados de forma que possam ser interpretados pelos métodos disponíveis na biblioteca Open3D. Essa parametrização consiste em organizar os dados na estrutura de uma matriz, onde cada linha representa um ponto e fornece informações sobre o seu índice, enquanto as colunas contêm as coordenadas correspondentes a esses pontos. A Tabela 1 mostra como uma possível entrada para o Open3D se comporta, com a ressalva que os valores apresentados são apenas ilustrativos. Tabela 1 – Exemplo de uma matriz de entrada para o Open3D. Coordenadas Índice do Ponto X Y Z 1 7 0 4 2 102 120 40 3 0 95 168 Fonte: Produção do Próprio Autor. 17 2.4 GERAÇÃO DAS CAMADAS INFERIORES Dado que a nuvem de pontos inicialmente representa apenas a superfície do objeto, torna-se necessário criar camadas abaixo dessa superfície para simular o volume das diferentes camadas da pele. No caso específico da pele, é importante não apenas criar essas camadas diretamente abaixo da superfície, mas sim levar em consideração a orientação normal de cada ponto da superfície, pois as camadas da pele, como a epiderme e a derme, estão distribuídas em relação à curvatura e orientação natural da pele, como podemos ver na Figura 3. Figura 3 – Imagem ilustrativa mostrando as camadas da pele. Fonte: Adaptado de Sociedade Brasileira de Dermatologia 2024 Dessa forma, as camadas da pele não podem simplesmente ser criadas diretamente para baixo da superfície. É necessário considerar a orientação de cada ponto da superfície em relação à sua normal. Por exemplo, se a mão estiver deitada, as camadas da pele seguirão para dentro em relação à curvatura natural da mão. Se a mão estiver em pé, as camadas também estarão empilhadas em direção ao interior do tecido, mas a direção será diferente, uma vez que a disposição das camadas segue o vetor normal à superfície. Ao usar a normal como referência, conseguimos simular a disposição das camadas internas da pele de maneira mais realista. O cálculo das normais é realizado montando um plano local para cada ponto da nuvem, utilizando os pontos vizinhos como auxílio. A partir desse plano, a normal é determinada como um vetor perpendicular, representando a orientação da superfície naquele local. No entanto, ao calcular as normais para cada ponto, é possível que a orientação não seja consistente em toda a superfície. Para evitar a propagação de erros durante o deslocamento dos pontos pela normal, é necessário verificar e corrigir a orientação de todas as normais após sua criação. Para isso, comparamos a normal de cada ponto com um plano tangente auxiliar, construído a partir de relações entre o ponto em que estamos iterando com seus pontos vizinhos. Se a normal estiver saindo do ponto em direção ao plano, a orientação está correta. Por outro lado, se a normal estiver saindo do ponto em direção contrária ao plano, a orientação estará errada, e a normal será invertida. Com as normais obtidas e corrigidas, podemos criar uma nova nuvem de pontos simplesmente deslocando cada ponto da nuvem original por um determinado valor em relação à sua normal, valor o qual será inserido pelo usuário. 18 2.5 GERAÇÃO DA VOXEL-GRID Uma voxel grid é o equivalente a um pixel no domínio tridimensional, onde cada região desse espaço 3D é representada por um cubo, denominado de voxel. Cada voxel pode conter informações sobre o objeto que ele contém, seja sua cor, densidade, ou outras propriedades. As vantagens de usar uma voxel grid incluem a simplicidade na estrutura de dados, que facilita a manipulação e processamento por algoritmos computacionais. Essa simplicidade permite que operações como detecção de colisões, simulações físicas e visualizações em 3D sejam realizadas de maneira mais eficiente. Além disso, a representação em voxels torna a modelagem de objetos complexos mais acessível, pois permite que o espaço tridimensional seja tratado como uma grade regular, facilitando cálculos e análises. Figura 4 – Representação de uma voxel grid aleatória. Fonte: Produção do próprio autor. Como a voxel grid é uma das possíveis entradas para os algoritmos de simulação baseados em Monte Carlo, nosso objetivo é converter a nuvem de pontos anterior em uma representação volumétrica adequada na grade de voxels. Para isso, é necessário que vários voxels ocupem o espaço delimitado pelo tamanho da camada especificada, ou seja, o usuário irá entrar com a espessura da camada que ele quer adicionar e o algoritmo terá de criar uma camada com essa espessura, onde o que forma a espessura dessa camada são os próprios voxels. Inicialmente, utilizamos a função do tópico anterior para adicionar novas camadas de pontos da nuvem original até alcançar a espessura desejada pelo usuário, sempre seguindo a normal. Após essa etapa, simplesmente convertemos cada um desses pontos em um voxel, obtendo assim a voxel grid referente à primeira camada. Caso seja requisitado pelo usuário, o processo deve ser repetido para quantas camadas forem necessárias, portanto, o algoritmo deve ter alguma forma de memorizar qual camada está sendo criada e qual a sua posição no espaço, a fim de não sobrepor as camadas criadas anteriormente. 19 2.6 GERAÇÃO DA MALHA A geração de uma malha (mesh) a partir de uma nuvem de pontos é uma abordagem alternativa à utilização de uma voxel grid para representar objetos tridimensionais. Enquanto a voxel grid divide o espaço em unidades cúbicas, a malha representa a superfície do objeto, que por sua vez pode reduzir o tempo de simulação em ordens de grandeza para alguns casos (YAN et al., 2022). Uma malha é uma estrutura tridimensional que representa a superfície de um objeto através da interconexão de faces, vértices e arestas. Essas faces, geralmente triangulares, são formadas por um conjunto de vértices que se conectam, criando uma rede de polígonos adjacentes. Isso permite a criação de superfícies mais complexas do que com uma grade de voxels, que por se basear em cubos não consegue fazer de maneira eficiente. Figura 5 – Representação de uma malha aleatória. Fonte: Produção do próprio autor. Além disso, as malhas tendem a ser computacionalmente mais leves em comparação com uma voxel grid. Isso se deve não apenas à menor estrutura de dados, mas também ao fato de que as GPUs (Unidades de Processamento Gráfico) modernas são otimizadas para a renderização de malhas triangulares. No entanto, é importante notar que, como uma malha representa apenas a superfície, ela não contém informações sobre o interior do objeto, o que torna o procedimento para a representação das diferentes camadas diferente. Dado que a malha trata apenas de superfícies, não é mais necessária a obtenção de várias camadas na nuvem de pontos, basta termos a nuvem original e a superfície inicial da camada seguinte. Desta forma, o algoritmo irá fazer apenas uma malha para cada camada da pele, sendo que a mesma representa o início da dada camada de tecido. A geração da malha a partir de uma nuvem de pontos geralmente parte da ideia de que é possível tratar os pontos da nuvem como vértices e, portanto, traçar segmentos de reta entre os pontos, que por 20 sua vez são as arestas, até conseguir as faces triangulares necessárias. Neste sentido, existem diversos métodos clássicos para a geração das faces que comporão a malha, e diversas variações desses métodos. Nesse projeto, foram explorados dois métodos de criação de malha: a reconstrução pelo método de Poisson e o Ball Pivoting Algorithm (BPA), os quais serão abordados nos tópicos a seguir. 2.6.1 Reconstrução pelo Método de Poisson A reconstrução de superfície por Poisson considera os pontos da nuvem e suas normais como amostras de um campo que representa a superfície dessa nuvem, ou seja, a própria malha. O que queremos é utilizar dessas amostras para obter uma função que representa essa superfície. Figura 6 – Representação 2D da reconstrução por Poisson. (a) Pontos e suas normais (b) Função indicadora Fonte: Adaptado de Kazhdan et al. 2006 A princípio, iremos encontrar uma superfície implícita do objeto por meio de uma função indicadora que nos retorna valores 0 ou 1, seja para dentro ou para fora da superfície. Olhando para a equação de Poisson, o que queremos encontrar é o próprio ϕ que representa a superfície desejada, portanto, para isso, será necessário obter a densidade ρ dada pela seguinte equação: ∇2ϕ = ρ (1) Nesse contexto, ρ é calculado como a divergência do campo vetorial N⃗ . Esse campo por sua vez é definido pelas normais dos pontos de entrada e só é diferente de 0 na vizinhança dos pontos. O método portanto consiste em utilizar essas informações para resolver a equação de Poisson, resultando em uma função indicadora que descreve a superfície, conforme: ∇2ϕ = ∇ · N⃗ (2) Após a obtenção da função indicadora, a superfície é extraída conectando todos os pontos do espaço tridimensional onde a função ϕ, que acabamos de encontrar, tem o valor 0.5, ou seja, o limiar entre as regiões "internas"e "externas"da superfície. Em suma, esse método assume que o gradiente da função indicadora é um campo vetorial que é zero para pontos longe da superfície e igual a normal da superfície voltada para dentro para pontos próximos dessa mesma superfície. Assim, o objetivo é encontrar a função escalar ϕ que o gradiente seja próximo do campo vetorial N⃗ , e para isso é aplicado o operador divergente, que torna o problema em um problema de Poisson. 21 Esse método produz um resultado suave e contínuo, preenchendo lacunas e garantindo que todos os pontos tenham sua relevância, além de ser, também, robusto a ruídos, visto que se baseia em uma otimização global. Para tanto, esse método é computacionalmente pesado, além de precisar de uma nuvem de pontos bastante densa para produzir resultados desejáveis. 2.6.2 Ball-Pivoting Algorithm (BPA) O Ball-Pivoting Algorithm (BPA) é outro método disponível na biblioteca Open3D para gerar malhas a partir de uma nuvem de pontos densa. Dado um conjunto de pontos que descrevem a superfície de um objeto, o BPA utiliza uma bola de raio r que rola sobre a superfície da nuvem. A ideia central do algoritmo é que, ao encontrar três pontos sobre os quais a bola consegue se apoiar simultaneamente, uma face triangular é formada entre esses três pontos. Esse processo então se repete ao longo da superfície, de forma que uma malha seja criada sobre a nuvem de pontos. Figura 7 – Representação 2D do BPA sobre uma nuvem de pontos. Fonte: Adaptado de Bernardini et al. 1999 Uma característica importante do BPA é a dependência da densidade da nuvem de pontos. Para que a bola não atravesse a superfície sem encontrar pontos de suporte, é necessário que a nuvem seja densa o suficiente em relação ao raio da bola, de forma que haja sempre uma nova combinação de três pontos para sustentar a bola, caso contrário, lacunas podem ser deixadas na superfície reconstruída. Além disso, é importante não utilizar um valor grande demais para esse raio, já que isso pode implicar que certos pontos serão ignorados na hora da reconstrução, dependendo do formato da superfície que o algoritmo está reconstruindo. Isso é particularmente crítico em nuvens de pontos que contém ”vales”, ou seja, áreas côncavas profundas. Nestes casos, a bola pode não conseguir se apoiar nos pontos mais profundos do vale, o que acata em uma perda de detalhes na superfície gerada. Portanto, o valor do raio r deve ser selecionado de acordo com a densidade e a escala da nuvem de pontos: um valor muito grande pode gerar uma malha menos detalhada, enquanto um valor muito pequeno pode gerar uma malha com lacunas. 22 3 RESULTADOS E DISCUSSÃO Foi desenvolvido um único arquivo de código, em linguagem Python, para a realização de todas as etapas discutidas anteriormente. A organização desse código em uma única classe permite integra- ção futura com alguma biblioteca pública, oferecendo assim suporte a outros desenvolvedores que trabalham em projetos similares. O código completo está disponível no apêndice deste trabalho. 3.1 IMAGEM 2D CAPTURADA A partir do smartphone modelo S20 FE 5g (Samsung, Yeongtong-gu, Coreia do Sul) foi obtida a foto da mão de um voluntário, apresentada na Figura 8. A resolução da imagem é de 3024x4032 pixels (largura e altura, respectivamente), uma característica do dispositivo utilizado. Com essa alta resolução, espera-se que a nuvem de pontos resultante seja densa, o que contribui para a precisão no modelo 3D. Figura 8 – Imagem RGB da mão de um voluntário. Fonte: Produção do próprio autor. 3.2 PREDIÇÃO DE PROFUNDIDADE Conforme detalhado na Seção 2.2, aplicando-se o algoritmo DPT-Large 512 do projeto MiDaS, versão 3.1, na imagem 2D mostrada na Figura 8, foi possível obter o mapa de profundidades apresentado na Figura 9. A implementação do MiDaS foi realizada utilizando a biblioteca ImPro (do termo 23 em inglês, Image Processing), disponível no respositório github.com/marlongarcia/impro. Após a importação da biblioteca para para o ambiente de desenvolvimento em Python, utilizou-se a classe midas desta biblioteca para efetuar o download automático do modelo DPT-Large 512 diretamente do repositório oficial do MiDaS no GitHub (github.com/isl-org/MiDaS). Aplicando-se o modelo escolhido na imagem 2D mostrada na Figura 8, foi possível obter o mapa de profundidades apresentado na Figura 9. Figura 9 – Profundidade predita pela rede neural profunda DPT-Large 512 do MiDaS versão 3.1. Fonte: Produção do próprio autor. É possível notar que o algoritmo identifica bem o contorno da mão, assim como foi capaz de reconhecer que a geometria arredondada na região superior a mão é na verdade o padrão de reflexão da fonte luminosa projetado na mesa. Contudo, esse algoritmo também apresenta previsões inadequadas em algumas regiões da imagem em questão. Para a área da mesa, esperava-se uma profundidade constante, ou pelo menos com pouca variação, já que a mesa na imagem original é plana e sem deformações. No entanto, é notável que os valores de profundidade na parte superior da mesa diminuem. Isso pode ocorrer pois há um grande foco na região da mão na imagem, o que resulta em uma quantidade limitada de informações sobre o ambiente ao redor. O algoritmo, portanto, pode ter dificuldade em estimar corretamente a profundidade da mesa devido à falta de dados contextuais adequados. Além disso, a estimativa da profundidade na junção entre os dedos médio e anular foi subestimada, uma vez que a escala de cinza entre os dedos não é a mesma da mesa. https://github.com/MarlonGarcia/impro https://github.com/isl-org/MiDaS 24 3.3 NUVEM DE PONTOS E CAMADAS INFERIORES Obtendo-se a imagem do mapa de profundidades, seguiu-se para a geração da nuvem de pontos. Para tanto, conforme descrito na Seção 2.3, foi utilizada a biblioteca Open3D. O primeiro passo foi converter cada pixel das Figuras 8 e 9 em coordenadas de um plano tridimensional. Nesse processo, as informações dos eixos X e Y foram obtidas a partir das posições dos pixels na imagem RGB (Figura 8), enquanto o eixo Z foi determinado pelo tom de cinza no mapa de profundidade (Figura 9). Assim, pixels mais escuros no mapa de profundidade, correspondem a valores menores no eixo Z (mais distantes), enquanto pixels mais claros correspondem a valores maiores (mais próximas). Com base nesses valores, as coordenadas tridimensionais de cada pixel foram agrupadas em uma lista, onde cada elemento contém três valores distintos representando suas coordenadas (X, Y, Z). Cada elemento dessa lista constitui um ponto na nuvem gerada. Inicia-se então uma nuvem de pontos pelo Open3D com o3d.geometry.PointCloud(), para então adicionar a lista previamente comen- tada neste objeto por o3d.utility.Vector3dVector(points), onde ”points” é referente a lista com todos os pontos e suas coordenadas. Aplicando-se este procedimento ao mapa de profundidades mostrado na Figura 9, obteve-se a nuvem de pontos apresentada na Figura 10. Nesta figura, é possível observar as vistas superior e posterior da nuvem, evidenciando as variações de profundidade em relação às dimensões X e Y. Figura 10 – Nuvem de pontos gerada após combinação das imagens. (a) Vista superior da nuvem de pontos (b) Vista posterior da parte dorsal da mão. Fonte: Produção do próprio autor. Considerando-se que a resolução original da imagem é de 3024×4032 pixels, a nuvem de pontos gerada a partir dessa imagem tende a ser bastante densa, implicando em um consumo elevado de memória para representá-la. Para amenizar esse problema, é feito uma redução do número de pontos da nuvem por meio da função downsample_pcd(pcd, fraction). A função adapta o método voxel_down_sample()da biblioteca Open3D para reduzir a quantidade de pontos de uma nuvem 25 a depender de uma fração, que é o parâmetro de entrada. A fração foi escolhida de forma que a superfície ainda seja representada corretamente, mesmo com uma densidade de pontos menor, como visto na Figura 11. Figura 11 – Nuvem de pontos, vista da parte posterior do dorso da mão, após a redução do número de pontos. Fonte: Produção do próprio autor. A fim de facilitar a compreensão do leitor, as análises posteriores irão referenciar a nuvem de pontos representada na Figura 11 como ”nuvem de entrada”. Por fim, para criar as camadas inferiores, obtemos a informação das normais através do método estimate_normals(), disponibilizado na biblioteca Open3D. O método em questão é capaz de estimar as normais de cada ponto da nuvem com base na posição dos pontos vizinhos, conforme é observado na Figura 12. Figura 12 – Nuvem de pontos do mapa de profundidade mostrado na Figura 8, para uma vista posterior da parte dorsal da mão, após a redução do número de pontos e a reorientação das normais. Fonte: Produção do próprio autor. 26 Como podemos ver nessa figura, o algoritmo utilizado gera as normais sem um padrão de orienta- ção. Portanto, para facilitar a implementação da camada inferior, orientamos todas as normais para uma mesma direção com o método orient_normals_consistent_tangent_plane() da biblioteca Open3D. O método em questão traça planos tangenciais em cada um dos pontos seguindo uma mesma orientação e, caso a normal do ponto em que o método está atuando no momento esteja indo em direção oposta ao plano tangencial, a sua normal será invertida, de forma que a orientação das normais de todos os pontos se mantenha consistente com o plano tangencial. Desta maneira ao criar uma nova camada, seja para cima ou para baixo, todos os pontos seguirão a mesma direção. Os pontos com as novas normais podem ser visualizados na Figura 13. Figura 13 – Nuvem de pontos do mapa de profundidade da Figura 8, após a reorientação de suas normais, em uma, para uma vista posterior da parte dorsal da mão. Fonte: Produção do próprio autor. 3.3.1 Camadas Inferiores Com a informação das normais, a criação de uma nova camada é facilitada, que foi obtida gerando- se uma nuvem de pontos a um determinado deslocamento da nuvem de entrada. Para isto, foram utilizadas as funções create_layer_at_offset e create_layers_until_offset. As funções em questão recebem como argumento de entrada a nuvem de pontos inicial e a distância, em pixel, da posição da nova nuvem de pontos em relação a nuvem inicial. Para o deslocamento, é multiplicado o valor da distancia com o vetor normal de cada um dos pontos da nuvem, para que então seja somado com a posição inicial da nuvem. Desta forma, cada um dos pontos irá se deslocar a uma determinada distância sobre o seu próprio vetor normal, para que então termine em uma nova posição. Na Figura 14 e na Figura 15 é possível ver a nuvem de entrada com uma outra nuvem abaixo dela, a um determinado deslocamento, e também o cenário onde várias nuvens de pontos são criadas entre a nuvem de entrada e o deslocamento inserido pelo usuário. A distância escolhida foi de 30 pixels, de forma a se tornar claro a distância coberta pelo deslocamento. 27 Figura 14 – Nuvem de pontos do mapa de profundidade para uma vista posterior da parte dorsal da mão mostrando-se a camada gerada em uma dada distância. Fonte: Produção do próprio autor. Figura 15 – Nuvem de pontos do mapa de profundidade para uma vista posterior da parte dorsal da mão preenchida até o deslocamento. Fonte: Produção do próprio autor. O deslocamento requerido é definido pelo usuário, e armazenado pela classe do Python no método init, para posteriormente ser utilizado pela função responsável pela criação das camadas. Dessa forma, enquanto a função está em execução, garante-se que cada nova camada seja criada abaixo da última, mantendo a sequência adequada. 28 3.4 GERAÇÃO DA GRADE DE VOXELS Nesta etapa, transformamos a nuvem de pontos obtida no passo anterior em uma grade de vo- xels. Esta transformação nada mais é do que a tradução dos pontos no equivalente em pixels no plano tridimensional (os voxels). Para isto, foi utilizado o método o3d.geometry.VoxelGrid. create_from_point_cloud(pcd, voxel_size) da biblioteca Open3D para converter cada ponto da nuvem de entrada em um cubo volumétrico, onde pcd é referente a nuvem de entrada e voxel_size é referente ao tamanho da aresta do cubo. A Figura 16 mostra a nuvem de pontos de entrada tranformada em uma grade de voxel. Já a Figura 17, mostra a mesma transformação apresentada na Figura 16, porém mostrando também as camadas inferiores, onde a camada verde é a primeira camada, e a camada lilás é a segunda camada. Figura 16 – Grade de voxels para a nuvem de entrada na vista posterior da parte dorsal da mão. Fonte: Produção do próprio autor. Devido não apenas à alta densidade de pontos, mas também à natureza mais complexa dos voxels, trabalhar com esses dados sem um pré-processamento adequado poderia causar diversos problemas, especialmente pelo alto consumo de memória necessário para manipulá-los. Além disso, o resultado apresenta uma aparência bastante quadriculada, o que não representa fielmente a superfície de um tecido biológico, podendo comprometer a precisão de simulações que utilizam esse modelo. Para mitigar esse problema, é possível aumentar a resolução da nuvem de pontos, o que reduziria a aparência quadriculada da superfície, mas isso implicaria em um aumento significativo do custo computacional e do tempo de processamento, já que aumentaria ainda mais a densidade dos pontos e, consequentemente, das camadas. Isso ocorre, principalmente por conta da natureza cúbica dos voxels, ou seja, um aumento linear das dimensões do cubo (que é o conjunto 3D dos voxels) provoca um aumento cúbico no número de voxels deste elemento, e consequentemente um aumento significativo do número de tarefas a serem computadas. 29 Figura 17 – Grade de voxels para múltiplas camadas simultâneas na vista posterior da parte dorsal da mão. Fonte: Produção do próprio autor. 3.5 GERAÇÃO DAS MALHAS Para a geração das malhas, partimos novamente da nuvem de pontos e processamos os dados pelo algoritmo de reconstrução por Poisson ou pelo Ball-Pivoting Algorithm (BPA). A Figura 18 apresenta o resultado final do processo de reconstrução, utilizando como parâmetros de entrada da função de reconstrução por Poisson, create_from_point_cloud_poisson, os valores 9 e 1,3 para a depth e a escala, respectivamente, valores que em geral retornam resultados suaves. Como esperado, o algoritmo de reconstrução por Poisson cria uma superfície contínua e suave na área onde os pontos estão distribuídos. No entanto, ele também pode gerar superfícies indesejadas fora da região de interesse. Para corrigir isso, aplicamos um filtro para remover essas superfícies fora do volume útil. Considerando que a nuvem de pontos está contida dentro de um cubo tridimensional, basta eliminar todas as informações externas a esse cubo. A Figura 19 mostra a malha após o processo de filtragem. 30 Figura 18 – Vista posterior da parte dorsal da mão na malha por reconstrução de Poisson. Fonte: Produção do próprio autor. Figura 19 – Vista posterior da parte dorsal da mão na malha filtrada por reconstrução de Poisson. Fonte: Produção do próprio autor. Caso seja necessário, também é possível ajustar os parâmetros de entrada da função de reconstrução por Poisson para obter diferentes formas de malha, a depender da acurácia requisitada pelo usuário, 31 como podemos ver na Figura 20. Mudando apenas o parâmetro depth da função para a confecção dessa figura, e já temos temos resultados drasticamente diferentes na representação, havendo um compromisso entre a qualidade da reconstrução e o tempo de execução dessa função. Valores muito altos para a depth nos retornam resultados bastante suaves, mas o tempo de execução aumenta exponencialmente. Para o caso contrário, onde o valor de depth é muito pequeno, a execução é bastante rápida, mas a representação do objeto não se torna muito clara. Figura 20 – Malhas por reconstrução de Poisson com diferentes parâmetros de entrada para (a) depth com valor de 5, e (b) depth com valor de 7. (a) (b) Fonte: Produção do próprio autor. Já para a reconstrução pelo BPA, é necessário obter o raio da bola que vai percorrer a superfície. Para isso, foi calculado a distância média entre os pontos vizinhos em toda a nuvem de entrada e multiplicado o resultado por 10, de forma que a esfera não ultrapasse a malha em regiões menos densas. Esse valor é então utilizado como o raio da esfera. O resultado do BPA para esse valor é representado na Figura 21 Figura 21 – Vista posterior da parte dorsal da mão na malha por BPA com um raio grande. Fonte: Produção do próprio autor. Podemos notar que mesmo com um raio grande ainda temos descontinuidade. Isso ocorre pois a distribuição dos pontos da nuvem de entrada não segue um padrão contínuo nas diferentes coordenadas 32 após ser realizado o downsample da mesma. Portanto, em determinadas regiões teremos lacunas maiores do que o comum, que por sua vez podem causar problemas na utilização deste método. Além disso, em regiões como entre os dedos, o algoritmo pode falhar em capturar adequadamente o contorno da superfície devido ao raio da esfera. Ajustar o tamanho do raio permite capturar detalhes em regiões estreitas, mas isso pode introduzir descontinuidades em outras partes da malha. A Figura 22 ilustra essa situação com um raio menor, onde é possível observar o impacto na continuidade da superfície. Obter então o valor ótimo desse raio torna-se uma tarefa bastante difícil, especialmente porque a nuvem de pontos é processada com objetivos variados, impondo diferentes restrições ao valor do raio para atender a essas demandas de forma equilibrada. Figura 22 – Vista posterior da parte dorsal da mão da malha por BPA com um raio pequeno. Fonte: Produção do próprio autor. Por fim, foi realizada a construção da malha juntamente com as camadas inferiores utilizando o algoritmo de reconstrução por Poisson, como podemos ver na Figura 23. Para isto, basta rodar a função create_from_point_cloud_poisson para cada uma das camadas. 33 Figura 23 – Vista posterior da parte dorsal da mão na malha e suas camadas inferiores por reconstrução de Poisson. Fonte: Produção do próprio autor. Um caso particular das camadas inferiores, seja por Poisson ou por BPA, é quando a camada inferior está muito abaixo da camada inicial. O que acontece aqui é que os pontos da nuvem inferior começam a se aglomerar em regiões que possuem curvas e, em casos extremos, isso pode afetar o formato final da malha. Analisando o caso particular de Poisson, teremos um peso maior no cálculo da malha para essas regiões mais densas, que por sua vez irá causar lóbulos nas bordas. O problema em questão é representado na Figura 24, onde se mostra somente a camada inferior, contendo lóbulos nas regiões de borda da mão. Figura 24 – Aglomeração dos pontos quando a camada inferior tem um deslocamento muito grande com a reconstrução por Poisson para a vista posterior do dorso da mão apenas da camada inferior. Fonte: Produção do próprio autor. 34 Para resolver esse problema, foi adicionado um filtro na nuvem de pontos de entrada que checa a proximidade dos pontos e, caso múltiplos pontos estejam muito próximos entre si, eles serão filtrados, conforme: # Get points from the point cloud points = np.asarray(pcd.points) # Find nearby neighbors and compute their mean distance tree = cKDTree(points) mean_dist = np.mean(tree.query(points, k=5)[0][:, 1:4]) # Mean distance to the nearest neighbor # Create a mask to filter out close points mask = np.ones(len(points), dtype=bool) # This keeps track of which points to retain # Iterate over each point to find nearby points and mark them for deletion for i in range(len(points)): if not mask[i]: # Skip points already marked for deletion continue idx = tree.query_ball_point(points[i], mean_dist) # Find nearby points within the mean distance radius if i in idx: idx.remove(i) # Exclude the current point ‘i‘ from deletion mask[idx] = False # Mark all nearby points (except ‘i‘) for deletion # Filter the points to keep only the ones marked as True in the mask points = points[mask] # Update the point cloud with the remaining points pcd.points = o3d.utility.Vector3dVector(points) Como pode ser visto no código acima, o algoritmo checa para a nuvem inteira se algum ponto tem uma distância entre o seu vizinho menor do que a distância média que cada um dos pontos tem para o seu ponto vizinho e, se isso for verdade, o ponto é marcado para deleção. Essa deleção é feita por um 35 array do tamanho do número de pontos da nuvem e, portanto, contém todos os índices dos pontos dessa nuvem. Inicialmente, todos os pontos existirão e, portanto, serão verdadeiros, mas, caso a condição previamente comentada seja satisfeita, o ponto em questão se torna falso e então será removido do array points. Por fim, o array points será inserido novamente no objeto do Open3D, atualizando então a nuvem de pontos. Na Figura 25, é representado as malhas das camadas inferiores após serem filtradas pelo algoritmo, junto da camada inicial. Figura 25 – Malhas para a vista posterior do dorso da mão por reconstrução de Poisson dos pontos aglomerados filtrados. Fonte: Produção do próprio autor. 36 4 CONCLUSÃO A organização do projeto em um fluxo de trabalho permitiu, a princípio, implementar um algoritmo em Python que executasse cada um de seus passos de forma automatizada e, por consequência, tornar a abordagem bastante prática. Para essa implementação, foi fundamental o uso da biblioteca Open3D, visto que a mesma possui diversos métodos para auxiliar na manipulação de nuvens de pontos, como a estimação das normais, informação importante para a geração do modelo tridimensional. Em geral, a biblioteca proporcionou uma implementação bastante prática do projeto, assim como um resultado final rápido e preciso. Para a acurácia dos modelos gerados, a qualidade da reconstrução 3D depende diretamente da precisão do modelo de predição de profundidade utilizado. Conforme os algoritmos de predição por inteligência artificial vão se desenvolvendo, a precisão dos modelos 3D que se baseiam nestes algoritmos também tenderá a crescer. Portanto, é esperado que essa metodologia se torne cada vez mais importante no futuro, à medida que o avanço desses algoritmos possibilitará aplicações mais precisas e eficientes, ampliando o alcance de suas utilizações em diversas áreas, como a medicina. Do ponto de vista da representação 3D de regiões anatômicas, a escolha entre voxel e malhas depende do resultado desejado de cada aplicação. Embora os voxels ofereçam uma maneira eficiente de representar superfícies e volumes, eles geralmente retornam modelos bastante quadriculados, que por sua vez pode comprometer a fidelidade do modelo. Em contrapartida, as malhas proporcionam uma representação suave da superfície e, embora ainda contenha imperfeições, é mais fácil de obter resultados precisos. Dito isso, é importante enfatizar o quanto a densidade da nuvem de pontos influencia a precisão dos dois métodos. Nuvens com baixa densidade de pontos podem resultar em superfícies irregulares, independente do método utilizado, porém, nuvens com uma alta densidade de pontos podem levar a um aumento significativo no tempo de processamento e no consumo de memória. Para projetos futuros, a otimização dos processos e a redução da complexidade computacional ao lidar com um grande volume de dados são bastante promissores, visto que aqui foi explorado o tema em regiões anatômicas específicas como a mão, mas digamos que o modelo de um corpo inteiro seja requisitado. Para isto seria necessário uma nuvem de pontos mais densa em níveis de grandeza da que foi utilizada. Portanto, métodos que são capazes de manipular essas nuvens ou redes neurais específicas para este contexto se tornam bastante desejadas. 37 REFERÊNCIAS BERNARDINI, F. et al. The ball-pivoting algorithm for surface reconstruction. IEEE transactions on visualization and computer graphics, IEEE, v. 5, n. 4, p. 349–359, 1999. CHANDRASEKHAR, S. Radiative transfer. [S.l.]: Courier Corporation, 2013. FANG, Q.; BOAS, D. A. Monte carlo simulation of photon migration in 3d turbid media accelerated by graphics processing units. Optics express, Optica Publishing Group, v. 17, n. 22, p. 20178–20190, 2009. FOLEY, J. D. Computer graphics: principles and practice. [S.l.]: Addison-Wesley Professional, 1996. v. 12110. GARCIA, M. R. impro: Image Processing Functions. 2024. Disponível em: . GENTLE, J. E. Computational statistics. [S.l.]: Springer, 2009. v. 308. HARRIS, C. R. et al. Array programming with numpy. Nature, Nature Publishing Group UK London, v. 585, n. 7825, p. 357–362, 2020. KAZHDAN, M.; BOLITHO, M.; HOPPE, H. Poisson surface reconstruction. In: Proceedings of the fourth Eurographics symposium on Geometry processing. [S.l.: s.n.], 2006. v. 7, n. 4. KAZHDAN, M.; HOPPE, H. Screened poisson surface reconstruction. ACM Transactions on Graphics (ToG), ACM New York, NY, USA, v. 32, n. 3, p. 1–13, 2013. RANFTL, R. et al. Towards robust monocular depth estimation: Mixing datasets for zero-shot cross-dataset transfer. IEEE transactions on pattern analysis and machine intelligence, IEEE, v. 44, n. 3, p. 1623–1637, 2020. (SBD), S. B. de D. Conheça a pele. 2024. Disponível em: . VIRTANEN, P. et al. Scipy 1.0: Fundamental algorithms for scientific computing in python. Nature Methods, v. 17, n. 3, p. 261–272, 2020. YAN, S. et al. Graphics-processing-unit-accelerated monte carlo simulation of polarized light in complex three-dimensional media. Journal of Biomedical Optics, Society of Photo-Optical Instrumentation Engineers, v. 27, n. 8, p. 083015–083015, 2022. ZHOU, Q.-Y.; PARK, J.; KOLTUN, V. Open3d: A modern library for 3d data processing. arXiv preprint arXiv:1801.09847, 2018. https://github.com/marlongarcia/impro https://github.com/marlongarcia/impro https://www.sbd.org.br/cuidados/conheca-a-pele/ https://www.sbd.org.br/cuidados/conheca-a-pele/ 38 APÊNDICE A – CÓDIGO UTILIZADO PARA MANIPULAR A NUVEM DE PONTOS E GERAR O MODELO 3D import numpy as np import open3d as o3d import matplotlib.pyplot as plt from scipy.spatial import cKDTree class PointCloudProcessor: def __init__(self): """ Initialize class attributes to track thresholds when creating point cloud layers. """ self.layer_until_current_threshold = 0 self.layer_at_current_threshold = 0 @staticmethod def load_and_create_pcd(image_path, depth_map_path): """ Load an image and its depth map to create a point cloud. Parameters: image_path (str): Path to the RGB image. depth_map_path (str): Path to the depth map corresponding to the RGB image. Returns: pcd (open3d.geometry.PointCloud): Generated point cloud object with colors. """ # Load image and depth map image = plt.imread(image_path) depth_map = np.array(plt.imread(depth_map_path)) # Get image dimensions height, width = depth_map.shape 39 # Create a mesh grid for pixel coordinates x, y = np.meshgrid(np.arange(width), np.arange(height)) # Flatten the mesh grid and depth map x_flat = x.flatten() y_flat = y.flatten() z_flat = depth_map.flatten() # Stack coordinates to create the point cloud points = np.vstack((x_flat, -y_flat, z_flat)).transpose() # Normalize the colors (0 ~ 1) colors = image.reshape(-1, 3) / 255.0 # This gives me an array where each element has 3 values, # corresponding to its RGB color scheme # Create an Open3D PointCloud object pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(points) # Assign 3 D points pcd.colors = o3d.utility.Vector3dVector(colors) # Assign corresponding colors return pcd @staticmethod def downsample_pcd(pcd, fraction): """ Downsample the point cloud to reduce the number of points. Parameters: pcd (open3d.geometry.PointCloud): Input point cloud. fraction (float): Fraction of points to retain (value between 0 and 1). Returns: dpcd (open3d.geometry.PointCloud): Downsampled point cloud. """ # Calculate target number of points as a fraction of the original 40 original_num_points = len(pcd.points) target_num_points = int(original_num_points * fraction) # Downsample the point cloud down_size = np.sqrt(original_num_points / target_num_points ) dpcd = pcd.voxel_down_sample(down_size) return dpcd @staticmethod def estimate_normals(pcd): """ Estimate normals for the point cloud. Parameters: pcd (open3d.geometry.PointCloud): Input point cloud. Returns: pcd (open3d.geometry.PointCloud): Point cloud with estimated normals. """ # Estimate the normals pcd.estimate_normals() # Ensure normal vectors point to the same direction pcd.orient_normals_consistent_tangent_plane(k=15) return pcd def create_layers_until_offset(self, pcd, offset_distance): """ Create multiple layers of points by offsetting along the normals up to the specified offset distance. Parameters: pcd (open3d.geometry.PointCloud): Input point cloud. offset_distance (int): Distance to offset along the normals for creating new layers. 41 Returns: new_pcd (open3d.geometry.PointCloud): Point cloud representing the newly created layers. """ # Get points and normals points = np.asarray(pcd.points) normals = np.asarray(pcd.normals) new_points = [] # List to store new layers of points # Calculate new layers of points for i in range(self.layer_until_current_threshold, self. layer_until_current_threshold + offset_distance): layer_i = points + i * normals # Offset the points by distance ‘i * normals‘ new_points.append(layer_i) new_points = np.vstack(new_points) # Stack all new layers into a single array # Create a new point cloud for the new layer new_pcd = o3d.geometry.PointCloud() new_pcd.points = o3d.utility.Vector3dVector(new_points) # Assign random colors to the new point cloud, for posterior visualization random_color = np.random.rand(1, 3) color_array = np.tile(random_color, (len(new_pcd.points), 1)) new_pcd.colors = o3d.utility.Vector3dVector(color_array) # Update the layer creation threshold for future use self.layer_until_current_threshold += offset_distance return new_pcd def create_layer_at_offset(self, pcd, offset_distance): """ Create a single new layer of points at the specified offset distance. 42 Parameters: pcd (open3d.geometry.PointCloud): Input point cloud. offset_distance (int): Distance to offset along the normals to create a new layer. Returns: new_pcd (open3d.geometry.PointCloud): Point cloud representing the new layer at the offset distance. """ # Get points and normals points = np.asarray(pcd.points) normals = np.asarray(pcd.normals) # Calculate the new layer of points at the offset distance layer_i = points + (self.layer_at_current_threshold + offset_distance) * normals # Create a new point cloud for the new layer new_pcd = o3d.geometry.PointCloud() new_pcd.points = o3d.utility.Vector3dVector(layer_i) # Assign random colors to the new point cloud, for posterior visualization random_color = np.random.rand(1, 3) color_array = np.tile(random_color, (len(new_pcd.points), 1)) new_pcd.colors = o3d.utility.Vector3dVector(color_array) # Update the threshold for future layer creation self.layer_at_current_threshold += offset_distance return new_pcd @staticmethod def create_mesh(pcd, method=’Poisson’): """ Create a 3D mesh from a point cloud using either Poisson reconstruction or BPA. This function also filters 43 points that are too close to each other, which may be relevant to not lose the structures original shape when the previously used offset is too big. Parameters: pcd (open3d.geometry.PointCloud): Input point cloud. method (str): Method to use for mesh creation. Can be either ’Poisson’ or ’BPA’. Default is ’Poisson’. Returns: p_mesh_crop (open3d.geometry.TriangleMesh): Cropped mesh generated from the point cloud. """ # Check if method is valid if method not in [’Poisson’, ’BPA’]: raise ValueError("Method must be either ’Poisson’ or ’ BPA’") # Get points from the point cloud points = np.asarray(pcd.points) # Find nearby neighbors and compute their mean distance tree = cKDTree(points) mean_dist = np.mean(tree.query(points, k=5)[0][:, 1:4]) # Mean distance to the nearest neighbor # Create a mask to filter out close points mask = np.ones(len(points), dtype=bool) # This keeps track of which points to retain # Iterate over each point to find nearby points and mark them for deletion for i in range(len(points)): if not mask[i]: # Skip points already marked for deletion continue idx = tree.query_ball_point(points[i], mean_dist) # Find nearby points within the mean distance radius 44 if i in idx: idx.remove(i) # Exclude the current point ‘i‘ from deletion mask[idx] = False # Mark all nearby points (except ‘i ‘) for deletion # Filter the points to keep only the ones marked as True in the mask points = points[mask] # Update the point cloud with the remaining points pcd.points = o3d.utility.Vector3dVector(points) # Estimate the normals pcd.estimate_normals() # Ensure normal vectors point to the same direction pcd.orient_normals_consistent_tangent_plane(k=15) # Create the mesh using Poisson reconstruction if method == ’Poisson’: poi_mesh = \ o3d.geometry.TriangleMesh. create_from_point_cloud_poisson(pcd, depth=7, width =0, scale=1.3, linear_fit=False)[0] # Crop the mesh using an axis-aligned bounding box bbox = pcd.get_axis_aligned_bounding_box() # The original image is a rectangle, so a box is used p_mesh_crop = poi_mesh.crop(bbox) # This is to filter out parts of the mesh that are unnecessary # Assign random colors to the mesh vertices, for posterior visualization random_color = np.random.rand(1, 3) vertex_colors = np.tile(random_color, (len(p_mesh_crop. vertices), 1)) p_mesh_crop.vertex_colors = o3d.utility.Vector3dVector( vertex_colors) 45 final_mesh = p_mesh_crop # Create the mesh using BPA elif method == ’BPA’: radius = mean_dist * 10 # Ball radius bpa_mesh = o3d.geometry.TriangleMesh. create_from_point_cloud_ball_pivoting(pcd, o3d. utility.DoubleVector([radius, radius*2])) # Assign random colors to the mesh vertices, for posterior visualization random_color = np.random.rand(1, 3) vertex_colors = np.tile(random_color, (len(bpa_mesh. vertices), 1)) bpa_mesh.vertex_colors = o3d.utility.Vector3dVector( vertex_colors) final_mesh = bpa_mesh return final_mesh if __name__ == ’__main__’: # Example usage image_path = ’C:/Users/moi/2dto3d/2dinp/2409.jpg’ depth_map_path = ’C:/Users/moi/2dto3d/depth/2409.jpg’ fraction = 0.01 offset_distance1 = 5 offset_distance2 = 10 processor = PointCloudProcessor() pcd = processor.load_and_create_pcd(image_path, depth_map_path) dpcd = processor.downsample_pcd(pcd, fraction) dpcd = processor.estimate_normals(dpcd) new_pcd_fill1 = processor.create_layers_until_offset(dpcd, offset_distance1) new_pcd_fill2 = processor.create_layers_until_offset(dpcd, offset_distance2) 46 new_pcd1 = processor.create_layer_at_offset(dpcd, offset_distance1) new_pcd2 = processor.create_layer_at_offset(dpcd, offset_distance2) mesh = processor.create_mesh(dpcd) mesh1 = processor.create_mesh(new_pcd1,) mesh2 = processor.create_mesh(new_pcd2) # Visualize o3d.visualization.draw_geometries([pcd]) o3d.visualization.draw_geometries([dpcd]) o3d.visualization.draw_geometries([dpcd], point_show_normal= True) o3d.visualization.draw_geometries([new_pcd_fill1, new_pcd_fill2 ]) o3d.visualization.draw_geometries([new_pcd1, new_pcd2]) o3d.visualization.draw_geometries([mesh], mesh_show_back_face= True) o3d.visualization.draw_geometries([mesh, mesh1, mesh2], mesh_show_back_face=True) # Voxelization voxel_size = 20 voxel_grid1 = o3d.geometry.VoxelGrid.create_from_point_cloud( new_pcd_fill1, voxel_size) voxel_grid2 = o3d.geometry.VoxelGrid.create_from_point_cloud( new_pcd_fill2, voxel_size) o3d.visualization.draw_geometries([voxel_grid1]) o3d.visualization.draw_geometries([voxel_grid1, voxel_grid2]) Folha de rosto Epígrafe Resumo Abstract Lista de abreviaturas e siglas Lista de símbolos Introdução Justificativa Objetivo Geral Objetivos Específicos Metodologia Aquisição da Imagem 2D Predição da Imagem de Profundidade Geração da Nuvem de Pontos Geração das Camadas Inferiores Geração da Voxel-Grid Geração da Malha Reconstrução pelo Método de Poisson Ball-Pivoting Algorithm (BPA) Resultados e Discussão Imagem 2D Capturada Predição de Profundidade Nuvem de Pontos e Camadas Inferiores Camadas Inferiores Geração da Grade de Voxels Geração das Malhas Conclusão Referências Código utilizado para manipular a nuvem de pontos e gerar o modelo 3D