[Generación y reconstrucción 3D] ZeroRF: reconstrucción rápida de 360° con vista dispersa con preentrenamiento cero

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

Título : ZeroRF: Reconstrucción rápida y dispersa de 360◦ con entrenamiento previo cero
Tarea : Reconstrucción dispersa; Extensión: Imagen a 3D, Texto a 3D
Autor : Ruoxi Shi* Xinyue Wei* Cheng Wang Hao Su, de UC San Diego
Código : https:// github.com/eliphatfs/zerorf



Resumen

  ZeroRF, un nuevo método de optimización escena por escena para la reconstrucción de 360° de vistas dispersas en campos neuronales. Actualmente, NeRF (Neural Radiation Field) ha demostrado una síntesis de imágenes de alta fidelidad, pero es difícil trabajar con vistas de entrada escasas y enfrenta limitaciones en la dependencia de los datos, el costo computacional y la generalización en diferentes escenarios. Para superar estos desafíos, la clave de ZeroRF es integrar primero una imagen de profundidad personalizada en una representación NeRF factorizada . A diferencia de los métodos tradicionales, ZeroRF utiliza un generador de red neuronal para parametrizar mallas de características , lo que permite una reconstrucción eficiente de vista dispersa de 360° sin ningún entrenamiento previo ni regularización adicional. Y se puede extender a aplicaciones de generación y edición de contenidos 3D.

I. Introducción

  Los avances en la representación de campos neuronales, como NeRF y sus desarrollos posteriores, permiten la síntesis de imágenes de alta fidelidad, la optimización acelerada y diversas aplicaciones posteriores, pero dependen de vistas de entrada densas. Especialmente cuando se trata de tareas de generación de contenidos 3D. Por tanto, resolver la reconstrucción desde un punto de vista escaso es un desafío importante.

  En los últimos años, los métodos de reconstrucción en vista dispersa [7, 23, 26, 33, 41, 60, 64, 66, 77] han atraído una atención cada vez mayor. Un enfoque de [7, 28, 33, 77], a menudo denominado nerf generalizable, se basa en un entrenamiento previo extenso con requisitos sustanciales de tiempo y datos para reconstruir directamente la escena de interés. Por tanto, el rendimiento de estos modelos está estrechamente relacionado con la calidad de los datos de entrenamiento , y la resolución de grandes redes neuronales está limitada debido al alto coste computacional de las grandes redes neuronales. Además, estos modelos son difíciles de generalizar efectivamente en diferentes escenarios. Otros métodos que siguen el paradigma de optimización escena por escena también incluyen módulos adicionales como modelos de lenguaje visual [23] y estimadores de profundidad [64] para ayudar en la reconstrucción , que han demostrado ser efectivos en términos de líneas de base estrechas pero funcionan mal en reconstrucciones de 360°. . Además, la aplicabilidad a datos del mundo real es limitada debido a la dependencia de una supervisión adicional, que puede no estar siempre disponible o ser precisa . Las personas también tienen antecedentes diseñados a mano que abarcan la continuidad [41], la teoría de la información [26], la simetría [51] y la regularización de la frecuencia [73]. Sin embargo, una regularización adicional puede impedir que NeRF reconstruya fielmente la escena [73]. Además, los antecedentes hechos a mano a menudo no logran adaptarse a cambios de configuración bastante sutiles.

  Además, los métodos de optimización escena por escena existentes para la reconstrucción de 360°, que normalmente requieren horas de entrenamiento (incluso en GPU grandes), convergen mucho más rápido que las representaciones NeRF, como Instant NGP [39] o TensoRF [8]. Más lento y difícil. aplicar en la práctica.

  Colocamos un TensoRF [8] en la escena de Lego del conjunto de datos sintéticos NeRF usando 4 y 100 ángulos de visión respectivamente, y después de que el entrenamiento convergiera, visualizamos un canal de la característica plana, como se muestra en la Figura 2.

Insertar descripción de la imagen aquí

Puede ver claramente que en la configuración de vista dispersa 4 los efectos de iluminación crean características ruidosas y distorsionadas, mientras que en la vista densa (100) los planos de características se ven exactamente como las imágenes de proyección ortográfica de Lego. Realizamos experimentos similares en representaciones de triplano [6, 17] y campos de diccionario [9] y descubrimos que esto no es específico de TensoRF sino que es un fenómeno general para estas representaciones de factorización basadas en cuadrículas . Por lo tanto, se puede plantear la hipótesis de que, bajo supervisión de vista dispersa, vista dispersa optimizada rápidamente, se puede lograr la reconstrucción si las características factorizadas se mantienen limpias .

  Propuesta: integrar una imagen de profundidad recortada anterior [61] en una representación NeRF factorizada (ver Figura 3). En lugar de optimizar directamente la cuadrícula de características como TensoRF, K-planes o Dictionary Fields [8, 9, 17], ZeroRF utiliza una red neuronal profunda (generador) inicializada aleatoriamente para parametrizar la cuadrícula de características.
