简体   繁体   English

这种棕褐色调转换算法有什么问题?

[英]What is wrong with this sepia tone conversion algorithm?

I seem to have a sepia tone that is almost working properly. 我似乎有一种几乎正常工作的棕褐色调。 For some reason a portion of the image turns out to be lime green! 由于某种原因,图像的一部分原来是柠檬绿! Does anyone know what i might be doing wrong? 有谁知道我可能做错了什么? Method is posted below. 方法发布在下面。

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

}

原版的乌贼

You have 2 problems in your algo (at least, if you follow algo description from here ). 您的算法中有2个问题(至少,如果您从此处遵循算法描述)。

First, as others pointed out, you have byte type overflow. 首先,正如其他人所指出的,你有字节类型溢出。 Second, all your output color values must be based on the input color values, not calculated sequentially. 其次,所有输出颜色值必须基于输入颜色值,而不是按顺序计算。

Here's the fixed main loop code: 这是固定的主循环代码:

        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
        }

Note that inside the Min function I cast the color value from double to int otherwise Min(double, double) overload is called and 255 gets first converted to double and then possibly back to byte, involving an extra rounding. 请注意,在Min函数内部,我将颜色值从doubleint否则调用Min(double, double)重载,255首先转换为double,然后可能返回到byte,包括额外的舍入。

In case if someone needs a sample console app sepia converter, here's the final code I have: 如果有人需要一个示例控制台应用程序棕褐色转换器,这里是我的最终代码:

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

To fix the issue, change the loop like this: 要解决此问题,请更改此循环:

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
}

There are arithmetic overflows occurring in your calculations, that's the reason for the wrong colors. 计算中出现算术溢出,这就是错误颜色的原因。 An expression of type double is being explicitly cast to byte before it is compared to 255, therefore it will never be greater than 255. double类型的表达式与255进行比较之前被显式地转换为byte ,因此它永远不会大于255。

Your values are overflowing and wrapping around. 你的价值观四溢,四处乱窜。

Your attempt to protect against this with (rgbValues[i + 0]) > 255 has no effect because a byte[] cannot store values over 255 anyway , so the values overflowed and wrapped as soon as you put them in rgbValues . 您尝试使用(rgbValues[i + 0]) > 255来防止此操作无效, 因为无论如何byte[]无法存储超过255的值,因此只要将它们放入rgbValues ,这些值就会溢出并包装。 You need to clamp them before storing them in the array. 将它们存储在阵列中之前,您需要将它们钳制起来。 C# has a function Math.Min() that would be excellent for this purpose. C#有一个函数Math.Min() ,非常适合这个目的。

On the other hand, given that you are getting overflow, you probably want to fix that in the first place — clamping will create an "overexposure" effect (because overexposure is clamping), which is probably undesirable. 另一方面,鉴于您正在溢出,您可能想要首先解决这个问题 - 夹紧会产生“过度曝光”效应(因为过度曝光是夹紧),这可能是不可取的。 Adjust your coefficients so that you are changing the color but not changing the (perceived) brightness (I don't have a reference for this; sorry). 调整系数,以便改变颜色,但不改变(感知)亮度(我没有参考;对不起)。

As an entirely separate problem, as noted by @Yacoder, your first line modifies the inputs the second one uses, and so on, so your calculation will be off. 作为一个完全独立的问题,如@Yacoder所述,您的第一行修改了第二行使用的输入,依此类推,因此您的计算将会关闭。 You need to either the three inputs or the three outputs in temporary variables. 您需要临时变量中的三个输入或三个输出。

You might also want to look into whether System.Drawing.Imaging has a color matrix image transformation operation, because that's what you're doing by hand here, and the system-provided version will probably be faster. 您可能还想查看System.Drawing.Imaging是否具有颜色矩阵图像转换操作,因为这是您在这里手动执行的操作,系统提供的版本可能会更快。 (I don't know C# so I can't comment on that.) (我不知道C#所以我不能对此发表评论。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM