[英]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函数内部,我将颜色值从double
为int
否则调用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.