一.ImageJ简介
ImageJ是一款由NIH(National Institutes of Health,美国国家卫生研究院)发起,可用于Windows,Mac,OSX和Linux等操作系统的图像处理开元软件,ImageJ小巧,只有5MB左右,界面简洁,容易上手。
二.ImageJ软件安装
1.下载ImageJ软件
在百度搜ImageJ,进入官网
2.点击Downlaod,进入下载界面
3.下载后解压,解压目录下的ImageJ.exe文件便可以运行软件,打开后的软件界面如下:
三. ImageJ的二次开发
1. 配置java的运行环境
(1)在ORACL官网上下载jdk:
我这里的版本是jdk-6u10-rc2-bin-b32-windows-i586-p-12_set_2008
(2)双击此exe文件,进入安装向导,安装路径可以修改,这里就安装在默认路径下:C:\Program Files\Java\jdk1.6.0_10目录下
(3)点击“下一步”,直到完成安装
(4)安装完毕后还要进行jdk运行环境配置,首先在系统属性中打开到如下界面:
在系统变量中新建第一个变量名:JAVA_HOME,变量值为前面jdk的安装路径,这里是C:\Program Files\Java\jdk1.6.0_10
新建第二个变量名:CLASSPATH,变量值为 .:%JAVA_HOME%\lib,注意符号添加正确,.:不要缺少
添加path变量,注意path变量如果已经有了,不要新建了。在变量值后面添加一个分号;隔开,在后面加入%JAVA_HOME%\lib,不然他会覆盖之前的值
(5)到此jdk环境配置结束
2. java的开发工具Eclipse的使用
(1)在官网上下载开发工具Eclipse,这里用的版本是eclipse-sdk-3.3-win32
(2)下载好后直接运行,出现默认工作空间路径选择,可以手动改变路径,设置后生成的项目都会保存在此目录下,本文的路径为D:\java3
(3)配置好后出现此界面
3. 验证实验小程序(验证前面的JDK和Eclipse已经安装配置好)
(1)选择菜单File下New选项,选择java projec,打开如下对话框
输入工程名,例如test1,输入完毕点击下一步直至完成
(2) 右击刚才新建的工程,创建一个包
输入包名,例如com.test1
(3) 右击com.test1,在弹跳出的选项中新建类
输入类名,例如shiyan1
(4) 类名新建好后,会出现带有头文件的java程序
(5) 编辑程序如下:
(6) 如果程序能正常运行,则在console窗口中会显示如下果:
4. 调用imagej源程序包中的图像处理函数
(1) 在imagej官网上下载imageJ源码包,这里我用的版本是ij148r-src
(2) 按照上述方法新建一个工程project1
(3) 打开ij148r-src的子文件夹source,里面包含的文件如下图:
(4) 打开工程project1文件夹,里面包含的文件如下:
(5) 将source中的plugins,macros,images文件夹和IJ_Props.txt文件拷贝到project1文件夹中,再把source中的ij文件夹拷贝到project1的子文件夹src中
(6) 右击eclipse中project1工程,在弹跳窗口中选择”Refresh”选项进行刷新,便能在src中看到imagej源码包
(7) 再按照上述方法新建一个包com.project1和类inverse
(8) 为了验证imagej源码包按上述操作后,可以成功调用,编辑一份java程序,此程序中调用了imagej的源码函数,实现了图像的打开,显示,以及反转操作
运行结果如下:
源码如下:
package com.project1;
import ij.process.ImageProcessor;
import ij.io.OpenDialog;
import ij.ImagePlus;
public class inverse {
public static void main(String args[]) {
OpenDialog wo=new OpenDialog("");
System.out.println("你打开的图像的路径是:");
String we=wo.getPath();
System.out.println(we);
//--------图像显示
ImagePlus ming=new ImagePlus(we);
ming.setTitle("原图像");
ming.show();
ImageProcessor ip1=ming.getProcessor();
ImageProcessor ip2=ip1.duplicate();
ip2.invert();
ImagePlus ming2=new ImagePlus("平滑后的图像",ip2);//图像有标题
ming2.show();
}
}
这样你就能够使用ImageJ中的图像处理函数了!深入的东西还要读者自己去挖掘!
ImageJ的源码函数众多,可以在imagej官网上查找每个类所包含的成员函数和成员变量:
注意
安装插件
在ImageJ的安装包里,有一个”plugins”的文件夹,ImageJ中所有的插件都是放在”plugins”文件夹中。”plugins”文件夹中允许创建与某个插件有关的子文件夹,此文件夹中存放的是与此插件相关的编译好的类文件以及配置文件。ImageJ的插件机制可以自行识别满足以下要求的插件
1)插件类按着规定必须继承以下三个类中的某一个 “PlugIn”,此类在运行时不需要输入图像,”PlugInFilter”,此类在运行时需要输入图像,以及”PlugInFrame”。
2)插件类的命名中必须至少有一个下划线。
3)插件类中不允许有包申明,它必须放在默认的包中。
4)编写好插件后,编译得到class文件,再按照相关要求写此插件的”plugins.config”文件,将所有class文件和config文件打包后放在ImageJ的plugin文件夹中即可。
重新运行ImageJ就能在Plugin中找到自己编写的插件。
开发环境
开发所使用的环境必须和ImageJ的环境对应,若用eclipse开发,ImageJ是x64则,eclipse必须是先x64,编译的class文件所对应的jdk应该与ImageJ的jdk对应
插件类编写
插件类若继承” PlugInFilter” 在插件类的构造函数中,若要传递参数需要写配置文件中写明,其setup函数需要返回输入图像的类型,否则在ImageJ中运行会出现”requires an image of type”,按照图像处理的需求指定相应的类型。
Java package
一些人用了一阵子的Java,可是对于 Java 的 package 跟 import 还是不太了解。很多人以为原始码 .java 文件中的 import 会让编译器把所 import 的程序通通写到编译好的 .class 档案中,或是认为 import 跟 C/C++ 的 #include 相似,实际上,这是错误的观念。
让我们先了解一下,Java 的 package 到底有何用处。
其实,package 名称就像是我们的姓,而 class 名称就像是我们的名字。package 名称有很多 . 的,就好像是复姓。比如说 java.lang.String,就是复姓 java.lang,名字为 String 的类别;java.io.InputStream 则是复姓java.io,名字为 InputStream 的类别。
Java 会使用 package 这种机制的原因也非常明显,就像我们取姓名一样,光是一间学校的同一届同学中,就有可能会出现不少同名的同学,如果不取姓的话,那学校在处理学生数据,或是同学彼此之间的称呼,就会发生很大的困扰。相同的,全世界的 Java 类别数量,恐怕比台湾人口还多,而且还不断的在成长当中,如果类别不使用套件名称,那在用到相同名称的不同类别时,就会产生极大的困扰。幸运的是,Java 的套件名称我们可以自己取,不像人的姓没有太大的选择 ( 所以有很多同名同姓的 ),如果依照 Sun 的规范来取套件名称,那理论上不同人所取的套件名称不会相同 ( 请参阅 "命名惯例"的相关文章 ),也就不会发生名称冲突的情况。
可是问题来了,因为很多套件的名称非常的长,在写程序时,会多打好多字,花费不少时间,比如说在A.B.C文件下有Point和Circle两个类,现在在程序中要调用:
A.B.C.Point P1=new A.B.C.Point();
A.B.C.Circle C1=new A.B.C.Circle();
实在是不美观又麻烦.于是,Sun 想了一个办法,就是 import. 就是在程序一开头的时候,说明程序中会用到那些类的路径.首先,在档案开头写:
import A.B.C.Point;
import A.B.C.Circle;
这两行说明了类的路径,所以当程序中提到Point就是指A.B.C.Point,而Circle就是指A.B.C.Circle,依此类推。于是原来的程序就变成:
Point P1=new Point();
Circle C1=new Circle();
这样看起来是不是清爽多了呢?如果这些类别用的次数很多,那就更能体会到import 的好处了。可是这样还是不够,因为懒是人的天性,还是会有人觉得打太多 import 了也很浪费时间,于是 Sun 又提供了一个方法:
import A.B.C.*; 意思就是,等一下程序中提到的没有姓名的类别,全都包含在A.B.C这个目录中。
注意点:但我们在程序中经常使用System.out这个类,为什么没有import System.out呢,因为java.lang 这个套件实在是太常太常太常用到了,几乎没有程序不用它的,所以不管你有没有写 import java.lang;,编译器都会自动帮你补上,也就是说编译器只要看到没有姓的类别,它就会自动去 java.lang 里面找找看,看这个类别是不是属于这个套件的。所以我们就不用特别去import java.lang 了。
为甚么我一开始说 import 跟 #include 不同呢?因为 import 的功能到此为止,它不像 #include 一样,会将档案内容载入进来。import 只是请编译器帮你打字,让编译器把没有姓的类别加上姓,并不会把别的文件的程式码写进来。如果你想练习打字,可以不要使用 import,只要在用到类别的时候,用它的全部姓名来称呼它就行了(就像例子一开始那样),跟使用 import 完全没有甚么两样。
先介绍Java的Package机制
基本原则:需要将类文件切实安置到其所归属之Package所对应的相对路径下。
例如:以下面程序为例:假设此Hello.java文件在D:\Java\下
package A;
public class Hello{
public static void main(String args[]){
System.out.println("Hello World!");
}
}
D:\Java>javac Hello.java 此程序可以编译通过.接着执行。
D:\Java>java Hello 但是执行时,却提示以下错误!
Exception in thread "main" java.lang.NoClassDefFoundError: hello (wrong name: A/Hello)
at java.lang.ClassLoader.defineClass0(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:537)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:251)
at java.net.URLClassLoader.access$100(URLClassLoader.java:55)
at java.net.URLClassLoader$1.run(URLClassLoader.java:194)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:187)
at java.lang.ClassLoader.loadClass(ClassLoader.java:289)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)
at java.lang.ClassLoader.loadClass(ClassLoader.java:235)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302)
原因是我们把生成的Hello.class规定打包在D:\Java\A文件中,必须在A文件中才能去运行。所以应该在D:\Java目录下建立一个A目录,然后把把Hello.class放在它下面,执行时,可正常通过!
D:\Java\>java A.hello 就会输出:Hello world!
现在介绍Java的import机制
我们在D:\Java目录下建立一个JInTian.java文件,其内容如下:
import A.Hello;
public class JInTian{
public static void main(String[] args){
Hello Hello1=new Hello();
}
}
D:\Java\>javac JInTian.java 编译成功!
D:\Java\>java JInTian 运行成功!
也就是你在JInTian.class中成功的引用了Hello.class这个类,是通过import A.Hello来实现的,如果你没有这句话,就会提示不能找到Hello这个类。
提示1:如果你在D:\Java目录下仍保留一个Hello.java文件的话,执行对主程序的编译命令时仍会报错!你自己可以试试呀!
提示2:如果你删除D:\Java\A\Hello.java文件的话,只留Hello.class文件,执行对主程序的编译命令时是可以通过,此时可以不需要子程序的源代码。
提出一个问题:如果把目录A剪切到其它目录,如D盘根目录下,在A目录如果执行编译和执行命令呢?
很明显,会报以下错误!当然了,前提条件是你没有设置classpath路径,其实只要没把类搜索路径设置到我这个位置就会出错的!你试试吧
imageJ 小例子
Java图像处理最快技术:ImageJ 学习第一篇 ImageJ是世界上最快的纯Java的图像处理程序。它可以过滤一个2048x2048的图像在0.1秒内(*)。这是每秒40万像素!ImageJ的扩展通过使用内置的文本编辑器和Java编译器的ImageJ的开发插件。500多插件可用。
数据类型:8位灰度或索引色,16位无符号整数,32位浮点和RGB色彩。
文件格式:读写所有支持的数据类型为TIFF(非压缩)或原始数据。打开和保存GIF,JPEG,BMP,PNG,PGM,FITS和ASCII。打开DICOM。使用URL打开的TIFF、GIF文件、JPEG文件、DICOMs和原始数据。也可以扩展许多其他格式 的插件。
图像增强:支持平滑,锐化,边缘检测,中值滤波和阈值的8位灰度和RGB彩色图像。交互方式调整亮度和8位,16位和32位图像的对比度。
几何操作:裁剪,缩放,调整大小和旋转。翻转垂直或水平。
分析:测量面积,平均值,标准偏差,最小的选择和最大或整个图像。测量长度和角度。使用现实世界中的度量单位,如毫米。校准使用密度标准。生成柱状图和剖面图。
色彩处理:分裂一个32位彩色图像转换成RGB或HSV分量。合并8位组件成彩色图像。RGB图像转换为8位索引颜色。应用伪彩色调色板为灰度图像。
基于ImageJ的项目有、CellProfiler(细胞图像分析)、idoimaging(医学影像)、ImageSurfer(3D可视化和分析)、MIPAV(3D成像和可视化)、NITRC(神经影像学工具和资源)等。
下面对ImageJ的编程基础介绍一下。
一、ImageJ的图像剖析
ImageJ的 图像由三个部分组成:
1、ImageProcessor对象实例:持有并提供了访问像素的方法。
2、Image对象实例:即java.awt.Image画在屏幕上。
3、ImagePlus对象实例:包括所有的元数据(标题,属性)、ImageProcessor和Image。
ImageJ的 图像堆栈由四部分组成:
1、ImageStack对象实例:持有像素阵列的阵列(LUT变化通过临时实例StackProcessor)
2、Image对象实例:即java.awt.Image画在屏幕上。
3、ImageProcessor对象实例:提供访问当前切片像素。
4、ImagePlus对象实例:持有ImageStack、ImageProcessor和Image。
使用堆栈时,要谨记以下几点:
1、ImagePlus的ImageProcessor实例在任何调用setSlice(INT)时都由它的像素替换对象。
2、ImageStack.getProcessor(int)在每次调用时都返回一个新的ImageProcessor,是 一个消耗大的操作。
3、ImagePlus的java.awt.Image中在每次调用setSlice(int)时被更新。
当ImagePlus调用updateAndDraw()时重新创建 java.awt.Image对象。如果你想改变被反映当前显示在屏幕上的图像,必须修改的像素之后调用updateAndDraw()。
二、创建图像
1、创建一个图像(详细)
int width = 400;
int height = 400;
ImageProcessor ip = new ByteProcessor(width, height);
String title = "My new image";
ImagePlus imp = new ImagePlus(title, ip);
imp.show();
有几个ImageProcessor类,每个都有自己专门的构造函数。ByteProcessor,ShortProcessor,FloatProcessor和ColorProcessor。
2、创建一个图像(简单方式)
new ImagePlus("My new image", new ByteProcessor(400, 400)).show();
3、创建任意数量任何类型的
A、一个简单的8位的400x400像素的灰度图像
ImagePlus imp = IJ.createImage("My new image", "8-bit black", 400, 400, 1);
imp.show();
// or, without getting back a reference:
IJ.newImage("My new image", "8-bit black", 400, 400, 1);B、堆栈的400×400像素的10色图片
ImagePlus imp = IJ.createImage("My new image", "RGB white", 400, 400, 10);
imp.show();
// again, without getting back a reference:
IJ.newImage("My new image", "RGB white", 400, 400, 10);
三、销毁图像
调用flush()将释放所使用的ImagePlus所有内存资源。
ImagePlus imp = ...
imp.flush();
注意:如果你持有一个从ImagePlus.getProcessor()方法获得ImageProcessor。即ImageProcessor的像素数组指针将被设置为null。你应该改为调用ImageProcessor的duplicate(),或直接通过getPixels()得到它的像素,并把它们存储在相同的尺寸的新ImageProcessor。
同样,java.awt.Image中获取自己的flush()方法调用也是如此。
四、打开图像
所有方法都围绕着ij.io.Opener类展开。
1、高层次的方式,从文件或URL
ImagePlus imp = IJ.openImage("/path/to/image.tif");
imp.show();
ImagePlus imp = IJ.openImage("http://www.example.org/path/to/image.tif");
imp.show();
// Without getting back a pointer, and automatically showing it:
IJ.open("/path/to/image.tif");
// Same but from an URL
IJ.open("http://www.example.org/path/to/image.tif");
2、从文件打开
Opener opener = new Opener();
ImagePlus imp = opener.openImage("/path/to/image.tif");
imp.show();
3、从URL打开
Opener opener = new Opener();
ImagePlus imp = opener.openImage("http://www.example.org/path/to/image.tif");
imp.show();
以上注意URL 包含http://如何的自动检测并正确解析。如果需要,可以直接调用:
ImagePlus imp = opener.openURL("http://www.example.org/path/to/image.tif");
五、编辑像素
1、运行ImageJ命令方式,这是一个高层次的方法,像素可以通过调用ImageJ的命令编辑图像:
ImagePlus imp = ...
// Making a binary image
IJ.run(imp, "Convert to Mask", ""); // "" means no arguments
// Resizing, opens a copy in a new window (the 'create' command keyword)
IJ.run(imp, "Scale...", "x=0.5 y=0.5 width=344 height=345 interpolate create title=[Scaled version of " +imp.getTitle() + "]");
...任何ImageJ命令可能被应用。你可以找出哪些命令来使用,哪些参数通过运行插件,并手动调用的ImageJ打开的图像上的菜单命令。
2、中级层次编辑方式:ImageProcessor(ROIs/selections)
在图像上绘制或填充ROI(感兴趣区域):
ImagePlus imp = ...
ImageProcessor ip = imp.getProcessor();
// Assuming 8-bit image
// fill a rectangular region with 255 (on grayscale this is white color):
Roi roi = new Roi(30, 40, 100, 100); // x, y, width, height of the rectangle
ip.setRoi(roi);
ip.setValue(255);
ip.fill();
// fill an oval region with 255 (white color when grayscale LUT):
OvalRoi oroi = new OvalRoi(50, 60, 100, 150); // x, y, width, height of the oval
ip.setRoi(oroi);
ip.setValue(255);
ip.fill(ip.getMask()); // notice different fill method
// regular fill() would fill the entire bounding box rectangle of the OvalRoi
// The method above is valid at least for PolygonRoi and ShapeRoi as well.
// draw the contour of any region with 255 pixel intensity
Roi roi = ...
ip.setValue(255);
ip.draw();
// update screen view of the image
imp.updateAndDraw();
3、ROIs的一些事情:
A、有很多selection/ROI类型:Roi(矩形之一,也是所有其它类型的父类),Line, OvalRoi, PolygonRoi, PointRoi, FreehandRoi, ShapeRoi, TextRoi。另外有一些子类型,如PolygonRoi里的POLYGON、POLYLINE 类型。
B、大部分的ROI是用于编辑图像非常有用; 一些用于图像分析(Line,PointRoi,TextRoi)。
C、最强大的ROI是ShapeRoi:java.awt.geom.GeneralPath支持它,它能够存储任意数量的任何形状的不连续区域的。
D、ip.fill(ip.getMask())方法是最安全的,可在各种场合使用,只需要检查ImageProcessor的mask通过getMask()返回的不为null。
旋转,翻转和缩放图像(或者ROI)
ImagePlus imp = ...
ImageProcessor ip = imp.getProcessor();
ip.flipHorizontal();
ip.flipVertical();
ip.rotateLeft();
ip.rotateRight();
// rotate WITHOUT enlarging the canvas to fit
double angle = 45;
ip.setInterpolate(true); // bilinear
ip.rotate(45);
// rotate ENLARGING the canvas and filling the new areas with background color
double angle = 45;
IJ.run(imp, "Arbitrarily...", "angle=" + angle + " grid=1 interpolate enlarge");
// scale WITHOUT modifying the canvas dimensions
ip.setInterpolate(true); // bilinear
ip.scale(2.0, 2.0); // in X and Y
// scale ENLARGING or SHRINKING the canvas dimensions
double sx = 2.0;
double sy = 0.75;
int new_width = (int)(ip.getWidth() * sx);
int new_height = (int)(ip.getHeight() * sy);
ip.setInterpolate(true); // bilinear
ImageProcesor ip2 = ip.resize(new_width, new_height); // of the same type as the original
imp.setProcessor(imp.getTitle(), ip2); // UPDATE the original ImagePlus
// update screen view of the image
imp.updateAndDraw();ImageProcessor类提供了绘制线条、文字和点等。看看在ImageProcessor的API。
4、低层次的编辑方式:像素数组
ImagePlus imp = ...
ImageProcessor ip = imp.getProcessor();
// Editing the pixel array
if (imp.getType() == ImagePlus.GRAY8) {
byte[] pixels = (byte[])ip.getPixels();
// ... do whatever operations directly on the pixel array
}
// Replacing the pixel array: ONLY if same size
if (imp.getType() == ImagePlus.GRAY8) {
int width = ip.getWidth();
int height = ip.getHeight();
byte[] new_pixels = new byte[width * height];
// set each pixel value to whatever, between -128 and 127
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
// Editing pixel at x,y position
new_pixels[y * width + x] = ...;
}
}
// update ImageProcessor to new array
ip.setPixels(new_pixels);
}
// Replacing the pixel array but of different length: for example, to resize 2.5 times in width and height
int new_width = (int)(ip.getWidth() * 2.5);
int new_height = (int)(ip.getHeight() * 2.5);
ImageProcessor ip2 = ip.createProcessor(new_width, new_height); // of same type
imp.setProcessor(imp.getTitle(), ip2);
if (imp.getType() == ImagePlus.GRAY8) {
byte[] pix = (byte[])imp.getProcessor().getPixels(); // or ip2.getPixels();
// .. process pixels ...
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
// Editing pixel at x,y position
new_pixels[y * width + x] = ...;
}
}
}
// DON'T forget to update the screen image!
imp.updateAndDraw();如果要显示的ImagePlus,更新图像只有必须的,
六、保存图像
1、高层次的方式
ImagePlus imp = ...
IJ.saveAs(imp, "tif", "/path/to/image.tif");
// or by using the file format extension:
IJ.save(imp, "/path/to/image.tif");
2、通过FileSaver类
ImagePlus imp = ...
new FileSaver(imp).saveAsTiff("/path/to/image.tif");该FileSaver类有更多的选择:saveAsTiffStack,saveAsJpeg,saveAsPng,saveAsGif ...等。
Writing ImageJ Plugins—A Tutorial
https://imagingbook.com/imagej-tutorial/