项目说明报告

基于java的图形图象处理系统设计与实现

随着计算机技术的迅速发展,数字图像处理技术在医学领域的研究和应用日益深入和广泛。现代医学已越来越离不开医学图像处理技术。医学图像处理技术在临床诊断、教学科研等方面发挥了重要的作用。计算机图像处理技术与影像技术的结合从根本上改变了医务人员进行诊断的传统方式。充分地利用这些技术可以提高诊断的正确性和准确性,提高诊断效率,降低医疗成本,可以更加充分地发挥各种医疗设备的功能。而且,随着数字化、智能化进程的深人,图像处理技术在医疗卫生领域将会有更加广阔的应用前景。

Java是Sun公司推出的一种面向对象编程语言。Java非常适合于企业网络和Internet环境,现已成为Internet中最受欢迎、最有影响的编程语言之一。目前国内使用Java语言开发的图像处理系统比较少,这也增加了这方面的研究价值。

本文首先对图像增强和图像分割中的几种算法进行了介绍,包括线性灰度变换,伪彩色处理,平滑处理,中值滤波,阈值分割,边缘检测等。然后用Java语言对上述各算法编程实现,并设计Java GUI(图形用户界面)用来显示图像处理的结果,以及创建一个数据库用于存储医学图像。

医学图像;图像增强;图像分割;面向

目录

1  引言  1

2  医学图像处理概述 3

2.1  什么是医学图像处理............................................................................. 3

2.2  医学图像处理及研究内容..................................................................... 3

2.2.1  超声图像.............................................................................................. 3

2.2.2  X射线图像........................................................................................... 4

2.2.3  磁共振成像.......................................................................................... 5

2.2.4  核医学成像.......................................................................................... 6

2.3  医学图像处理技术新进展..................................................................... 7

3  Java语言的特点     11

3.1  面向对象编程....................................................................................... 11

3.1.1  抽象.................................................................................................... 11

3.1.2  面向对象编程的3个原则................................................................ 12

3.2  Java的特性.......................................................................................... 13

3.3  Java语言的前景.................................................................................. 15

4  Java语言实现图像处理 16

4.1  图像增强技术....................................................................................... 16

4.1.1  灰度变换............................................................................................ 17

4.1.2  伪彩色处理........................................................................................ 19

4.1.3  平滑化处理........................................................................................ 22

4.1.4  其他图像增强技术............................................................................ 24

4.2  图像分割技术....................................................................................... 25

4.2.1  阈值分割法........................................................................................ 25

4.2.2  边缘检测法........................................................................................ 28

4.3  图像复原技术....................................................................................... 31

4.4  本章小结............................................................................................... 33

5  设计流程 34

5.1  主流程图............................................................................................... 34

5.2  图像处理界面....................................................................................... 35

5.3  图像的加载........................................................................................... 36

5.4  图像的处理........................................................................................... 38

5.5  数据库的建立....................................................................................... 42

开发工具 44

6.1  JCreator概述...................................................................................... 44

6.2  JCreator编辑界面的组成.................................................................. 44

结 论     47

致 谢     48

参考文献       49

附录A  英文原文 50

附录B  中文翻译 59

附录源程序   66

1  引言

数字图像处理技术是20世纪60年代随着计算机技术和超大规模集成电路的发展而产生、发展和不断成熟起来的一个新兴技术领域,它在理论上和实际应用中都取得了巨大的成就。

视觉是人类最重要的感知手段,图像又是视觉的基础。早期图像处理的目的是改善图像质量,它以人为对象,以改善人的视觉效果为目的。图像处理中输入的是质量低的图像,输出的是改善质量后的图像。常用的图像处理方法有图像增强、复原、编码、压缩等。首次获得成功应用的是美国喷气推进实验室(JPL)。他们对航天探测器徘徊者7号在1964年发回的几千张月球照片进行图像处理,如几何校正、灰度变换、去除噪声等,并考虑了太阳位置和月球环境的影响,由计算机成功地绘制出月球表面地图,获得了巨大的成功。随后又对探测飞船发回的近十万张照片进行更为复杂的图像处理,获得了月球的地形图、彩色图及全景镶嵌图,为人类登月创举奠定了坚实的基础,也推动了数字图像处理这门学科的诞生。在以后的宇航空间技术探测研究中,数字图像处理技术都发挥了巨大的作用。

数字图像处理技术取得的另一个巨大成就是在医学上。1972年英国EMI公司工程师Housfield发明了用于头颅诊断的X射线计算机断层摄影装置,也就是我们通常所说的CT(Computer Tomograph)。CT的基本方法是根据人的头部截面的投影,经计算机处理来重建截面图像,称为图像重建。1975年EMI公司又成功研制出全身用的CT装置,获得了人体各个部位鲜明清晰的断层图像。1979年,这项无损伤诊断技术被授予诺贝尔奖,以表彰它对人类做出的划时代贡献。

从20世纪70年代中期开始,随着计算机技术和人工智能、思维科学研究的迅速发展,数字图像处理技术向更高、更深层次发展。人们已开始研究如何用计算机系统解释图像,类似人类视觉系统理解外部世界,这被称为图像理解或计算机视觉。很多国家,特别是发达国家投入更多的人力、物力到这项研究,取得了不少重要的研究成果。其中代表性的成果是70年代末MIT的Marr提出的视觉计算理论,这个理论成为计算机视觉领域其后十多年的主导思想。

20世纪80年代末期,人们开始将其应用于地理信息系统,研究海图的自动读入、自动生成方法。数字图像处理技术的应用领域不断拓展。

数字图像处理技术的大发展是从20世纪90年代初开始的。自1986年以来,小波理论与变换方法迅速发展,它克服了傅里叶分析不能用于局部分析等方面的不足之处,被认为是调和分析半个世纪以来工作之结晶。Mallat在1988年有效地将小波分析应用于图像分解和重构。小波分析被认为是信号与图像分析在数学方法上的重大突破。随后数字图像处理技术迅猛发展,到目前为止,图像处理在图像通讯、办公自动化系统、地理信息系统、医疗设备、卫星照片传输及分析和工业自动化领域的应用越来越多。

进入21世纪,随着计算机技术的迅猛发展和相关理论的不断完善,数字图像处理技术在许多应用领域受到广泛重视并取得了重大的开拓性成就。属于这些领域的有航空航天、生物医学工程、工业检测、机器人视觉、公安司法、军事制导、文化艺术等。该技术成为一门引人注目、前景远大的新型学科。

2  医学图像处理概述

近年来,随着计算机及其相关技术的迅速发展,图像处理技术日益成熟,使得该技术深入到医学领域的方方面面,开创了数字医疗的新时代。

借助图像处理技术的有力手段,医学图像的质量和显示方法可以得到极大的改善,从而使得诊断水平可以借助于图像处理与分析手段来得到极大的改善,这不仅可以基于现有的医学图像设备来极大地提高医学临床诊断水平,而且能为医学培训、医学研究与教学、计算机辅助临床外科手术等提供电子实现手段,为医学的研究与发展提供坚实的基础,具有无可估量的价值。

2.1  什么是医学图像处理

医学图像处理就是利用计算机系统对生物医学图像进行的具有临床医学意义的处理和分析。

医学图像处理是一个很复杂的过程。医学图像作为一种信息源,也和其它有关病人的信息一样,是医生做出判断时的依据。医生在判读医学图像时,要把图像与他的解剖学、生理学和病理学等知识作对照,还要根据经验来捕捉图像中的有重要意义的细节和特征。所以要从一幅或几幅医学图像中判断出是否有异常,或属于什么疾病,是一种高级的脑力劳动。任意拿一张有异常的CT图像、X线照片或超声图像来看,如果不是训练有素的医生,是难以发现图像上的异常的。所以对医学图像进行处理显得尤为重要。

2.2  医学图像处理及研究内容

2.2.1  超声图像

超声是超过正常人耳能听到的声波,频率在20 000赫兹(Hertz,Hz)以上。超声检查是利用超声的物理特性和人体器官组织声学性质上的差异,以波形、曲线或图像的形式显示和记录,借以进行疾病诊断的检查方法。40年代初就已探索利用超声检查人体,50年代已研究、使用超声使器官构成超声层面图像,70年代初又发展了实时超声技术,可观察心脏及胎儿活动。超声诊断由于设备不似CT或MRI设备那样昂贵,可获得器官的任意断面图像,还可观察运动器官的活动情况,成像快,诊断及时,无痛苦与危险,属于非损伤性检查,因此,在临床上应用已普及,是医学影像学中的重要组成部分。不足之处在于图像的对比分辨力和空间分辨力不如CT和MRI高。

超声在介质中以直线传播,有良好的指向性。这是可以用超声对人体器官进行探测的基础。当超声传经两种声阻抗不同相邻介质的界面时其声阻抗差大于0.1%,而界面又明显大于波长,即大界面时,则发生反射,一部分声能在界面后方的相邻介质中产生折射,超声继续传播,遇到另一个界面再产生反射,直至声能耗竭。反射回来的超声为回声。声阻抗差越大,则反射越强,如果界面比波长小,即小界面时,则发生散射。超声在介质中传播还发生衰减,即振幅与强度减小。衰减与介质的衰减系数成正比,与距离平方成反比,还与介质的吸收及散射有关。

人体结构对超声而言是一个复杂的介质,各种器官与组织,包括病理组织有它特定的声阻抗和衰减特性。因而构成声阻抗上的差别和衰减上的差异。超声射入体内,由表面到深部,将经过不同声阻抗和不同衰减特性的器官与组织,从而产生不同的反射与衰减。这种不同的反射与衰减是构成超声图像的基础。将接收到的回声,根据回声强弱,用明暗不同的光点依次显示在影屏上,则可显出人体的断面超声图像,称这为声像图。人体器官表面有被膜包绕,被膜同其下方组织的声阻抗差大,形成良好界面反射,声像图上出现完整而清晰的周边回声,从而显出器官的轮廓。根据周边回声能判断器官的形状与大小。

图2.1 急性阑尾超声影像

2.2.2  X射线图像

X线图像是当前临床应用最广泛的一种医学图像,如何从X线图像获得更多的信息,是提高诊断技术水平的一个重要方向。

X线图像建立在当X线透过人体时,各种脏器与组织对X线的不同吸收程度的基础上,因而在接收端将得到不同的射线强度。接收端射线强度的变化,如被记录在底片上就变成灰度的变化。

基于这个原理,所得的X线图像是把三维结构的人体在二维空间中投影成像的技术,是人体内各层结构重叠后的图像。因此,处理的基本目的是要在图片上把特定的脏器轮廓从周围的结构中分离出来。几十年来,X线技术的发展可以说大都是为了提高X线图像的分辨能力。例如,各种X线照片的处理技术(包括增强,分割,识别等)X线断层摄影技术,X线CT技术,X线减影技术等。此外,也要尽可能减少病人和医生所受到的X线辐射剂量。

2.2 骨纤维异常增殖症X光片

2.2.3  磁共振成像

核磁共振(MRI)又叫核磁共振成像技术。是继CT后医学影像学的又一重大进步。自80年代应用以来,它以极快的速度得到发展。其基本原理:是将人体置于特殊的磁场中,用无线电射频脉冲激发人体内氢原子核,引起氢原子核共振,并吸收能量。在停止射频脉冲后,氢原子核按特定频率发出射电信号,并将吸收的能量释放出来,被体外的接受器收录,经电子计算机处理获得图像,这就叫做核磁共振成像。

核磁共振是一种物理现象,作为一种分析手段广泛应用于物理、化学生物等领域,到1973年才将它用于医学临床检测。为了避免与核医学中放射成像混淆,把它称为核磁共振成像术(MR)。

MR是一种生物磁自旋成像技术,它是利用原子核自旋运动的特点,在外加磁场内,经射频脉冲激后产生信号,用探测器检测并输入计算机,经过处理转换在屏幕上显示图像。

MR提供的信息量不但大于医学影像学中的其他许多成像术,而且不同于已有的成像术,因此,它对疾病的诊断具有很大的潜在优越性。它可以直接作出横断面、矢状面、冠状面和各种斜面的体层图像,不会产生CT检测中的伪影;不需注射造影剂;无电离辐射,对机体没有不良影响。MR对检测脑内血肿、脑外血肿、脑肿瘤、颅内动脉瘤、动静脉血管畸形、脑缺血、椎管内肿瘤、脊髓空洞症和脊髓积水等颅脑常见疾病非常有效,同时对腰椎椎间盘后突、原发性肝癌等疾病的诊断也很有效。

2.3 颅脑病变MR影像

MR也存在不足之处。它的空间分辨率不及CT,带有心脏起搏器的患者或有某些金属异物的部位不能作MR的检查,另外价格比较昂贵。

2.2.4  核医学成像

将放射性同位素(RI)作为示踪物质,直接注入人体,并在体外用测定器对它的分布、聚集、变化等进行测定记录,所得的图像即为核医学图像。 

核医学图像与其它用作诊断的医学图像相比,是有其特长的。首先,由于可以选择对特定脏器的生理作用有关联的放射性药物,因而可对不同的脏器做图像观察和分析研究,即有做选择性造影的能力;其次,由于放射性药物可以有不同的寿命,因而有可能对放射性物质在体内的活动进行长时间的观察,从而有可能测定体内各脏器的摄取、排泄、循环以及代谢等机能,即具有动态机能测定的能力,这些重大特长都是其它医学图像所不具有的。由于核医学图像具有的“选择性造影能力”和“动态机能测定能力”,所以虽然它有一系列的缺点,但在临床诊断中还是得到了广泛的应用。

