[Generación y reconstrucción 3D] SSDNeRF: generación 3D y reconstrucción de NeRF de difusión de una sola etapa

Índice de contenidos de los artículos de la serie.

Título : NeRF de difusión de una sola etapa: un enfoque unificado para la generación y reconstrucción 3D
Documento : https://arxiv.org/pdf/2304.06714.pdf
Tarea : Generación 3D incondicional (como generar diferentes automóviles a partir del ruido, etc.), única Ver
organización de generación 3D : Hansheng Chen,1,* Jiatao Gu,2 Anpei Chen, Tongji, Apple, Universidad de California
Código : https://github.com/Lakonik/SSDNeRF


Resumen

  Tareas de síntesis de imágenes con reconocimiento 3D, incluida la generación de escenas y la síntesis de nuevas vistas basadas en imágenes. Este artículo propone SSDNeRF, que utiliza un modelo de difusión para aprender antecedentes generalizables para campos de radiación neuronal (NeRF) a partir de imágenes de múltiples vistas de diferentes objetos . Estudios anteriores utilizaron un enfoque de dos etapas, basándose en NeRF preentrenado como datos reales para entrenar modelos de difusión. Por el contrario, SSDNeRF, como paradigma de entrenamiento de extremo a extremo de una sola etapa, optimiza conjuntamente la decodificación automática y los modelos de difusión latente de NeRF para lograr una reconstrucción 3D simultánea y un aprendizaje previo (incluso incluyendo vistas dispersas). Durante las pruebas, la difusión previa se puede generar directamente de forma incondicional o combinarse con observaciones arbitrarias de objetos invisibles para realizar la reconstrucción NeRF. SSDNeRF muestra resultados sólidos comparables o mejores que los métodos de tareas específicas en términos de generación incondicional y reconstrucción 3D de vista única/dispersa.

I. Introducción

  Con el desarrollo de la representación neuronal y los modelos generativos , se han desarrollado algoritmos únicos para la generación de contenido 3D (como la reconstrucción 3D de vista única o múltiple y la generación de contenido 3D), pero falta un marco integral para conectar tecnologías multitarea. Neural Radiation Fields (NeRF) resuelve el problema de renderizado inverso mediante un ajuste de superresolución, mostrando resultados impresionantes en la síntesis de nuevas vistas (pero solo aplicable a vistas densas y difícil de generalizar a observaciones escasas). Por el contrario, muchos métodos de reconstrucción 3D de vista dispersa [pixelNeRF, Mvsnerf, ViT NeRF] se basan en codificadores de alimentación directa de imágenes a 3D, pero no pueden manejar la ambigüedad de las áreas ocluidas y no pueden generar imágenes claras. En términos de generación incondicional, las redes generativas adversarias (GAN) con reconocimiento 3D [34, 5, 18, 14] están parcialmente limitadas en el uso de discriminadores de una sola imagen, que no pueden permitir que las relaciones entre vistas se aprendan de manera efectiva a partir de múltiples imágenes. ver datos .

  En trabajos anteriores [como NeRF basado en puntajes, Gaudí, difusión triplana, Diffrf] , se han aplicado ldm similares a la generación 2D y 3D, pero generalmente requieren dos etapas de entrenamiento. La primera etapa excluye el modelo de difusión y solo pre- Trenes VAE (Autoencoder variable) o decodificador automático . Sin embargo, en el caso del nerf de difusión, creemos que el entrenamiento de dos etapas induce patrones de ruido y artefactos en el código latente (especialmente con vistas dispersas) debido a la incertidumbre de la representación inversa, lo que impide que el modelo de difusión aprenda efectivamente el potencial limpio. colector. Para resolver este problema,

  Este artículo propone un marco unificado SSDNeRF (Single Stage Diffusion NeRF), que utiliza un modelo de difusión latente tridimensional (LDM) para modelar la escena de generación de código latente antes y aprende un previo tridimensional generalizable a partir de imágenes de múltiples vistas para manejar. varios Tareas tridimensionales (Fig. 1). El paradigma de capacitación de una sola etapa permite el aprendizaje de un extremo a otro de los pesos de Difusión y NeRF. Este enfoque combina sesgos generados y representados de manera consistente para mejorar el rendimiento general y permite el entrenamiento con datos de vista dispersos. Además, el previo tridimensional aprendido del modelo de difusión incondicional se puede utilizar para el muestreo flexible de escenas de prueba de observaciones arbitrarias.

Insertar descripción de la imagen aquí

Las principales aportaciones son las siguientes:

1. Proponer SSDNeRF, un método unificado de generación 3D incondicional y reconstrucción 3D basado en imágenes 2. Como nuevo paradigma de entrenamiento de una sola etapa, SSDNeRF aprende conjuntamente modelos de reconstrucción y difusión NeRF
a partir de imágenes de múltiples vistas de una gran cantidad de objetos. (incluso solo hay tres vistas por escena) 3. Se propone un esquema de muestreo de ajuste de guía para utilizar la difusión aprendida antes de realizar la reconstrucción 3D a partir de cualquier número de vistas en el momento de la prueba.

2. Trabajo relacionado

2.1. GAN 3D

Las GAN se han utilizado con éxito para la generación tridimensional   mediante la integración de renderizado basado en proyecciones en el generador . Se han explorado previamente varias representaciones 3D (nubes de puntos, cuboides, esferas [27] y vóxeles); más recientemente NeRF y campos de características con renderizadores de volumen [34, 18, 5] y con renderizadores de malla de superficie diferenciable [14]. Todos los métodos anteriores están entrenados con discriminadores de imágenes 2D y no pueden razonar sobre las relaciones entre vistas, lo que los hace muy dependientes del sesgo del modelo de consistencia 3D y no pueden utilizar de manera efectiva datos de vistas múltiples para aprender geometrías complejas y diversas . El gan tridimensional se utiliza principalmente para la generación incondicional. Aunque la reconstrucción 3D de imágenes se puede completar mediante la inversión GAN [12], no se puede garantizar la autenticidad debido a la capacidad de expresión potencial limitada, como se indica en el artículo [Diffrf, RenderDiffusion].

2.2 Regresión y generación condicionadas por vista

  La reconstrucción 3D escasa se puede resolver haciendo una regresión de nuevas vistas de la imagen de entrada . Varias arquitecturas propuestas [8, 59, 28, 61] codifican la imagen en características de volumen que se proyectan a una vista de destino supervisada a través de la representación de volumen . Sin embargo, no pueden razonar sobre la ambigüedad y producir contenido diverso y significativo, lo que a menudo conduce a resultados ambiguos . Por el contrario, los modelos generativos condicionados por imágenes son mejores para sintetizar contenido diferente : 3DiM [57] propone generar nuevas vistas a partir de un modelo de difusión de imágenes condicionado por vistas , pero este modelo carece de sesgo de consistencia tridimensional. [Sparsefusion, Nerdi, Nerfdiff] Extraiga la parte anterior del modelo de difusión 2D condicionado por imagen en nerf para fortalecer las restricciones 3D . Estos métodos son paralelos a nuestra trayectoria en el sentido de que modelan relaciones de visión cruzada en el espacio de la imagen, mientras que nuestro modelo es de naturaleza tridimensional.

2.3 Decodificador automático y Difusión NeRF

  El esquema de ajuste de escena única de NeRF se puede generalizar a múltiples escenas compartiendo algunos parámetros en todas las escenas y tratando el resto como códigos de escena separados [7]. Por lo tanto, NeRF multiescena se puede entrenar como un decodificador automático [35], aprendiendo conjuntamente
bancos de códigos y compartiendo pesos de decodificador
. Con una arquitectura adecuada, los códigos de escena pueden verse como antecedentes gaussianos latentes, lo que permite la finalización e incluso la generación 3D [24, 48, 38]. Sin embargo, al igual que las GAN 3D, estas señales latentes no son lo suficientemente expresivas como para reconstruir fielmente objetos detallados . [Gaudí, De los datos a la functa, Rodin, etc.] Decodificadores automáticos ordinarios mejorados con antecedentes de difusión latente. DiffRF [32] utiliza DIffusion antes de realizar la finalización 3D. Estos métodos entrenan modelos de Auto-Decodificador y Difusión de forma independiente en dos etapas, lo cual es limitado.

