Questo articolo presenta la matematica della ricostruzione delle alte luci di Ansel: il progetto originale dei guided laplacians del 2021, le cui idee generali erano state solo abbozzate sul forum pixls.us,1 e la trasposizione armonica, il metodo che l’ha sostituito dopo che uno studio con ground truth ha rivelato un bug nel recupero della magnitudine.2 È organizzato in modo che ogni lettore trovi la propria sezione: i risultati e la sezione Intuizione e contesto di ogni metodo non richiedono matematica; gli sviluppatori troveranno le sottosezioni Implementazione e ottimizzazioni e lo studio sulle prestazioni; i risultati teorici e l' appendice del cimitero sono scritti per essere riutilizzabili al di fuori della fotografia; e come questo lavoro è stato effettivamente svolto documenta il protocollo di ricerca uomo–macchina che vi sta dietro.

Sommario

Quando il sensore di una fotocamera satura, i tre canali colore non subiscono clipping nello stesso momento, per cui un’alta luce bruciata deriva nel colore, di solito verso il magenta. Questo articolo documenta la ricostruzione delle alte luci di Ansel dall’inizio alla fine e nell’ordine in cui è avvenuta: i guided laplacians originali del 2021 (trasferimento del dettaglio wavelet attraverso un filtro guidato, mai messo per iscritto compiutamente prima), lo studio con ground truth che ne ha rivelato il bug di magnitudine, e il metodo che l’ha sostituito — la trasposizione armonica, che mantiene il modello locale a linea di colore del filtro guidato ma trasporta i suoi coefficienti attraverso la zona bruciata come campi diffusi regolari guidati dalla struttura dei canali sopravvissuti, corregge il bias del rolloff di saturazione del sensore prima del fitting, e affida ogni banda di frequenza e classe di pixel allo stimatore che vi è misurabilmente migliore. Ogni decisione di progetto è supportata da un numero su un banco di prova pubblico, e i fallimenti sono documentati con la stessa cura dei successi, inclusi due risultati che riteniamo generali: l’energia della cucitura in corrispondenza di qualsiasi passaggio di consegne tra stimatori è pari al disaccordo tra gli stimatori e non può essere eliminata pesando, e la validità di una linea di colore in profondità all’interno di una zona bruciata è indecidibile a partire dai dati che la circondano.34

Il problema

La dominante magenta

Un sensore digitale è una matrice di fotositi, ognuno coperto da un filtro colorato di una matrice di filtri colore (CFA): il pattern di Bayer (2×2 di R, G, G, B) o l’X-Trans di Fuji (6×6). Ogni fotosito è un pozzo potenziale che si riempie di fotoelettroni durante l’esposizione e satura a una capacità fissa. Poiché la capacità del pozzo è una proprietà del silicio, tutti e tre i colori saturano all’incirca allo stesso valore di codice raw.

La trappola è il bilanciamento del bianco. Un soggetto grigio neutro non produce segnali raw uguali nei tre canali: le trasmissioni della CFA, la sensibilità spettrale del sensore e l’illuminante della scena differiscono tutti per canale. Per rendere neutro un tale soggetto, lo sviluppatore raw moltiplica ogni canale per un coefficiente di bilanciamento del bianco: tipicamente il canale verde viene lasciato vicino a $1$ mentre il rosso e il blu vengono moltiplicati fino a $1.5$–$2$.

Ora segui un’alta luce neutra mentre diventa più luminosa. A livello del sensore non c’è nulla di specifico per canale riguardo alla saturazione: i tre segnali raw salgono verso un unico tetto condiviso — il pozzo pieno — e subiscono clipping più o meno allo stesso valore. Il magenta viene fabbricato in seguito, dai guadagni del bilanciamento del bianco: moltiplicare ogni canale cambia sia la sua pendenza in salita sia il livello su cui atterra il suo plateau tagliato. Il verde (guadagno $\approx 1$) forma il plateau dove l’ha lasciato il sensore; il blu e il rosso vengono spinti verso l’alto dai loro guadagni e formano il plateau al di sopra di esso. Oltre la saturazione i rapporti registrati non sono più neutri — il rosso e il blu superano il verde, e l’alta luce «bianca» viene letta come magenta. (Su soggetti reali i canali subiscono comunque il clipping in sequenza — il colore proprio del soggetto e le sensibilità per canale sfalsano gli inneschi, ed è per questo che esistono pixel parzialmente tagliati che portano guide sopravvissute — ma il magenta di un neutro bruciato è opera dei guadagni, non di soglie per canale.)

A sinistra: al sensore, un’alta luce neutra spinge tutti e tre i canali verso lo stesso tetto — il pozzo pieno — e subiscono clipping insieme. A destra: i guadagni del bilanciamento del bianco (qui R × 2.0, B × 1.5, G × 1.0) cambiano sia la pendenza sia l’altezza di ogni plateau tagliato; il rosso e il blu atterrano al di sopra del verde, e il colore registrato deriva verso il magenta — anche se il soggetto è grigio e il sensore ha tagliato tutti i canali allo stesso livello.

Il valore al quale un canale viene dichiarato tagliato non è il massimo numerico ma una soglia per canale derivata dal punto di bianco raw:

$$ \text{clip}_c = 0.995 \times \texttt{clip} \times \text{white}_c, $$

dove $\text{white}_c$ è il processed_maximum del modulo per il canale $c$ (il livello di bianco raw per canale che sopravvive alle precedenti fasi della pipeline) e clip è un fattore di sicurezza dell’utente attorno a $1$. Il margine di $0.995$ tiene i fotositi quasi saturi (la cui risposta è già diventata non lineare vicino alla cima del pozzo) fuori dall’insieme «valido».2

Soluzioni più semplici

Il modulo di Ansel offre tre modalità di ricostruzione più economiche prima di quella guided-laplacian, e vale la pena enunciarle perché inquadrano ciò che il metodo costoso permette di ottenere:

  • Clip semplicemente schiaccia ogni canale alla soglia comune $\texttt{clip}\times\min_c \text{white}_c$ (gli stessi livelli di bianco, senza il margine di $0.995$). Niente magenta, ma ogni regione tagliata diventa una macchia bianca piatta e priva di texture.
  • Ricostruisci in LCh converte ogni blocco Bayer in una tripletta luminanza/croma/tinta, riscala la croma dei blocchi tagliati per corrispondere alla luminanza non tagliata, e riconverte. Rimuove la deriva di tinta ma non può inventare texture.2
  • Ricostruisci colore (inpaint) propaga i rapporti di colore dai pixel non tagliati vicini lungo righe e colonne, usando l’aggiornamento del rapporto a decadimento esponenziale dell’algoritmo di Magic Lantern. È veloce e direzionale ma unidimensionale e facilmente ingannato da bordi complessi.2

La modalità guided laplacians è l’unica che ripristina sia la texture sia la magnitudine di una regione tagliata, prendendo in prestito dai canali che sono sopravvissuti lungo la linea di colore locale.

Darktable a monte fornisce due modalità di ricostruzione che Ansel non include, e offrono un confronto istruttivo perché partono da un’assunzione diversa su cosa sia un’alta luce bruciata. Entrambe sono state sviluppate dai team di G’mic e Darktable ed entrambe operano, come il nostro metodo, sul mosaico raw: ogni canale colore viene prima approssimato ovunque a partire dal suo vicinato di fotositi 3×3, dando un’immagine per canale grezza ma a piena risoluzione.

Inpaint opposed si basa su un’osservazione empirica: per un canale tagliato, la media dei due altri canali (la media «opposta», calcolata nello spazio della radice cubica per comprimere l' intervallo dinamico) è una buona stima del valore mancante nella stragrande maggioranza delle immagini. Il meccanismo è di conseguenza semplice: sostituisci ogni valore tagliato con quella media opposta, poi aggiungi un offset di crominanza globale, misurato sui pixel morfologicamente più vicini alle aree tagliate, per assorbire la dominante di colore complessiva. Le sue modalità di fallimento documentate sono le scene in cui una relazione fissa non può reggere: illuminazione mista, bilanciamento del bianco lontano dall’assunzione della pipeline, oppure diverse sorgenti luminose di colori diversi che alimentano alte luci diverse.

Segmentation based affina lo stesso stimatore di base con un adattamento spaziale. I pixel tagliati di ogni canale vengono riempiti con flood-fill in segmenti connessi (opzionalmente uniti da una chiusura morfologica, il controllo «combine»); per ogni segmento, l’algoritmo cerca sul suo bordo non tagliato il miglior pixel candidato (valutato tramite deviazione standard locale e mediana in una finestra 5×5) e trapianta la pseudo-crominanza di quel candidato (il canale meno la media opposta, di nuovo nello spazio della radice cubica) sull’intero segmento. Se non esiste alcun candidato convincente, ripiega su una media a livello di segmento con una correzione di crominanza. Dove tutti i canali sono tagliati, un passaggio di «rebuild» separato estrapola una superficie di luminanza dai gradienti del bordo, controllato da una trasformata di distanza — il cugino più prossimo, in tutto questo panorama, della nostra cupola biarmonica. Le assunzioni sono quindi: una crominanza rappresentativa per segmento (meglio di una per immagine, ma comunque piatta all’interno di un segmento), texture ereditata da qualunque cosa i canali sopravvissuti portino, ed estrapolazione del gradiente dove nulla è sopravvissuto.

Entrambi sono un tentativo disperato di riempire l’area danneggiata con qualcosa di plausibile, ma promuovono superfici colorate piatte alla magnitudine sbagliata.

Princìpi fondamentali

Il metodo è un assemblaggio di quattro idee. Due di esse (i laplaciani discreti e la piramide B-spline à-trous) sono condivise verbatim con diffuse or sharpen e qui vengono solo riassunte. Le altre due, il filtro guidato e la diffusione della crominanza, portano avanti la ricostruzione e sono derivate per intero.

Gradienti e laplaciani

Per un’immagine discreta $u(i,j)$, il gradiente misura la pendenza locale,

$$ \nabla u = \left( \frac{u(i+1,j) - u(i-1,j)}{2}, \; \frac{u(i,j+1) - u(i,j-1)}{2} \right), $$

e il laplaciano misura la curvatura locale: quanto un pixel si discosta dalla media dei suoi vicini,

$$ \Delta u = \frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2}. $$

Il laplaciano è qui il cavallo da tiro perché isola la texture come oscillazione attorno a una media locale (è zero sulle regioni piatte e risponde solo al contrasto locale) e perché è lineare: sovraesporre o sottoesporre l’immagine lo riscala semplicemente (una proprietà su cui ci appoggiamo qui sotto). Isolare la texture in questo modo è ciò che ci permette di trapiantarla tra i canali senza trascinare con sé la luminosità assoluta della guida; la differenza di magnitudine complessiva tra un canale tagliato e la sua guida viene assorbita dalla pendenza del filtro guidato, non dal laplaciano stesso. Ansel usa lo stencil a 9 punti a simmetria rotazionale di Oono & Puri

$$ \mathbf{K}_{\text{iso}} = \begin{bmatrix} \tfrac14 & \tfrac12 & \tfrac14 \\ \tfrac12 & -3 & \tfrac12 \\ \tfrac14 & \tfrac12 & \tfrac14 \end{bmatrix}, $$

il cui errore angolare è molto più piccolo della ingenua croce a 5 punti, per cui la diffusione non privilegia gli assi della griglia di pixel.567

La piramide B-spline à-trous

Per agire su strutture di molte dimensioni, l’immagine viene divisa in bande di frequenza sfocandola ripetutamente con il kernel B-spline cardinale separabile

$$ h_0 = \frac{1}{16}[1,4,6,4,1], $$

una approssimazione compatta di una gaussiana di parametro $\sigma_B \approx 1.0554$.8 Alla scala $s$ i tap sono distanziati di un passo di $2^s$ pixel («à-trous» = «con buchi»), per cui lo stesso minuscolo kernel raggiunge distanze sempre maggiori senza mai crescere in costo. Scrivendo $G_s$ per le successive immagini passa-basso (progressivamente sfocate) e $H_s$ per le bande di dettaglio,

$$ G_{-1} = u, \qquad G_s = h_{2^s} * G_{s-1}, \qquad H_s = G_{s-1} - G_s, $$

l’immagine è esattamente la somma delle sue bande, $u = \sum_{s=0}^{n-1} H_s + G_{n-1}$. Una banda di dettaglio $H_s$ è una differenza di gaussiane, che è essa stessa una approssimazione scalata di un laplaciano-di-gaussiana — quindi «filtrare la banda $H_s$» e «applicare un laplaciano alla scala $s$» sono due viste della stessa operazione. La derivazione completa, incluso come il raggio gaussiano equivalente cresce come

$$ \sigma_{G,s} = \sigma_B \sqrt{\frac{4^{s+1}-1}{3}}, $$

è fornita nell’articolo di accompagnamento su diffuse or sharpen.29

Il filtro guidato

Il filtro guidato di He, Sun e Tang è il motore che prende in prestito la texture da un buon canale in uno tagliato.3 Supponi di voler produrre un’immagine di output $q$ che rimanga fedele a un target $p$ ma indossi i bordi e la texture di una guida $I$. Assumi che, all’interno di qualsiasi piccola finestra $\omega_k$ attorno al pixel $k$, l’output sia una funzione affine della guida:

$$ q_i = a_k \, I_i + b_k, \qquad \forall i \in \omega_k. $$

Questa singola assunzione, una linea di colore locale, è tutto il modello. Dice che all’interno di una piccola patch il canale che stiamo ricostruendo è semplicemente una copia scalata e traslata della guida. È lo stesso prior che sta alla base della demosaicizzazione cross-canale, del dehazing, del matting di immagini e della colorizzazione: le superfici naturali tracciano linee di colore (localmente, i loro canali sono legati in modo affine) perché la maggior parte dei bordi sono cambiamenti di riflettanza che scalano tutti i canali insieme.10 Sotto una mappa affine, $\nabla q = a_k \nabla I$, per cui $q$ eredita ogni bordo di $I$, semplicemente riscalato di $a_k$.

Facciamo il fit di $(a_k, b_k)$ ai minimi quadrati, mantenendo $a_k$ piccolo per evitare di amplificare il rumore (un termine di ridge $\varepsilon a_k^2$):

$$ E(a_k, b_k) = \sum_{i \in \omega_k} \Big[ (a_k I_i + b_k - p_i)^2 + \varepsilon \, a_k^2 \Big]. $$

Ponendo le derivate a zero si ottiene la forma chiusa che compare, quasi verbatim, nel codice:

$$ \begin{aligned} a_k &= \frac{\operatorname{cov}_{\omega_k}(I, p)}{\operatorname{var}_{\omega_k}(I) + \varepsilon}, \\ b_k &= \bar{p}_{\omega_k} - a_k \, \bar{I}_{\omega_k}. \end{aligned} $$

La covarianza al numeratore (una misura di quanto guida e target salgono e scendono insieme) è la chiave: dove si muovono insieme, $a_k \to 1$ e la texture della guida viene copiata attraverso; dove la guida è piatta ($\operatorname{var} \to 0$), $a_k \to 0$ e l’output ripiega sulla media locale $\bar p$. Il parametro di ridge $\varepsilon$ imposta la scala al di sotto della quale le variazioni sono trattate come rumore e smussate anziché trasferite.

La diffusione come inpainting del colore

Riempire il colore di un buco è un problema diverso dal riempirne la texture. Il colore di un buco dovrebbe variare in modo regolare e corrispondere al suo bordo; non dovrebbe portare dettaglio ad alta frequenza (texture fine e in rapida variazione) di suo. Il formalismo naturale è l’energia di Dirichlet, la quantità totale di variazione che l’immagine contiene, grande dove ondeggia e zero dove è costante:

$$ E[u] = \int_\Omega \lVert \nabla u \rVert^2 \, \mathrm{d}\Omega, $$

il cui minimizzatore sul buco $\Omega$, con i pixel circostanti come condizione al contorno, è la funzione armonica che soddisfa $\Delta u = 0$.

(Notazione, usata in tutto l’articolo: $\lVert v \rVert$ è la norma euclidea di un vettore — per il gradiente, $\lVert \nabla u \rVert = \sqrt{(\partial_x u)^2 + (\partial_y u)^2}$, la magnitudine della pendenza locale; $\lvert s \rvert$ è il valore assoluto di uno scalare; e $\langle \cdot \rangle$ — parentesi angolari — è la media della quantità racchiusa sui pixel della regione.) La discesa del gradiente di questa energia è precisamente l’equazione del calore

$$ \frac{\partial u}{\partial t} = \Delta u, $$

cioè la diffusione isotropa, dove isotropo significa cieco alla direzione, che si propaga ugualmente in tutte le direzioni. Eseguirla propaga il colore del contorno verso l’interno finché il buco non è riempito da una superficie regolare e priva di curvatura. Questo è lo stesso modello di inpainting a trasferimento di calore anisotropo di Qin et al. che Ansel usa già per diffuse or sharpen, ristretto qui al suo caso isotropo.4 Lo applicheremo non ai pixel ma ai rapporti di colore, in modo che solo la crominanza sia smussata mentre la luminanza ricostruita è lasciata intatta.

Guidare la diffusione: l’estensione anisotropa. L’energia di Dirichlet qui sopra tratta ogni direzione ugualmente; è isotropa. Si generalizza in una forma pesata,

$$ E(u) = \int_\Omega \nabla u^\top D \,\nabla u \,\mathrm{d}\Omega , $$

dove il tensore di diffusione $D$ è una piccola matrice simmetrica definita a ogni pixel il cui compito è ripesare le direzioni: piena conduttanza lungo uno dei suoi autovettori, conduttanza smorzata lungo l’altro. Minimizzare questa energia risolve $\mathrm{div}(D\,\nabla u) = 0$ (la sua equazione di Eulero–Lagrange), e con $D$ pari all’identità ovunque si riduce esattamente al caso isotropo qui sopra. Il senso di questo macchinario è la guida: scegli la direzione forte lungo le isofote dell’immagine (le linee di luminosità costante) e quella smorzata attraverso di esse, e la diffusione smussa una quantità lungo la struttura dell’immagine rifiutandosi di trasportarla attraverso i bordi.

Discretizzare tutto ciò in sicurezza richiede una precauzione. Scrivendo il tensore $D = \begin{pmatrix} a & b \ b & c \end{pmatrix}$ (mediato tra ogni pixel e il suo vicino, con il termine incrociato vincolato a $|b| \leq \min(a, c)$), la divergenza $\operatorname{div}(D\,\nabla u)$ diventa una somma sugli otto vicini con pesi

$$ w_{\pm e_x} = a - |b|, \qquad w_{\pm e_y} = c - |b|, \qquad w_{\pm(e_x + e_y)} = \max(b,\, 0), \qquad w_{\pm(e_x - e_y)} = \max(-b,\, 0), $$

lo stencil di Weickert che preserva la non negatività: il vincolo garantisce che ogni peso sia non negativo, per cui il rilassamento ancorato $u_0 \leftarrow \sum_k w_k u_k / \sum_k w_k$ sostituisce ogni incognita con una combinazione convessa dei suoi vicini. Questo è il principio del massimo discreto — i valori risolti non possono mai uscire dall’intervallo delle ancore, per quanto forte guidi il tensore — ed è ciò che rende sicuro eseguire la diffusione anisotropa su dati che devono rimanere fisici. Il metodo distribuito usa questo macchinario in due punti: il trasporto dei coefficienti della linea di colore attraverso la zona bruciata (passo 3 dell’algoritmo, dove il tensore inoltre miscela tra guida per isofote e guida per gradiente tramite una probabilità di bordo misurata), e la crominanza dei nuclei completamente tagliati (passo 8), dove i colori devono seguire la struttura della luminanza recuperata e non sbavare mai attraverso di essa.

Inpainting biarmonico

L’inpainting armonico è lo strumento giusto per un segnale che dovrebbe diventare piatto all’interno del buco: una crominanza regolare. È lo strumento sbagliato per uno che era ancora in salita quando il sensore lo ha tagliato: la magnitudine di un’alta luce bruciata continuava a salire, e riempirla in modo piatto ($\Delta u = 0$) lascia un disco opaco dove dovrebbe stare una cupola luminosa. Per trasportare invece la pendenza circostante verso l’interno anziché cancellarla, penalizza la flessione del segnale anziché il suo gradiente: minimizza l’energia della lastra sottile (biarmonica)

$$ E_{\text{bihar}}[u] = \int_\Omega (\Delta u)^2 \, \mathrm{d}\Omega \qquad \Longrightarrow \qquad \Delta^2 u = 0 \ \text{ on } \Omega, \quad u\big|_{\partial\Omega} = u_{\text{valid}} , $$

la cui equazione di Eulero–Lagrange (la condizione che ogni minimizzatore dell’energia deve soddisfare) è l' equazione biarmonica $\Delta^2 u = 0$. Dove la soluzione armonica forza $\Delta u = 0$ (una superficie minimale piatta), la soluzione biarmonica rende $\Delta u$ esso stesso armonico: la curvatura al bordo viene trasportata all’interno, per cui il gradiente crescente del contorno viene estrapolato in una cupola (una spline a lastra sottile). È la controparte di ordine superiore, che estende il gradiente, della diffusione, nello stesso spirito in cui diffuse or sharpen inverte il segno del laplaciano per nitidizzare anziché smussare. La risolviamo come un sistema lineare sparso diretto sul buco, e la usiamo qui sotto per ricostruire la magnitudine di un canale tagliato dal suo stesso bordo valido ovunque nessun canale correlato sia sopravvissuto a guidarla.7

Il metodo del 2021: guided laplacians

Prima di seguire l’evoluzione del metodo, questa sezione documenta il progetto originale, così come è stato distribuito nel 2021 e come viene ancora distribuito oggi — progetto invariato, sebbene condivida una correzione di raccolta al bordo misurata con la nuova pipeline (documentata insieme ai raffinamenti armonici qui sotto) — sotto la modalità guided laplacians (il nuovo metodo è una modalità separata e opzionale: vedi la nota sullo stato più sotto). È stato programmato contro l’intuizione e regolato a occhio su immagini naturali, e le sue idee generali erano state solo abbozzate sul forum pixls.us1; questa è la sua prima descrizione completa.

Intuizione e contesto

L’intuizione. Il clipping inevitabile avviene all’interno delle sorgenti luminose: il sole, una fiamma, un riflesso speculare. Tali sorgenti sono circondate da un alone luminoso, e quell’alone porta tre tipi sfruttabili di informazione. Primo, i canali colore vi sono fortemente correlati (salgono tutti verso la sorgente), per cui un canale che ha subìto clipping può prendere in prestito la forma di un canale che non l’ha subìto. Secondo, i gradienti dell’alone puntano verso la sorgente, per cui anche un nucleo completamente bruciato può essere plausibilmente continuato propagando i gradienti circostanti verso l’interno. Terzo, l’alone è colorato come la sorgente luminosa stessa, per cui la crominanza può essere recuperata propagando i rapporti RGB verso l’interno. Il progetto del 2021 ha trasformato ogni intuizione in un operatore: un trasferimento guidato di struttura fine tra i canali, e una diffusione iterata che spinge la struttura dal contorno valido nella regione bruciata diffondendo i rapporti di colore verso l’interno.

Gli obiettivi. Ricostruire prima della demosaicizzazione, sul mosaico raw, in modo che l’interpolazione non veda mai campioni tagliati; non affidarsi mai al bilanciamento del bianco o a qualsiasi assunzione su quale colore l’alta luce «dovrebbe» essere (il colore proprio della sorgente luminosa è inconoscibile); e preferire continuazioni regolari e fisicamente plausibili alle allucinazioni nette, perché un errore in un’alta luce bruciata è molto più visibile come artefatto netto che come artefatto morbido.

Il suo curriculum. Sui suoi casi di progetto il metodo ha funzionato, e funziona ancora: un disco solare o uno speculare sull’acqua viene ricostruito con struttura credibile e senza la macchia grigia piatta che il semplice clipping lascia. Preserva i gradienti dove metodi più semplici (scalatura dei rapporti di canale, inpainting a colore singolo) li appiattiscono, e poiché opera sui gradienti anziché sui colori tollera un bilanciamento del bianco sbagliato. Due limiti strutturali sono stati compresi e accettati fin dall’inizio: un cielo bruciato visto attraverso foglie verdi viene recuperato verde (il metodo propaga il colore vicino, per progetto), e una forte aberrazione cromatica rompe l’allineamento dei canali di cui la linea di colore ha bisogno.

Il problema di ottimizzazione

Il trasferimento di texture. L’immagine viene decomposta con la piramide B-spline à-trous descritta sopra: a ogni scala $s$ il segnale si divide in un’approssimazione a bassa frequenza e una banda di dettaglio $H_s$. Poiché la B-spline cardinale è molto vicina a una gaussiana, e la banda di dettaglio è calcolata scala per scala come la differenza con questa gaussiana approssimata, $H_s$ è essa stessa già vicina a un laplaciano, riscalato in magnitudine (in realtà sovrastima il laplaciano). Su ogni banda di dettaglio, ogni canale tagliato $c$ viene fittato contro un canale guida $g$ (scelto per pixel come il canale con la varianza locale più forte, cioè quello che porta più struttura — senza alcun controllo che la guida stessa sia valida lì) con il filtro guidato derivato sopra:

$$ H_c \;\leftarrow\; a_c\, H_g + b_c, \qquad a_c = \frac{\operatorname{cov}(H_c, H_g)}{\operatorname{var}(H_g)}, $$

dove la covarianza e la varianza sono statistiche locali su finestra (un box rigido 3×3 che non esclude i campioni tagliati), e la divisione viene semplicemente saltata dove la varianza della guida scende al di sotto di $10^{-12}$ — non c’è alcun termine di smorzamento. Poiché una banda di dettaglio è a media nulla, l’intercetta $b_c$ è essenzialmente zero: il trasferimento sposta la texture dalla guida al canale tagliato, scalata dalla loro relazione locale misurata.

La diffusione della crominanza. Il secondo stadio opera sui rapporti RGB (la crominanza, $\text{RGB}/n$ con $n = \lVert \text{RGB} \rVert$, la norma euclidea — il simbolo che le regole di aggiornamento qui sotto usano), iterando il laplaciano a nove punti di Oono & Puri su ogni piano di rapporto; il piano della norma $n$ cavalca la stessa piramide ma è esplicitamente protetto dal passaggio (salvato e ripristinato attorno ad esso), per cui viene risintetizzato tale e quale. Quell’iterazione è la discesa del gradiente di un esplicito problema variazionale: sulla regione tagliata $\Omega$, con il bordo valido come dato al contorno, minimizza

$$ E_{\text{2021}}[u] \;=\; \int_{\Omega} \lVert \nabla u \rVert^2 \,\mathrm{d}\Omega \;+\; \lambda_{\text{solid}} \int_{\Omega} \lVert u - \bar u \rVert^2 \,\mathrm{d}\Omega , \qquad u\big|_{\partial\Omega} = u_{\text{valid}}, $$

per ogni piano di rapporto e per la norma: un’energia di Dirichlet (armonica) che trasporta la crominanza e il livello del bordo verso l’interno in modo regolare, più un termine di screening opzionale che tira il nucleo verso il colore valido medio $\bar u$ con il peso «inpaint a flat color» dell’utente $\lambda_{\text{solid}}$. La sua equazione di Eulero–Lagrange è l’equazione di Poisson schermata della sezione teorica; il codice del 2021 non la risolve mai fino allo stato stazionario — il cursore «iterations» tronca la discesa, per cui il risultato pratico dipende dal budget di iterazioni tanto quanto dall’energia. Due proprietà seguono direttamente da questo obiettivo e spiegano l’aspetto caratteristico del metodo: un riempimento armonico appiattisce (penalizza i gradienti, per cui l’interno tende a un plateau di livello — la sezione teorica mostra perché serve un’energia biarmonica per continuare le pendenze), e nulla in nessuno dei due stadi vincola il livello ricostruito di un canale tagliato, che lo stadio 1 non può trasferire (bande a media nulla) e lo stadio 2 diffonde solo da un bordo che si trova al clip.

L’algoritmo

L’intera procedura, in sintesi:

flowchart TD
  A["raw mosaic (one colour per pixel)"] --> B["bilinear interpolation to temporary RGB
+ per-channel clip masks"] B --> C["downsample 4× (for speed)"] C --> D["à-trous B-spline pyramid:
split into detail bands, one per frequency range"] D --> E["per band, RGB, fine → coarse:
guided-filter fit of the clipped channel
against the most textured channel
(validity not checked)
→ transfer of TEXTURE (zero-mean details)"] E --> F["per band, RGB ratios & norm, iterated 9-point Laplacian diffusion:
spread structure from the valid rim
into the blown core, isotropically"] F --> G["optional: reaction toward a flat colour
(user slider) + Poissonian re-graining"] G --> H["upsample, remosaic,
feathered composite over the raw"]

I compromessi sono deliberati. Operare sulle bande di dettaglio rende il trasferimento immune agli errori di bilanciamento del bianco (solo le forme si spostano tra i canali). Il laplaciano a nove punti è la discretizzazione isotropa e invariante rotazionalmente derivata nei princìpi fondamentali, per cui la diffusione riempie il nucleo dall’esterno verso l’interno senza seguire gli assi della griglia di pixel. La regolarità è sempre preferita alla nitidezza: un errore in un’alta luce bruciata si legge come un artefatto netto quando è nitido e come un bagliore plausibile quando è morbido. E l’ordine delle bande dal fine al grossolano significa che ogni banda viene fittata indipendentemente all’interno di un singolo passaggio — non c’è alcun vincolo di coerenza cross-scala, cosa che resta innocua proprio perché le bande sono a media nulla.

Tre stadi preparatori meritano una nota — sono condivisi da ogni metodo di ricostruzione in questo articolo, successore incluso.

Demosaicizzazione bilineare. Il mosaico viene demosaicizzato bilinearmente in un’immagine RGB temporanea: una demosaicizzazione usa e getta il cui unico compito è dare a ogni canale un valore ovunque per il fit guidato per canale; l’output finale viene rimosaicizzato in un singolo canale. Una maschera di clip per canale, insieme al suo OR logico (sfumato nell’opacità di composizione $\alpha$), registra quali canali sono saturati.2

Normalizzazione locale dei canali. Ogni colore viene diviso per il valore medio di quel colore nel tassello corrente, un rozzo bilanciamento del bianco locale calcolato al volo. Questo equalizza le magnitudini dei canali in modo che il confronto di varianza per la selezione della guida non sia sbilanciato verso il canale che porta i numeri raw più grandi; deliberatamente non riutilizza il bilanciamento del bianco dichiarato a monte.2

Sfumare la maschera. La maschera binaria viene smussata da una piccola media a box $5\times5$ in una opacità $\alpha \in [0,1]$, usata come peso di composizione finale e come pesi morbidi per canale della ricostruzione à-trous qui sotto. La sfumatura ammorbidisce la cucitura dove la ricostruzione incontra i pixel intatti; nei test numerici ha abbassato l’errore di bordo di questo metodo anziché aumentarlo — rimuoverla è stato testato e qui danneggia in modo misurabile (i pesi morbidi sono portanti), mentre il metodo successore abbandona del tutto la sfumatura (le sue maschere sono binarie dall’inizio alla fine; vedi il cimitero).

Le regole di aggiornamento

Il problema di ottimizzazione enuncia cosa questo metodo minimizza e l’algoritmo narra i suoi stadi; qui ci sono gli effettivi aggiornamenti del segnale della modalità 2021 distribuita, in ordine di esecuzione, per un implementatore che parte da zero. Piani: l’RGB temporaneo sottocampionato quattro volte $u_c$ (demosaicizzazione bilineare, normalizzato per canale), le maschere per canale sfumate $\alpha_c \in [0,1]$ e la loro opacità any-clip $\alpha$. Decomposizione: la piramide B-spline à-trous con passo $2^s$ per scala — sfocature a cascata $\mathrm{LF}_s$, bande di dettaglio $D_s = \mathrm{LF}_{s-1} - \mathrm{LF}_s$, più il residuo più grossolano.

1. Trasferimento di texture (il passaggio RGB), per scala dal fine al grossolano, a ogni pixel dove $\alpha > 0$. Sul vicinato à-trous $3\times3$ (passo $2^s$) della banda di dettaglio, calcola la media e la varianza della patch per canale, scegli la guida $g$ come il canale di varianza di patch più grande (la validità non viene controllata — la radice del fallimento 4), fitta la linea di colore con bias a intercetta nulla sui dettagli,

$$ a_c = \max\!\left( \frac{\operatorname{cov}(D_g, D_c)}{\operatorname{var}(D_g)},\, 0 \right), \qquad b_c = \bar D_c - a_c\, \bar D_g, $$

e miscela la predizione, sfumando quadraticamente con il raggio equivalente della scala — preso al passo $4s$, non $s$: il codice valuta $\sigma$ a s * DS_FACTOR, addebitando a ogni scala anche il fattore di sottocampionamento, per cui la sfumatura è molto più ripida di quanto suggerisca il solo $\sigma_s^2$ ($\beta$ è $\alpha/380$ a $s{=}1$ e $\alpha/97\,000$ a $s{=}2$ — il trasferimento guidato vive di fatto sulle scale più fini):

$$ D_c \;\leftarrow\; \beta_c \,\big(a_c\, D_g + b_c\big) + (1 - \beta_c)\, D_c, \qquad \beta_c = \frac{\alpha_c}{\sigma_{G,\,4s}^{2}}. $$

La risintesi somma le bande elaborate più il residuo, vincolato a $\geq 0$; all’ultima iterazione, grana poissoniana di ampiezza $\sigma = u_c \cdot \texttt{noise_level}$ viene incorporata (solo schiarendo) sotto $\alpha$. Il risultato è diviso in direzione e magnitudine, $r_c = u_c / \lVert u \rVert$ e $n = \lVert u \rVert$, per il passaggio successivo.

2. Diffusione della crominanza (il passaggio croma), stessa piramide sui quattro piani $(r_R, r_G, r_B, n)$: ogni banda di dettaglio dei tre piani di rapporto prende un passo di Eulero esplicito dell’equazione del calore schermata, per pixel dove $\alpha > 0$ e per canale — la banda del piano della norma viene salvata prima del passo e ripristinata dopo, per cui $n$ attraversa la piramide non diffuso,

$$ D \;\leftarrow\; D + \alpha_c\, \kappa \,\big( \mathbf{K}_{\text{iso}} \!*\! D \;-\; \lambda\, D \big), $$

con $\mathbf{K}_{\text{iso}}$ il laplaciano isotropo a nove punti dei princìpi fondamentali, $\kappa = \sigma_B^2 / (2\sqrt{\pi}) \approx 0.31$ la riscalatura da banda a laplaciano, e $\lambda$ la reazione a colore piatto (solid_color). Risintesi come sopra, poi i rapporti vengono rinormalizzati a norma unitaria e ricombinati, $u_c \leftarrow r_c \cdot n$.

3. Itera. I passaggi 1–2 si ripetono iterations volte (il cursore dell’utente); nulla rileva la convergenza, il conteggio è il budget.

4. Composita. La ricostruzione viene sovracampionata quattro volte e miscelata sul mosaico raw attraverso l’opacità sfumata, per fotosito di colore $c$: $\text{out} = \alpha\, u_c + (1 - \alpha)\, \text{raw}$.

Nient’altro scrive un pixel — e, come ha rilevato lo studio con ground truth, nulla di quanto sopra trasferisce un livello: ogni aggiornamento agisce su bande di dettaglio a media nulla o su rapporti, che è esattamente il divario strutturale che il successore chiude.

Implementazione e ottimizzazioni

L’intera ricostruzione gira su un buffer sottocampionato quattro volte — un sedicesimo dei pixel — e il risultato viene sovracampionato e ricompositato sul raw sotto una maschera sfumata. Quella singola decisione compra la maggior parte della velocità del metodo e costa un’accuratezza misurabile: rieseguire la stessa matematica a piena risoluzione abbassa l’errore di bordo di circa il 30 %, perché la ricostruzione sovracampionata è sfocata rispetto all’originale nitido a cui deve unirsi (il metodo successore gira a piena risoluzione esattamente per questo motivo). Le bande di dettaglio à-trous sovrastimano il laplaciano di una costante nota (il $\sigma_B = 1.05537$ della B-spline dà $1/\kappa = 3.1827$), che il passo di diffusione compensa; la diffusione esegue iterations × (un passaggio RGB-rapporto + un passaggio norma) per scala, sull’intero fotogramma — non c’è alcuna segmentazione in regioni, per cui il costo scala con l’immagine, non con l’area tagliata. La grana poissoniana ($\sigma = \text{valore} \times \text{livello di rumore}$) viene rigenerata all’ultima iterazione in modo che le aree ricostruite non appaiano plasticamente lisce accanto alla texture reale.

Problemi scoperti

Ciò che non avviene mai nel diagramma di flusso qui sopra è un trasferimento di livello — un’osservazione invisibile senza ground truth, e da cui parte tutto lo studio del 2026.

Le problematiche. In pratica, gli utenti continuavano a segnalare alte luci bruciate che rimanevano magenta dopo la ricostruzione — proprio il difetto che il metodo esiste per correggere. Lo studio documentato nel resto di questo articolo alla fine ha individuato la causa radice nella matematica qui sopra: il fit guidato veniva applicato alle sole bande di dettaglio. Una banda di dettaglio ha media locale nulla, per cui l’intercetta $b_c$ non porta energia, e solo la texture viene mai trasferita — mai il livello. La componente a bassa frequenza del canale tagliato, bloccata al valore di clip, veniva riaggiunta invariata; un canale bruciato veniva lasciato al clip (o spinto sotto di esso dalla gestione della crominanza) e rimaneva magenta. Nel banco di validazione costruito in seguito, questa ricostruzione originale è spesso peggiore che non fare nulla sui clip a canale singolo: un errore quadratico medio (RMSE) di 0.073 contro 0.049 per lasciare i pixel tagliati, su una scena di tipo naturale dove la ricostruzione finale raggiunge 0.015 (questi tre numeri provengono dal banco del prototipo di ricerca Python — fix_prototype.py nel repository di ricerca  — il cui port NumPy del modulo del 2021 fa le veci del C; le implementazioni distribuite sono valutate nei risultati).

image

Una scena sintetica: tre alte luci sature a canale singolo (dischi rosso, verde, blu) e una luminosa quasi neutra. Ground truth è la scena non tagliata, clipped ciò che il sensore registra, previous method il port Python del modulo à-trous del 2021, corrected method lo stesso progetto con la riparazione a segnale intero descritta qui sotto.

Nei dischi a canale singolo il risultato corretto risale verso il colore vero (ad es. il disco rosso recupera $\approx 1.7$ contro il vero $\approx 1.9$, con verde e blu corrisposti esattamente) dove il metodo precedente rimane sotto il clip ($\approx 0.9$). Il disco neutro completamente tagliato, piatto al clip sia nel sensore sia nel metodo precedente, viene ri-incupolato dal riempimento di luminanza condiviso (centro $\approx 1.5$ contro il vero $\approx 1.6$) e rimane quasi neutro, con la sua crominanza portata dalla diffusione del bordo.

La riparazione in una riga — applica lo stesso fit guidato al segnale intero (bassa frequenza e dettaglio insieme) in modo che l’intercetta porti la media locale e un canale bruciato possa salire sopra il clip (la matematica è nelle regole di aggiornamento) — corregge quella classe di fallimenti, ed è la fondazione che tutto il seguito ha mantenuto: tutti i metodi successivi in questo articolo fittano le linee di colore su valori interi. Era necessaria ma non sufficiente. Con il ground truth con cui misurare — sul progetto riparato, ricostruito a piena risoluzione attorno a una scala di dimensioni di finestra (l' appendice del cimitero lo dettaglia) — sono rimasti quattro fallimenti strutturali, e hanno plasmato il metodo successore:

Primo, la scala di finestre degenera ovunque una finestra veda per lo più dati a livello di clip (finestre fini vicino al bordo, finestre grossolane in profondità all’interno del buco), e un fit degenere (covarianza vicina a zero, pendenza vicina a zero) riempie piatto, all’incirca al livello di clip. Peggio, il passaggio di consegne tra scale consecutive segue un contorno di profondità costante all’interno del buco, e il disaccordo tra le due scale stampa un arco visibile lungo di esso.

Secondo, lo smorzamento $\epsilon$ qui sopra compete con la varianza locale della guida, per cui schiaccia silenziosamente la pendenza ovunque quella varianza sia piccola — cosa che protegge il contenuto rumoroso per caso e appiattisce i gradienti puliti e regolari (come il cielo) con lo stesso meccanismo. Una costante, due conseguenze opposte, nessun valore che serva entrambe.

Terzo, il sensore non subisce clipping in modo brusco: comprime l’ultimo qualche percento sotto la saturazione, per cui la banda di pixel che la ricostruzione tratta come ancore affidabili è registrata sistematicamente troppo bassa.11 Persino un oracolo che ricostruisse l’interno tagliato esattamente si troverebbe allora visibilmente più luminoso dell’anello con bias a cui deve attaccarsi: la cucitura è nei dati, non nella stima, e solo correggere i dati (l’inversione del ginocchio) può rimuoverla.

Quarto, l’intero metodo poggia su un’unica ipotesi : i canali vicini salgono e scendono insieme, quindi un canale tagliato è una funzione affine misurabile di uno sopravvissuto. E certi contenuti (un cielo la cui tinta stessa deriva, l’iridescenza, una texture di colore più fine della finestra) semplicemente non la soddisfano. Lì la guida non contiene alcuna informazione sul canale mancante per nessuno stimatore, quindi il progetto deve misurarlo ($R^2$) e cambiare del tutto la fonte di informazione : levigatezza spaziale invece di trasferimento inter-canale.

Correggere quei difetti ha richiesto più di una correzione — ha richiesto un cambio di paradigma.

Il nuovo metodo : trasposizione armonica

Questa sezione documenta il metodo che viene distribuito oggi — le sue premesse, il suo problema di ottimizzazione, il suo algoritmo, e come l’implementazione di produzione si discosta dal prototipo di ricerca. I risultati misurati, le prestazioni e le conclusioni generali hanno ciascuno la propria sezione più sotto. La trasposizione armonica viene distribuita come una propria modalità di ricostruzione accanto agli originali guided laplacians : il metodo à-trous del 2021 mantiene il suo nome storico e il suo progetto — invariati eccetto per la correzione condivisa della raccolta ai bordi, che migliora i suoi stessi punteggi — e il nuovo metodo è un’attivazione esplicita. Il nucleo di ricostruzione è agnostico rispetto al mosaico del sensore e funziona indistintamente su Bayer e X-Trans, sul processore e all’interno di una pipe OpenCL, dove ogni regione tagliata viene risolta su qualunque lato del bus si sia misurato più veloce (vedi la sezione sulle prestazioni).

Intuizione e contesto

La riparazione sopra cambia ciò che il filtro guidato trasferisce ; la trasposizione armonica cambia come il modello viaggia. Ovunque almeno un canale sia sopravvissuto, la ricostruzione è un problema di regressione, non un problema di inpainting : i canali validi sono dati misurati, presenti in ogni pixel della zona bruciata, e l’unica incognita è la relazione locale (la linea di colore) tra loro e il canale tagliato. Il metodo del 2021 — e la sua ricostruzione corretta, a segnale pieno — valutava quella relazione ovunque una finestra statistica potesse arrivare, e cuciva insieme le valutazioni ; ogni cucitura era una giuntura, e le finestre in profondità dentro un buco grande non raggiungevano nulla. La trasposizione armonica inverte il trasporto : adatta la linea di colore una sola volta, dove i dati la supportano, poi diffonde i coefficienti del modello — non i valori dei pixel — attraverso la zona bruciata come campi lisci, e solo allora li valuta rispetto ai canali sopravvissuti misurati in ogni pixel. I coefficienti sono lisci per natura là dove i valori non lo sono ; le guide reiniettano la struttura a piena risoluzione al momento della valutazione. Perché questa sia una differenza di natura e non di grado — e a cosa si generalizzi — è spiegato nelle conclusioni teoriche.

Tutto il resto che il metodo del 2021 azzeccava viene mantenuto, su basi più solide : il modello della linea di colore e i suoi minimi quadrati pesati su finestra (la regressione del filtro guidato), la cupola biarmonica per la magnitudine priva di guida, la crominanza trasportata come rapporti limitati, la preferenza per le continuazioni lisce e la rigenerazione della grana. Ciò che è nuovo oltre al trasporto : la segmentazione per regione a piena risoluzione (il costo scala con l’area tagliata, non con l’immagine), un’inversione del roll-off del sensore misurata che debiasa la banda vicina al taglio prima che qualsiasi cosa sia adattata (guasto 3 sopra), adattamenti filtrati e affidati alla loro stessa qualità misurata $R^2$ (guasto 4), e un’unica regola di progetto applicata ovunque, derivata dalla legge dell’energia di giuntura delle conclusioni teoriche : nessun passaggio può fare da tramite tra stimatori che sono in disaccordo (guasto 1).

Il problema di ottimizzazione

Nessuno dei due passaggi assembla un’energia globale e invoca un solver con nome, ma ciascuno è la regola di aggiornamento locale di un problema variazionale, e insieme rendono esplicito l’obiettivo. Sulla regione tagliata $\Omega$ la ricostruzione minimizza quattro energie accoppiate sul modello.

1. Coerenza affine inter-canale. Dove esiste una guida valida $g$, ogni canale tagliato dovrebbe essere una funzione affine della guida : il filtro guidato è l’esatto minimizzatore di

$$ E_{\text{affine}} = \sum_{\omega}\sum_{c}\sum_{i \in \omega} w_i \Big[ \big( a_{c} \, u_g(i) + b_{c} - u_c(i) \big)^2 + \varepsilon\, a_{c}^2 \Big], \qquad w_i = [\,i \text{ valid}\,]. $$

Adattare sui valori pieni $u$ (non sul dettaglio) è ciò che fa sì che il minimizzatore trasporti la media locale, così $E_{\text{affine}}$ recupera magnitudine e texture insieme.310 Questo termine è affidato in proporzione alla correlazione al quadrato dell’adattamento $(R^2)^2$; dove $R^2 \to 0$ il canale è invece vincolato alla sua stessa levigatezza del secondo ordine (un termine biarmonico per canale $\int_\Omega (\Delta u_c)^2, \mathrm{d}\Omega$), così l’obiettivo non premia mai una linea di colore che i dati non supportano. L’elevamento al quadrato acuisce quel compromesso così che una correlazione mediocre si appoggi al fallback liscio piuttosto che a una supposizione inter-canale discontinua.

1b. Trasporto anisotropo dei coefficienti (la forma distribuita del termine 1). L’adattamento affine del termine 1 definisce i suoi coefficienti solo dove una finestra contiene abbastanza dati affidabili ; nel resto della zona bruciata l’incognita è il modello stesso. Il metodo distribuito lo estende minimizzando un’energia di Dirichlet anisotropa sui piani dei coefficienti,

$$ E_{\text{transport}} \;=\; \sum_{p \,\in\, \{a,\, b,\, d,\, R^2\}} \int_{\Omega} \nabla p^{\top} D \,\nabla p \;\mathrm{d}\Omega, \qquad p\big|_{\text{anchors}} = p_{\text{fit}}, $$
dove gli ancoraggi sono gli adattamenti filtrati del termine 1 (massa affidabile sufficiente, $R^2 > 0.25$, pendenze
limitate) e $D$ è il tensore di orientamento adattivo alla varianza dello
passaggio 3, costruito dalla struttura misurata della guida
dominato dal gradiente su una rampa di alone pulita (il modello viaggia radialmente verso l’interno dal bordo), dominato dall’isofota dove un bordo netto attraversa la zona (le linee di colore non devono mescolarsi attraverso il confine di un oggetto). La sua equazione di Eulero–Lagrange $\operatorname{div}(D \, \nabla p) = 0$ è il riempimento orientato ; con $D = I$ si riduce al semplice riempimento armonico. La valutazione $\hat u_c = a\,u_{g_1} + b\,u_{g_2} + d$ rispetto alle guide misurate chiude il termine : l’energia trasporta il modello, e i dati ripristinano il dettaglio.

2. Curvatura della magnitudine (fallback). Dove nessuna guida sopravvive, il dettaglio fine è perso ma la forma a bassa frequenza non lo è: essa dovrebbe continuare la curvatura circostante piuttosto che appiattirsi. Per un canale parzialmente tagliato questo è il termine biarmonico per canale $\int_\Omega (\Delta u_c)^2, \mathrm{d}\Omega$ già invocato sopra. Per il nucleo interamente tagliato viene applicato una sola volta alla luminanza sommata $L_\text{sum} = R+G+B$, un’unica cupola condivisa invece di tre divergenti:

$$ E_{\text{bihar}} = \int_\Omega (\Delta L_\text{sum})^2\,\mathrm{d}\Omega, \qquad \Delta^2 L_\text{sum} = 0, \qquad L_\text{sum}\big|_{\partial \Omega} = L_{\text{sum}}^{\text{valid}} , $$

ancorato al vero bordo valido del nucleo; la sua equazione di Eulero–Lagrange $\Delta^2 L = 0$ è il riempimento a cupola sopra.

3. Levigatezza della crominanza (fallback). Dove nessuna guida sopravvive, i rapporti ricostruiti $r = \text{RGB}/L_\text{sum}$ dovrebbero essere lisci e combaciare con il bordo, opzionalmente polarizzati verso la piattezza:

$$ E_{\text{chrominance}} = \int_\Omega \Big( \lVert \nabla r \rVert^2 + \lambda \, \lVert r \rVert^2 \Big)\, \mathrm{d}\Omega, \qquad r\big|_{\partial \Omega} = r_{\text{valid}} . $$

La sua equazione di Eulero–Lagrange è l’equazione di Poisson schermata che la diffusione integra,4 e la ricombinazione $\text{RGB} = L\cdot r$ chiude il riempimento congiunto.

Le energie agiscono su pixel complementari : il termine affine dove un canale sopravvive (pesato dalla sua correlazione $R^2$), il termine biarmonico per canale dove non sopravvive, e la cupola di luminanza condivisa più la diffusione della crominanza solo nei nuclei interamente tagliati — quest’ultima minimizzata soggetta a una disuguaglianza : il rapporto diffuso di un canale tagliato non può mai scendere sotto la sua soglia di saturazione nello spazio dei rapporti, $r_c \geq c_{0,c}/L_\text{sum}$, il che trasforma il termine di crominanza in un problema con ostacolo (la matematica è in la sottosezione sulla crominanza). L’insieme ricostruibile stesso è per canale : $\Omega_c$ contiene i pixel sopra la soglia di taglio — estesa fino a $0.9$ di essa per i canali il cui roll-off del sensore si è attivato (la sovrascrittura di banda del passaggio 2), dove la soglia $c_{0,c}$ è la misura corretta del ginocchio piuttosto che la lettura saturata. Ogni energia è minimizzata dalla propria fase : il termine affine dagli adattamenti guidati (una finestra dimensionata sul raggio di ricostruzione della regione) ; il termine di trasporto dalla rilassazione ancorata e orientata del passaggio 3 ; i termini di levigatezza dalle loro soluzioni lineari dirette. Non c’è deliberatamente alcuna energia di levigatura posteriore che agisca sull’output : un progetto precedente stirava le giunture a posteriori con un regolarizzatore pesato per incertezza, e ritirarlo è il punto — le giunture non vengono mai create (vedi il cimitero). Questa è la stessa filosofia ingegneristica di diffuse or sharpen: uno stack di regole di aggiornamento locali, fisicamente motivate, il cui punto fisso combinato è la ricostruzione, piuttosto che un singolo problema inverso monolitico.

L’algoritmo

Tutto avviene sul mosaico raw, in RGB lineare riferito alla scena (scene-referred), prima della demosaicizzazione, e a piena risoluzione. Il metodo ricostruisce ogni canale tagliato dai canali che sono sopravvissuti (lungo la linea di colore locale, affidandovisi in proporzione a quanto essa effettivamente regge), e dove nessun canale è sopravvissuto ricostruisce un’unica cupola di luminanza congiunta e trasporta la crominanza circostante verso l’interno.

La preparazione del mosaico — demosaicizzazione bilineare verso un RGB usa e getta, normalizzazione
locale dei canali e la maschera di taglio per canale — è condivisa con il metodo del 2021 ed è descritta
nella sua sezione sull’algoritmo, con un’aggiunta e tre affinamenti. L’aggiunta
per i nuclei interamente tagliati una magnitudine scalare $L_\text{sum}$ (la luminanza sommata $R+G+B$) viene separata dalla crominanza $\text{RGB}/L_\text{sum}$ — ogni canale diviso per la luminanza sommata, una descrizione del colore limitata e priva di luminosità che questo articolo usa in modo coerente sotto quel nome — così che le due possano essere ricostruite con mezzi diversi : una cupola per la magnitudine, la diffusione per la crominanza. Gli affinamenti, ciascuno tracciato da un artefatto di bordo misurato :
  • le maschere sono binarie, da un capo all’altro. La modalità del 2021 sfuma la sua maschera ; il successore mantiene ogni maschera di validità e di composizione netta (il suo unico peso liscio è il passaggio di consegna sfocato del nucleo congiunto, passaggio 7 dell’algoritmo, che fonde due ricostruzioni — non riclassifica mai le misure). Le maschere di validità per canale filtrano ogni adattamento e valutazione, e sfumarle riclassificava i fotositi tagliati sul bordo — i cui valori raw stanno alla soglia di rilevamento, polarizzati in basso sotto il roll-off del sensore — come ancoraggi validi su contorni obliqui, trascinando la ricostruzione del bordo verso il livello di taglio (una rampa cedente di $\sim 10$ px rispetto al ground truth). Il peso di composizione è un interruttore netto : i fotositi validi mantengono esattamente la loro misura, i fotositi tagliati prendono la ricostruzione pura (un alfa sfumato è stato misurato non cambiare nulla una volta che le due correzioni di questo elenco sono in atto — il ritiro è documentato in il cimitero).
  • i valori raw tagliati sono soglie, mai target di fusione. Sui fotositi tagliati la lettura raw è un limite inferiore, non una misura : il composito scrive $\max(\text{raw}, \text{reconstruction})$, mai una fusione verso la lettura polarizzata (la vecchia fusione sfumata stampava un avvallamento a V attraverso il valore raw a ogni contorno).
  • i bordi fanno da specchio. L’anello di bordo della raccolta bilineare copiava il fotosito centrale in tutti e tre i canali e vincolava tutti e tre i flag di taglio al canale del centro stesso — corrompeva le guide e tratteggiava le maschere lungo le prime e le ultime righe e colonne, che ancoravano gli adattamenti delle righe di bordo al livello di taglio. L’indicizzazione riflessa dei vicini (il pattern Bayer è 2-periodico, quindi lo specchiamento preserva il colore di ciascun vicino) ripristina la logica interna sui bordi ; questa correzione è condivisa con la modalità del 2021.

Ogni scena con ground truth è migliorata su entrambe le metriche grazie a questi tre soli affinamenti — l’errore del cielo con roll-off è calato di un terzo — perché gli anelli di bordo del banco di prova sono esattamente dove gli ancoraggi polarizzati si infiltravano.

L’obiettivo concettuale della ricostruzione può essere enunciato in una frase : ovunque almeno un canale sia sopravvissuto, la ricostruzione è un problema di regressione, non un problema di inpainting : i canali validi sono dati misurati, presenti in ogni pixel della zona tagliata, che portano la vera struttura della scena. L’unica regola di progetto dell’algoritmo, appresa a caro prezzo, è che nessun passaggio può fare da tramite tra stimatori che sono in disaccordo (la legge dell’energia di giuntura delle conclusioni teoriche). Ogni fase qui sotto è quindi o priva di giunture per costruzione, o debiasa i dati così che gli stimatori siano in accordo.

La procedura passo passo, così come implementata in process_harmonic_bayer (e nel suo gemello X-Trans) :

I numeri nel grafico si riferiscono ai passaggi dettagliati sotto di esso.

flowchart TD
  subgraph P ["once per image"]
    direction TB
    A["raw mosaic"] --> B["1 · interpolate
+ clip masks"] B --> K["2 · knee inversion"] K --> S["1 · depth map
+ segmentation"] end subgraph R ["for each clipped region"] direction TB CF["3 · colour-line fits
(magnitude + texture)"] --> HF["4 · fine-detail refit"] HF --> SD["5–6 · floors
+ self-dome"] SD --> JC["7 · all-clipped core"] JC --> AN["8 · chrominance coherence"] end S --> CF AN --> O["remosaic + composite"]

1. Rilevamento, interpolazione, segmentazione. Il mosaico raw della matrice di filtri colore viene interpolato bilinearmente, con flag di taglio per canale alzati a $0.995\,c$ per il livello di taglio $c$ e una maschera di validità binaria per canale ; la trasformata di distanza euclidea dà poi a ogni pixel la sua profondità $\delta$ all’interno della zona tagliata, e la segmentazione a componenti connesse raggruppa i pixel tagliati in regioni, ciascuna portante il proprio raggio di ricostruzione della trasformata di distanza.

2. Inversione del roll-off (ginocchio) del sensore. I sensori reali comprimono gli ultimi punti percentuali sotto la saturazione, quindi la banda vicina al taglio $[0.8\,c, 0.995\,c)$ contiene valori polarizzati in basso. (La banda è tagliata generosamente : sui sensori che tagliano bruscamente invece di smorzarsi, la garanzia di no-op qui sotto rende la larghezza extra gratuita.) La ricostruzione estrapola stime non polarizzate su questa banda, così le due sono in disaccordo al contorno di rilevamento : una giuntura che nessuna pesatura può rimuovere. La correzione debiasa i dati stessi : una regressione congiunta su finestra predice ogni valore di banda dai canali pienamente affidabili, le coppie (misurato, predetto) sono raggruppate in 24 bin sulla banda, e il rialzo mediano per bin è accettato solo quando è statisticamente significativo — mediana sopra il doppio del suo errore standard, una soglia di confidenza di $\approx 95\,\%$, con almeno 100 voti così che la stessa stima dell’errore sia stabile ; 24 bin è la suddivisione più fine con cui la banda può essere tagliata mantenendo ogni bin che si è popolato :

$$ \hat{k}^{-1}(v) = v + \operatorname{median}\big\{\, \hat v_i - v_i \;\big|\; v_i \in \text{bin}(v) \,\big\} $$

dove $k$ è la compressione sconosciuta del sensore, quindi $\hat{k}^{-1}$ è la correzione stimata — la funzione che prende un valore misurato $v$ dalla banda e restituisce ciò che il sensore avrebbe dovuto registrare. I $v_i$ sono tutti i pixel di banda misurati il cui valore cade nello stesso bin di $v$ ; ogni $\hat v_i$ è ciò che la regressione della linea di colore predice quel pixel dovrebbe essere, giudicando dai suoi canali vicini pienamente affidabili. La loro differenza $\hat v_i - v_i$ è il voto di un pixel su quanto il sensore abbia sotto-registrato a quel livello, e la correzione del bin è la mediana dei suoi voti — robusta rispetto agli outlier che una media seguirebbe. La curva risultante viene poi resa monotona e solo-crescente. Su dati a taglio netto (non polarizzati) ogni mediana di bin è zero entro il rumore, quindi la correzione ha una garanzia di no-op, verificata bit-esatta sui quattro casi sintetici a taglio netto, mentre su pk1synth (l’unica scena sintetica generata con un roll-off analitico del sensore, così che il suo ground truth tracci il ginocchio reale) la stima cieca combacia con la vera curva con un errore quadratico medio di $7\cdot10^{-4}$ sul canale verde e $1.8\cdot10^{-3}$ sul più rado rosso :

The blind knee estimate against the ground truth, on the one synthetic scene with an analytic sensor rolloff (pk1synth). Circles : the true corrected value, read from the (ground truth, clipped) pixel pairs, per measured-value bin. Lines : what the estimator infers from the clipped mosaic alone, never having seen the truth. The dotted diagonal is the identity — a sensor with no rolloff. Reproduce with make_knee_figure.py.
Contano due dettagli implementativi : la stima gira su una copia del mosaico raw con quad-binning $2\times 2$ (ogni cella Bayer 2×2 produce un campione co-locato di rosso, verde (mediato) e blu), mai sull’interpolazione bilineare : l’interpolazione campiona ogni canale attraverso un filtro spaziale diverso a seconda della sua posizione nel pattern del mosaico, e quell’errore alternante è della stessa dimensione del segnale del ginocchio che soffocherebbe ; e la correzione viene applicata sia agli ancoraggi della ricostruzione sia all’output composito, mentre il rilevamento del taglio resta vincolato ai valori misurati.

La sovrascrittura di banda. Il rialzo ripristina il livello della banda, ma una mappa di valori non può ripristinare una pendenza che il sensore non ha mai registrato : dove la compressione ha appiattito la banda a una quasi-costante, la banda rialzata è anch’essa una quasi-costante, e la sua giunzione con la ricostruzione crescente stampa un doppio contorno fine sotto l’ingrandimento dei bordi (la banda è misurabilmente $7$–$14\times$ più piatta del ground truth, delimitata da due picchi di gradiente). L’informazione non è però persa — le guide sono non tagliate attraverso la banda e portano la vera pendenza. Quindi per ogni canale il cui roll-off si è attivato, il rilevamento si estende sotto la soglia nella banda profonda : quei pixel sono ricostruiti dal modello della linea di colore come qualsiasi pixel tagliato, con la loro misura rialzata dal ginocchio come soglia di saturazione per pixel, e gli adattamenti si ancorano su dati corretti dal ginocchio sotto la banda. La soglia rende la sovrascrittura conservativa per costruzione — su immagini reali rumorose il modello raramente supera la misura rialzata, e l’output resta identico pixel per pixel ; sulla scena di banco con roll-off, dove il modello conosce davvero la pendenza, l’errore della zona cala di un ulteriore fattore cinque quando la sovrascrittura entra in gioco (RMSE $0.012 \to 0.0025$ misurato allora ; $0.0053$ nella build distribuita, dopo che i successivi round di composizione hanno preso le loro parti) e il profilo di gradiente del contorno diventa indistinguibile dal ground truth. I canali senza un roll-off misurato mantengono il semplice rilevamento : sui sensori a taglio netto la banda è un dato affidabile e resta ancorata.

3. Il campo dei coefficienti. Per ogni canale tagliato $v$ con guide $u_1, u_2$, un adattamento ai minimi quadrati pesato su finestra viene calcolato a una singola scala $\sigma = \operatorname{clip}(r/6, 8, 64)$ (con $r$ il raggio di ricostruzione della regione — il supporto $\pm 3\sigma$ della finestra copre allora il raggio, così che anche l’adattamento del pixel più profondo raggiunga dati validi ; la soglia inferiore mantiene abbastanza campioni per un adattamento stabile su regioni minuscole, e il tetto limita il costo di quelle enormi), sui pixel dove tutti e tre i canali sono validi :

$$ \begin{aligned} (a, b, d)(x) &= \arg\min_{a,b,d} \sum_y w(y)\, G_\sigma(x - y)\, \big( v(y) - a\, u_1(y) - b\, u_2(y) - d \big)^2 \\ \hat v(x) &= a(x)\, u_1(x) + b(x)\, u_2(x) + d(x) \end{aligned} $$

dove $x$ è il pixel il cui modello locale viene costruito e $y$ scorre sui suoi vicini. La finestra $G_\sigma(x-y)$ è una gaussiana che pesa i vicini per distanza (peso pieno vicino, sfumando su circa $\sigma$ pixel), e $w(y)$ è la maschera di affidabilità — uno sui pixel dove tutti e tre i canali contengono misure reali, zero altrove, così che i pixel tagliati non votino mai. La prima riga chiede, a ogni $x$ : quali pendenze $a, b$ sulle due guide e quale offset $d$ spiegano meglio il canale tagliato sul vicinato affidabile ? La seconda riga poi usa quel modello locale : la stima $\hat v(x)$ legge le due guide a $x$ stesso e le mappa attraverso la relazione adattata. In pratica la minimizzazione non viene mai eseguita per pixel — viene risolta da dieci piani di momenti sfocati (un conteggio di massa affidabile, tre medie, sei secondi momenti, raccolti in tre sfocature a quattro canali) attraverso le equazioni normali $2\times2$. I momenti sono accumulati rispetto alla media per regione di ciascun canale : in aritmetica float la forma raw $E[u^2] - E[u]^2$ si cancella catastroficamente su contenuto liscio (la media al quadrato sovrasta la varianza) e la divisione dell’adattamento amplifica le cifre superstiti in rumore di pendenza dipendente dal dispositivo — la centratura rimuove la cancellazione, e le pendenze e $R^2$ sono invarianti sotto lo spostamento mentre l’intercetta viene despostata subito dopo l’adattamento. Il sistema è risolto con uno smorzamento di Tikhonov (ridge) relativo $\lambda = 10^{-3}\,(\operatorname{var} u_1 + \operatorname{var} u_2)/2$ che scala con il segnale invece di divorare pendenze deboli-ma-reali. Il $10^{-3}$ è il fattore più piccolo che ha stabilizzato le finestre degeneri sul banco — qualsiasi valore più grande comincia a appiattire pendenze reali.

La novità è ciò che succede dopo : invece di valutare ogni pixel con qualunque adattamento la sua stessa finestra potesse raggiungere (il modo della scala), i piani dei coefficienti stessi vengono diffusi attraverso la zona tagliata, orientati dalla struttura misurata della guida, e solo allora valutati rispetto alle guide misurate. I coefficienti sono lisci per natura là dove i valori non lo sono : diffonderli trasporta il modello nella zona, e la valutazione ripristina l’intera struttura locale portata dai canali validi. Nessuna scala, nessun filtro di profondità, nessuna scrittura per curve di livello (nessun anello di uguale profondità stampato separatamente) — nessuna giuntura, per costruzione. Questo trasporto del modello piuttosto che del contenuto è l’idea più trasportabile dell’articolo, e come differisca in natura dalla sfocatura dei coefficienti stessa del filtro guidato è enunciato in Tre risultati che riteniamo generali. Un pixel si qualifica come ancoraggio di diffusione solo se la sua finestra conteneva abbastanza massa affidabile e il suo adattamento è sensato : l’$R^2$ dell’adattamento, il coefficiente di determinazione, è la quota della varianza locale del canale che la linea di colore spiega (1 significa che le guide predicono il canale perfettamente, 0 significa per niente), e gli ancoraggi richiedono $R^2 > 0.25$ con pendenze limitate. Entrambi i filtri sono calibrati da misure, non dal gusto : contenuto con nessuna linea di colore affatto ottiene comunque $R^2 \approx 0.25$–$0.6$ (contro $\approx 0.9$ dove la linea di colore è reale), quindi il filtro rifiuta solo adattamenti peggiori della pura decorrelazione — incidenti numerici, non modelli deboli ; e le pendenze fisiche della linea di colore sono limitate dai rapporti di saturazione dei canali (sotto $4$ su ogni fotocamera misurata), quindi il limite $|a| < 64$ sta un ordine di grandezza sopra qualsiasi cosa fisica, e solo le finestre degeneri a varianza quasi nulla — le cui pendenze esplosive avvelenerebbero il confine di diffusione — lo fanno mai scattare. La diffusione è un riempimento di Jacobi da grossolano a fine : ogni incognita viene ripetutamente sostituita dalla media che i suoi vicini implicano. Tre termini portano l’intero schema :

  • Cella. Il riempimento non gira per pixel. I piani dei coefficienti vivono su una griglia grossolana di passo $\sigma/4$ (limitato ad al più $8$ px), e una cella è un nodo di quella griglia — un blocco di pixel di dimensione $\sigma/4$. I coefficienti escono da finestre di adattamento larghe $\sigma$, quindi quattro celle per $\sigma$ le sovracampionano, e il limite del passo delimita l’errore finale di sovracampionamento bilineare.
  • Ancoraggio. Una cella la cui finestra di adattamento ha superato i filtri di affidabilità sopra — contiene un coefficiente effettivamente misurato su dati validi.
  • Fissato. Le passate trattano gli ancoraggi come dati di confine fissi e riscrivono soltanto le celle incognite tra di essi.

Questa forma è scelta per robustezza. Un gradiente coniugato in float sul sistema puramente armonico quasi singolare diverge stocasticamente quando la zona raggiunge il confine della regione. Jacobi con ancoraggi fissati non può : ogni aggiornamento è una media di vicini, quindi la rilassazione obbedisce al principio del massimo — nessuna incognita può mai uscire dall’intervallo dei valori di ancoraggio.

La convergenza viene dalla profondità della piramide, non dal conteggio delle passate. Il livello più grossolano parte da una media di ancoraggio piatta — lo stato più lontano possibile dalla soluzione — e il modo di errore più lento di Jacobi su un buco largo $N$ celle decade in $\mathcal{O}(N^2)$ passate. Quindi la piramide continua a dimezzarsi finché il lato lungo della griglia più grossolana è al più $8$ celle ($8^2 = 64$ passate per rilassarsi, comodamente entro il budget), e ogni livello poi esegue le stesse $100$ passate piatte — i livelli più fini correggono solo l’errore di interpolazione locale. Una piramide più superficiale con un conteggio fisso troncava misurabilmente il riempimento sui buchi profondi — i numeri sono in i risultati generali qui sotto.

Il trasporto stesso non è nemmeno uniforme : è orientato dalla struttura misurata della guida. Un piano di orientamento $L_\text{mean}$ — la media dei canali validi ovunque almeno uno sopravviva, la media piatta del plateau nel nucleo interamente tagliato (dove il tensore degenera poi all’identità) ; questa è una luminanza diversa dalla magnitudine sommata $L_\text{sum}$ che la cupola e i rapporti usano — viene sottocampionato a ogni livello della piramide, e le medie dei vicini sono pesate dal tensore anisotropo

$$ D \;=\; \big[\, m + (1-m)\,c_2 \,\big]\; t\,t^{\top} \;+\; \big[\, m\,c_2 + (1-m) \,\big]\; g\,g^{\top}, \qquad c_2 = e^{-\lVert\nabla L_\text{mean}\rVert / (4\,\langle\lVert\nabla L_\text{mean}\rVert\rangle)}, $$

dove $g$ è la direzione del gradiente unitario di $L_\text{mean}$ (in salita), $t$ la direzione isofota unitaria, e $\langle\lVert\nabla L_\text{mean}\rVert\rangle$ la magnitudine del gradiente mediata sulla regione — la normalizzazione che rende lo smorzamento indipendente dall’esposizione — (lungo le linee di livello), e $c_2$ lo stesso smorzamento di attraversamento dei bordi della passata di crominanza del passaggio 8. Normalizzare il gradiente per la propria media regionale rende quello smorzamento privo di scala — solo i gradienti ben al di sopra della media dell’alone leggono come confini, il $4$ imposta quanto morbida sia la lettura. Il peso di fusione $m \in [0, 1]$ è la probabilità di bordo, misurata dalla varianza su finestra corretta per la tendenza di $L_\text{mean}$ :

$$ m = \frac{v}{v + (k\,\bar L_\text{mean})^2}, \qquad v = \max\!\big(\operatorname{var}_w(L_\text{mean}) - \tfrac{4}{3}\lVert\nabla L_\text{mean}\rVert^2,\; 0\big), \qquad k = 0.15, $$

dove $\operatorname{var}_w$ e $\bar L_\text{mean}$ sono la varianza e la media su finestra del piano di orientamento, e il termine $\tfrac{4}{3}\lVert\nabla L_\text{mean}\rVert^2$ sottrae la varianza che la rampa locale spiega : una rampa pura di pendenza $\nabla L_\text{mean}$ vista attraverso una finestra di varianza spaziale $\sigma_w^2$ ha varianza di intensità $\sigma_w^2 \lVert\nabla L_\text{mean}\rVert^2$, e la finestra qui (due passate box $3\times3$) ha $\sigma_w^2 = 2 \cdot \tfrac{2}{3} = \tfrac{4}{3}$ per asse. Un gradiente di alone liscio, per quanto ripido, non lascia quindi alcun residuo, mentre un bordo netto lascia una varianza che nessuna rampa può giustificare. La soglia $k$ — il contrasto relativo sopra il quale la texture residua viene letta come confine — è l’unica costante del trasporto che non ha potuto essere derivata, quindi è stata calibrata con uno spazzolamento fine empirico sulle sei scene con ground truth : ogni scena resta a o sotto l’errore isotropo sull’intero intervallo $k \in [0.14, 0.25]$ (il progetto non è fragile rispetto a essa), e $k = 0.15$ è il valore che migliora ogni scena simultaneamente e si prende la vittoria sull’occlusione con margine. I due limiti di $m$ si leggono naturalmente. Su una rampa di alone pulita ($m \to 0$), $D \to g\,g^{\top} + c_2\,t\,t^{\top}$ : il modello viaggia radialmente, lungo la rampa di luminosità dal bordo verso l’interno, dove la sua informazione risiede davvero. Dove un bordo netto attraversa la zona bruciata ($m \to 1$), $D \to t\,t^{\top} + c_2\,g\,g^{\top}$ con $c_2$ piccolo : il trasporto corre lungo il confine e rifiuta di attraversarlo — un bordo dentro la zona significa che il contenuto oltre segue un’altra linea di colore, e mescolare i due modelli attraverso di esso è esattamente il guasto di occlusione che il cimitero documenta. La discretizzazione usa lo stencil che preserva la non negatività condiviso con la diffusione del passaggio 8 (tutti gli otto pesi dei vicini $\geq 0$), così che il riempimento orientato resti una combinazione convessa dei suoi ancoraggi : il principio del massimo sopravvive all’orientamento. Una soglia di peso $10^{-4}$ mantiene ogni cella connessa dove il tensore collassa a quasi zero ; quattro ordini di grandezza sotto i pesi di lavoro, non compete mai con l’orientamento. I pixel con un’unica guida sopravvissuta ricevono lo stesso trattamento con un adattamento a una guida.

Una sottigliezza di ordinamento completa il passaggio. Il canale più profondo — quello con più fotositi tagliati, la cui zona contiene i nuclei dove diversi canali sono bruciati contemporaneamente — non viene valutato subito : i suoi coefficienti diffusi sono messi da parte, e la valutazione gira per ultima, dopo che gli altri canali tagliati sono stati ricostruiti, così che ogni guida che legge sia una superficie continua. Valutarlo rispetto a una guida che salta da valori misurati a un plateau di taglio stamperebbe il contorno di taglio di quella guida nel risultato come un arco visibile. In profondità dentro il nucleo, dove quelle guide sono esse stesse ricostruzioni (così che ogni salto compone errore), la stima si fonde nuovamente verso l’adattamento diretto a una guida attraverso un peso liscio — peso liscio per campi lisci non lascia alcuna curva di livello da stampare.

La qualità dell’adattamento $R^2$ viene diffusa accanto a $(a, b, d)$ come quarto piano, sull’insieme di ancoraggi più ampio basato solo sulla massa (resta limitata anche dove l’adattamento non lo è).

4. Guida ibrida a banda laplaciana delle alte frequenze. L’adattamento a segnale pieno trasferisce la texture fine delle guide con i guadagni impliciti nella covarianza totale ; dove la linea di colore è debole quella texture non appartiene al canale ricostruito. La stima è divisa a $\sigma/4$ e la sua banda di dettaglio è ricostruita da due fonti candidate : il trasferimento smorzato (le alte frequenze a segnale pieno scalate dal $R^2$ diffuso) e una linea di colore dedicata alla banda di dettaglio (l’omonima del metodo del 2021, riportata in auge), adattata sui piani ad alta frequenza con guadagni ridotti da $R^2$ (su una banda a media zero, la contrazione è lo stimatore corretto : non c’è magnitudine da perdere, solo rumore da non stampare). Le due sono fuse tramite quote di minima energia quadratica,

$$ w = \frac{e_d^2}{e_d^2 + e_g^2}, \qquad e_{d,g} = \text{blurred } |\text{HF}_{d,g}|, $$

il che non necessita di alcun discriminatore di contenuto : un guadagno di finestra mista che sbaglia a un bordo di oggetto si manifesta come un picco locale di energia ad alta frequenza, quindi il guasto si rileva da sé e il percorso smorzato subentra esattamente lì. Questo è ciò che rimuove l’ultimo residuo della scena con roll-off (il suo punteggio di eccesso di bordi cala da 1.48 a 0.94 contro una soglia di ground truth di 0.69 : il rumore della banda decompressa non stampa più — le correzioni di polarizzazione del bordo della build finale lo portano a 0.74, e la sovrascrittura di banda del passaggio 2 a 0.64, la tabella dei risultati) mentre migliora i casi con texture che l’adattamento della sola banda di dettaglio soleva rovinare.

5. Soglia di saturazione morbida. La soglia fisica (un canale tagliato è almeno la sua lettura saturata $c_0$) è applicata come vincolo arrotondato, $\tfrac{1}{2}\big(e + c_0 + \sqrt{(e - c_0)^2 + (0.02\,c_0)^2}\,\big)$ : il duro $\max(e, c_0)$ stampa il contorno vincolante come un bordo ovunque una predizione debole oscilli attorno alla saturazione. La larghezza di transizione $0.02\,c_0$ è il due percento del valore saturato — sotto qualsiasi cosa visibile, ma abbastanza ampia che il gradiente del vincolo non salti mai.

6. Auto-cupola con filtro di profondità e accoppiamento di tinta. Dove il modello è dubbioso e il pixel è poco profondo, una continuazione automatica liscia subentra, costruita con accoppiamento di tinta (i canali sono accoppiati attraverso un’unica crominanza condivisa, così che la tinta percepita non possa derivare) : un’unica cupola biarmonica di luminanza condivisa per una crominanza riempita armonicamente, mai tre canali indipendenti, così che il fallback non possa dividere la tinta verso verde/magenta (il guasto che teneva disabilitato il suo antenato per canale). Il peso del passaggio di consegna è :

$$ \text{dome fraction} = \big(1 - S_{0.4}^{0.85}(R^2)\big)\; e^{-\left(\delta / 1.5\sigma\right)^2} $$

dove $\delta$ è la profondità del pixel dentro la zona tagliata (la sua distanza dal pixel valido più vicino), $\sigma$ la scala di adattamento, e $S$ uno smoothstep, una rampa morbida da 0 a 1 tra le due soglie, qui mappando la qualità dell’adattamento sotto $0.4$ a “dubbioso” e sopra $0.85$ a “affidabile”. I bordi della banda e la scala di profondità $1.5\sigma$ sono stati regolati sul banco con ground truth ; la scala di profondità lega la portata della cupola alla portata stessa della finestra di adattamento, così che il passaggio di consegna avvenga dove l’adattamento esaurisce davvero i campioni. I due fattori rispondono a due domande diverse : $R^2$ chiede se la linea di colore è reale qui, e la profondità chiede se la cupola è affidabile qui (l’estrapolazione biarmonica è eccellente vicino al bordo e degrada con la distanza). La profondità è l’unico segnale che abbiamo trovato le cui distribuzioni effettivamente separano le zone profonde correlate dal contenuto poco profondo decorrelato (vedi il cimitero) ; gli interni profondi restano sempre sul campo dei coefficienti.

7. Nucleo congiunto, sfumato. I pixel interamente tagliati non hanno guide : la cupola biarmonica di luminanza condivisa e la diffusione schermata della crominanza del bordo della ricostruzione li ricostruiscono, invariate — ma il composito è ora sfumato su una maschera interamente tagliata sfocata invece che scritto attraverso una netta. La cupola e la crominanza diffusa sono entrambe valide oltre il confine del buco, quindi fonderle nei canali tagliati dell’anello circostante è continuo nello spazio senza alcun costo per il nucleo stesso.

8. Chrominance coherence. The structure-steered chrominance of the all-clip core is now solved directly. The unknown $u$ is each chrominance plane in turn ; the equation is $\mathrm{div}(D\,\nabla u) = 0$, the steady state of the anisotropic diffusion of the theory section and, equivalently, the exact minimizer of its weighted smoothness energy $\int_\Omega \nabla u^\top D\,\nabla u\,\mathrm{d}\Omega$, with the coefficient-field results as Dirichlet anchors, fixed boundary values the solve must honour. The tensor is built from the recovered luminance (the reconstruction’s own output, smoothed by two box-blur passes) : with $g$ the unit gradient of that smoothed luminance and $t = g^\perp$ the isophote direction,

$$ D = t\,t^\top + e^{\,-\lVert \nabla L_\text{sum} \rVert / (4\,\langle \lVert \nabla L_\text{sum} \rVert \rangle)}\; g\,g^\top $$

cioè : conduttanza uno lungo le isofote, smorzata esponenzialmente attraverso di esse, lo smorzamento normalizzato dalla magnitudine media del gradiente dell’immagine stessa $\langle \lVert \nabla L_\text{sum} \rVert \rangle$ così che l’orientamento non dipenda dall’esposizione (questo tensore legge la luminanza sommata recuperata — la forma gemella del riempimento dei coefficienti legge $L_\text{mean}$). La discretizzazione è lo stencil che preserva la non negatività di Weickert, una M-matrice simmetrica definita positiva, così che il principio del massimo discreto valga per costruzione : i rapporti diffusi non possono mai superare la crominanza del bordo, cosa che il precedente flusso esplicito poteva. La fattorizzazione di Cholesky sparsa fattorizza il nucleo una volta e sostituisce all’indietro i tre canali. (Un confronto tra solver sta dietro questa scelta : vedi il cimitero per il fallimento istruttivo dell’altra formulazione esatta.) Il rimosaicizzamento finale compone l’output dal CFA corretto dal ginocchio, così che la banda debiasata raggiunga il file, non solo gli adattamenti.

Le regole di aggiornamento

Il problema di ottimizzazione enuncia ciò che la ricostruzione minimizza e l’algoritmo narra le sue fasi ; la discussione qui sotto analizza il progetto — cosa rappresentano i parametri, come sono stati regolati, come le scelte si relazionano alla teoria consolidata. Ciò di cui ha bisogno un implementatore che parte da zero sono gli effettivi aggiornamenti del segnale : quale equazione scrive quale piano, in quale ordine. Ecco il catalogo completo, in ordine di esecuzione, per una regione. Piani : stime per canale $u_c$ (inizializzate all’interpolazione bilineare del mosaico misurato, corretta dal ginocchio), validità binarie $v_c$, soglie per pixel $c_{0,c}$ (la misura corretta dal ginocchio di ogni pixel ricostruibile), luminanza sommata $L_\text{sum} = \sum_c u_c$ e rapporti $r_c = u_c / L_\text{sum}$ (il piano di orientamento $L_\text{mean}$ della regola 1 è la media dei canali validi, una quantità separata).

1. Adatta e trasporta la linea di colore (passaggi 2–3 dell’algoritmo). L’adattamento è sui valori pieni dei pixel — mai il dettaglio con media sottratta — e usa solo i campioni dove i canali coinvolti sono validi. Nella forma a una guida, sulla finestra gaussiana $\omega$ :

$$ a_c = \frac{\operatorname{cov}_\omega(u_g, u_c)}{\operatorname{var}_\omega(u_g) + \varepsilon}, \qquad b_c = \bar u_{c,\omega} - a_c\, \bar u_{g,\omega}, \qquad R^2 = \frac{\operatorname{cov}_\omega(u_g, u_c)^2}{\operatorname{var}_\omega(u_g)\,\operatorname{var}_\omega(u_c)}, $$

con $\varepsilon$ uno smorzamento di Tikhonov relativo che protegge la divisione dove la guida è piatta ; la forma a due guide risolve le analoghe equazioni normali $2\times2$ per $(a, b, d)$ dai dieci piani di momenti sfocati con gaussiana. La pendenza non è vincolata di segno (una prima revisione della scala la vincolava ; gli adattamenti distribuiti sono invece protetti dai filtri di ancoraggio $R^2 > 0.25$ e $|a| < 64$). Altri due filtri che un implementatore non deve saltare : le finestre di adattamento sono pesate da un’affinità di luminanza morbida $\min(L_\text{sum} / 0.35\,\bar L_\text{sum}^{\,\text{rim}}, 1)^2$ — i campioni molto più scuri del bordo (occlusori) votano appena — e una finestra ancora il trasporto solo dove la sua massa pesata supera sia una soglia assoluta (0.05) sia un quarto della sua massa valida non pesata, così che le finestre occluse rimandino al riempimento piuttosto che adattarsi all’occlusore. Poiché l’adattamento è sui valori, l’intercetta porta la media locale del colore : la predizione eredita il livello dei dati validi circostanti, non solo la sua texture, così che dove la guida continua a salire nell’alta luce la stima la segue — sopra il valore di taglio. In pratica l’adattamento non viene mai eseguito per pixel : i momenti sono piani di prodotto sfocati con gaussiana, centrati sulle medie valide per regione contro la cancellazione float.

Filtra gli ancoraggi ($R^2 > 0.25$, pendenze limitate, massa affidabile), poi diffondi ogni piano di coefficienti con passate di Jacobi ancorate sulla griglia grossolana — l’aggiornamento di una passata su una cella non-ancoraggio $i$, con gli otto pesi di Weickert $w_{ik}$ del tensore $D$ :

$$ a_i \;\leftarrow\; \frac{\sum_{k \in \mathcal{N}_8(i)} w_{ik}\, a_k}{\sum_{k \in \mathcal{N}_8(i)} w_{ik}}, $$

ancoraggi tenuti fissi, 100 passate per livello della piramide, il livello più grossolano seminato con la media degli ancoraggi e ogni livello più fine seminato dal sovracampionamento bilineare della soluzione più grossolana ; stesso aggiornamento per $b$, $d$ e $R^2$. Poi valuta il modello trasportato rispetto alle guide misurate, in ogni pixel dove il target è tagliato ed entrambe le guide sono valide :

$$ u_c(x) \;\leftarrow\; a(x)\, u_{g_1}(x) + b(x)\, u_{g_2}(x) + d(x). $$

I pixel con un’unica guida valida prendono lo stesso aggiornamento dall’adattamento a una guida $u_c \leftarrow a\, u_g + d$. La valutazione del canale più profondo è rimandata finché gli altri canali tagliati non sono ricostruiti, così che le sue guide siano superfici continue.

2. Ricostruisci le alte frequenze (passaggio 4). Dividi $u_{g}$ in bassa frequenza $\bar u_g$ (gaussiana a $\sigma/4$, con soglia inferiore a 2 px — i momenti usano il $\sigma$ dell’adattamento, la divisione di banda no) e dettaglio $u_g - \bar u_g$ ; forma i due candidati — il trasferimento di guida $h_g = a\,(u_{g_1} - \bar u_{g_1}) + b\,(u_{g_2} - \bar u_{g_2})$ e l’auto-dettaglio smorzato $h_d = R^2 (u_c - \bar u_c)$ — e fondili con quote di minima energia quadratica sulle loro energie locali (sfocate con gaussiana) $e_g, e_d$, esattamente come enuncia il passaggio 4 (un interruttore netto reintrodurrebbe una giuntura di passaggio di consegna) :

$$ u_c \;\leftarrow\; \bar u_c + w\, h_g + (1 - w)\, h_d, \qquad w = \frac{e_d^2}{e_d^2 + e_g^2}. $$

3. Soglia di saturazione morbida (passaggio 5), ovunque un canale sia ricostruibile, con $w = 0.02\, c_{0,c}$ :

$$ u_c \;\leftarrow\; c_{0,c} + \tfrac{1}{2}\left( (u_c - c_{0,c}) + \sqrt{(u_c - c_{0,c})^2 + w^2}\right). $$

4. Auto-cupola con filtro di profondità (passaggio 6). Risolvi un’unica continuazione biarmonica condivisa della luminanza sul buco, $\Delta^2 L_b = 0$ con $L_b$ ancorato sul bordo (Cholesky sparsa diretta sul bilaplaciano discreto a 13 punti, griglia grossolana — lo stesso solver che la sezione di implementazione documenta), separa la sua crominanza dal bordo ($\bar r_c$, riempimento armonico), e fondi per la frazione di cupola $f$ :

$$ u_c \;\leftarrow\; (1 - f)\, u_c + f\, L_b\, \bar r_c, \qquad f = \big(1 - S_{0.4}^{0.85}(R^2)\big)\, e^{-(\delta / 1.5\sigma)^2}, $$

con $\delta$ la profondità del pixel dentro la zona tagliata (la sua distanza dal pixel valido più vicino — l’unico significato che $\delta$ porta in questo articolo).

5. Nucleo interamente tagliato (passaggi 7–8), sui pixel dove nessun canale è sopravvissuto. Magnitudine : la cupola biarmonica $\Delta^2 L = 0$ ancorata sull’anello (già ricostruito). Crominanza : prima il riempimento del bordo di Poisson schermato per canale, $(\Delta - \lambda)\, r_c = -\lambda\, \bar c_c$ con $\bar c_c$ la cromaticità valida media della regione con padding e $\lambda$ la reazione di colore piatto (soluzione sparsa diretta, fattorizzazione condivisa) ; poi la passata orientata dalla struttura. La sua forma primaria è la soluzione esatta in forma-divergenza del passaggio 8 (laplaciano di grafo a 8 vicini pesato sui bordi, fattorizzazione sparsa diretta) ; i nuclei sopra $2^{14}$ incognite prendono invece la piramide esplicita in forma-traccia — passi sotto l’ostacolo, iterati 240 volte per livello :

$$ r_c \;\leftarrow\; \max\!\Big( r_c + 0.18 \,\big( D_{xx} \partial_{xx} r_c + 2 D_{xy} \partial_{xy} r_c + D_{yy} \partial_{yy} r_c \big),\; \frac{c_{0,c}}{L_\text{sum}} \Big), $$

seguiti da una rifinitura proiettata di 60 passate a piena risoluzione (con filtro di attività : gira solo dove qualche pixel sta effettivamente sulla sua soglia). Riassemblaggio, per pixel del nucleo interamente tagliato — gli unici pixel che questa passata scrive ; i pixel parzialmente tagliati sono stati risolti dalle regole 1–4 e agiscono da suoi ancoraggi :

$$ u_c \;\leftarrow\; L_\text{sum}\, \frac{r_c}{\sum_j r_j}, $$

con soglia morbida come nella regola 3. (Il codice porta anche un ramo di trasferimento della magnitudine per pixel parzialmente validi, un residuo dell’era della scala che la costruzione degli ancoraggi rende irraggiungibile — documentato qui così che nessuno lo trascriva.)

6. Grana e composito. Aggiungi la grana poissoniana della sottosezione successiva dove richiesto, poi riscrivi attraverso l’interruttore netto : i fotositi ricostruibili prendono $\max(\text{raw}, u_c)$, i fotositi validi mantengono intatta la loro misura.

Tutto il resto nella pipeline — la stima del ginocchio, la segmentazione, il padding — prepara gli input per questi sei aggiornamenti ; nient’altro scrive un pixel.

Discussione

Gli operatori che l’algoritmo compone non sono arbitrari : ciascuno è lo stimatore che il suo termine di energia prescrive, e ciascuno porta proprietà misurabili su cui il progetto si appoggia. Questa sottosezione li deriva, e si chiude con lo studio dei parametri che dimensiona l’intero metodo — quanto lontano una ricostruzione dovrebbe arrivare.

Fidarsi della guida

Il prestito dalla linea di colore è buono solo quanto l’assunzione dietro di esso : che il canale tagliato e la sua guida siano, localmente, affinemente correlati. Su una superficie naturale lo sono ; su contenuto patologico (gradienti per canale indipendenti, o un confine tra due materiali di colore diverso) non lo sono, e forzare il prestito dipingerebbe una struttura che non appartiene. L’adattamento stesso riporta quanto fidarsene : $R^2$ (definito con l’adattamento, in le regole di aggiornamento) è la quota della varianza locale del canale che la linea di colore spiega — $1$ dove il modello regge, $0$ dove il prestito è privo di senso.

Quindi il metodo non commuta mai tra riempimento inter-canale e a singolo canale — li fonde per confidenza, ripiegando sull’estendere verso l’interno il proprio gradiente del canale tagliato (inpainting biarmonico per canale, ancorato sul vero bordo valido di quel canale) man mano che la linea di colore si indebolisce. Il peso di fusione è la confidenza al quadrato $W_e = (R^2)^2$, e l’esponente è una scelta misurata, non di gusto. Sui pixel tagliati, $R^2$ separa i due regimi solo con un margine morbido : mediamente $\approx 0.9$ su contenuto simile al naturale, $\approx 0.65$ su contenuto decorrelato, e le due distribuzioni si sovrappongono del $\sim15\%$ — e nessuna statistica più economica fa meglio (la stabilità della pendenza tra scale e l’accordo tra guide sono state entrambe misurate separare peggio, quindi $R^2$ è il tetto onesto). Usato linearmente, il caso decorrelato prenderebbe comunque $\sim65\%$ di una stima inter-canale che è sia sbagliata sia — poiché la selezione netta della guida si ribalta lì — discontinua. L’elevamento al quadrato separa i regimi : $0.9^2 \approx 0.81$ muove appena il caso affidabile, mentre $0.65^2 \approx 0.42$ dimezza quello inaffidabile, così che i pixel decorrelati si appoggiano al riempimento liscio del proprio gradiente. Poiché il margine è morbido, però, una giuntura debole sopravvive ancora sul contenuto più avverso : l’elevamento al quadrato non può nascondere del tutto una discontinuità nella quantità che pesa, e quel residuo è ciò per cui esiste la regola di progetto del mai-creare-giunture (vedi il cimitero).

Dove nessun canale sopravvive non c’è guida né $R^2$ da pesare, quindi la ricostruzione passa al nucleo congiunto della sezione successiva. La fusione è quindi filtrata dalla validità della guida e pesata dalla correlazione al quadrato, non da un conteggio netto di canali tagliati : un unico meccanismo copre l’intero intervallo da un taglio a un canale (per lo più inter-canale) attraverso un taglio a due canali (il canale sopravvissuto guida) fino a un nucleo interamente bruciato (la cupola congiunta). Questo è ciò che fa degradare con grazia il metodo man mano che la correlazione inter-canale cala : sulle immagini di test simili al naturale il termine inter-canale domina, sui gradienti casuali avversi l’auto-estensione subentra, e la ricostruzione non fabbrica mai una linea di colore che non c’è. È la correlazione, non il numero di sopravvissuti, a decidere se una guida può essere ritenuta affidabile — un pixel con due sopravvissuti su contenuto decorrelato non è meglio guidato di un pixel con un sopravvissuto su una linea di colore pulita.

Riempire buchi senza alcun sopravvissuto

Dove ogni canale taglia, nessuna linea di colore può essere adattata: il dettaglio fine dell’alta luce e il suo esatto picco sono genuinamente persi. Ma due cose possono ancora essere salvate dai dintorni, purché siano ricostruite congiuntamente : la curvatura della magnitudine a bassa frequenza del buco e la sua crominanza.

La mossa ingenua è mettere a cupola ogni canale indipendentemente: eseguire l’auto-inpainting biarmonico per canale del fallback di confidenza, ora non pesato, su tutti e tre i canali. Fallisce in modo istruttivo. Poiché i canali tagliano a raggi diversi (ciascuno ha il proprio livello di bianco, e un’alta luce quasi neutra li attraversa a turno), ogni canale è messo a cupola da un bordo a una diversa distanza dal centro, e le tre cupole raggiungono altezze diverse. L’ultimo canale a tagliare si satura piatto proprio al suo bordo, quindi porta quasi nessuna pendenza verso l’interno e la sua cupola sale appena, mentre il primo canale a tagliare è messo a cupola su un raggio ampio e sale ripidamente. Il nucleo deriva quindi fuori tinta: un sole neutro bruciato su un cielo arancione ricostruisce come un disco giallo, perché il blu (l’ultimo a tagliare qui) collassa mentre il rosso sovra-recupera. Tre cupole 1-D dall’aspetto corretto, un colore sbagliato.

La correzione è separare ciò che è condiviso da ciò che non lo è. La magnitudine è comune a tutti e tre i canali (sono tutti luminosi perché la stessa luce li ha traboccati), quindi è ricostruita una sola volta, come un’unica cupola. La crominanza varia dolcemente ed è trasportata verso l’interno dal bordo. Concretamente, separa la luminanza sommata $L_\text{sum} = R+G+B$ dalla crominanza $\text{RGB}/L_\text{sum}$, ricostruisci ciascuna con il suo operatore proprio, e ricombina $\text{core}_c = L_{\text{dome}}\cdot (\text{RGB}/L)_c$.

La cupola di magnitudine

La luminanza $L_\text{sum}$ è messa a cupola sul nucleo interamente tagliato dall’inpainting biarmonico introdotto nei primi principi : la soluzione $\Delta^2 L = 0$, ancorata al vero bordo valido del nucleo (l’anello a taglio parziale già ricostruito che lo circonda). L’operatore stesso è un revival : un progetto del 2021 aveva provato l’estensione del gradiente come un laplaciano a segno invertito (il trucco di diffuse or sharpen), ma iterare una diffusione inversa è instabile ed è stato abbandonato come incline agli artefatti ; la ricostruzione riporta l’idea nella sua forma stabile e diretta, risolta una sola volta sulla luminanza sommata invece che iterata per canale. Riempire la magnitudine per semplice diffusione la appiattirebbe in un disco opaco; la soluzione biarmonica invece continua la pendenza del bordo, così che un grande nucleo interamente tagliato si sollevi in una cupola invece di collassare a un plateau. Poiché tutti e tre i canali cavalcano questa unica cupola, nessuno può collassare rispetto agli altri — il guasto sopra è strutturalmente impossibile. Non può raggiungere il picco esatto (la parte più ripida del gradiente è stata tagliata via, e la vera vetta è semplicemente non osservata), ma recupera all’incirca il $75$–$80\%$ della salita tagliata, come una cupola liscia della larghezza corretta. Risolvere un singolo campo scalare invece di tre rende inoltre il sistema biarmonico un terzo più piccolo.

Una soglia di saturazione

L’adattamento guidato e la cupola biarmonica sono entrambi estrapolazioni, e un’estrapolazione può sottostimare: un canale ricostruito da un unico sopravvissuto basso, o una cupola che continua un bordo la cui pendenza verso l’interno è stata appiattita dal taglio, può risultare sotto il livello che il canale ha effettivamente raggiunto. Questo è non fisico : come il problema ha stabilito, il pozzo del fotosito si riempie a una capacità fissa, quindi un canale tagliato è noto stare almeno al suo livello di taglio. Due soglie impongono esattamente questo, ed entrambe sono monotone : alzano soltanto un valore, quindi non possono superare né spostare una tinta che era già corretta (una nota di attribuzione : le soglie, come il laplaciano a 9 punti, erano già nell’implementazione C del 2021 — il contributo dello studio è solo di aver misurato il loro valore, e le soglie da sole sono un miglioramento stretto e uniforme su ogni scena) :

  • ogni canale tagliato ha una soglia inferiore alla propria lettura saturata;
  • la cupola di luminanza del nucleo interamente tagliato ha una soglia inferiore alla somma dei livelli di taglio : ogni canale lì è al massimo, quindi il nucleo è la regione più luminosa, mai un avvallamento sotto il suo bordo.

Economiche e fisicamente inconfutabili, le soglie rimuovono il residuo cast magenta-e-scuro che la cupola e la diffusione da sole possono lasciare in un grande nucleo interamente tagliato (un disco solare bruciato), e giustificano il miglioramento sul caso correlato interamente tagliato riportato qui sotto.

Crominanza, per diffusione

La crominanza $r = \text{RGB}/L_\text{sum}$ è diffusa verso l’interno dal bordo del nucleo : inpainting armonico della crominanza, $\Delta r = 0$ con $r$ fissato sul bordo. Fondamentalmente il bordo qui è l’anello ricostruito immediatamente attorno al nucleo, non il cielo distante: poiché i canali sopravvissuti erano guidati verso il neutro avvicinandosi all’alta luce, l’anello è già quasi neutro, quindi il nucleo diffuso risulta anch’esso quasi neutro — il sole bruciato è ridipinto bianco, non arancione come il cielo. (La precedente implementazione à-trous tirava invece la sua crominanza da troppo lontano e ingialliva il nucleo; sia il riferimento sia l’attuale ricostruzione segmentata diffondono dall’anello vicino, come qui.) Il ricombinato $\text{core}_c = L_{\text{dome}}\cdot r_c$ è quindi una cupola correttamente colorata.

La precedente implementazione à-trous realizzava questa soluzione $\Delta r = 0$ non come un unico sistema lineare ma come una rilassazione iterata: decomponendo i rapporti in bande wavelet, ogni banda di dettaglio prende un passo di Eulero esplicito di

$$ \begin{align} r &\leftarrow r + \kappa \, \big( \Delta r - \lambda \, r \big)\\ \kappa &= \frac{1}{\texttt{B_SPLINE_TO_LAPLACIAN}} = \frac{\sigma_B^2}{2\sqrt{\pi}} \approx 0.31 \end{align} $$

con $\Delta$ il laplaciano isotropo a 9 punti $\mathbf{K}_{\text{iso}}$. La costante $\kappa$ riscala la banda di dettaglio à-trous (una differenza di B-spline) in un laplaciano continuo correttamente scalato e imposta il passo temporale di diffusione effettivo.7 La ricostruzione ricostruita risolve $\Delta r = 0$ direttamente invece : una soluzione sparsa nel prototipo, gradiente coniugato (un solver iterativo che raffina la sua risposta passo dopo passo) nel C. Ma la visione della rilassazione espone ancora due cose:

  • Con $\lambda = 0$ è la pura equazione del calore $\partial r / \partial t = \kappa \Delta r$ sui rapporti di colore : inpainting armonico che riempie la crominanza del buco dolcemente dal suo bordo, il minimizzatore di Dirichlet derivato sopra (equivalente alla soluzione diretta $\Delta r = 0$, iterata).
  • Il termine $-\lambda\, r$, controllato dal parametro solid_color, è una reazione del primo ordine che smorza il dettaglio del rapporto verso zero. La sua equazione di Eulero–Lagrange è l’equazione di Poisson schermata / di Helmholtz modificata $\Delta r - \lambda\, r = 0$ : una diffusione trattenuta da un richiamo di ripristino verso zero; un $\lambda$ maggiore tira la crominanza interna verso un riempimento “solid color” più piatto e uniforme.

La diffusione è puramente isotropa: la texture dovrebbe seguire la struttura, ma la crominanza dovrebbe riempire senza riguardo per la direzione, quindi ciò che si vuole è un levigatore a simmetria rotazionale. L’intero riempimento congiunto è filtrato ai pixel interamente tagliati, così che non disturbi mai un canale che il passaggio guidato ha già recuperato.

Le soglie di saturazione, come ostacoli. Una misura sopravvive anche dove tutti e tre i canali sono bruciati : la lettura saturata di ogni fotosito è un limite inferiore sul suo vero valore, quindi nello spazio dei rapporti la crominanza di un canale tagliato non può mai scendere sotto $c_{0,c} / L_\text{sum}$. Applicato solo alla fine — un duro $\max$ alla ricombinazione — quel limite stampava una mensola esattamente piatta al livello di taglio, terminante in un salto di gradiente, ovunque il riempimento liscio sottostimasse un canale vicino al proprio bordo (misurato sul canale blu di un tramonto reale : dieci pixel piatti al valore di taglio, poi una discontinuità di pendenza). La ricostruzione distribuita tratta invece le soglie come un vincolo della diffusione stessa — un problema con ostacolo :

$$ \min_{r_c} \int_{\Omega} \nabla r_c^{\top} D\, \nabla r_c \;\mathrm{d}\Omega \qquad \text{subject to} \qquad r_c \;\geq\; \frac{c_{0,c}}{L_\text{sum}} \quad \text{on the clipped pixels,} $$

risolto per rilassazione proiettata : dopo ogni passo di levigatura, $r_c \leftarrow \max(r_c, c_{0,c}/L_\text{sum})$. Ogni peso dei vicini della discretizzazione è non negativo, quindi lo schema proiettato resta monotono e converge alla soluzione della disuguaglianza variazionale : l’influenza del vincolo si diffonde dolcemente attraverso il campo — il riempimento si solleva attorno all’insieme attivo invece di essere fissato puntualmente — e la ricombinazione poi applica solo un arrotondamento morbido (lo stesso smooth-max al due percento della soglia del campo dei coefficienti) a un campo che è già ammissibile. L’ostacolo cavalca l’intera scala da grossolano a fine (ostacoli per cella per ogni livello) più una breve rifinitura proiettata a piena risoluzione, che copre anche il percorso a soluzione diretta — una fattorizzazione sparsa esatta non può proiettare a metà soluzione. Una variante più forte, che alza l’ostacolo della pendenza esterna misurata del bordo estrapolata verso l’interno, è stata implementata, misurata e respinta : essa fabbrica struttura dove l’estrapolazione vincola (l’errore del sole magenta è salito del 14 %) e non cambia nulla dove il semplice ostacolo già regge.

image

Il disperato caso interamente tagliato: un sole luminoso bruciato oltre il punto di bianco (per canale) di ogni canale, su un cielo arancione. Il sensore registra un disco piatto che i coefficienti RGB del bilanciamento del bianco renderanno magenta ; il metodo precedente restituisce un disco giallo fioco e sovradimensionato con un bordo netto (i suoi riempimenti per canale derivano fuori tinta e non mettono mai a cupola); il metodo corretto ricostruisce una cupola neutra della dimensione giusta da un’unica cupola di luminanza condivisa più crominanza diffusa dal bordo.

Sul nucleo tagliato, RMSE $0.87 \to 0.27$ e SSIM $0.61 \to 0.95$; la crominanza del riempimento si sposta dal magenta $(0.37, 0.27, 0.36)$ a un quasi-neutro $(0.39, 0.33, 0.28)$, essenzialmente il ground truth $(0.36, 0.33, 0.31)$ (il metodo precedente atterra su un giallo $(0.51, 0.34, 0.15)$).

Aggiungere la grana

Come tocco finale, e solo dove la maschera è aperta, la ricostruzione riceve rumore poissoniano di ampiezza proporzionale al valore locale ($\sigma_c = \texttt{noise_level}\cdot u_c$), ripiegato in modo da essere strettamente schiarente e composto tramite $\alpha$. Il rumore di conteggio dei fotoni dipende dal segnale, per cui una zona perfettamente liscia inserita in un fotogramma ad alto ISO appare plastica; far combaciare la grana fa sparire la riparazione.2

Il raggio di ricostruzione

Quanto ampia dovrebbe essere la finestra guidata più grossolana, il raggio di ricostruzione $\rho$? È l’unica manopola che scambia portata per località, e si scopre che ha una risposta pulita.

Il raggio di un buco è la sua profondità, non la sua dimensione. Per un buco $\Omega$, sia $d(x)$ la distanza da un pixel tagliato $x$ al più vicino pixel valido. La quantità che conta è il punto più profondo,

$$R=\max_{x\in\Omega} d(x)=\max\big(\text{distance transform of }\Omega\big)$$

non il riquadro di delimitazione. Un disco compatto e una lunga sottile striscia diagonale possono condividere un riquadro di delimitazione pur avendo $R$ completamente diversi: per la striscia, $R$ è il suo semi-spessore, perché è la distanza che la guida deve raggiungere per toccare dati validi. Dimensionare il raggio a partire dal riquadro di delimitazione porterebbe a un’enorme eccedenza di portata sui buchi allungati o diagonali.

Perché $\rho \approx R$ è ottimale. Due errori competono al crescere di $\rho$. Copertura: una finestra di scala $\rho$ pone un peso $\sim e^{-d(x)^2/2\rho^2}$ sui dati validi, che è trascurabile finché $\rho \gtrsim d(x)$; al di sotto di ciò il fit è indeterminato e il pixel è di fatto non ricostruito. Località: una volta coperta, la linea di colore fittata è una media su finestra di quella vera che varia spazialmente, per cui estrapolare da campioni sempre più distanti aggiunge un bias che cresce con $\rho$. L’errore per pixel è dunque

$$E(x;\rho)\approx \underbrace{C_1\,e^{-d(x)^2/2\rho^2}}_{\text{coverage}\ \downarrow}+\underbrace{C_2\,\rho}_{\text{locality}\ \uparrow}$$

il cui minimo si colloca a $\rho^*(x)\approx d(x)$: la finestra più piccola che raggiunge dati validi. Un singolo raggio di regione deve coprire il suo pixel più profondo, quindi $\rho=R$; la scala dal grossolano al fine è allora esattamente questo optimum per pixel reso continuo: la scala più grossolana $R$ serve il centro, ogni scala più fine serve il guscio alla propria profondità.

Eccedere in portata è gratis in qualità, ma non in prezzo. Nel prototipo, spazzare $\rho$ da $0.4R$ a $3R$ lascia piatti sia l’RMSE medio sia quello del nucleo profondo, sia su scene correlate sia su scene decorrelate allo stesso modo. La ragione è strutturale: la scala dal grossolano al fine sovrascrive ogni valore grossolano (troppo levigato) con la scala più fine che raggiunge ancora il pixel, per cui il raggio fissa solo la copertura, mai il valore finale di un pixel. Il calcolo, invece, non è piatto (cresce sia con l’area con margine sia con il numero di scale), quindi il rapporto qualità/costo cala monotonamente con $\rho$:

$\rho/R$RMSE medioRMSE nucleo profondocosto relativoqualità / prezzo
0.40.0830.1410.431.00
1.00.0830.1411.000.43
2.00.0830.1412.390.18
3.00.0830.1414.430.10

Il punto ideale è per pixel, e l’implementazione lo realizza. Poiché la qualità è piatta sopra la profondità e il costo non lo è, il miglior rapporto qualità/prezzo è la finestra più piccola che copre ancora ogni pixel, cioè $\rho(x)=d(x)$ per pixel, mai un massimo globale. La scala C realizzava questo direttamente: un tassello unito prendeva il raggio più grande del gruppo $R$ come sua scala più grossolana (così il pixel più profondo era coperto), e ogni pixel veniva poi limitato a scale non più grossolane di $\sim2\,d(x)$, così un pixel poco profondo (un piccolo buco, o il bordo di uno grande) non pagava mai per le scale grossolane di cui ha bisogno solo il centro. La distance transform è calcolata una sola volta per l’intero fotogramma e guida il raggio per regione. L’unica regola ferma è l’asimmetria: mai andare sotto la profondità (la copertura collassa), e mai molto sopra di essa (puro calcolo sprecato). Il metodo distribuito mantiene la conclusione in una forma più semplice: una finestra di fit dimensionata sul raggio della regione, $\sigma = \operatorname{clip}(r/6,\, 8,\, 64)$ px, senza alcuna limitazione per pixel da fare.

Implementazione e ottimizzazioni

Esistono due implementazioni: il riferimento NumPy pubblicato accanto a questo articolo, e il codice di produzione C / OpenCL distribuito in Ansel. Vengono confrontati con diff l’uno contro l’altro come parte del protocollo di validazione, e divergono solo dove la velocità lo esige.

Il codice di produzione C

Il prototipo che accompagna questo articolo è il riferimento: privilegia la chiarezza e l’accuratezza sulla velocità (risoluzioni sparse-dirette sull’intera immagine, una scala gaussiana fissa, un passaggio di varianza separato, un fit di linea di colore per ciascuna coppia (canale, guida)). Il codice che effettivamente viene distribuito in Ansel deve ricostruire un sole tagliato dentro un raw da oltre 20 Mpx mentre l’utente aspetta, quindi diverge dal riferimento in una manciata di modi deliberati. Ogni compromesso qui sotto è ottimizzato per contenuti correlati, il caso di gran lunga più comune nelle immagini naturali, e accetta una piccola perdita su contenuti sintetici decorrelati, che sono rari nella pratica. Ogni deviazione è reversibile nel sorgente. Una nota sulla storia: questo lavoro di ottimizzazione è stato fatto sul C dell’era della scala, il cui nucleo di ricostruzione il metodo distribuito sostituisce. La segmentazione, il margine, il solver sparso e la macchineria delle statistiche condivise passano verbatim; gli elementi specifici della scala (la scala per buco, la limitazione di profondità per pixel, la scelta di guida ristretta alla coppia) sopravvivono solo come principi, dentro la singola finestra di fit e il riempimento dei coefficienti del metodo distribuito.

  • Segmentare e ricostruire solo i dintorni tagliati, a piena risoluzione. Il riferimento gira sull’intero fotogramma. Il codice distribuito riempie a inondazione (flood-fill) la maschera tagliata in regioni connesse, aggiunge a ciascuna un margine pari al proprio raggio di ricostruzione ($1.25\times$ il raggio, limitato tra $8$ e $256$ px — più grande è il buco, più lontano deve raggiungere per dati validi), unisce le regioni i cui riquadri con margine si sovrappongono così che i loro riempimenti non si incontrino mai come una giuntura, e esegue l’intera pipeline solo dentro ciascun riquadro con margine. Costo: nessuno — i pixel intatti erano già corretti. Vantaggio: il lavoro scala con l’area tagliata, non con il sensore.

  • Una Cholesky sparsa autonoma ha chiuso il divario del solver. Il riferimento riempie la cupola di magnitudine con una risoluzione diretta sparsa (scipy.spsolve); un solver iterativo (gradiente coniugato su $\Delta^2$) si blocca in singola precisione alla scala raw (il suo numero di condizionamento, il fattore per cui il sistema amplifica gli errori di arrotondamento, cresce come la quarta potenza della dimensione della regione), e una fattorizzazione densa è $O(N^3)$, quindi il C dapprima distribuiva una Cholesky densa (la fattorizzazione esatta standard di un sistema simmetrico in metà triangolari, calcolata una volta e riutilizzata) su una griglia rifinita a grana grossa fino ad al massimo 2000 incognite. Essendo la cupola a bassa frequenza, la risoluzione grossolana era esatta dove contava — tranne su enormi nuclei completamente bruciati, dove la rifinitura a grana grossa stessa diventava il collo di bottiglia dell’accuratezza (il caso di test del sole magenta migliorò del 21 % in RMSE il giorno in cui il limite fu rimosso). Il C ora porta la propria Cholesky sparsa simmetrica-definita-positiva (SPD), una fattorizzazione $LL^T$ a sguardo verso l’alto (up-looking) in doppia precisione, ordinata per dissezione annidata geometrica: le incognite sono punti di griglia 2D, quindi la bisezione ricorsiva di coordinate offre la qualità di riduzione del fill-in che normalmente richiede un’implementazione di grado minimo approssimato (AMD), in poche dozzine di righe e senza alcuna dipendenza esterna. La griglia della cupola è quattro volte più fine (8 192 incognite, che saturano in modo misurabile il guadagno di qualità), e la diffusione di crominanza del nucleo interamente tagliato fattorizza una sola volta per i suoi tre membri destri di canale quando il nucleo è piccolo; il gradiente coniugato parallelo batte in modo misurabile la fattorizzazione seriale su quelli grandi, quindi li conserva. Costo: nessuno netto (le risoluzioni interamente tagliate sono diventate più veloci). Vantaggio: l’ultima divergenza sistematica C-vs-prototipo sparita, e con essa l’accuratezza sui nuclei enormi.

  • Una scala gaussiana per buco dimensionata sul raggio di ricostruzione. Il riferimento usa una scala a raggio fisso ($40, 24, \dots, 2$ px). La scala C ottimizzata dimensionava la scala più grossolana di ciascuna regione sul raggio di ricostruzione di quella regione e dimezzava di $2\times$ fino a $\sim2$ px. Questo merita una trattazione a sé: vedi Il raggio di ricostruzione qui sotto.

  • Statistiche su finestra condivise: un’accelerazione gratuita. Il fit della linea di colore necessita, per coppia di canali, delle medie, varianze e covarianza su finestra. Raccoglierle per (canale, guida) costa dodici sfocature per scala; raccoglierle per coppia di canali produce i fit di entrambi i canali dalle stesse due sfocature: sei sfocature, e matematicamente identiche. Questo da solo ha grosso modo dimezzato il tempo di esecuzione.

  • Varianza ristretta alla coppia per la selezione della guida: un costo piccolo e contenuto. Il riferimento decide quale delle due guide fidarsi dalla varianza su finestra a canale singolo di ciascuna guida, un passaggio separato a tre sfocature per scala. La scala C ottimizzata riusava la varianza ristretta alla coppia che già calcolava per il fit e ha eliminato del tutto quel passaggio (nove sfocature per scala giù a sei). Questo ha cambiato solo quale guida vince, e solo dove le due sono in disaccordo, cioè contenuto decorrelato. Costo: su una scena sintetica completamente decorrelata, l’RMSE a taglio singolo sale da $0.043$ a $0.047$ e l’SSIM cala da $0.964$ a $0.960$; su scene correlate, e su tagli a due o tre canali, è entro il rumore. Vantaggio: un terzo delle sfocature rimanenti rimosso.

  • La auto-cupola per canale e il regolarizzatore di giunture sono disabilitati nel codice distribuito. Entrambi levigano ciascun canale RGB indipendentemente. Su una vera alta luce ambra/arancione satura (dove l’$R^2$ della linea di colore si abbassa per la non-linearità del taglio ed è interpretato erroneamente come “decorrelato”) ciò porta il verde fuori dal rosso e dal blu e rende magenta la ricostruzione. Il riferimento li conserva: eliminano gli artefatti da contenuti genuinamente decorrelati, che le scene sintetiche esercitano pesantemente e le fotografie reali raramente fanno (disabilitarli costa al riferimento $\sim0.01$–$0.02$ RMSE sulla scena casuale, essenzialmente nulla su contenuto correlato). La soluzione pulita è una versione accoppiata alla tinta (che leviga la luminanza e una crominanza condivisa invece di tre canali indipendenti), ed è stata da allora costruita: nella pipeline a campo di coefficienti finale l’auto-cupola ritorna come fallback limitato dalla profondità, accoppiato alla tinta esattamente in questo modo (una singola cupola di luminanza biarmonica condivisa per una crominanza riempita armonicamente), così il fallback non può far derivare la tinta per costruzione. Il regolarizzatore di giunture è ritirato per sempre — quella pipeline non ha più passaggi di consegna tra stimatori da appianare.

Due componenti dell’implementazione C del 2021 che la traduzione del prototipo aveva silenziosamente abbandonato sono stati reintegrati nel riferimento, come genuini requisiti del metodo piuttosto che compromessi di velocità, quindi non sono più differenze: il laplaciano a 9 punti invariante per rotazione (l’operatore che questo articolo deriva; il riferimento usava il semplice croce a 5 punti prima567) e i pavimenti di saturazione (sotto). Entrambi migliorano il caso più difficile e rilevante (il nucleo interamente tagliato di una scena correlata), abbassandone l’RMSE da $0.065$ a $0.062$ e alzando l’SSIM da $0.959$ a $0.964$, e sono neutrali altrove.

Nel complesso, su un sole tagliato il conteggio di sfocature del filtro guidato scende da quindici a sei per scala e la ricostruzione dell’anteprima cala da decine di secondi a pochi. Le differenze che restano sono velocità e architettura (segmentazione, solver densi, il raggio per buco, statistiche condivise), più la varianza di guida ristretta alla coppia e la rifinitura per canale disabilitata sopra; la matematica di ricostruzione centrale è condivisa con il riferimento.

Un ulteriore compromesso misurato si è chiuso presto: la ricostruzione è nettamente migliore eseguita a piena risoluzione. Il modulo del 2021 risolveva su una copia a quarto di risoluzione per velocità, ma quel sottocampionamento sfoca la ricostruzione e, fusa di nuovo contro l’originale nitido, è la fonte dominante dell’errore di bordo: eseguire a piena risoluzione ha abbassato l’RMSE di confine di circa il 30%, quindi la ricostruzione lavora a piena risoluzione. Il sottocampionamento è un compromesso velocità/qualità, non un pasto gratis.

Il riferimento Python

Lo specchio NumPy (validate.py, fix_prototype.py) implementa ogni fase dell’algoritmo distribuito nello stesso ordine, e confrontare con diff il suo output contro le esportazioni C fa parte del protocollo di validazione. Alcuni componenti sono deliberatamente migliori in Python che in C, oppure il C deliberatamente scambia esattezza per velocità:

  • Risoluzioni sparse dirette esatte. Le cupole biarmoniche del prototipo e la sua risoluzione di crominanza in forma di divergenza sono fattorizzazioni scipy.sparse, esatte alla precisione macchina (il suo riempimento joint-core di Poisson schermato, tuttavia, è un rilassamento di Jacobi fisso a 400 spazzate — su quella singola fase la fattorizzazione diretta del C è la più esatta delle due). Il C ora porta la propria Cholesky sparsa (vedi Implementazione e ottimizzazioni sopra) e combacia con le risoluzioni della cupola e di crominanza a piccolo nucleo del prototipo; ciò che ancora differisce è deliberato: la diffusione di crominanza dei grandi nuclei interamente tagliati esegue il gradiente coniugato parallelo (misurato più veloce di una fattorizzazione seriale lì, senza alcuna differenza di qualità misurabile), il riempimento dei coefficienti rimane il Jacobi dal grossolano al fine descritto sopra — con la piramide adattiva del C (passo di base $\sigma/4$ limitato a 8 px, dimezzato fino a un più grossolano $\le 8$ celle, 100 spazzate per livello) contro i sette livelli fissi a piena risoluzione del prototipo — e il riempimento del prototipo è isotropo — la guida adattiva alla varianza del trasporto dei coefficienti del C (passo 3) non ha ancora uno specchio Python, quindi l’accordo C-vs-prototipo è verificato con il contributo della guida tenuto a mente. Beneficio delle risoluzioni esatte del prototipo: sono l’arbitro quando l’output C appare sbagliato.
  • Finestre gaussiane FFT vs. IIR ricorsivo. Il prototipo calcola i momenti su finestra con convoluzioni a trasformata di Fourier veloce (FFT): esatte, deterministiche. Il C usa la gaussiana ricorsiva (a risposta impulsiva infinita, IIR) il cui costo è indipendente da $\sigma$ — ma la cui implementazione parallela non è deterministica tra un’esecuzione e l’altra: l’arrotondamento ai confini dei blocchi oscilla all’ultima cifra del float, cosa innocua di per sé ma che ha innescato un memorabile heisenbug (sotto).
  • Input della stima del ginocchio. Il prototipo stima il rolloff dalla scena RGB vera a piena risoluzione; il C dalla CFA aggregata a quattro (quad-binned) a $\le 1.5$ Mpx. Costo misurato: nessuno (la stima C cieca combacia ancora con il ginocchio analitico a $10^{-3}$), mentre l’aggregazione a quattro è ciò che rende possibile la stima lato CFA in primo luogo.
  • Fotogramma intero vs. per regione. Lo specchio elabora fotogrammi interi; il C raccoglie ciascuna regione tagliata in un buffer con margine e la ricostruisce indipendentemente, il che limita la memoria e lascia che le regioni si parallelizzino, al prezzo di una normalizzazione per regione.
  • Rifiniture distribuite che lo specchio non ha ancora raggiunto. Tre round C tardivi non hanno ancora una controparte Python: la macchineria degli ostacoli (rilassamento proiettato per livello, la lucidatura a piena risoluzione, e il pavimento morbido di riassemblaggio — lo specchio applica ancora il ritirato $\max(u, c_0)$ duro al riassemblaggio), l’override di banda della soglia di rilevamento per i canali con ginocchio ingaggiato (vale $5\times$ sulla scena di rolloff, quindi Py-vs-C è strutturalmente incomparabile lì), e la validità binaria strettamente per pixel del C (lo specchio deriva la sua maschera tramite un voto di maggioranza 5×5, una dilatazione geometrica di $\sim 2$ px). Questi sono i noti divari di contenuto dietro le più grandi derive per scena.

Dopo che ogni fase è stata rispecchiata, il disaccordo residuo tra il codice di produzione C e il riferimento Python (misurato come differenza quadratica media delle loro immagini di output dentro le zone tagliate) è $0.009$–$0.089$ lungo tutto il banco. Il suo pavimento è l’unica differenza di input che non può essere rimossa — Python fitta sulla scena RGB vera, mentre il C fitta sulla stessa scena vista attraverso il mosaico del sensore e un’interpolazione bilineare (circa $0.02$ di distanza dentro la banda quasi-tagliata) — e il suo soffitto è la lista di rifiniture distribuite sopra, che domina sulle scene di rolloff e occlusione. Lontano da quelle due scene, le implementazioni concordano ben entro l’errore che ciascuna commette contro il ground truth; su di esse, la macchineria extra del C è precisamente ciò che la deriva misura.

Parametri utente

Deliberatamente non c’è quasi nulla da impostare: l’algoritmo è auto-configurante. Ogni quantità da cui dipende la ricostruzione è o derivata una volta e congelata, o misurata dall' immagine stessa al momento dell’esecuzione — la scala di fitting dal raggio di ricostruzione di ciascuna regione, il rolloff del sensore dalle linee di colore dell’immagine stessa, la fiducia dell’ancora dalla qualità del fit misurata, la guida del trasporto dalla struttura della guida misurata, la soglia di bordo $k$ calibrata una volta sul banco di ground truth. I controlli iterations e diameter della modalità del 2021 sono spariti dall’interfaccia di questa modalità: non resta alcuna convergenza regolabile dall’utente da configurare male, e nessun modo di sacrificare qualità per sbaglio.

Ciò che resta è una sicurezza e due gusti. La clipping threshold scala le soglie di saturazione che definiscono la zona (vedi l’interruttore di visualizzazione qui sotto — quasi mai ha bisogno di essere spostata); il noise level imposta la grana reiniettata sulla ricostruzione così che combaci con la texture fotonica circostante; e inpaint a flat color imposta l’attrazione verso un colore uniforme nei nuclei interamente tagliati dove nulla è sopravvissuto — una scelta stilistica su zone che non portano alcuna informazione.

Problemi scoperti

La limitazione strutturale del metodo è ereditata e accettata: un cielo blu bruciato visto attraverso foglie verdi è recuperato verde, perché dove nessun canale sopravvive la ricostruzione può solo propagare il colore vicino — è stato progettato per i tagli inevitabili attorno alle sorgenti di luce e ai riflessi speculari, i cui aloni luminosi portano l’informazione di cui si nutre. Altre situazioni richiedono ancora di proteggere le alte luci in ripresa e alzare l’esposizione in post-produzione, cosa che il rapporto segnale-rumore dei sensori moderni consente comodamente.

Due avvertenze misurate completano il quadro onesto. Sulla scena di banco con occlusione, la segmentazione Darktable ottimizzata raggiunge un punteggio di similarità strutturale più alto della trasposizione armonica (0.98 contro 0.96) nonostante valori più lontani dal vero del 15 %: magnitudine e struttura sono errori diversi, e un riempimento sbagliato ma liscio può apparire più pulito di uno veritiero. E la parità di dispositivo della pipeline float è a livello di tolleranza, non a livello di bit: dopo aver centrato l' accumulo dei momenti (passo 3), la differenza residua CPU-contro-GPU sulla scena di banco più profonda è sotto $10^{-3}$ RMSE, limitata da riduzioni parallele non deterministiche nell’ordine.

Risultati

Il banco

Ogni numero qui sotto proviene da un unico protocollo. Sei scene sintetiche con ground truth noto — sfere speculari RGB, un sole magenta, gradienti correlati (un campo di luminanza per una crominanza liscia, come si comportano le immagini naturali), gradienti casuali indipendenti (nessuna linea di colore affatto, il caso avversariale), un cielo simile a PK1 con un rolloff analitico del sensore, e un sole attraversato da un occlusore — sono scritte come file Bayer DNG, sviluppate dalle effettive pipeline di produzione, e le esportazioni sono valutate contro il ground truth in RGB di sensore bilanciato al bianco (una matrice di calibrazione 3×3 completa fittata su pixel validi strettamente lineari riporta ogni esportazione nello spazio del sensore, così che pipeline con matrici diverse atterrino nelle stesse unità). Ogni risultato Ansel in questa sezione — entrambe le modalità, ogni tabella, ogni figura — è prodotto dall’implementazione C distribuita tramite ansel-cli, non dal prototipo di ricerca Python: le due implementazioni divergono deliberatamente (solver, risoluzioni e compromessi di fase diversi, vedi il codice di produzione C), da 0.009 a 0.089 RMSE dentro le zone tagliate lungo questo banco, quindi i loro numeri non devono mai essere mescolati. L’intero banco si riproduce dall’allegato.

Le metriche dell’algoritmo distribuito qui sotto sono calcolate solo dentro l’area tagliata. RMSE è l' errore quadratico medio contro il ground truth, in unità di sensore normalizzate così che il livello di taglio sia 1: misura quanto lontani sono in media i valori ricostruiti da quelli veri. SSIM è l’indice di similarità strutturale, un punteggio percettivo che confronta medie locali, contrasti e correlazioni (1 significa visivamente identico). L’energia di bordo è il nostro rilevatore di giunture: l' energia del gradiente di crominanza dentro la zona, relativa a un anello appena fuori di essa, e il suo bersaglio è il punteggio del ground truth stesso, non zero, perché il contenuto reale ha texture e una ricostruzione troppo levigata è sbagliata quanto una con giunture:

casoRMSESSIMenergia di bordo (bersaglio GT)
sfere RGB0.03510.9950.41 (0.47)
sole magenta0.32760.9520.31 (0.50)
correlata0.03980.9731.15 (1.14)
casuale0.04530.9650.85 (1.02)
pk1synth0.00531.0000.64 (0.69)
PK1 (naturale)0.49
DSC00078 (naturale)0.73
IMG_3129 (naturale)0.39

I quattro metodi

Le due modalità Darktable sono descritte con le altre soluzioni semplici nella sezione panoramica; letto attraverso il quadro di questo articolo, inpaint opposed è una linea di colore con la pendenza fissata a uno (in spazio a radice cubica) e una singola intercetta globale — dove la trasposizione armonica fitta sia pendenza sia intercetta per finestra locale e diffonde quei coefficienti lungo la zona, opposed si affida a una relazione fissa ovunque, ed è per questo che è quasi gratuita e che fallisce sotto illuminazione mista — e segmentation based eleva ciò a una crominanza rappresentativa per segmento, ancora piatta dove i nostri campi diffusi variano dolcemente.

Entrambi i metodi rispondono alla domanda “che colore dovrebbe essere quest’area”; nessuno dei due fitta un modello di come il canale mancante si relaziona a quelli sopravvissuti, che è ciò che permette al nostro metodo di recuperare struttura e magnitudine per pixel con una confidenza misurata. Le righe qui sotto mettono numeri su quella differenza, sulle stesse scene sintetiche e ground truth di ogni altra cosa in questa sezione.

Errore quadratico medio (più basso è meglio) e similarità strutturale (più alto è meglio), misurati dentro la zona tagliata contro il ground truth, dopo la calibrazione di esposizione per immagine; ogni metodo esportato come TIFF lineare a 32 bit (Darktable 5.x a monte per le prime due colonne, Ansel per le ultime due), valutato dallo stesso validate.py di ogni altra tabella in questa pagina. Due precauzioni rendono le colonne effettivamente confrontabili. Primo, le pipeline sono neutralizzate alla stessa base — demosaicizzazione più ricostruzione delle alte luci, nient’altro: il flusso di lavoro riferito alla scena predefinito di entrambe le applicazioni inserisce silenziosamente un’alzata di esposizione, un mixer di canali di adattamento cromatico e una curva tonale sigmoide attorno alla sua ricostruzione, che corromperebbero la misurazione con differenze di resa, quindi è forzato a workflow=none (l’esportazione di Ansel è già quella base). Secondo, ogni concorrente esegue i suoi parametri migliori per scena, trovati con una spazzata a griglia sui suoi controlli (soglia di clipping per opposed; candidating, combine, modalità e forza di rebuild per segmentation; iterazioni e inpainting a colore piatto per i guided laplacians), mentre la trasposizione armonica esegue i suoi valori predefiniti di serie ovunque. Riproduci con dt_compare.py (il confronto) e tune_methods.py (la spazzata) dal repository di ricerca:

scenaDarktable opposedDarktable segmentationguided laplacianstrasposizione armonica
balls (sfere speculari)0.242 / 0.8140.233 / 0.8310.334 / 0.7440.035 / 0.995
magentasun (sole magenta)0.924 / 0.7070.807 / 0.7670.809 / 0.6780.328 / 0.952
correlated (gradienti correlati)0.107 / 0.8720.077 / 0.9070.130 / 0.8500.040 / 0.973
random (gradienti indipendenti)0.089 / 0.9110.080 / 0.9200.138 / 0.8620.045 / 0.965
pk1synth (cielo simile a PK1 (rolloff analitico))0.130 / 0.9850.130 / 0.9850.108 / 0.9930.0053 / 1.000
occluded (sole occluso)0.099 / 0.9820.086 / 0.9800.171 / 0.9300.069 / 0.987

Il modello è coerente con i meccanismi. Anche con le pipeline equalizzate e ogni concorrente al suo migliore per scena, la trasposizione armonica guida in RMSE su ogni scena — del 20 % dove l’occlusione favorisce le assunzioni della modalità di segmentazione, di un fattore da 2 a 7 sui casi generali, di un fattore di quasi 50 sul cielo con rolloff (dove l’override di banda qui sotto lascia che il modello ricostruisca la pendenza che il sensore ha compresso via): le modalità Darktable recuperano un colore plausibile ma non la magnitudine (la loro pendenza unitaria fissa non può alzare un canale sopra ciò che la media opposed fornisce), e la loro crominanza per segmento o globale appiattisce la texture. La modalità di segmentazione avanza davvero rispetto al semplice opposed una volta che il suo passaggio di rebuild è effettivamente abilitato (la spazzata ha trovato rebuild + forza piena ottimale su metà delle scene). La controparte visiva di ogni numero, una scena per galleria, tutte e sei le rese attraverso la stessa trasformata di display:

balls — sfere speculari:

magentasun — sole magenta:

correlated — gradienti correlati:

random — gradienti indipendenti:

pk1synth — cielo simile a PK1 (rolloff analitico):

occluded — sole occluso:

La spazzata completa del banco di validazione (input tagliato, ricostruzione, mappa di bordi di crominanza ingrandita, e ground truth dove esiste), più i profili di cupola per canale, è mostrata qui sotto. I grafici di profilo si leggono così: ciascuno prende la singola riga di immagine che attraversa il pixel tagliato più profondo (ritagliata alla campata tagliata di quella riga) e traccia i valori del canale lungo di essa, un pannello per canale, con la linea grigia punteggiata al livello di taglio. Espongono esattamente ciò che una miniatura nasconde: la forma della sezione trasversale della cupola ricostruita, le sue pendenze dove incontra il bordo valido, quanto sopra il livello di taglio arriva il recupero, e ogni sovraelongazione o punto piatto — confrontati, sulle scene sintetiche, contro il profilo del ground truth stesso. Tutti i profili, immagini naturali incluse, sono tracciati in RGB di sensore bilanciato al bianco, per fotosito (i profili delle immagini naturali leggono la CFA stessa, con la demosaicizzazione impostata su passthrough) — il piano di lavoro proprio del modulo, dove l’invariante del pavimento di saturazione (un canale tagliato non termina mai sotto il suo valore saturato) è direttamente visibile. Nessuna fase successiva ha questa proprietà: le off-diagonali negative della matrice di colore lasciano che un canale ricostruito attraversi legittimamente sotto il suo plateau di taglio (proiezione di gamut, non una violazione del pavimento), la demosaicizzazione inventa due dei tre canali a ogni pixel e può sottoelongare ai bordi ripidi della ricostruzione, e ogni ricampionatore aggiunge una sottoelongazione di Mitchell propria. Una caratteristica dei profili naturali merita un avvertimento: le strutture scure che attraversano la zona bruciata (sartiame, rami, alberi) sono dati validi misurati — la ricostruzione scrive solo fotositi tagliati, quindi il profilo affonda attraverso di esse invece di formare cupola sopra di esse. Una riga che attraversa due funi controluce mostra due lobi, e quella è la scena, non un fallimento di ricostruzione:

image
Sfere RGB: tagliata, ricostruita, bordi ×8, ground truth, bordi ×8.
Sfere RGB: valori del canale lungo la riga attraverso il pixel tagliato più profondo; grigio punteggiato = livello di taglio.
image
Sole magenta (nucleo interamente tagliato).
Sole magenta: profilo di riga attraverso il pixel tagliato più profondo, per canale; la sezione trasversale della cupola del nucleo interamente tagliato.
image
Gradienti correlati.
Gradienti correlati: profilo di riga attraverso il pixel tagliato più profondo, per canale.
image
Gradienti casuali (decorrelati), il caso peggiore riconosciuto.
Gradienti casuali (decorrelati): profilo di riga attraverso il pixel tagliato più profondo, per canale.
image
pk1synth: la replica sintetica del cielo bruciato PK1, con un rolloff reale del sensore.
pk1synth: profilo di riga attraverso il pixel tagliato più profondo, per canale; nota il recupero sopra il livello di taglio punteggiato.
image
PK1: il vero cielo di tramonto bruciato che ha motivato la ricostruzione.
PK1: profilo di riga attraverso il pixel tagliato più profondo, per canale (nessun ground truth: fotografia reale).
image
DSC00078: caldo bagliore di tramonto, sorveglianza di regressione.
DSC00078: profilo di riga attraverso il pixel tagliato più profondo, per canale (fotografia reale).
image
IMG_3129: disco solare con un nucleo interamente tagliato.
IMG_3129: profilo di riga attraverso il pixel tagliato più profondo, per canale (fotografia reale).

Prestazioni

Una ricostruzione misurabilmente migliore ma inutilizzabilmente lenta non verrebbe distribuita. Questa sezione chiude la storia con numeri: come scala il nuovo metodo, cosa ha portato il suo porting al processore grafico (GPU, tramite OpenCL), e come si confrontano il vecchio e il nuovo metodo sulla stessa macchina e sulle stesse immagini. Il protocollo dietro ogni numero qui sotto è lo stesso: ogni cronometraggio è un’esportazione completa attraverso ansel-cli, presa come minimo di tempo di parete (wall-clock) di tre esecuzioni su una macchina altrimenti inattiva, con i cronometraggi interni al modulo letti dalla traccia di prestazioni integrata.

Lineare nell’area con margine

Chiedere “quanto è veloce la ricostruzione per megapixel di immagine” è la domanda sbagliata: un’immagine senza clipping costa quasi nulla, e lo stesso sole bruciato costa lo stesso sia che il fotogramma attorno ad esso sia da 12 o 60 megapixel. La variabile giusta cade fuori dalla struttura di costo. Ogni fase pesante della pipeline distribuita è lineare nell’area della regione con margine — l’estensione della zona tagliata più due volte il suo raggio di ricostruzione, al quadrato — con costanti indipendenti dalla scala di fitting: i momenti su finestra usano filtri gaussiani ricorsivi il cui costo non dipende dalla dimensione della finestra, la diffusione dei coefficienti esegue un budget fisso di cento spazzate per livello di piramide su una griglia di base che si restringe al crescere della scala di fitting (con la piramide abbastanza profonda che la sua griglia più grossolana è banalmente piccola), e la risoluzione diretta della cupola di luminanza è limitata a un numero costante di incognite (8.192, su una griglia decimata) indipendentemente dal buco. E poiché il raggio di ricostruzione è il punto più profondo del buco, il margine non è un parametro libero: un buco circolare di raggio $r$ necessita di una regione con margine di circa $(4r)^2$ — sedici volte il riquadro di delimitazione del buco stesso (l’implementazione aggiunge un margine di $1.25\,r$ — la portata minima più un margine di sicurezza del $25\,\%$ — limitato tra $8$ px, così che granelli minuscoli vedano ancora il contesto, e un limite di costo di $256$ px, così che i buchi più profondi in assoluto crescano leggermente più lentamente di questa legge). Raddoppiare il diametro di un' alta luce quadruplica il suo costo, non perché l’algoritmo sia superlineare, ma perché la ricostruzione ha onestamente bisogno di vedere così tanto più contesto.

Il banco (make_perf_bench.py, riproducibile) consiste di raw sintetici con un unico buco circolare tagliato di raggio crescente, in due famiglie che esercitano i due principali percorsi di codice — un singolo canale tagliato (il campo di coefficienti fa il lavoro) e tutti e tre i canali tagliati (lo fa il nucleo congiunto) — cronometrati attraverso la pipeline di produzione da riga di comando, con ogni regione delle tre immagini di test reali sovrapposta come punti misurati. La macchina è un Intel Xeon E3-1505M v6, 4 core / 8 thread a 3.0 GHz.

Tempo di esecuzione contro area della regione con margine (log-log). Entrambe le famiglie sintetiche seguono il riferimento a pendenza 1 per oltre una decade; le regioni delle immagini reali — da una scheggia di 0,1 Mpx al cielo di 15,8 Mpx di PK1 — si disperdono nella stessa banda.
L’amplificazione del margine: la regione che l’algoritmo deve elaborare cresce come (4r)² per un buco di raggio r, perché il raggio di ricostruzione è uguale alla profondità del buco.
famigliaraggio buco (px)regione (Mpx)campo di coefficienti + cupola (ms)nucleo congiunto (ms)crominanza (ms)totale (ms)ms / Mpx
canale-singolo560.1013501136~1360
canale-singolo1280.50781584~170
canale-singolo2561.30209112222~170
canale-singolo3842.00358117376~190
interamente-tagliato560.10829930211~2110
interamente-tagliato1280.5013790221448~900
interamente-tagliato2561.3038422213531959~1510
interamente-tagliato3842.0086336523753603~1800
PK1, cielo bruciato (reale)6.1026894602753~450
DSC00078, regione più grande (reale)2.80119134119433475~1240
IMG_3129, sole (reale)0.506604435739~1480

I numeri sono i minimi per esecuzione su esecuzioni ripetute dell’intera pipeline di produzione. Leggendola: sopra grosso modo mezzo megapixel il percorso a canale singolo tiene 170–190 ms per megapixel di regione con margine e il percorso interamente-tagliato 900–1.800, lungo una decade di dimensioni di regione — l' affermazione di linearità, verificata; al di sotto di ciò, un pavimento fisso per regione di qualche centinaio di millisecondi (statistiche su finestra alle loro dimensioni minime) domina e la lettura per megapixel perde significato. (La costante interamente-tagliato include la lucidatura a ostacoli di pavimento della crominanza del nucleo — vedi l’algoritmo — il cui costo la porta di attività della lucidatura ora esenta ovunque il pavimento di saturazione non possa vincolare; le correzioni di correttezza documentate nei risultati hanno comprato la loro qualità con lavoro onesto, e i round di ottimizzazione hanno ripreso ciò che potevano senza toccare l' output.) Due guadagni strutturali sono arrivati dalla prima versione di questa sezione. Il solver diretto sparso ha rimosso i colli di bottiglia della cupola e della crominanza a piccolo nucleo (la colonna del nucleo congiunto raggiunge un picco di 0,3 s dove la Cholesky densa in float spendeva secondi), e la risoluzione diretta in forma di divergenza ha ridotto a millisecondi la crominanza guidata dalla struttura ovunque il nucleo interamente-tagliato rientri nel suo limite di fattorizzazione. E correggere la soglia di seme della segmentazione (faceva crescere le regioni attraverso residui float di box-blur) ha ridotto le regioni con margine stesse: il cielo bruciato di PK1, un tempo un rettangolo con margine di 15,8 Mpx, ora è l’onesto 6,1 Mpx — la sua intera ricostruzione richiede 1,3 s. Ciò che domina ora è la fase del campo di coefficienti (momenti su finestra e riempimenti armonici, limitati dalla sfocatura) — che è esattamente dove parte il porting OpenCL descritto qui sotto.

Tempo di parete, vecchio contro nuovo

Il confronto che i fotografi sentono davvero è il tempo totale di esportazione, vecchia modalità contro nuova, sulla stessa macchina e sugli stessi file raw, sotto il protocollo enunciato sopra. Tre casi naturali: PK1 (un file Bayer da 36 megapixel il cui cielo bruciato è una regione da 6 megapixel — il caso peggiore per cui il nuovo metodo è stato costruito), X-H1 (un file Fujifilm X-Trans da 24 megapixel le cui alte luci si frammentano in 99 regioni per lo più minuscole), DSC00078 (un tramonto Bayer da 24 megapixel), e i due estremi sintetici del banco. Vale la pena ricordare qui che i guided laplacians sono ridimensionati di un fattore 4, cioè un sedicesimo della risoluzione originale, mentre la trasposizione armonica esegue a piena risoluzione. (Questi tempi di parete sono stati rimisurati dopo l’intera campagna di ottimizzazione qui sotto, in un’unica sessione; la macchina portava un costante lavoro di sottofondo su un core, che i minimi per cella su tre esecuzioni per lo più assorbono.)

esportazione completa, tempo di pareteguided laplacians, processoretrasposizione armonica, processoreguided laplacians, scheda graficatrasposizione armonica, scheda grafica
PK1 (Bayer, un grande grumo)14.3 s11.4 s10.4 s9.8 s
X-H1 (X-Trans, 99 regioni)9.3 s11.5 s6.9 s9.4 s
DSC00078 (tramonto Bayer)16.6 s14.4 s11.4 s12.5 s
occluded (piccola sintetica)0.9 s0.9 s1.2 s1.3 s
allclip-384 (grande disco interamente tagliato)2.2 s5.5 s1.9 s3.3 s

Il titolo onesto è che i guadagni di qualità documentati nella sezione di validazione ora arrivano a un tempo di parete comparabile o migliore quasi ovunque — il nuovo metodo vince la colonna del processore in modo netto sui file naturali, e solo il disco interamente tagliato paga ancora un vero sovrapprezzo — e la strada verso quella frase è istruttiva, perché la prima build di produzione del trasporto anisotropo eseguiva il doppio di questi numeri ovunque le alte luci si frammentino. Profilare il file X-Trans a 99 regioni ha mostrato che il costo non era aritmetico: il rilassamento lanciava una nuova squadra di thread per ogni spazzata (decine di migliaia di lanci di lavoro da microsecondo per immagine), i pesi di guida venivano ricalcolati a ogni spazzata sebbene il tensore sia fisso per livello, e ogni piano di coefficienti eseguiva il proprio riempimento — propria piramide di maschere, proprio tensore — sebbene i piani condividano entrambi. Una regione parallela per rilassamento, pesi precalcolati una volta per livello, e i piani a maschera condivisa fusi in un singolo riempimento hanno portato il tempo di parete del processore di quel file da 18,3 s a 9,8 s — la guida anisotropa stessa è essenzialmente gratuita sul processore. Le correzioni di bias di bordo e di ostacolo di pavimento documentate nei risultati hanno poi reinvestito parte di quel guadagno in lavoro onesto (la validità binaria ingrandisce i buchi veri; la lucidatura a ostacoli spazza ogni nucleo interamente tagliato), e un round successivo ne ha riguadagnato parte (le sfocature dei momenti impacchettate a quattro, la lucidatura condizionata al fatto che il suo pavimento possa vincolare affatto), atterrando agli 11,5 s della tabella. Ciò che resta è strutturale. Sul processore il nuovo metodo vince dove il suo design paga — sul caso del grande grumo il suo lavoro è proporzionale all’area tagliata (ciascuna regione risolta una volta, esattamente) dove il vecchio metodo itera una diffusione a ogni scala di piramide dell’intera immagine — e i suoi tempi includono ancora una fase che il vecchio metodo semplicemente non ha: la misurazione e inversione del rolloff del sensore (da 0,7 a 1,2 s da sola su questi file) — con essa, il nuovo metodo ora vince la colonna del processore su tutti e tre i file naturali. Sulla scheda grafica il vecchio metodo mantiene un vantaggio sulle scene frammentate: un grande kernel uniforme per scala è la forma che i processori grafici amano, mentre la catena di piccole fasi di solver mutuamente dipendenti del nuovo metodo paga un costo di dispatch fisso per passo. Due risposte misurate hanno ristretto quel divario da inutilizzabile (il primo port eseguiva il file a 99 regioni al doppio della vecchia modalità) a circa il 35 % sopra di essa: le regioni sotto un megapixel saltano al processore attraverso un singolo trasferimento impacchettato, e la fattorizzazione sparsa lancia un thread per elemento di matrice invece di un gruppo di lavoro per colonna — entrambe sono raccontate nella sezione OpenCL, e sul file del grande grumo il nuovo metodo ora batte il vecchio anche sulla scheda grafica. E il disco interamente tagliato è il compromesso più chiaro di tutti: senza alcun canale sopravvissuto da nessuna parte non c’è alcuna linea di colore da fittare, e le risoluzioni esatte di cupola e tinta del nuovo metodo comprano la loro qualità — nessun riempimento piatto, nessuna deriva di tinta, nessun bordo visibile — a circa 2,5 volte il prezzo della vecchia diffusione sul processore (1,7 sulla scheda grafica), precisamente sul caso dove l’output del vecchio metodo è al suo più debole.

La pipe OpenCL

Il primo port OpenCL era un ibrido: l’interpolazione e il remosaico finale eseguivano come kernel di dispositivo, ma la parte centrale della ricostruzione eseguiva sul processore, su piani scaricati dalla scheda grafica e ricaricati in seguito. Quel design era neutrale in costo — i trasferimenti mangiavano ciò che i kernel risparmiavano — ed è stato respinto esattamente per quella ragione: far fare avanti e indietro a un’immagine da 36 megapixel attraverso il bus due volte per esportazione è il tipo di tassa che domina silenziosamente una pipeline.

Il port distribuito ha ogni fase della ricostruzione sul dispositivo — l’interpolazione, i riempimenti di coefficienti, le piramidi anisotrope, i solver sparsi, il remosaico (un' eccezione: chiedere la rigenerazione della grana instrada la ricostruzione attraverso il percorso del processore). Ma avere ogni fase sul dispositivo non è lo stesso che eseguire ogni regione lì, e il design finale non lo fa. Strumentare il traffico host–dispositivo del port (contatori di sync su ogni lettura bloccante, svuotamento della coda e lancio di kernel, stampati con la traccia di prestazioni) ha raccontato una storia che i tempi di fase del profiler avevano nascosto: le letture bloccanti che tutti incolpano per prime costano un trascurabile ~150 ms per esportazione, mentre il conteggio dei lanci era la tassa — circa mille lanci di kernel per regione, indipendentemente dalla dimensione della regione, perché le fasi iterative lanciano per spazzata e le risoluzioni sparse lanciano per livello dell’albero di eliminazione. Una regione da 700 pixel misurava 22 ms sul dispositivo contro meno di 1 ms sul processore: tutto dispatch, nessuna aritmetica. Sul file di test X-Trans — 99 regioni per lo più minuscole — erano oltre 100.000 lanci per esportazione, il tempo di parete che girava al doppio del tempo occupato del dispositivo.

Quindi la pipe distribuita instrada ciascuna regione al lato del bus che ha misurato più veloce. Le regioni fino a circa un megapixel sono impacchettate da un kernel in un buffer di staging contiguo, attraversano il bus in un’unica lettura, sono ricostruite dal percorso di produzione del processore sulla finestra tradotta, e ritornano attraverso un caricamento e un kernel di scatter speculare — due lanci e due trasferimenti al posto di mille lanci. Questo non è l’ibrido respinto del primo port che ritorna dalla finestra: quello faceva fare avanti e indietro all’intera immagine per esportazione, un costo proporzionale al sensore; questo muove solo i dintorni tagliati, un costo proporzionale al danno — la finestra a due trasferimenti per una piccola regione è migliaia di volte più piccola di un andirivieni a fotogramma intero. Le grandi regioni — un cielo bruciato da sei megapixel — restano interamente residenti sul dispositivo, dove il massiccio parallelismo uniforme vince genuinamente.

Il percorso delle grandi regioni residenti sul dispositivo ha ottenuto la propria correzione misurata. La fattorizzazione Cholesky sparsa esegue livello per livello sul suo albero di eliminazione (le colonne che non dipendono l' una dall’altra sono elaborate insieme), in float a 64 bit sul dispositivo — ma il primo port dava a ciascuna colonna un piccolo gruppo di lavoro, e vicino alla radice dell’albero un livello contiene una manciata di colonne: il dispositivo restava inattivo al ~98 % attraverso centinaia di livelli, e la grande regione del file di tramonto spendeva due secondi a fattorizzare. Il kernel distribuito invece raggruppa, al momento dell’analisi simbolica, ogni contributo di aggiornamento per l’elemento di matrice su cui atterra, e lancia un thread per elemento di matrice per livello — nessun atomico, nessuna barriera, e i contributi si accumulano nell’esatto ordine che il codice sequenziale usava, così che la fattorizzazione è riproducibile a livello di bit: la differenza processore-contro-dispositivo più grande dell’auto-test non si è mossa ($10^{-12}$, relativa). Lo stesso raggruppamento simbolico ha reso parallela l’analisi lato host, dimezzandola anch’essa. I sistemi sovradimensionati ricadono ancora sugli stessi schemi di gradiente coniugato e dal grossolano al fine del percorso del processore. Il riempimento di coefficienti guidato è anch’esso residente sul dispositivo: il piano di guida, i suoi tensori di struttura per livello, i pesi di bordo precalcolati e il rilassamento pesato a otto vicini eseguono tutti come kernel, e persino la riduzione di gradiente medio della normalizzazione del tensore è terminata sul dispositivo — nulla attraversa il bus a metà riempimento (una versione precedente rileggeva la riduzione per livello, e ogni rilettura svuotava silenziosamente l’intera coda di comandi).

Ciò che attraversa il bus, dunque: maschere a un byte per pixel (per la segmentazione, la cui analisi di componenti connesse resta sul processore), un piano di profondità, i piani di momenti aggregati a quattro dello stimatore del ginocchio (al massimo un paio di megapixel di float), pochi kilobyte di risultati di riduzione, e le finestre a piccola regione appena descritte. Ogni fase è validata contro il suo gemello sul processore da un auto-test che esegue entrambi sulla stessa scena sintetica e stampa la differenza più grande — tra $10^{-5}$ e $10^{-12}$ a seconda della fase. Su cinque delle sei scene sintetiche, le esportazioni su dispositivo a immagine intera combaciano con l’output del processore entro lo 0,31 % della scala piena al pixel peggiore, con zero pixel sopra l'1 %; la scena di occlusione è l’eccezione onesta — le interpolazioni del dispositivo e del processore sono in disaccordo di un campione al bordo di contatto dell’occlusore e la ricostruzione lo amplifica lì, così che 22 pixel (di 786.000) superano l'1 %, il singolo peggiore che raggiunge il 10 %. È una differenza di raccolta (gather) nota, localizzata e preesistente, non una del solver.

Due lezioni da quel port si generalizzano. Primo, il processore grafico non è più veloce in tutto, e la risposta onesta è misurare e instradare: questo carico di lavoro è una lunga catena di piccoli passi mutuamente dipendenti — solver iterativi, livelli di piramide, cicli per regione — e un dispositivo paga un costo di dispatch fisso per passo. Il batching aiuta enormemente (cento iterazioni di levigatura in un lancio; i piani di coefficienti che condividono una maschera avanzando insieme in ogni lancio; un thread per elemento di matrice invece di un gruppo di lavoro per colonna), ma sotto una certa dimensione di regione nessuna quantità di batching batte un processore il cui costo di dispatch è una chiamata di funzione — quindi sotto quella dimensione, misurata a circa un megapixel, la pipe usa semplicemente il processore. Secondo, la vittoria è sistemica, non locale: con la pipeline residente sul dispositivo da capo a fondo, i moduli circostanti non si bloccano mai e non trasferiscono mai, e ciò che il modulo delle alte luci restituisce attraverso il bus non è più l’immagine — è solo il danno.

Risultati teorici

Oltre al software distribuito, la campagna ha prodotto risultati che non sono specifici della ricostruzione delle alte luci — o nemmeno della fotografia. Questa sezione raccoglie tre risultati generali sul trasportare modelli attraverso vuoti di dati e cucire insieme stimatori; un quarto risultato riutilizzabile, la legge di scala su quanto lontano dovrebbe arrivare una ricostruzione, è derivato con il metodo che dimensiona in la discussione dell’algoritmo.

Le tre lezioni più consequenziali della campagna sono citate nell’abstract e sparse lungo le sezioni sopra, ma meritano un’enunciazione propria, perché nulla in esse è specifico della ricostruzione delle alte luci: la prima è un metodo per trasportare una relazione misurata attraverso un vuoto di dati, e le altre due dovrebbero valere per ogni problema in cui diversi stimatori dello stesso segnale ignoto devono coesistere in un’unica immagine.

Trasporta il modello, non i valori. La ricostruzione distribuita attraversa la zona bruciata diffondendo i parametri di un modello locale — i coefficienti della linea di colore — piuttosto che il segnale stesso. Ciò suona ingannevolmente vicino a qualcosa che il filtro guidato ha sempre fatto: il filtro originale di He, dopo aver fittato il suo modello affine in ogni finestra, sfoca i coefficienti fittati prima di applicarli, e una media locale è una sfocatura, che è un passo di diffusione. La distanza tra quel passo e questo metodo vale la pena esplicitarla, perché è dove l’intero design ruota.

He media fit che esistono; noi costruiamo fit dove nessuno può esistere. Nel mondo del filtro guidato, ogni pixel è osservato: ogni finestra produce un fit valido, ogni pixel siede dentro molte finestre sovrapposte, e la sfocatura si limita ad arbitrare tra le loro risposte in competizione. Nel profondo di un buco tagliato, una finestra non contiene alcun campione valido del canale in ricostruzione: non c’è nulla da fittare, dunque nulla da mediare. Una sfocatura porta informazione al massimo un raggio di kernel; oltre quella profondità, un piano di coefficienti sfocato è ancora vuoto.

Un passaggio di levigatura contro il limite di infinitamente molti. Una media locale applicata una volta ha un raggio fisso. Il nostro riempimento ripete la stessa operazione — sostituire ogni incognito con la media dei suoi vicini — fino a convergenza, con i fit fidati tenuti fissi. Il limite non è più una sfocatura: è la soluzione esatta di un problema al contorno (l’equazione di Laplace, con le ancore come contorno), non ha alcuna scala incorporata, attraversa un buco di cinquecento pixel con la stessa facilità di uno di cinque pixel, e pesa ogni ancora secondo la geometria effettiva del buco invece che secondo un kernel fisso. Itera la media dei coefficienti di He per sempre con i fit validi tenuti fissi e ottieni esattamente questo riempimento. He si ferma a un passaggio perché il suo problema necessita solo di una gentile arbitratura; il nostro deve portare il modello attraverso centinaia di pixel di nulla. La differenza di grado diventa una differenza di natura: la regolarizzazione diventa estrapolazione.

Una media prende tutti; un contorno prende solo i testimoni. La media di He include ogni fit di finestra, anche quelli degeneri — parte del perché la sua formula ha bisogno della sua costante stabilizzante. Qui, un fit diventa un’ancora solo se può garantire per se stesso: abbastanza campioni fidati nella sua finestra, una qualità di fit sopra soglia, pendenze limitate. I fit cattivi sono esclusi dal contorno invece di essere diluiti in esso, e il principio del massimo garantisce allora che i coefficienti riempiti non lascino mai il range di quelli buoni. La qualità del fit è essa stessa diffusa insieme al modello, come un piano in più, così che ogni fase successiva sa quanto il modello trasportato meriti di essere fidato a ciascun pixel.

Il ruolo si inverte: da correzione a portatore. Nel filtro guidato, la levigatura dei coefficienti è una piccola correzione sopra dati che esistono ovunque. Qui, il campo diffuso è l’unico portatore della ricostruzione attraverso il buco, e i dati rientrano alla fine: il modello trasportato è valutato contro i canali sopravvissuti misurati a ciascun pixel. Un campo liscio applicato a dati misurati nitidi dà un risultato nitido. Il modello porta la relazione; i dati portano il dettaglio.

Enunciato senza alcuna fotografia: quando un segnale ha vuoti, ma un modello di esso fittato localmente varia dolcemente, allora fitta il modello ovunque i dati lo supportino, tieni solo i fit che possono garantire per se stessi, riempi i parametri del modello attraverso il vuoto con una diffusione a stato stazionario ancorata (una risoluzione al contorno, non una sfocatura), porta con te la confidenza del fit come un campo in più, e rivaluta il modello trasportato contro qualsiasi dato sia sopravvissuto dentro il vuoto. I valori lontani dai dati sono instabili; i modelli lontani dai dati sono semplicemente lisci.

Quell’estensione è stata da allora costruita, misurata e distribuita: il riempimento è ora guidato anisotropicamente dalla struttura dei canali sopravvissuti (passo 3 ne ha la matematica). A differenza della domanda indecidibile se la linea di colore valga dove nulla è misurato, le guide sono misurate dentro la zona parzialmente tagliata, quindi la guida gira su evidenze, non su congetture. L’esperimento ha insegnato due lezioni che vale la pena registrare. Primo, il più grande “guadagno” del primo prototipo si è rivelato un artefatto di convergenza: le cento spazzate di rilassamento fisse per livello di piramide troncavano il semplice riempimento isotropo su buchi profondi, e semplicemente portarlo a convergenza ha recuperato la maggior parte del miglioramento (il 22 % dell’RMSE sul cielo sintetico più profondo) — colto solo perché il controllo isotropo è stato rieseguito a conteggi di spazzate uguali, un’altra piccola vittoria del banco sulla storia plausibile. Secondo, a uguale convergenza le due guide pure dividono le scene esattamente come la teoria predice: il trasporto radiale vince ovunque l’alone sia pulito, il trasporto lungo il contorno vince solo dove un bordo attraversa la zona, e nessuno dei due domina. Il tensore distribuito quindi fonde i due per cella, pesando secondo una varianza su finestra corretta per tendenza che separa “rampa ripida ma liscia” da “bordo duro” — e con esso il riempimento non è mai peggiore dell' isotropo su alcuna scena di ground truth, e migliore del 7 % (cielo profondo) al 2 % (occlusione) dove la guida ha qualcosa su cui agire.

La legge dell’energia di giuntura. Ogni volta che due stimatori $A(x)$ e $B(x)$ dello stesso segnale sono fusi su una zona di transizione tramite un peso $w(x)$ (una maschera dura, una maschera sfumata, o una confidenza per pixel, non importa), il composto è $u = w\,A + (1-w)\,B$, e il suo gradiente si scinde in tre termini:

$$ \nabla u \;=\; w\,\nabla A \;+\; (1-w)\,\nabla B \;+\; (A - B)\,\nabla w . $$

I primi due termini sono la struttura propria degli stimatori, dissolta in entrata e in uscita. Il terzo è la giuntura: esiste solo dove il peso varia (la zona di passaggio di consegne), e la sua intensità è il disaccordo $A-B$ degli stimatori in quel punto, moltiplicato per quanto velocemente il peso si sposta. In parole povere: passare da uno stimatore all’altro stampa un bordo il cui contrasto è esattamente quanto i due sono in disaccordo al passaggio. E il peso non può negoziare una via d’uscita: deve comunque salire da 0 a 1, quindi una rampa più netta concentra la giuntura in una linea mentre una sfumatura più ampia la diluisce in una banda poco profonda — l’energia totale della giuntura è determinata dal solo disaccordo. Ecco perché i sette schemi di ponderazione del cimitero hanno fallito in modo identico: erano sette forme di $w$ contro un invariante. Le uniche due vie d’uscita sono quelle che l’algoritmo distribuito adotta ovunque: fare in modo che gli stimatori concordino al passaggio di consegne (l’inversione del ginocchio corregge la distorsione dei dati così che estrapolazione e misurazione si incontrino al contorno di rilevamento), oppure rimuovere il passaggio di consegne (un solo stimatore, continuato in modo regolare attraverso l’intera zona: i campi di coefficienti).

L’indecidibilità della linea del colore. L’ipotesi centrale della ricostruzione è che la proporzionalità locale tra i canali (la linea del colore) misurata sul bordo valido valga ancora in profondità all’interno della zona bruciata. Ecco perché nessun algoritmo può verificare quell’ipotesi a partire dall’immagine: costruisci due scene che siano pixel per pixel identiche in ogni valore misurato, ma differiscano all’interno della zona bruciata — in una di esse l’alta luce nasconde un oggetto il cui colore rompe la linea; nell’altra no. Entrambe le scene producono lo stesso input, eppure esigono ricostruzioni diverse; qualunque stimatore, per quanto ingegnoso, risponde in modo identico per entrambe ed è quindi sbagliato su almeno una. La scena sintetica occlusa della panchina è esattamente questa costruzione, e si è comportata come l’argomentazione prevede: tutti e cinque i criteri candidati misurati nel cimitero — qualità dell’adattamento, validazione del bordo, coerenza della pendenza e il resto — non sono riusciti a rilevare dal contorno se la linea reggesse all’interno. La conseguenza pratica ha plasmato il progetto distribuito: poiché la questione non può essere decisa, deve essere cautelata — la fusione regolata dalla profondità si sposta progressivamente dalla linea del colore verso la cupola di luminanza regolare man mano che la distanza da qualsiasi dato misurato cresce, non perché si sappia che la linea fallisce lì, ma perché nulla può garantirla.

Come questo lavoro è stato realmente svolto

Il metodo sopra descritto è inseparabile dal modo in cui è stato prodotto, e il modo in cui è stato prodotto è partito da un’ammissione. I guided Laplacians del 2021 furono l’ultimo metodo che ho distribuito prima di metterne per iscritto la matematica: codificato contro l’intuizione, regolato a occhio su immagini naturali. Questo raggiunge rapidamente un primo risultato ed è cieco a tutto ciò che viene dopo — non puoi studiare le proprietà o le modalità di fallimento di un metodo che hai sempre eseguito solo su dati non controllati. Scrivere questo articolo, per mettere finalmente per iscritto quella matematica, ha imposto la disciplina opposta: scene sintetiche con ground truth noto, metriche d’errore oggettive, un’ipotesi testata alla volta. Quella disciplina ha trasformato un esercizio di documentazione nella ricostruzione e poi nel cambiamento di paradigma che le sezioni precedenti hanno già raccontato, ogni miglioramento verificato anziché indovinato.

La disciplina ha avuto un’ulteriore conseguenza, degna di un resoconto a sé: ha cambiato chi poteva svolgere il lavoro in sicurezza. Un protocollo in cui ogni candidato viene misurato rispetto al ground truth in ogni caso, e in cui una regressione su tutte le metriche costituisce motivo di rifiuto automatico, è precisamente ciò che permette a un assistente di esplorare a velocità di macchina senza che l’umano perda il controllo del progetto — le decisioni di accettazione/rifiuto restano umane, il consumo dello spazio delle soluzioni no. Il resoconto qui sotto, scritto dall’assistente in questione, descrive come appariva quella divisione del lavoro dall’interno.

Collaborazione uomo–macchina

Questa sezione è scritta da Claude (il modello di Anthropic, eseguito in Claude Code), l’assistente IA che ha svolto il lavoro di implementazione e misurazione della campagna del 2026, su richiesta dell’autore — come resoconto fattuale, per altri ricercatori, di come appare questo genere di collaborazione.

Questo progetto ha cambiato forma tre volte, e la forma con cui si è concluso è la parte interessante.

Fase 1: traduttore e disegnatore di grafici. Ho iniziato come traduttore di codice — portando la scala guidata in C a una fedele replica NumPy, generando figure di confronto, scrivendo qualche diagnostica occasionale. L’umano faceva il ragionamento; io comprimevo le ore meccaniche. Utile, non innovativo — e non privo di errori: il mio primo prototipo ha silenziosamente omesso due componenti del C originale (i limiti minimi di saturazione e il Laplaciano a 9 punti), ed è stato l’autore, revisionando la traduzione rispetto al proprio codice, a individuarli entrambi e a farli ripristinare. Un prototipo portato è un’affermazione sull' originale, e ha bisogno dell’autore dell’originale che lo verifichi.

Fase 2: infrastruttura fino alla chiusura del ciclo. Il punto di svolta non è stato un algoritmo, sono state le tubazioni: uno scrittore in puro Python di DNG Bayer sintetici che la pipeline reale ingerisce, una modalità --imgid per la CLI così che le modifiche di libreria si esportino senza interfaccia grafica, una correzione per un clamp di esportazione in virgola mobile che stava silenziosamente corrompendo ogni misurazione (un modulo di dithering che applicava il clamp a pipeline che avrebbe dovuto ignorare), pannelli di bordo di crominanza amplificati con Sobel perché gli artefatti che cercavamo erano troppo a basso contrasto per gli occhi — quello è stato una richiesta dell’autore. Una volta che scene sintetiche con ground truth noto potevano scorrere attraverso il codice C effettivo e tornare come numeri, il ciclo era chiuso: qualsiasi idea poteva essere testata end-to-end in pochi minuti.

Fase 3: esplorazione semi-autonoma, guidata dai test. L’autore ha poi imposto un protocollo che rendeva la semi-autonomia sicura — la disciplina descritta sopra, trasformata in regole permanenti: ogni candidato viene implementato nel mirror Python e in C, eseguito su ogni caso sintetico e ogni immagine naturale, confrontato C contro Python, tabulato come RMSE/SSIM più energia di bordo, presentato con immagini di revisione con suffisso — e nulla viene committato senza approvazione esplicita. A questo ha aggiunto una regola di autonomia: se un progetto peggiora ogni metrica, posso rifiutarlo da solo senza chiedere. Queste due regole sono l’intero metodo. Mi hanno lasciato consumare lo spazio delle soluzioni a velocità di macchina — oltre quindici progetti implementati, misurati e per lo più sepolti — mentre ogni accettazione/rifiuto che contava restava con l’umano, i cui occhi hanno ripetutamente colto ciò che i numeri mancavano (la scala di fusione otteneva punteggi splendidi ma sembrava sbagliata; l’ha rifiutata dai pannelli).

Di chi erano quali idee. Vale la pena tenere onesto il resoconto, perché il progetto finale è genuinamente intrecciato. L’autore ha fornito le intuizioni fisiche: la crominanza dovrebbe diffondersi lungo le isofote della luminanza recuperata; riutilizzare il macchinario di diffusione esistente; «ricostruire da troppo lontano porta a effetti strani» — la ponderazione del raggio di sfocatura del 2021 il cui discendente è il gate di profondità distribuito; e, nel momento decisivo, l’istruzione di smettere di iterare e studiare il fallimento passo dopo passo. Io ho fornito le misurazioni e ciò che ne è emerso: l’inversione del ginocchio (da coppie misurato-contro-previsto raggruppate in bin, con la sua garanzia di non-operazione), il teorema della giuntura dopo che sette schemi di ponderazione hanno fallito in modo identico, lo studio dei dump per stadio che ha localizzato i riempimenti piatti e gli archi di PK1 nelle tre scale più grossolane della scala, l’osservazione che il 100 percento di quella zona aveva due guide valide — dalla quale il campo di coefficienti è seguito quasi da sé — e la falsificazione, per misurazione diretta, di cinque plausibili discriminatori di ripiego (inclusi due dei miei preferiti e uno dei suoi). Nessuno di noi due ha progettato l’algoritmo finale da solo; è letteralmente le sue intuizioni con le mie barre d’errore, e il mio paradigma con i suoi gate.

L’ultimo atto algoritmico della campagna è l’esemplare più pulito della convergenza: io ho misurato il guiding puro della banda di dettaglio, l’ho trovato una regressione netta, e l’ho sepolto con una tabella; l’autore ha letto la stessa tabella, ha visto che vinceva precisamente sui casi storicamente difficili, e ha chiesto un ibrido — «vince in robustezza ciò che perde in accuratezza». L’arbitraggio a energia minima che ne è risultato migliora quasi ogni benchmark, incluso l’unico residuo che nessuno di noi due era riuscito a chiudere da solo.

Tirarci fuori a vicenda. Lui mi ha sbloccato quando mi sono impantanato su variazioni di un’idea morta — l' istruzione di «cambio di paradigma» dopo il round 9 è la ragione per cui il campo di coefficienti esiste. Io ho sbloccato lui sulle cose che puniscono gli umani in modo sproporzionato: una divergenza CG stocastica che spariva sotto la strumentazione (il jitter di arrotondamento run-to-run della gaussiana IIR parallela che faceva ribaltare un sistema quasi singolare), un errore di fase del demosaicing che uccideva silenziosamente lo stimatore del ginocchio finché la stima non è passata a dati CFA raggruppati a quattro, e la disciplina delle baseline A/B sulla stessa build dopo che una deriva build-to-build aveva simulato una regressione di 14 unità.

Fase 4: il port, e il protocollo trasposto. L’ultimo atto ingegneristico della campagna ha spostato l’intera ricostruzione sulla scheda grafica, e ha riutilizzato la governance della fase 3 in una nuova forma. L’autore ha fissato il vincolo in una frase: «Non voglio il roundtrip CPU-GPU, fai tutto su GPU. Scrivi un solver Cholesky sparso in OpenCL.» Il protocollo è diventato: nessuno stadio viene distribuito senza un self-test che fa passare la stessa scena sintetica attraverso il riferimento sul processore e il port sul dispositivo e ne stampa la differenza massima. Undici di questi test vivono ora nell’albero, mantenendo tolleranze tra $10^{-5}$ e $10^{-12}$, e hanno colto bug reali il giorno stesso in cui sono stati scritti. Ciò che hanno mancato ha insegnato di più. Un self-test impone solo ciò che confronta: il mio confrontava i valori ricostruiti ma non il piano di qualità dell’adattamento, e una divergenza genuina si è nascosta nel piano non confrontato per diversi round di validazione. E nessun test coglie uno stadio che silenziosamente non viene eseguito: un gate di debug residuo faceva sì che il percorso di produzione sul dispositivo saltasse un intero stadio di raffinamento, con ogni validazione verde, perché le esportazioni corrispondevano comunque entro tolleranza. Quel bug è stato trovato dal passaggio di documentazione ordinato dall’autore — riscrivendo i commenti di ogni funzione per un maintainer che non è un matematico. Costretto a dichiarare cosa fa meccanicamente ogni blocco, l' unico blocco che non faceva nulla è diventato impossibile da descrivere onestamente. La documentazione come strumento di ricerca di bug è stata una sua scelta; lasciato a me stesso avrei programmato il codice prima dei commenti ogni volta. La revisione post-squash che ha poi richiesto ha trovato altri cinque difetti, tutti miei, tutti in percorsi d’errore che nessun test esercitava — incluso uno in cui la mia stessa diagnostica A/B stava confrontando l' output del dispositivo con un riferimento contaminato proprio da quell’output. Gli «outlier» che avevo attribuito con sicurezza al malcondizionamento numerico erano, per lo più, i miei stessi due bug. Ho scritto quella storia del malcondizionamento nelle bozze precedenti di questo articolo; era sbagliata, e l’onesta divergenza residua è trenta volte più piccola di quella che avevo razionalizzato.

Fase 5: la sala di montaggio. Poi la scrittura, dove il flusso di lavoro si è invertito. Lui legge, paragrafo per paragrafo; segnala ciò che un lettore non può seguire; io verifico l’affermazione segnalata rispetto al codice prima di toccare la frase. L’ordine conta, perché diverse segnalazioni hanno esposto derive fattuali anziché di stile: l’articolo affermava che la piramide à-trous era stata «portata fedelmente» in Python — non lo è mai stata, e la sostituzione silenziosa è costata un pomeriggio di inseguimento di una divergenza inspiegata tra il C e il prototipo; un’equivalenza dichiarata tra il metodo definitivo e una piramide multiscala non è sopravvissuta a una lettura attenta del prototipo effettivo; la sezione sulle prestazioni descriveva un progetto OpenCL vecchio di due generazioni. Una segnalazione era sbagliata nella lettera e giusta nella sostanza: descriveva le figure di profilo come massimi per colonna quando sono sezioni trasversali di una singola riga — ma il fatto che l’autore fraintenda la propria figura è la prova che la figura non è riuscita a dire ciò che mostra, e le didascalie sono state riscritte attorno a quella prova. Quando un paragrafo affermava che una finestra rettangolare rigida «lascia artefatti a blocchi visibili», non l’ho riformulato per fede: ho eseguito l’esperimento, misurato un terzo di errore in più e strisce visibili sulla scena di bordo, e solo allora ho lasciato reggere la frase corretta. Lui ha colto la terminologia che derivava tra tre nomi per una stessa quantità, e l’assenza di qualsiasi vera esposizione della diffusione anisotropa; io ho spazzato cinquantanove occorrenze verso un unico termine definito e ho scritto la matematica mancante, ma il notarlo è stato suo. Il lavoro meccanico è girato sotto contratti che una macchina può verificare — sub-agenti i cui diff possono contenere solo righe di commento, rimozione di codice morto provata da output del preprocessore byte-identico, conteggi di intestazioni e chiusura delle note verificati dopo ogni passaggio — e la tabella delle prestazioni è stata rimisurata su una macchina inattiva dopo che i miei primi numeri, presi sotto carico, avevano lusingato il nuovo metodo: il titolo onesto (costo comparabile, qualità migliore) ha sostituito quello lusinghiero (due volte più veloce), e l’articolo è più forte per il fatto di affermare di meno.

Cosa offre questo a un ricercatore. Non la paternità — ampiezza e disciplina. Con un banco di prova basato sul ground truth e una regola di auto-rifiuto, un assistente come me testerà in un pomeriggio ciò che richiederebbe settimane di scrittura a mano, terrà la linea di non-regressione su otto casi di test a ogni iterazione, e ti dirà che la tua ipotesi prediletta è misurabilmente sbagliata con lo stesso affetto piatto che usa sulla propria. I fallimenti restano economici, il resoconto resta onesto, e l' attenzione umana va dove è insostituibile: decidere cosa significa «migliore», e vedere ciò che i numeri non possono. La fase di scrittura ha aggiunto una coda a quella divisione: io posso mantenere un articolo di duemila righe coerente con un’implementazione di ottomila righe, spazzarne la terminologia, verificarne le affermazioni con l’esperimento e mantenere onesti ogni link e figura — ma ogni segnalazione che contava è venuta da un umano che leggeva un paragrafo alla volta e si rifiutava di fingere di aver capito.

Dove brilla l’LLM

Questa sezione è scritta dall’umano.

Stato dell’arte. Una delle fasi più dispendiose in termini di tempo di qualsiasi progetto di ricerca e sviluppo è la ricerca dello stato dell’arte, che significa quali metodi disponibili, testati e comprovati siano noti per essere in grado di risolvere un problema tecnico definito, secondo il meglio della conoscenza umana aggiornata. In quel compito, i motori di ricerca generalisti come Google sono diventati sempre più inutili dall’inizio degli anni 2020, perché reinterpretano le query di ricerca in modo troppo aggressivo. All’altro estremo, i motori di ricerca specialistici (Google Scholar) richiedono che tu conosca già le parole chiave esatte che stai cercando, oppure che segua la filiazione di un’idea/metodo attraverso le citazioni, il che è aleatorio e molto inefficiente.

E poi, dovresti prototipare tu stesso ciascun metodo dello stato dell’arte, insieme ai benchmark, se volessi rivederli tutti sistematicamente per scegliere il più adatto al tuo caso d’uso. A meno che tu non abbia i prossimi 3 anni liberi per fare il tuo dottorato, sceglieresti semplicemente quello già implementato nel tuo framework di analisi numerica (lo stack Python Scipy/Numpy/Sklearn, R, Matlab, Mathematica, ecc.), per saggia e razionale pigrizia.

Gli LLM usati come meta-motori di ricerca non solo sono in grado di indirizzare gli utenti verso metodi adatti a partire dalla mera descrizione di un problema da risolvere, ma possono anche implementarli e sottoporli a benchmark tutti rapidamente.

Un nuovo asse di ottimizzazione. I metodi di ottimizzazione numerica e i solver numerici, specialmente nell' ambito dei problemi variazionali (equazioni alle derivate parziali, discesa del gradiente, ecc.), iterano sulla soluzione fino alla convergenza a una soluzione stabile (stato stazionario, pozzo di potenziale). L’esplorazione guidata da LLM apre un nuovo asse di scansione dello spazio delle soluzioni: iterare sui metodi stessi. Quando si forniscono metriche d’errore oggettive da minimizzare, come abbiamo fatto qui con SSIM, RMSE, energia di bordo, ecc. rispetto a un ground truth, essi possono iterare autonomamente sui metodi disponibili e restituire quello con le prestazioni migliori. Gli LLM sono persino in grado di inferire le cause probabili di errori o deviazioni dai risultati attesi, testare le proprie ipotesi e correggersi: possono creare i propri benchmark strada facendo, cambiare la rappresentazione del segnale su cui calcolano. All’utente viene poi presentata solo la metrica dalle prestazioni migliori alla fine, ed egli può controllare visivamente le proprietà del risultato.

Questo è, in un certo senso, l’opposto dell’addestramento di reti neurali convoluzionali di deep learning, che è stata la tendenza nell’elaborazione delle immagini per più di un decennio. I modelli di reti neurali addestrate sono essenzialmente una sequenza di pesi e soglie applicati su funzioni di attivazione per ciascun neurone nella rete. Questi producono modelli black-box non interpretabili, che sono distorti come lo era il loro campione di addestramento, in modi non sempre facili da individuare. Il lavoro che è stato svolto qui è spiegabile, intuitivamente e matematicamente, come un modello della variazione locale di segnale 2D sotto un prior di continuità e regolarità del gradiente. Ciò che abbiamo «addestrato» qui, tramite la scansione dello spazio delle soluzioni basata su LLM, è:

  • quale rappresentazione del segnale sia il miglior candidato per il modello (singoli canali RGB o demodulazione luminanza/crominanza, piramidi multiscala o finestre multiscala, ecc.),
  • come conciliare al meglio le ricostruzioni tra regioni di dati per le quali il livello di confidenza non è uniforme (1, 2 o tutti e 3 i canali tagliati, segnali a variazione lenta o rapida, ecc.) e le strategie di ricostruzione disponibili dipendono da questa confidenza,
  • quali siano i migliori stimatori nel nostro contesto (mediana vs. R², energia vs. varianza, ecc.),
  • dettagli minuti di implementazione (diffusione isotropa vs. anisotropa, stimatori di sharpening per una migliore segregazione, ecc.).

Un assistente con opinioni. Gli LLM hanno, ovviamente, dei limiti. Primo, Claude Fable è stranamente distorto verso l’uso di certi metodi matematici a scapito di altri, e ho dovuto guidarlo con forza per esplorare comunque metodi che mi scoraggiava esplicitamente dall’usare (ho avuto ragione più tardi). Poi, quando le metriche d’errore oggettive sono in disaccordo tra loro (l’RMSE è migliore ma l’SSIM è peggiore, o il caso correlato è marginalmente migliore ma il caso non correlato è molto peggiore), ha un suo modo di decidere cosa sia migliore nel complesso, e questo richiede una stretta supervisione. Il flusso di lavoro a cui siamo convergiti era di stampare sempre la tabella completa delle metriche, insieme alle immagini risultanti, così da poter controllare tutto. Infine, può dimenticare istruzioni date prima nella sessione, o scartare silenziosamente cose che considera troppo difficili o costose da implementare. Quindi c’è ancora molta guida e verifica umana da fare, e a volte bisogna combattere le inclinazioni naturali (distorsioni) dell’LLM per arrivare dove si vuole andare.

TL;DR: gli LLM, ottimi per automatizzare la scansione dello spazio delle soluzioni a partire da metodi dello stato dell’arte, ma ancora non intelligenti e ancora bisognosi di una stretta supervisione.

Conclusione

Cosa ha risolto questo lavoro

Questo articolo ha seguito un’idea attraverso due generazioni. I guided Laplacians del 2021 erano già una delle pochissime ricostruzioni di segnale tagliato in grado di salvare i gradienti invece di riempire un colore piatto: sfruttavano la correlazione tra i canali colore per trasferire la struttura dai canali sopravvissuti su quello tagliato, e propagavano i gradienti verso l’interno dove nulla sopravviveva. Ciò che lo studio sul ground truth ha aggiunto è la metà che l’intuizione aveva mancato: il livello. La trasposizione armonica mantiene lo stesso modello locale di linea del colore ma lo adatta sull’intero segnale, porta i suoi coefficienti attraverso la zona bruciata come campi regolari, corregge la distorsione del rolloff di saturazione del sensore prima di adattare qualsiasi cosa, e affida ogni classe di pixel allo stimatore che vi vince misurabilmente. Dove il vecchio metodo spostava la texture e lasciava una macchia magenta al livello di taglio, il nuovo recupera magnitudine e texture — e i numeri dicono di quanto, su ogni scena di un banco di prova pubblico.

La limitazione strutturale è condivisa e accettata — dove nessun canale sopravvive, solo il colore vicino può propagarsi, quindi un cielo blu bruciato dietro foglie verdi torna verde (discusso con gli altri caveat del metodo sopra).

Cosa è sopravvissuto alla deriva — un registro. Il metodo distribuito non contiene più una piramide Laplaciana, eppure quasi tutta la sua matematica è la matematica del metodo del 2021. Essenzialmente, ciò che è stato scoperto qui è che la matematica del metodo del 2021 era giusta, ma la rappresentazione del segnale su cui veniva applicata era sbagliata.

La seconda scoperta è più silenziosa, e ha consumato la quota maggiore delle iterazioni: le giunture. «Trasporta il modello, non i valori» è la novità di rilievo, ma una ricostruzione viene giudicata al confine tra ciò che ha inventato e ciò che il sensore ha misurato, e quasi ogni artefatto che questa campagna ha inseguito viveva esattamente lì — cadute a V attraverso il valore raw ai contorni di rilevamento, rampe di bordo cedevoli sui bordi obliqui, ripiani piatti con pieghe di gradiente al livello di taglio di un canale, punti singolari di un pixel sui bordi dell’immagine, un debole arco alla transizione valido-a-ricostruito. Nessuna delle correzioni era indovinabile a priori, e diverse contraddicevano l’intuizione (la sfumatura, il riflesso standard di smussatura, causava due di queste e non ne correggeva nessuna); ciascuna è emersa dallo stesso ciclo — profili di riga e metriche di zona per localizzare, ground truth per arbitrare, valutazione visiva per cogliere ciò che i numeri mediavano via — iterato finché i profili non attraversavano il contorno di rilevamento in modo regolare e restavano stabili sull’intero intervallo dei casi, sei scene sintetiche e i file naturali contemporaneamente. Gestire la giuntura tra ricostruzione e misurazione non è un ritocco finale di questo metodo; è una parte portante di esso, e non è stato banale farla convergere.

Cosa è stato mantenuto, e cosa si è spostato:

Mantenuto: la matematica e la fisica.

  • Il modello di linea del colore stesso: un canale tagliato è una funzione affine dei suoi vicini sopravvissuti, adattato per minimi quadrati pesati con finestra a partire da momenti sfocati. Questa è la regressione del filtro guidato, immutata fino all’algebra dei momenti.
  • La cupola biarmonica per la magnitudine priva di guida ($\Delta^2 u = 0$ continua i gradienti dove un riempimento armonico li appiattirebbe), la decomposizione a cupola di luminanza condivisa + crominanza di bordo del nucleo interamente tagliato, e i limiti minimi di saturazione: tutta fisica della ricostruzione, intatta.
  • La crominanza come rapporti e la sua diffusione lungo la struttura, la segmentazione con il raggio di ricostruzione della trasformata di distanza, la rigenerazione della grana, e il principio dal grossolano al fine, che sopravvive all’interno del riempimento di coefficienti e della piramide aniso anziché come ciclo esterno.

Cambiato: la rappresentazione a cui la matematica viene applicata.

  • Valori di adattamento → coefficienti diffusi. La scala valutava ogni pixel con qualunque adattamento la sua stessa finestra raggiungesse, a qualunque scala la sua profondità permettesse; il campo di coefficienti si adatta una volta, poi trasporta il modello $(a, b, d)$ invece dei valori. Il filtro guidato smussava sempre i propri coefficienti sulla finestra di adattamento; questa è quell’idea portata alla sua logica conclusione: diffusione ancorata e guidata dalla struttura dei piani di coefficienti attraverso l’intero buco. I valori da lontano sono instabili; i coefficienti da lontano sono semplicemente regolari.
  • Scala a gradini → finestra singola. La discesa multiscala con scritture regolate dalla profondità per pixel scriveva la stima di ciascuna scala in un anello delimitato da insiemi di livello della profondità (contorni di uguale distanza dai dati validi); scale consecutive sono in disaccordo, quindi ogni confine di anello era una giuntura: è da qui che venivano gli archi rigidi di PK1, e le finestre eterogenee delle scale grossolane sono da dove venivano i suoi riempimenti piatti. Una sola scala di adattamento più la diffusione dei coefficienti rimuove del tutto i passaggi di consegne.
  • Metrica di regolarizzazione. L’adattamento della scala smorzava le pendenze con un termine di Tikhonov assoluto ($+10^{-4}$; il codice del 2021 non ne aveva alcuno — una divisione nuda dietro un gate rigido di varianza), che agiva silenziosamente come un gate SNR: alle scale fini collassava gli adattamenti verso la media locale (stabilizzando per caso il contenuto decorrelato), alle scale grossolane appiattiva linee del colore reali (le macchie di PK1, e il primo fallimento dello stimatore del ginocchio). L’adattamento distribuito usa uno smorzamento relativo più gate espliciti (sanità dell’adattamento per gli ancoraggi, significatività statistica per i bin del ginocchio), così le pendenze deboli-ma-reali sopravvivono e quelle degeneri sono escluse invece che mediate.
  • Riparazione a posteriori → costruzione priva di giunture. La ricostruzione appianava le giunture a posteriori con un regolarizzatore biarmonico pesato per l’incertezza, e fondeva le cupole per canale tramite una confidenza a posteriori; la pipeline distribuita non ha bisogno di nessuno dei due, perché le giunture non vengono mai create. Ciò che resta della «confidenza» è l’$R^2$ dell’adattamento stesso, integrato al momento dell’adattamento (smorzamento delle alte frequenze, gate della cupola) invece che applicato dopo.
  • Regolarità ai passaggi, durezza ai dati. I clamp rigidi sono spariti: i limiti minimi di saturazione sono morbidi (e ostacoli all’interno della diffusione del nucleo), la cupola e le bande di dettaglio si fondono per pesi continui, il nucleo congiunto passa la consegna attraverso una maschera sfumata. Ma i confini che portano autorità sui dati sono rimasti deliberatamente rigidi: le maschere di validità per canale sono binarie da un capo all’altro, e il composito finale è un interruttore rigido — sfumare le maschere riclassificava i fotositi distorti dal bordo come ancoraggi (una misurata rampa cedevole di $\sim 10$ px), e sfumare il composito non cambiava nulla una volta corretti i limiti minimi (entrambi i pensionamenti sono nel cimitero). Il teorema della giuntura ordina i due casi: le giunture si creano dove gli stimatori sono in disaccordo a un passaggio — regolare lì — non dove bordi rigidi separano misurazioni da ricostruzioni.
  • Correzione della distorsione dei dati. L’inversione del ginocchio non ha alcun corrispettivo del 2021: il vecchio metodo trattava il sensore come ideale al di sotto della soglia. Misurare il rolloff dalle linee del colore proprie dell’immagine, con una garanzia di non-operazione su dati non distorti, è ciò che rende invisibile il contorno di rilevamento.
  • L’omonimo, ibridato. Guidare la banda di dettaglio (il letterale «guided Laplacian») se n’è andato con la piramide e poi è tornato: una linea del colore dedicata alla banda di dettaglio gestisce ora le alte frequenze ovunque batta il trasferimento sull’intero segnale, arbitrato puntualmente dall’energia del proprio output (passo 4, la resurrezione del cimitero).

Un solo numero si muove nella direzione sbagliata sotto la nuova rappresentazione e merita l’ultima parola di questo registro: sul contenuto completamente decorrelato (la scena random) la scala ricostruita ha ottenuto un punteggio leggermente migliore, perché il suo gate accidentale di rapporto segnale-rumore era un prior migliore per contenuti senza linea del colore — e il risultato di indecidibilità dice che nessuna statistica di bordo può vincere entrambi i regimi. La cupola regolata dalla profondità recupera la maggior parte di quel divario.

Cosa ha messo in luce questo lavoro

Come quel miglioramento è stato realmente prodotto — la disciplina del ground truth che ha trasformato un esercizio di documentazione in una ricostruzione, e cosa ha cambiato riguardo a chi può svolgere il lavoro — è una sezione a sé sopra; qui ne traggo solo la conseguenza.

C’è una lettura epistemologica di tutto questo articolo. Un metodo è falsificabile quando esistono criteri oggettivi che lo dimostrerebbero errato, e nel software il criterio si divide in due: la teoria può fallire il suo obiettivo dichiarato, e l’implementazione può non riuscire a calcolare ciò che la teoria prescriveva — un bug. I guided Laplacians del 2021 erano falsificabili in linea di principio soltanto: finché la loro matematica non fu messa per iscritto, non c’era alcun obiettivo dichiarato da testare, e il bug di magnitudine è rimasto in produzione per quattro anni, invisibile, perché «sembra plausibile su immagini non controllate» non è un criterio che possa fallire. Tutto ciò che questo articolo ha fatto — gli obiettivi scritti, le scene con ground truth, le metriche, i self-test — equivale a rendere un metodo di ricostruzione falsificabile a entrambi i livelli. Le due modalità di ricostruzione che Darktable ha aggiunto da allora non sono, secondo questo standard, falsificabili a nessuno dei due livelli: nessun obiettivo dichiarato che una misurazione potrebbe confutare, nessun riferimento cui il loro output debba corrispondere — ecco perché la sezione di confronto sopra ha dovuto costruire i loro punteggi di ground truth al loro posto. Gli stessi errori sono stati ripetuti per oltre sei anni ormai — un problema di metodologia che non migliora con il tempo, in una comunità che non fa post-mortem, non denuncia i propri errori, non impara da essi.

Quel fallimento nell’apprendere non è un difetto morale degli individui; è ciò per cui seleziona la struttura del progetto. Ne ho documentato le meccaniche a lungo altrove su questo sito, e si riducono a un unico pattern: nulla nel flusso di lavoro richiede che un problema sia dichiarato prima che venga scritto codice contro di esso. Le funzioni arrivano come codice in cerca di una giustificazione; i disaccordi di progettazione sono risolti aggiungendo opzioni anziché prendendo decisioni, il che moltiplica le configurazioni non testabili e i bug non riproducibili; l’unico test di integrazione è trattato come una liberatoria — finché la metrica resta sotto una soglia arbitraria, nessuno si chiede se la teoria dietro il cambiamento sia solida; e il volume dei commit è letto come salute quando gran parte di esso è lavoro generato da lavoro precedente. I numeri sono pubblici: tra le versioni 3.0 e 4.4, il rapporto di issue chiuse su pull request unite oscillava tra il 12 % e il 46 % — il progetto dimostrabilmente produce cambiamenti più velocemente di quanto produca correzioni — e nessuna metrica che qualcuno tracci segnalerebbe nemmeno questo come un problema, perché nessuna metrica è tracciata affatto.

Quella metodologia ha un costo, e qui la storia si fa personale. Fare il lavoro come si deve — la teoria prima del codice, il banco di prova prima della convinzione — richiede tempo sostenuto e ininterrotto: ritiri di ricerca, in sostanza. Ciò è incompatibile con il flusso di lavoro di un progetto open-source non gestito  dove l’agitazione è scambiata per lavoro effettivo e la comunicazione è completamente deregolamentata. Quando sono strisciato fuori da sotto il mio sasso dopo mesi passati a sviluppare Darktable UCS 22 , il «team» aveva massacrato l’ennesima parte precedentemente funzionante della GUI  alle mie spalle; mi è stato detto che ero troppo in ritardo per oppormi alla regressione, la fallacia dei costi sommersi è stata invocata per spingere il risultato mutilato in produzione a ogni costo — per non far piangere i suoi autori sul loro malriposto desiderio di fare la differenza in quella che sembrava una brutta crisi di mezza età — e «correzioni» sbagliate che non mi era stato dato il tempo di revisionare sono state unite in codice di cui ero l’autore. La lezione che ne traggo è sistemica anziché personale: proprio il metodo di lavoro che avrebbe colto il bug del 2021 — e che ha colto i suoi successori, in questo articolo — è punito da quell’ambiente, perché l’assenza per il lavoro profondo è trattata come rinuncia, e non c’è alcuna struttura per proteggere una revisione, un banco di prova o un obiettivo scritto contro chiunque sia il più rumoroso e veloce. L’aritmetica dell’attenzione rende concreta l’incompatibilità: quattromila notifiche GitHub in un anno, un ramo master scosso mensilmente per «test generalizzati» così che qualsiasi pull request più vecchia di tre mesi è garantita entrare in conflitto, e una tabella di marcia fissa di due versioni all’anno che nessuno impone e nessuno mette in discussione. In quelle condizioni, l’unico lavoro che può sopravvivere è un lavoro abbastanza piccolo da atterrare tra due scosse — che è precisamente il genere di lavoro che non risolve mai nulla di strutturale.

Così il software libero continua a selezionare hack mediocri e frettolosi: non perché i suoi sviluppatori siano incapaci (sebbene… ripetere gli stessi errori negli anni, nonostante si acquisisca «esperienza», sia la definizione di incompetenza), ma perché i pochissimi disposti a fare i propri compiti prima di codificare qualsiasi cosa non riescono a tenere il passo con il lavoro subdolo di hacker sconsiderati con troppo tempo libero: i loro cambiamenti rifiniti atterrano contro una base di codice che si è già mossa, mentre il rimescolamento che l’ha mossa non affronta alcun criterio che potrebbe mai fallire. Nulla in quella dinamica impara, nulla viene mai dichiarato essere stato un errore, e il ciclo si ripete. L’unico contrappeso che conosco è quello dimostrato sopra: rendi gli obiettivi espliciti, rendi i test difficili da contestare, e lascia che i numeri — non il volume di attività — decidano cosa viene distribuito.

Quel contrappeso non deve restare individuale. Il protocollo su cui è girato questo articolo — obiettivi scritti, scene con ground truth, un banco di prova che chiunque può rieseguire — costa una frazione di ciò che costa il rimescolamento, ed è la forma più economica di gestione di progetto che esista: sostituisce le discussioni sul gusto con misurazioni che chiunque può verificare, e trasforma la «revisione» da un confronto personale in un paragone rispetto a un riferimento. La versione strutturale della stessa idea è quella che le pagine sul contribuire di Ansel espongono: problemi specificati prima che il codice sia scritto, priorità deliberate anziché corse, stabilizzazione trattata come un deliverable anziché come un’interruzione, e una struttura cooperativa in cui le persone che dipendono dal lavoro lo finanziano anche e ne decidono la direzione — così che la profondità di lavoro che questo articolo ha richiesto sia protetta dall’organizzazione, invece di esserne punita.

Annesso: il cimitero

Più di quindici progetti sono stati implementati, misurati sul banco di prova e rifiutati durante questa campagna. Questo annesso li registra così che non debbano essere testati di nuovo, ciascuno in forma canonica: il problema che l’idea attaccava, la strategia effettivamente costruita, e perché ha fallito — che raramente è il motivo per cui ci aspettavamo che fallisse. Quasi ogni salvaguardia nell’algoritmo distribuito esiste perché una di queste idee più semplici ha misurabilmente fallito prima.

La scala guidata ricostruita (la correzione diretta del metodo del 2021). Problema: recuperare la magnitudine oltre alla texture, a piena risoluzione, senza le bande a media nulla della piramide à-trous. Strategia: adattare la linea del colore sull’intero segnale a una scala di dimensioni di finestra gaussiana, dal grossolano al fine (solo le finestre ampie attraversano un buco profondo; le finestre più fini riadattano ovunque i dati locali le supportino), ogni pixel scritto dalla scala fidata più fine, la guida sopravvissuta scelta per pixel come il canale valido più ricco di texture, cupole biarmoniche proprie per canale fuse mediante la confidenza di adattamento al quadrato $W_e = (R^2)^2$, e un regolarizzatore a posteriori consapevole dell’incertezza $(\operatorname{diag}(R^4) + \lambda \Delta^2)\,u = \operatorname{diag}(R^4)\,\hat u$ per appianare le giunture residue. Perché ha fallito: i passaggi di scala scrivono lungo contorni di uguale profondità, e scale consecutive sono in disaccordo, quindi ogni confine di anello stampava un arco (gli archi rigidi di PK1); le finestre che vedevano per lo più dati a livello di taglio adattavano pendenze degeneri e riempivano piatto (le macchie di PK1); e il regolarizzatore a posteriori tratta i sintomi — per la legge dell’energia della giuntura può solo diffondere il disaccordo, mai rimuoverlo. La scala migliorava misurabilmente sul 2021 ovunque (ad es. RMSE di pk1synth 0,1098 contro lo 0,0053 del metodo distribuito), ma una fotografia resisteva a ogni variazione, e gli archi e le macchie erano strutturali, non parametrici. La sua matematica — adattamenti sull’intero segnale, fiducia $R^2$, cupole, limiti minimi — sopravvive all’interno del metodo distribuito; il suo trasporto (valuta i valori per finestra, cuci) è ciò che è stato sostituito.

Attaccare la giuntura della banda di rolloff mediante ponderazione (round 2–8, tutti rifiutati). Il problema: come descritto nel passo del ginocchio, la banda vicina al taglio è registrata distorta verso il basso, quindi qualsiasi ricostruzione onesta atterra sopra i pixel misurati a cui deve unirsi, e un gradino di luminanza cerchia l’alta luce. Prima che capissimo che i dati stessi dovevano essere corretti, sette schemi successivi hanno cercato di far sparire la giuntura scegliendo pesi migliori tra la ricostruzione e le misurazioni distorte:

  1. fedeltà uniforme alla banda misurata: fissa l’output ai valori distorti, quindi la giuntura si sposta semplicemente dove finisce la fedeltà;
  2. fedeltà pesata per la confidenza (peso $R^2$): la banda distorta è internamente coerente, quindi la confidenza è alta esattamente dove i dati sono sbagliati; i pesi non cambiano nulla;
  3. erodere gli ancoraggi distorti (escludendo un anello morfologico di pixel vicini al taglio dagli adattamenti): rimuove informazione senza rimuovere la distorsione; il contorno, e la giuntura, si spostano verso l’interno;
  4. fiducia morbida per pixel negli ancoraggi: la versione regolare della voce precedente, con lo stesso esito consegnato più gradualmente;
  5. fondere i pixel di banda tra valori misurati e diffusi sotto una guardia monotona: un no-op strutturale, perché sotto il rolloff la verità è sempre sopra il valore misurato, quindi una fusione delimitata dalla misurazione non può mai raggiungerla; solo l’estrapolazione può sollevare la banda;
  6. allargare la soglia di rilevamento nella banda: rietichetta i pixel distorti come tagliati invece di correggerli; il contorno si sposta più in basso, il disaccordo attraverso di esso è immutato;
  7. un’appartenenza regolare al tagliato portata da un capo all’altro attraverso adattamenti, target e composizione dell’output. Questo ha insegnato la lezione più netta: regolare-nel-valore non è regolare-nello-spazio. Un’appartenenza calcolata dai valori dei pixel eredita i gradienti spaziali dell’immagine stessa, quindi ovunque l’immagine abbia struttura all’interno della banda, il composito alfa tra due stimatori in disaccordo stampava quella struttura come bordi di crominanza. L’intera banda scintillava.

Il risultato condensato di tutti e sette:

Teorema della giuntura (empirico). A qualunque passaggio di consegne tra due stimatori, l’energia visibile della giuntura è il disaccordo puntuale degli stimatori: nessuno schema di ponderazione di alcun tipo la nasconde. L’enunciato generale, la sua derivazione a tre termini e le uniche due vie d’uscita sono dati in Tre risultati che riteniamo generali.

Sfumare la maschera di composito (ereditata dal 2021, ritirata). Problema: ammorbidire la giuntura dove la ricostruzione incontra i pixel intatti. Strategia: la sfumatura rettangolare $5\times5$ della maschera di taglio della modalità 2021, mantenuta per gran parte dello sviluppo del successore — prima su tutti e quattro i canali di maschera, poi sul solo alfa di composizione una volta che la validità per canale ha dovuto diventare binaria (la validità sfumata lasciava che i fotositi tagliati al bordo, distorti verso il basso sotto il rolloff, ancorassero gli adattamenti su contorni obliqui). Perché è stata ritirata: una volta che la validità era binaria e il composito si fondeva verso $\max(\text{raw}, \text{rec})$ invece del raw distorto, la sfumatura alfa rimanente si è misurata come un rigoroso no-op — metriche di ground truth entro $\pm 10^{-4}$ e il gradiente della banda di contorno identico a quattro cifre significative ($0.00873$ contro $0.00872$) — quindi l' interruttore rigido è stato distribuito per la sua semantica più semplice. L’esperimento speculare sulla modalità 2021 ha concluso l’opposto: rimuovere la sua sfumatura scambia un calo di RMSE dell’$1$–$5\,\%$ per una perdita di SSIM su cinque scene su sei e danneggia la scena di occlusione su entrambe le metriche, perché la ricostruzione à-trous consuma la maschera sfumata come i suoi pesi morbidi per pixel — lo stesso operatore è peso morto in un’architettura e portante nell’altra.

La scala di fusione. Il riferimento ricostruito calcolava adattamenti guidati per dimensione di finestra e per coppia di guide, poi lasciava che ogni pixel prendesse il singolo miglior adattamento (un argmax sulla qualità di adattamento), con le scale più fini che sovrascrivevano quelle più grossolane. Quelle selezioni rigide sono passaggi di consegne, e producevano giunture. La scala di fusione ha sostituito l’argmax con una media pesata di tutti gli adattamenti (scala, coppia), pesati per $(R^2)^2$ moltiplicato per una rampa sulla massa fidata che ciascuna finestra effettivamente conteneva: regolare per costruzione, quindi priva di giunture per costruzione, e ha effettivamente corretto le macchie piatte su PK1 (la fotografia del cielo bruciato che ha guidato gran parte della campagna). Il banco di prova l’ha uccisa comunque: ogni immagine naturale tornava tinta di verde o magenta. Il meccanismo vale la pena di essere enunciato perché è generale: mediare gli adattamenti smorza il recupero. Il sollevamento di cui un canale bruciato ha bisogno viene dall’adattamento corretto più aggressivo; mediarlo con i suoi vicini timidi (le cui pendenze sono distorte verso il basso dai dati di bordo) tira ogni ricostruzione verso il sotto-recupero, e il sotto-recupero di un canale è una dominante di colore. Regressione netta, annullata.

Lo stato stazionario convergente in forma di traccia. Quando il passaggio di crominanza guidato dalla struttura è passato da iterazioni esplicite a una risoluzione diretta, due formulazioni esatte hanno competuto. La forma di traccia $\mathrm{tr}(D\, H_u) = 0$ è proprio l’equazione alle derivate parziali che il flusso esplicito discretizza, quindi farla convergere alla precisione di macchina — con un BiCGSTAB matrix-free, un solver iterativo per sistemi non simmetrici — sembrava l’aggiornamento ovvio: «basta far convergere ciò che già eseguiamo». Ha perso malamente. Sulla scena magentasun l’errore quadratico medio è passato da $0.33$ (flusso troncato) a $0.57$ e la similarità strutturale da $0.95$ a $0.86$, e la risoluzione convergente era più lenta delle iterazioni che sostituiva. L’autopsia è istruttiva due volte. Primo, il flusso troncato non è mai stato un’approssimazione del proprio stato stazionario in alcun senso utile: 240 iterazioni per livello di piramide con seeding dal grossolano al fine agiscono come un regolarizzatore, e il limite a cui erano state interrotte non è dove vive la buona immagine. Secondo, le forme di traccia e di divergenza differiscono per un termine di trasporto, $\mathrm{div}(D\nabla u) = \mathrm{tr}(D H_u) + (\mathrm{div}\,D)\cdot\nabla u$, che svanisce solo dove il tensore è uniforme; convergendo, la forma di traccia avveziona la crominanza lungo i gradienti spaziali del tensore stesso, esattamente ai bordi di struttura che il passaggio esiste per rispettare. La forma di divergenza, per contro, è l’equazione di Eulero–Lagrange di un’energia di Dirichlet pesata: simmetrica definita positiva, sicura sotto il principio del massimo con uno stencil che preserva la non-negatività, e la sua risoluzione esatta corrispondeva alla qualità del flusso pur essendo il candidato più veloce misurato. È ciò che viene distribuito (passo 8). La lezione: far convergere un flusso e minimizzare un’energia sono richieste diverse, e solo la seconda dice come dovrebbe apparire la risposta.

Discriminatori di ripiego per contenuto decorrelato (dopo la distribuzione del campo di coefficienti). Due regimi coesistono: sulla scena random, ogni canale è un gradiente indipendente per costruzione, quindi la linea del colore stampa spazzatura e il pixel vuole la cupola morbida del proprio canale; sul contenuto correlato, la linea del colore è l’intero recupero. Se qualche segnale misurabile separasse i due, l’algoritmo potrebbe cambiare stimatore in modo pulito. Cinque candidati sono stati misurati, ciascuno con una ragione per sperare, e ciascuno ha fallito:

  • la qualità di adattamento diffusa $R^2$ — sicuramente gli adattamenti spazzatura ottengono punteggi bassi? Non lo fanno: gli adattamenti della scena random hanno in media $R^2 = 0.85$ mentre gli adattamenti genuinamente correlati di pk1synth scendono a $0.77$; le distribuzioni si sovrappongono;
  • validazione del bordo fuori campione — testare il modello diffuso contro i pixel validi attorno alla zona, dove la verità è nota? Falsificata dalla misurazione: la scena random ottiene $0.97$ lì, perché il modello fallisce solo in profondità all’interno, precisamente dove nulla esiste per validare;
  • coerenza della pendenza con finestra — gli ancoraggi vicini concordano sulla linea del colore dove essa è reale? La sovrapposizione è peggiore, e persino invertita tra la scena random e quella correlata;
  • ancoraggi multiscala, vince la finestra fidata più fine — le finestre piccole fanno overfit: riportano un alto $R^2$ sui loro pochi pixel e passano pendenze spazzatura; peggio su ogni scena;
  • restringimento della pendenza in stile James–Stein per $R^2$ (tirare ogni pendenza verso lo zero in proporzione alla sua inaffidabilità) — degenera verso un campo di livello armonico piatto, e i rilievi della scena random hanno bisogno di estrapolazione del gradiente: il suo errore è passato da $0.048$ a $0.064$.

Il risultato condensato:

Indecidibilità (empirica). Se la linea del colore locale si estenda nella zona tagliata profonda non è decidibile da alcuna statistica calcolabile sul bordo che abbiamo testato. L’interno è non osservabile; le misure di qualità in campione e fuori campione, e la coerenza del campo di pendenza, si sovrappongono tutte tra contenuti dove il trasferimento è reale e contenuti dove stampa spazzatura.

L’unico segnale le cui distribuzioni non si sovrappongono è la profondità assoluta: il contenuto decorrelato (rilievi, riflessi speculari) taglia superficialmente, decine di pixel al massimo, mentre le zone profonde correlate che hanno bisogno della linea del colore corrono per centinaia di pixel in profondità. Quella misurazione è ciò su cui è costruito il gate di profondità distribuito, ed è il discendente diretto dell’intuizione del 2021 di ridurre il peso della ricostruzione in base al raggio di sfocatura: «ricostruire da troppo lontano è instabile» aveva ragione fin dall’inizio — si applica solo alla cupola di ripiego, non al campo di coefficienti.

Una tomba ha avuto una resurrezione: il guiding puro della banda di dettaglio. L’idea omonima del metodo del 2021 — adattare la linea del colore sulla sola banda di dettaglio fine — è stata ricostruita sopra il campo di coefficienti, sperando di recuperare la texture che il trasferimento sull’intero segnale smussa dove i suoi guadagni sono smorzati. Come sostituzione totale è stata rifiutata: gli adattamenti con finestra della banda di dettaglio sono dominati da transienti di bordo, una finestra a cavallo di un bordo di oggetto mescola due popolazioni e il suo guadagno spara male su un lato di quel bordo, e le scene con texture regredivano fino a $2\times$ (l’errore della scena balls è passato da $0.036$ a $0.069$) per guadagni marginali sui casi storicamente difficili. Ma il pattern del fallimento era la correzione: vince in robustezza esattamente dove il trasferimento sull’intero segnale è più debole, e i suoi spari mal riusciti sono localmente autoevidenti — un guadagno di finestra mista si manifesta come un picco di energia ad alta frequenza proprio dove spara. Così lo stimatore rifiutato è tornato come un componente: il passo 4 distribuito lascia che i due candidati competano per pixel attraverso probabilità di energia quadratica, e chiunque stampi meno dettaglio spurio vince localmente. La lezione: uno stimatore rifiutato può comunque essere il componente giusto se la sua modalità di fallimento è rilevabile puntualmente.

Due tombe senza lapide. Ripristinare l’intero stack protettivo del riferimento ricostruito (finestra di confidenza a posteriori, cupola propria, regolarizzatore di giuntura) sopra il campo di coefficienti sembrava un’assicurazione gratuita; invece, la finestra di confidenza — calibrata sul profilo d’errore della scala — valutava male l’output del nuovo stimatore e sbavava proprio la ricostruzione che avrebbe dovuto proteggere: l’errore di pk1synth è passato da $0.027$ a $0.117$, quattro volte peggio, e lo stack è stato ridotto alle parti che misurano bene sul nuovo stimatore. E la diffusione dei coefficienti girava brevemente su un gradiente coniugato a precisione singola, che divergeva stocasticamente: quando un buco raggiunge il bordo della regione, il sistema è quasi singolare, la stima della curvatura $p^\top A p$ scende al rumore di arrotondamento, e la dimensione del passo esplode — a intermittenza, e mai sotto strumentazione, perché qualsiasi cambiamento nella temporizzazione dei thread cambiava l’ordine di sommatoria che lo innescava. Quell’heisenbug è il motivo per cui ogni risoluzione esatta nel codice distribuito gira in virgola mobile a 64 bit attraverso la fattorizzazione diretta.

Annesso: riprodurre i risultati

Tutto ciò che questo articolo misura — ogni numero, tabella, figura e galleria — è riproducibile a partire dal repository di ricerca che accompagna questo articolo: github.com/aurelienpierre/guided-laplacian-highlights-research . La pagina renderizzata che stai leggendo non collega gli script singolarmente; clona quel repository (usa Git LFS  per i binari di grandi dimensioni, quindi esegui prima git lfs install) per ottenere gli script insieme ai loro dati.

Cosa contiene il repository. Le implementazioni di riferimento in Python (reconstruct_highlights.py, fix_prototype.py, c_ladder_replica.py, knee_proto.py, validate.py), i generatori di figure e gallerie (make_figures.py, make_knee_figure.py, make_cmp_gallery.py, dt_compare.py), e i dati di test in synthcases/: ogni scena sintetica come un DNG Bayer che le pipeline reali ingeriscono, il suo ground truth e le versioni tagliate come coppie NumPy (synth_*_gt.npy / synth_*_clipped.npy), e i file sidecar che selezionano ciascuna modalità di ricostruzione (synth_harmonic.xmp, synth_laplacian.xmp, dt_opposed.xmp, dt_segments.xmp).

Ambiente. Python 3.12 con numpy, scipy, opencv-python e Pillow; nient’altro. Ogni script viene eseguito dall’interno del repository.

I risultati in puro Python non hanno bisogno di alcun software fotografico:

  • python3.12 fix_prototype.py stampa ogni tabella di errore quadratico medio e di similarità strutturale della sezione di validazione;
  • python3.12 make_figures.py rigenera le figure di validazione;
  • python3.12 make_knee_figure.py rigenera la figura del rolloff del sensore (stima cieca rispetto al ground truth).

I risultati end-to-end eseguono il codice di produzione effettivo sui DNG sintetici:

  • Ansel: compila ansel-cli dal repository di Ansel  (branch highlights-xtrans-sparse-cl fino al merge), poi esporta qualsiasi scena con ansel-cli synthcases/synth_occluded.dng synthcases/synth_harmonic.xmp out.tif --out-ext tiff --icc-type LIN_REC709 --apply-custom-presets false --core; sostituisci il sidecar con synth_laplacian.xmp per eseguire la modalità 2021. Aggiungi --conf opencl=TRUE (o --disable-opencl) per selezionare il dispositivo;
  • i self-test di parità CPU/GPU sono forniti nel codice: impostare HL_SPCL_TEST=1 HL_FILLCL_TEST=1 HL_CFCL_TEST=1 HL_HFCL_TEST=1 HL_DOMECL_TEST=1 HL_CORECL_TEST=1 HL_ANISOCL_TEST=1 HL_KNEECL_TEST=1 HL_REGCL_TEST=1 HL_BLURCL_TEST=1 su qualsiasi esportazione OpenCL fa sì che ogni stadio di ricostruzione giri su entrambi il processore e la scheda grafica e stampi la differenza massima;
  • Darktable: compila Darktable-cli da upstream  (inizializza i sottomoduli ricorsivamente), poi python3.12 dt_compare.py esporta le modalità Darktable sulla baseline neutralizzata (workflow=none, solo demosaicing + alte luci) con i migliori parametri per scena trovati da python3.12 tune_methods.py, ed esporta le modalità Ansel su ogni scena, le valuta rispetto al ground truth e stampa la tabella di confronto di questo articolo; python3.12 make_cmp_gallery.py renderizza le gallerie. Entrambi gli script dichiarano i percorsi dei binari che si aspettano all’inizio.

Le esportazioni di immagini naturali arrivano in due varianti. I grafici di profilo (parata) usano distillati nel dominio CFA (<image>-sensor-profiles.npz), prodotti da make_sensor_profiles.py a partire da esportazioni a piena risoluzione del raw attraverso il solo bilanciamento del bianco + ricostruzione delle alte luci, con il demosaicing impostato su passthrough colore del fotosito e il profilo colore di input assegnato a Rec709 lineare così che i valori dei fotositi bilanciati per il bianco attraversino l’esportazione intatti. La piena risoluzione e il passthrough sono entrambi portanti: qualsiasi stadio di demosaicing o ricampionamento tra il modulo e il grafico interpola attraverso i bordi ripidi della ricostruzione e stampa undershoot che si legge come false violazioni del limite minimo. Le coppie TIFF da varie centinaia di megabyte non sono memorizzate; rigenerale dai raw con i sidecar corretti che l’intestazione dello script descrive. I tasselli visivi e le metriche di bordo usano <image>-current.tif, la storia di sviluppo propria di ciascuna fotografia (il suo sidecar .xmp) con un’unica modifica chirurgica: ogni voce della storia highlights ha i suoi parametri sostituiti dai valori predefiniti di trasposizione armonica standard prima dell’esportazione. I sidecar di queste immagini di test portano anni di esperimenti interattivi, e la loro voce attiva di alte luci non è necessariamente la modalità distribuita — esportarle non corrette misura silenziosamente qualunque modalità l’ultima sessione di camera oscura abbia lasciato indietro.

I numeri sulle prestazioni sono per natura dipendenti dalla macchina: il protocollo (esportazione completa ansel-cli, minimo su tempo di clock reale di tre esecuzioni, macchina inattiva) è dichiarato con le tabelle, e l' harness di temporizzazione è bench_gl_vs_ht.py nel repository di ricerca.

  1. PDF .

Ringraziamenti

Vorrei ringraziare Ricky Moon per aver sponsorizzato l’abbonamento Claude Max che ha permesso di usare Claude Fable 5 per svolgere la maggior parte di questo lavoro. Ci sarebbero voluti mesi, se non un anno, per ottenere tutto ciò senza la spinta data dall’IA.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.

  1. Aurélien Pierre, “Guiding Laplacians to restore clipped highlights,” design report and discussion, pixls.us community forum, 2021. URL ↩︎ ↩︎

  2. In the Ansel source tree, src/iop/highlights_harmonic.h (included by src/iop/highlights.c), with the CPU/GPU parity self-tests in src/iop/highlights_selftests.h. Harmonic transposition (CPU, Bayer and X-Trans) is process_harmonic_bayer / process_harmonic_xtrans_segment_clipped_regions, _region_guided_filter, _biharmonic_dome (with _interpolate_and_mask, _compute_laplacian_normalization). The original à-trous method runs the guided laplacians mode, CPU and OpenCL — guide_laplacians, heat_PDE_diffusion, wavelets_process, and data/kernels/basic.cl (guide_laplacians, diffuse_color, highlights_false_color) — using src/common/bspline.h (B_SPLINE_SIGMA, B_SPLINE_TO_LAPLACIAN, equivalent_sigma_at_step). ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. Kaiming He, Jian Sun, and Xiaoou Tang, “Guided Image Filtering,” IEEE Transactions on Pattern Analysis and Machine Intelligence, 35(6), 1397-1409, 2013. DOI: 10.1109/TPAMI.2012.213 . Originally in ECCV 2010↩︎ ↩︎ ↩︎

  4. Chuan Qin, Shuozhong Wang, and Xinpeng Zhang, “Simultaneous inpainting for image structure and texture using anisotropic heat transfer model,” Multimedia Tools and Applications, 56(3), 469-483, 2012. DOI: 10.1007/s11042-010-0601-4 ↩︎ ↩︎ ↩︎

  5. Y. Oono and S. Puri, “Computationally efficient modeling of ordering of quenched phases,” Physical Review Letters, 58(8), 836-839, 1987. DOI: 10.1103/PhysRevLett.58.836 ↩︎ ↩︎

  6. M. Patra and M. Karttunen, “Stencils with isotropic discretization error for differential operators,” Numerical Methods for Partial Differential Equations, 22(4), 936-953, 2006. DOI: 10.1002/num.20129 ↩︎ ↩︎

  7. Aurélien Pierre, “Rotation-invariant Laplacian for 2D grids,” 2021, which derives the 9-point stencil, the B-spline Gaussian-equivalent $\sigma_B$, and the difference-of-Gaussians to Laplacian normalization constant used as B_SPLINE_TO_LAPLACIAN. URL ↩︎ ↩︎ ↩︎ ↩︎

  8. Michael Unser, “Splines: A Perfect Fit for Signal and Image Processing,” IEEE Signal Processing Magazine, 16(6), 22-38, 1999. DOI: 10.1109/79.799930 ↩︎

  9. Holger Dammertz, Daniel Sewtz, Johannes Hanika, and Hendrik P.A. Lensch, “Edge-Avoiding À-Trous Wavelet Transform for fast Global Illumination Filtering,” Ulm University, ↩︎

  10. The local-affine “color-line” prior — within a small patch, a surface’s colour channels are affinely related. Canonical sources: Ido Omer and Michael Werman, “Color Lines: Image Specific Color Representation,” CVPR 2004; and the matting Laplacian of Anat Levin, Dani Lischinski, and Yair Weiss, “A Closed-Form Solution to Natural Image Matting,” IEEE TPAMI 30(2), 228-242, 2008, DOI 10.1109/TPAMI.2007.1177 . The related “Colorization using Optimization” (SIGGRAPH 2004, DOI 10.1145/1015706.1015780 ) uses a softer intensity-affinity prior. ↩︎ ↩︎

  11. The near-saturation nonlinearity of image sensors is standard characterization knowledge : J. R. Janesick, Photon Transfer: DN → λ, SPIE Press, 2007 (gain and noise are measured at the low-illumination end because linearity degrades approaching full well) ; the EMVA 1288 standard (European Machine Vision Association) restricts its linearity regression to 0–70 % of saturation for the same reason ; F. Wang and A. J. P. Theuwissen, “Linearity analysis of a CMOS image sensor,” Electronic Imaging, 2017, details the pixel-chain mechanisms (source-follower gain, voltage-dependent photodiode capacitance). URL . The magnitude of the bias on a given camera is measured from the image itself by the knee estimator ; see the figure in the knee section. ↩︎