[Génération et reconstruction 3D] SSDNeRF : génération et reconstruction 3D de NeRF de diffusion en un seul étage

Table des matières des articles de la série

Titre : NeRF de diffusion en une seule étape : une approche unifiée de la génération et de la reconstruction 3D
Article : https://arxiv.org/pdf/2304.06714.pdf
Tâche : Génération 3D inconditionnelle (telle que la génération de différentes voitures à partir du bruit, etc.), simple Voir
l'organisation de la génération 3D : Hansheng Chen,1,* Jiatao Gu,2 Anpei Chen, Tongji, Apple, University of California
Code : https://github.com/Lakonik/SSDNeRF


Résumé

  Tâches de synthèse d'images compatibles 3D, y compris la génération de scènes et la synthèse de nouvelles vues basées sur des images. Cet article propose SSDNeRF, qui utilise un modèle de diffusion pour apprendre des a priori généralisables pour les champs de rayonnement neuronal (NeRF) à partir d'images multi-vues de différents objets . Des études antérieures utilisaient une approche en deux étapes, s'appuyant sur le NeRF pré-entraîné comme données réelles pour entraîner des modèles de diffusion. En revanche, SSDNeRF, en tant que paradigme de formation de bout en bout en une seule étape, optimise conjointement les modèles de décodage automatique et de diffusion latente de NeRF pour réaliser simultanément une reconstruction 3D et un apprentissage préalable (y compris même des vues clairsemées). Lors des tests, l'a priori de diffusion peut être directement généré de manière inconditionnelle ou combiné avec des observations arbitraires d'objets invisibles pour effectuer une reconstruction NeRF. SSDNeRF montre des résultats robustes comparables ou meilleurs que les méthodes spécifiques à une tâche en termes de génération inconditionnelle et de reconstruction 3D à vue unique/ clairsemée.

Introduction

  Avec le développement du rendu neuronal et des modèles génératifs , des algorithmes uniques pour la génération de contenu 3D (tels que la reconstruction 3D à vue unique/multi-vue et la génération de contenu 3D) ont été développés, mais il manque un cadre complet pour connecter les technologies multitâches. Neural Radiation Fields (NeRF) résout le problème de rendu inverse grâce à un ajustement de super-résolution, montrant des résultats impressionnants dans la synthèse de nouvelles vues (mais applicables uniquement aux vues denses et difficiles à généraliser aux observations clairsemées). En revanche, de nombreuses méthodes de reconstruction 3D à vue clairsemée [pixelNeRF, Mvsnerf, ViT NeRF] s'appuient sur des encodeurs d'image en 3D à action directe, mais elles ne peuvent pas gérer l'ambiguïté des zones occluses et ne peuvent pas générer d'images claires. En termes de génération inconditionnelle, les réseaux contradictoires génératifs (GAN) sensibles à la 3D [34, 5, 18, 14] sont partiellement limités dans l'utilisation de discriminateurs à image unique, qui ne peuvent pas permettre d'apprendre efficacement des relations entre vues à partir de multi- afficher les données.

  Dans des travaux antérieurs [tels que Score-based NeRF, Gaudi, triplane diffusion, Diffrf] , des ldm similaires ont été appliqués à la génération 2D et 3D, mais ils nécessitent généralement deux étapes de formation. La première étape exclut le modèle de diffusion et uniquement pré- trains VAE (Autoencoder variable) ou décodeur automatique . Cependant, dans le cas du nerf de diffusion, nous pensons que la formation en deux étapes induit des modèles de bruit et des artefacts dans le code latent (en particulier avec des vues clairsemées) en raison de l'incertitude du rendu inverse, ce qui empêche le modèle de diffusion d'apprendre efficacement le potentiel propre. collecteur. Pour résoudre ce problème,

  Cet article propose un cadre unifié SSDNeRF (Single Stage Diffusion NeRF), qui utilise un modèle de diffusion latente tridimensionnelle (LDM) pour modéliser l'a priori de génération de code latent de la scène, et apprend un a priori tridimensionnel généralisable à partir d'images multi-vues pour gérer divers Tâche tridimensionnelle (Fig. 1). Le paradigme de formation en une seule étape permet un apprentissage de bout en bout des poids de diffusion et NeRF. Cette approche combine les biais générés et rendus de manière cohérente pour améliorer les performances globales et permet l'entraînement sur des données de vue clairsemées. De plus, l’a priori tridimensionnel appris du modèle de diffusion inconditionnelle peut être utilisé pour un échantillonnage flexible de scènes de test d’observations arbitraires.