2.4 NeRF como autodecodificador

  Dado un conjunto de imágenes bidimensionales de una escena y los parámetros correspondientes de la cámara, el campo de luz de la escena se puede ajustar en un espacio tridimensional, expresado como una función óptica y ψ (r) (donde r se usa para parametrizar el punto final y la suma de un rayo en la dirección del espacio mundial, ψ representa el parámetro del modelo, y∈R 3 + representa el formato RGB de la luz recibida. NeRF representa el campo de luz como radiación integrada a lo largo de la luz que pasa a través del volumen tridimensional (ver mi blog para principios específicos: [Reconstrucción 3D] Principio NeRF + Explicación del código) .

  NeRF también se puede generalizar a entornos de múltiples escenarios compartiendo algunos parámetros del modelo entre todos los escenarios [7]. Dadas múltiples escenas { yijgt , rijgt }, donde yijgt ,rijgt es el j-ésimo par de píxeles y rayos RGB de la i-ésima escena , cada código de escena se puede optimizar minimizando la pérdida de renderizado L2 {x i } y el parámetro compartido ψ :

Insertar descripción de la imagen aquí
Con este objetivo en mente, el modelo se entrena como un decodificador automático. El código de escena {x i } puede interpretarse como un código latente. Bajo el supuesto de distribuciones gaussianas independientes, la función óptica puede verse como la forma de un decodificador:
Insertar descripción de la imagen aquí

2.5 Desafíos en generación y reconstrucción

  Se puede generar incondicionalmente un autodecodificador con pesos de entrenamiento ψ decodificando códigos latentes extraídos de antecedentes gaussianos. Sin embargo, para asegurar la continuidad de la generación, se requiere un espacio latente de baja dimensión y un decodificador complejo , lo que aumenta la dificultad de reconstruir verdaderamente cualquier vista en la optimización.

2.6 Modelo de difusión potencial

Los modelos de difusión latente (LDM) aprenden una distribución previa p ϕ (x)   en un espacio latente con parámetros ϕ , lo que permite representaciones latentes más expresivas (como cuadrículas de imágenes 2D). En términos de generación de campos neuronales, trabajos anteriores [2, 32, 13 , 47] adoptaron un esquema de entrenamiento de dos etapas: primero entrene un decodificador automático para obtener el código latente xi de cada escena , y luego entrene como datos reales LDM . LDM inyecta ruido gaussiano ϵ∼N (0, I) en x i , generando un código de ruido x i (t) en el paso de tiempo de difusión t de la función empírica de programación de ruido α (t) , σ (t) : = α (t ) x i(t) ϵ . Luego, una red de eliminación de ruido con pesos entrenables ϕ elimina el ruido en x i (t) para predecir un código de eliminación de ruido x ^ \hat{x}X^ yo. Esta red se entrena comúnmente con una pérdida de eliminación de ruido L2 simplificada:
Insertar descripción de la imagen aquí

w (t) es una función de ponderación empírica dependiente del tiempo, y x ^ \hat{x}X^ ϕ(xi(t), t)formula una red de eliminación de ruido de segmentación temporal.

  1. Muestreo incondicional/guiado

  Utilizando los pesos entrenados ϕ , varios intérpretes (por ejemplo, DDIM) toman muestras de la difusión anterior p ψ (x) y eliminan el ruido de forma recursiva x (t) , comenzando con ruido gaussiano aleatorio x (t) hasta que se alcanza un estado sin ruido x (0) . El proceso de muestreo puede guiarse por el gradiente de la pérdida de renderizado frente a observaciones conocidas , lo que permite la reconstrucción 3D a partir de la imagen en el momento de la prueba.

  1. Limitaciones del entrenamiento en dos etapas para tareas 3D

  LDM que utiliza VAE de imágenes 2D generalmente se entrena en dos etapas; cuando se usa el decodificador automático NeRF para entrenar LDM : la obtención de un código latente expresivo a través de la optimización basada en renderizado está subdeterminada, lo que genera ruido en la red de eliminación de ruido (Figura 2, esquina superior izquierda); además , es muy difícil reconstruir nerfs a partir de vistas escasas sin conocimientos previos (Fig. 2, esquina inferior izquierda), lo que limita el entrenamiento a entornos de vista densa.
Insertar descripción de la imagen aquí


3. Método de este artículo.

SSDNeRF, un marco que conecta un expresivo decodificador automático NeRF de tres planos con un modelo de difusión latente de tres planos. La Figura 3 proporciona una descripción general del modelo.

Insertar descripción de la imagen aquí

3.1 Entrenamiento NeRF de difusión de una sola etapa

   Un decodificador automático puede verse como un VAE que utiliza un codificador de tabla de búsqueda en lugar de un codificador de red neuronal típico. Por lo tanto, los objetivos de formación se pueden derivar de forma similar a los VAE. Utilizando el decodificador NeRF
p ψ ({y j } | x, {r j }) y la difusión latente anterior p ϕ (x) , el objetivo del entrenamiento es minimizar el logaritmo negativo de los datos observados { y ij gt , r ij gt } Límite superior variacional de probabilidad (NLL) . Este artículo obtiene una pérdida de entrenamiento simplificada al ignorar la incertidumbre (varianza) en el código latente:

Insertar descripción de la imagen aquí
Entre ellos, el código de escena {x i } , el parámetro anterior ϕ y el parámetro del decodificador ψ se optimizan conjuntamente en una única etapa de entrenamiento. Esta pérdida incluye la pérdida de representación L rend en la Ecuación 1 y un término previo de difusión en forma de NLL. Siguiendo el artículo [Entrenamiento de máxima verosimilitud de modelos de difusión basados ​​en puntajes, Modelado generativo basado en puntajes en espacio latente, etc.] , reemplazamos la NLL de difusión con el límite superior aproximado L diff (también conocido como destilación fraccionada) en la ecuación (2 ) . Sumando el factor de peso de la experiencia, el objetivo final del entrenamiento es:

Insertar descripción de la imagen aquí

  El entrenamiento de una sola etapa, utilizando los códigos de escena con pérdida limitada {xi } anteriores , permite que lo aprendido antes complete la parte invisible, lo cual es particularmente beneficioso para el entrenamiento con datos de vista escasos (los códigos de triplano expresivos son muy inciertos ).

Equilibrio de renderizado y pesos anteriores

  La relación de peso antes de la renderización λ rend / λ diff es la clave para el entrenamiento en una sola etapa. Para asegurar la generalización, se diseña un mecanismo de ponderación empírico , en el que la pérdida por difusión se normaliza mediante la media móvil exponencial (EMA) de la norma de Frobenius del código de escena, expresada como: λ diff := c diff / EMA(||x i | | 2 F ), c ​​​​diff es una escala fija; λ rend := c rend (1−e −0.1Nv )/N v . Los pesos de renderizado están determinados por el número de vistas visibles Nv : La ponderación basada en Nv es una calibración del decodificador pψ , que evita que la pérdida de renderizado escale linealmente con el número de rayos.

Comparación con campos neuronales generativos de dos etapas.

  El método anterior de dos etapas [Gaudi, Diffrf, generación de campo neuronal 3D mediante difusión triplana] ignoró el término anterior λ diff L diff en la primera etapa de entrenamiento . Esto puede verse como establecer los pesos previos al renderizado λ renddiff al infinito, lo que da como resultado códigos de escena sesgados y ruidosos xi . El artículo [generación de campo neuronal 3D mediante difusión triplana] alivia parcialmente este problema imponiendo una regularización de variación total (TV) en el código de escena triplano para forzar un previo suave, similar a la restricción LDM en el espacio latente (columna central de la Figura 2). ). Control3Diff propone aprender un modelo de difusión condicional sobre datos generados por 3D GAN previamente entrenados en imágenes de vista única. Por el contrario, nuestra formación de una sola etapa tiene como objetivo incorporar directamente la difusión antes de promover la coherencia de un extremo a otro.


3.2 Muestreo y ajuste guiados por imágenes

  Para lograr una reconstrucción NeRF rápida y generalizable que abarque la reconstrucción desde una sola vista hasta una densa de múltiples vistas, proponemos realizar un muestreo guiado por imágenes mientras se ajusta el código de muestreo teniendo en cuenta los antecedentes de difusión y las probabilidades de representación . De acuerdo con el método de muestreo guiado por reconstrucción de [Modelos de difusión de video], se calcula el gradiente de representación aproximado g, es decir, un código de ruido x (t) :
Insertar descripción de la imagen aquí
donde, (t)(t) ) es un relación señal-ruido basada en (SNR) (el hiperparámetro ω es 0,5 o 0,25). El gradiente guiado g se combina con la predicción de puntuación incondicional, expresada como el par de salidas sin ruido x ^ \hat{x}X^ Corrección:

Insertar descripción de la imagen aquí
La escala de orientación es λ gd . Empleamos un muestreador de corrección de predicción [52] para resolver x (0) alternando un paso DDIM con múltiples pasos de corrección de Langevin .

  Observamos que las pautas de reconstrucción no pueden imponer estrictamente restricciones de representación para reconstrucciones fieles. Para resolver este problema, reutilizamos la Ecuación 4, ajustando el código de escena muestreado x mientras congelamos los parámetros de difusión y decodificador:
Insertar descripción de la imagen aquí
donde λ'diff es el peso anterior en el momento de la prueba, que debe ser menor que el valor de peso de entrenamiento λ diff (porque lo aprendido previamente del conjunto de datos de entrenamiento es menos confiable cuando se transfiere a un conjunto de datos de prueba diferente) . Utilice Adam para optimizar el código x para realizar ajustes finos

Comparación con métodos de ajuste fino de NeRF anteriores
  Aunque el ajuste fino con pérdidas de renderizado es común en los métodos de regresión NeRF condicionados por vista [8, 61], nuestro método de ajuste fino difiere en el uso de una pérdida previa de difusión en el código de escena 3D, que mejora significativamente la generalización a nuevas vistas, como se muestra en 5.3.

3.3 Algunos detalles

  1. Caché de gradiente anterior

La reconstrucción NeRF de tres planos requiere al menos cientos de iteraciones de optimización   para cada código de escena xi . Entre las pérdidas de una sola etapa en la fórmula (4), la pérdida de difusión L diff tarda más en verificarse que la pérdida de representación NeRF nativa L rend , lo que reduce la eficiencia general. Para acelerar el entrenamiento y el ajuste, introducimos una técnica llamada Almacenamiento en caché de gradiente previo , donde el gradiente de retropropagación almacenado en caché x λ diff L diff se reutiliza en múltiples pasos de Adam mientras se actualiza el gradiente renderizado en cada paso . . Permite menos difusión que el renderizado. El siguiente es el pseudocódigo del algoritmo principal:

  1. Parametrización y ponderación para eliminar ruido.

  Modelo de eliminación de ruido x ^ \hat{x}X^ ϕ(x(t), t)se implementa como una red U-Net en DDPM (122 millones de parámetros en total). Su entrada y salida son características de tres planos ruidosos y sin ruido (canales de tres planos apilados), respectivamente. Para la forma de la prueba, adoptamosla parametrizaciónv v^\hat{v}[43: Destilación progresiva para muestreo rápido de modelos de difusión]v^ ϕ(x(t),t), de modo quex ^ \hat{x}X^ = α(t)x(t)−σ(t)v ^ \hat{v}v^ . Con respecto a la función de ponderación w(t), LSGM [54] adopta dos mecanismos diferentes para optimizar elxilatentey el peso de difusiónϕ;Por el contrario, observamos que la ponderación basada en la relación señal-ruido w (t)= (α(t)/σ (t))utilizada en la Ecuación 5funciona bien.


4. Experimentar

41 conjuntos de datos

  Los experimentos utilizan conjuntos de datos ShapeNet SRN [6,48] y Amazon Berkeley Objects (ABO) Tables [9]. El conjunto de datos SRN proporciona dos tipos de escenas de un solo objeto, a saber, automóviles y sillas. La división tren/prueba de vagones es 2458/703 y la de sillas es 4612/1317. Cada escena de entrenamiento tiene 50 vistas aleatorias desde una esfera y cada escena de prueba tiene 251 vistas en espiral desde el hemisferio superior. El conjunto de datos de ABO Tables proporciona una división de tren/prueba de 1520/156 escenas de mesa, cada una con 91 vistas desde el hemisferio superior. La resolución de renderizado es 128×128.

4.2 Generación incondicional

  La generación incondicional se evalúa utilizando los conjuntos de datos SRN Cars y ABO Tables. El conjunto de datos Cars plantea desafíos a la hora de generar texturas nítidas y complejas, mientras que el conjunto de datos Tables consta de diferentes geometrías y materiales realistas. El modelo se entrena 1 millón de veces en todas las imágenes del conjunto de entrenamiento.

  1. Esquemas y métricas de verificación

  Para SRN Cars , siguiendo Functa, tomamos muestras de 704 escenas del modelo de difusión y renderizamos cada escena usando las 251 poses de cámara fijas en el conjunto de prueba . Para la tabla ABO, se muestrearon 1000 escenas según DiffRF y cada escena se representó utilizando 10 cámaras aleatorias. Genere anotaciones métricas de calidad: Distancia de inicio de Frechet (FID) y Distancia de inicio de kernel (KID) .

  1. Comparación con métodos de última generación :
    como se muestra en la Tabla 1, en los automóviles SRN, el SSDNeRF de una etapa supera significativamente a EG3D en KID (más adecuado para conjuntos de datos pequeños). Al mismo tiempo, su FID es significativamente mejor que Functa (que usa un LDM pero tiene un código latente de baja dimensión). Entre las tablas ABO, SSDNeRF tiene un rendimiento significativamente mejor que EG3D y DiffRF.

  2. De una sola etapa y de dos etapas :
    en automóviles SRN, se compararon el entrenamiento de una sola etapa y el entrenamiento de dos etapas regularizado por TV bajo la misma arquitectura del modelo. Los resultados en la Tabla 1 muestran las ventajas de la etapa única.

Insertar descripción de la imagen aquí

4.3 Reconstrucción NeRF de vista dispersa

  Los datos de Cars presentan el desafío de recuperar diferentes texturas, y los datos de Chair requieren reconstruir con precisión diferentes formas. El modelo se entrena con todas las imágenes del conjunto de entrenamiento durante 8000 iteraciones, y encontramos que programas más largos conducen a un rendimiento reducido en la reconstrucción de objetos invisibles. Este comportamiento es consistente con los resultados de la interpolación.

  1. Planes de Evaluación e Indicadores

  Se utiliza la métrica de evaluación de PixelNeRF [59]: dada la imagen de entrada de la muestra de escena de prueba, el código de escena de tres planos se obtiene mediante ajuste de guía y la nueva calidad de vista se evalúa para la imagen en perspectiva desconocida. Indicadores de calidad de imagen : relación de modulación de señal máxima promedio (PSNR), similitud estructural (SSIM) y similitud de parche de imagen perceptiva aprendida (LPIPS) [60]. y FID entre imágenes sintéticas y reales (como 3DiM).

  1. Comparar con otros métodos

  En la Tabla 2: SSDNeRF tiene los mejores L PIPS y la mejor fidelidad perceptiva. 3DiM produce imágenes de alta calidad (mejor FID) pero tiene la fidelidad más baja a la verdad del terreno (PSNR); CodeNeRF reporta el mejor PSNR en autos de vista única, pero su expresividad limitada da como resultado resultados borrosos (Figura 5) y L PIPS y FID; VisionNeRF logra un rendimiento equilibrado en todas las métricas de vista única, pero puede tener dificultades para generar detalles de textura en el lado invisible del automóvil (por ejemplo, el otro lado de la ambulancia en la Figura 5). Además, SSDNeRF tiene ventajas obvias en la reconstrucción de vista dual, logrando el mejor rendimiento en todas las métricas relevantes.

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

4.4 Entrenamiento de SSDNeRF en datos de vista dispersa

Esta sección entrena SSDNeRF en un subconjunto de vistas dispersas del conjunto de entrenamiento completo de SRN Cars, seleccionando aleatoriamente un conjunto fijo de tres vistas en cada escena . Tenga en cuenta que habrá una caída de rendimiento esperada razonable en comparación con el entrenamiento con vista densa, ya que todo el conjunto de datos de entrenamiento se ha reducido al 6 % de su tamaño original.

  1. Generar incondicionalmente

A mitad del entrenamiento, los códigos de tres planos se restablecen a su promedio. Esto ayuda a evitar que el modelo se quede atascado en un mínimo local con artefactos geométricos sobreajustados. Duplique el tiempo de entrenamiento en consecuencia. El modelo logró un buen FID de 19,04±1,10 y un KID/10−3 de 8,28±0,60. Los resultados se muestran en la Figura 7.

Insertar descripción de la imagen aquí

  1. Reconstrucción de vista única

Adoptamos la misma estrategia de formación que en el punto 5.3. Con nuestro enfoque de ajuste de orientación, el modelo logra una puntuación LPIPS de 0,106, incluso mejor que la mayoría de los métodos anteriores que utilizan el conjunto de entrenamiento completo de la Tabla 2.

  1. Comparación con la regularización de TV

La Figura 8 (b) muestra las imágenes y geometrías RGB representadas por los códigos latentes de escena aprendidos de las tres vistas durante el entrenamiento. Por el contrario, los decodificadores automáticos de tres planos ordinarios con regularización de TV (Figura 8(a)) a menudo no logran reconstruir escenas a partir de vistas escasas, lo que genera graves artefactos geométricos. Por lo tanto, antes era inviable entrenar modelos de dos etapas con latencia de expresión en datos de vista dispersa.

4.5 interpolación NeRF

     De acuerdo con la configuración de DDIM, se muestrean dos valores iniciales x (T) ∼N(0,I) , se interpolan usando interpolación lineal esférica [46], y luego se usa el solucionador determinista para obtener las muestras interpoladas. Sin embargo, como se señala en [37, 40], el modelo de difusión gaussiano estándar a menudo conduce a una interpolación no suave . En SSDNeRF (los resultados se muestran en la Figura 9), encontramos que un modelo entrenado con reconstrucción temprana de vista dispersa (a) produce transiciones razonablemente suaves, mientras que un modelo entrenado con generación incondicional (b) produce muestras distintas pero discontinuas. Esto sugiere que la detención temprana preserva un proceso previo más fluido, lo que conduce a una mejor generalización para la reconstrucción de vista dispersa.