La intuición detrás de esto es que bajo una supervisión incierta, las redes neuronales se generalizan mejor que las redes de búsqueda en la gran mayoría de los casos. De manera más teórica, las redes neuronales tienen mayor resistencia al ruido y los artefactos que los datos que son fáciles de percibir y recordar [21, 22, 61]. El diseño no requiere ninguna regularización o entrenamiento previo adicional y se puede aplicar de manera uniforme a múltiples representaciones. La parametrización también es "sin pérdidas" en el sentido de que existe un conjunto de parámetros de red profundos de modo que se puede lograr cualquier malla de características objetivo determinada.

  ZeroRF, como nuevo método de optimización escena por escena, realiza experimentos extensos en diferentes redes generativas para la parametrización y diferentes representaciones de descomposición para encontrar la reconstrucción combinada de 360◦ de vista dispersa más adecuada.

  1. No se requiere ningún modelo de entrenamiento previo, lo que evita posibles sesgos en los datos de entrenamiento y restricciones en configuraciones como la resolución o la distribución de la cámara;
  2. El entrenamiento y la inferencia son rápidos porque se basan en una representación NeRF descompuesta y se ejecutan en menos de 30 segundos;
  3. Tiene el mismo poder expresivo teórico que la representación factorizada subyacente;
  4. Nueva síntesis de vista de calidad de última generación utilizando entrada de vista dispersa en síntesis de red [38] y puntos de referencia de iluminación abierta [30]

Dada la capacidad de reconstrucción de 360° de alta calidad de ZeroRF, nuestro método se puede aplicar en varios campos, incluida la generación y edición de contenido 3D.

2. Trabajo relacionado y conocimientos previos

2.1 Síntesis de nueva perspectiva

  La tecnología de renderizado neuronal allana el camino para lograr una calidad de renderizado fotorrealista en una nueva síntesis de vistas: Neural Radiation Fields (NeRF) es el primero en introducir perceptrones multicapa (MLP) para almacenar campos de radiación y permitir un renderizado significativo con calidad de renderizado de volumen. Posteriormente, Plenoxels y DVGO adoptaron representación basada en vóxeles; TensoRF, Instant-NGP y DiF [9] propusieron estrategias de descomposición para acelerar el entrenamiento; MipNeRF y RefNeRF fueron MLP basados ​​en coordenadas, y Point-NeRF se basó en representación basada en nubes de puntos. Algunos métodos reemplazan el campo de densidad con una función de distancia con signo (SDF) [Neuralangelo, Unisurf, Permutosdf, Neus] o convierten el campo de densidad en una representación de cuadrícula [Mobilenerf, Neumanifold, Bakedsdf] para mejorar la reconstrucción de la superficie. Estos métodos pueden extraer mallas de alta calidad sin afectar seriamente su calidad de renderizado. Además, el trabajo de [salpicadura gaussiana 3d, salpicadura gaussiana 4d, gaussianas 3d deformables] ha utilizado la salpicadura gaussiana para lograr la representación del campo de radiación en tiempo real.

2.2 Red profunda antes

  Si bien generalmente se cree que el éxito de las redes neuronales profundas se debe a su capacidad para aprender de conjuntos de datos a gran escala, la arquitectura de las redes profundas en realidad captura una gran cantidad de características antes de cualquier aprendizaje.
Entrenar un clasificador lineal sobre características de una red convolucional estocástica puede producir un rendimiento mucho mayor que la adivinación aleatoria [20]. Las redes inicializadas aleatoriamente también se caracterizan por tener pocos alumnos [1, 18, 50]. Lo anterior se puede avanzar aún más mediante la destilación de estas características aleatorias, a partir de este sesgo inductivo y utilizando diferentes enfoques para mejorar el aprendizaje de la representación de imágenes.

  A diferencia de estos trabajos, la imagen de profundidad previa [61] explota directamente esta profundidad previa sin más destilación . Los resultados muestran que el generador GAN se puede utilizar como una estructura parametrizada con alta impedancia de ruido y, por lo tanto, se puede aplicar a tareas de restauración de imágenes como eliminación de ruido, superresolución y pintura. Esto se aplicará aún más a diversas aplicaciones de imágenes y microscopía [36, 43, 54, 55, 62] y se ampliará con mejoras teóricas y prácticas en decodificadores de profundidad [21, 22]. ZeroRF sigue un paradigma similar al incorporar antecedentes profundos en la parametrización del campo de radiación .

2.3 Reconstrucción de vista dispersa

  NeRF presenta limitaciones con escasas observaciones debido a información insuficiente. Para resolver el desafío, algunos métodos se entrenan previamente [Mvsnerf, SRF, Sharf, Grf, pixelnerf] en una gran cantidad de conjuntos de datos para transferir conocimientos previos y ajustar el modelo en la escena de destino . Por el contrario, otro enfoque de investigación se centra en optimizar cada escenario mediante métodos de regularización diseñados manualmente [Poner a nerf a dieta, Flipnerf, Mixnerf, Geconer, Sparf, Freenerf] . Por ejemplo, para aumentar la coherencia semántica, DietNeRF utiliza el transformador visual CLIP para extraer características de alto nivel. Muchos de ellos diseñan funciones de pérdida para mitigar las inconsistencias entre vistas, ya sea basándose en la teoría de la información. SPARF utiliza redes previamente entrenadas para correspondencia o estimación de profundidad para compensar la falta de información 3D.

2.4 NeRF

  NeRF representa un campo de radiación de escena tridimensional a través de MLP: ingresando una posición 3D x y la dirección de vista d, genera la densidad de volumen σ x y el color relacionado con la vista c x :
Insertar descripción de la imagen aquí

2.5 TensoRF

  TensoRF reemplaza MLP en NeRF y selecciona un volumen de características para acelerar el entrenamiento: utiliza la descomposición CANDECOMP/PARAFAC o descomposición VM para descomponer el volumen de características en factores. ZeroRF usa descomposición VM: dado un tensor tridimensional T∈R I,J,K , descompone un tensor en múltiples vectores y matrices:
Insertar descripción de la imagen aquí

