C#
?首先,在开始开发之前个人就在C+OpenCV
、C++MFC+OpenCV
、C#+Graphics库
这几种开发方式之间徘徊。虽然Teacher仅仅对于作业做出实现相应的算法的要求,而并未对于程序的交互做出详细的要求,然而我又不满足于仅仅实现算法,想要把每次大作业都作为一个锻炼自己的机会,希望能够做一个把关于图像处理的功能集成在一起的交互式的桌面应用,倾向于选择使用C#+Graphics
库的方法。
其次,由于之前使用C#
开发过相关的桌面应用,对C#
较为熟悉,此外,使用VS2015
可以较快的实现UI
的开发而可以把主要的精力放在图像处理算法的相关实现上,加上C#
封装了图形绘制的库方法,可以避免过度的去考虑如何调用Windows API
实现在界面上画一条直线,在界面上画一个圆这样的最最基本也是最繁琐的操作,综上,最终确定使用C#
来开发。
使用语言:C#(.NET Framework 4.5.2)
开发环境:VS2015
图像点运算技术报告概述写在前面的话开发为何使用C#?开发语言及开发环境目录一、灰度直方图的绘制1.灰度直方图的含义2.实现效果3.功能说明二、灰度线性变换1.灰度线性变换的含义2.实现效果1.原图 2.当k>1时3.当k = 1时1.d>0,增加图像亮度2.d<0,减少图像亮度4.当0<k<1时5.当k<0时3.功能说明三、灰度的非线性变换1.灰度对数变换1.对数变换含义2.实现效果 3.功能说明2.灰度幂次变换1.幂次变换含义2.实现效果 3.功能说明3.灰度指数变换1.指数变换含义2.实现效果3.功能说明四、灰度拉伸1.灰度拉伸含义2.实现效果3.功能说明五、灰度直方图均衡1.直方图均衡含义2.实现效果 3.功能说明遇到的问题以及解决方式1.图像处理性能问题1.像素处理VS.内存操作1.像素提取法2.内存法 2.现场计算VS.灰度映射表2.库函数使用问题3.参数范围选择问题1.对数变换2.幂次变换3.指数变换4.代码复用问题参考资料
这个之前有一个误区,之前一直认为一个24色位图必须先进行灰度化才能进行直方图的绘制,即采用灰度化的经验公式
将每个像素的RGB
分量都统一成灰度值再进行统计进而绘制直方图,之后发现对于彩色图像仅需要直接将每个像素的RGB
分量直接统计入直方图的0~255
级中即可。
高亮度图像的直方图组成集中在灰度高的一侧。8 位灰度图能表示 256 种灰度,也就是灰度取值范围为 0 至 255。其中 0 表示黑色,255 表示白色。对于高亮度图像,整个画面的颜色偏亮,故灰度直方图偏向灰度高的一侧。相反,低亮度图像的直方图则偏向灰度较低的一侧。
在高对比度的图像中,直方图的覆盖范围很广。图像在任意一段灰度范围中都有一定的像素数量。同时,高对比图像的灰度分布相比其他图像而言较为均匀,整个直方图显得比较滑。而低对比度图像的灰度则主要分布在中间狭窄的区域中,图像就像被冲谈了一样。
MATLAB
中灰度直方图画的那么优雅,故而模仿一下MATLAB
的版本,其中绘制柱状直方图直线的颜色信息为RGB(0, 114, 189)
。HistForm.cs
)
灰度线性变换是灰度变换的一种,图像的灰度变换通过建立灰度映射来调整源图像的灰度从而达到图像增强的目的。灰度映射通常是用灰度变换曲线来表示的。
灰度线性变换就是将图像的像素值通过指定的线性函数进行变换,以此增强或者减弱图像的灰度。灰度线性变换的公式就是常见的一维线性函数:
设x 为原始灰度值,则变换后的灰度值y 为:
通过讨论 的取值来分析灰度线性变换的效果。
此时用于增加图像的对比度。图像的像素值在变换后全部增大,整体显示效果被增强。
这种情况常用于调节图像亮度。亮度的调节是让图像的各像素值都增加或者减少一定量。这种情况下可以通过改变 的值达到增加或者减少图像亮度的目的。
效果与 时相反,图像的对比度和整体效果都被削弱。 越小,图像的灰度分布越窄,图像看起来也就越偏灰色。
在这种情况下,源图像较亮的区域变暗 ,而较暗的区域会变亮。此时可以使函数中的 让图像实现反色效果。
LinearToolForm.cs
)在绘制直线的时候需要确定两个点(直线的起始点和终止点),因此涉及到确定直线和坐标系是否有交点,以及交点数目等问题,比如在处理时出现直线和坐标系四条直线有三个交点而其中两个交点重合(比如均为直角坐标系下的(0,0)),使得选择两个重合的顶点绘制直线以至于在图中得不到任何直线的问题,此问题的解决采用的自己的想法而没有去搜索相应的通用方法,详见代码。
常见的灰度的非线性变换有三种,包括:对数变换、幂次变换、指数变换。
对数变换的基本形式为:
其中 控制曲线的垂直偏移量: 为正常数,控制曲线的弯曲程度。
对数变换实现了图像灰度扩展和压缩的功能。它扩展低灰度值而压缩高灰度值,让图像的灰度分布更加符合人的视觉特征。
LogToolForm.cs
)幂次变换的基本形式为:
其中、 均为正数。与对数变换相同,幂次变换将部分灰度区域映射到更宽的区域中 当时,幂次变换转变为线性变换。
输出灰度值会随着指数的增加迅速扩大。当指数稍大时,整个变换曲线趋近于一条垂直线。此时原始图像中的绝大部分灰度值经过变换后会变成最大值 产生的图像几乎全黑,失去了非线性变换的意义。在实际运用中经常对基本表达式的 和 进行约束,让它们的取值范围在0~1
之间。
修改幂次变化公式使 与的取值范围都在0~255
之间。
由函数曲线得知:
当 时,变换函数曲线在正比函数上方。此时扩展低灰度级,压缩高灰度级,使图像变亮。这一点与对数变换十分相似。
当 时,变换函数曲线在正比函数下方。此时扩展高灰度级,压缩低灰度级,使图像变暗。
PowerToolForm.cs
)
指数变换的基本形式为:
其中参数 、 控制曲线形状,参数控制曲线的左右位置。
指数变换的作用是扩展图像的高灰度级、压缩低灰度级。虽然幂次变换也有这个功能,但是图像经过指数变换后对比度更高,高灰度级也被扩展到了更宽的范围。
ExpToolForm.cs
)由于环境光线或采集设备等原因,图像的灰度有时会集中于某一较小区间,如图像过亮或过暗等,这时就需要对图像的灰度进行拉伸使之覆盖较大的取值区间,从而提高图像的对比度以便于观察。这种处理可以利用线性变换曲线建立灰度映射来完成。
灰度拉伸又叫做对比度拉伸,它与线性变换有些类似,不同之处在于灰度拉伸使用的是分段线性变换,所以它最大的优势是变换函数可以由用户任意合成。灰度拉伸的公式为:
灰度拉伸需要指定两个控制点,它们用于控制灰度拉伸变换函数的图形。一般情况下有 ,成立。灰度拉伸常用于扩展指定灰度范围,以改善图像质量 。
中间线段的斜率大于 1,如果一幅图像对比度较低,就可以利用这类控制点对图像进行对比度拉伸,如下图所示。
中间线段的斜率小于 1,作用与上一条相反,用于降低图像的对比度。
此时变换函数变化为一条线性函数,它产生一个没有变化的图像。
这是一种特殊情况,此时变换函数变为阀值函数,产生二值图像。
StretchingToolForm.cs
)灰度均衡是以累计分布函数变换为基础的直方图修正法,它可以产生一幅灰度级分布概率均匀的图像。灰度均衡同样也属于改进图像的方法,灰度均衡后的图像具有最大的信息量。
下面进行灰度均衡变换函数的推导。
设转换前图像的密度函数为,其中;转化后图像的密度函数为,同样有;灰度均衡变换函数为 。从概率理论可以得到如下公式:
转化后图像灰度均匀分布,有,故:
两边取积分有:
这就是图像的累计分布函数。对于图像而言,密度函数为:
其中 表示灰度值,表示灰度级为 的像素个数, 表示图像总像素个数。通过上面的公式就能推导出基于离散型的灰度均衡公式:
其中 的取值范围是 0 至 255。
EqualizationToolForm.cs
)
在开发的过程当中遇到了各种各样的问题,包括算法以及文件读取方法导致的性能问题、库函数使用不熟悉导致的图像处理错误的问题、参数范围选择的问题、以及代码复用的问题等。
在处理每一个像素并将其灰度化的时候,有三种做法,最初始的实现为了简便我采用的是提取像素的方法,这种方法采用GDI+
中的Bitmap.GetPixel
和Bitmap.SetPixel
的方法来将位图中每一个像素设置为灰度或者其他颜色,后来实践中发现一旦像素点过多,每刷新一次都会产生程序假死的现象,体验非常糟糕。
考虑改用内存法,内存法将图像数据直接复制到内存中,使得程序的运行速度大大提高,基本上做到实时处理,体验较好。还有一种方法是指针法,指针法与内存法开始都是使用LockBits
方法获取位图首地址,但是方法更简单,直接应用指针对位图进行操作,所以效率较内存法更高,但是由于C#
默认不支持指针运算,需要使用unsafe
关键字,而且需要开启不安全模式,最终没有采用。
三种方法的效率对比如下:
前两种方法的实现如下:
在进行像素灰度映射的过程中最初始的时候采用现场计算的方法,即对于每一个像素点都使用线性变换函数或非线性计算函数计算一次,导致大量的计算冗余,使得图像处理的速度非常缓慢,之后考虑由于映射函数的自变量的取值范围为0~255
,可以对于0~255
先计算出因变量的值做一个灰度映射表,而当每个像素需要做映射变换时可以直接查找得到映射后的值,使得图像处理的效率显著地提高。
库函数的使用方面出现的一个问题是:在使用内存法处理图像的时候,我没有仔细的查看相关库函数的说明,自己想当然的使用库函数导致出现错误。
在使用内存法读入图像时有如下代码:
AخABitmapData bmpData = originalBitmap.LockBits(rect, ImageLockMode.ReadOnly, originalBitmap.PixelFormat);
出现的问题是图像处理的时候只能处理图像的大概3/4
的图像,并且可以处理的图像显示出来的颜色非常诡异,而另外1/4
的图像颜色没有变化,后来发现像素的默认格式是ARGB
所以我按照图像在内存中依照BGR
连续排列的想法处理导致色彩错位并且只能处理3/4
的图像,在仔细查看了库函数后发现仅仅将参数调成PixelFormat.Format24bppRgb
就可以解决上述问题,代码如下:
xxxxxxxxxx
BitmapData bmpData = originalBitmap.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
在进行灰度非线性变换时,即进行对数变换、幂次变换、指数变换的时候发现按照推导公式直接写出代码往往一个很小的变化就导致图像完全变白或者完全变黑,后来发现由于这些非线性变换对于参数的微小变化十分敏感而如果不把参数的敏感度适当降低一些的话,一个小小的调节往往使得图像变化过于剧烈。
对于三种非线性变换的参数调节如下:
可以看出参数的敏感度降低1000倍
可以看出参数的敏感度降低10倍,参数的敏感度降低100倍
可以看出参数的敏感度降低10倍,参数的敏感度降低1000倍
由于在线性变换、对数变换、幂次变换、指数变换、灰度拉伸、灰度均衡化都用到了绘制直方图,在线性变换、对数变换、幂次变换、指数变换、灰度拉伸都用到了坐标系的绘制,直观地把代码在各个文件里面拷来拷去导致代码的可读性以及可维护性非常差,往往要改动一个地方得到各个文件中都进行相应的修改,故而采用把如直方图绘制,坐标系绘制封装为一个CommonTool
类,提供所有图像处理中共有的操作。