Что не так с этим алгоритмом преобразования тонов сепии? - PullRequest
2 голосов
/ 26 февраля 2012

Кажется, у меня сепия, которая почти работает нормально. По какой-то причине часть изображения оказывается светло-зеленой! Кто-нибудь знает, что я могу делать неправильно? Метод размещен ниже.

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

}

Original Sepia

Ответы [ 3 ]

6 голосов
/ 26 февраля 2012

У вас есть 2 проблемы в вашем алгоритме (по крайней мере, если вы следуете описанию алгоритма из здесь ).

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

Вот фиксированный код основного цикла:

        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))); //red
            rgbValues[i + 1] = (byte) Math.Min(255, (int)((.349 * inputRed) + (.686 * inputGreen) + (.168 * inputBlue))); //green
            rgbValues[i + 0] = (byte) Math.Min(255, (int)((.272 * inputRed) + (.534 * inputGreen) + (.131 * inputBlue))); //blue
        }

Обратите внимание, что внутри функции Min я приведузначение цвета от double до int в противном случае вызывается перегрузка Min(double, double), и 255 сначала преобразуется в удвоение, а затем, возможно, обратно в байт, включая дополнительное округление.

В случае, если кому-то нужен образецКонвертер-конвертер сепии, вот последний код, который у меня есть:

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))); //red
                rgbValues[i + 1] = (byte)Math.Min(255, (int)((.349 * inputRed) + (.686 * inputGreen) + (.168 * inputBlue))); //green
                rgbValues[i + 0] = (byte)Math.Min(255, (int)((.272 * inputRed) + (.534 * inputGreen) + (.131 * inputBlue))); //blue
            }

            System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, numBytes);
            bmp.UnlockBits(bmpData);
        }
    }
}
5 голосов
/ 26 февраля 2012

Чтобы исправить проблему, измените цикл следующим образом:

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); // red
    rgbValues[i + 1] = (byte)Math.Min((.349 * red) + (.686 * green) + (.168 * blue), 255.0); // green
    rgbValues[i + 0] = (byte)Math.Min((.272 * red) + (.534 * green) + (.131 * blue), 255.0); // blue
}

В ваших вычислениях возникают арифметические переполнения, что является причиной неправильных цветов. Выражение типа double явно приводится к byte до того, как сравнивается с 255, поэтому оно никогда не будет больше 255.

2 голосов
/ 26 февраля 2012

Ваши значения переполнены и окружают.

Ваша попытка защититься от этого с помощью (rgbValues[i + 0]) > 255 не имеет никакого эффекта , потому что byte[] не может хранить значения свыше 255 в любом случае , поэтому значения переполняются и переносятся, как только вы помещаете их в rgbValues , Вам необходимо зафиксировать их перед тем, как сохранить их в массиве. В C # есть функция Math.Min(), которая отлично подходит для этой цели.

С другой стороны, учитывая, что вы получаете переполнение, вы, возможно, захотите исправить это в первую очередь - зажим создаст эффект "передержки" (потому что переэкспонирование - это зажим), что, вероятно, нежелательно. Настройте свои коэффициенты так, чтобы вы меняли цвет, но не меняли (воспринимаемую) яркость (у меня нет ссылки на это; извините).

Как совершенно отдельная проблема, как отмечает @Yacoder, ваша первая строка изменяет входные данные, используемые второй, и т. Д., Так что ваш расчет будет отключен. Вам нужно либо три входа, либо три выхода во временных переменных.

Возможно, вы также захотите узнать, имеет ли System.Drawing.Imaging операцию преобразования изображения color matrix , потому что это то, что вы делаете здесь вручную, и предоставленная системой версия, вероятно, будет быстрее. (Я не знаю C #, поэтому я не могу это комментировать.)

...