核医学图像存在的弱点与检测方法有关。首先,由于被检查人员和操作人员所受辐射量的原因,做检查时用的放射性同位素剂量是不大的,而γ线的放射是同位素衰变的一种随机现象,所以核医学图像中含有较大的时间统计误差。为了使两个区域检测到的γ线数的变化不被统计涨落所淹没,同时又不增加同位素剂量,只好扩大每个区域的面积。这就导致核医学图像空间分辨率的降低。

    正是由于核医学图像的空间分辨率低、信噪比低以及对比度低这些特点,使得在利用核医学图像做诊断时,医生的主观介入较多,往往造成解释上的差异。因此,引入图像处理技术和识别技术,对核医学图像来讲尤为重要。

图2.4显示了一种叫做“正电子放射断层”(PET)的核成像。

图2.4 PET图像

2.3  医学图像处理技术新进展

1.医学图像处理技术

医学图像处理技术包括很多方面,图像分割、图像配准和融合以及伪彩色处理技术和纹理分析在医学领域的应用和发展越来越广泛。

     图像分割就是把图像中具有特殊涵义的不同区域分开来,这些区域使互不相交的每一个区域都满足特定区域的一致性。它是图像处理与图像分析中的一个经典问题。图像分割技术发展至今,已在灰度阈值分割法、边缘检测分割法、区域跟踪分割法的基础上结合特定的理论工具有了更进一步的发展。比如基于三维可视化系统结合Fast Marching算法和Watershed变换的医学图像分割方法,能得到快速、准确的分割结果。

     图像分割同时又是进行三维重建的基础,分割的效果直接影响到三维重建后模型的精确性,分割可以帮助医生将感兴趣的物体(病变组织等)提取出来,帮助医生能够对病变组织进行定性及定量的分析,从而提高医生诊断的准确性和科学性。

     医学图像配准是通过寻找某种空间变换,使两幅图像的对应点达到空间位置和解剖结构上的完全一致。要求配准的结构能使两幅图像上所有的解剖点,或至少是所有具有诊断意义以及手术区域的点都达到匹配。目前医学图像配准方法有基于外部特征的图像配准(有框架)和基于图像内部特征的图像配准(无框架)两种方法。后者由于其无创性和可回溯性,已成为配准算法的研究中心。基于互信息的弹性形变模型也逐渐成为研究热点。

     图像配准是图像融合的前提,是公认难度较大的图像处理技术,也是决定医学图像融合技术发展的关键技术。近年来国外在图像配准方面研究很多,如几何矩的配准、利用图像的相关系数、样条插值等多项式变换对图像进行配准。国内研究人员也提出了一些相应的算法:一致图像配准方法、金字塔式多层次图像配准方法、基于互信息的方法。在努力提高配准精度的同时,目前提出的多种方法都力求整个过程自动化,其结果导致实现算法的过程复杂而耗费时间。

  不同的医学图像提供了相关脏器的不同信息,图像融合的潜力在于综合处理应用这些成像设备所得信息以获得新的有助于临床诊断的信息。利用可视化软件,对多种模态的图像进行图像融合,可以准确地确定病变体的空间位置、大小、几何形状及它与周围生物组织之间的空间关系,从而及时高效地诊断疾病,也可以用在手术计划的制定、病理变化的跟踪、治疗效果的评价等方面。在放疗中,利用MR图像勾勒画出肿瘤的轮廓线,也就是描述肿瘤的大小;利用CT图像计算出放射剂量的大小以及剂量的分布,以便修正治疗方案。在制定手术方案时,对病变与周围组织关系的了解是手术成功与否的关键,所以CT与MR图像的融合为外科手术提供有利的佐证,甚至为进一步研究肿瘤的生长发育过程及早期诊断提供新的契机。在CT成像中,由于骨组织对X线有较大的吸收系数,因此对骨组织很敏感;而在MR成像中,骨组织含有较低的质子密度,所以MR对骨组织和钙化点信号较弱,融合后的图像对病变的定性、定位有很大的帮助。由于不同医学成像设备的成像机制不同,其图像质量、空间与时间特性有很大差别。因此,实现医学图像的融合、图像数据转换、图像数据相关、图像数据库和数据理解都是亟待解决的关键技术。

  对一幅黑白图像,人眼一般只能辨别出4到5比特的灰度级别,而人眼能辨别出上千种不同的颜色。针对这一特点,人们往往将黑白图像经过处理变为彩色图像,充分发挥人眼对彩色的视觉能力,从而使观察者能从图像中取得更多的信息,这就是伪彩色图像处理技术。经过伪彩色处理技术,即密度分割技术,提高了对图像特征的识别。通过临床研究对X线、CT、MRI、B超和电镜等图片均进行了伪彩色技术的尝试,取得了良好的效果,部分图片经过处理后可以显现隐性病灶。

  纹理是人类视觉的一个重要组成部分,迄今为止还难以适当地为纹理建模。为此有关专家进行了大量的探索研究,但未能获得有关纹理的分析、分类、分割及其综合的有效解释。有研究针对肝脏疾病难以根除、危害面广的问题,采用灰度梯度共生矩阵的方法,分别提取纤维化肝组织和正常肝组织的CT图像的纹理特征,提出了基于友度梯度共生矩阵的小梯度优势、灰度均方差、灰度嫡等参数作为图像的纹理特征量。通过选取的纹理参数,可以看到正常组和异常组之间存在显著性差异,为纤维化CT图像临床诊断提供了依据。

2.三维医学图像的可视化

     三维医学图像的可视化通常是利用人类的视觉特性,通过计算机对二维数字断层图像序列形成的三维体数据进行处理,使其变换为具有直观立体效果的图像来展示人体组织的三维形态。三维医学图像可视化技术通常分为面绘制和体绘制两种方法。

  医学数据的可视化,已成为数据可视化领域中最为活跃的研究领域之一。实现三维数据可视化的方法很多,空间域方法的典型算法包括:射线投射法、足迹法、剪切一曲变法(目前被认为是一种速度最快的体绘制算法)等;变换域方法的典型算法有频域体绘制法和基于小波的体绘制法,其中小波的体绘制技术显现出较好的前景。

  随着互联网技术不断发展,跨越空间限制的远程虚拟现实技术已经逐步成为可能。 基于虚拟现实技术利用美国国家医学图书馆VHP(Visible Human Project)完整数据重建可视人体,综合VTK, VRML和OperFGL等可视化平台的优势,采用三维互动、空间电磁定位、立体视觉等虚拟现实技术,实现了全数字可拆装人体骨骼的本地和远程互动学习。三维虚拟现实让邀游人体世界成为可能,可以呈现一个物理上并不存在但又实实在在看得见、摸得着的真实人体,使用者可以无数次地解剖这个虚拟人以了解人体的结构。

  在临床方面,提出了一种用AVS/Express开发的基于PC的Le Fort手术模型系统原型。利用AVS用xpress大量预制的可视化编程对象模块,快速构建系统的结构框架和功能模块,生成的原型能对以DICOM(Digital Imaging and Communications in Medicine)格式存储的颅颌面CT序列断层图像进行预处理,并进行三维重建,在交互式操作环境中,显示颅颌面各种组织的解剖结构,进行相应的三维测量,模拟Le Fort I手术的截骨头,对截骨段实行任意的平移颌旋转。在体视化方面一直致力于提高重建速度(实时显示利于交互操作),使重建效果理想,减少冗余信息及存储空间。

3.针对PACS的图像压缩

     图像存档及通信系统(picture archiving and communication system,PACS)是近年来国内外新兴的医学影像信息技术,是专门为医学图像管理而设计的,包括图像获取、处理、存储、显示或打印的软硬件系统,是医学影像、数字化图像技术、计算机技术和网络通信技术相结合的产物。显然,计算机网络是PACS的重要组成部分,它负责提供底层图像传输服务,是PACS的软硬件基础,正是通过各个层次的网络才将PACS中的图像获取、存储显示以及医疗数据的管理等单元连为一体,使之形成一个统一、高性能的系统. PACS需要解决数据传输和图像存储的问题,如何利用有限的存储空间存储更多的图像,医学图像压缩是关键的技术之一,也是近年来图像处理技术中的一个重点研究的问题。

  医学图像的压缩无疑是减低应用系统成本,提高网络传输效率,减少存储空间的一个重要途径。DICOM作为医学图像与通信的重要标准,加入了对图像压缩算法的支持。目前DICOM正在研究对最新的压缩标准JPEG2000支持的可能性。随着新一代静态图像压缩标准JPEG2000的发展,小波理论在这个领域成为研究的热点。

  医学图像是医学诊断和疾病治疗的重要根据,在临床上具有非常重要的应用价值。确保医学图像压缩后的高保真度是医学图像压缩首要考虑的因素,现在医学图像上常常采用无损压缩,因为它能够精确地还原原图像。但是无损图像压缩的压缩比很低,一般为1∶2~4,而有损图像压缩的压缩比可以高达1∶50,甚至更高。所以将这两种压缩方法在保证使用要求的基础上结合起来,在获取高的压缩质量的前提下提高压缩比,这也是目前医学图像研究领域的一个热点。

3  Java语言的特点

3.1  面向对象编程

Java的核心是面向对象编程。事实上,所有的Java程序都是面向对象的,你别无选择。这一点与C++不同,因为在那里你可以选择是否面向对象编程。面向对象编程与Java密不可分,因此,在你编写哪怕是最简单的Java程序以前,也必须理解它的基本原则。

3.1.1  抽象

面向对象编程的一个实质性的要素是抽象。人们通过抽象(abstraction)处理复杂性。例如,人们不会把一辆汽车想象成由几万个互相独立的部分所组成的一套装置,而是把汽车想成一个具有自己独特行为的对象。这种抽象使人们可以很容易地将一辆汽车开到杂货店,而不会因组成汽车各部分零件过于复杂而不知所措。他们可以忽略引擎、传动及刹车系统的工作细节,将汽车作为一个整体来加以利用。

使用层级分类是管理抽象的一个有效方法。它允许你根据物理意义将复杂的系统分解为更多更易处理的小块。从外表看,汽车是一个独立的对象。一旦到了内部,你会看到汽车由若干子系统组成:驾驶系统,制动系统,音响系统,安全带,供暖,便携电话,等等。再进一步细分,这些子系统由更多的专用元件组成。例如,音响系统由一台收音机、一个CD播放器、或许还有一台磁带放音机组成。从这里得到的重要启发是,你通过层级抽象对复杂的汽车(或任何另外复杂的系统)进行管理

复杂系统的分层抽象也能被用于计算机程序设计。传统的面向过程程序的数据经过抽象可用若干个组成对象表示,程序中的过程步骤可看成是在这些对象之间进行消息收集。这样,每一个对象都有它自己的独特行为特征。你可以把这些对象当作具体的实体,让它们对告诉它们做什么事的消息做出反应。这是面向对象编程的本质。

面向对象的概念是Java的核心,对程序员来讲,重要的是要理解这些概念怎么转化为程序。你将会发现,在任何主要的软件工程项目中,软件都不可避免地要经历概念提出、成长、衰老这样一个生命周期,而面向对象的程序设计,可以使软件在生命周期的每一个阶段都处变不惊,有足够的应变能力。例如,一旦你定义好了对象和指向这些对象的简明的、可靠的接口,你就能很从容很自信地解除或更替旧系统的某些组成部分。

3.1.2  面向对象编程的3个原则

所有面向对象的编程语言都提供帮助你实现面向对象模型的机制,这些机制是封装,继承及多态性。当然,Java也不例外。

封装:封装(Encapsulation)是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。理解封装性的一个方法就是把它想成一个黑匣子,它可以阻止在外部定义的代码随意访问内部代码和数据。对黑匣子内代码和数据的访问通过一个适当定义的接口严格控制。封装代码的好处是每个人都知道怎么访问它,但却不必考虑它的内部实现细节,也不必害怕使用不当会带来负面影响。

Java封装的基本单元是类。一个类(class)定义了将被一个对象集共享的结构和行为(数据和代码)。一个给定类的每个对象都包含这个类定义的行为和结构,好像它们是从同一个类的模子中铸造出来似的。因为这个原因,对象有时被看作是类的实例(instances of a class)。所以,类是一种逻辑结构,而对象是真正存在的物理实体。

当创建一个类时,你要指定组成那个类的代码和数据。从总体上讲,这些元素都被称为该类的成员(members)。具体地说,类定义的数据称为成员变量(member variables)或实例变量(instance variables)。操作数据的代码称为成员方法(member methods)或简称方法(methods)。如果你对C/C++熟悉,可以这样理解:Java程序员所称的方法,就是C/C++程序员所称的函数(function)。在完全用Java编写的程序中,方法定义如何使用成员变量。这意味着一个类的行为和接口是通过方法来定义的,类这些方法对它的实例数据进行操作。

既然类的目的是封装复杂性,在类的内部就应该有隐藏实现复杂性机制。类中的每个方法或变量都可以被标记为私有(private)或公共(public)。类的公共接口代表类的外部用户需要知道或可以知道的每件事情;私有方法和数据仅能被一个类的成员代码所访问,其他任何不是类的成员的代码都不能访问私有的方法或变量。既然类的私有成员仅能被程序中的其他部分通过该类的公共方法访问,那么你就能保证不希望发生的事情就一定不会发生。当然,公共接口应该小心仔细设计,不要过多暴露类的内部内容。

