数字图像处理(Digital Image Processing)是通过计算机对图像进行去除噪声、增强、复原、分割、提取特征等处理的方法和技术。数字图像处理的产生和迅速发展主要受三个因素的影响:
一是计算机的发展;
二是数学的发展(特别是离散数学理论的创立和完善);
三是广泛的农牧业、林业、环境、军事、工业和医学等方面的应用需求的增长。
一、实验内容:
主要是图像的几何变换的编程实现,具体包括图像的读取、改写,图像平移,图像的镜像,图像的转置,比例缩放,旋转变换等,具体要求如下:
1、编程实现图像平移,要求平移后的图像大小不变;
2、编程实现图像的镜像;
3、编程实现图像的转置;
4、编程实现图像的比例缩放,要求分别用双线性插值和最近邻插值两种方法来实现,并比较两种方法的缩放效果;
5、编程实现以任意角度对图像进行旋转变换,要求分别用双线性插值和最近邻插值两种方法来实现,并比较两种方法的旋转效果。
二、实验目的和意义:
本实验的目的是使学生熟悉并掌握图像处理编程环境,掌握图像平移、镜像、转置和旋转等几何变换的方法,并能通过程序设计实现图像文件的读、写操作,及图像平移、镜像、转置和旋转等几何变换的程序实现。
三、实验原理与主要框架:
3.1实验所用编程环境:
VisualC++(简称VC)是微软公司提供的基于C/C++的应用程序集成开发工具、VC拥有丰富的功能和大量的扩展库,使用它能有效的创建高性能的Windows应用程序和Web应用程序。
VC除了提供高效的C/C++编译器外,还提供了大量的可重用类和组件,包括著名的微软基础类库(MFC)和活动模板类库(ATL),因此它是软件开发人员不可多得的开发工具。
VC丰富的功能和大量的扩展库,类的重用特性以及它对函数库、DLL库的支持能使程序更好的模块化,并且通过向导程序大大简化了库资源的使用和应用程序的开发,正由于VC具有明显的优势,因而我选择了它来作为数字图像几何变换的开发工具。
在本程序的开发过程中,VC的核心知识、消息映射机制、对话框控件编程等都得到了生动的体现和灵活的应用。
3.2实验处理的对象:256色的BMP(BITMAP)格式图像
BMP(BITMAP)位图的文件结构:
具体组成图:BITMAPFILEHEADER
位图文件头
(只用于BMP文件)bfType=”BM”bfSizebfReserved1
bfReserved2
bfOffBits
biSize
biWidth
biHeight
biPlanes
biBitCount
biCompression
biSizeImage
biXPelsPerMeter
biYPelsPerMeter
biClrUsed
biClrImportant
1、BMP文件组成
BMP文件由文件头、位图信息头、颜色信息和图形数据四部分组成。
2、BMP文件头
BMP文件头数据结构含有BMP文件的类型(必须为BMP)、文件大小(以字节为单位)、位图文件保留字(必须为0)和位图起始位置(以相对于位图文件头的偏移量表示)等信息。
3、 位图信息头
BMP位图信息头数据用于说明位图的尺寸(宽度,高度等都是以像素为单位,大小以字节为单位,水平和垂直分辨率以每米像素数为单位),目标设备的级别,每个像素所需的位数,位图压缩类型(必须是0)等信息。
4、颜色表
颜色表用于说明位图中的颜色,它有若干个表项,每一个表项是一个RGBQUAD
类型的结构,定义一种颜色、具体包含蓝色、红色、绿色的亮度(值范围为0-255)
位图信息头和颜色表组成位图信息
5、位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,扫描行之间是从下到上。
Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充。
3.3BMP(BITMAP)位图的显示:
①一般显示方法:
1、申请内存空间用于存放位图文件
2、位图文件读入所申请内存空间中
3、在函数中用创建显示用位图,用函数创建兼容DC,用函数选择显示删除位图
但以上方法的。缺点是:1)显示速度慢;
2)内存占用大;
3)位图在缩小显示时图形失真大,(可通过安装字体平滑软件来解决);
4)在低颜色位数的设备上(如256显示模式)显示高颜色位数的图形(如真彩色)图形失真严重。
②BMP位图缩放显示:
用视频函数来显示位图,内存占用少,速度快,而且还可以对图形进行淡化(Dithering)处理、淡化处理是一种图形算法,可以用来在一个支持比图像所用颜色要少的设备上显示彩色图像、BMP位图显示方法如下:
1、打开视频函数,一般放在在构造函数中
2、申请内存空间用于存放位图文件
3、位图文件读入所申请内存空间中
4、在函数中显示位图
5、关闭视频函数,一般放在在析构函数中
以上方法的优点是:
1)显示速度快;
2)内存占用少;
3)缩放显示时图形失真小;
4)在低颜色位数的设备上显示高颜色位数的图形图形时失真小;
5)通过直接处理位图数据,可以制作简单动画。
3.4程序中用到的访问函数
Windows支持一些重要的DIB访问函数,但是这些函数都还没有被封装到MFC中,这些函数主要有:
1、SetDIBitsToDevice函数:该函数可以直接在显示器或打印机上显示DIB、在显示时不进行缩放处理。
2、StretchDIBits函数:该函数可以缩放显示DIB于显示器和打印机上。
3、GetDIBits函数:还函数利用申请到的内存,由GDI位图来构造DIB、通过该函数,
可以对DIB的格式进行控制,可以指定每个像素颜色的位数,而且可以指定是否进行压缩。
4、CreateDIBitmap函数:利用该函数可以从DIB出发来创建GDI位图。
5、CreateDIBSection函数:该函数能创建一种特殊的DIB,称为DIB项,然后返回一个GDI位图句柄。
6、LoadImage函数:该函数可以直接从磁盘文件中读入一个位图,并返回一个DIB句柄。
7、DrawDibDraw函数:Windows提供了窗口视频(VFW)组件,VisualC++支持该组件、VFW中的DrawDibDraw函数是一个可以替代StretchDIBits的函数、它的最主要的优点是可以使用抖动颜色,并且提高显示DIB的速度,缺点是必须将VFW代码连接到进程中、
3.5图像的几何变换
图像的几何变换,通常包括图像的平移、图像的镜像变换、图像的转置、图像的缩放和图像的旋转等。
一、实验的目的和意义
实验目的:本实验内容旨在让学生通过用VC等高级语言编写数字图像处理的一些基本算法程序,来巩固和掌握图像处理技术的基本技能,提高实际动手能力,并通过实际编程了解图像处理软件的实现的基本原理。为学生进一步学习数字摄影测量、遥感和地理信息系统等专业课程以及应用图像处理解决实际问题奠定基础。
二、实验原理和方法
(1)Raw格式到BMP格式的转换:
Raw格式:Raw格式文件是按照数字图像组成的二维矩阵,将像素按行列号顺序存储在文件中。这种文件只含有图像像素数据,不含有信息头,因此,在读图像时,需要根据文件大小,计算图像所包含的行列号,或者需要事先知道图像大小(矩阵大小)。RAW文件按图像上行到下行、左列到右列顺序存储。
BMP格式:BMP文件数据区按图像上下行到上行、左列列到右列顺序存储到数据区。BMP文件由文件头、信息头、颜色表、数据区四个部分组成。
做Raw格式文件到BMP格式文件的转化,先要为BMP格式文件申请四部分的内存:文件头,位图信息头,颜色表,图象数据,然后根据输入值以及Raw文件信息,BMP格式文件信息计算出这几部分的值,赋给他们,写到BMP文件中去。
(2)灰度图象的线性拉伸:
灰度变化是点运算,将原图象的每个像素的灰度值改成线性变化之后的灰度即可。
灰度的线性变换就是指图像的中所有点的灰度按照线性灰度变换函数进行变换。灰度变换方程如下:
该方程为线性方程。式中参数为输入图像的像素的灰度值,参数为输出图像的灰度值。
设原图象的灰度范围为[a,b],变化之后的范围为[a’,b’],则:
fA=(b’-a’)/(b-a)
fB=-(b’-a’)/(b-a)*a+a’
如果算出来的值大于255,则让它等于255,小于0则让其等于0。
(3)局部处理(3*3高通滤波,3*3低通滤波):
局部处理在处理某一像素时,利用与该像素相邻的一组像素,经过某种变换得到处理后图像中某一点的像素值。目标像素的邻域一般是由像素组成的二维矩阵,该矩阵的大小为奇数,目标像素位于该矩阵的中央,即目标像素就是区域的中心像素。经过处理后,目标像素的值为经过特定算法计算后所得的结果。
实际上都是利用卷积来实现的,卷积往往用一个矩阵表示,将矩阵的中心对齐某个像素,矩阵中的值乘到相应的像素中去,然后将所有乘积加起来就得到中心像素的灰度值。边界像素不做处理,仍为原来的灰度值。求出的像素灰度值若超过[0~255],则向离其最近的属于该范围的像素值靠拢。
(4)图象几何处理(图象平移,图象缩放):
对于图像平移来说,若平移量是(tx,ty),像素在原图像中的坐标为(x0,y0),则变化后的坐标为(x1,y1),x1=x0+tx,y1=y0+ty。平移只需改变像素的灰度值,不必改变位图信息头和调色板内容。
对于图像缩放,假设放大因子为ratio,缩放的变换矩阵为:
图像信息头中新图像的宽度和高度都变为原来宽度和高度分别与水平垂直比例的乘积,图像大小变为新宽度(变为4的整数倍)与新高度的乘积。
(5)灰度图象中值滤波:
中值滤波也属于局部处理的一种,将窗口中的各个像素排序之后排序,取中值赋给模板中心的像素,所以窗口中个数一般是基数。
我用的中值滤波窗口是十字丝的9个数的窗口。
(6)灰度图象边缘检测:
边缘检测有三种算子:Roberts,Prewit,Sobel。三种算子都是做一阶差分的,通过算子算出各个像素的梯度值,将水平梯度的绝对值和垂直梯度的绝对值相加,若此梯度值大于某个阈值,则将其灰度值赋为255,否则赋为0。
(7)图象旋转:
图像旋转一般是以图像中心为中心顺时针旋转,利用图像的四个角点求出图像旋转后的大小。
先计算以图像中心为原点坐标系下原图像四个角点的坐标值,按照旋转矩阵计算其旋转之后的坐标值,根据四个角点的新坐标值计算出最大宽度和高度作为新图像的宽度和高度值,按照计算值修改位图信息头,申请一块新内存,存储旋转后图像的灰度值。
旋转矩阵如下:
同样要求各个像素在原图像中的坐标,先将新图像的坐标系平移到图像中心,做逆时针旋转,然后再平移到屏幕左上角,然后将原图像对应坐标的值赋给新图像。
(8)图象二值化:
判断分析法:假定图像的灰度区间为[0,L-1],则选择一阈值T将图像的像素分为两组。
为最大值所对应的T,就是所求判断分析法的分割阈值。
搜寻到阈值之后,灰度值小于阈值的像素赋0,其他的赋1,修改文件信息头,调色板,申请新内存。
(9)图象直方图:
统计各灰度值出现的频数,以及像素的总个数,用频数除以总个数作为频率,以灰度值作为横坐标,频率作为纵坐标绘图。
三、实验过程和步骤
首先要建立一个基于MFC的多文档工程,将视图基类改为滚动视图,以自己的学号命名。
我用的是书上给的CDib类,类里面有获取BMP宽度,高度的函数,有指向位图信息头的指针,指向图象数据的指针,因此我在文档类(Doc类)里定义了一个CDib类的对象,打开以及保存文件的时候利用这个对象去调用CDib里读取与存储文件的函数,并且可以利用这个对象的两个指针对打开的图象进行各种操作。
1、Raw格式到BMP格式的转换:
首先建立一个RawToBMP的对话框,在上面加上四个编辑框(一个输入打开文件的路径一个输入保存文件的路径,另两个),两个按钮,以及默认的确认,取消按钮。利用类向导插入此对话框类,并且为前两个编辑框定义CString的两个变量,用来存储打开与保存文件的路径。同时为两个浏览按钮添加消息响应函数,在消息函数里创建CFileDialog对象,利用此对象的函数将两个路径值赋给前两个编辑框的成员变量。再为OK键添加消息响应函数,分别定义BMP格式文件前三部分数据变量,计算出各变量的值,并且利用一个CFile对象获取Raw图象的数据,利用另一个CFile对象将数据存储到所输入的路径的文件中去,CFile对象的Read函数会自动创建一个文件。
然后在菜单上新建一个菜单,为菜单添加消息响应函数,在其消息响应函数里创建RowToBMP对话框。这样点击菜单后就会弹出一个对话框,按确定键之后就可以读取Raw文件并且存储BMP文件,完成整个消息循环。
2、灰度图象的线性拉伸:
创建一个对话框来输入变化后的灰度值,为对话框的两个编辑框定义成员变量,在文档类中添加处理函数,按照对话框输入值计算出fA与fB,做一个循环,将0到255的灰度值,计算出拉伸后的灰度值(超限情况特殊处理),存放在下标为此值的一个数组中,然后利用文档类的中定义的CDib类的成员变量m_DIB,获得当前打开的图像指向图像数据部分的指针m_DIB、m_pBits,在数组中查出每个像素变化后的灰度值,并将此值赋给指针m_pBits指向的内存。刷新视图。
然后在菜单中加上线性拉伸的菜单,为该菜单的ID添加消息响应函数,在该函数中创建对话框,并调用文档类线性拉伸的函数,将对话框的两个成员变量传给此函数。
3、局部处理:
在文档类里添加低通滤波和高通滤波的成员函数,在函数中使用m_DIB对象中指向图像数据部分的指针m_pBits,首先申请一个新内存,将原来图像的灰度值存储起来,然后定义9个BYTE类型的指针,利用双重嵌套循环,在循环中每次用这9个指针指向复制图像对应模板中的9个数,然后按照模板中的数值计算出中心像素的灰度值,判断是否超过范围,如果超过范围则做相应的处理,否则将此值直接赋给m_pBits中对应的中心像素。循环之后刷新视图。
添加局部处理的菜单,为菜单设置消息响应函数,在菜单消息响应函数中调用文档类的函数,完成对m_DIB的处理。
4、图像几何变换:
建立平移对话框,定义两个成员变量,分别存储输入的水平位移和垂直位移。
在文档类里添加平移函数,申请一块新内存复制原图像的信息,在函数中将外层循环变量i视为纵坐标,内层循环变量j视为横坐标,通过双重循环,对每个像素,求出其在原图像中的坐标(i0,j0),将复制图像中的对应(i0,j0)的像素灰度值赋给m_DIB、m_pBits指针中的图像。如果在原图像中找不到该像素,置为背景色。刷新视图。
在菜单中添加图像平移菜单,并为该菜单添加消息响应函数,在此函数中创建平移对话框,调用文档类的平移函数,将对话框的成员变量传入该函数。
建立缩放对话框类,为此类定义两个成员变量,存储输入的水平缩放因子和垂直缩放因子。
再在文档类中添加缩放函数,利用m_DIB、m_pBMI(指向位图信息头的指针),修改位图信息头中的宽度,高度,图像大小。计算出新图像的大小,申请一块新内存存储新图像,同平移函数一样,计算出每个像素在原图像中的坐标,i0=i/PRatio,j0=j/VRatio,PRatio与VRatio分别为水平缩放因子和垂直缩放因子。将原图像中对应坐标的灰度值赋给新内存,然后将m_DIB、m_pBits(指向图像数据的指针)指向新内存,刷新视图。
5、中值滤波:
在文档类中添加两个成员函数。一个用来把传入的指针里的内容排序,一个用来做中值滤波。也要申请一块新内存来复制原图像的信息,双重嵌套循环,边界像素不处理,对每个像素,使用一个大小为9个字节的数组来存放复制图像窗口中各像素值,然后将数组首地址传入排序的函数中,将中间的值赋给当前图像窗口中心的像素。排序函数我用的是快速排序法。
在菜单中添加中值滤波菜单项,为其添加消息响应函数,调用文档类的中值滤波函数。
6、边缘检测:
在文档类中定义三个函数,分别为Roberts,Prewit,Sobel算子处理函数,处理时,先申请新内存复制原来图像信息,边界像素不作处理,对每个像素值,求出其在复制图像中的梯度,判断,若梯度值大于150(这个是我自己定的),则将灰度值赋为255,否则置零。
菜单中添加边缘检测菜单,置属性为Pop—up,添加三个下一级菜单,分别为Roberts,Prewit,Sobel,各个菜单的消息响应函数中调用文档类中各自的处理函数。
7、图像旋转:
创建一个对话框输入旋转角度,在文档类中添加成员函数。
先将角度化为弧度值。
计算原图像四个角点的坐标,以及新图像四个角点的坐标。
根据新图像四个角点的坐标,取对角线上两个点横坐标差值较大值作为宽度,纵坐标差值较大值作为高度。
根据计算出来的高度和宽度修改文件信息头,并且申请内存存储新图像。
计算每点的像素在原来图像中的坐标从而获取其灰度值,写入新内存。
将m_DIB、m_pBits指向该新内存。刷新视图。
添加图像旋转菜单,在菜单响应函数中创建对话框,调用文档类中旋转函数,将对话框中获取的角度传给旋转函数。
8、图像二值化:
在文档类添加一个成员函数,根据传人的图像和阈值返回组间方差和组内方差的比值。
再添加一个成员函数,进行二值化。
在函数中:
计算新BMP文件的大小,申请一块新内存,存储新的整个BMP文件的信息,将位图信息头中biBitCount置为1,调色板数组只有两个两个元素,下标为0的三个灰度值都为0,下标为1的三个灰度值为255。
从最大灰度值到最小灰度值之间搜寻上述函数返回值最大的值,作为阈值。
对每个像素,若其原来灰度值小于阈值,赋1,否则赋0。
将m_DIB,m_pBits指向新内存的图像数据部分,m_DIB、m_pBMI指向位图信息头。
9、图像直方图:
为文档类添加一个int型指针成员变量m_pGray,在构造函数中将该指针赋空,在文档类中定义了一个函数,统计各个灰度值出现的频数,申请一个内存,存储在这个内存中,并将m_pGray指向它。
创建一个画直方图的对话框,添加Picture控件,在控件里调用文档类成员变量,画直方图。添加一个滚动条,用来确定阈值,为滚动条添加消息响应函数,按照滚动条的值进行二值化。
在菜单中添加直方图菜单,添加消息响应函数,在响应函数中创建直方图对话框对象。
最后,因为我开始做工程的时候没有把菜单设计好,做得有点乱,所以,我又在View里添加WM_CONTEXTMENU消息响应函数,在函数体内用CMenu类来实现弹出菜单。
四、实验总结与体会
这次实验学到最大的东西,是自己总算有MFC编程的概念了,虽然自己VC++考试的分数还不错,但是里面的很多东西,不通过自己的编程时绝对不能真正理解。比如说封装性,这次用CDib的方便,很好地利用了类的封装性。另外,比如MFC是基于消息响应机制的,这就决定了,要利用鼠标或者菜单响应函数去实现功能,而用c语言编写程序的时候,完全是按主函数的线程来的。
另外,我也学会了调试的真正含义。以前都只知道那几个按键是做什么用的,调试的真正目的,是根据自己的算法来检验程序计算的各个值是否符合,从而可以很快速方便地查到自己的错误。
自学也是很重要的一方面。实际上,在现在来说,用MSDN也不是很难的事了,我们不应该被英文打到,而且现在,随着对一些专有名词熟悉了之后,看MSDN也容易一些了,万一不懂的函数,也可以利用网络查到很多函数功能用法的解释。
刚开始的时候做的是位图的读取和显示,实在是不知从哪里做起,所以就照着实验书上敲了前面的部分,但是慢慢地也看懂了代码的意思。所以后来的基本上都是自己做的了,但是算法还是基本上和书上差不多。不过自己编的时候还是有很多细节的部分没有注意到,比如说,强制数据类型转换,我自己编的时候没有注意这个问题,结果出了很多错,有些事由于函数调用引起的,有些是由于不等号两边数据的匹配问题,还有的是由于指针的移动,直到这个时候,才真正明白实验书上程序为什么那么多强制类型转换,虽然书上很多东西不是尽善尽美,但是对于我这种刚开始学会编程的人还是有很多可以学习的地方的。
如老师所说,算法的效率是很重要的。要提高算法的效率,一个是要简化计算(不得不说,这需要数学基础),另外一个就是要避免许多重复的计算。在参考书上的程序里,很多时候,为了避免这种重复的计算(在循环中表现尤其明显),会把某些数当常数算出来,只要后来加上这个常数就可以,这样,效率高很多。
另外,对许多出错的情况,我的程序里也没有做好。比如,如果打开的不是8位图像,我的程序不会提示错误,正常结束,而可能做错,所以,这也是我应该向别人程序学习的地方。
最后一个,自己菜单的布局也是很乱的。要从一开始就布局好。