灰度图像的线性对比度拉伸






4.29/5 (9投票s)
一个实现 8 位灰度图像分段线性对比度拉伸的程序(C#)。
引言
图像处理操作会处理图像的单个像素以生成另一张图像。点运算将输入图像中的每个像素转换为输出像素,其中四种此类运算已在我们 之前的文章 中进行了描述。在本文中,我们实现了一个分段线性对比度拉伸操作,包含三个线性段。我们专注于 8 位灰度图像,每像素位深为 8 位,其中最小和最大像素值分别为 0 和 255。
我们发布本文的目的是在一个简单易懂的应用程序中演示一个图像处理(对比度拉伸)操作。我们的目标是包含少量简单的类(不超过十个类),并且不包含任何外部依赖;从而使其成为一个具有特定目的的独立应用程序。
分段线性对比度拉伸
基本变换如下图所示。在该图中,横轴 r 代表输入像素值,纵轴 s 代表输出像素值。可以看出,有三条直线段用于将输入像素转换为其对应的输出像素值。换句话说,从输入像素值到输出像素值的转换是通过图中所示的分段线性配置文件进行的。指定对比度拉伸映射的参数是四个值 r2、s2、r3、s3,它们决定了中间直线段的位置。修改这四个值中的任何一个都会修改对比度拉伸变换。r1、s1、r4、s4 的值是固定的。
所附的应用程序允许用户修改这些值,并相应地显示结果图像。
软件设计与代码
开发此应用程序所使用的语言是 C#,开发工具是 Visual Studio 2008 Express Edition。
所需的软件功能包括:
- 打开并显示图像。由于 8 位灰度图像可能不易获得,因此需要额外的功能将 24 位或 32 位彩色图像转换为灰度。用于此转换的公式是
grayscaleValue = 0.3 * red + 0.59 * green + 0.11 * blue
。 - 绘制分段线性对比度拉伸配置文件。
- 提供用户修改配置文件参数的设施。
- 根据配置文件更新图像,并相应地显示更新后的图像。
- 允许用户查看对应于当前显示配置文件的 LUT 值。
- 保存更新后的图像。
为了加快图像更新速度,使用了查找表。此查找表的条目数固定为 256,与图像大小无关。
该软件设计为使用可重用的图形控件。这里使用了两个可重用的图形控件(其中一个是从头开始开发的):
ImagePanelControl
- 用于显示 8 位灰度图像,该控件从我们上面引言中引用的早期文章中重用。ContrastStretchControl
- 用于显示分段线性对比度拉伸配置文件。此控件旨在显示线性配置文件,并允许用户拖动中间线段的端点。中间线段末端的红色小矩形(在鼠标按下和移动时)可以被移动到矩形区域内,以改变线的方向/位置。使用双缓冲来消除鼠标移动过程中显示线段时的任何闪烁;为此,所有绘图都在后台缓冲区进行,然后后台缓冲区的内容最终被传输到控件的主图形。这 بالإضافة 设置控件的DoubleBuffered
属性为true
。鼠标按钮释放时,此控件将计算对应于配置文件的查找表。查找表的计算可能是此控件最重要的功能,其代码如下所示。在三个直线段区域中的每个区域中,使用不同的公式来确定映射。当任何分母值变为零时,会采取特殊处理。
int r2, r3, r4;
double s2, s3, s4;
byte lookUpTable = new byte[256];
public void ComputeLookUpTable()
{
int i;
if ((p4.X - p1.X) != 0)
{
double p41x = 255.0 / (p4.X - p1.X);
double p41y = 255.0 / (p4.Y - p1.Y);
byte b = 0;
r2 = Convert.ToInt32((p2.X - p1.X) * p41x);
r3 = Convert.ToInt32((p3.X - p1.X) * p41x);
s2 = (p2.Y - p1.Y) * p41y;
s3 = (p3.Y - p1.Y) * p41y;
int r32 = r3 - r2;
int r43 = r4 - r3;
double s32 = s3 - s2;
double s43 = s4 - s3;
double factor1 = 0.0, factor2 = 0.0;
if (r32 != 0) factor1 = s32 / r32;
if (r43 != 0) factor2 = s43 / r43;
for (i = 0; i < 256; ++i)
{
if (i <= r2)
{
if (r2 == 0)
b = Convert.ToByte(s2);
else
b = Convert.ToByte(i * s2 / r2);
}
else if ((r2 < i) && (i <= r3))
{
if (r32 == 0)
b = Convert.ToByte(s3);
else
b = Convert.ToByte(s2 + factor1 * (i - r2));
}
else // i > r3
{
if (r43 == 0)
b = Convert.ToByte(s4);
else
b = Convert.ToByte(s3 + factor2 * (i - r3));
}
lookUpTable[i] = b;
}
}
主窗体将不同的控件连接在一起,并将它们集成到单个应用程序中。
有趣的效果
使用这种对比度拉伸类型可以获得一些有趣的效果。
例如,使用下图所示的配置文件,可以对图像进行阈值处理。阈值处理将输入像素值的一个范围映射到 0(黑色),并将其互补范围映射到 255(白色),从而创建二值图像。更改垂直线的 r 坐标会更改阈值的值。
下图所示的配置文件将起到反转(负片)图像的效果。
下图所示的配置文件会获得一种特殊效果,其中一些强度被反转,而另一些则没有。这使得图像中同时存在正部和负部。
下图所示的配置文件会导致整个输入像素值范围映射到单个灰度值,从而使图像看起来更“均匀”。
结论与进一步工作
上面描述了一个用于对图像执行分段线性对比度拉伸操作的简单应用程序。该应用程序允许用户打开图像并修改对比度拉伸操作的参数,之后图像会相应更新。用户还可以查看当前应用的查找表的值。可以尝试不同的对比度拉伸参数来查看各种效果,如阈值处理、反转等。该应用程序的灵感来源于书籍:《数字图像处理》(作者:Gonzalez 和 Woods)。
可以进行进一步的扩展,以包括一种更快的图像更新方式。除了使用三个线段外,还可以编程实现用户指定的线段数量。在鼠标移动过程中更新图像,而不是在鼠标抬起时更新,是一个可能的扩展;但是,这需要近乎实时的性能,以及显著更快的实现和图像更新。
致谢
作者感谢 Harsha T 对用户界面的有益建议。