继承:继承(Inheritance)是一个对象获得另一个对象的属性的过程。继承很重要,因为它支持了按层分类的概念。大多数知识都可以按层级(即从上到下)分类管理。使用了继承,一个对象就只需定义使它在所属类中独一无二的属性即可,因为它可以从它的父类那儿继承所有的通用属性。所以,可以这样说,正是继承机制使一个对象成为一个更具通用类的一个特定实例成为可能。

继承性与封装性相互作用。如果一个给定的类封装了一些属性,那么它的任何子类将具有同样的属性,而且还添加了子类自己特有的属性。这是面向对象的程序在复杂性上呈线性而非几何性增长的一个关键概念。新的子类继承它的所有祖先的所有属性。它不与系统中其余的多数代码产生无法预料的相互作用。

多态性:多态性(polymorphism ,来自于希腊语,表示多种形态)是允许一个接口被多个同类动作使用的特性,具体使用哪个动作与应用场合有关,下面我们以一个后进先出型堆栈为例进行说明。假设你有一个程序,需要3种不同类型的堆栈。一个堆栈用于整数值,一个用于浮点数值,一个用于字符。尽管堆栈中存储的数据类型不同,但实现每个栈的算法是一样的。如果用一种非面向对象的语言,你就要创建3个不同的堆栈程序,每个程序一个名字。但是,如果使用Java,由于它具有多态性,你就可以创建一个通用的堆栈程序集,它们共享相同的名称。

多态性的概念经常被说成是一个接口,多种方法。这意味着可以为一组相关的动作设计一个通用的接口。多态性允许同一个接口被同一类的多个动作使用,这样就降低了程序的复杂性。选择应用于每一种情形的特定的动作(specific action)(即方法)是编译器的任务,程序员无需手工进行选择。你只需记住并且使用通用接口即可。

如果用得当,在由多态性、封装性和继承性共同组成的编程环境中可以写出比面向过程模型环境更健壮、扩展性更好的程序。精心设计的类层级结构是重用你花时间和努力改进并测试过的程序的基础,封装可以使你在不破坏依赖于类公共接口的代码基础上对程序进行升级迁移,多态性则有助于你编写清楚、易懂、易读、易修改的程序。

除了面向对象特性外,Java还具有其它的特点。

3.2  Java的特性

作为一种程序语言,Java拥有许多重要的特性:简单的、面向对象的、网络的、解释的、健壮的、安全的、可移植的、高性能的、多线程以及动态性

前面已对面向对象特性做了具体的说明,下面针对其它的特性加以说明。

1.简单性:Java语言通过提供最基本的方法来完成指定的任务 ,只需理解一些基本的概念 ,就可以用它编写出适合于各种情况的应用程序。 Java略去了运算符重载、多重继承等模糊的概念,并且通过实现自动垃圾收集大大简化了程序设计者的内存管理工作。另外,Java也适合于在小型机上运行,它的基本解释器及类的支持只有 40KB左右,加上标准类库和线程的支持也只有215KB左右。库和线程的支持也只有 215KB左右。

2.网络性:Java本身等于就是通过学习网络而产生的,它的许多功能与应用都是与网络相关,从最初的确良Applet、简化的Socket、交互式的JSP/Servlet网络程序到今日热门的Web Serivice等,都注定了Java在网络相关的领域占有一席之地。事实上,Java应用最多的领域也正是网络服务这一块。

3.解释执行:Java解释器直接对 Java字节码进行解释执行。字节码本身携带了许多编译时信息,使得连接过程更加简单。

4.健壮性:JavaC/C++中一些功能强大但不容易掌握的功能去除了。以指针功能为例,即使是有经验的开发人员在使用指针功能时也得小心翼翼,避免编写出使程序崩溃的错误,诸如此类的功能在Java中被去除,为的是让Java在使用时更为简单,编写出来的程序更为健壮。

5.安全性:用于网络、分布环境下的 Java必须要防止病毒的入侵。 Java不支持指针, 一切对内存的访问都必须通过对象的实例变量来实现 ,这样就防止程序员使用“特洛伊”木马等欺骗手段访问对象的私有成员,同时也避免了指针操作中容易产生的错误。

6.可移植性:又可称为独立于平台性。是指无需修改程序便能够运行在不同的计算机环境中。Java程序被编译成一种名为字节码的文件,字节码可被任何带有Java解释器的操作系统、软件或设备运行。

7.高性能:一般情况下,可移植性,稳定性和安全性几乎总是以牺牲性能为代价的,解释型语言的执行效率一般也要低于直接执行源码的速度。但Java所采用的措施却很好的弥补了这些性能差距。Java字节码格式的设计充分考虑了性能的因素,其字节码的格式非常简单,这使得经由Java解释器解释执行后可产生高效的机器码。Java编译器生成的字节码和机器码的执行效率相差无几。据统计,Java字节码的执行效率非常的接近于由CC++生成的机器码的执行效率。

8.多线程:多线程机制使应用程序能够并行执行 ,而且同步机制保证了对共享数据的正确操作。通过使用 多线程 ,程序设计者可以分别用不同的线程完成特定的行为 ,而不需要采用全局的事件循环机制 ,这样就很容易地实现网络上的实时交互行为。

9.动态性:Java的设计使它适合于一个不断发展的环境。在类库中可以自由地加入新的方法和实例变量而不会影响用户程序的执行。并且 Java通过接口来支持多重继承 ,使之比严格的类继承具有更灵活的方式和扩展性。

3.3  Java语言的前景

Java语言有着广泛的应用前景,大体上可以从以下几个方面来考虑其应用:

1.所有面向对象的应用开发,包括面向对象的事件描述、处理、综合等;

2.计算过程的可视化、可操作化的软件的开发;

3.动态画面的设计,包括图形图像的调用;

4.交互操作的设计(选择交互、定向交互、控制流程等); 

5. Internet的系统管理功能模块的设计,包括Web页面的动态设计、管理和交互操作设计等;

6.Intranet(企业内部网)上的软件开发(直接面向企业内部用户的软件);

7.与各类数据库连接查询的SQL语句实现;

8.进行手机通讯和其他的一些嵌入式的开发(比如手机和网络游戏);

9.其它应用类型的程序。

4  Java语言实现图像处理

4.1  图像增强技术

    数字图像的增强是图像处理中的一个重要研究内容之一,是图像处理的一项基本技术。图像增强是指按特定的需要突出一幅图像的某些信息,同时,削弱或除去某些不需要的信息的处理方法。

图像增强在人眼对图像的识别中很重要。人眼有这样一视觉特性:由人眼的视觉和人的心理特性可知,变化幅度较大,细节丰富的区域容易引起人眼的注意,而变化平坦的区域则不容易注意。在人们对视觉的研究中,进行过如下实验,将两幅图同时放在测试者的视野中,其中一幅有黑色线条,另一幅全部都为白色,用仪器分别记录下眼睛注视两幅图的时间,结果表明了人眼有70%的时间在注视带线条的那一幅,这说明变化剧烈的图像更吸引人的注意力,对灰度图像而言,人眼会将注意力集中在灰度值变化大的区域。根据人眼的这一视觉特性,图像处理中对灰度图像的增强就是为了解决这一视觉上的障碍。

图像增强的方法分为两大类:空间域方法和频率域方法。

空间域增强是指增强构成图像的像素。空间域方法是直接对这些像素操作的过程。空间域处理可由下式定义:

                                                  (4.1)

其中是增强处理前的图像函数,是增强处理后的图像函数,是对的一种操作,其定义在的邻域。式(4.1)可用图4.1表示。

图4.1 空间域方法示意图

频率域方法是在图像的某种变换域内对图像的变换值进行运算,如表示增强处理前的图像函数,表示空间运算函数,增强处理后的图像函数是由和的卷积的,即

                                  (4.2)

根据卷积理论,在频域中有下面的变换关系

                                                (4.3)

这里、和分别表示、和的傅立叶变换,称为传递函数。

在实际应用中,可根据需要先对图像函数进行傅立叶变换,并选定传递函数,然后由式(4.3)计算出,最后通过傅立叶反变换得出增强处理后的图像函数,即

                                (4.4)

4.1.1  灰度变换

如果一幅图像灰度的对比度差,图像的质量就不好。为了改善图像灰度的对比度,可以对图像中样点的灰度进行刻度尺方面的改变。假设和分别表示原始图像及增强处理后图像像素的灰度。这样使原始图像的像素灰度转换成增强后图像对应像素的灰度转换关系的一般表达式为

                                       (4.5)

线性灰度变换的一般表达式

                                                 (4.6)

其中:a为图像的对比度,如果a>1,对比度增强,如果a<1,对比度减弱。

      b为图像的亮度,如果b>0,亮度增强,如果b<0,亮度降低。

下面来看一下线性灰度变换的Java代码实现,如下只列出如何实现算法的代码。

              red=(int)(a * red + b);

                            green=(int)(a * green + b);

                            blue=(int)(a * blue + b);

                            if(red>=255)

                            {

                                   red=255;

                            }

                            if(green>=255)

                            {

                                   green=255;

                            }

                            if(blue>=255)

                            {

                  blue=255;

                            }

a是调整图像对比度的变量,b是调整图像亮度的变量。由于R、G、B分量空间范围是0~255,所以需设置一个限定条件,防止越界。

从图4.2中可以清晰的对比出经线性变换后的图像更容易观察出病灶,进而减少误诊率。

图4.2 左图为原始图像,右图为经线性灰度变换后图像

上述所讨论的线性变换为正比变换,下面简要说明一下反比变换,在某些情况下,反比变换得到的图像更容易观察出病变。

灰度级范围为[0,L-1]的图像反转变换的表达式为:

                                                      (4.7)

其中:和分别表示处理前后的像素值。

用这种方式倒转图像的强度产生图像反转的对等图像。这种处理尤其适用于增强嵌入于图像暗色区域的白色或灰色细节,特别是当黑色面积占主导地位时。一个例子示于图4.3,原始图像为一乳房的数字X照片,可看到有一小块病变。尽管事实上两幅图在视觉内容上都一样,但注意,在这种特殊情况下,分析乳房组织结构时反转图像要容易得多。

下面来看一下反比变换的Java代码实现,如下只列出了某一像素点反比变换代码。

int red=255-cm.getRed(pixels[i*iw+j]);

                    int green=255-cm.getGreen(pixels[i*iw+j]);

                    int blue=255-cm.getBlue(pixels[i*iw+j]);

   

图4.3 左图为原始数字乳房照片,右图为反变换得到的反转图像

4.1.2  伪彩色处理

   伪彩色处理是用彩色来代替像素灰度值的一种技术。由于人眼对彩色的分辨率远高于对灰度差的分辨率,所以这种技术可用来识别灰度差较小的像素。这是一种视觉效果明显而技术又不是很复杂的图像增强技术。灰度图像中,如果相邻像素点的灰度相差不大,人眼将无法从图像中提取相应的信息,因为人眼分辨灰度的能力很差,一般只有几十个数量级,但是人眼对彩色信号的分辨率却很强,这样将黑白图像转换为彩色图像后,人眼可以提取更多的信息量。在转换过程中,对灰度图像中的每一个像素点,取得该点的灰度值并送入红、绿、蓝三个通道实施不同的变换,产生相应的红、绿、蓝的亮度值。

图4.4是一种简单的映射变换曲线,按照这张曲线图,可以轻松地将灰度图像的256个灰度级转换为一幅伪彩色图像。

图4.4 一种简单的映射变换曲线

由图4.4所示的伪彩色还原映射变换曲线图,得到如下映射函数,其中,、、表示伪彩色灰度值,表示原始灰度图像的灰度值。

                             (4.8)

                            (4.9)

                            (4.10)

公式(4.8)、(4.9)、(4.10)给出了一种通过映射变换曲线给灰度着色的方法,其Java代码实现如下:

        int grey = pixels[i*iw+j]&0xff;

        int alpha=cm.getAlpha(pixels[i*iw+j]);

int red,green,blue ;

switch(grey/64)

              {

                    case 0:red = 0;green = 4 * green;blue = 255;break;

                    case 1:red = 0;green = 255;blue = 511 - 4 * grey;break;

                    case 2:red = 4 * grey – 511;green = 255;blue = 0;break;

                    case 3:red = 255;green = 1023 - 4 * grey;blue = 0;break;

           } 

        pixels[i*iw+j]=alpha<<24|red<<16|green<<8|blue;

grey为某像素点的灰度值,因为图像已经是灰度色,所以在这里只取蓝色分量作为灰度进行运算。

上面给出了一种通过映射变换曲线给灰度着色的方法,下面再给出一种通过色彩表映射的方法给灰度着色。对于任意一幅图像,可以先将灰度降到16级,然后根据灰度与相应的颜色对应关系,求出新的颜色。表4.1表示不同灰度级与相应的颜色对应关系。表中的序号表示0~15共16个灰度级,序号下方就是该灰度级将要映射的颜色。

表4.1 灰度级颜色对照表

0

1

2

3

4

5

6

7

深蓝

深绿

深红

深灰

蓝紫

草绿

#000000

#000055

#005500

#550000

#3F3F3F

#550055

#0000FF

#555500

8

9

10

11

12

13

14

15

绿

深蓝绿

#00FF00

#FF0000

#808080

