c# दो पास में एल्गोरिदम का आकार बदलें




algorithm image-processing (3)

मैंने निम्नलिखित आकार बदलने वाले एल्गोरिदम लिखा है जो छवि को ऊपर या नीचे सही ढंग से स्केल कर सकता है। यह बहुत धीमी है हालांकि प्रत्येक पाश पर वजन की सरणी के माध्यम से आंतरिक पुनरावृत्ति के कारण।

मैं काफी हद तक निश्चित हूं कि मुझे एल्गोरिदम को दो पासों में विभाजित करने में सक्षम होना चाहिए जैसा कि आप दो पास गॉसियन ब्लर के साथ करेंगे जो परिचालन जटिलता को काफी कम करेगा और प्रदर्शन को तेज करेगा। दुर्भाग्य से मैं इसे काम नहीं कर सकता। क्या कोई मदद करने में सक्षम होगा?

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

प्रत्येक आईरसप्लर दिए गए इंडेक्स के आधार पर वजन की उचित श्रृंखला प्रदान करता है। निम्नानुसार bicubic resampler काम करता है।

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

मौजूदा एल्गोरिदम द्वारा आकार की गई छवि का एक उदाहरण यहां दिया गया है। आउटपुट सही है (नोट करें कि चांदी की शीन संरक्षित है)।

मूल छवि

छवि bicubic resampler का उपयोग कर आकार में आधा।

यह कोड एक बड़ी library का हिस्सा है जिसे मैं कोरफैक्स में छवि प्रसंस्करण जोड़ने के लिए लिख रहा हूं।


ठीक है तो यहां मैं इसके बारे में कैसे चला गया।

यह चाल पहली बार छवि की चौड़ाई को आकार देने के लिए मूल छवि के समान ही है। हम परिणामस्वरूप पिक्सल को अस्थायी छवि में संग्रहीत करते हैं।

फिर यह उस छवि को हमारे अंतिम आउटपुट में आकार देने का मामला है।

जैसा कि आप देख सकते हैं कि हम अब प्रत्येक पिक्सेल पर वजन संग्रह दोनों के माध्यम से पुनरावृत्त नहीं कर रहे हैं। पुनरावृत्ति होने के बावजूद बाहरी पिक्सेल लूप दो बार एल्गोरिदम अपने परीक्षण छवियों में लगभग 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;
            }
        }
    });

एक अमूर्त दृष्टिकोण से निर्णय (छवि कुशलता के बारे में ज्यादा नहीं जानते) मुझे लगता है कि ऐसा लगता है कि आप वजन और स्रोत रंग (सबसे निचले फोरैच लूप में) के मानों की गणना कर रहे हैं (जब भी सूचकांक की एक ही जोड़ी फिर से फसल हो जाती है) ; क्या उन्हें पहले से ही प्रीकंप्यूट करना संभव होगा? आपको अपने क्षैतिज वेइट और वर्टिकल वेइट मैट्रिक्स के लिए 'प्रत्यक्ष उत्पाद' मैट्रिक्स की गणना करने की आवश्यकता होगी (केवल इंडेक्स की प्रत्येक जोड़ी (x, y) के मानों को गुणा करना) और रंग को भी लागू कर सकते हैं। स्रोत को अग्रिम में एक्सपैंड करें। इन कार्यों को समानांतर और 'प्रत्यक्ष उत्पाद' (माफ करना, मुझे उस जानवर के लिए सही नाम नहीं पता) में किया जा सकता है, कई पुस्तकालयों में उपलब्ध होना चाहिए।


आप एक भारित voronoi आरेख कोशिश कर सकते हैं। यादृच्छिक रूप से अंक का एक सेट आज़माएं और voronoi आरेख की गणना करें। लॉयड के एल्गोरिदम के साथ बहुभुज को चिकना करें और बहुभुज के रंग को अलग करें। भार भारित voronoi आरेख गणना करें। उदाहरण के लिए voronoi stippling और मोज़ेक: http://www.mrl.nyu.edu/~ajsecord/stipples.html और http://www.evilmadscientist.com/2012/stipplegen-weighted-voronoi-stippling-and-tsp-paths-in-processing/





imageprocessor