Applicazione desktop completa per creazione e ottimizzazione di time-lapse. Il vincolo che ne definisce l'architettura è l'assenza totale di componenti di terze parti: il progetto non ha alcun PackageReference e non invoca processi esterni. Oltre alla libreria standard di .NET si usano solo API native di Windows (WIC, Media Foundation, GDI+, DWM) richiamate via P/Invoke scritto a mano. Sono in-house tutte le parti che di norma si delegherebbero a una libreria: il parser binario EXIF/XMP, la misura di luminanza e la curva di deflicker, il calcolo del campo vettoriale di movimento con il motion blur sintetico, il multiplexer MP4 e ogni controllo dell'interfaccia. Scelte algoritmiche che meritano una nota: - il deflicker usa una regressione lineare locale pesata con seconda passata robusta, così le rampe reali di luce (alba, tramonto) sopravvivono mentre lo sfarfallio del diaframma viene rimosso; una media mobile semplice le appiattirebbe entrambe; - la sfocatura mancante si compone in quadratura con quella già incisa nello scatto, perché sommarla linearmente renderebbe l'immagine troppo morbida; - la luminanza si misura come media logaritmica troncata, invariante alla scala e insensibile a cieli bruciati e ombre chiuse. L'elaborazione non produce file temporanei e mantiene un'occupazione di memoria stazionaria: buffer poolati e canale a capacità limitata rendono i fotogrammi vivi indipendenti dalla lunghezza della sequenza. Verificato con "Titano.exe --selftest": 23 controlli su una sequenza sintetica dalle proprietà note, incluse la struttura del contenitore prodotto e la sua ri-decodifica con il lettore di sistema. Tutti superati. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
49 lines
1.9 KiB
C#
49 lines
1.9 KiB
C#
using Titano.Imaging;
|
||
|
||
namespace Titano.Motion;
|
||
|
||
/// <summary>
|
||
/// Interpolazione temporale fra due fotogrammi guidata dal campo vettoriale.
|
||
///
|
||
/// Si usa il warping all'indietro bidirezionale: l'istante t viene ricostruito prelevando da
|
||
/// A lungo −t·v e da B lungo +(1−t)·v, poi i due contributi vengono miscelati con pesi
|
||
/// complementari. Il warping all'indietro non lascia buchi (a differenza di quello in avanti)
|
||
/// e la miscelazione incrociata attenua gli errori del campo nelle zone di occlusione.
|
||
/// </summary>
|
||
public static class FrameInterpolator
|
||
{
|
||
/// <summary>Genera il fotogramma all'istante <paramref name="t"/> ∈ [0,1] fra A e B.</summary>
|
||
public static void Interpolate(ImageBuffer a, ImageBuffer b, MotionField flowAtoB,
|
||
float t, ImageBuffer destination)
|
||
{
|
||
if (t <= 0f) { destination.CopyFrom(a); return; }
|
||
if (t >= 1f) { destination.CopyFrom(b); return; }
|
||
|
||
int width = a.Width;
|
||
int height = a.Height;
|
||
var srcA = a.Data;
|
||
var srcB = b.Data;
|
||
var dst = destination.Data;
|
||
float wa = 1f - t;
|
||
|
||
Parallel.For(0, height, y =>
|
||
{
|
||
int rowBase = y * width * ImageBuffer.Channels;
|
||
for (int x = 0; x < width; x++)
|
||
{
|
||
int index = rowBase + x * ImageBuffer.Channels;
|
||
flowAtoB.Sample(x, y, out float vx, out float vy);
|
||
|
||
MotionBlurRenderer.SampleBilinear(srcA, width, height, x - vx * t, y - vy * t,
|
||
out float ar, out float ag, out float ab);
|
||
MotionBlurRenderer.SampleBilinear(srcB, width, height, x + vx * wa, y + vy * wa,
|
||
out float br, out float bg, out float bb);
|
||
|
||
dst[index] = ar * wa + br * t;
|
||
dst[index + 1] = ag * wa + bg * t;
|
||
dst[index + 2] = ab * wa + bb * t;
|
||
}
|
||
});
|
||
}
|
||
}
|