2012-01-27

Skalowanie obrazu z wykorzystaniem filtrów.

Skalowanie w dwóch wymiarach możemy zrealizować jako superpozycję skalowania w poszczególnych wymiarach osobno. Np. najpierw w poziomie, a później w pionie. Samo skalowanie to splot odpowiedzi impulsowej filtra z pikselami poszczególnych linii i kolumn. Splot po stronie wymiaru przestrzennego oznacza mnożenie po stronie częstotliwości.

Przy pomniejszaniu każdy piksel wyjściowy rekontrujemy z pewnego zbioru pikseli wejściowych. Jeśli promień filtra to 2, a współczynnik pomniejszania to 3 to bierzemy pod uwagę 12 pikseli źródła.

Przy powiększaniu bierzemy 4 piksele.

Na te 12 i 4 piksele rozciągamy odpowiednio filtr.

Ponieważ dla każdego wiersza i każdej kolumny współczynniki filtra są takie same warto je wstępnie policzyć.

Pojawia się też pytanie: co zrobić dla skrajnych pikseli, dla których obszar do filtrowania wychodzi poza bitmapę. Tutaj mamy generalnie 3 rozwiązania: kopiować skrajny piksel, dokonać lustrzanego odbicia lub zawinąć. To ostanie przydaje się kiedy zamierzamy teksturować przedmiot wielokrotnie tą samą teksturą. Pierwsze nadaje zbyt dużego znaczenia skrajnym pikselowi. Ja z reguły dla samplowania bitmap wykorzystuję metodę drugą.

Szerokość efektywna filtra (nasze 12 i 4) może być liczbą zmiennoprzecinkową. Sam środek naszego filtra także. Bardzo ważne jest tutaj takie dobranie jego szerokości całkowitej, by wartości filtra wewnątrz obszaru były różne od zera, a poza równe mu.

Na samym końcu warto jeszcze raz przejrzeć wagi i wyeliminować z nich te o wadze 0, które mimo wszystko mogą się pojawić. Poza tym możemy także znormalizować nasz filtr tak by pole powierzchni pod nim była równa jeden (suma wag filtra), dzięki temu unikniemy małych zmian w jasności.

Samego filtrowania powinniśmy dokonywać w liniowej przestrzeni kolorów.

Kod metody wyliczającej wagi:

public class PixelWeight : IComparable<PixelWeight>
{
    public double Weight;
    public int Pos;

    public override string ToString()
    {
        return String.Format("pos: {0}; weight: {1}", Pos, Weight);
    }

    public int CompareTo(PixelWeight a_other)
    {
        return Pos - a_other.Pos;
    }
}