Insérer la description de l'image ici

Les principales contributions sont les suivantes :

1. Proposer SSDNeRF, une méthode unifiée de génération 3D inconditionnelle et de reconstruction 3D à base d'images ; 2. En tant que nouveau paradigme de formation en une seule étape, SSDNeRF apprend conjointement les modèles de reconstruction NeRF et de diffusion
à partir d'images multi-vues d'un grand nombre d'objets. (même s'il n'y a que trois vues par scène) 3. Un schéma d'échantillonnage de guidage et de réglage fin est proposé pour utiliser la diffusion apprise avant d'effectuer une reconstruction 3D à partir d'un nombre quelconque de vues au moment du test.

2. Travaux connexes

2.1. GAN 3D

Les GAN ont été utilisés avec succès pour la génération tridimensionnelle   en intégrant le rendu basé sur la projection dans le générateur . Diverses représentations 3D ont été explorées précédemment (nuages ​​de points, cuboïdes, sphères [27] et voxels) ; plus récemment NeRF et champs de caractéristiques avec des rendus de volume [34, 18, 5], et avec des rendus de maillage de surface différentiable [14]. Les méthodes ci-dessus sont toutes entraînées avec des discriminateurs d'images 2D et ne peuvent pas raisonner sur les relations entre vues, ce qui les rend fortement dépendantes du biais du modèle de cohérence 3D et ne peuvent pas utiliser efficacement les données multi-vues pour apprendre des géométries complexes et diverses . Le gan tridimensionnel est principalement utilisé pour la génération inconditionnelle. Bien que la reconstruction 3D d'images puisse être complétée par l'inversion GAN [12], l'authenticité ne peut pas être garantie en raison de la capacité d'expression potentielle limitée, comme indiqué dans l'article [Diffrf, RenderDiffusion].

2.2 Régression et génération conditionnées par la vue

  La reconstruction 3D clairsemée peut être résolue en régressant de nouvelles vues de l'image d'entrée . Diverses architectures proposées [8, 59, 28, 61] codent l'image en caractéristiques de volume qui sont projetées sur une vue cible supervisée via le rendu de volume . Cependant, ils ne peuvent pas raisonner sur l’ambiguïté et produire un contenu diversifié et significatif, ce qui conduit souvent à des résultats ambigus . En revanche, les modèles génératifs conditionnés par l'image sont meilleurs pour synthétiser différents contenus : 3DiM [57] propose de générer de nouvelles vues à partir d'un modèle de diffusion d'image conditionné par la vue , mais ce modèle manque de biais de cohérence tridimensionnelle. [Sparsefusion, Nerdi, Nerfdiff] Extrayez l'a priori du modèle de diffusion 2D conditionné par l'image dans nerf pour renforcer les contraintes 3D . Ces méthodes sont parallèles à notre trajectoire dans la mesure où elles modélisent les relations entre vues dans l'espace image, alors que notre modèle est de nature tridimensionnelle.

2.3 Décodeur automatique et Diffusion NeRF

  Le schéma d'ajustement de scène unique de NeRF peut être généralisé à plusieurs scènes en partageant certains paramètres dans toutes les scènes et en traitant le reste comme des codes de scène séparés [7]. Par conséquent, le NeRF multi-scènes peut être formé en tant que décodeur automatique [35], apprenant conjointement
les banques de codes et partageant les poids du décodeur
. Avec une architecture appropriée, les codes de scène peuvent être considérés comme des a priori gaussiens latents, permettant la complétion 3D et même la génération [24, 48, 38]. Cependant, comme les GAN 3D, ces latents ne sont pas suffisamment expressifs pour reconstruire fidèlement des objets détaillés . [Gaudi, From data to functa, Rodin, etc.] Décodeurs automatiques ordinaires améliorés avec a priori de diffusion latente. DiffRF [32] utilise DIffusion avant d'effectuer la complétion 3D. Ces méthodes entraînent indépendamment les modèles d’auto-décodeur et de diffusion en deux étapes, ce qui est limité.

