Napisałem poniższy algorytm zmiany rozmiaru, który może poprawnie skalować obraz w górę lub w dół. Jest jednak zbyt powolny ze względu na wewnętrzną iterację poprzez szereg wag w każdej pętli.Algorytm podziału rozmiaru na dwa przebiegi
Jestem prawie pewien, że powinienem móc podzielić algorytm na dwa przebiegi, podobnie jak w przypadku dwuprzebiegowego rozmycia Gaussa, które znacznie zmniejszyłoby złożoność operacyjną i przyspieszyło działanie. Niestety nie mogę go uruchomić. Czy ktoś mógłby pomóc?
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;
}
}
});
Masy i wskaźniki obliczane są w następujący sposób. Jeden dla każdego wymiaru:
/// <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; }
}
Każdy IResampler dostarcza odpowiednią serię wag na podstawie danego indeksu. bimubowy resampler działa w następujący sposób.
/// <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;
}
}
Oto przykład obrazu skalowanego przez istniejący algorytm. Wynik jest poprawny (zauważ, że zachowano srebrzysty połysk).
oryginalny obraz
Obraz o połowę wielkości używając Bicubic Resampler.
Kod jest częścią znacznie większy library że piszę do przetwarzania obrazu, aby dodać corefx.
jakie są indeksy i wartości masy? bez niego nie ma sposobu, aby obliczyć podziały rekursji ... inne niż brutalna siła ... i tylko jeśli niektóre właściwości są spełnione ... również dobrym pomysłem byłoby udostępnienie przykładowego obrazu przed i po filtrowaniu, więc jest coś do porównaj z ... – Spektre
@Spektre Dodałem dużo więcej informacji do pytania. Jeśli potrzebujesz jeszcze czegoś, daj mi znać. –
cóż, jest to dwu-sześcienny (nie jest to arbitralny splot, który, jak zakładałem z twojego kodu, początkowo niezbyt głęboki), nie można go rozwiązać za pomocą podziału rekurencyjnego. Zamiast tego można to rozdzielić na problem 2x1D, tak jak planujesz, ale nie jestem pewien, czy będzie on szybszy (na dzisiejszych komputerach), a następnie bezpośrednie ponowne próbkowanie 2D. Muszę zrobić kilka testów na ten temat, ale nie będę miał na to czasu aż do poniedziałku. – Spektre