Insertar descripción de la imagen aquí

5. Código

5.1 Generación incondicional

Seleccione el archivo de configuración aquí : ssdnerf_cars_uncond.py y descargue el peso correspondiente : ssdnerf_cars_uncond_1m_emaonly.pth. El significado del archivo de configuración se muestra a continuación:

ssdnerf_cars3v_uncond
   │      │      └── testing data: test unconditional generation
   │      └── training data: train on Cars dataset, using 3 views per scene
   └── training method: single-stage diffusion nerf training

stage2_cars_recons1v
   │     │      └── testing data: test 3D reconstruction from 1 view
   │     └── training data: train on Cars dataset, using all views per scene
   └── training method: stage 2 of two-stage training

提示:权重可能不是跟模型完全匹配,猜想可能因为在重建中还会训练;另外就是想引入一些网络的随机权重,增强泛化性。此外,大部分重要函数,都用C++做了编译(便于cuda并行计算),因此无法解析

1. Generar ruido como entrada

num_batches = len(data['scene_id'])                              # 6
noise = torch.randn((num_batches, *self.code_size), 
                     device=get_module_device(self))             # (8,3,6,128,128)

code_diff = code_diff.reshape(code.size(0), *self.code_reshape)  # (8,18,128,128

2.proceso de difusión:
ddim_sample de la clase: clase GaussianDiffusion(nn.Module):

sample_fn_name = 'ddim'

# 参数设置---------------------------------------------------------------------
x_t = noise
num_timesteps = self.test_cfg.get('num_timesteps', self.num_timesteps)  # 50
langevin_steps = self.test_cfg.get('langevin_steps', 0)                 # 0
langevin_t_range = self.test_cfg.get('langevin_t_range', [0, 1000])     # [0,1000]
timesteps = torch.arange(start=self.num_timesteps - 1, end=-1, 
                 step=-(self.num_timesteps / num_timesteps)).long().to(device)
# 从01000步中,随机选50个时间步

# 50次的逆扩散过程 (最重要!!)-------------------------------------------------
for step, t in enumerate(timesteps):                 # 0, 999
    t_prev = timesteps[step + 1]                     # 979
    x_t, x_0_pred = self.p_sample_ddim( x_t, t, t_prev, concat_cond=None, cfg=self.test_cfg, **kwargs)
    # 以上 self.p_sample_ddim过程,下面有详解

	eps_t_pred = (x_t - self.sqrt_alphas_bar[t] * x_0_pred) / self.sqrt_one_minus_alphas_bar[t]
	pred_sample_direction = np.sqrt(1 - alpha_bar_t_prev - tilde_beta_t * (eta ** 2)) * eps_t_pred
	x_prev = np.sqrt(alpha_bar_t_prev) * x_0_pred + pred_sample_direction      # (8,18,128,128)

    x_t = x_prev              # 更新x_t,再次循环

2.1 En self.p_sample_ddim, varios parámetros predefinidos: factor de difusión β \betab、a、a ˉ \bar{a}aesperar _

def prepare_diffusion_vars(self):

	## 1.扩散因子 beta :-----------------------------------------------------------
	scale = 1000 / diffusion_timesteps      # 一般是(1000步)
	beta_0 = scale * 0.0001
	beta_T = scale * 0.02
	self.betas = np.linspace(beta_0, beta_T, diffusion_timesteps, dtype=np.float64)
	
	## 2.alphas及其他参数 :--------------------------------------------------------
	self.alphas = 1.0 - self.betas
	self.alphas_bar = np.cumproduct(self.alphas, axis=0)
	self.alphas_bar_prev = np.append(1.0, self.alphas_bar[:-1])
	self.alphas_bar_next = np.append(self.alphas_bar[1:], 0.0)
	
	## 3. 计算扩散概率 q(x_t | x_0)--------------------------------------------
	self.sqrt_alphas_bar = np.sqrt(self.alphas_bar)
	self.sqrt_one_minus_alphas_bar = np.sqrt(1.0-self.alphas_bar)
	self.log_one_minus_alphas_bar = np.log(1.0-self.alphas_bar)
	self.sqrt_recip_alplas_bar = np.sqrt(1.0 / self.alphas_bar)
	self.sqrt_recipm1_alphas_bar = np.sqrt(1.0 / self.alphas_bar-1)
	
	##  4.计算后验概率 q(x_{
    
    t-1} | x_t, x_0)  -----------------------------------
	self.tilde_betas_t = self.betas * (1-self.alphas_bar_prev) / (1-self.alphas_bar)
	    
	## 5.clip log var for tilde_betas_0 = 0----------------------------------------
	self.log_tilde_betas_t_clipped = np.log(np.append(self.tilde_betas_t[1], self.tilde_betas_t[1:]))
	self.tilde_mu_t_coef1 = np.sqrt(self.alphas_bar_prev) / (1 - self.alphas_bar) * self.betas
	self.tilde_mu_t_coef2 = np.sqrt(self.alphas) * (1 - self.alphas_bar_prev) / (1 - self.alphas_bar)

2.2 Proceso de difusión inversa en self.p_sample_ddim: Unet estándar (con operación de autoatención), las dimensiones delantera y trasera permanecen sin cambios

	alpha_bar_t_prev = self.alphas_bar[t_prev]  
	embedding = self.time_embedding(t)    # bs*[999] --> (bs,512)
	h, hs = x_t, []
	
	## 0.如果有condition,跟噪声x_t拼接
	if self.concat_cond_channels > 0:
	    h = torch.cat([h, concat_cond], dim=1)
	    
	## 1.下采样阶段 
	for block in self.in_blocks:
	    h = block(h, embedding)
	    hs.append(h)                           # (8,512,8,8)
	
	# 2.中间阶段
	h = self.mid_blocks(h, embedding)
	
	# 上采样
	for block in self.out_blocks:
	    h = block(torch.cat([h, hs.pop()], dim=1), embedding)    #   (8,128,128,128)
	denoising_output = self.out(h)                    

return denoising_output                                # (8,18,128,128)

2.3 En self.p_sample_ddim, obtenga el x_0 previsto

denoising_output = self.denoising(x_t,t,concat_cond=None)   # 上步结果(8,18,128,128if self.denoising_mean_mode.upper() == 'V':
    x_0_pred = sqrt_alpha_bar_t * x_t - sqrt_one_minus_alpha_bar_t * denoising_output
    # 预测的x_0 是 x_t和denoising_output结果的线性组合

if clip_denoised:
    x_0_pred = x_0_pred.clamp(*clip_range)        # 按-22 截断,防止梯度消失/爆炸
  1. self.get_density: La densidad de predicción de NeRF
    se encuentra en la clase grande BaseNeRF (nn.Module):
density_grid = self.get_init_density_grid(num_scenes=8, device)

    tmp_grid = torch.full_like(density_grid, -1)     # (8,262144*[-1]


    # 生成64*64*64的网格:--------------------------------------------------------------------------
    if iter_density < 16:
        X = torch.arange(self.grid_size, dtype=torch.int32, device=device).split(S)   # 64
        Y = torch.arange(self.grid_size, dtype=torch.int32, device=device).split(S)
        Z = torch.arange(self.grid_size, dtype=torch.int32, device=device).split(S)

        # 只循环一次:
        for xs in X:
            for ys in Y:
                for zs in Z:
                    # 构建三维网格----------------------------------------------------------------------
                    xx, yy, zz = custom_meshgrid(xs, ys, zs)         # (64,64,64)
                    coords = torch.cat([xx.reshape(-1, 1), yy.reshape(-1, 1), zz.reshape(-1, 1)],
                                       dim=-1)                       # [262144, 3]
                    indices = morton3D(coords).long()                # (262144): 打乱的索引
                    xyzs = (coords.float() - (self.grid_size - 1) / 2) * (2 * decoder.bound / self.grid_size)  # 以立方体中心点做归一化到(-1,1)
                    # 添加噪声--------------------------------------------------------------------------
                    half_voxel_width = decoder.bound / self.grid_size    # 1/64
                    xyzs += torch.rand_like(xyzs) * (2 * half_voxel_width) - half_voxel_width   
                    # 解码出密度值----------------------------------------------------------------------
                    sigmas = decoder.point_decode(xyzs , code)       # 下面有解析
                    tmp_grid[:, indices] = sigmas.clamp(max=torch.finfo(tmp_grid.dtype).max).to(tmp_grid.dtype)
            
                    # 求有效密度------------------------------------------------------------------------
		            valid_mask = (density_grid >= 0) & (tmp_grid >= 0)                                 # (8,262144)
		            density_grid[:] = torch.where(valid_mask, torch.maximum(density_grid * decay, tmp_grid), density_grid)   # (8,262144)
		            mean_density = torch.mean(density_grid.clamp(min=0))  # -1 regions are viewed as 0 density.       # 352.75
		            iter_density += 1
		
		            # 保存结果到 bitfield ----------------------------------------------------------------
		            density_thresh = min(mean_density, density_thresh)     # min(352.75, 0.1)
		            packbits(density_grid, density_thresh, density_bitfield)           
		            # 将 grid 与 thresh 作比较,每8个字节为一组,大于的存在 bitfield 中