2.4. NeRF comme décodeur automatique

  Étant donné un ensemble d'images bidimensionnelles d'une scène et les paramètres de caméra correspondants, le champ lumineux de la scène peut être ajusté dans un espace tridimensionnel, exprimé sous la forme d'une fonction optique y ψ (r) (où r est utilisé pour paramétrer le point final et somme d'un rayon dans la direction de l'espace mondial, ψ représente le paramètre du modèle, y∈R 3 + représente le format RVB de la lumière reçue. NeRF représente le champ lumineux sous forme de rayonnement intégré le long de la lumière traversant le volume tridimensionnel (voir mon blog pour les principes spécifiques : [Reconstruction 3D] Principe NeRF + Explication du code) .

  NeRF peut également être généralisé à des paramètres multi-scénarios en partageant certains paramètres du modèle entre tous les scénarios [7]. Étant donné plusieurs scènes { yijgt , rijgt }, où yijgt ,rijgt est la jème paire de pixels et de rayons RVB de la ième scène , chaque code de scène peut être optimisé en minimisant la perte de rendu L2 {x i } et le paramètre partagé ψ :

Insérer la description de l'image ici
Dans cet objectif, le modèle est formé comme un décodeur automatique. Le code de scène {x i } peut être interprété comme un code latent. Sous l'hypothèse de distributions gaussiennes indépendantes, la fonction optique peut être considérée comme la forme d'un décodeur :
Insérer la description de l'image ici

2.5 Défis de la génération et de la reconstruction

  Un auto-décodeur avec des poids d'entraînement ψ peut être généré de manière inconditionnelle en décodant les codes latents extraits des a priori gaussiens. Cependant, pour assurer la continuité de la génération, un espace latent de faible dimension et un décodeur complexe sont nécessaires , ce qui augmente la difficulté de reconstruire véritablement une vue dans l'optimisation.

2.6 Modèle de diffusion potentielle

Les modèles de diffusion latente (MLD) apprennent une distribution antérieure p ϕ (x)   dans un espace latent avec des paramètres ϕ , permettant des représentations latentes plus expressives (telles que des grilles d'images 2D). En termes de génération de champ neuronal, des travaux antérieurs [2, 32, 13 , 47] ont adopté un schéma de formation en deux étapes : d'abord entraîner un décodeur automatique pour obtenir le code latent xi de chaque scène , puis l'entraîner en tant que données réelles LDM . LDM injecte du bruit gaussien ϵ∼N (0, I) dans x i , générant un code de bruit x i (t) au pas de temps de diffusion t de la fonction empirique de planification du bruit α ​​(t) , σ (t) : = α (t ) x je(t) ϵ . Ensuite, un réseau de débruitage avec des poids entraînables ϕ supprime le bruit dans x i (t) pour prédire un code de débruitage x ^ \hat{x}X^ je. Ce réseau est généralement formé avec une perte de débruitage L2 simplifiée :
Insérer la description de l'image ici

w (t) est une fonction de pondération empirique dépendant du temps, et x ^ \hat{x}X^ ϕ(xi(t), t)formule un réseau de débruitage de segmentation temporelle.

  1. Échantillonnage inconditionnel/guidé

  À l'aide des poids entraînés ϕ , divers interprètes (par exemple DDIM) échantillonnent à partir de l'avant de diffusion p ψ (x) et débruitent de manière récursive x (t) , en commençant par un bruit gaussien aléatoire x (t) jusqu'à ce qu'un état débruit soit atteint x (0) . Le processus d'échantillonnage peut être guidé par le gradient de perte de rendu par rapport aux observations connues , permettant une reconstruction 3D à partir de l'image au moment du test.

  1. Limites de la formation en deux étapes pour les tâches 3D

  Le LDM utilisant des VAE d'images 2D est généralement entraîné en deux étapes ; lors de l'utilisation du décodeur automatique NeRF pour entraîner le LDM : l'obtention d'un code latent expressif via une optimisation basée sur le rendu est sous-déterminée, ce qui entraîne du bruit dans le réseau de débruitage (Figure 2, coin supérieur gauche) ; De plus , il est très difficile de reconstruire des nerfs à partir de vues clairsemées sans a priori appris (Fig. 2, coin inférieur gauche), ce qui limite la formation aux paramètres de vue denses.
Insérer la description de l'image ici


3. Méthode de cet article

SSDNeRF, un framework qui connecte un décodeur automatique NeRF expressif à trois plans avec un modèle de diffusion latente à trois plans. La figure 3 donne un aperçu du modèle.

Insérer la description de l'image ici

3.1 Formation NeRF à diffusion en une seule étape

   Un décodeur automatique peut être considéré comme un VAE utilisant un encodeur de table de recherche au lieu d'un encodeur de réseau neuronal typique. Les objectifs de formation peuvent donc être dérivés de manière similaire à ceux de la VAE. En utilisant le décodeur NeRF
p ψ ({y j } | x, {r j }) et l'a priori latent de diffusion p ϕ (x) , l'objectif de la formation est de minimiser le logarithme négatif des données observées { y ij gt , r ij gt } Limite supérieure variationnelle de la vraisemblance (NLL) . Cet article obtient une perte d'entraînement simplifiée en ignorant l'incertitude (variance) dans le code latent :

Insérer la description de l'image ici
Parmi eux, le code de scène {x i } , le paramètre antérieur ϕ et le paramètre du décodeur ψ sont optimisés conjointement en une seule étape d'apprentissage. Cette perte inclut la perte de rendu Lrend dans l'équation 1 , et un terme antérieur de diffusion sous la forme de NLL. Suite à l' article [Formation au maximum de vraisemblance des modèles de diffusion basés sur les scores, Modélisation générative basée sur les scores dans l'espace latent, etc.] , nous remplaçons la diffusion NLL par la limite supérieure approximative L diff (également connue sous le nom de distillation fractionnée) dans l'équation (2 ) . En ajoutant le facteur de poids de l'expérience, l'objectif final de l'entraînement est :