donde v r a es el factor vectorial y M r b, c es el factor matricial.

3. Método de este artículo.

  La tubería ZeroRF se muestra en la Figura 3: utilizando una red generadora profunda de muestras de ruido gaussiano estándar congeladas como entrada, se generan planos y vectores a la manera de TensoRF-VM para formar un volumen de características tensoriales descompuestos. Luego, el volumen de la característica se muestrea en el rayo de renderizado y se decodifica mediante un perceptrón multicapa (MLP) (proceso de renderizado de volumen estándar con pérdida de MSE).

  La idea principal de ZeroRF es aplicar una red de generación profunda no entrenada como parametrización de la cuadrícula de características espaciales ( consulte la red Tensorial3D en el código para obtener más detalles: genere dos tensores 3D para formar el espacio de características del código ). La red puede aprender patrones a diferentes escalas a partir de observaciones dispersas, generalizándose naturalmente a vistas no vistas, sin requerir más trucos de muestreo ni regularización explícita, lo que generalmente requiere un ajuste manual extenso, a diferencia del trabajo previo sobre reconstrucción de vistas dispersas.
Los diseños importantes son los siguientes: composición espacial (representación de volúmenes de características), la estructura del generador de representación y la estructura del decodificador de características .

Insertar descripción de la imagen aquí

3.1 Descomposición del volumen de características

  Los principios de aplicación de redes generativas profundas para la parametrización son generalmente aplicables a cualquier representación basada en cuadrícula. La solución más sencilla es parametrizar una función Volumen directamente . Sin embargo, si se desea una alta calidad de renderizado, el volumen de funciones será particularmente grande, consumirá memoria y será computacionalmente ineficiente. TensorRF utiliza la descomposición tensorial para explotar la naturaleza de bajo rango de los volúmenes de características. Cuando el vector es una constante, la representación de tres planos utilizada en [17] puede considerarse como un caso especial del vector representado por TensoRF-VM. DiF descompone el volumen de la característica en múltiples volúmenes más pequeños que codifican diferentes frecuencias . Instant-NGP [39] adopta un mapa hash de resolución múltiple porque la información en las características es de naturaleza escasa.

  En estas descomposiciones, el hash rompe la correlación espacial entre unidades adyacentes, por lo que no se pueden aplicar prioridades de profundidad. Hay redes generativas profundas disponibles para parametrización ( TensorRF, triplano y DiF ).
Construimos arquitecturas generadoras para generar vectores 1D, matrices 2D y volúmenes 3D, en base a las cuales experimentamos con las tres descomposiciones . Debido a principios de funcionamiento similares, se obtiene un mejor rendimiento que las tecnologías anteriores; TensoRF-VM tiene el mejor rendimiento y es la opción final para la factorización.

3.2 Arquitectura del generador

  La calidad de la parametrización profunda depende en gran medida del marco. Hasta ahora, la mayoría de los generadores son arquitecturas Conv y Attention , incluido el decodificador profundo (DD), la difusión estable (SD), el codificador automático variacional (VAE), el decodificador en Kadinsky y la generación SimMIM basada en un dispositivo decodificador ViT . ZeroRF convierte capas de convolución, agrupación y muestreo ascendente 2D en 1D y 3D para obtener los generadores 1D y 3D correspondientes necesarios para diferentes descomposiciones .

  Inicialmente, estos generadores son bastante grandes, ya que están diseñados para encajar en un conjunto de datos muy grande para producir contenido de alta calidad . Esto dará como resultado tiempos de ejecución innecesariamente largos y una convergencia más lenta cuando se trata de escenarios NeRF individuales. Afortunadamente, descubrimos que el rendimiento de ZeroRF después de la convergencia permanece sin cambios cuando reducimos el ancho y la profundidad del modelo. Por lo tanto, mantenemos la composición de los bloques pero modificamos los tamaños de estas arquitecturas para mejorar la velocidad del entrenamiento . Tenga en cuenta que durante la inferencia solo necesitamos almacenar la representación del campo de radiación y no el generador, por lo que durante el renderizado ZeroRF tiene cero gastos generales en comparación con su método de factorización subyacente .

  Descubrimos que el SD VAE y su parte decodificadora, así como el decodificador Kadinsky, son igualmente eficaces en la síntesis de nuevas vistas, seguido del decodificador de profundidad, mientras que la arquitectura SimMIM, como profundidad anterior al campo de radiación, resulta ineficaz . El codificador SD/Kadinsky es principalmente la estructura de convolución original, Kadinsky agrega atención propia en los dos primeros bloques. Elegimos el decodificador SD (modificado) como la opción final de arquitectura del generador porque es el menos intensivo desde el punto de vista computacional .

3.3 Arquitectura del decodificador

  Nuestra arquitectura de decodificador sigue SSDNeRF : decodifica desde la cuadrícula de características con interpolación lineal (bilineal o trilineal), la proyecta con la primera capa lineal, lo que da como resultado un código de características básico compartido entre la decodificación de densidad y apariencia . Descubrimos que el código de característica compartido puede ayudar a reducir las moscas volantes al acoplar estrechamente la geometría y la apariencia . Aplique la activación SiLU y llame a otra capa lineal para la predicción de la densidad . Para la predicción del color , codificamos la orientación de la vista con armónicos esféricos (SH) y la agregamos a las características base mediante la proyección de capas lineales para agregar dependencia de la vista. Luego aplicamos la activación SiLU y predecimos los valores RGB usando otra capa lineal similar a la predicción de densidad, expresada de la siguiente manera:

Insertar descripción de la imagen aquí