#00FFFF

#FFFF00

#FFFFFF

#005555

#FF00FF

色彩表映射的方法很简单:只需将原图像中所有灰度值 oldGray降为16级灰度,即对于任意一点,其新的灰度级为:

newGray=16×oldGray / 255                               (4.11)

然后根据新的灰度级与表4.1进行对照,求出新的颜色。其Java代码实现如下:

int alpha=cm.getAlpha(pixels[i*iw+j]);

int grey = pixels[i*iw+j]&0xff;

int[] colorTable = {0x000000,0x000055,0x005500,0x550000,0x3F3F3F,

0x550055,0x0000FF,0x555500,0x00FF00,0xFF0000,0x808080,

0x00FFFF,0xFFFF00,0xFFFFFF,0x005555,0xFF00FF};

int newGrey = grey * 16 / 255;

pixels[i*iw+j]=alpha<<24|colorTable[newGrey];

其实从伪彩色的概念就可以理解,伪彩色不是真彩色,是一种人为着色。所以在处理这项技术时,方法是多种多样的。除了上面提到的16级灰度着色外,还可以采用更多级的灰度着色,如256级灰度着色。总之,不管怎样构造这张色彩表,它都是一种人为着色,其目的就是要突出目标,方便人查看。

图4.5 左图为原始图像,右图为经256级灰度着色后图像

4.1.3  平滑化处理

图像的平滑处理技术即图像的去噪声处理。主要是为了去除实际成像过程中,因成像设备和环境所造成的图像失真,提取有用信息。图像平滑处理在消除或减弱图像噪声和假轮廓的同时,对图像细节也有一定的衰减作用。因此,图像平滑的直观效果是图像噪声和假轮廓得以去除或衰减,但同时图像将变得比处理前模糊了,模糊的程度要看对高频成份的衰减程度而定。

邻域平均法:邻域平均法是一种简单的在空间对图像进行平滑处理的方法,它易于实现,效果也较好。邻域平均法的基本思想是:由于噪声是图像上的一些样点的灰度造成突变,那么就可以以这样的样点为中心取一个邻域,用邻域内其他样点的灰度平均值来代替要处理的样点的灰度,其结果对亮度突变的点产生了“平滑”的效果。

假设图4.6是在某一图像中取一个邻域,其中e点认为是噪声点,那么就以e点为中心取了这样一个邻域,在处理后图像中e点的灰度值为

                            (4.12)

式中a、b、c、d、f、g、h、i分别为邻域内各个样点的灰度值

a

b

c

d

e

f

g

h

i

图4.6 以e为中心的一个邻域

下面来看平滑化处理的核心代码:

ColorModel cm=ColorModel.getRGBdefault();

for(int i=1;i<ih-1;i++)

{

       for(int j=1;j<iw-1;j++)

{      

              int alpha=cm.getAlpha(pixels[i*iw+j]);

              int red=cm.getRed(pixels[i*iw+j]);

              int green=cm.getGreen(pixels[i*iw+j]);

              int blue=cm.getBlue(pixels[i*iw+j]);

              int red1=cm.getRed(pixels[(i-1)*iw+j-1]);

              int red2=cm.getRed(pixels[(i-1)*iw+j]);

              int red3=cm.getRed(pixels[(i-1)*iw+j+1]);

              int red4=cm.getRed(pixels[i*iw+j-1]);

              int red6=cm.getRed(pixels[i*iw+j+1]);

              int red7=cm.getRed(pixels[(i+1)*iw+j-1]);

              int red8=cm.getRed(pixels[(i+1)*iw+j]);

              int red9=cm.getRed(pixels[(i+1)*iw+j+1]);

              int averageRed=(red1+red2+red3+red4+red6+red7+red8+red9)/8;

                           

              int green1=cm.getGreen(pixels[(i-1)*iw+j-1]);

              int green2=cm.getGreen(pixels[(i-1)*iw+j]);

              int green3=cm.getGreen(pixels[(i-1)*iw+j+1]);

              int green4=cm.getGreen(pixels[i*iw+j-1]);

              int green6=cm.getGreen(pixels[i*iw+j+1]);

              int green7=cm.getGreen(pixels[(i+1)*iw+j-1]);

              int green8=cm.getGreen(pixels[(i+1)*iw+j]);

              int green9=cm.getGreen(pixels[(i+1)*iw+j+1]);

              int averageGreen=(green1+green2+green3+green4+green6+green7+

green8+green9)/8;

                           

              int blue1=cm.getBlue(pixels[(i-1)*iw+j-1]);

              int blue2=cm.getBlue(pixels[(i-1)*iw+j]);

              int blue3=cm.getBlue(pixels[(i-1)*iw+j+1]);

              int blue4=cm.getBlue(pixels[i*iw+j-1]);

              int blue6=cm.getBlue(pixels[i*iw+j+1]);

              int blue7=cm.getBlue(pixels[(i+1)*iw+j-1]);

              int blue8=cm.getBlue(pixels[(i+1)*iw+j]);

              int blue9=cm.getBlue(pixels[(i+1)*iw+j+1]);

              int averageBlue=(blue1+blue2+blue3+blue4+blue6+blue7+blue8+blue9)/8;

             pixels[i*iw+j]=alpha<<24|averageRed<<16|averageGreen<<8|averageBlue;

       }

}

图4.7 左图为原始图像,右图为平滑处理后的图像

从图4.7中可以看出,虽然经过平滑处理后图像变得比处理前模糊了,但可以去除实际成像过程中,因成像设备和环境所造成的图像失真,即消除或减弱图像噪声和假轮廓,提取有用信息。

4.1.4  其他图像增强技术

在前面几节,介绍了三种常用的医学图像增强技术,由于篇幅的原因,还有几种技术并不做详细的讨论,在此,做一简单的介绍,功能本人已经实现,代码见附录C。

中值滤波:低通滤波可以减弱图像中的噪声,但同时也使图像细节模糊了;而高通滤波增强了图像中的边缘细节,但同时也增强了噪声信号。中值滤波是介于两者,在一定条件下,中值滤波方法可以做到既减弱了噪声又保护了图像的细节,得到较好的处理效果。

中值滤波的基本思想是把数字序列中一点的值用该点的一个邻域中各点的中值来代替。假设有一组数值,,,……,,且这个数值按大小顺序排列成……,则这个数组的中值为

               (4.13)

锐化处理:图像在传输和转换过程中,一般来讲,质量都要降低,除了噪声的因素之外,图像一般都要变得模糊一些。这主要因为图像的传输或转换系统的传递函数对高频成分的衰减作用,造成图像的细节轮廓不清晰。图像锐化的作用就是补偿图像的轮廓,使图像较清晰。

如果说对图像取平均的效果是使图像的细节变模糊,而微分与积分在数学上是相对立的,因此可以说微分处理将使图像的细节清晰。根据上述思想引入了微分尖锐化处理方法。梯度是一种微分运算,在微分尖锐化处理方法中最常用的就是梯度法,它的基本思想是:设图像函数为,它的梯度为,数字图像某样点处的梯度值和邻近样点间的灰度差成正比,因此在图像灰度变化平缓区域较小,在线条轮廓处等灰度变化快的区域,较大,这样就可以用梯度值来代替处的灰度值,经过这样变换后的图像在线条轮廓等灰度突变处更明显,从而达到锐化的目的。

4.2  图像分割技术

图像分割的直接结果是得到了区域内的像素集合,或位于区域边界上的像素集合,这两个集合是互补的。在图像分析应用中我们感兴趣的常常仅是图像中的某些区域,通常称之为目标。

图像分割是图像处理的另一个重要分支,它的输出不一定是一幅完整的图像,可能是图像的某些特性描述,其目的是从一幅图像中找出所需要的目标,或将图像划分成性质不同的若干区域。

医学图像分割就是一个根据区域间的相似或不同把图像分割成若干区域的过程。目前,主要以各种细胞、组织与器官的图像作为处理的对象。例如磁共振颅脑图像的分割,其目的就在于清晰地描绘出颅脑各个解剖结构的边界,如灰质、白质、脑脊液以及MR图像中的其它组织。

4.2.1  阈值分割法

从一幅图像中找到目标最常用的方法是在该图像中确定区分目标与背景分界点,即阈值,例如一幅图像的分布范围[z1zk],令为z1 zk之间的任意值,选取为阈值可将图像变为

                                          (4.14)

其中为图像在点像素的灰度值。由些可以获得一幅分割后的二值图像。(4.14)式也可以写成更一般的形式,即

                                             (4.15)

上式表示,将图像像素各点的灰度值与门限值T比较.如果像素的灰度值大于或等于门限值,则设为一种灰度值,如果小于,则设为另一种灰度值。

也可以将阈值设为一个灰度范围[,],如果图像的灰度落在该范围内,则灰度全部变为1,其余像素的灰度级都变为零,即

                                       (4.16)

在某些情况下,也可以设置灰度门限值,凡灰度级高于此门限值的像素,均保持原灰度级不变,其余像素的灰度级变为零,分割后的图像可表示为

                                   (4.17)

假设某一图像中,凡是属于“目标”部分的像素点的灰度级都比较高,而属于“背景”部分像素点的灰度级都比较低,这样在“目标”与“背景”两部分间可能存在一个灰度的边界,这个边界就可以作为灰度的阈值,根据图像像素点的灰度即将图像空间划分成一些区域,在这些区域内部其特性是相同的,或者说是均匀的,两个相邻区域彼此特性是不相同的,其间存在着边界,这个边界称为灰度阈值,这种方法称为灰度阈值分割法。如果能通过某种科学的方法求出一个阈值,并认为灰度值大于的点属于目标点,而灰度值小于的点是背景点,那么就可以从一整幅图像中找到目标部分了。

Java代码实现图像二值化处理:

           int grey=100

                     String s=JOptionPane.showInputDialog(null,"输入二值化的

阈值(0-255)",100)

                   if(s!=null)

                     {

                        grey=Integer.parseInt(s)

           }

                     if(grey>255)

                     {

                        grey=255

                     }else if(grey<0)

                     {

                            grey=0

                     }

           ColorModel cm=ColorModel.getRGBdefault()

                     for(int i=0i<iw*ihi++)

                     {

                            int red,green,blue

                            int alpha=cm.getAlpha(pixels[i])

                            if(cm.getRed(pixels[i])>grey)

                            {

                                   red = 255

                            }else{ red=0}

                                  

                            if(cm.getGreen(pixels[i])>grey)

                            {

                                   green=255

                            }else{green=0}

                                  

                            if(cm.getBlue(pixels[i])>grey)

                            {

                                   blue=255

                            }else{blue=0}

                                  

                            pixels[i]=alpha<<24|red<<16|green<<8|blue

                     }

 

4.8 左图为原始颅骨图像,右图为经二值化处理后的图像

    从图4.8中可以看出,选择适当的阈值对图像进行二值化处理后可以清晰的观察出病灶位置及大小,实现了将病灶分割出来,方便了医生诊断。

4.2.2  边缘检测法

图像边缘对图像识别和计算机分析十分有用。边缘提取首先检查出图像局部性特性的不连续性,然后再将这些不连续的边缘像素连成完备的边界。边缘的特性是沿边缘走向的像素变化平缓,而垂直与边缘方向的像素变化剧烈。所以,从这个意义上讲,提取边缘的算法就是检查出符合边缘特性的边缘像素的数学算子。

常用的边缘检测算子有Roberts算子、Laplace算子、Sobel算子等。

  1. Roberts算子

图像的梯度定义为

                                      (4.18)

梯度的模为

                                   (4.19)

通常把梯度的模就叫做图像的梯度。Roberts算子是用斜向上4个像素交叉差分来表示梯度的,即

     (4.20)

上式也可以简化为

                    (4.21)

也可以表示成掩模的形式如图4.9所示

  1. 0

0   -1

                          

  1.  1

-1   0

4.9 Roberts算子

2Laplace算子

可以利用对图像的各个像素的二阶导数和之和的方法寻找边界。即

                               (4.22)

对于数字图像,可以用差分近似微分,即可对图像的每个像素取方向和方向的二阶差分之和来近似上式,因此有

                    (4.23)

0

1

0

1

-4

1

0

1

0

这就是Lap 

与方向无关的边缘检测算子对检出边缘点是合适的。 

Laplace算子相当于一个图4.10所示的滤波器。

                                                         4.10 Laplace算子

3.Sobel算子

Sobel算子是一种简单常用的算子,它是对数字图像的每个像素,考查其相邻点像素灰度的加权差,即

                                (4.24)

Sobel算子可以写成图4.11的掩膜形式。

1

0

-1

1

2

1

2

0

-2

0

0

0

1

0

-1

-1

-2

-1

4.11 Sobel算子

Java语言实现Robert梯度算法:

ColorModel cm=ColorModel.getRGBdefault()

for(i=1i<ih-1i++)

{

       for(int j=1j<iw-1j++)

       {

              int alpha=cm.getAlpha(pixels[i*iw+j])

              int red5=cm.getRed(pixels[i*iw+j])

              int red6=cm.getRed(pixels[i*iw+j+1])

              int red8=cm.getRed(pixels[(i+1)*iw+j])

              int red9=cm.getRed(pixels[(i+1)*iw+j+1])

              int robertRed=Math.max(Math.abs(red5-red9),Math.abs(red8-red6))

                                  

              int green5=cm.getGreen(pixels[i*iw+j])

              int green6=cm.getGreen(pixels[i*iw+j+1])

              int green8=cm.getGreen(pixels[(i+1)*iw+j])

              int green9=cm.getGreen(pixels[(i+1)*iw+j+1])

int robertGreen=Math.max(Math.abs(green5-green9),

Math.abs(green8-green6))                         int blue5=cm.getBlue(pixels[i*iw+j])

              int blue6=cm.getBlue(pixels[i*iw+j+1])

              int blue8=cm.getBlue(pixels[(i+1)*iw+j])

              int blue9=cm.getBlue(pixels[(i+1)*iw+j+1])

              int robertBlue=Math.max(Math.abs(blue5-blue9),Math.abs(blue8-blue6))

                                                                              pixels[i*iw+j]=alpha<<24|robertRed<<16|robertGreen<<8|robertBlue

       }

4.12 左图为原始骨骼图像,右图为经边缘检测后的图像

    从图像中可以观察出,经边缘检测后腿部骨骼的边缘清晰的显示出来,从医学角度上看,医生可以根据骨骼的边缘信息进行直观的诊断,更加容易确诊。

4.3  图像复原技术

    图像复原和前面讨论的图像增强的目的都是改善图像质量,但改善的方法和评价的标准则不同。图像增强是突出图像中感兴趣的特征,衰减那些不需要的信息,因此它不考虑图像退化的真实物理过程,增强后的图像也不一定去逼近原始图像;而图像复原则是针对图像的退化原因设法进行补偿,这就需要对图像的退化过程有一定的先验知识,利用图像退化的逆过程去恢复原始图像,使复原后的图像尽可能地接近原图像,因此,复原应有一个客观的质量标准,以指导实际复原接近最佳。

本人并没有太多涉及图像复原方向的内容,在这里只对这种技术作一些简单的理论介绍。

如图4.13所示,退化过程中可以被模型化为一个退化函数和一个加性噪声项,处理一幅输入图像产生一幅退化图像。给定和关于退化函数的一些知识以及外加噪声项,图像复原的目的是获得关于原始图像的近似估计。通常我们希望这一估计尽可能接近原始输入图像,并且和的信息知道得越多,所得到的就会越接近。

                                  

                             噪声   

    

退化函数

   复原滤波

                     4.13 图像退化/复原过程的模型

复原前,图4.13中的输出关系可表示为:

                                (4.25)

为了讨论方便,可将噪声假设为零,则。如果

             (4.26)

则系统是一个线性系统。这里,和是比例常数,和是任意两幅输入图像。

若==1,式(4.26)变为:

                  (4.27)

就是所谓的加性。这一特性简单地表明,如果为线性算子,那么,两个输入之和的响应等于两个响应之和。

如果,式(4.26)变为:

                                  (4.28)

这称为均匀性。它表明任何与常数相乘的输入的响应等于该输入响应乘以相同的常数。即一个线性算子具有加性和均匀性。

对于任意,和,如果

                                   (4.29)

则一个具有输入输出关系的系统称为位置不变系统(或空间不变系统)。这个定义说明图像中任一点的响应只取决于在该点的输入值,而与该点的位置无关。

如果将退化过程看成是线性空间不变的,就可以利用线性系统的理论来解决图像的复原问题,而且实际中大多数退化过程中是可以用线性空间不变系统近似的。但这种假设并不总是合理的,讨论非线性空间变化的退化模型可能更具有普遍的实际意义,也会更加准确,但同时由于问题的解算过于困难而难以实现。

4.4  本章小结

    图像是信息可视化的重要手段。一幅医学图像以直观的形式给医生提供辅助诊断和治疗的信息。但是,从仪器出来的原始图像由于受到成像设备和获取条件等因素的影响,可能出现图像质量的退化,甚至伪迹;而高质量的图像有时也很难用肉眼直接得到有用的诊断信息。所以对医学图像进行处理显得特别的重要。要对医学图像进行处理,首要的任务就是对获取的图像进行增强和分割,即滤除噪声和干扰,突出感兴趣的区域或边缘,从面为进一步分析和处理奠定基础,从图像得到定量和更深刻的信息。

5  设计流程

5.1  主流程图

本系统的总体设计流程图如图5.1所示

设计Java GUI

选择组件

设置组件的布局

为控制图像处理的组件添加监听器

添加文件选择器

创建数据库

连接数据库

实现数据库的查询

及插入

实现图像的加载

实现图像处理

图5.1 总体设计流程图

程序设计流程图大体介绍:

首先设计Java GUI即Java用户图形界面,对其中的按钮,菜单添加事件监听器,以便能够当用户触发事件做出相应的响应,然后添加了文件选择器以及连接数据库并实现了数据库查询、插入功能,使用户可以从本地磁盘或从数据库中查询图像。将图像显示在GUI后就可以通过相应的按钮对图像进行处理。

具体模块的实现将在以下几节中详细介绍。

5.2  图像处理界面

本设计的图形用户界面如图5.2所示。

图5.2 图像处理界面

    在这个界面中,无论是按钮还是菜单选项,都添加了监听器用来监听是否有事件发生,并且都有一个相应的事件处理器。当用户单击其中任何一个都会触发相应的事件,然后将事件发送到事件处理器中进行处理。界面的左侧用于显示载入的图像,即原始图像,右侧用于显示处理后图像,以便对处理前后的图像进行对比。详细代码参见附录C。

5.3  图像的加载

图像加载的流程图如图5.3所示。

创建MediaTracker类的对象跟踪图像的加载

等待图像完全加载

异常处理

获取图像的宽度iw和高度ih

创建一个iw*ih大小的数组pixels用于存储图像像素

创建PixelGrabber类的对象

将图像中每点的像素存储在数组pixels中

异常处理

创建MemoryImageSource类的对象将数组中的像素产生一幅图像

调用repaint方法

图5.3 图像加载流程图

本设计可以处理从本地磁盘或从数据库中调出的图像,但有一个共同点就是所采用的加载方式是一样的,都是利用MediaTracker类的对象跟踪图像的加载,看如下代码:

MediaTracker tracker = new MediaTracker(this);

          Image im=Toolkit.getDefaultToolkit( ).getImage(filename);

tracker.addImage(im,0);

MediaTracker类是一个跟踪多种媒体对象状态的实用工具类。媒体对象可以包括音频剪辑和图像,但目前仅支持图像。要使用媒体跟踪器,需要创建一个MediaTracker实例,然后对每个要跟踪的图像调用其addImage方法。另外,还可以为每个图像分配一个惟一的标识符,此标识符可控制获取图像的优先级顺序。此处将标识符设置为0。Image是表示图形图像的所有类的超类。Toolkit.getDefaultToolkit( ).getImage(filename);返回一幅图像,类型为Image。该图像从指定文件filename中获取像素数据。同时也为调用Image类中的获取图像长和宽的方法创造了条件。接着看下面的代码:

int iw=im.getWidth(this);

          int ih=im.getHeight(this);

          int[] pixels=new int[iw*ih];

以上三段代码是对图像im分别获得其长和宽,然后创建数组,数组的长度为图像长和宽的乘积,即为图像中点的数量。

try{

          PixelGrabber pg=new PixelGrabber(im,0,0,iw,ih,pixels,0,iw);

          pg.grabPixels( );

          }catch (InterruptedException e) {

                    e.printStackTrace( );

               }

PixelGrabber类实现可以附加在Image或ImageProducer对象上以获得该图像像素子集的ImageConsumer。上述代码首先创建一个PixelGrabber对象,以便从指定的图像im中将像素矩形部分(0,0,iw,ih)抓取到给定的数组pixels中。以默认的RGB ColorModel形式将像素存储到数组中。然后调用grabPixels()方法请求Image开始传递像素,并等待传递完相关矩形中的所有像素。如果成功抓取了像素,则返回true;在中止、有错误或超时的情况下返回false。所以将其放在try语句中,以捕获异常。

ImageProducer ip=new MemoryImageSource(iw,ih,pixels,0,iw);

              Image tmp=createImage(ip);

              repaint();

MemoryImageSource类是ImageProducer接口的一个实现,该接口使用一个数组为Image生成像素值。上面语句构造一个使用默认RGB ColorModel中的整数数组为Image对象生成数据的ImageProducer对象,然后createImage使用指定的图像生成器创建一幅图像。repaint( )方法会自动调用paint( ),将图像显示在组件上。(如图5.4所示)

public void paint(Graphics g)

          {

                   if(flagLoad)

                   {

                           g.drawImage(tmp,9,171,this);

                     }

}

图5.4 加载图像

5.4  图像的处理

本设计中所实现的灰度变换、伪彩色处理、平滑处理、阈值分割及边缘检测所采用的方法是对图像中各像素的R、G、B三个分量做运算。其流程图如图5.5所示。

创建ColorModel类

对象

运用ColorModel类中的方法将像素值转换为颜色分量(R、G、B)和alpha分量

运用反比灰度变换算法对图像的R、G、B分量作运算

运用伪彩色处理算法对图像的R、G、B分量作运算

运用平滑处理算法对图像的R、G、B分量作运算

运用二值化处理算法对图像的R、G、B分量作运算

运用边缘检测算法对图像的R、G、B分量作运算

运用正比灰度变换算法对图像的R、G、B分量作运算

将图像像素R、G、B分量运算后的结果及alpha分量值存储在像素数组pixels中

创建MemoryImageSource类的对象将数组中的像素产生一幅图像

调用repaint方法

Y

图像是否加载

图5.5 灰度变换、伪彩色处理、平滑处理、阈值分割及边缘检测流程图

如果图像已加载,则首先创建一个ColorModel对象,此ColorModel抽象类封装了将像素值转换为颜色分量(例如,红色、绿色和蓝色)和alpha分量的方法。为了将图像呈现到屏幕、打字机或其他图像上,必须将像素值转换为颜色和alpha分量。看如下代码:

        ColorModel cm=ColorModel.getRGBdefault();

                for(i=0;i<ih;i++)

                {

                    for(int j=0;j<iw;j++)

                    {

                           int alpha=cm.getAlpha(pixels[i*iw+j]);

                           int red=cm.getRed(pixels[i*iw+j]);

                           int green=cm.getGreen(pixels[i*iw+j]);

                           int blue=cm.getBlue(pixels[i*iw+j]);

          }

        }

上面的语句可得到每个像素点的R、G、B及alpha分量的值,然后通过对各点的R、G、

B做运算即可实现图像的处理。最后将运算后图像的颜色分量及alpha分量值存入像素数组pixels中,应用MemoryImageSource类中的方法产生图像。

例如:下面的代码是实现图像反转灰度变换模块的完整代码。其结果如图5.6所示。

                public void jfanzhuan_ActionPerformed(ActionEvent e)

               {

                      if(flagLoad)

                      {

                           ColorModel cm=ColorModel.getRGBdefault();                                                                  for(i=0;i<ih;i++)

                           {

                                  for(int j=0;j<iw;j++)

                                  {

                                         int alpha=cm.getAlpha(pixels[i*iw+j]);

                                         int red=255-cm.getRed(pixels[i*iw+j]);

                                         int green=255-cm.getGreen(pixels[i*iw+j]);

                                         int blue=255-cm.getBlue(pixels[i*iw+j]);

                                         pixels[i*iw+j]=alpha<<24|red<<16|green<<8|blue;

                                  }

                            }

                         ImageProducer ip=new MemoryImageSource(iw,ih,pixels,0,iw);

                           tmp=createImage(ip);

                           repaint();

                    }else{

                          JOptionPane.showMessageDialog(null,"请先打开一幅图片!",

                                      "提示",JOptionPane.WARNING_MESSAGE);

                      }

                }

图5.6 点击反转灰度变换后的GUI

5.5  数据库的建立

本设计实现了一个医学图像档案,即创建了一个数据库用于存放病人ID,姓名及其图像信息。可以通过病人的ID进行图像信息的查询,还可以将新的图像信息添加进此档案中。其设计流程图如图5.7所示。

创建数据库

连接数据库

实现从数据库中查询功能

实现向数据库中插入功能

将从数据库中查询的图像信息

显示于GUI

异常处理

异常处理

提示插入成功

用户退出系统

断开与数据库的连接

异常处理

图5.7 数据库设计流程图

本设计所使用的数据库为Access数据库,首先用Access创建了image.mdb,在其中创建了一个名为pic的表用于存放病人ID,姓名及其图像信息。

创建完数据库后就要对数据库进行连接,看如下代码:

                    try

                        {

                              Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

                              con = DriverManager.getConnection("jdbc:odbc:image");

                              stmt = con.createStatement();

                        }

                        catch(Exception e){}

上述语句首先调用了Class类的forName方法,将返回一个sun.jdbc.odbc.JdbcOdbcDriver类的对象,称为加载JDBC-ODBC Bridge驱动器。然后通过调用DriverManager 类的getConnection方法,连接地址为jdbc:odbc:image的数据库,并且返回一个Connection类型的对象赋予变量con。这样便建立了与数据库image的连接。接着调用Connection类的createStatement方法,创建了一个Statement类的对象stmt,即创建了一个将SQL语句传递给数据库的Statement对象。

ResultSet rs = stmt.executeQuery("SELECT ID, 姓名,图片 FROM pic");

此句调用Statement对象的executeQuery方法执行SQL语句,将结果返回给一个ResultSet类型的变量rs,通过调用ResultSet类中的方法即可实现数据库的查询。

                 String s1=JOptionPane.showInputDialog(null,"请输入ID");

                 String s2 = JOptionPane.showInputDialog(null,"请输入姓名");

                 String strInc = "INSERT INTO pic(ID,姓名,图片) Values('"+s1+"','"+s2+"','"+filename+"')";

                 stmt.executeUpdate(strInc);

当执行插入操作时,程序首先弹出对话框要求用户输入ID及姓名,然后即可将GUI中的图像保存进数据库。详细代码参见附录C。

当用户退出本系统时,除了释放系统资源外还要关闭与数据库操作相关的链接,其代码如下:

rs.close();    //关闭记录集

stmt.close();  //关闭Statement对象

con.close();   //关闭数据库链接

6  开发工具

利用记事本和JDK开发Java程序虽然在理论上是完全可行的,但一般Java程序的开发都会借助一些工具加快开发速度。

JCreator是一个小巧灵活的Java开发工具,它可将Java的程序的编写、编译、运行和调试集成进自身的环境中直接进行开发,且无需进行环境变量的设定。我们可以直接在JCreator中编辑Java源文件,选择相应的菜单和点击对应的按钮就可以完成Java程序的编译、运行等工作,十分方便。由于JCreator集成了编辑源文件编译运行调试为一体所以又被称为IDE(Integration Developer Environment集成开发环境),其他的具有类似特点的开发工具也被称为IDE

6.1  JCreator概述

1JCreator的特点

JCreator是共享软件用来开发基于Java的应用程序安装后只有4M左右且只需32M内存即可运行。

Java程序开发需要安装Java的开发工具包(JDK)JCreator安装程序本身并不附带所以需要先安装JDK才能利用JCreator进行开发

2JCreator的工程(Project)工作空间Workspace

JCreator利用工程(Project)理相应的文件一个工程由多个Java源文件和其他一些文件组成JCreator中工程文件的扩展名为jcp

JCreator的工作空间(Workspace)来管理工程一个工作空间中可以包含多个工程,JCreator中工作空间文件的扩展名为jcw

JCreator规定一个工程必须包含在一个工作空间中所以当我们创建一个工程时JCreator会自动给我们创建一个工作空间已包含当前工程JCreator在同一时刻只容许打开一个工作空间。

6.2  JCreator编辑界面的组成

JCreator的界面如图6.1所示

图6.1 JCeator界面

1.文件视图

文件视图用来显示工程中的所有文件,并且可以显示当前的工程名称和工程所在的工作空间的名称。

一个工作空间中可以包含多个工程,但只能有一个活动工程。活动工程是指当前起作用的工程。双击工作空间中的一个工程名字将使得该工程成为活动工程,活动工程的名字显示为黑体,而非活动工程显示为普通字体。

文件视图图解

2.代码视图

代码视图用来编辑工程文件的源代码。在文件视图中双击某个文件名称,即可在代码视图中对该文件进行编辑JCreator提供了代码帮助的功能。即如果输入相应的方法或变量,代码视图可提供该方法或变量的动态提示。如果代码提示不出现,可能的原因为JDK及相关类库文件没有正确设置或书写的代码有错误。

3.类视图

类视图显示了当前显示在代码视图中Java源文件的类的层次和类中的方法,双击某一方法将直接在代码视图中定位到该方法的定义处。

4.输出视图

输出视图主要用来输出编译相关的信息。如果有错误信息出现,双击错误信息的第一行即可在源代码中定位该错误。

随着远程医疗技术的蓬勃发展,对医学图像处理提出的要求也越来越高。医学图像处理技术发展至今,各个学科的交叉渗透已是发展的必然趋势,其中还有很多亟待解决的问题。有效地提高医学图像处理技术的水平,与多学科理论的交叉融合、医务人员和理论技术人员之间的交流就显得越来越重要。多维、多参数以及多模式图像在临床诊断与治疗中将发挥更大的作用。

本设计主要是用Java语言实现对医学图像的简单处理,包括线性灰度变换、伪彩色处理、平滑处理等图像增强算法和阈值分割、边缘检测等图像分割算法。并将处理后的结果显示于Java GUI中。通过这次设计,使我对数字图像处理的几种算法理解的更加深刻,也掌握了当今流行的一门编程语言Java,对以后的工作和学习有很大的帮助。

由于时间有限,设计只做了本地处理,也就是数据库中的图像信息是保存在本地的。Java的优势实际上在于Web方面,如果在医院有一台服务器,服务器的数据库中保存着病人的信息,我们就可以通过网络访问这台服务器,从数据库中查询病人的图像信息或向数据库中添加病人的图像信息,方便管理病人的档案。

参考文献

[1] 刘榴娣,刘明奇.《实用数字图像处理》.第一版.北京理工大学出版社.2003年

[2] 章梳晋.《图像处理和分析》.第一版.清华大学出版社.2006年

[3] 陈浩,李本富.《医学图像处理技术新进展》.第四军医大学学报.2004年,第5期

[4] 林信良.《Java JDK 5.0学习笔记》.清华大学出版社.2006年

[5] David Flanagan.《JAVA IN A NUTSHELL》.第三版.OREILLY出版社.1999年

[6] 王宏.《数字图像处理—Java语言实现》.第一版.东北大学出版社.2005年

[7] Rafael C.Gonzalez,Richard E.Woods.《冈萨雷斯 数字图像处理》.阮秋琦,阮宇智.第二版.电子工业出版社.2005年

附录A  英文原文

Object-Oriented Programming in Java

Java is an object-oriented programming language. All Java programs use objects, and every Java program is defined as a class.

If you do not have any object-oriented (OO) programming background, don’t worry; this chapter does not assume any prior experience. If you do have experience with OO programming, however, be careful. The term “object-oriented” has different meanings in different languages. Don’t assume that Java works the same way as your favorite OO language. This is particularly true for C++ programmers. Java uses object-oriented programming concepts that are familiar to C++ programmers and even borrows C++ syntax in a number of places, but the similarities between Java and C++ are not nearly as strong as those between Java and C. Don’t let your experience with C++ lull you into a false familiarity with Java.

The Member of a Class

A class is a collection of data, stored in named fields, and code, organized into named methods, that operates on that data. The fields and methods are called members of a class. The members of a class come in two distinct types: class, or static, members are associated with the class itself, while instance members are associated with individual instances of the class (i.e., with objects). This gives us four types of members:

· Class fields

· Class methods

· Instance fields

· Instance methods

The simple class definition for the class Circle, shown in Example 1, contains all four types of members.

Example 1: A Simple Class and its Members

Public class Circle {

         // A class field

         pubic static final double PI = 3.14159;        // A useful constant

         // A class method: just compute a value based on the arguments

         public static double radiansToDegrees(double rads) {

return rads * 180 / PI;

         }

         // An instance field

         public double r;                        // The radius of the circle

         // Two instance methods: they operate on the instance fields of and object

         public double area( ) {                // Compute the area of the circle

return PI * r * r;

         }

         public double circumference( ) {// Compute the circumference of the circle

           return 2 * PI * r;

         }

}

Class Fields

A class field is associated with the class in which it is defined, rather than with an instance of the class. The following line declares a class field:

       public static final double PI = 3.14159;

This line declares a field of type double named PI and assigns it a value of 3.14159. As you can see, a field declaration looks quite a bit like the local variable declarations. The difference, of course, is that variables are defined within methods, while fields are members of classes.

The static modifier says that the field is a class field. Class fields are sometimes called static fields because of this static modifier. The final modifier says that the value of the field does not change. Since the field PI represents a constant, we declare it final so that it cannot be changed. It is a convention in Java (and many other languages) that constants are named with capital letters, which is why out field is named PI, not pi. Defining constants like this is a common use for class fields, meaning that the static and final modifiers are often used together. Not all class fields are constants, however. In other words, a field can be declared static without declaring it final. Finally, the public modifier says that anyone can use the field. This is a visibility modifier, and we’ll discuss it and related modifiers in more detail later in this chapter.

The key point to understand about a static field is that there is only a single copy of it. This field is associated with the class itself, not with instances of the class. If you look at the various methods of the Circle class, you’ll see that they use this field. From inside the Circle class, the field can be referred to simply as PI. Outside the class, however, both class and field names are required to uniquely specify the field. Methods that are not part of Circle access this field as Circle.PI.

The names of class fields are qualified by the unique names of the classes that contain them, however. Thus, Java does not suffer from the name collisions that can affect other languages.

Class Methods

As with class fields, class methods are declared with the static modifier:

       public static double radiansToDegree(double rads) { return rads * 180 / PI; }

This line declares a class method named radiansToDegrees( ). It has a single parameter of type double and returns a double value. The body of the method is quite short; it performs a simple computation and returns the result.

Like class fields, class methods are associated with a class, rather than with an object. When invoking a class method from code that exists outside the class, you must specify both the name of the class and the method. For example:

// How many degree is 2.0 radians?

double d = Circle.radiansToDegree(2.0);

If you want to invoke a class method from inside the class in which it is defined, you don’t have to specify the class name. However, it is often good style to specify the class name anyway, to make it clear that a class method is being invoked.

Note that the body of our Cicle.radiansToDegrees( ) method uses the class field PI.A class method can use any class fields and class methods of its own class. But it cannot use any instance fields or instance methods because class methods are not associated with an instance of the class. In other words, although the radiansToDegrees( ) method is defined in the Circle class, it does not use any Circle objects. The instance fields and instance methods of the class are associated with Circle objects, not with the class itself. Since a class method is not associated with an instance of its class, it cannot use any instance methods of fields.

As we discussed earlier, a class field is essentially a global variable. In a similar way, a class method is a global method, or global function. Although radiansToDegrees( ) does not operate on Circle objects, it is defined within the Circle class because it is a utility method that is sometimes useful when working with circles. In many non-object-oriented programming languages, all methods, or functions, are global. You can write complex Java programs using only class methods. This is not object-oriented programming, however, and does not take advantage of the power of the Java language. To do true object-oriented programming, we need to add instance fields and instance methods to our repertoire.

Instance Fields

Any field declared without the static modifier is an instance field:

       public double r;     // The radius of the circle

Instance fields are associated with instances of the class, rather than with the class itself. Thus, every Circle object we create has its own copy of the double field r. In our example, r represents the radius of a circle. Thus, each Circle object can have a radius independent of all other Circle object.

Inside a class definition, instance fields are referred to by name alone. You can see an example of this if you look at the method body of the circumference( ) instance method. In code outside the class, the name of an instance method must be prepended by a reference to the object that contains it. For example, if we have a Circle object named c, we can refer to its instance field r as c.r:

Circle c = new Circle( );  // Create a new Circle object; store it in variable c

c.r = 2.0;               // Assign a value to its instance field r

Circle d = new Circle( );   // Create a different Circle object

d.r = c.r * 2;            // Make this one twice as big

Instance fields are key to object-oriented programming. Instance fields define an object; the values of those fields make one object distinct from another.

Instance methods

Any method not declared with the static keyword is an instance method. An instance method operates on an instance of a class (an object) instead of operation on the class itself. It is with instance methods that object-oriented programming starts to get interesting. The Circle class defined in Example 1 contains two instance methods, area( ) and circumference( ), that compute and return the area and circumference of the circle represented by a given Circle object.

To use an instance method from outside the class in which it is defined, we must prepend a reference to the instance that is to be operated on. For example:

Circle c = new Circle( );    // Create a Circle object; store in variable c

c.r = 2.0;                // Set an instance field of the object

double a = c.area( );       // Invoke an instance method of the object

If you’re new to object-oriented programming, that last line of code may look a little strange. I did not write

a = area(c);

Instead, I wrote:

a = c.area( );

This is why it is called object-oriented programming; the object is the focus here, not the function call. This small syntactic difference is perhaps the single most import feature of the object-oriented paradigm.

The point here is that we don’t have to pass an argument to c.area( ). The object we are operating on, c, is implicit in the syntax. Take a look at Example 1 again. You’ll notice the same thing in the signature of the area( ) method: it doesn’t have a parameter. Now look at the body of the area( ) method: it uses the instance field r. Because the area( ) method is part of the same class that defines this instance field, the method can use the unqualified name r. It is understood that this refers to the radius of whatever Circle instance invokes the method.

Creating and Initializing Objects

Take another look at how we’ve been creating Circle objects:

Circle c = new Circle( );

What are those parentheses doing there? They make it look like we’re calling a method. In fact, that is exactly what we’re doing. Every class in Java has at least one constructor, which is a method that has the same name as the class and whose purpose is to perform any necessary initialization for a new object. Since we didn’t explicitly define a constructor for our Circle class in Example 1, Java gave us a default constructor that takes no arguments and performs no special initialization.

Here’s how a constructor works. The new operator creates a new, but uninitialized, instance of the class. The constructor method is then called, with the new object passed implicitly (a this reference, as we saw earlier), and whatever arguments that are specified between parentheses passed explicitly. The constructor can use these arguments to do whatever initialization is necessary.

Defining a Constructor

There is some obvious initialization we could do for our circle object, so let’s define a constructor. Example 2 shows a new definition for Circle that contains a constructor that lets us specify the radius of a new Circle object. The constructor also uses the this reference to distinguish between a method parameter and an instance field that have the same name.

Example 2: A Constructor for the Circle Class

public class Circle {

public static final double PI = 3.14159;     // A constant

public double r;   // An instance field that holds the radius of the circle

// The constructor method: initialize the radius field

public Circle(double r) { this.r = r; }

// The instance methods: compute values based on the radius

public double circumference( ) { return 2 * PI * r; }

public double area( ) { return PI * r * r; }

}

When we relied on the default constructor supplied by the compiler, we had to write code like this to initialize the radius explicitly;

Circle c = new Circle( );

c.r = 0.25;

With this new constructor, the initialization becomes part of the object creation step:

Circle c = new Circle(0.25);

Here are some important notes about naming, declaring, and writing constructors:

· The constructor name is always the same as the class name.

· Unlike all other methods, a constructor is declared without a return type, not even void.

· The body of a constructor should initialize the this object.

· A constructor should not return this or any other value.

Definging Multiple Constructors

Sometimes you want to initialize an object in a number of different ways, depending on what is most convenient in a particular circumstance. For example, we might want to initialize the radius of a circle to a specified value or a reasonable default value. Since our Circle class has only a single instance field, there aren’t too many ways we can initialize it, of course. But in more complex classes, it is often convenient to define a variety of constructors. Here’s how we can define two constructors for Circle:

public Circle( ) { r = 1.0; }

public Circle(double r) { this.r = r; }

It is perfectly legal to define multiple constructors for a class, as long as each constructor has a different parameter list. The compiler determines which constructor you wish based on the number and type of arguments you supply.

Invoking One Constructor from Another

There is a specialized use of the this keyword that arises when a class has multiple constructors; it can be used from a constructor to invoke one of the other constructors of the same class. In other words, we can rewrite the two previous Circle constructors as follows:

// This is the basic constructor: initialize the radius

public Circle(double r) { this.r =r; }

// This constructor uses this( ) to invoke the constructor above

public Circle( ) { this(1.0); }

This this( ) syntax is a method invocation that calls one of the other constructors of the class. The particular constructor that is invoked is determined by the number and type of arguments, Of course. This is a useful technique when a number of constructors share a significant amount of initialization code, as it avoids repetition of that code. This would be a more impressive example, of course, if the oneparameter version of the Circle( ) constructor did more initialization than it does.

There is an important restriction on using this( ): it can appear only as the first statement in a constructor. It may, of course, be followed by any additional initialization a particular version of the constructor needs to do. The reason for this restriction involves the automatic invocation of superclass constructor methods.

Data Hiding and Encapsulation

We started this chapter by describing a class as “a collection of data and methods.” One of the important object-oriented techniques we haven’t discussed so far is hiding the data within the class and making it available only through the methods. This technique is known as encapsulation because it seals the data (and internal methods) safely inside the “capsule” of the class, where it can be accessed only by trusted users (i.e., by the methods of the class).

Why would you want to do this? The most important reason is to hide the internal implementation details of your class. If you prevent programmers from relying on those details, you can safely modify the implementation without worrying that you will bread existing code that uses the class.

Another reason for encapsulation is to protect your class against accidental or willful stupidity. A class often contains a number of interdependent fields that must be in a consistent state. If you allow a programmer (including yourself) to manipulate those fields directly, he may change one field without changing important related fields, thus leaving the class in an inconsistent state. If, instead, he has to call a method to change the field, that method can be sure to do everything necessary to keep the state consistent. Similarly, if a class defines certain methods for internal use only, hiding these methods prevents users of the class from calling them.

Here’s another way to think about encapsulation: when all the data for a class is hidden, the methods define the only possible operations that can be performed on objects of that class. Once you have carefully tested and debugged your methods, you can be confident that the class will work as expected. On the other hand, if all the fields of the class can be directly manipulated, the number of possibilities you have to test becomes unmanageable.

There are other reasons to hide fields and methods of a class, as well:

· Internal fields and methods that are visible outside the class just clutter up the API.  Keeping visible fields to a minimum keeps your class tidy and therefore easier to use and understand.

· If a field or method is visible to the users of your class, you have to document it. Save yourself time and effort by hiding it instead.

附录B  中文翻译

Java的面向对象程序设计

Java是一种面向对象程序设计语言。所有的Java程序都使用对象,每个Java程序都是用类定义的。

如果你没有面向对象(OO)程序设计的背景,请不要担心,本章并没有假设你以前有任何的经验。然而,如果你有OO的程序设计经验,那么也请仔细阅读,在不同的程序语言里“面向对象”这个专有名词会有不同的意义。不要以为Java会和你所爱用的OO语言有着相同的动作方式,尤其是C++程序员,虽然 Java与C++采用了C语言中相当多的语法,但这两种语言之间的相似度在语法上并没有太大的差异,就因为这样,所以请有C++经验的程序员在学习Java时要特别的小心。

类的成员

类可被看成数据与与动作在数据上的程序代码的集合。数据被存储于字段里,而程序代码则是有组织的被放入方法中。字段和方法被称为类的成员,类的成员有两个不同的类型:类成员与该类本身是相关联的,而实例成员则是与的某个实例(即对象)相关联。我们有四种成员:

· 类字段

· 类方法

· 实例字段

· 实例方法

例1是一个名为Circle的类的简单定义,其中包含了这四种成员。

例1:一个简单的类与其成员

Public class Circle {

         // 一个类字段

         pubic static final double PI = 3.14159;        // 一个有用的常量

         // 一个类方法:使用自变量来计算数值

         public static double radiansToDegrees(double rads) {

return rads * 180 / PI;

         }

         // 一个实例字段

         public double r;                        // 圆的半径

         // 两个实例方法:它们以对象的实例字段来加以运算

         public double area( ) {                // 计算圆的周长

return PI * r * r;

         }

         public double circumference( ) {// Compute the circumference of the circle

           return 2 * PI * r;

         }

}

类字段

类字段是与定义它的类相关的,而不是与类的实例相关联的。以下便是声明一个类字段的程序代码:

   public static final double PI = 3.14159;

上述代码声明了一个double类型且名为PI的字段,同时将它的值指定为3.14159。正如你所看到的,字段的声明看起来与局部变量的声明十分的相似。当然,也有不同之处:变量只能在方法中被定义,而字段则是类的成员。

static修饰符表示该字段是一个类字段。类字段有时候被称作静态字段,就是因为static修饰符的关系。final修饰符表示该字段的值是不能被变动的,因为字段PI代表一个常量,所以我们将它声明为final让它不会被修改。这是Java中(在许多其他程序语言中也可见到的)的一个惯例,也就是常量通常都会使用大写来表示,这也就是为什么我们的字段名是PI,而不是pi的原因。定义这样的常量经常会使用类字段,也就是说static与final修饰符会常常被放在一起使用。然而,并不是所有的类字段都是常量。换句话说,字段可以被声明为static而不被声明为final。最后,public修饰符表示任何人都可以使用该字段,这是一个常见的修饰符,而且我们会在稍后的章节更详细地介绍它与其他的相关修饰符。

了解静态字段的一个重点是,对静态字段而言,永远只存在一份副本。此字段是与该类本身相关,而不是与该类的实例相关。如果你看到了Circle类中的许多方法,你将会发现它们都会使用这个字段,在Circle类的内部,该字段可以直接以PI来表示;而在该类的外部,若要指定该字段,则类与字段名称都必须指定得很详细。其他不是Circle的方法如果要使用此字段,则必须以Circle.PI才能访问到。

类字段的名称是由定义它们的类的唯一名称来决定的。因此,Java不会遭遇到其他程序语言会发生的名称冲突问题。

类方法

跟类字段一样,类方法也使用static修饰符来声明:

   public static double radiansToDegree(double rads) { return rads * 180 / PI; }

上述的程序代码声明了一个名为radiansToDegrees( )的方法,它有一个double类型的参数与一个double类型的返回值。该方法的主体非常简短,只执行了一个简单的计算动作后就将结果返回。

和类字段一样,类方法是与类相关的,而不是与对象相关。当从该类之外调用该类方法时,你必须同时指明类的名称与方法。例如:

   // 半弧度值为2。0的角度值是多少?

double d = Circle.radiansToDegree(2.0);

如果你想要从某个类的内部调用该类的方法时,你不需要指明该类的名称。然而,不管在什么情况之下都指明类的名称,这种做法是个很好的习惯,它会让你更清楚地知道哪个类的方法被调用了。

请注意,Cicle.radiansToDegrees( )方法使用了类字段PI,类方法可以使用该类中的所有类字段与类方法,但却不能使用任何的实例字段或实例方法,因为类方法与该类的实例是不相关的。换句话说,虽然radiansToDegrees( )方法定义了Circle类,但它不能使用任何的Circle对象。该类的实例字段与实例方法是与Circle对象相关的,而不是与类本身相关的。因为类方法与该类的实例是没有关联的,所以该类方法不能使用傈僳的实例方法或字段。

我们在之前已经讨论过,类字段基本上就是全局变量。同样地,类方法也是全局方法或全局函数。虽然radiansToDegrees( )并不能作用于Circle对象,但是因为当在处理与圆形有关的操作的时候,它有时候是个有用的实用方法,所以它被定义于Circle类中。在很多非面向对象的程序语言中,所有的方法或函数都是全局的。当然,你可以只使用类方法来编写复杂的Java程序,然而,这并不是一个面向对象的程序设计方式,同时也没有利用Java语言所提供的强大功能。为了要写一个真正面向对象的程序,我们必须使用实例字段落与实例方法。

实例字段

任何没有使用static修饰符声明的字段都是实例字段:

   public double r;     // 圆的半径

实例字段是与该类的实例相关联的,而不是与该类本身相关。因此,我们所创建的每一个Circle对象都会有一个与其他所有的Circle对象独立的半径。

在类定义里,对实例字段的引用只是通过它的名称。你可以察看实例方法circumference( )的主体,如此你就可以看到这样的一个例子。在该类外部的程序代码中,如果引用该实例方法,则必须在该实例方法的名称前面加上该对象的名称。例如,如果变量c拥有一个对Circle对象的引用时,则可以使用c.r来引用圆的半径:

   Circle c = new Circle( ); // 创建一个Circle对象;将它存储于变量c中

c.r = 2.0;              // 赋一个值给它的实例字段r

Circle d = new Circle( );   // 创建一个新的Circle对象

d.r = c.r * 2;            // 让此圆的半径为原先的两倍大

实例字段在面向对象程序设计中是相当重要的,实例字段拥有该对象的状态,而这些字段的值使得该对象不同于其他的对象。

实例方法

任何不是使用static关键字声明的方法都是实例方法。实例方法只作用于类的实例(即对象),而不是该类本身。从实例方法开始,面向对象程序设计开始变得有趣了。在例1中定义的Circle类,包含了两个实例方法,area( )与circumference( ),它们会计算与返回该Circle对象表示的圆的面积与周长。

如果你要从定义实例方法类的外部来引用实例方法,就必须在该实例方法的名称前面加上该实例的名称,例如:

   Circle c = new Circle( ); // 创建一个Circle对象并将这存储于变量c中

c.r = 2.0;                // 设定该对象的一个实例字段

double a = c.area( );       // 调用一个该对象的实例方法

如果你没有面向对象程序设计的经验,可能会对上述程序的最后一行感到陌生。我们并没有将它写成:

a = area(c);

而写成:

a = c.area( );

这就是为什么我们称他为面向对象程序设计的原因。在这里的焦点是对象,而不是函数调用。这个小小的不同,或许就是面向对象最重要的特点了。

重点是我们还没有将自变量传递给c.area( ),我们所操作的对象c在语法上存在一些隐含条件。再看一下例1,你将在area( )方法的签名中发现相同的情况,它也没有自变量。现在再来看看area( )方法的主体,它使用了实例字段r。因为area( )方法也在同一个类中,因此该方法能调用r。当任何Circle实例调用此方法时,便会引用该圆的半径。

对象的创建与初始化

让我们来看看如何创建Circle对象:

   Circle c = new Circle( );

这个小括号在这里有什么用途呢?这让它看起来像在调用一个方法。事实上,这正是我们所要做的。Java里的每一个类至少都会有一个与该类同名的构造函数,它的目的是对新对象执行必须的初始化操作。因为在例1里,我们并没有为Circle类定义构造函数,所以Java会给我们一个默认的构造函数,它没有任何的自变量,也不会执行特殊的初始化操作。

下面将介绍构造函数是如何执行的。使用new运算符为这个类创建一个新的但未初始化的实例。然后构造函数方法会被调用,而新的对象会被隐含地传递给它(this引用,就像我们之前所看到的),在括号内指明的自变量也会被显示地传递。构造函数可以使用这些自变量来做必要的初始化操作。

定义构造函数

我们可以为Circle对象做一些明确的初始化操作,所以让我们来定义一个构造函数。例2显示了新的Circle类定义,其中包含一个构造函数,能让我们指定新Circle对像的半径。构造函数也使用了this引用来区分拥有相同名称的方法参数与实例字段。

例2:Circle类的构造函数

   public class Circle {

public static final double PI = 3.14159;     // 一个常量

public double r;   // 一个定义圆半径的实例字段

// 构造函数:初始化半径字段

public Circle(double r) { this.r = r; }

// 实例方法:依据半径来计算值

public double circumference( ) { return 2 * PI * r; }

public double area( ) { return PI * r * r; }

}

当我们使用由编译器所提供的默认构造函数时,我们必须使用以下的方式来初始化半径:

Circle c = new Circle( );

c.r = 0.25;

如果我们使用此构造函数,初始化就会变成创建对象步骤的一部分:

Circle c = new Circle(0.25);

这里有一些关于命名、声明与编写构造函数时应注意的重要事项:

· 构造函数的名称一定要和类名相同

· 和其他的方法不一样的是,构造函数在声明时不可指定返回值的类型,即使是void也不可以。

· 构造函数的主体必须初始化this对象。

· 构造函数不可以返回this或其他任何的值。构造函数可以包含一个return语句。

定义多个构造函数

在特殊的情况下你会想要使用最方便的方式,用不同的方法来庙宇对象的初始值。例如,我们会想要将圆的半径值设定为某个初始值或合理的默认值。因为我们的Circle类只有一个实例字段,当然不可能使用太多的方式来设定初始值。但在比较复杂的类里,定义多个构造函数是非常方便的,以下的例子是我们为Circle类定义了两个构造函数:

public Circle( ) { r = 1.0; }

public Circle(double r) { this.r = r; }

为一个类定义多个构造函数是完全合法的,只要每一个构造函数有不同的参数列表就可以了。编译器会依你所给定的自变量个数与自变量类型来决定使用中一个构造函数。

在一个构造函数中调用另一个构造函数

当类有多个构造函数时,this关键字会有一个特定的用法:在一个构造函数中调用同一个类中的另一个构造函数。换句话说,我们可以将之前的那两个Circle构造函数重写成以下的样子。

// 这是基本构造函数:将半径初始化

public Circle(double r) { this.r =r; }

// 此构造函数使用this( )来调用上面的构造函数

public Circle( ) { this(1.0); }

this( )语法是一个方法的调用,它会调用该类中的其他构造函数,当然,被调用的构造函数是由其自变量的个数与类型决定的。当许多的构造函数占用了大部分的初始化程序代码时,这就是相当有用的技术,因为它可避免程序代码的重复。如果只有一个参数的Circle( )构造函数做了很多初始化的操作,则此例子便更能显示出这种做法的威力。

    在使用this( )时有个非常重要的限制:它只可以出现在构造函数的第一条语句里。当然,在它的事面可以接着此构造函数所需要的任何初始设定。这个限制的原因牵涉到对超类构造函数方法的自动调用。

数据隐藏与封装

在本章一开始时,我们就介绍过类是“数据与方法的集合”。到目前为止,还有一个很重要的面向对象技术我们尚未讨论,那就是类的数据隐藏,而且只能通过方法来引用这些数据。这样的技术通常叫做封装,因为它可将类的数据(包括内部方法)安全地封装在类这个“密封体”里,这样它只能够让被信赖的用户(即类中的方法)来使用。

为什么要这样做呢?最重要的原因就是要隐藏类的内部实现细节。如果你不想让其他的程序接触到这些细节,那么你就可以安全地修改类的实现,而不用担心已经使用这个类的程序代码会因为你的修改而产生任何副作用。

另一个使用封装的原因是为了要保护你的类,以避免碰上意外或故意的错误。一个类通常包含许多的字段,而它们彼此处于一致的状态之下。如果你允许程序员(包括你自己在内)直接操作这些字段,就可能会更改一个字段而没有更改其他重要的相关字段,因而让类产生了不一致的状态。如果换成必须调用一个方法才能更改字段,而这种方法可以确保完成所有必要的操作以保持类的一致性。同样地,如果类定义了某些只提供给内部使用的方法,则将这些方法隐藏起来就可以避免用户调用它们。

关于封装的另一个想法是:当一个类的所有数据都隐藏起来时,类的方法就可以定义这个类的对象唯一可能执行的运算。一旦方法经过小心测试与调试之后,你就可以预期它会正常动作。另一方面,如果该类的所有字段可以直接地被操作,那么调试的过程可能就会难以控制。

将类中的字段与方法隐藏的理由还有:

· 若内部的字段与方法可以让外部看见,会让类的API变得混乱。所以保持可见字段最小的状态,会让你的类显得更为简洁,同时让它更易于使用与理解。

· 如果类中的字段或方法是可见的,你就必须以文件来加以说明。如果想要节省时间就把它给隐藏起来吧!

附录C  源程序

import java.awt.*;

import java.awt.event.*;

import java.awt.image.*;

import javax.swing.*;

import javax.swing.border.*;

import javax.swing.event.*;

import java.io.*;

import java.sql.*;

public class MedicalImageProcess extends JFrame implements ActionListener

{

      JPanel jp1,jp11,jp111,jp112,jp2,jp21,jp211,jp212,northPanel,pictPanel,pict1,pict2;

       JTabbedPane jtp;

       JButton xianxing,fanzhuan,weicaise,pinghua,ruihua,zhongzhi,erzhihua,bianyuan;

       TitledBorder titled1,titled2,titled3,titled4;

       JSlider jSlider;

       JMenuBar bar;

       JMenu fileMenu,dataMenu;

       JMenuItem openItem,exitItem,checkItem,insertItem;

       JFileChooser chooser;

       File file;

       String filename;

       Image im,tmp,tmp0;

       int i,iw,ih;

       int[] pixels;

       Border etched1,etched2;

       SoftBevelBorder softBevelBorder;

       Connection con;

       Statement stmt;

       ResultSet rs;

       String id,name,pi;

       boolean flagGray = false;

       boolean flagLoad = false;

       boolean flag=false;

      

       public MedicalImageProcess()

       {

              super("Java数字图像处理");

              Container contents = getContentPane();//创建一个容器,所有基本组件都放入其中

              jtp = new JTabbedPane(SwingConstants.TOP);

             

              jp1 = new JPanel();

              jp11 = new JPanel();

              jp111 = new JPanel();

              jp112 = new JPanel();

             

              jp2 = new JPanel();

              jp21 = new JPanel();

              jp211 = new JPanel();

              jp212 = new JPanel();

             

              northPanel = new JPanel();

              pictPanel = new JPanel();

              pict1 = new JPanel();

              pict2 = new JPanel();

              jSlider = new JSlider(JSlider.HORIZONTAL,0,255,100);

                    

              xianxing = new JButton("线性灰度变换");

              fanzhuan = new JButton("反转灰度变换");

              weicaise = new JButton("伪彩色处理");

              pinghua = new JButton("平 滑 处 理");

              ruihua = new JButton("锐 化 处 理");

              zhongzhi = new JButton("中 值 滤 波");

              erzhihua = new JButton("二值化处理");

              bianyuan = new JButton("边 缘 检 测");

             

              titled1 = new TitledBorder("图像灰度化及伪彩色处理");

              titled2 = new TitledBorder("图像平滑锐化处理及其中值滤波");

              titled3 = new TitledBorder("图像二值化处理");

              titled4 = new TitledBorder("图像的Robert边缘检测");

                    

          softBevelBorder = new SoftBevelBorder(2,Color.black,Color.white);//实现凹凸斜面

         

              init();

                    

              jp111.setBorder(titled1);//设置带标题边框的边框

              jp112.setBorder(titled2);

              jp211.setBorder(titled3);

              jp212.setBorder(titled4);

             

              jSlider.setMajorTickSpacing(50);//设置主刻度标记间隔

              jSlider.setMinorTickSpacing(10);//设置次刻度标记间隔

              jSlider.setPaintTicks(true);//设置在滑块上绘制刻度标记

              jSlider.setPaintTrack(true);//通知是否绘制滑块

              jSlider.setPaintLabels(true);//通知是否绘制标签

              jp111.add(xianxing);

              jp111.add(fanzhuan);

              jp111.add(weicaise);

              jp112.add(pinghua);

           jp112.add(ruihua);

              jp112.add(zhongzhi);

              jp11.setLayout(new GridLayout(1,2));

              jp11.add(jp111);

              jp11.add(jp112);

             

              jp211.add(jSlider);

              jp211.add(erzhihua);

              jp212.add(bianyuan);

              jp21.setLayout(new GridLayout(1,2));

              jp21.add(jp211);

              jp21.add(jp212);

             

              jp1.setLayout(new BorderLayout());

              jp2.setLayout(new BorderLayout());

                    

              pictPanel.setBorder(softBevelBorder);

             

              northPanel.setLayout(new BorderLayout());

             

              jp1.add(jp11);

              jp2.add(jp21);

                    

              jtp.add("图 像 增 强",jp1);

              jtp.add("图 像 分 割",jp2);

                    

              northPanel.add(jtp,BorderLayout.CENTER);

             etched1 = BorderFactory.createEtchedBorder();//浮雕化外观效果的边框

etched2 = BorderFactory.createEtchedBorder();

       pictPanel.setLayout(new GridLayout(0,2));

              pict1.setBorder(etched1);

              pict2.setBorder(etched2);

              pictPanel.add(pict1);

              pictPanel.add(pict2);

          contents.add(northPanel,BorderLayout.NORTH);

              contents.add(pictPanel,BorderLayout.CENTER);

             

              try

              {

                     Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

                     con = DriverManager.getConnection("jdbc:odbc:image");

                     stmt = con.createStatement();

              }

              catch(Exception e){}

             

              xianxing.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jxianxing_ActionPerformed(e);

                     }

              });

             

              fanzhuan.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jfanzhuan_ActionPerformed(e);

                     }

              });

             

              weicaise.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jweicaise_ActionPerformed(e);

                     }

              });

             

              pinghua.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jpinghua_ActionPerformed(e);

                     }

              });

              ruihua.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jruihua_ActionPerformed(e);

                     }

              });

             

              zhongzhi.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jzhongzhi_ActionPerformed(e);

                     }

              });

             

              jSlider.addChangeListener(new ChangeListener(){

                     public void stateChanged(ChangeEvent e)

                     {

                            jerzhihua_ActionPerformed(e);

                     }

              });

             

              erzhihua.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jerzhihua_ActionPerformed(e);

                     }

              });

             

              bianyuan.addActionListener(new ActionListener(){

                     public void actionPerformed(ActionEvent e){

                            jbianyuan_ActionPerformed(e);

                     }

              });

       }

       public void init()

       {

              bar = new JMenuBar();//创建一个菜单栏

              setJMenuBar( bar );

             

              fileMenu = new JMenu("File");//创建菜单

              fileMenu.setMnemonic('F');

              openItem = new JMenuItem("Open");//菜单项

              openItem.setMnemonic('O');

              exitItem = new JMenuItem("Exit");

              exitItem.setMnemonic('X');

             

              dataMenu = new JMenu("Data");

              dataMenu.setMnemonic('D');

              checkItem = new JMenuItem("Check");

              checkItem.setMnemonic('C');

              insertItem = new JMenuItem("Insert");

              insertItem.setMnemonic('I');

             

              fileMenu.add(openItem);

              fileMenu.add(exitItem);

             

              dataMenu.add(checkItem);

              dataMenu.add(insertItem);

             

              bar.add(fileMenu);

              bar.add(dataMenu);

             

              openItem.addActionListener(this);

              exitItem.addActionListener(this);

              checkItem.addActionListener(this);

              insertItem.addActionListener(this);

   }

      

       public void actionPerformed(ActionEvent event)

       {

              if(event.getSource() == openItem)

              {

                     chooser = new JFileChooser("F:/");//文件选择器

                     chooser.addChoosableFileFilter(new MyFileFilter("jpg;gif", "图像文件"));

                  int returnVal = chooser.showOpenDialog(pictPanel);

                  if(returnVal == JFileChooser.APPROVE_OPTION)//选择确认后返回该值

                  {

                        file = chooser.getSelectedFile();//返回选中的文件

                        filename = file.getPath();//将此抽象路径名转换为一个路径名字符串

                      jLoad_ActionPerformed(event);

                  }                 

              }

              else if(event.getSource() == exitItem)

           {

                     int i = JOptionPane.showConfirmDialog(null,"要退出系统吗?",

"退出系统",2,2);

                     if(i == 0)//如果用户按的是确定

                     {

                            try

                            {

                                   rs.close();    //关闭记录集

                                   stmt.close(); //关闭Statement对象

                                   con.close();  //关闭数据库链接

                            }    

                            catch(Exception e6){}

                            this.dispose();//释放资源

                            System.exit(0);

                     }              

           }

           else if(event.getSource() == checkItem)

              {

                     try

                     {

                            int rec=0;

                            String s=JOptionPane.showInputDialog(null,"请输入ID");

                            rs = stmt.executeQuery("SELECT ID, 姓名,图片 FROM pic");

                            while (rs.next())

                            {

                                   id= rs.getString("ID");

                                   name= rs.getString("姓名");

                                   pi=rs.getString("图片");

                                   if(id.equals(s.trim()))

                                   {

                               filename=pi;

                              jLoad_ActionPerformed(event);

                                          rec=1;

                                          break;

                                   }

                            }

                            if(rec==0)JOptionPane.showMessageDialog(null,"数据库中没有此图片");

                            rs.close();                                

                     }

                  catch(Exception e2){}

              }

              else if(event.getSource() == insertItem)

              {

                     try

                     {

                            String s1=JOptionPane.showInputDialog(null,"请输入ID");

                            String s2 = JOptionPane.showInputDialog(null,"请输入姓名");

                            String strInc = "INSERT INTO pic(ID,姓名,图片)

Values('"+s1+"','"+s2+"','"+filename+"')";

                            stmt.executeUpdate(strInc);

                     }

                     catch(Exception e3){}     

                     finally{

                            JOptionPane.showMessageDialog(null,"图像已添加进数据库");

                     }           

              }

       }

参考资源:

https://download.csdn.net/download/dwf1354046363/22827822

[计算机毕设]基于java的图形图象处理系统设计与实现(源代码+项目报告)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