Insérer la description de l'image ici

  La formation en une seule étape, utilisant les codes de scène avec contraintes de perte ci-dessus {xi } , permet à l'apprenant avant de terminer la partie invisible, ce qui est particulièrement bénéfique pour la formation sur des données de vue clairsemées (les codes triplans expressifs sont sérieusement incertains )

Équilibrage du rendu et des poids antérieurs

  Le rapport poids avant rendu λ renddiff est la clé de l'entraînement en une seule étape. Pour assurer la généralisation, un mécanisme de pondération empirique est conçu , dans lequel la perte de diffusion est normalisée par la moyenne mobile exponentielle (EMA) de la norme de Frobenius du code de scène, exprimée comme suit : λ diff := c diff / EMA(||x i | | 2 F ), c ​​​​diff est une échelle fixe ; λ rend := c rend (1−e −0.1Nv )/N v . Les poids de rendu sont déterminés par le nombre de vues visibles Nv : la pondération basée sur Nv est un calibrage du décodeur pψ , empêchant la perte de rendu d'évoluer linéairement avec le nombre de rayons

Comparaison avec les champs neuronaux génératifs à deux étages

  La méthode précédente en deux étapes [Gaudi, Diffrf, génération de champ neuronal 3D utilisant la diffusion triplan] ignorait le terme précédent λ diff L diff dans la première étape de la formation . Cela peut être considéré comme un réglage des poids préalables au rendu λ renddiff à l'infini, ce qui entraîne des codes de scène biaisés et bruyants xi . L'article [Génération de champ neuronal 3D à l'aide de la diffusion triplan] atténue partiellement ce problème en imposant une régularisation à variation totale (TV) sur le code de scène triplan pour forcer un a priori lisse, similaire à la contrainte LDM sur l'espace latent (colonne du milieu de la figure 2). ). Control3Diff propose d'apprendre un modèle de diffusion conditionnelle sur des données générées par 3D GAN pré-entraînées sur des images à vue unique. En revanche, notre formation en une seule étape vise à intégrer directement la diffusion avant de favoriser la cohérence de bout en bout.


3.2 Échantillonnage et réglage fin guidés par l'image

  Pour obtenir une reconstruction NeRF rapide généralisable couvrant la reconstruction à vue unique à la reconstruction dense à vues multiples, nous proposons d'effectuer un échantillonnage guidé par l'image tout en affinant le code d'échantillonnage en prenant en compte les a priori de diffusion et les probabilités de rendu . Selon la méthode d'échantillonnage guidé par reconstruction de [Modèles de diffusion vidéo], le gradient de rendu approximatif g est calculé, c'est-à-dire un code de bruit x (t) :