F x es el campo característico, σ(·) es la función sigmoidea y Θ• es la capa lineal. A diferencia de los decodificadores utilizados en TensoRF y DiF, este decodificador no consume ninguna codificación posicional , que de otro modo podría filtrar información posicional (más allá de la profundidad anterior), destruyendo o degradando el rendimiento de ZeroRF .

4. Experimentar

4.1 Configuración experimental

  El experimento utilizó el optimizador AdamW, β 1 = 0,9, β 2 = 0,98 y atenuación de peso 0,2. La tasa de aprendizaje comienza en 0,002 y decae a 0,001 en un programa de cosenos. Entrene ZeroRF para 10.000 iteraciones. Durante el proceso de renderizado de volumen, tomamos muestras uniformes de 1024 puntos para cada rayo y utilizamos la poda de ocupación y la selección de oclusión para acelerar este proceso .

4.2 Conjuntos de datos e indicadores

  Las métricas de evaluación son PSNR, SSIM y L PIPS ; todas las vistas de entrada se obtienen ejecutando KMeans en el vector de transformación de la cámara y seleccionando la vista más cercana al centroide del clúster.

  1. Conjunto de datos sintéticos NeRF :
      el conjunto de datos sintéticos NeRF contiene 8 objetos de diferentes materiales y geometrías. Los experimentos se realizaron utilizando 4 o 6 vistas como entrada y el modelo se evaluó en 200 vistas de prueba.

  2. Conjunto de datos de OpenIllumination :

  Un conjunto de datos del mundo real a nivel de luz de 8 objetos con geometrías complejas bajo iluminación única, 4 o 6 vistas extraídas de 38 vistas de entrenamiento y evaluadas en 10 vistas de prueba.

  1. Conjunto de datos DTU :

  DTU se centra principalmente en objetos orientados hacia adelante en lugar de reconstrucción de 360°, pero para completar, incluimos nuestros resultados sobre DTU en la Figura 6. Usamos 3 vistas como entrada y probamos el modelo en las vistas restantes.

4.2 Visualización de resultados

  Comparación con el método NeRF de pocos disparos de última generación:

1.RegNeRF: basado en continuidad y regularización RealNVP previamente entrenada,
2.DietNeRF: usando el modelo CLIP previamente entrenado
3.InfoNeRF: usando entropía como regularizador
4.FreeNeRF: basado en regularización de frecuencia
5.FlipNeRF: usando simetría espacial previa

Insertar descripción de la imagen aquí

Insertar descripción de la imagen aquí

  La mayoría de los modelos de referencia exhiben diversos grados de deficiencias obvias, incluidos cambios de color flotantes y notables en los resultados sintéticos (resaltados en el cuadro rojo de la figura). Para los antecedentes previamente entrenados, el modelo anterior RegNeRF no se entrenó en imágenes de referencia amplias y no logró reconstruir objetos en la configuración de 360◦; curiosamente, DietNeRF usando CLIP como modelo previo funcionó mejor en imágenes reales que en imágenes sintéticas. El efecto es mejor, lo que es consistente con la distribución de datos previos al entrenamiento de CLIP. FreeNeRF y FlipNeRF funcionan relativamente bien en síntesis nerf, pero fallan en OpenIllumination.

4.3 Análisis

  El impacto del número de visualizaciones escasas . El experimento de diseño se muestra en la Figura 7 : ZeroRF tiene una ventaja significativa sobre la representación base de TensoRF en vistas dispersas; cuando las vistas se vuelven más densas, ZeroRF sigue siendo competitivo, aunque con una ventaja menor.

  Característica Método de descomposición del volumen . Aplicamos el generador ZeroRF a triplanar, TensoRF y DiF y comparamos el rendimiento en el conjunto de datos sintéticos NeRF (entrada de 6 vistas). Los resultados se muestran en la Tabla 3 . Las inclusiones del generador mejoran continuamente la representación base y ambos logran un rendimiento de última generación. Esto muestra que el principio de utilizar parametrización profunda es generalmente aplicable a representaciones basadas en cuadrículas.

Insertar descripción de la imagen aquí

  Marco generador . La Tabla 4 compara las características del suroeste de diferentes estructuras y la Figura 8 visualiza un canal de diferentes características planas del Generador. Sin ningún plano previo y directamente optimizado, las características tienen ruido de alta frecuencia y límites de visión visibles. En comparación, el decodificador SD y el modelo Kadinsky produjeron características limpias y buenas. El decodificador ViT totalmente atento de SimMIM utiliza partición de parches y se pueden ver artefactos en bloques. MLP supone una transición muy suave en la malla y, por lo tanto, no representa fielmente el contenido de la escena. En general, las arquitecturas convolucionales producen características que son más consistentes con la escena.

Insertar descripción de la imagen aquí
  Importancia del ruido . El ruido de entrada es la clave a priori. Reemplace esto con características entrenables inicializadas en cero y el marco se romperá por completo (última fila de la Tabla 4). No se observa ninguna mejora en el rendimiento si se descongela el ruido: dado que la tasa de aprendizaje es pequeña en comparación con el tamaño del ruido, la estructura del ruido permanece sin cambios durante el entrenamiento. Pero introduce una sobrecarga adicional y ralentiza la convergencia. Por ello, mantenemos el ruido congelado durante el entrenamiento.

5. Aplicaciones específicas

