【问题标题】:What is wrong with this sepia tone conversion algorithm?这个棕褐色调转换算法有什么问题?
【发布时间】:2012-03-15 22:47:26
【问题描述】:

我似乎有一种几乎可以正常工作的棕褐色调。由于某种原因,图像的一部分变成了石灰绿色!有谁知道我可能做错了什么?方法贴在下面。

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

}

【问题讨论】:

  • 请不要在标题前加上“C#”之类的前缀。这就是标签的用途。
  • @KevinReid 抱歉,发帖有点慢!

标签: c# .net colors


【解决方案1】:

要解决此问题,请按如下方式更改循环:

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。

【讨论】:

  • 这是不完整的,不会产生棕褐色调的图像。
  • 每个颜色分量都需要单独转换,看我的回答。
  • 啊,是的,你是对的。对不起,我第一次没有注意到。我编辑了我的答案,并为更正 +1 :)
【解决方案2】:

您的算法有 2 个问题(至少,如果您遵循 here 的算法描述)。

首先,正如其他人指出的那样,您有字节类型溢出。 其次,您所有的输出颜色值都必须基于输入颜色值,而不是按顺序计算。

这是固定的主循环代码:

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

【讨论】:

  • 您可以使用255.0 明确声明 255 是双精度数。
  • @YuriyGuts:我不想让它翻倍,我想将 255 从整数类型转换为字节,所以我可以依赖该转换在字节范围内。跨度>
【解决方案3】:

你的价值观正在溢出和环绕。

您尝试使用(rgbValues[i + 0]) &gt; 255 来防止这种情况没有效果因为byte[] 无论如何都无法存储超过 255 的值,因此一旦将它们放入 @987654323 中,值就会溢出并包装@。您需要在 将它们存储在数组中之前将它们夹住。 C# 有一个函数 Math.Min() 非常适合此目的。

另一方面,鉴于您正在溢出,您可能希望首先解决这个问题 - 钳制会产生“过度曝光”效果(因为过度曝光就是钳制),这可能是不可取的。调整你的系数,以便你改变颜色但不改变(感知的)亮度(我没有这方面的参考;抱歉)。

正如@Yacoder 所指出的,作为一个完全独立的问题,您的第一行修改了第二行使用的输入,依此类推,因此您的计算将被关闭。您需要临时变量中的三个输入或三个输出。

您可能还想查看System.Drawing.Imaging 是否有颜色矩阵图像转换操作,因为这是您在此处手动执行的操作,系统提供的版本可能会更快. (我不懂 C#,所以无法对此发表评论。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 2020-08-19
    • 2011-05-07
    • 1970-01-01
    • 2011-10-01
    • 1970-01-01
    相关资源
    最近更新 更多