2012-01-18

Periodogram

Periodogram to zsumowana transformata DFFT serii obrazów. DFFT z pojedynczego obrazu może nam nie pozwolić wychwycić jakiegoś trendu w obrazie, rzeczy które powtarzają się z pewną częstotliwością. Co innego suma takich DFFT.

Periodogram jest przydatny do oceny jakości samplerów. Idealnie wymagamy od samplera by próbki były rozmieszczone w miarę równo od siebie i losowo. W miarę równo po stronie FFT oznacza w miarę równo od siebie odległe piki, tym węższe (mniej rozmyte) czym równiej od siebie rozmieszone. Ponieważ mamy do czynienia z próbkami w 2D piki te powinny tworzyć okręgi, co oznacza, że jeśli weźmiemy próbkę A i kąty wszystkich próbek wokół niej rozmieszczonych to są one równomierny rozmieszczone.

Punkty do generowania perodiogramów musimy przeskalować. Normalnie mamy próbkę na piksel albo i więcej jeśli mamy subsampling. Obraz musi też być w miarę duży by wychwycić wzorzec częstotliwości w pojedynczym obrazie. Odstępami pomiędzy próbkami regulujemy odstępy pomiędzy okręgami na periodogramie. Trochę zabawy i można dobrać parametry jak należy.

Interesuje nas tylko kilka pierwszych okręgów, czym dalej tym bardziej znikają one w szumie i potrzeba więcej obrazów z większą ilością próbek.

Do generacji periodogramów służy osobna klasa renderera:

public class SamplesPeriodogramRenderer : Renderer
{
    [YAXNode]
    public int Repeats = 100;

    [YAXNode]
    public int ScaleSamples = 25;

    [YAXNode]
    public double ScaleValues = 0.01;

    ColorArrayFloat m_periodogram;

    public override void Render()
    {
        m_periodogram = new ColorArrayFloat(Film.Width, Film.Height);

        for (int i = 0; i < Repeats; i++)
        {
            m_rects_to_update.Enqueue(Film.GetFilmRect());
            DoUpdateCallback();

            Scene scene = Scene.DeepClone();
            scene.RenderOptions.Renderer = 
                Renderer.Create(RendererType.SamplesVisualizer);
            (scene.RenderOptions.Renderer as SamplesVisualizerRenderer).Scale = 
                ScaleSamples;
            scene.ActiveCamera.Film.Postprocessors.Add(new FFT2Transform());

            if (i != 0)
                (scene.ActiveCamera.Film.Resampler as ExactResampler).SuppressLog = 
                    true;

            if ((i % 5 == 0) && (i > 0))
                Loggers.Raytracer.Info("Periodogram progress: {0}", i * Repeats / 
                    100);

            Renderer renderer = scene.CreateRenderer();
            renderer.ShouldStop += () => DoShouldStop();
            renderer.Render();

            var part = renderer.Canvas.PostprocessedArray;

            foreach (var p in part.Pixels())
                m_periodogram[p.X, p.Y] += part[p.X, p.Y] * ScaleValues;

            m_rects_to_update.Enqueue(Film.GetFilmRect());
            DoUpdateCallback();

            if (DoShouldStop())
                break;
        }
    }

    public override void UpdateBitmap(Bitmap a_bmp)
    {
        using (Graphics g = Graphics.FromImage(a_bmp))
        {
            Rectangle rect;
            while (m_rects_to_update.TryDequeue(out rect))
            {
                Bitmap bmp = m_periodogram.GetBitmap(rect);
                g.DrawImage(bmp, rect);
            }
        }
    }

    public override RendererType RendererType
    {
        get
        {
            return RendererType.SamplerPeriodogram;
        }
    }
}

Jej zadaniem jest generować tylko punkty z powierzchni projekcji kamery, same promienie nie są śledzone. Same próbki są skalowane, by było widać odstępy między nimi. Skalowaniu podlegają też końcowe wartości kolorów na periodogramie, chodzi o to by nie był on zbyt jasny ani zbyt ciemny.

Za samą generację DFFT2 odpowiada postprocesor.




Dla mnie najważniejsze było potwierdzenie tego, że mój kod generuje rozkład Poissona, a nie coś innego. Moje periodogramy wyglądają tak jak inne, które można znaleźć w sieci, tak więc w kodzie raczej nie ma błędów. Same periodogramy wyglądają jak powinny, czyli w kodzie samplera Poisonna nie ma błędów.

W przyszłości zamierzam dodać samplowanie adaptatywne. Być może będę generować próbki tylko dla małego kwadratu i wykorzystywać je do całego renderowane obrazu (z jakimś mniej lub bardziej losowym mieszaniem). Obecnie renderowanie odbywa się kafelkami, zamierzam dla każdego kafelka osobno generować punkty do samplowania i zwalniać je jak nie są już potrzebne. Pozwoli to zaoszczędzić trochę pamięci, zwłaszcza przy samplowaniu adaptatywnym. Dla samplowania adaptatywnego będzie to chyba naturalna opcja dla zsamplowania jakiegoś obszaru z wyższą częstotliwością poprzez utworzenie nowej pary sampler/resampler. Z tym zwalnianiem to też nie taka prosta sprawa, dla Poisonna potrzebuje brzegowe próbki z sąsiednich kafelków, tak więc tak od razu nie możemy się pozbyć próbek z kafelków. Periodogram na pewno pozwoli mi stwierdzić, że wszystko działa.

Brak komentarzy:

Prześlij komentarz