5.1 Texto a 3D e Imagen a 3D.

  Teniendo en cuenta las poderosas capacidades de reconstrucción de volúmenes dispersos de ZeroRF, una idea simple es utilizar modelos existentes para realizar una generación consistente de múltiples vistas y aplicar ZeroRF para elevar las vistas dispersas a 3D. Tarea de imagen a 3D, use Zero123++ para actualizar una sola imagen a 6 vistas y ajuste un ZeroRF en la imagen generada. Para texto a 3D, primero se llama a SDXL para generar una imagen a partir del texto y se aplica el proceso de imagen a 3D descrito anteriormente. Como se muestra en la Figura 9, ZeroRF puede producir reconstrucciones confiables de alta calidad a partir de las imágenes multivista generadas. La instalación de ZeroRF tarda solo 30 segundos en la GPU A100.

Insertar descripción de la imagen aquí

5.2 Texturas de malla y edición de texturas.

  Texturizado de malla y edición de texturas. ZeroRF también puede usar la geometría congelada proporcionada para reconstruir la apariencia: renderice aleatoriamente 4 imágenes de la malla, colóquelas en una imagen grande y aplique Instruct-Pix2Pix [4] para editar la imagen según las indicaciones del texto. Luego, coloque un ZeroRF en las cuatro imágenes y hornee los valores de color nuevamente en la superficie del puré. En este caso, instalar ZeroRF solo lleva 20 segundos, como se muestra en la Figura 10.

Insertar descripción de la imagen aquí

2. Leer datos

El código es el siguiente (ejemplo):

