图像是人类获取和交换信息的主要来源,因此,图像处理的应用领域必然涉及到人类生活和工作的方方面面。随着人类活动范围的不断扩大,图像处理的应用领域也将随之不断扩大。(1)航天和航空技术方面的应用 数字图像处理技术在航天和航空技术方面的应用,除了上面介绍的JPL对月球、火星照片的处理之外,另一方面的应用是在飞机遥感和卫星遥感技术中。许多国家每天派出很多侦察飞机对地球上有兴趣的地区进行大量的空中摄影。对由此得来的照片进行处理分析,以前需要雇用几千人,而现在改用配备有高级计算机的图像处理系统来判读分析,既节省人力,又加快了速度,还可以从照片中提取人工所不能发现的大量有用情报。从60年代末以来,美国及一些国际组织发射了资源遥感卫星(如LANDSAT系列)和天空实验室(如SKYLAB),由于成像条件受飞行器位置、姿态、环境条件等影响,图像质量总不是很高。因此,以如此昂贵的代价进行简单直观的判读来获取图像是不合算的,而必须采用数字图像处理技术。如LANDSAT系列陆地卫星,采用多波段扫描器(MSS),在
数字图像处理技术与图象处理系统是七十年代末期形成一个独立学科,当时只能处理静止图象,主要用于军事、科研医学等领域。图象处理系统是为了加快处理速度而设计的专用系统,在中小型计算机控制下运行。这些系统的规模大,价格昂贵。面向PC机的图象处理系统是八十年代中后期开始出现的。它价格便宜,易于扩充, 软件丰富,因此很快得到推广,带动了图象处理技术的普及。
在过去的二十年里,C和C++已经成为在商业软件的开发领域中使用最广泛的语言。它们为程序员提供了十分灵活的操作,不过同时也牺牲了一定的效率。与诸如Microsoft,Visual Basic, 等语言相比,同等级别的C/C++应用程序往往需要更长时间来开发。由于C/C++语言的复杂性,许多程序员都试图寻找一种新的语言,希望能在功能与效率之间找到一个更为理想的权衡点。
目前有些语言,以牺牲灵活性的代价来提高效率。可是这些灵活性正是C/C++程序员所需要的。这些解决方案对编程人员的限制过多(如屏蔽一些底层代码控制的机制),其所提供的功能难以令人满意。这些语言无法方便地同早先的系统交互,也无法很好地和当前的网络编程相结合。
对于C/C++用户来说,最理想的解决方案无疑是在快速开发的同时又可以调用底层平台的所有功能。他们想要一种和最新的网络标准保持同步并且能和已有的应用程序良好整合的环境。另外,一些C/C++开发人员还需要在必要的时候进行一些底层的编程。
微软推出C# (C sharp)是微软对这一问题的解决方案。C#是一种最新的、面向对象的编程语言。它使得程序员可以快速地编写各种基于Microsoft .NET平台的应用程序,Microsoft .NET提供了一系列的工具和服务来最大程度地开发利用计算与通讯领域。正是由于C#面向对象的卓越设计,使它成为构建各类组件的理想之选--无论是高级的商业对象还是系统级的应用程序。使用简单的C#语言结构,这些组件可以方便的转化为XML 网络服务,从而使它们可以由任何语言在任何操作系统上通过Internet进行调用。最重要的是,C#使得C++程序员可以高效的开发程序,而绝不损失C/C++原有的强大的功能。因为这种继承关系,C#与C/C++具有极大的相似性,熟悉类似语言的开发者可以很快的转向C#。
效率与安全性新兴的网络经济迫使商务企业必须更加迅速的应对竞争的威胁。开发者必须不断缩短开发周期,不断推出应用程序的新版本,而不仅仅是开发一个"标志性"的版本。
C#在设计时就考虑了这些问题。它使开发者用更少的代码做更多的事,同时也不易出错。新的应用程序开发模型意味着越来越多地解决方案依赖于新出现的网络标准,例如HTML,XML,SOAP等。现存的开发工具往往都是早于Internet出现的,或者是在我们所熟知的网络还处于孕育期时出现的。所以,它们一般无法很好地支持最新的网络技术。
C#程序员可以在Microsoft.NET平台上事半功倍的构建应用程序的扩展框架。C#包含了内置的特性,使任何组件可以轻松转化为XML网络服务,通过Internet被任何操作系统上运行的任何程序调用。更突出的是,XML网络服务框架可以使现有的XML网络服务对程序员来说就和C#对象一样。这样,程序员就可以方便地使用他们已有的面向对象的编程技巧来开发利用现有的XML网络服务。
还有一些精细的特性,使得C#成为一流的网络编程工具。例如,XML正逐渐成为在网络上传输结构化数据的标准。这种数据集合往往非常小。为提高性能,C#允许把XML数据直接映射到struct数据类型,而不是class。这样对处理少量的数据非常有效。
即使是专家级的C++程序员也常会犯一些最简单的小错误--比如忘了初始化变量,但往往就是这些小错误带来了难以预料的问题,有些甚至需要很长时间来寻找和解决。一旦一个程序作为产品来使用,就算最简单的错误纠正起来也可能要付出极其昂贵的代价。C#的现代化设计能够消除很多常见的C++编程错误。 例如:资源回收减轻了程序员内存管理的负担;C#中变量由环境自动初始化;变量是类型安全的。这样,程序员编写与维护那些解决复杂商业问题的程序就更方便了。
更新软件组件是一项很容易出错的工作,因为代码的修改可能无意间改变原有程序的语义。为协助开发者进行这项工作,C#为版本的更新提供内在的支持。例如,方法重载必须显式声明。这样可以防止编码错误,保证版本更新的灵活性。还有一个相关的特性就是对接口和接口继承的内在支持。这些特性使得C#可以开发复杂的框架并且随着时间不断发展更新它。
总体来说,这些特性使得开发程序项目的后续版本的过程更加健壮,从而减少后续版本的开发成本。
C#语言允许类型定义的,扩展的元数据。这些元数据可以应用于任何对象。项目构建者可以定义领域特有的属性并把他们应用于任何语言元素-类,接口等等。然后,开发人员可以编程检查每个元素的属性。这样,很多工作都变得方便多了,比如编写一个小工具来自动检查每个类或接口是否被正确定义为某个抽象商业对象的一部分,或者只是创建一份基于对象的领域特有属性的报表。定制的元数据和程序代码之间的紧密对应有助于加强程序的预期行为和实际实现的之间的对应关系。
作为一种自动管理的,类型安全的环境,C#适合于大多数企业应用程序。但实际的经验表明有些应用程序仍然需要一些底层的代码,要么是因为基于性能的考虑,要么是因为要与现有的应用程序接口兼容。这些情况可能会迫使开发者使用C++,即使他们本身宁愿使用更高效的开发环境。C#采用以下对策来解决这一问题:内置对组建对象模型(COM)和基于Windows的API的支持;允许有限制地使用纯指针(Native Pointer)。
在C#中,每个对象都自动生成为一个COM对象。开发者不再需要显式的实现IUnknown和其他COM接口.这些功能都是内置的.类似的,C#可以调用现有的COM对象,无论它是由什么语言编写的。
C#包含了一个特殊的功能,使程序可以调用任何纯API。在一段特别标记的代码中,开发者可以使用指针和传统C/C++特性,如手工的内存管理和指针运算。这是其相对于其它环境的极大优势。这意味着C#程序员可以在原有的C/C++代码的基础上编写程序,而不是彻底放弃那些代码。无论是支持COM还是纯API的调用,都是为了使开发者在C#环境中直接拥有必要的强大功能。
所以C#是一种现代的面向对象语言。它使程序员快速便捷地创建基于Microsoft .NET平台的解决方案。这种框架使C#组件可以方便地转化为XML网络服务,从而使任何平台的应用程序都可以通过Internet调用它。C#增强了开发者的效率,同时也致力于消除编程中可能导致严重结果的错误。C#使C/C++程序员可以快速进行网络开发,同时也保持了开发者所需要的强大性和灵活性。
C#中,Image为源自 Bitmap 和 Metafile 的类提供功能的抽象基类,使用Image可以操作各种支持的图片,如GIF, BMP, JPG, Image.FromFile()返回的是某个继承自Image的具体类的对象,在这里,就是Bitmap或者Metafile其中之一。这Bitmap不仅仅对应于bmp,其实只要是像素式的图片格式(矢量格式不行),理论上都可以用Bitmap。由于Bitmap是忽略图像格式的,所以,在本图像处理的源代码中,并没有给出不同图像格式转换的代码,我们所做的仅仅是创建一个Bitmap对象,用 Image.FromFile()方法载入图像并保存到我们所创建的Bitmap对象中即可。对载入图像,我们可以使用SystemDrawing命名空间里提供的Getpixel方法提取像素的RGB值来进行处理。
2.1 BMP图像
2.1 BMP图像的基本介绍
如今Windows(3.x以及95,98,NT)系列已经成为绝大多数用户使用的操作系统,它比DOS成功的一个重要因素是它可视化的漂亮界面。那么Windows是如何显示图象的呢?这就要谈到位图(bitmap)。
在 Windows 3.0 以前,Windows系统用的是DDB(设备有关位图)。DDB没有调色板,显示的颜色依赖硬件,处理色彩很不方便。所以 Microsoft 在 Windows 3.0中 重新定义了BMP文件格式(BMP 3.0),使其支持设备无关位图——也就是DIB。时至今日,BMP的版本号已升至5.0(Windows NT 4.0、Windows95 定义了 BMP 4.0,Windows 98、Windows 2000 定义了 BMP 5.0),但基本结构没有变——仍是 BMP文件头 和 DIB 组成。
Windows 3.1以上版本提供了对设备无关位图DIB的支持。DIB位图可以在不同的机器或系统中显示位图所固有的图像。与DDB相比而言,DIB是一种外部的位图格式,经常存储为以BMP为后缀的位图文件(有时也以DIB为后缀)。DIB位图还支持图像数据的压缩。与Windows DIB结构相似,但不完全相同的另一种DIB是OS/2采用的DIB。
DIB位图的位数据紧跟在颜色表后面。数据可以是不压缩的,也可以是压缩的。对4位和8位位图,可以采用RLE(游程长度编码)压缩,分别称为RLE4和RLE8位图。
位数据以行为单位存储,每行都被填充到一个四字节边界,即每行所占的存储长度总是四字节(32位)的倍数,不足时将多余位用0填充。位图行的存储次序是颠倒的,即位图文件中第一行数据对应的是位图的最底行。对于像素位数为1的DIB位图,其每个像素只占1位,每个字节存储八个像素。字节的最高位对应于最左边的像素。在没有压缩的像素位数为4的DIB位图中,每个字节存储两个像素,高四位对应于最左边的像素,每行填充到一个四字节边界。采用RLE编码压缩的四位DIB由一系列组组成。有三种类型的组:重复组、文字组和特殊组。重复组由两个字节组成,第一个字节表示像素个数,第二个字节表示一对像素的值。文字组由一个0字节、一个像素计数字节和文字像素字节组成。像素计数值必须至少为3(小于3时,可采用重复组编码),文字像素应填充到一个偶数字节边界。特殊组中,00 00表示一行的结束,00 01表示位图的结束,00 02 xx yy表示位置增量,即图像向右走xx个像素,向下走yy个像素。
在没有压缩的像素位数为8的DIB位图中,每个字节存储一个像素,每行填充到一个四字节边界。采用R LE编码压缩的四位DIB由一系列组组成。有三种类型的组:重复组、文字组和特殊组。重复组内两个字节组成,第一个字节表示像素个数,第二个字节表示像素值。文字组由一个0字节、一个像素计数字节和文字像素字节组成。像素计数值必须至少为3(小于3时,可采用重复组编码),文字像素应填充到一个偶数字节边界。特殊组中,00 00表示一行的结束,00 01表示位图的结束,0002xx yy表示位置增量,即图像向右走xx个像素, 向下走yy个像素。在像素位数为24的DIB位图中,每个像素占三字节,从左到右的每一字节分别存储蓝、绿、红的颜色值。每行用0填充到一个四字节边界。
OS/2 DIB和Windows DIB的主要区别是位图信息结构(信息头结构和颜色表结构)不同。而它们的图像位数据的存储方式是完全一样的。
我们知道,普通的显示器屏幕是由许许多多点构成的,我们称之为象素。显示时采用扫描的方法:电子枪每次从左到右扫描一行,为每个象素着色,然后从上到下这样扫描若干行,就扫过了一屏。为了防止闪烁,每秒要重复上述过程几十次。例如我们常说的屏幕分辨率为640×480,刷新频率为70Hz,意思是说每行要扫描640个象素,一共有480行,每秒重复扫描屏幕70次。我们称这种显示器为位映象设备。所谓位映象,就是指一个二维的象素矩阵,而位图就是采用位映象方法显示和存储的图象。举个例子,图1.1是一幅普通的黑白位图,图1.2是被放大后的图,图中每个方格代表了一个象素。我们可以看到:整个骷髅就是由这样一些黑点和白点组成的。在设计中,我们也是对图像的像元进行处理的。
而自然界中的所有颜色都可以由红、绿、蓝(R,G,B)组合而成。有的颜色含有红色成分多一些,如深红;有的含有红色成分少一些,如浅红。针对含有红色成分的多少,可以分成0到255共256个等级,0级表示不含红色成分;255级表示含有100%的红色成分。同样,绿色和蓝色也被分成256级。这种分级概念称为量化。这样,根据红、绿、蓝各种不同的组合我们就能表示出256×256×256,约1600万种颜色。这么多颜色对于我们人眼来说已经足够丰富了。
常见颜色的RGB组合值
颜色 R G B
红 255 0 0
蓝 0 255 0
绿 0 0 255
黄 255 255 0
紫 255 0 255
青 0 255 255
白 255 255 255
黑 0 0 0
灰 128 128 128
当一幅图中每个象素赋予不同的RGB值时,能呈现出五彩缤纷的颜色了,这样就形成了彩色图。
图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个象素。对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。对于256色位图,一个字节刚好可以表示1个象素。对于真彩色图,三个字节才能表示1个象素。
要注意两点:
(1)每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。
(2)一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推,最后得到的是最上面一行的最右一个象素。
Windows操作系统统一管理着诸如显示,打印等操作,将它们看作是一个个的设备,每一个设备都有一个复杂的数据结构来维护。所谓设备上下文就是指这个数据结构。然而,我们不能直接和这些设备上下文打交道,只能通过引用标识它的句柄(实际上是一个整数),让Windows去做相应的处理。
图像读取主要方法是利用OpenFileDialog(文件打开控件)获得打开图像的绝对路径,用System.Drawing里Bitmap.FromFile方法将所获得的图像转换成DIB并加载到内存中,同时将加载的图像复制到pictureBox1图像框中。
private void menuItem2_Click(object sender, System.EventArgs e)
{
OpenFileDialog imageopen = new OpenFileDialog();
imageopen.Title = "请打开图像文件" ;
imageopen.InitialDirectory = @"c:\" ;
imageopen.Filter = "(图像文件)*.bmp;jepg;jpg;gif;png|*.bmp;*.jepg;*.jpg;*.gif;*.png" ;
imageopen.FilterIndex = 1 ;
imageopen.RestoreDirectory = true ;
if(imageopen.ShowDialog() == DialogResult.OK)
{
filepath=imageopen.FileName;
this.progressBar1.Value=20;
this.pictureBox1.Image=System.Drawing.Bitmap.FromFile(filepath);
}
this.pictureBox2.Image=null;
}
从某种角度上来讲,图像处理是基于统计学概念上的,所以,为了能够将图像变成计算机所能够识别并处理的数据,我们必须对图像进行量化,使得我们能从数值概念上获得对图像的映像。这里,我们引入灰度图像的概念:灰度图像是一种具有从黑到白256级灰度色域或等级的单色图像。该图像中的每个像素用8位数据表示,因此像素点值介于黑白间的256种灰度中的一种。该图像只有灰度等级,而没有颜色的变化。这样,我们可以将图像的RGB属性归一为灰度属性,由此就可以方便我们对图像进行处理。
首先我们应该清楚的是灰度直方图是一个从0-255范围变化的步长为一的数组,数组的每一个元素对应的是每一个灰度值。在这里,我们首先定义了一个int型数组,数组的大小为256,数组名为Histogram。其中,Histogram[i]对应得是灰度为i的像素的个数。
在这里,我们使用的计算灰度的算法为Gray=(int)(0.3*r+0.59*g+0.11*b)。其中,r,g,b分别为所处理像素的RGB值。算法的源代码如下:
private void menuItem24_Click(object sender, System.EventArgs e)
{
int height=this.pictureBox1.Image.Height;
int width=this.pictureBox1.Image.Width;
Bitmap process=(Bitmap)this.pictureBox1.Image;
Color pixel;
int [] Histogram=new int[256];
int Times,Gray,r,g,b;
for (i=0;i<=255;i++)
{
Histogram[i]=0;
}
for(i=0;i<width;i++)
{
for(j=0;j<height;j++)
{
pixel=process.GetPixel(i,j);
r=pixel.R;
g=pixel.G;
b=pixel.B;
Gray=(int)(0.3*r+0.59*g+0.11*b);
Histogram[Gray]=Histogram[Gray]+1;
}
}
}
下图为对某一图像处理后所获得的灰度直方图,从该图中我们可以获得一个图像的灰度分布的直观映像。
平移(translation)变换大概是几何变换中最简单的一种了。如下图,初始坐标为(x0,y0)的点经过平移(tx,ty)(以向右,向下为正方向)后,坐标变为(x1,y1)。这两点之间的关系是x1=x0+tx ,y1=y0+ty。
如下图所示
以矩阵的形式表示为
我们更关心的是它的逆变换:
这是因为:我们想知道的是平移后的图象中每个象素的颜色。例如我们想知道,新图中左上角点的RGB值是多少?很显然,该点是原图的某点经过平移后得到的,这两点的颜色肯定是一样的,所以只要知道了原图那点的RGB值即可。那么到底新图中的左上角点对应原图中的哪一点呢?将左上角点的坐标(0,0)入公式(2.2),得到x0=-tx ,y0=-ty;所以新图中的(0,0)点的颜色和原图中(-tx , -ty)的一样。这样就存在一个问题:如果新图中有一点(x1,y1),按照公式(2.2)得到的(x0,y0)不在原图中该怎么办?通常的做法是,把该点的RGB值统一设成(0,0,0)或者(255,255,255)。
旋转(rotation)有一个绕着什么转的问题,通常的做法是以图象的中心为圆心旋转。在我们熟悉的坐标系中,将一个点顺时针旋转a角后的坐标变换公式,如下图所示,r为该点到原点的距离,在旋转过程中,r保持不变;b为r与x轴之间的夹角。
旋转前:x0=rcosb;y0=rsinb
旋转a角度后:
x1=rcos(b-a)=rcosbcosa+rsinbsina=x0cosa+y0sina;
y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa;
以矩阵的形式表示:
上面的公式中,坐标系xoy是以图象的中心为原点,向右为x轴正方向,向上为y轴正方向。它和以图象左上角点为原点o’,向右为x’轴正方向,向下为y’轴正方向的坐标系x’o’y’之间的转换关系如何呢
设图象的宽为w,高为h,容易得到:
逆变换为:
理解了上述理论基础,其实在C#中我们有现成的方法函数进行操作,我们可以利用Graphics对象所生成的g.RotateTransform方法函数来对图像进行旋转操作。图像旋转后我们还需要将旋转所得到的图像填充到指定的矩形区域中,在这里我们使用了g.FillRectangle方法函数来进行填充。
private void panel2_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
this.panel2.Refresh();
Graphics g = e.Graphics;
int angel=Convert.ToInt16(this.numericUpDown4.Value);
System.Drawing.Bitmap temp=new Bitmap(filepath);
TextureBrush brush=new TextureBrush(temp);
g.RotateTransform(flaot(angel)); g.FillRectangle(brush,0,0,this.ClientRectangle.Width,this.ClientRectangle.Height);
return;
}
假设放大因子为ratio,(为了避免新图过大或过小,我们在程序中限制0.25≤ratio≤4),缩放(zoom)的变换矩阵很简单:
由于放大图象时产生了新的象素,以及浮点数的操作,得到的坐标可能并不是整数,这一点我们在介绍旋转时就提到了。我们采用的做法是找与之最临近的点。实际上,更精确的做法是采用插值(interpolation),即利用邻域的象素来估计新的象素值。其实我们前面的做法也是一种插值,称为最邻近插值(Nearest Neighbour Interpolation)。下面先介绍线形插值(Linear Interpolation)。
线形插值使用原图中两个值来构造所求坐标处的值。举一个一维的例子。下图所示,如果已经知道了两点x0,x2处的函数值f(x0),f(x2),现在要求x1处的函数值f(x1)。我们假设函数是线形的,利用几何知识可以知道
f(x1)=(f(x2)-f(x0))(x1-x0)/(x2-x0)+f(x0)
在图象处理中需要将线形插值扩展到二维的情况,即采用双线形插值(Bilinear Intrepolation),
| | 双线形插值的示意图 |
已知a、b、c、d四点的灰度,要求e点的灰度,可以先在水平方向上由a,b线形插值求出g、c、d线形插值求出f,然后在垂直方向上由g,f线形插值求出e。
线形插值基于这样的假设:原图的灰度在两个象素之间是线形变化的。一般情况下,这种插值的效果还不错。更精确的方法是采用曲线插值(Curvilinear Interpolation),即认为象素之间的灰度变化规律符合某种曲线,但这种处理的计算量是很大的。
同样的,我们可以利用Graphics对象所生成的g.FillRectangle方法函数来对图像进行缩放操作。图像缩放后我们还需要将缩放所得到的图像填充到指定的矩形区域中,同样在这里我们使用了g.FillRectangle方法函数来进行填充。
private void panel2_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
this.panel2.Refresh();
Graphics g = e.Graphics;
float fx=(float)(this.numericUpDown1.Value/10);
float fy=(float)(this.numericUpDown2.Value/10);
System.Drawing.Bitmap temp=new Bitmap(filepath);
TextureBrush brush=new TextureBrush(temp);
g.ScaleTransform(fx,fy);
g.FillRectangle(brush,0,0,this.ClientRectangle.Width,this.ClientRectangle.Height);
checkscale=0;
}
彩色图像黑白化处理通常有三种方法:最大值法、平均值法、加权平均值法
三种方法的原理
最大值法:最大值法是每个像素点的RGB值等于原像素点的RGB值中最大的一个,即R=G=B=MAX( R,G,B ); 效果,最大值发产生亮度很高的黑白图像。
平均值法:平均值法使每个像素点的RGB值等于原像素点的RGB值的平均值,即R=G=B=(R+G+B)/3
加权平均法:加权平均法根据需要指定每个像素点RGB的权数,并取其加权平均值,即R=G=B=(Wr*R+Wg*G+Wb*B )/3 。Wr、Wg、Wb表示RGB的权数,均大于零,通过取不同的权数可实现不同的效果。
本程序中采用的是平均值法来处理图像:
private void menuItem20_Click(object sender, System.EventArgs e)