def point_decode(self, xyzs, dirs, code, density_only=False):
    
    num_scenes, _, n_channels, h, w = code.size()        # 8,6,128,128

    # # 坐标上插值code特征:
    point_code = F.grid_sample(code.reshape(24,6,128,128),    
                               self.xyz_transform(xyzs),      # (24,1,262144,2) .取出其中的3个面坐标,并拼接
                               mode=self.interp_mode, padding_mode='border', 
                               align_corners=False).reshape(8,3,6, 262144)
    point_code = point_code.permute(0, 3, 2, 1).reshape( 2097152, 18)                         
    
    base_x = self.base_net(point_code)                 # linear(2097152, 18)-> (2097152, 64) 
    base_x_act = self.base_activation(base_x)
    sigmas = self.density_net(base_x_act).squeeze(-1)  # (2097152, 64) -> (2097152, 1) 
    rgb = None if density_only
  1. Renderizar imagen (decodificar)
image, depth = self.render(decoder, code, density_bitfield, h, w, test_intrinsics, test_poses, cfg=cfg)      

Entrada de función: # código: (8,3,6,128,128) campo de bits: (8,32768) pose: (8,251,4,4) intrínsecos: [131.250, 131.250, 64.0, 64.0]
La siguiente es la expansión específica:

## 1. 得到射线起点和方向---------------------------------------------------
rays_o, rays_d = get_cam_rays(poses, intrinsics, h, w)  #(8,251,128,128,3) 拓展部分有解析

## 2.VolumeRenderer-----------------------------------------------------
outputs = decoder(rays_o, rays_d, code, density_bitfield, grid_size, dt_gamma=0, perturb=False)
   
    # 2.1查询每条射线跟 aabb(-1,-1,-1,1,1,1)交点---------------------------
    nears, fars = batch_near_far_from_aabb(rays_o, rays_d, self.aabb, self.min_near=0.2)
    #  nears、fars:(8, 4112384=251*128*128)

    # 2.2 渲染256次                

          step = 0
          while step < self.max_steps:
                # 射线上均匀采样空间点,插值得到对应密度 delta--------------------
                xyzs, dirs, deltas = march_rays(4112384, n_step, rays_alive, rays_t,
                                     rays_o, rays_d_single, self.bound=1, bitfield, 1, 
                                     grid_size, nears, fars,  align=128, perturb=False, dt_gamma=0, max_steps=256)

                sigmas, rgbs,= self.point_decode(xyzs, dirs, code) # xyzs(4112512, 3)  code(3,6,128,128)
                   
                    # point_decode展开如下:---------------------------------------
	                point_code_single = F.grid_sample(code_single, self.xyz_transform(xyzs_single),
	                                         mode='bilinear', padding_mode='border', align_corners=False ).squeeze(-2) # (3,6,4112512)


			        base_x = self.base_net(point_code)       # linear (4112512, 18) -> (4112512, 64) 
			        base_x_act = self.base_activation(base_x)
			        sigmas = self.density_net(base_x_act).squeeze(-1)     # (4112512, 64) -> (4112512, 1) 

		            # 渲染颜色(因为颜色需要位置信息+方向信息)----------------------------
		            sh_enc = sh_encode(dirs, self.degree=4)   # 三维坐标,位置编码至16维
		            color_in = self.base_activation(base_x + self.dir_net(sh_enc))  # linear+Silu->(4112512,64)
		            rgbs = self.color_net(color_in)    # linear:(64,3)
		            if self.sigmoid_saturation > 0:
		                rgbs = rgbs * (1 + self.sigmoid_saturation * 2) - self.sigmoid_saturation

                # 光线追踪和 图像生成:
                composite_rays(4112512, rays_t, sigmas, rgbs, deltas, depth, image, T_thresh=0.001)

La función composite_rays realiza cálculos iterativos en cada rayo y finalmente obtiene la información de la imagen compuesta en función de los pasos de propagación de la luz, acumulación de valores de color y actualización de parámetros.

Finalmente se obtienen tres resultados (tamaño de lote = 8): imagen (4112384,3), suma_pesos (4112384), profundidad (4112384), después de la operación de remodelación, se obtiene la imagen final:

weights = torch.stack(outputs['weights_sum'], dim=0) 
rgbs = (torch.stack(outputs['image'], dim=0) + self.bg_color * (1 - weights.unsqueeze(-1))     # self.bg_color = 1
depth = torch.stack(outputs['depth'], dim=0) 

5.2 Entrenamiento de reconstrucción NeRF (vista múltiple): ssdnerf_cars_recons1v

El archivo de configuración es: ssdnerf_cars_recons1v.py. Seleccione 50 perspectivas cada vez para la reconstrucción.

1. Pérdida del proceso de difusión (la lista de códigos almacenada en caché es muy importante):

# 1.加载缓存cache--------------------------------------------------------------------------------------
code_list_, code_optimizers, density_grid, density_bitfield = self.load_cache(data)
      
      cache_list=[None, None, None, None, None, None, None, None]  # 初始化为bs个None
      # 循环bs次--------------------------------------------------------------
      for scene_state_single in cache_list:
          if scene_state_single is None:
              # 随机生成以下参数(torch.empty等)
              code_list_.append(self.get_init_code_(None, device))                   # (3,6,128,128)
              density_grid.append(self.get_init_density_grid(None, device))          # (262144)
              density_bitfield.append(self.get_init_density_bitfield(None, device))  #(32768)

# 2.读入图像------------------------------------------------------------------------------------------
concat_cond = None                     # 这里没有使用图像拼接条件
if 'cond_imgs' in data:
    cond_imgs = data['cond_imgs']  #  (8,50,128,128,3) 8个batch,50个视角的图
    cond_intrinsics = data['cond_intrinsics']  # [fx, fy, cx, cy] (bs,50,4)
    cond_poses = data['cond_poses']       # (bs,50,4,4)

# 3. 计算射线(根据pose和intrinc)-------------------------------------------------------------------
 cond_rays_o, cond_rays_d = get_cam_rays(cond_poses, cond_intrinsics, h, w)  # (bs,50,128,128,3)
 dt_gamma_scale = self.train_cfg.get('dt_gamma_scale', 0.0)        # 0.5
 dt_gamma = dt_gamma_scale / cond_intrinsics[..., :2].mean(dim=(-2, -1))    # [0.0038]*(8)

# 4.采样时间步 t------------------------------
t = self.sampler(num_batches).to(device)  # [605, 733, 138, 312, 997, 128, 178, 752]
# 5. 生成噪声
noise = torch.rand().to(device)    # (bs, 18, 128, 128)

# 6.根据时间t,采样噪声
x_t, mean, std = self.q_sample(x_0, t, noise)  # x_0就是上步的code_list_,经过缩放和激活

			    def q_sample(self, x_0, t, noise=None):

			        mean = var_to_tensor(self.sqrt_alphas_bar, t.cpu(), x_0.shape, device)  将输入变量sqrt_alphas转换为张量,并根据索引t提取相应的值    
			        std = var_to_tensor(self.sqrt_one_minus_alphas_bar, t.cpu(), x_0.shape, device)
			        return x_0 * mean + noise * std,  mean,  std

# 7.逆扩散过程-------------------------------------------------------------------------------------------
denoising_output = self.pred_x_0( x_t, t, grad_guide_fn=None, concat_cond=None, cfg=cfg, update_denoising_output=True)
		# 7.1 位置编码------------------------------------------------------------------------
		embedding = self.time_embedding(t)    # bs --> (bs,512) 正余弦编码+conv+silu。具体展开如下:
					embedding = self.blocks(self.sinusodial_embedding(t))
							    def sinusodial_embedding(timesteps, dim, max_period=10000):
							        half = dim // 2        # 128//2
							        freqs = torch.exp(-np.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half)   
							        # freqs:64:[1.0, 0.8, 0.7, 0.6...0.0014]
							        args = timesteps[:, None].float() * freqs[None]
							        embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
							
							        return embedding       # (bs,128)

		# 7.2 上下采样,用3D卷积和注意力qkv--------------------------------------------------------------
        h, hs = x_t, []                            # (bs,18,128,128)
        if self.concat_cond_channels > 0:
            h = torch.cat([h, concat_cond], dim=1)
        # forward downsample blocks
        for block in self.in_blocks:
            h = block(h, embedding)
            hs.append(h)                           # (bs,512,8,8)

        # forward middle blocks
        h = self.mid_blocks(h, embedding)

        # forward upsample blocks
        for block in self.out_blocks:
            h = block(torch.cat([h, hs.pop()], dim=1), embedding)    #   (bs,128,128,128)
        outputs = self.out(h)                      # (bs,18,128,128)

        return outputs

# 8.  x_0 是输入x_t与预测值denoising_output的加权差:----------------------------------------------------
if self.denoising_mean_mode.upper() == 'V':
    x_0_pred = sqrt_alpha_bar_t * x_t - sqrt_one_minus_alpha_bar_t * denoising_output

# 9.self.ddpm_loss-----------------------------------------------------------------------------------
class DDPMMSELossMod(DDPMLossMod):
    def mse_loss(pred, target):

    return F.mse_loss(pred, target, reduction='none')    # (bs,18,128,128) target就是v_t
    
loss.mean(dim=list(range(1, loss.ndim)=4))    # (bs)
loss_rescaled = loss * weight.to(timesteps.device)[timesteps] * self.weight_scale   # weight_scale=4

quartile = (timesteps / total_timesteps * 4).int()        # [2, 3, 0, 0, 3, 2, 0, 2]
for idx in range(4):
    loss_quartile = reduce_loss(loss[quartile == idx], reduction)   # mean
    log_vars[f'{prefix_name}_quartile_{idx}'] = loss_quartile.item()    # log_vars包含4个loss,求均值得到loss_diffusion
loss_diffusion.backward()

Luego actualice el gradiente almacenado en caché:

    if extra_scene_step > 0:
        prior_grad = [code_.grad.data.clone() for code_ in code_list_]        # (8,3,6,128,128)

2.inverse_code: pérdida de NeRF

# 10.NeRF渲染-----------------------------------------------------------------------------------
for inverse_step_id in range(n_inverse_steps):                  # 循环16if inverse_step_id % self.update_extra_interval == 0:       # 16 的整数倍
        # density_grid, density_bitfield 分别是0初始化的(bs,262144)(bs,32768);经过upgrade后,填充进了sigma
        # update_extra_state 是网格生成过程(同上述无条件生成的3.4节),得到coord与sigma
        self.update_extra_state(decoder, code, density_grid, density_bitfield, iter_density, density_thresh=cfg.get('density_thresh', 0.01))

    inds = raybatch_inds[inverse_step_id % num_raybatch]       # 从 0 到 第200份的射线
    rays_o, rays_d, target_rgbs = self.ray_sample(
        cond_rays_o, cond_rays_d, cond_imgs, n_inverse_rays, sample_inds=inds)   # 选出某份射线:(bs,4096,3)


	## 11.march_rays_train(cuda封装的函数)-------------------------------------------------------------
	xyzs_single, dirs_single, deltas_single, rays_single = march_rays_train(
					                    rays_o_single, rays_d_single, self.bound, density_bitfield_single, 1, grid_size_single, nears_single, fars_single, 
					                    perturb=perturb, align=128, force_all_rays=True, dt_gamma=dt_gamma_single.item(), max_steps=self.max_steps)
	nears, fars = batch_near_far_from_aabb(rays_o, rays_d, self.aabb, self.min_near)
	weights_sum_, depth_, image_ = composite_rays_train(sigmas, rgbs, deltas_, rays_, T_thresh)   # (8,4096)(8,4096)(8,4096,3)
	
	
	## 12.最终的2个损失
	pixel_loss = self.pixel_loss(out_rgbs, target_rgbs, **kwargs) * (scale * 3)     # F.mse_loss, 权重weight=20,求平均   ->7.73
	reg_loss = (code.abs() ** 2).mean()                       # code:特征的大小  -> 3.99e-11
	loss = pixel_loss + reg_loss
	
	## 13. 复制prior_grad的梯度到 code_ (在一个inverse_step中,code_梯度永远来自prior_grad)----------------
    if prior_grad is not None:
        if isinstance(code_, list):
            for code_single_, prior_grad_single in zip(code_, prior_grad):           # 循环8次
                code_single_.grad.copy_(prior_grad_single)      # (3,6,128,128)

    loss.backward()
  1. Actualizar caché
self.save_cache(
    code_list_, code_optimizers,
    density_grid, density_bitfield, data['scene_id'], data['scene_name'])

5.3 Razonamiento de reconstrucción dispersa: ssdnerf_cars3v_recons1v

El archivo de configuración es: ssdnerf_cars3v_recons1v.py. Seleccione una canción a la vez para reconstruir la escena 3D.

with torch.no_grad():
    cond_mode = self.test_cfg.get('cond_mode', 'guide')     # 'guide_optim'
    # 1.val_guide 得到三平面特征code, 以及空间网格特征
    code, density_grid, density_bitfield = self.val_guide(data, **kwargs)    # code:(bs,18,128,128)
            # 1.0 初始化三大变量--------------------------------------------------------------
            density_grid = torch.zeros((num_scenes, self.grid_size ** 3), device=device)        # (8,262144)
            density_bitfield = torch.zeros((num_scenes, self.grid_size ** 3 // 8), dtype=torch.uint8, device=device)    # (8,32768)
            noise = torch.randn((num_scenes, *self.code_size), device)          # (8,3,6,128,128)
            
            # 1.1 扩散75-----------------------------------------------------------------------
		    x_t = noise
		    num_timesteps = self.test_cfg.get('num_timesteps', self.num_timesteps)        # 75
	        langevin_t_range = self.test_cfg.get('langevin_t_range', [0, 1000])           # [0,1000]
		    timesteps = torch.arange(start= 75 - 1, end=-1, step=-(1000/ 75)    # 75[0,1000]的随机数

            for step, t in enumerate(timesteps):                                    # 逆扩散75步,每次更新x_t
                x_t, x_0_pred = self.p_sample_ddim( x_t, t, t_prev, concat_cond=None)

			          # 1.1.1 Unet 通过x_t与t,预测x_0:---------------------------------------------------------
				   	  denoising_output = self.denoising(x_t, t, concat_cond=concat_cond)      # (8,18,128,128)
					  x_0_pred = sqrt_alpha_bar_t * x_t - sqrt_one_minus_alpha_bar_t * denoising_output
					  self.update_extra_state(decoder, x_0_pred, density_grid, density_bitfield,
					                             0, density_thresh=self.test_cfg.get('density_thresh', 0.01))
			          # 1.1.2 利用 x_0_pred 三平面特征,特征空间解码,更新density_grid,density_bitfield-------------------
			          rays_o, rays_d, target_rgbs = self.ray_sample(cond_rays_o, cond_rays_d, 
			                                             cond_imgs, n_inverse_rays, sample_inds=inds)  # (bs,16384,3)
			          # 1.1.3 NeRF 射线解码-----------------------------------------------------------------
			          nears, fars = batch_near_far_from_aabb(rays_o, rays_d, self.aabb, self.min_near=0.2)
			          xyzs_single, dirs_single, deltas_single, rays_single = march_rays_train()
			          sigmas, rgbs, num_points = self.point_decode(xyzs, dirs, code)    # F_grid插值,fc层计算密度/颜色 xyz(bs,93440,3-> (724480)(3495936,3) 8个点数
			          weights_sum, depth, image = batch_composite_rays_train(sigmas, rgbs, deltas, rays, num_points, T_thresh)   # (8,16384)(8,16384)(8,16384,3)
			          # 1.1.4 计算loss--------------------------------------------------------------
			          loss = grad_guide_fn(x_0_pred)
			          pixel_loss = self.pixel_loss(out_rgbs, target_rgbs, **kwargs) * (scale * 3)
			          reg_loss = self.reg_loss(code, **kwargs)
			          loss = pixel_loss + reg_loss
			          grad = torch.autograd.grad(loss, x_t)[0]   # (8,18,128,128) 计算 loss对x_t的梯度
			          torch.set_grad_enabled(False)
			          x_0_pred.detach_()
			          x_0_pred -= grad * ((sqrt_one_minus_alpha_bar_t ** (2 - snr_weight_power * 2))
			              * (sqrt_alpha_bar_t ** (snr_weight_power * 2 - 1))* guidance_gain)   # guidance_gain=52484.8
			          eps_t_pred = (x_t - self.sqrt_alphas_bar[t] * x_0_pred) / self.sqrt_one_minus_alphas_bar[t]
			          pred_sample_direction = np.sqrt(1 - alpha_bar_t_prev - tilde_beta_t * (eta ** 2)) * eps_t_pred    # (8,18,128,128)
			          x_t = np.sqrt(alpha_bar_t_prev) * x_0_pred + pred_sample_direction                             # (8,18,128,128)

    # 2. 继续逆扩散,更新特征---------------------------------------------------------------------------
    code, density_grid, density_bitfield = self.val_optim(data, code_=self.code_activation.inverse(code).requires_grad_(True),
                                                          density_grid, density_bitfield=density_bitfield, **kwargs)
            # 2.1 根据pose,计算射线-----------------------------------------------------------------------
	        cond_imgs = data['cond_imgs']  # (num_scenes, num_imgs, h, w, 3)
	        cond_intrinsics = data['cond_intrinsics']  # (num_scenes, num_imgs, 4), in [fx, fy, cx, cy]
	        cond_poses = data['cond_poses']
	
	        cond_rays_o, cond_rays_d = get_cam_rays(cond_poses, cond_intrinsics, h, w)     # (bs,1,128,128,3)
         
            for inverse_step_id in range(n_inverse_steps):                             # 25个inverse步骤
                loss, log_vars = diffusion(self.code_diff_pr(code), return_loss=True, concat_cond=None)
                # 上面是diff_train,采样t,Unet去噪, 计算4个ddpm_mes损失
                loss.backward()
                
                prior_grad = code_.grad.data.clone()
                self.inverse_code(decoder, cond_imgs, cond_rays_o, cond_rays_d, dt_gamma=dt_gamma, cfg code_=code_, density_grid, density_bitfield, prior_grad=prior_grad)

                     # inverse_code包含以下步骤:
                     for inverse_step_id in range(n_inverse_steps):        # 循环4if inverse_step_id % self.update_extra_interval == 0:
                            self.update_extra_state(decoder, code, density_grid, density_bitfield,iter_density, 
                                                    density_thresh=cfg.get('density_thresh', 0.01))    # 生成网格,F特征插值、base_net更新sigma

	                     rays_o, rays_d, target_rgbs = self.ray_sample( cond_rays_o, cond_rays_d, cond_imgs, n_inverse_rays, sample_inds=inds)        # (bs, 16384, 3)
	                     out_rgbs, loss, loss_dict = self.loss( decoder, code, density_bitfield, target_rgbs, rays_o, rays_d)
	                     code_.grad.copy_(prior_grad)
	                     loss.backward()
 # 3.-------------------------------------------------------------------------------------------
 log_vars, pred_imgs = self.eval_and_viz(data, decoder, code, density_bitfield, viz_dir=viz_dir, cfg)
        image, depth = self.render( decoder, code, density_bitfield, h, w, test_intrinsics, test_poses, cfg=cfg)      #code:(8,3,6,128,128)bitfield:(8,32768) pose:(8,251,4,4)

La siguiente es una expansión de la Parte 3: self.render

# 输入数据的num_imgs有250张
test_intrinsics = data['test_intrinsics']  # (num_scenes, num_imgs, 4), in [fx, fy, cx, cy]
test_poses = data['test_poses']
test_imgs = data['test_imgs']  # (num_scenes, num_imgs, h, w, 3)

outputs = decode(batch_near_far_from_aabb、march_rays、self.point_decode等,循环self.max_steps=256)
        results = dict(
            weights_sum=weights_sum,          # bs个(4096000)
            depth=depth,                      # bs个(4096000)
            image=image)                      # bs个(40960003)

weights = torch.stack(outputs['weights_sum'], dim=0) if num_scenes > 1 else outputs['weights_sum'][0]    # 01
rgbs = (torch.stack(outputs['image'], dim=0) if num_scenes > 1 else outputs['image'][0]) \
      + self.bg_color * (1 - weights.unsqueeze(-1))
depth = torch.stack(outputs['depth'], dim=0) if num_scenes > 1 else outputs['depth'][0]
pred_imgs = image.permute(0, 1, 4, 2, 3).reshape(8 * 250, 3, h, w).clamp(min=0, max=1)

# 测试生成的全视角图像,和数据集真实图像的指标
test_psnr = eval_psnr(pred_imgs, target_imgs)   # bs*250个数
test_ssim = eval_ssim_skimage(pred_imgs, target_imgs, data_range=1)

# 最后是保存图像(与真实图像拼接,便于对比)在work_dir文件夹中,此处忽略。。。。。。。。。。。。。。

5.4 Entrenamiento de reconstrucción escaso: ssdnerf_cars3v_recons1v

El archivo de configuración es: ssdnerf_cars3v_recons1v.py Cada vez, se seleccionan 3 perspectivas para el entrenamiento de reconstrucción.

# 1.载入缓存.若无缓存,初始化(生成)code等三大变量:------------------------------------------------
code_list_, code_optimizers, density_grid, density_bitfield = self.load_cache(data)
code = self.code_activation(torch.stack(code_list_, dim=0), update_stats=True)

# 2.载入图像----------------------------------------------------------------------------
cond_imgs = data['cond_imgs']  # (num_scenes, num_imgs, h, w, 3)     (8,3,128,128,3)
cond_intrinsics = data['cond_intrinsics']  # (num_scenes, num_imgs, 4), in [fx, fy, cx, cy] (8,3,4)
cond_poses = data['cond_poses']       # (8,3,4,4)

num_scenes, num_imgs, h, w, _ = cond_imgs.size()
# (num_scenes, num_imgs, h, w, 3)
cond_rays_o, cond_rays_d = get_cam_rays(cond_poses, cond_intrinsics, h, w)  # (8,3,128,128,3)
dt_gamma_scale = self.train_cfg.get('dt_gamma_scale', 0.0)        # 0.5
# (num_scenes,)
dt_gamma = dt_gamma_scale / cond_intrinsics[..., :2].mean(dim=(-2, -1))    # [0.0038]*(8)

# 3. 扩散模型损失
loss_diffusion, log_vars = diffusion(self.code_diff_pr(code), concat_cond=None, return_loss=True,
                                     x_t_detach=x_t_detach, cfg=self.train_cfg)
                t = self.sampler(num_batches).to(device)              # [630, 757, 989, 452,...] bs个
                noise = torch.randn((num_batches, *image_shape))
                x_t, mean, std = self.q_sample(x_0, t, noise)         # 从x_0 采样到x_t 
                denoising_output = self.denoising(x_t, t, concat_cond=concat_cond)      # Unet逆扩散过程(8,18,128,128)

loss_diffusion.backward()                

# 4. 更新 先验梯度-------------------------------------------------------------------------------------
prior_grad = [code_.grad.data.clone() for code_ in code_list_]        # (8,3,6,128,128)

# 5.self.inverse_code:NeRF渲染:----------------------------------------------------------------------
raybatch_inds, num_raybatch = self.get_raybatch_inds(cond_imgs, n_inverse_rays)  # (8,3,128,12834096 -> 随机分成 12份的 (8,4096)
self.update_extra_state(decoder, code, density_grid, density_bitfield,
                        iter_density, density_thresh=cfg.get('density_thresh', 0.01))

nears, fars = batch_near_far_from_aabb(rays_o, rays_d, self.aabb, self.min_near)
xyzs_single, dirs_single, deltas_single, rays_single = march_rays_train( rays_o_single, rays_d_single, 
                                 self.bound, density_bitfield_single, 1, grid_size_single, nears_single, fars_single,  perturb=True)


sigmas, rgbs, num_points = self.point_decode(xyzs, dirs, code)    # F_grid插值,fc层计算密度/颜色 (8,438144,3-> (3495424)(3495424,3) 8个点数
            weights_sum, depth, image = batch_composite_rays_train(sigmas, rgbs, deltas, rays, num_points, T_thresh)   # (8,4096)(8,4096)(8,4096,3)

pixel_loss = self.pixel_loss(out_rgbs, target_rgbs, **kwargs) * (scale * 3)     # F.mse_loss, 权重weight=20,求平均   ->7.73
reg_loss = self.reg_loss(code, **kwargs)
loss = pixel_loss + reg_loss

for code_single_, prior_grad_single in zip(code_, prior_grad):           # 循环8次
    code_single_.grad.copy_(prior_grad_single)      # (3,6,128,128)
loss.backward()

# 6.更新 density_grid, density_bitfield------------------------------------------------------------
self.update_extra_state( decoder, code, density_grid, density_bitfield,
                0, density_thresh=self.train_cfg.get('density_thresh', 0.01))


# 7.loss_decoder = self.loss_decoder(decoder, code, density_bitfield, cond_rays_o, cond_rays_d,
                                     cond_imgs, dt_gamma, cfg=self.train_cfg) #-----------------------
    # decoder_loss作为第三个损失,与第二部损失相同。内容如下:
	nears, fars = batch_near_far_from_aabb(rays_o, rays_d, self.aabb, self.min_near)
	for i in range(batchsize):
		xyzs_single, dirs_single, deltas_single, rays_single = march_rays_train(
							                   rays_o_single, rays_d_single, self.bound, density_bitfield_single,
							                   1, grid_size_single, nears_single, fars_single,
							                   perturb=perturb, align=128, force_all_rays=True,
							                   dt_gamma_single.item(), max_steps=256)      # (4096,3->(435968,3)(435968,3)(435968,2)
	    sigmas, rgbs, num_points = self.point_decode(xyzs, dirs, code)    # F_grid插值,fc层计算密度/颜色 (8,447212,3-> (3495936)(3495936,3) 8个点数
	    weights_sum, depth, image = batch_composite_rays_train(sigmas, rgbs, deltas, rays, num_points, T_thresh)   # (8,4096)(8,4096)(8,4096,3)

# 8.更新 decoder 梯度------------------------------------------------------------------------------
for code_, prior_grad_single in zip(code_list_, prior_grad):
    code_.grad.copy_(prior_grad_single)
loss_decoder.backward()

# 9.缓存cache-------------------------------------------------------------------------------------
self.save_cache( code_list_, code_optimizers, density_grid, density_bitfield, data['scene_id'], data['scene_name'])

# 10.验证-----------------------------------------------------------------------------------------
train_psnr = eval_psnr(out_rgbs, target_rgbs)

* Funciones encapsuladas .cuda

lib/ops/raymarching/scr/raymarching.h contiene las siguientes funciones (definidas específicamente en el archivo raymarching.cu)

#pragma once

#include <stdint.h>
#include <torch/torch.h>

void near_far_from_aabb(const at::Tensor rays_o, const at::Tensor rays_d, const at::Tensor aabb, const uint32_t N, const float min_near, at::Tensor nears, at::Tensor fars);
void sph_from_ray(const at::Tensor rays_o, const at::Tensor rays_d, const float radius, const uint32_t N, at::Tensor coords);
void morton3D(const at::Tensor coords, const uint32_t N, at::Tensor indices);
void morton3D_invert(const at::Tensor indices, const uint32_t N, at::Tensor coords);
void packbits(const at::Tensor grid, const uint32_t N, const float density_thresh, at::Tensor bitfield);

void march_rays_train(const at::Tensor rays_o, const at::Tensor rays_d, const at::Tensor grid, const float bound, const float dt_gamma, const uint32_t max_steps, const uint32_t N, const uint32_t C, const uint32_t H, const uint32_t M, const at::Tensor nears, const at::Tensor fars, at::Tensor xyzs, at::Tensor dirs, at::Tensor deltas, at::Tensor rays, at::Tensor counter, at::Tensor noises);
void composite_rays_train_forward(const at::Tensor sigmas, const at::Tensor rgbs, const at::Tensor deltas, const at::Tensor rays, const uint32_t M, const uint32_t N, const float T_thresh, at::Tensor weights_sum, at::Tensor depth, at::Tensor image);
void composite_rays_train_backward(const at::Tensor grad_weights_sum, const at::Tensor grad_image, const at::Tensor sigmas, const at::Tensor rgbs, const at::Tensor deltas, const at::Tensor rays, const at::Tensor weights_sum, const at::Tensor image, const uint32_t M, const uint32_t N, const float T_thresh, at::Tensor grad_sigmas, at::Tensor grad_rgbs);

void march_rays(const uint32_t n_alive, const uint32_t n_step, const at::Tensor rays_alive, const at::Tensor rays_t, const at::Tensor rays_o, const at::Tensor rays_d, const float bound, const float dt_gamma, const uint32_t max_steps, const uint32_t C, const uint32_t H, const at::Tensor grid, const at::Tensor nears, const at::Tensor fars, at::Tensor xyzs, at::Tensor dirs, at::Tensor deltas, at::Tensor noises);
void composite_rays(const uint32_t n_alive, const uint32_t n_step, const float T_thresh, at::Tensor rays_alive, at::Tensor rays_t, at::Tensor sigmas, at::Tensor rgbs, at::Tensor deltas, at::Tensor weights_sum, at::Tensor depth, at::Tensor image);

limitación

     Actualmente, este método se basa en los parámetros de la cámara Ground Truth durante el entrenamiento y las pruebas. El trabajo futuro puede explorar modelos de transformación invariante. Además, a medida que aumenta el tiempo de entrenamiento, la difusión previa se vuelve discontinua, afectando la generalización . Aunque la parada anticipada se utiliza temporalmente, un mejor diseño de red o un conjunto de datos de entrenamiento más grande pueden resolver fundamentalmente este problema.


expandir

1.Indicadores FID, KID, LPIPS

La distancia de inicio de Frechet (FID) es una métrica utilizada para evaluar la calidad de las imágenes generadas por GAN. Se basa en la distancia de Fréchet entre la imagen generada y la imagen real , que mide la similitud entre las dos distribuciones de imágenes. Cuanto menor sea el FID, mejor será la calidad de la imagen generada.

Insertar descripción de la imagen aquí

LPIPS : Del artículo "La eficacia irrazonable de las características profundas como métrica de percepción",
Insertar descripción de la imagen aquí
cuanto menor es el valor, más similares son las dos imágenes. Las dos entradas se envían a la red neuronal F (puede ser VGG, Alexnet, Squeezenet) para la extracción de características, la salida de cada capa se activa y normaliza, y luego se calcula la distancia L2 después de multiplicar los pesos de las capas w.
Insertar descripción de la imagen aquí

La distancia de inicio del kernel (KID) es otra métrica para evaluar la calidad de las imágenes generadas por las GAN. Se basa en la función del núcleodistancia entre la imagen generada y la imagen real , que mide la diferencia entre la imagen real y la imagen generada mapeando los vectores de características en un espacio de alta dimensión y calculando la distancia de Frechet de la matriz del núcleo entre a ellos. KID también es una medida de la calidad de la imagen generada, similar a FID.

Para la implementación de código específico, consulte https://blog.51cto.com/u_16175458/6906283

2. Regularización de televisión

Regularización de TV, el nombre completo es Regularización de variación total. La regularización de TV logra suavizar la imagen minimizando la magnitud del gradiente de la imagen.

Específicamente, para una imagen bidimensional, la regularización de TV puede lograr un suavizado minimizando la magnitud del gradiente de la imagen . Esto suprime el ruido y los detalles de la imagen. Hace que la imagen sea más suave. La regularización de TV generalmente se aplica al término de regularización de problemas de optimización para equilibrar la relación entre el ajuste de datos y la suavidad.

Para métodos como NeRF (Neural Radiance Fields), que se utilizan principalmente para la reconstrucción tridimensional, la regularización de TV ayuda a mejorar la calidad de los resultados de la reconstrucción. Esto se debe a que en la reconstrucción 3D, debido a factores como la escasez de datos y el ruido, los resultados de la reconstrucción a menudo contienen detalles y ruido innecesarios .

Además, la regularización de TV también puede ayudar a fortalecer las restricciones del modelo de aprendizaje profundo y ayudar a mejorar la capacidad de generalización y la capacidad anti-ruido del modelo. Por lo tanto, para tareas de reconstrucción 3D como NeRF, la aplicación de la regularización de TV ayuda a mejorar la calidad de los resultados de la reconstrucción y aumentar la solidez del modelo.

3.Representación NeRF (desde los parámetros internos y externos de la cámara hasta los rayos)

rayos_o, rayos_d = get_cam_rays(poses, intrinsics, h, w)
analiza principalmente la función get_cam_rays, ubicada en lib/core/utils/nerf_utils.py

directions = get_ray_directions(h, w, intrinsics, norm=False)  # (8, 251, h, w,3)

      def get_ray_directions():
	    batch_size = intrinsics.shape[:-1]
	    x = torch.linspace(0.5, w - 0.5, w, device=device)
	    y = torch.linspace(0.5, h - 0.5, h, device=device)
	    
	    # 相对坐标,除以焦距,就是射线方向----------------------------------------------
	    directions_xy = torch.stack(
	        [((x - intrinsics[..., 2:3]) / intrinsics[..., 0:1])[..., None, :].expand(*batch_size, h, w),
	         ((y - intrinsics[..., 3:4]) / intrinsics[..., 1:2])[..., :, None].expand(*batch_size, h, w)], dim=-1)
	    # (8,251,128,128, 2)
	    directions = F.pad(directions_xy, [0, 1], mode='constant', value=1.0)
	    
	    return directions


rays_o, rays_d = get_rays(directions, c2w, norm=True)

       def get_rays(directions, c2w, norm=False):
		   rays_d = directions @ c2w[..., None, :3, :3].transpose(-1, -2)   # (8,251,128,128,3)
		   rays_o = c2w[..., None, None, :3, 3].expand(rays_d.shape)        # (8,251,128,128,3)
		   if norm:
		       rays_d = F.normalize(rays_d, dim=-1)                         # 坐标归一化到 -11之间
		   return rays_o, rays_d

Supongo que te gusta

Origin blog.csdn.net/qq_45752541/article/details/134984758
Recomendado
Clasificación