public static void PrecalculateWeights(int a_src_width, int a_src_height,
    int a_dest_width, int a_dest_height, 
    out List<List<PixelWeight>> a_horz_weights,
    out List<List<PixelWeight>> a_vert_weights, Filter a_filter, 
    bool a_optimize = true)
{
    double scalex = 1.0 * a_dest_width / a_src_width;
    double scaley = 1.0 * a_dest_height / a_src_height;

    double src_ray_x = a_filter.Ray / scalex;
    double src_ray_y = a_filter.Ray / scaley;

    a_horz_weights = new List<List<PixelWeight>>(a_dest_width);

    if (scalex < 1)
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(src_ray_x + 2));

        for (int x = 0; x < a_dest_width; x++)
        {
            double src_center_x = x / scalex;

            int start_xx = (int)Math.Floor(src_center_x - src_ray_x);
            int end_xx = (int)Math.Ceiling(src_center_x + src_ray_x);

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_x = src_center_x.Round();

            List<PixelWeight> weights = new List<PixelWeight>();

            for (int xx = start_xx; xx <= end_xx; xx++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectX(xx),
                    Weight = a_filter.Evaluate((src_center_x - xx) * scalex) * scalex
                });
            }

            Debug.Assert(a_filter.Evaluate(
                (src_center_x - (start_xx - 1)) * scalex).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                (src_center_x - (end_xx + 1)) * scalex).IsAlmostEquals(0));

            a_horz_weights.Add(weights);
        }
    }
    else
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(a_filter.Ray + 2));

        for (int x = 0; x < a_dest_width; x++)
        {
            double src_center_x = x / scalex;

            int start_xx = (int)Math.Floor(src_center_x - a_filter.Ray);
            int end_xx = (int)Math.Ceiling(src_center_x + a_filter.Ray);

            List<PixelWeight> weights = new List<PixelWeight>();

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_x = src_center_x.Round();

            for (int xx = start_xx; xx <= end_xx; xx++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectX(xx),
                    Weight = a_filter.Evaluate(src_center_x - xx)
                });
            }

            Debug.Assert(a_filter.Evaluate(
                src_center_x - (start_xx - 1)).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                src_center_x - (end_xx + 1)).IsAlmostEquals(0));

            a_horz_weights.Add(weights);
        }
    }

    a_vert_weights = new List<List<PixelWeight>>(a_dest_height);

    if (scaley < 1)
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(src_ray_y + 2));

        for (int y = 0; y < a_dest_height; y++)
        {
            double src_center_y = y / scaley;

            int start_yy = (int)Math.Floor(src_center_y - src_ray_y);
            int end_yy = (int)Math.Ceiling(src_center_y + src_ray_y);

            List<PixelWeight> weights = new List<PixelWeight>();

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_y = src_center_y.Round();

            for (int yy = start_yy; yy <= end_yy; yy++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectY(yy),
                    Weight = a_filter.Evaluate((src_center_y - yy) * scaley) * scaley
                });
            }

            Debug.Assert(a_filter.Evaluate(
                (src_center_y - (start_yy - 1)) * scaley).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                (src_center_y - (end_yy + 1)) * scaley).IsAlmostEquals(0));

            a_vert_weights.Add(weights);
        }
    }
    else
    {
        OverlayCorrector overlay_corrector = OverlayCorrector.Create(
            OverlayMethod.Mirror,
            a_src_width, a_src_height, (int)(a_filter.Ray + 2));

        for (int y = 0; y < a_dest_height; y++)
        {
            double src_center_y = y / scaley;

            int start_yy = (int)Math.Floor(src_center_y - a_filter.Ray);
            int end_yy = (int)Math.Ceiling(src_center_y + a_filter.Ray);

            List<PixelWeight> weights = new List<PixelWeight>();

            if (a_filter.FilterType == FilterType.NearestNeighbour)
                src_center_y = src_center_y.Round();

            for (int yy = start_yy; yy <= end_yy; yy++)
            {
                weights.Add(new PixelWeight()
                {
                    Pos = overlay_corrector.CorrectY(yy),
                    Weight = a_filter.Evaluate(src_center_y - yy)
                });
            }

            Debug.Assert(a_filter.Evaluate(
                src_center_y - (start_yy - 1)).IsAlmostEquals(0));
            Debug.Assert(a_filter.Evaluate(
                src_center_y - (end_yy + 1)).IsAlmostEquals(0));

            a_vert_weights.Add(weights);
        }
    }

    if (a_optimize)
    {
        foreach (var ws in a_vert_weights.Concat(a_horz_weights))
        {
            double sum = ws.Sum(w => w.Weight);

            foreach (var w in ws)
                w.Weight /= sum;

            for (int i = ws.Count - 1; i >= 0; i--)
            {
                if (ws[i].Weight.IsAlmostEquals(0))
                    ws.RemoveAt(i);
            }

            ws.Sort();
        }
    }
}

Kod samej metody reskalującej:

public void Resize(ColorArrayFloat a_dest,
    FilterType a_filter)
{
    List<List<PixelWeight>> horz_weights, vert_weights;
    ResizerResampler.PrecalculateWeights(Width, Height, a_dest.Width, a_dest.Height, 
        out horz_weights, out vert_weights, Filter.Create(a_filter));

    ColorArrayFloat temp_bd = new ColorArrayFloat(a_dest.Width, Height);

    for (int x = 0; x < temp_bd.Width; x++)
    {
        List<PixelWeight> weights = horz_weights[x];

        for (int y = 0; y < temp_bd.Height; y++)
        {
            ColorFloat color = new ColorFloat();

            foreach (PixelWeight pw in weights)
                color += GetColor(pw.Pos, y) * pw.Weight;

            temp_bd.SetColor(x, y, color.AboveZero);
        }
    }

    for (int y = 0; y < a_dest.Height; y++)
    {
        List<PixelWeight> weights = vert_weights[y];

        for (int x = 0; x < a_dest.Width; x++)
        {
            ColorFloat color = new ColorFloat();

            foreach (PixelWeight pw in weights)
                color += temp_bd.GetColor(x, pw.Pos) * pw.Weight;

            a_dest.SetColor(x, y, color.AboveZero);
        }
    }
}

Przykład klasy filtra:

public class LanczosFilter : Filter
{
    public double Tau = 3;

    public override double Ray
    {
        get
        {
            return Tau;
        }
    }

    public override double Evaluate(double a_value)
    {
        double v = Math.Abs(a_value);

        if (v > Tau)
            return 0;

        if (v < Constants.DOUBLE_PRECISION)
            return 1;

        v = v * MathExtensions.PI;
        double p3 = v / Tau;
        return Math.Sin(v) * Math.Sin(p3) / (v * p3);
    }
}

Brak komentarzy:

Prześlij komentarz