data = pd.read_csv(
    'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())

Los datos solicitados por la red URL utilizada aquí.


6. Análisis de código

1. Entorno de instalación

Primero instale los entornos python3.7 y cuda11.x:

# bashrc文件中,将默认cuda指向11.5
export PATH=/usr/local/cuda-11.5/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-11.5/lib64:$LD_LIBRARY_PATH

# 创建环境
conda create -y -n ssdnerf python=3.7
conda activate ssdnerf

# 安装 PyTorch
conda install pytorch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 cudatoolkit=11.3 -c pytorch

# 安装 MMCV and MMGeneration
pip install -U openmim
mim install mmcv-full==1.6
git clone https://github.com/open-mmlab/mmgeneration && cd mmgeneration && git checkout v0.7.2
pip install -v -e .
cd ..

# 安装 SpConv
pip install spconv-cu114

# Clone this repo and install other dependencies
git clone <this repo> && cd <repo folder> && git checkout ssdnerf-sd
pip install -r requirements.txt

# 安装 InstantNGP依赖
git clone https://github.com/ashawkey/torch-ngp.git

cd lib/ops/raymarching/
pip install -e .
cd ../shencoder/
pip install -e .
cd ../../..

2.Imagen a 3D

Primero, necesita usar Zero123++ (https://github.com/SUDO-AI-3D/zero123plus) para generar 6 vistas RGBD a partir de una sola imagen. La imagen de muestra es la siguiente:
Insertar descripción de la imagen aquí
Ejecute el siguiente código (debe iniciar sesión en su cuenta wandb con anticipación):

python zerorf.py --load-image=examples/ice.png

Análisis de código Zerorf.py: ingrese solo una imagen para generar un video de 360 ​​​​grados:

# 1.读入6张图像(默认已经用zero123++模型,生成了6个视角)-------------------------------------------------------
image = torch.tensor(numpy.array(Image.open(args.load_image)).astype(numpy.float32) / 255.0).cuda()                                      # (960,640,4)
images = einops.rearrange(image, '(ph h) (pw w) c -> (ph pw) h w c', ph=3, pw=2)[None]  # (1,6,320,320,4)

# 2.读入meta,生成每张图的intri(内参)和pose(外参)-----------------------------------------------
cond_intrinsics = data['cond_intrinsics']  # [fx, fy, cx, cy]  [350,350,160,160]

BLENDER_TO_OPENCV_MATRIX = numpy.array([
    [1,  0,  0,  0],
    [0, -1,  0,  0],
    [0,  0, -1,  0],
    [0,  0,  0,  1]
], dtype=numpy.float32)

poses = numpy.array([(numpy.array(frame['transform_matrix']) @ BLENDER_TO_OPENCV_MATRIX) * 2
        for frame in meta['sample_0']['view_frames']])      # 外参pose默认是固定的。有兴趣可以研究zero123模型

La pose del parámetro externo de la imagen está guardada previamente en mata.json y los valores específicos se muestran en la siguiente figura:

Insertar descripción de la imagen aquí

# 3.读入缓存(第一次循环没有缓存,默认生成4个纯0向量)--------------------------------------------------------
code_list_, code_optimizers, density_grid, density_bitfield = self.load_cache(data)

# 4.根据内外参,生成射线---------------------------------------------------------------------------------
cond_rays_o, cond_rays_d = get_cam_rays(cond_poses, cond_intrinsics, h, w)        # 还是6张图的

# 5.decode 过程
loss, log_vars, out_rgbs, target_rgbs = self.loss_decoder( self.decoder, code, density_bitfield, cond_rays_o, cond_rays_d, ond_imgs)

loss.backward()


# 4.射线中采样batch(4096):self.ray_sample:-----------------------------------------------------
rays_o = cond_rays_o.reshape(num_scenes, num_scene_pixels, 3)    # 320*320*6=614400 -> (1,614400,3)
rays_d = cond_rays_d.reshape(num_scenes, num_scene_pixels, 3)    # 320*320*6=614400 -> (1,614400,3)
if num_scene_pixels > n_samples:                                 # 总射线数 > 4096
        sample_inds = [torch.randperm( target_rgbs.size(1), device=device
                     )[:n_samples if self.patch_loss is None and self.patch_reg_loss is None else n_samples // (self.patch_size ** 2)]
                      for _ in range(num_scenes)]                # 614400 根射线中,随机选4096根
        sample_inds = torch.stack(sample_inds, dim=0)
    scene_arange = torch.arange(num_scenes, device=device)[:, None]
    rays_o = rays_o[scene_arange, sample_inds]          # (1,4096,3)
    rays_d = rays_d[scene_arange, sample_inds]          # (1,4096,3)
    target_rgbs = target_rgbs[scene_arange, sample_inds]         # (1,4096,4)

# 4.5 初始化 code
code = code.to(next(self.parameters()).dtype)          # (1,3,8,20,20)初始化的全0向量

# 4.6 更新 code
for _ in range(update_extra_state):
    self.update_extra_state(code, *extra_args, **extra_kwargs)

#----------------------------以下是VolumeRenderer中的decode过程:------------------------------------

# 5.prepro(code)过程,用于生成三为特征。重点代码为:
self.code_proc_buffer = self.code_proc_pr_inv(self.preprocessor(self.code_proc_pr(code))
# self.preprocessor:TensorialGenerator生成两个3D张量-----------------------------------------------------------
	# 循环两次,通过随即噪声,生成两个Tensorial 3D张量 --> (2,524288=64^3)
	class TensorialGenerator(nn.Module):
	
	        self.subs = nn.ModuleList([(Tensorial3D)(in_ch=8, out_ch=16, noise_res=4)])  # 这里可选Tensorial1D Tensorial2D
	
	    def forward(self, _):
	        r = []
	        for sub in self.subs:
	            sub_out = sub()                            # 从噪音生成特征:(1,16,32,32,32)
	            r.append(torch.flatten(sub_out, 1))        # (1,16,32,32,32-> (524288)
	        return torch.cat(r, 1)                         # 循环两次,生成两个Tensorial 3D张量 --> (1,1048576)
## 其中,Tensorial3D网络随机生成噪声,其网络架构如下图:

Insertar descripción de la imagen aquí

6. Generar coordenadas densamente muestreadas---------------------------------------------------- ------------------------------------------------- - ----------------------------------

grid_size =  64
# 间隔采样
X = torch.arange(grid_size, dtype=torch.int32, device=device).split(S)   # (64):[0,1,2,...]
Y = torch.arange(grid_size, dtype=torch.int32, device=device).split(S)   # (64):[0,1,2,...]
Z = torch.arange(grid_size, dtype=torch.int32, device=device).split(S)   # (64):[0,1,2,...]

for xs in X:
    for ys in Y:
        for zs in Z:
            # 生成3D坐标
            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)[0~64)之间 
            # 026243的索引,并打乱顺序
            indices = morton3D(coords).long()                 # [N=262144]
            # indices[0,4,32,36,256,260,288,292,2048,2052,2080,2084...262111, 262139, 262143]
            
            xyzs = (coords.float() - (grid_size - 1) / 2) * (2 * self.bound / grid_size)   # 归一化到(-1,1)
            
            # 添加噪声
            half_voxel_width = self.bound / grid_size    # 1/64
            xyzs += torch.rand_like(xyzs) * (2 * half_voxel_width) - half_voxel_width
# 7.point_decode-----------------------------------------------------------------------

输入:code(dens.color两个3D特征块: 2*16,32,32,32)),针对xyzs(262144=64^3, 3)做特征插值,得到(262144,16*2 输出
point_code = self.get_point_code(code, xyzs)
# self.get_point_code 具体展开如下:
class FreqFactorizedDecoder(TensorialDecoder):
    def get_point_code(self, code, xyzs):
        for i, (cfg, band) in enumerate(zip(preprocessor.tensor_config, self.freq_bands)):
            start = sum(map(numpy.prod, preprocessor.sub_shapes[:i]))         # 0
            end = sum(map(numpy.prod, preprocessor.sub_shapes[:i + 1]))       # 524288 = 16*32^3
            got: torch.Tensor = code[..., start: end].reshape(code.shape[0], *preprocessor.sub_shapes[i])     # (1,16,32,32,32)
            assert len(cfg) + 2 == got.ndim == 5, [len(cfg), got.ndim]
            coords = xyzs[..., ['xyzt'.index(axis) for axis in cfg]]          # (1,262144,3)
            if band is not None:
                coords = ((coords % band) / (band / 2) - 1)
            coords = coords.reshape(code.shape[0], 1, 1, xyzs.shape[-2], 3)   # (1,1,1,262144,3)
            codes.append(
                F.grid_sample(got, coords, mode='bilinear', padding_mode='border', align_corners=False)
                .reshape(code.shape[0], got.shape[1], xyzs.shape[-2]).transpose(1, 2)
            )                                                               # (262144,16)两次循环 got3D 特征不同
        return torch.cat(codes, dim=-1)

8. Luego viene el renderizado: ------------------------------------------------- ---- ---------------------------------------------- ---- ----

sigmas, rgbs = self.point_code_render(point_code, dirs=Ninguno)

La característica point_code obtenida en el paso anterior no representa el color y la densidad por separado:

# 8.1 公共特征point_code,渲染sigma---------------------------------------------------------------
base_x = self.base_net(point_code)                  # linear:(32,64)
base_x_act = self.base_activation(base_x)           # Silu
sigmas = self.density_net(base_x_act).squeeze(-1)   # linear:(64,1)  ->(262144,1)

# 8.2 渲染RGB-----------------------------------------------------------------------------
if dirs is None:
    rgbs = None


#
density_grid = sigma 
mean_density = torch.mean(density_grid.clamp(min=0))  # - 0.977
density_thresh = min(mean_density, 0.05)

# 9. near_far_from_aabb
self.aabb:[-1,-1,-1,1,1,1], 具体函数见最后拓展
nears, fars = batch_near_far_from_aabb(rays_o, rays_d, self.aabb.to(rays_o), self.min_near)    # (1,4096)(1,4096)



# 10.march_rays_train 采样渲染(这块封装成c语言了,代码可见拓展)
# 该代码接受光线的起点、方向、网格数据等作为输入,并根据特定的算法进行光线追踪,最终计算出光线与场景的交点及相关信息。这段代码是用于实现神经辐射场(NeRF)模型中的光线追踪部分。
    xyzs = torch.zeros(M, 3, dtype=rays_o.dtype, device=rays_o.device)
    dirs = torch.zeros(M, 3, dtype=rays_o.dtype, device=rays_o.device)
    ts = torch.zeros(M, 2, dtype=rays_o.dtype, device=rays_o.device)

    get_backend().march_rays_train(rays_o, rays_d, density_bitfield, bound, contract, dt_gamma, max_steps, N, C, H,
                                   nears, fars, xyzs, dirs, ts, rays, step_counter, noises)
return xyzs, dirs, ts, rays

Seguimiento por venir. . .

Expandir

提示:这里对文章进行总结:

1.codificación morton3D

  La codificación de Morton, también conocida como codificación Z, es un método utilizado para asignar coordenadas en un espacio multidimensional a un espacio unidimensional . Intercala los bits de las coordenadas de modo que las coordenadas adyacentes en el espacio unidimensional permanezcan adyacentes después de la codificación. Este método de codificación se usa comúnmente en indexación espacial, gráficos y gráficos por computadora para buscar y procesar datos rápidamente en espacios multidimensionales. Cuando usamos coordenadas 2D para la codificación Morton, supongamos que tenemos un punto con coordenadas 2D (3, 5) y su representación binaria es (011, 101) respectivamente. La codificación Morton entrelazará estos dos números binarios para obtener 010111, que es 23 en decimal. Esto asigna coordenadas bidimensionales a un valor en un espacio unidimensional. El objetivo principal de la codificación Morton es mantener las coordenadas adyacentes en el espacio unidimensional aún adyacentes después de la codificación.

void morton3D(const at::Tensor coords, const uint32_t N, at::Tensor indices);

2.near_far_from_aabb: Calcula los puntos más cercanos y más lejanos

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

Los pasos específicos son los siguientes:

1. Calcule el índice de rayos n procesado actualmente en función del índice de hilo.
2. Localice el punto de inicio y la dirección del rayo correspondiente según el índice del rayo.
3. Calcule los parámetros de intersección del rayo y AABB en los tres ejes de x, y y z.
4. Calcule los puntos cercano y lejano del rayo y AABB según los parámetros de intersección.
5. Los puntos cercanos y lejanos calculados se almacenan en cercanos y lejanos.

El principio de esta función del núcleo es utilizar la capacidad de computación paralela de CUDA para realizar cálculos independientes en cada rayo para acelerar el proceso de cálculo. A través de la computación paralela, se pueden procesar múltiples rayos simultáneamente, mejorando la eficiencia informática. Las funciones de llamada específicas son las siguientes:

__global__ void kernel_near_far_from_aabb(
    const scalar_t * __restrict__ rays_o,
    const scalar_t * __restrict__ rays_d,
    const scalar_t * __restrict__ aabb,
    const uint32_t N,
    const float min_near,
    scalar_t * nears, scalar_t * fars
) {
    
    
    // parallel per ray
    const uint32_t n = threadIdx.x + blockIdx.x * blockDim.x;
    if (n >= N) return;

    // locate
    rays_o += n * 3;
    rays_d += n * 3;

    const float ox = rays_o[0], oy = rays_o[1], oz = rays_o[2];
    const float dx = rays_d[0], dy = rays_d[1], dz = rays_d[2];
    const float rdx = 1 / dx, rdy = 1 / dy, rdz = 1 / dz;

    // get near far (assume cube scene)
    float near = (aabb[0] - ox) * rdx;
    float far = (aabb[3] - ox) * rdx;
    if (near > far) swapf(near, far);

    float near_y = (aabb[1] - oy) * rdy;
    float far_y = (aabb[4] - oy) * rdy;
    if (near_y > far_y) swapf(near_y, far_y);

    if (near > far_y || near_y > far) {
    
    
        nears[n] = fars[n] = std::numeric_limits<scalar_t>::max();
        return;
    }

    if (near_y > near) near = near_y;
    if (far_y < far) far = far_y;

    float near_z = (aabb[2] - oz) * rdz;
    float far_z = (aabb[5] - oz) * rdz;
    if (near_z > far_z) swapf(near_z, far_z);

    if (near > far_z || near_z > far) {
    
    
        nears[n] = fars[n] = std::numeric_limits<scalar_t>::max();
        return;
    }

    if (near_z > near) near = near_z;
    if (far_z < far) far = far_z;

    if (near < min_near) near = min_near;

    nears[n] = near;
    fars[n] = far;
}

3.march_rays_train

Este código acepta el punto de partida, la dirección, los datos de la cuadrícula, etc. de la luz como entrada, realiza el trazado de rayos de acuerdo con un algoritmo específico y finalmente calcula el punto de intersección de la luz y la escena y la información relacionada. Este código se utiliza para implementar la parte de trazado de rayos del modelo de campo de radiación neuronal (NeRF).

__global__ void kernel_march_rays_train(
    const scalar_t * __restrict__ rays_o,
    const scalar_t * __restrict__ rays_d,
    const uint8_t * __restrict__ grid,
    const float bound, const bool contract,
    const float dt_gamma, const uint32_t max_steps,
    const uint32_t N, const uint32_t C, const uint32_t H,
    const scalar_t* __restrict__ nears,
    const scalar_t* __restrict__ fars,
    scalar_t * xyzs, scalar_t * dirs, scalar_t * ts,
    int * rays,
    int * counter,
    const scalar_t* __restrict__ noises
) {
    
    
    // parallel per ray
    const uint32_t n = threadIdx.x + blockIdx.x * blockDim.x;
    if (n >= N) return;

    // is first pass running.
    const bool first_pass = (xyzs == nullptr);

    // locate
    rays_o += n * 3;
    rays_d += n * 3;
    rays += n * 2;

    uint32_t num_steps = max_steps;

    if (!first_pass) {
    
    
        uint32_t point_index = rays[0];
        num_steps = rays[1];
        xyzs += point_index * 3;
        dirs += point_index * 3;
        ts += point_index * 2;
    }

    // ray marching
    const float ox = rays_o[0], oy = rays_o[1], oz = rays_o[2];
    const float dx = rays_d[0], dy = rays_d[1], dz = rays_d[2];
    const float rdx = 1 / dx, rdy = 1 / dy, rdz = 1 / dz;
    const float rH = 1 / (float)H;
    const float H3 = H * H * H;

    const float near = nears[n];
    const float far = fars[n];
    const float noise = noises[n];

    const float dt_min = 2 * SQRT3() / max_steps;
    const float dt_max = 2 * SQRT3() * bound / H;
    // const float dt_max = 1e10f;

    float t0 = near;
    t0 += clamp(t0 * dt_gamma, dt_min, dt_max) * noise;
    float t = t0;
    uint32_t step = 0;

    //if (t < far) printf("valid ray %d t=%f near=%f far=%f \n", n, t, near, far);

    while (t < far && step < num_steps) {
    
    
        // current point
        const float x = clamp(ox + t * dx, -bound, bound);
        const float y = clamp(oy + t * dy, -bound, bound);
        const float z = clamp(oz + t * dz, -bound, bound);

        float dt = clamp(t * dt_gamma, dt_min, dt_max);

        // get mip level
        const int level = max(mip_from_pos(x, y, z, C), mip_from_dt(dt, H, C)); // range in [0, C - 1]

        const float mip_bound = fminf(scalbnf(1.0f, level), bound);
        const float mip_rbound = 1 / mip_bound;

        // contraction
        float cx = x, cy = y, cz = z;
        const float mag = fmaxf(fabsf(x), fmaxf(fabsf(y), fabsf(z)));
        if (contract && mag > 1) {
    
    
            // L-INF norm
            const float Linf_scale = (2 - 1 / mag) / mag;
            cx *= Linf_scale;
            cy *= Linf_scale;
            cz *= Linf_scale;
        }

        // convert to nearest grid position
        const int nx = clamp(0.5 * (cx * mip_rbound + 1) * H, 0.0f, (float)(H - 1));
        const int ny = clamp(0.5 * (cy * mip_rbound + 1) * H, 0.0f, (float)(H - 1));
        const int nz = clamp(0.5 * (cz * mip_rbound + 1) * H, 0.0f, (float)(H - 1));

        const uint32_t index = level * H3 + __morton3D(nx, ny, nz);
        const bool occ = grid[index / 8] & (1 << (index % 8));

        // if occpuied, advance a small step, and write to output
        //if (n == 0) printf("t=%f density=%f vs thresh=%f step=%d\n", t, density, density_thresh, step);

        if (occ) {
    
    
            step++;
            t += dt;
            if (!first_pass) {
    
    
                xyzs[0] = cx; // write contracted coordinates!
                xyzs[1] = cy;
                xyzs[2] = cz;
                dirs[0] = dx;
                dirs[1] = dy;
                dirs[2] = dz;
                ts[0] = t;
                ts[1] = dt;
                xyzs += 3;
                dirs += 3;
                ts += 2;
            }
        // contraction case: cannot apply voxel skipping.
        } else if (contract && mag > 1) {
    
    
            t += dt;
        // else, skip a large step (basically skip a voxel grid)
        } else {
    
    
            // calc distance to next voxel
            const float tx = (((nx + 0.5f + 0.5f * signf(dx)) * rH * 2 - 1) * mip_bound - cx) * rdx;
            const float ty = (((ny + 0.5f + 0.5f * signf(dy)) * rH * 2 - 1) * mip_bound - cy) * rdy;
            const float tz = (((nz + 0.5f + 0.5f * signf(dz)) * rH * 2 - 1) * mip_bound - cz) * rdz;

            const float tt = t + fmaxf(0.0f, fminf(tx, fminf(ty, tz)));
            // step until next voxel
            do {
    
    
                dt = clamp(t * dt_gamma, dt_min, dt_max);
                t += dt;
            } while (t < tt);
        }
    }

    //printf("[n=%d] step=%d, near=%f, far=%f, dt=%f, num_steps=%f\n", n, step, near, far, dt_min, (far - near) / dt_min);

    // write rays
    if (first_pass) {
    
    
        uint32_t point_index = atomicAdd(counter, step);
        rays[0] = point_index;
        rays[1] = step;
    }
}

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

Supongo que te gusta

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