Подтвердить что ты не робот

Разделить алгоритм изменения размера на два прохода

Я написал следующий алгоритм изменения размера, который может правильно масштабировать изображение вверх или вниз. Это слишком медленно, хотя из-за внутренней итерации через массив весов на каждом цикле.

Я уверен, что я должен был бы разделить алгоритм на два прохода так же, как и с двухпроходным гауссовским размытием, что значительно снизило бы эксплуатационную сложность и ускорило бы производительность. К сожалению, я не могу заставить его работать. Кто-нибудь сможет помочь?

Parallel.For(
    startY,
    endY,
    y =>
    {
        if (y >= targetY && y < targetBottom)
        {
            Weight[] verticalValues = this.verticalWeights[y].Values;

            for (int x = startX; x < endX; x++)
            {
                Weight[] horizontalValues = this.horizontalWeights[x].Values;

                // Destination color components
                Color destination = new Color();

                // This is where there is too much operation complexity.
                foreach (Weight yw in verticalValues)
                {
                    int originY = yw.Index;

                    foreach (Weight xw in horizontalValues)
                    {
                        int originX = xw.Index;
                        Color sourceColor = Color.Expand(source[originX, originY]);
                        float weight = yw.Value * xw.Value;
                        destination += sourceColor * weight;
                    }
                }

                destination = Color.Compress(destination);
                target[x, y] = destination;
            }
        }
    });

Массы и индексы вычисляются следующим образом. Один для каждого измерения:

/// <summary>
/// Computes the weights to apply at each pixel when resizing.
/// </summary>
/// <param name="destinationSize">The destination section size.</param>
/// <param name="sourceSize">The source section size.</param>
/// <returns>
/// The <see cref="T:Weights[]"/>.
/// </returns>
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
    IResampler sampler = this.Sampler;
    float ratio = sourceSize / (float)destinationSize;
    float scale = ratio;

    // When shrinking, broaden the effective kernel support so that we still
    // visit every source pixel.
    if (scale < 1)
    {
        scale = 1;
    }

    float scaledRadius = (float)Math.Ceiling(scale * sampler.Radius);
    Weights[] result = new Weights[destinationSize];

    // Make the weights slices, one source for each column or row.
    Parallel.For(
        0,
        destinationSize,
        i =>
            {
                float center = ((i + .5f) * ratio) - 0.5f;
                int start = (int)Math.Ceiling(center - scaledRadius);

                if (start < 0)
                {
                    start = 0;
                }

                int end = (int)Math.Floor(center + scaledRadius);

                if (end > sourceSize)
                {
                    end = sourceSize;

                    if (end < start)
                    {
                        end = start;
                    }
                }

                float sum = 0;
                result[i] = new Weights();

                List<Weight> builder = new List<Weight>();
                for (int a = start; a < end; a++)
                {
                    float w = sampler.GetValue((a - center) / scale);

                    if (w < 0 || w > 0)
                    {
                        sum += w;
                        builder.Add(new Weight(a, w));
                    }
                }

                // Normalise the values
                if (sum > 0 || sum < 0)
                {
                    builder.ForEach(w => w.Value /= sum);
                }

                result[i].Values = builder.ToArray();
                result[i].Sum = sum;
            });

    return result;
}

/// <summary>
/// Represents the weight to be added to a scaled pixel.
/// </summary>
protected class Weight
{
    /// <summary>
    /// The pixel index.
    /// </summary>
    public readonly int Index;

    /// <summary>
    /// Initializes a new instance of the <see cref="Weight"/> class.
    /// </summary>
    /// <param name="index">The index.</param>
    /// <param name="value">The value.</param>
    public Weight(int index, float value)
    {
        this.Index = index;
        this.Value = value;
    }

    /// <summary>
    /// Gets or sets the result of the interpolation algorithm.
    /// </summary>
    public float Value { get; set; }
}

/// <summary>
/// Represents a collection of weights and their sum.
/// </summary>
protected class Weights
{
    /// <summary>
    /// Gets or sets the values.
    /// </summary>
    public Weight[] Values { get; set; }

    /// <summary>
    /// Gets or sets the sum.
    /// </summary>
    public float Sum { get; set; }
}

Каждый IResampler предоставляет соответствующую серию весов на основе данного индекса. бикубический повторитель работает следующим образом.

