灰度图像的点运算






4.63/5 (7投票s)
本文介绍四种灰度图像的点处理操作(C#)。
引言
图像处理操作通过操作输入图像的像素来生成输出图像。这类操作可以分为多种类型,其中两种是点操作和区域操作。点操作可能是最简单的图像处理操作。输出图像中的像素值仅取决于输入图像中对应像素的值。换句话说,如果输出图像中位置 (x, y) 的像素值仅取决于输入图像中位置 (x, y) 的像素值,则该操作为点操作。点操作将输入图像中的每个像素映射到输出图像中对应的像素。相比之下,区域操作则使用输入像素周围的一组像素来生成单个输出像素。滤波器就是区域操作的例子。
点操作用于增强图像。原始图像中不清晰的细节在应用点算子后可能会变得清晰可见。例如,图像中的暗区在操作后可能会变亮。也许最简单的点操作是增加和减少亮度。如果一个算子将每个像素值加上一个常数,那么这个点操作就会增加图像的亮度。类似的减法算子会降低亮度。
在本文中,我们对 8 位灰度图像的四种点操作进行了简单的实现。8 位灰度图像的输入和输出图像的像素值范围均为 0-255。我们选择灰度图像是因为它们有助于理解这些操作的简便性。我们在此演示的四种点操作是:
- 线性操作,
- 对数操作,
- 指数操作,以及
- 伽马校正操作。
公开领域有许多图像处理教程和文章,其中许多都执行复杂的操作。然而,提供简单操作和易于理解的实现的教程文章却相对罕见。本文旨在填补这一空白。本文提供的实现很简单,旨在教授和说明给初学者,而不是优化性能。
点操作
一种简单的点操作的可能公式是
其中 *a* 和 *b* 是常数,*p = p(x,y)* 是输入图像中位置 (x,y) 的灰度强度,*q = q(x,y)* 是输出图像中对应的灰度强度。上面提到的四种不同操作在函数 *f(p)* 的结构上有所不同。
对于线性操作,;因此,公式变为
。
对于对数操作,,因此,
。
对于指数操作,,得到的公式为
。
对于伽马校正操作,,其中
是一个实数;公式变为
。
实际上,其中一些公式可能需要细微的调整才能使其满意地工作。正如代码所示,指数操作需要一个这样的细微调整。
给定一个点操作,就需要解出 *a* 和 *b* 的值,从而计算图像中每个像素的结果值。一旦知道了 *a* 和 *b* 的值,就可以轻松计算任何输入像素的输出像素值。由于 8 位图像中只有 256 个不同的灰度值需要考虑,因此构建一个查找表并从该表中分配输出像素值在计算上是高效的。使用查找表可以避免不必要的重复计算。无论图像的大小如何,查找表只有 256 个条目(对于 8 位灰度输入和输出图像)。用户使用滑块设置的两个数字 *low* 和 *high* 也用于计算 *a* 和 *b*,它们构成了点操作的下限和上限范围。
软件设计
此应用程序使用的语言是 C#,开发平台是 Visual Studio 2008 Express Edition。
所需的软件功能包括:
- 打开图像并显示它。由于 8 位灰度图像可能不容易获得,因此增加了一个功能,可以将 24 位或 32 位彩色图像转换为灰度。此转换的公式为:*GrayscaleValue = 0.3 * red + 0.59 * green + 0.11 * blue*。
- 显示灰度图像的直方图。
- 执行点操作 - 线性、对数、指数或伽马 - 创建查找表,并将其应用于灰度图像。
- 根据上下滑块的位置,通过重新创建查找表并相应地更新像素来更新所选点操作的图像。
该软件设计使用可重用图形控件。此处开发了两个可重用图形控件:
ImagePanelControl
- 用于显示 8 位灰度图像。此控件不仅显示图像,还方便图像滚动。此控件还提供水平和垂直滚动条;滚动条的移动与图像位置相关联。GraphControl
- 用于显示图像直方图。此控件旨在计算灰度图像的直方图,并绘制与轴对应的垂直和水平刻度线。
设计中的一个附加功能是使用继承。不同的点操作,如线性、对数等,都从名为 GrayscaleOp
的父类继承,该父类声明了通用的成员和方法。然后,就可以实例化适当的子类并使用它了。
软件实现和代码片段
应用程序中的不同类是:MainForm
、ImagePanelControl
、GraphControl
、GrayscaleOp
、LinearOp
、Log10Op
、ExponentOp
和 GammaOp
。主窗体除了其他可视化控件(如按钮、单选按钮、滑块和标签)外,还包含上述两个图形控件。
ImagePanel
控件有一个 List<byte> pix8
数据成员,其中包含图像像素,以及图像的宽度和高度。给定这些参数,它使用以下代码动态创建图像:
private void CreateImage()
{
bmp = new Bitmap(imgWidth, imgHeight,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
BitmapData bmd = bmp.LockBits(new Rectangle(0, 0,
imgWidth, imgHeight),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
bmp.PixelFormat);
// This 'unsafe' part of the code populates
// the bitmap bmp with data stored in pix8.
// It does so using pointers, and therefore the need for 'unsafe'.
unsafe
{
int pixelSize = 3;
int i, j, j1, i1;
byte b;
for (i = 0; i < bmd.Height; ++i)
{
byte* row = (byte*)bmd.Scan0 + (i * bmd.Stride);
i1 = i * bmd.Width;
for (j = 0; j < bmd.Width; ++j)
{
b = (byte)(pix8[i1+j]);
j1 = j * pixelSize;
row[j1] = b; // Red
row[j1 + 1] = b; // Green
row[j1 + 2] = b; // Blue
}
}
} // end unsafe
bmp.UnlockBits(bmd);
}
使用 BitmapData
类是为了通过 C 风格的指针算术使图像创建过程更快,而不是使用更慢的 SetPixel
方法。Scan0
是像素数据缓冲区的起始位置,Stride
是每行中的字节数(包括任何必要的填充)。此类中的另一个重要方法是 Paint
处理程序中的 g.DrawImage()
。
ImagePanelControl
类负责正确显示图像,它将滚动条与图像连接起来,以便在滚动条移动时图像能正确滚动。
GraphControl
类如上所述,创建图像的直方图。它使用 GDI+ 来绘制直方图。首先创建直方图。之后,确定直方图中的最大条目,并将其存储为 histMax
。然后使用 GDI+ 命令显示直方图。直方图由一组相邻的垂直线组成,看起来像一个连续的直方图。
pt1 = new Point();
pt2 = new Point();
Pen p = new Pen(Color.Blue);
Pen p1 = new Pen(Color.Tomato);
Graphics g = Graphics.FromHwnd(this.Handle);
for (i = 0; i < 256; ++i)
{
pt1.X = Convert.ToInt32(xLength * i / 255.0) + margin;
pt1.Y = Height - margin;
pt2.X = pt1.X;
pt2.Y = pt1.Y - Convert.ToInt32(histogram[i] * yLength / histMax);
g.DrawLine(p, pt1, pt2);
}
LinearOp
、Log10Op
、ExponentOp
和 GammaOp
类都包含它们对 ComputeLookUpTable
方法的实现,下面以 Log10Op
为例(其中一个示例):
public override void ComputeLookUpTable(int low, int high, double gamma)
{
double a, b;
double lHigh = Math.Log10(high + 1.0);
double lLow = Math.Log10(low + 1.0);
double range = lHigh - lLow;
a = 255.0 / range;
b = -a * lLow;
int intVal;
for (int i = 0; i < 256; ++i)
{
if (i <= low) lookUpTable[i] = 0;
else if (i > high) lookUpTable[i] = 255;
else
{
intVal = Convert.ToInt32(a * Math.Log10(i + 1) + b);
if (intVal > 255) intVal = 255;
if (intVal < 0) intVal = 0;
lookUpTable[i] = (byte)intVal;
}
}
}
其余代码用于通过主窗体将应用程序集成到一个整体中。
需要注意的是,Visual Studio 项目需要先编译一次才能打开应用程序的主窗体。这是必需的,因为 ImagePanelControl
和 GraphControl
这两个控件是主窗体的一部分,并且这些控件对应的二进制文件必须可用才能正确显示主窗体。在此提供的压缩项目文件中未包含二进制文件。
结果与结论
上面描述了一个演示灰度图像点处理的简单应用程序。该应用程序使用户能够应用四种点操作 - 线性、对数、指数和伽马校正操作。该应用程序的快照显示在本页顶部。选择一个单选按钮会将相应的点操作应用于图像。对于伽马操作,可以选择要应用的伽马值,范围从一组值中选取。该应用程序的灵感来自 **Nick Efford 的著作《数字图像处理》**。该应用程序的进一步工作包括添加更多此类点处理操作和优化应用程序的性能。一种简单的优化方法是将 LUT(查找表)仅应用于图像的显示部分,使其失效,然后将 LUT 应用于图像的其余部分。其他类型的优化也是可能的。