una ulam teoría serie residuo mostrar gaussianos explicar dev conjetura como codigo algoritmo c# algorithm image-processing imageprocessor

c# - ulam - Dividir el algoritmo de cambio de tamaño en dos pasos



serie de ulam algoritmo (3)

He escrito el siguiente algoritmo de cambio de tamaño que puede escalar correctamente una imagen hacia arriba o hacia abajo. Sin embargo, es demasiado lento debido a la iteración interna a través del conjunto de pesos en cada ciclo.

Estoy bastante seguro de que podría dividir el algoritmo en dos pases, como lo haría con un desenfoque Gaussiano de dos pasadas, que reduciría enormemente la complejidad operativa y aceleraría el rendimiento. Desafortunadamente no puedo hacer que funcione. ¿Alguien podría ayudar?

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; } } });

Los pesos y los índices se calculan de la siguiente manera. Uno para cada dimensión:

/// <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; } }

Cada IResampler proporciona las series de ponderaciones apropiadas según el índice dado. el remuestreador bicúbico funciona de la siguiente manera.

/// <summary> /// The function implements the bicubic kernel algorithm W(x) as described on /// <see href="https://en.wikipedia.org/wiki/Bicubic_interpolation#Bicubic_convolution_algorithm">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; } }

Aquí hay un ejemplo de una imagen redimensionada por el algoritmo existente. La salida es correcta (tenga en cuenta que el brillo plateado se conserva).

Imagen original

La imagen se redujo a la mitad usando el remuestreador bicúbico.

El código es parte de una library mucho más grande que estoy escribiendo para agregar procesamiento de imágenes a corefx.


A juzgar por un punto de vista abstracto (sin saber mucho sobre manipulación de imágenes), creo que parece que estás calculando los valores de peso y color de fuente (en el ciclo foreach más interno) varias veces (cada vez que aparece el mismo par de índices) ; ¿Sería factible simplemente precalcularlos de antemano? Necesitaría calcular una Matriz de "producto directo" para sus matrices de Ponderación Horizontal y Peso Vertical (simplemente multiplicando los valores para cada par de índices (x, y)) y también podría aplicar el Color.Expandir a la fuente con antelación. Estas tareas pueden realizarse en paralelo y el ''producto directo'' (lo siento, no conozco el nombre correcto para esa bestia) debería estar disponible en muchas bibliotecas.


Ok así que así es como lo hice.

El truco es cambiar primero el tamaño del ancho de la imagen manteniendo la altura igual a la imagen original. Almacenamos los píxeles resultantes en una imagen temporal.

Entonces, se trata de cambiar el tamaño de esa imagen a nuestro resultado final.

Como puede ver, ya no estamos iterando a través de ambas colecciones de peso en cada píxel. A pesar de tener que iterar dos veces el bucle de píxel externo, el algoritmo fue mucho más rápido en su operación con un promedio de alrededor del 25% más rápido en mis imágenes de prueba.

// 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; } } });