Insérer la description de l'image ici
où, (t)(t) ) est un rapport signal/bruit basé sur (SNR) (l'hyperparamètre ω est de 0,5 ou 0,25). Le gradient guidé g est combiné avec la prédiction du score inconditionnel, exprimé comme la paire de sorties débruitées x ^ \hat{x}X^ Correction :

Insérer la description de l'image ici
L'échelle de guidage est λ gd . Nous utilisons un échantillonneur de prédiction-correction [52] pour résoudre x (0) en alternant une étape DDIM avec plusieurs étapes de correction de Langevin .

  Nous observons que les conseils de reconstruction ne peuvent pas appliquer strictement les contraintes de rendu pour des reconstructions fidèles. Pour résoudre ce problème, nous réutilisons dans l'équation 4, en ajustant finement le code de scène échantillonné x tout en gelant les paramètres de diffusion et de décodeur :
Insérer la description de l'image ici
λ'diff est le poids antérieur au moment du test, qui doit être inférieur à la valeur du poids d'entraînement λ diff (car l'a priori appris à partir de l'ensemble de données d'entraînement est moins fiable lorsqu'il est transféré vers un autre ensemble de données de test) . Utilisez Adam pour optimiser le code x pour un réglage précis

Comparaison avec les méthodes de réglage fin NeRF précédentes
  Bien que le réglage fin avec pertes de rendu soit courant dans les méthodes de régression NeRF conditionnées par la vue [8, 61], notre méthode de réglage fin diffère en utilisant une perte préalable de diffusion sur le code de scène 3D. , qui améliore considérablement la généralisation à de nouvelles vues, comme le montre 5.3.

3.3 Quelques détails

  1. Cache de dégradé antérieur

La reconstruction NeRF à trois plans nécessite au moins des centaines d'itérations d'optimisation   pour chaque code de scène xi . Parmi les pertes en un seul étage de la formule (4), la perte de diffusion L diff prend plus de temps à vérifier que la perte de rendu NeRF native L rend , ce qui réduit l'efficacité globale. Pour accélérer la formation et le réglage fin, nous introduisons une technique appelée Prior Gradient Caching, dans laquelle le gradient de rétropropagation mis en cache x λ diff L diff est réutilisé dans plusieurs étapes Adam tout en actualisant le dégradé rendu à chaque étape . . Il permet moins de diffusion que le rendu. Voici le pseudocode de l'algorithme principal :

  1. Paramétrage et pondération pour le débruitage

  Modèle de débruitage x ^ \hat{x}X^ ϕ(x(t),t)est implémenté en tant que réseau U-Net dans DDPM (122 millions de paramètres au total). Son entrée et sa sortie sont respectivement des caractéristiques à trois plans bruitées et débruitées (canaux de trois plans empilés ensemble). Pour la forme du test, nous adoptonsla v-paramétragev^\hat{v}[43 : Progressive distillation for fast sampling of diffusion models]v^ ϕ(x(t),t), de sorte quex ^ \hat{x}X^ = α(t)x(t)−σ(t)v ^ \hat{v}v^ . Concernant la fonction de pondération w(t), LSGM [54] adopte deux mécanismes différents pour optimiser respectivement lexiet le poids de diffusionϕ; nousEn revanche, nous observons que la pondération basée sur le rapport signal sur bruit w (t)= (α(t)/σ (t))utilisée dans l'équation 5fonctionne bien.


4. Expérimentez

41 jeux de données

  Les expériences utilisent les ensembles de données ShapeNet SRN [6,48] et Amazon Berkeley Objects (ABO) Tables [9]. L'ensemble de données SRN fournit deux types de scènes à objet unique, à savoir les voitures et les chaises. La division train/essai des voitures est 2458/703 et celle des chaises est 4612/1317. Chaque scène d'entraînement comporte 50 vues aléatoires depuis une sphère et chaque scène de test comporte 251 vues en spirale depuis l'hémisphère supérieur. L'ensemble de données ABO Tables fournit une répartition train/test de 1 520/156 scènes de table, chacune avec 91 vues depuis l'hémisphère supérieur. La résolution de rendu est de 128×128.

4.2 Génération inconditionnelle

  La génération inconditionnelle est évaluée à l’aide des ensembles de données SRN Cars et ABO Tables. L'ensemble de données Cars pose des défis dans la génération de textures nettes et complexes, tandis que l'ensemble de données Tables se compose de différentes géométries et de matériaux réalistes. Le modèle est entraîné 1 million de fois sur toutes les images de l'ensemble d'entraînement.

  1. Schémas et mesures de vérification

  Pour SRN Cars , à la suite de Functa, nous échantillonnons 704 scènes du modèle de diffusion et rendons chaque scène en utilisant les 251 poses de caméra fixes dans l'ensemble de test . Pour la table ABO, 1 000 scènes ont été échantillonnées selon DiffRF et chaque scène a été rendue à l'aide de 10 caméras aléatoires. Générez des annotations de métriques de qualité : Frechet Inception Distance (FID) et Kernel Inception Distance (KID) .

  1. Comparaison avec les méthodes de pointe :
    Comme le montre le tableau 1, sur les voitures SRN, le SSDNeRF à un étage surpasse considérablement l'EG3D en KID (plus adapté aux petits ensembles de données). Dans le même temps, son FID est nettement meilleur que Functa (qui utilise un LDM mais possède un code latent de faible dimension). Parmi les tableaux ABO, SSDNeRF fonctionne nettement mieux que EG3D et DiffRF.

  2. En une seule étape et en deux étapes :
    sur les voitures SRN, la formation en une seule étape et la formation en deux étapes régularisée par TV sous la même architecture de modèle ont été comparées. Les résultats du tableau 1 montrent les avantages de l'entraînement en une seule étape.

Insérer la description de l'image ici

4.3 Reconstruction NeRF à vue clairsemée

  Les données Cars présentent le défi de récupérer différentes textures, et les données Chair nécessitent de reconstruire avec précision différentes formes. Le modèle est entraîné sur toutes les images de l'ensemble d'entraînement pendant 8 000 itérations, et nous constatons que des programmes plus longs entraînent une réduction des performances dans la reconstruction d'objets invisibles. Ce comportement est cohérent avec les résultats de l'interpolation

  1. Plans d'évaluation et indicateurs

  La métrique d'évaluation de PixelNeRF [59] est utilisée : étant donné l'image d'entrée de l'échantillon de scène de test, le code de scène à trois plans est obtenu par réglage fin du guidage, et la nouvelle qualité de vue est évaluée pour l'image en perspective inconnue. Indicateurs de qualité d'image : rapport de modulation de signal de crête moyen (PSNR), similarité structurelle (SSIM) et similarité de patch d'image perceptuelle apprise (LPIPS) [60]. et FID entre images synthétiques et images réelles (comme 3DiM).

  1. Comparez avec d'autres méthodes

  Dans le tableau 2 : SSDNeRF possède le meilleur L PIPS et la meilleure fidélité perceptuelle. 3DiM produit des images de haute qualité (meilleur FID) mais a la plus faible fidélité à la vérité terrain (PSNR) ; CodeNeRF rapporte le meilleur PSNR sur les voitures à vue unique, mais son expressivité limitée entraîne une sortie floue (Figure 5 ) et L PIPS et FID ; VisionNeRF atteint des performances équilibrées sur toutes les mesures à vue unique, mais peut avoir des difficultés à générer des détails de texture sur le côté invisible de la voiture (par exemple, l'autre côté de l'ambulance dans la figure 5). De plus, SSDNeRF présente des avantages évidents dans la reconstruction à double vue, permettant d'obtenir les meilleures performances sur toutes les mesures pertinentes.

Insérer la description de l'image ici

Insérer la description de l'image ici

4.4 Formation SSDNeRF sur des données de vue clairsemées

Cette section entraîne SSDNeRF sur un sous-ensemble de vues clairsemées de l'ensemble de formation complet SRN Cars, en sélectionnant au hasard un ensemble fixe de trois vues dans chaque scène . Notez qu'il y aura une baisse de performances attendue raisonnable par rapport à l'entraînement en vue dense puisque l'ensemble des données d'entraînement a été réduit à 6 % de sa taille d'origine.

  1. Générer sans condition

À mi-chemin de l’entraînement, les codes des trois plans sont réinitialisés à leur moyenne. Cela permet d'éviter que le modèle ne reste bloqué dans un minimum local avec un surajustement d'artefacts géométriques. Doublez le temps de formation en conséquence. Le modèle a atteint un bon FID de 19,04 ± 1,10 et un KID/10−3 de 8,28 ± 0,60. Les résultats sont présentés dans la figure 7.

Insérer la description de l'image ici

  1. Reconstruction à vue unique

Nous adoptons la même stratégie de formation que 5.3. Grâce à notre approche de réglage fin du guidage, le modèle atteint un score LPIPS de 0,106, encore meilleur que la plupart des méthodes précédentes utilisant l'ensemble de formation complet du tableau 2.

  1. Comparaison avec la régularisation TV

La figure 8 (b) montre les images RVB et les géométries représentées par les codes latents de scène appris à partir des trois vues au cours de la formation. En revanche, les décodeurs automatiques ordinaires à trois plans avec régularisation TV (Figure 8 (a)) ne parviennent souvent pas à reconstruire des scènes à partir de vues clairsemées, ce qui entraîne de graves artefacts géométriques. Par conséquent, la formation de modèles en deux étapes avec une latence d’expression sur des données de vue clairsemées était auparavant irréalisable.

4.5 Interpolation NeRF

     Selon les paramètres de DDIM, deux valeurs initiales x (T) ∼N(0,I) sont échantillonnées, interpolées à l'aide d' une interpolation linéaire sphérique [46], puis le solveur déterministe est utilisé pour obtenir les échantillons interpolés. Cependant, comme souligné dans [37, 40], le modèle de diffusion gaussienne standard conduit souvent à une interpolation non lisse . Dans SSDNeRF (résultats présentés dans la figure 9), nous constatons qu'un modèle entraîné avec une reconstruction de vue clairsemée à arrêt précoce (a) produit des transitions raisonnablement fluides, tandis qu'un modèle entraîné avec une génération inconditionnelle (b) produit des échantillons distincts mais discontinus. Cela suggère que l'arrêt précoce préserve un a priori plus fluide, conduisant à une meilleure généralisation à la reconstruction de vues clairsemées.

Insérer la description de l'image ici

5. Coder

5.1 Génération inconditionnelle

Sélectionnez le fichier de configuration ici : ssdnerf_cars_uncond.py, et téléchargez le poids correspondant : ssdnerf_cars_uncond_1m_emaonly.pth. La signification du fichier de configuration est la suivante :

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. Générer du bruit en entrée

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.processus de diffusion :
ddim_sample de la classe : class 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 Dans self.p_sample_ddim, divers paramètres prédéfinis : facteur de diffusion β \betab、a、a ˉ \bar{a}unattendez _

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 Processus de diffusion inverse dans self.p_sample_ddim : Unet standard (avec fonctionnement d'auto-attention), les dimensions avant et arrière restent inchangées

	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 Dans self.p_sample_ddim, obtenez le x_0 prédit

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 densité de prédiction NeRF
    est située dans la grande classe 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. Rendre l'image (décodage)
image, depth = self.render(decoder, code, density_bitfield, h, w, test_intrinsics, test_poses, cfg=cfg)      

Entrée de fonction : # code : (8,3,6,128,128) champ de bits : (8,32768) pose : (8,251,4,4) intrinsèques : [131.250, 131.250, 64.0, 64.0]
Ce qui suit est l'expansion spécifique :

## 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 fonction composite_rays effectue des calculs itératifs sur chaque rayon et obtient finalement les informations de l'image composite en fonction des étapes de propagation de la lumière, d'accumulation des valeurs de couleur et de mise à jour des paramètres.

Finalement, trois résultats (batchsize=8) sont obtenus : image(4112384,3), poids_sum(4112384), profondeur(4112384). Après l'opération de remodelage, l'image finale est obtenue :

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 Formation à la reconstruction NeRF (multi-vues) : ssdnerf_cars_recons1v

Le fichier de configuration est : ssdnerf_cars_recons1v.py. Sélectionnez à chaque fois 50 perspectives pour la reconstruction.

1. Perte du processus de diffusion (la code_list_ mise en cache est très 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()

Mettez ensuite à jour le dégradé mis en cache :

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

2.inverse_code : perte 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. Mettre à jour le cache
self.save_cache(
    code_list_, code_optimizers,
    density_grid, density_bitfield, data['scene_id'], data['scene_name'])

5.3 Raisonnement de reconstruction clairsemée : ssdnerf_cars3v_recons1v

Le fichier de configuration est : ssdnerf_cars3v_recons1v.py. Sélectionnez une chanson à la fois pour reconstruire la scène 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)

Ce qui suit est une extension de la partie 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 Formation à la reconstruction clairsemée : ssdnerf_cars3v_recons1v

Le fichier de configuration est : ssdnerf_cars3v_recons1v.py. A chaque fois, 3 perspectives sont sélectionnées pour l'entraînement à la reconstruction.

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

Fonctions encapsulées *.cuda

lib/ops/raymarching/scr/raymarching.h contient les fonctions suivantes (spécifiquement définies dans le fichier 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);

limitation

     Actuellement, cette méthode s'appuie sur les paramètres de la caméra Ground Truth lors de la formation et des tests. Des travaux futurs pourraient explorer des modèles invariants par transformation. De plus, à mesure que le temps de formation augmente, la diffusion préalable devient discontinue, affectant la généralisation . Bien que l’arrêt anticipé soit utilisé temporairement, une meilleure conception du réseau ou un ensemble de données d’entraînement plus important pourrait résoudre fondamentalement ce problème.


développer

1.Indicateurs FID, KID, LPIPS

Frechet Inception Distance (FID) est une métrique utilisée pour évaluer la qualité des images générées par les GAN. Elle est basée sur la distance de Fréchet entre l'image générée et l'image réelle , qui mesure la similarité entre les deux distributions d'images. Plus le FID est bas, meilleure est la qualité de l’image générée.

Insérer la description de l'image ici

LPIPS : Tiré de l'article "The Unreasonable Effectiveness of Deep Features as a Perceptual Metric".
Insérer la description de l'image ici
Plus la valeur est petite, plus les deux images sont similaires. Les deux entrées sont envoyées au réseau neuronal F (peut être VGG, Alexnet, Squeezenet) pour l'extraction de caractéristiques, la sortie de chaque couche est activée et normalisée, puis la distance L2 est calculée après multiplication des poids de couche w.
Insérer la description de l'image ici

La distance de création du noyau (KID) est une autre mesure permettant d'évaluer la qualité des images générées par les GAN. Il est basé sur la distance de fonction du noyau entre l'image générée et l'image réelle , qui mesure la différence entre l'image réelle et l'image générée en mappant les vecteurs de caractéristiques dans un espace de grande dimension et en calculant la distance de Frechet de la matrice du noyau entre eux. KID est également une mesure de la qualité de l'image générée, similaire au FID.

Pour une implémentation de code spécifique, voir https://blog.51cto.com/u_16175458/6906283

2. Régularisation TV

Régularisation TV, le nom complet est Régularisation de variation totale. La régularisation TV permet d'obtenir un lissage de l'image en minimisant l'ampleur du gradient de l'image.

Plus précisément, pour une image bidimensionnelle, la régularisation TV peut obtenir un lissage en minimisant l'ampleur du gradient de l'image . Cela supprime le bruit et les détails de l'image. Rend l'image plus lisse. La régularisation TV est généralement appliquée au terme de régularisation des problèmes d'optimisation pour équilibrer la relation entre l'ajustement des données et la fluidité.

Pour les méthodes telles que NeRF (Neural Radiance Fields), principalement utilisées pour la reconstruction tridimensionnelle, la régularisation TV contribue à améliorer la qualité des résultats de reconstruction. En effet, dans la reconstruction 3D, en raison de facteurs tels que la rareté des données et le bruit, les résultats de la reconstruction contiennent souvent des détails et du bruit inutiles .

En outre, la régularisation de la télévision peut également contribuer à renforcer les contraintes sur le modèle d'apprentissage profond et à améliorer la capacité de généralisation et la capacité anti-bruit du modèle. Par conséquent, pour les tâches de reconstruction 3D telles que NeRF, l’application de la régularisation TV contribue à améliorer la qualité des résultats de reconstruction et à augmenter la robustesse du modèle.

3. Rendu NeRF (des paramètres internes et externes de la caméra aux rayons)

rayons_o, rayons_d = get_cam_rays(poses, intrinsèques, h, w)
analyse principalement la fonction get_cam_rays, située dans 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

Je suppose que tu aimes

Origine blog.csdn.net/qq_45752541/article/details/134984758
conseillé
Classement