3 votes

Quel est le problème avec cet algorithme de conversion en sépia?

Il semble que j'ai une teinte sépia qui fonctionne presque correctement. Pour une raison quelconque, une partie de l'image se révèle être vert citron! Est-ce que quelqu'un sait ce que je pourrais faire de mal? La méthode est affichée ci-dessous.

 private void SepiaBitmap(Bitmap bmp)
{
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
        System.Drawing.Imaging.PixelFormat.Format32bppRgb);

    IntPtr ptr = bmpData.Scan0;

    int numPixels = bmpData.Width * bmp.Height;
    int numBytes = numPixels * 4;
    byte[] rgbValues = new byte[numBytes];

    System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, numBytes);
    for (int i = 0; i < rgbValues.Length; i += 4)
    {
        rgbValues[i + 2] = (byte)((.393 * rgbValues[i + 2]) + (.769 * rgbValues[i + 1]) + (.189 * (rgbValues[i + 0]))); //red
        rgbValues[i + 1] = (byte)((.349 * rgbValues[i + 2]) + (.686 * rgbValues[i + 1]) + (.168 * (rgbValues[i + 0]))); //green
        rgbValues[i + 0] = (byte)((.272 * rgbValues[i + 2]) + (.534 * rgbValues[i + 1]) + (.131 * (rgbValues[i + 0]))); //blue

        if ((rgbValues[i + 2]) > 255)
        {
            rgbValues[i + 2] = 255; 
        }

        if ((rgbValues[i + 1]) > 255)
        {
            rgbValues[i + 1] = 255;
        }
        if ((rgbValues[i + 0]) > 255)
        {
            rgbValues[i + 0] = 255;
        }
    }

    System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
    this.Invalidate();
    bmp.UnlockBits(bmpData);

}

OriginalSépia

6voto

Max Galkin Points 10116

Vous avez 2 problèmes dans votre algo (au moins, si vous suivez la description de l'algo à partir ici).

Premièrement, comme d'autres l'ont souligné, vous avez un débordement de type byte. Deuxièmement, toutes les valeurs de couleur de sortie doivent être basées sur les valeurs de couleur d'entrée, et non calculées séquentiellement.

Voici le code de la boucle principale corrigé :

        for (int i = 0; i < rgbValues.Length; i += 4)
        {
            int inputRed = rgbValues[i + 2];
            int inputGreen = rgbValues[i + 1];
            int inputBlue = rgbValues[i + 0];

            rgbValues[i + 2] = (byte) Math.Min(255, (int)((.393 * inputRed) + (.769 * inputGreen) + (.189 * inputBlue))); //rouge
            rgbValues[i + 1] = (byte) Math.Min(255, (int)((.349 * inputRed) + (.686 * inputGreen) + (.168 * inputBlue))); //vert
            rgbValues[i + 0] = (byte) Math.Min(255, (int)((.272 * inputRed) + (.534 * inputGreen) + (.131 * inputBlue))); //bleu
        }

Remarquez qu'à l'intérieur de la fonction Min, je change la valeur de couleur de double en int sinon la surcharge Min(double, double) est appelée et 255 est d'abord converti en double puis éventuellement en byte, impliquant un arrondi supplémentaire.

Si quelqu'un a besoin d'un convertisseur sepia dans une application console, voici le code final que j'ai :

namespace ConsoleApplication8_Sepia
{
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;

    class Program
    {
        static void Main(string[] args)
        {
            Bitmap b = (Bitmap)Bitmap.FromFile("c:\\temp\\source.jpg");
            SepiaBitmap(b);
            b.Save("c:\\temp\\destination.jpg", ImageFormat.Jpeg);
        }

        private static void SepiaBitmap(Bitmap bmp)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppRgb);
            IntPtr ptr = bmpData.Scan0;

            int numPixels = bmpData.Width * bmp.Height;
            int numBytes = numPixels * 4;
            byte[] rgbValues = new byte[numBytes];

            System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, numBytes);
            for (int i = 0; i < rgbValues.Length; i += 4)
            {
                int inputRed = rgbValues[i + 2];
                int inputGreen = rgbValues[i + 1];
                int inputBlue = rgbValues[i + 0];

                rgbValues[i + 2] = (byte)Math.Min(255, (int)((.393 * inputRed) + (.769 * inputGreen) + (.189 * inputBlue))); //rouge
                rgbValues[i + 1] = (byte)Math.Min(255, (int)((.349 * inputRed) + (.686 * inputGreen) + (.168 * inputBlue))); //vert
                rgbValues[i + 0] = (byte)Math.Min(255, (int)((.272 * inputRed) + (.534 * inputGreen) + (.131 * inputBlue))); //bleu
            }

            System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
            bmp.UnlockBits(bmpData);
        }
    }
}

4voto

Yuriy Guts Points 2142

Pour résoudre le problème, modifiez la boucle comme ceci :

for (int i = 0; i < rgbValues.Length; i += 4)
{
    int red = rgbValues[i + 2];
    int green = rgbValues[i + 1];
    int blue = rgbValues[i + 0];

    rgbValues[i + 2] = (byte)Math.Min((.393 * red) + (.769 * green) + (.189 * blue), 255.0); // rouge
    rgbValues[i + 1] = (byte)Math.Min((.349 * red) + (.686 * green) + (.168 * blue), 255.0); // vert
    rgbValues[i + 0] = (byte)Math.Min((.272 * red) + (.534 * green) + (.131 * blue), 255.0); // bleu
}

Des débordements arithmétiques se produisent dans vos calculs, c'est la raison pour laquelle les couleurs sont incorrectes. Une expression de type double est explicitement convertie en byte avant d'être comparée à 255, donc elle ne dépassera jamais 255.

3voto

Kevin Reid Points 8806

Vos valeurs débordent et s'enroulent autour.

Votre tentative de protéger contre cela avec (rgbValues[i + 0]) > 255 n'a aucun effet parce qu'un byte[] ne peut pas stocker des valeurs supérieures à 255 de toute façon, donc les valeurs ont débordé et se sont enroulées dès que vous les avez mises dans rgbValues. Vous devez les pincer avant de les stocker dans le tableau. C# dispose d'une fonction Math.Min() qui serait excellente à cette fin.

D'autre part, étant donné que vous obtenez un dépassement, vous voudrez probablement corriger cela en premier lieu - le pincement créera un effet de "sur-exposition" (parce que la sur-exposition est le pincement), ce qui est probablement indésirable. Ajustez vos coefficients de sorte que vous changiez la couleur sans changer la luminosité (perçue) (je n'ai pas de référence pour cela ; désolé).

Comme un problème totalement distinct, comme l'a noté @Yacoder, votre première ligne modifie les entrées que la deuxième utilise, et ainsi de suite, donc votre calcul sera erroné. Vous devez soit les trois entrées soit les trois sorties dans des variables temporaires.

Vous voudrez peut-être également voir si System.Drawing.Imaging dispose d'une opération de transformation d'image par matrice de couleurs, car c'est ce que vous faites à la main ici, et la version fournie par le système sera probablement plus rapide. (Je ne connais pas C# donc je ne peux pas commenter là-dessus.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X