组合灰度和彩色图像
用于制作组合灰度和彩色图像的程序

引言
Macromedia Fireworks 软件中,我花费数小时以 800% 的放大倍率使用套索工具来创建混合灰度与彩色图像,后来我厌倦了这种低效的方式,认为一定有更好的办法。因此,我着手创建了这个应用程序,以让我的生活,以及希望他人的生活,变得更轻松。这是我的初次尝试,如果我收到积极的反馈,表示其他人确实想使用这个应用程序,我将在时间允许的情况下扩展其功能。
背景
此应用程序使用不安全代码和指针进行图像处理。请查看 CodeProject 上 Christian Graus 的“图像处理入门”系列文章,以良好地理解这项技术。此外,在选择图像区域时,我使用了 CodeProject 上 q123456789 关于“蚂蚁行进的秘密”这篇文章中的代码。
Using the Code
大多数图像包含三个通道:红色、绿色和蓝色。有时会使用第四个“alpha”通道来处理透明度,但这与本文无关。每个通道为每个像素包含一个 0 到 255 之间的值,共计 16777216 种可能的颜色。因此,选择要保留在图像中的颜色不像仅仅选择一个像素并将所有不匹配该颜色的其他像素转换为灰度那样简单。相反,必须使用一定的容差级别。但是,这个级别会根据图像中颜色的差异程度以及用户想要保留的颜色而有很大差异。
我发现处理此问题的更有效方法之一是使用像素颜色之间的比率值。一旦用户选择了一个他们想要保留的图像像素,程序将计算红色和绿色通道、红色和蓝色通道以及绿色和蓝色通道之间的值差异。然后,这个比率可以用于整个图像,以查找相似颜色的像素,即使亮度级别可能非常不同。例如,这将匹配值为红 = 50、绿 = 80、蓝 = 110 的像素与值为红 = 180、绿 = 210、蓝 = 240 的像素,因为在每组像素中,红和绿之间的差异均为 30 等。
然而,即使使用上述比率方法,仍然无法匹配很多像素,最多只有 256 个。为了获得合理的颜色选择,必须在每个通道差异上使用误差值。因此,该程序提供了三个文本框,供用户为红绿差异、红蓝差异以及绿蓝差异提供误差值。通过将此误差值添加到差异数字并从中减去,可以包含更广泛的颜色范围。例如,如果红色和绿色像素之间的差异为 25,用户输入误差值为 20,则红色和绿色通道之间差异在 5 到 45 之间的所有像素都将在此范围内。
另一个有用的功能是,很多时候颜色会出现在图像的多个位置,而用户只想在某个区域保留它。因此,需要提供一些边界,允许用户选择图像中的一个小区域以保留彩色。在此示例程序中,我只添加了简单的矩形代码,尽管它可以扩展到任何形状。我仅实现了 CodeProject 上“蚂蚁行进的秘密”程序的绘图部分,以便用户可以在绘制矩形时看到它。这使得图像必须直接绘制在窗体上,而不是使用可以轻松添加滚动条的 picturebox。话虽如此,这是程序中基本的图像处理功能。
private void GrayColor()
{
int height = user_image.Height;
int width = user_image.Width;
// calculate difference between each color in the selected pixel.
// Note: Do not use absolute value because each pixel in the image
// code below is checked in the same manner.
int diffRG, diffRB, diffGB;
diffRG = pixcolor.R - pixcolor.G;
diffRB = pixcolor.R - pixcolor.B;
diffGB = pixcolor.G - pixcolor.B;
// setup color acceptance range
int RGlow, RGhigh, RBlow, RBhigh, GBlow, GBhigh, xmin, xmax, ymin, ymax;
RGlow = diffRG - Convert.ToInt16(tbRG.Text); // user pixel value range
RGhigh = diffRG + Convert.ToInt16(tbRG.Text);
RBlow = diffRB - Convert.ToInt16(tbRB.Text);
RBhigh = diffRB + Convert.ToInt16(tbRB.Text);
GBlow = diffGB - Convert.ToInt16(tbGB.Text);
GBhigh = diffGB + Convert.ToInt16(tbGB.Text);
// user selection limits
xmin = rect.X;
xmax = xmin + rect.Width;
ymin = rect.Y;
ymax = ymin + rect.Height;
BitmapData bmData = user_image.LockBits(new Rectangle(0, 0, width,
height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
System.IntPtr Scan0 = bmData.Scan0;
int nOffset = bmData.Stride - width * 3;
//blue is pixel[0], green is pixel[1], and red is pixel[2]
unsafe
{
byte* p = (byte*)(void*)Scan0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
// if the pixel color ratio is within ratio of selected area,
// not in separate statements because we don't care which
// evaluation is not true
if (x > xmin && x < xmax && y > ymin && y < ymax &&
p[2] - p[1] > RGlow && p[2] - p[1] < RGhigh &&
p[2] - p[0] > RBlow && p[2] - p[0] < RBhigh &&
p[1] - p[0] > GBlow && p[1] - p[0] < GBhigh)
{
img_array_backup[y, x] = true;
}
p += 3;
}
p += nOffset;
}
}
user_image.UnlockBits(bmData);
}
请注意,它不会直接修改位图,而是将用户指定的误差限制内匹配的像素在布尔二维数组上设置为 `true`。此数组用作蒙版;所有值为 `true` 的像素将保留彩色,否则它们将被转换为灰度颜色。这使得函数可以对同一图像多次运行,从而允许用户在生成最终图像之前选择多种颜色。
现在我们有了一种简单的方法来选择要影响的图像颜色和区域,现在我们需要一种简单的方法让用户理解他们已选择和未选择的内容。同样,有几种不同的方法可以做到这一点。我只是将用户的图像和上一步创建的蒙版传递到一个新窗体。用户可以选择撤销颜色选择、添加另一种颜色或接受最终图像。添加另一种颜色时,它会反转蒙版,以便用户看到已选择的颜色现在是灰度的。一旦用户对图像满意,将运行以下代码来创建最终图像。
private void DrawImage()
{
int width = user_image.Width;
int height = user_image.Height;
BitmapData bmdata = user_image.LockBits(new Rectangle(0, 0, width,
height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
System.IntPtr Scan0 = bmdata.Scan0;
int nOffset = bmdata.Stride - width * 3;
unsafe
{
byte* p = (byte*)(void*)Scan0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
if (!img_array[y, x]) // convert to grayscale
{
p[2] = p[1] = p[0] = (byte)(.299 * p[2] + .587 * p[1]
+ .114 * p[0]);
}
p += 3;
}
p += nOffset;
}
}
user_image.UnlockBits(bmdata);
DrawBackground(); // refresh the image
}
使用演示应用程序
演示应用程序包含一个简单的菜单栏,允许用户打开和保存图像,以及放大或缩小当前打开的图像。要选择颜色,请单击左侧的十字准星按钮,然后在图像中单击颜色。红色、绿色和蓝色标签将显示所选颜色的像素值。可以通过三个文本框更改误差量。如果您希望仅将其应用于图像的一小部分区域,请选择虚线矩形按钮并在图像上绘制区域。如果未选择区域,则颜色将应用于整个图像。做出选择后,单击“应用”按钮。具有所选颜色的图像将显示在新窗体中。请注意,新窗体上的缩放控件是通过上下文菜单实现的,并且由于图像绘制在面板内的 picturebox 上,因此现在可以使用滚动条。如果您选择的颜色影响了您不想要的图像区域或颜色,请选择“重新定义颜色”按钮。如果您想添加更多颜色,请选择“添加颜色”按钮,它将返回到原始窗体,并显示您的图像,其中已选择的颜色现在为灰度。请注意,您需要再次选择要影响的图像区域。如果图像完全符合您的要求,请选择“创建图像”按钮,它将返回到主窗体并显示最终图像。
历史
- 2008年1月25日:初始发布