/// <summary>
/// The function implements the bicubic kernel algorithm W(x) as described on
/// <see href="#" onclick="location.href='https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm'; return false;">Wikipedia</see>
/// A commonly used algorithm within imageprocessing that preserves sharpness better than triangle interpolation.
/// </summary>
public class BicubicResampler : IResampler
{
    /// <inheritdoc/>
    public float Radius => 2;

    /// <inheritdoc/>
    public float GetValue(float x)
    {
        // The coefficient.
        float a = -0.5f;

        if (x < 0)
        {
            x = -x;
        }

        float result = 0;

        if (x <= 1)
        {
            result = (((1.5f * x) - 2.5f) * x * x) + 1;
        }
        else if (x < 2)
        {
            result = (((((a * x) + 2.5f) * x) - 4) * x) + 2;
        }

        return result;
    }
}

Вот пример изображения, измененного существующим алгоритмом. Результат правильный (обратите внимание, что серебристый блеск сохраняется).

Исходное изображение

Оригинальное немасштабированное изображение

Изображение уменьшено пополам с использованием бикубического ресамплера.

Изображение пополам по размеру

Этот код является частью гораздо более крупной библиотеки, которую я пишу, чтобы добавить обработку изображений в corefx.

4b9b3361

Ответ 1

Хорошо, так вот, как я это делал.

Хитрость заключается в том, чтобы сначала изменить только ширину изображения, сохраняя высоту так же, как и исходное изображение. Мы сохраняем результирующие пиксели во временном изображении.

Затем это случай изменения размера изображения до нашего окончательного вывода.

Как вы можете видеть, мы больше не итерируем через весовые коллекции на каждом пикселе. Несмотря на то, что в течение второго цикла пикселя повторялся цикл с двумя пикселями, алгоритм был намного быстрее, чем в среднем на 25% быстрее, чем на моих тестовых изображениях.

// Interpolate the image using the calculated weights.
// First process the columns.
Parallel.For(
    0,
    sourceBottom,
    y =>
    {
        for (int x = startX; x < endX; x++)
        {
            Weight[] horizontalValues = this.HorizontalWeights[x].Values;

            // Destination color components
            Color destination = new Color();

            foreach (Weight xw in horizontalValues)
            {
                int originX = xw.Index;
                Color sourceColor = Color.Expand(source[originX, y]);
                destination += sourceColor * xw.Value;
            }

            destination = Color.Compress(destination);
            this.firstPass[x, y] = destination;
        }
    });

// Now process the rows.
Parallel.For(
    startY,
    endY,
    y =>
    {
        if (y >= targetY && y < targetBottom)
        {
            Weight[] verticalValues = this.VerticalWeights[y].Values;

            for (int x = startX; x < endX; x++)
            {
                // Destination color components
                Color destination = new Color();

                foreach (Weight yw in verticalValues)
                {
                    int originY = yw.Index;
                    int originX = x;
                    Color sourceColor = Color.Expand(this.firstPass[originX, originY]);
                    destination += sourceColor * yw.Value;
                }

                destination = Color.Compress(destination);
                target[x, y] = destination;
            }
        }
    });

Ответ 2

Вы можете попробовать взвешенную диаграмму ворона. Попробуйте случайным образом набор точек и вычислить диаграмму voronoi. Сгладьте полигоны с алгоритмом Ллойда и интерполируйте цвет многоугольника. С весом вычислить взвешенную диаграмму ворона. Например, voronoi штриховка и мозаика: http://www.mrl.nyu.edu/~ajsecord/stipples.html и http://www.evilmadscientist.com/2012/stipplegen-weighted-voronoi-stippling-and-tsp-paths-in-processing/.

Ответ 3

Судя по абстрактной точке зрения (не зная много о манипуляции с изображением), я думаю, что это выглядит так, что вы много раз вычисляете значения веса и исходного цвета (в самой внутренней петле foreach) (всякий раз, когда одна и та же пара индексов посевов снова); было бы целесообразно заранее заранее прекомпопировать их? Вам нужно будет вычислить матрицу "прямого продукта" для ваших матриц HorizontalWeight и VerticalWeight (просто умножая значения для каждой пары индексов (x, y)), а также может применить Color.Expand к исходному файлу заранее. Эти задачи могут выполняться параллельно, а "прямой продукт" (извините, я не знаю правильного имени для этого зверя) должен быть доступен в большом количестве библиотек.