ImageJ是一款基于Java的开源图像处理软件,目前在生物及医学图像分析中使用比较广泛。注意,这个使用广泛是Java客户端软件使用比较广泛,而很少作为类库应用于Java工程。
由于项目中需要编写爬虫进行模拟登录,登录时需要处理图片验证码,而自己写的图片处理方式并不能达到想要的结果,例如去噪,去除杂点,二值化等操作,所以就使用了这款Java的开源图像处理软件。
目前此软件有两个版本,imagej1.x和imagej2,项目中使用了imagej 1.52p版本,其实是想使用imagej2版本的,但是因为在项目中使用了阿里云的maven仓库,而阿里云实际只代理了maven center及 jcenter的maven仓库,imagej1.x在maven中央仓库中,imagej2版本在https://mvnrepository.com/中能够搜的到,但是imagej2并未发布到中央仓库,因此,阿里云的maven仓库中也没有这个版本。
言归正传,现在看一下imagej的使用方式:
1、在pom.xml里面配置
<dependency>
<groupId>net.imagej</groupId>
<artifactId>ij</artifactId>
<version>1.52p</version>
</dependency>
2、 在Java代码中使用
// 初始图片路径
IJ.open(openPath);
// 设置大小
IJ.run("Size...", "width=120 height=40 constrain interpolate");
// 去噪
IJ.run("Remove Outliers...", "radius=1 threshold=50 which=Dark");
// 去除杂点
IJ.run("Despeckle");
// 二值
IJ.run("Make Binary");
// 另存图片
IJ.save(savePath);
如上所示,此类库使用比较简单方便,其在Java项目中以及SpringMvc中使用没有任何问题,但是由于新项目使用了SpringBoot,其在运行的时候总是抛出java.awt.HeadlessException异常。如是问题的解决开始了,首先了解一下:
1. 什么是 java.awt.headless?
Headless模式是系统的一种配置模式。在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式。
2. 何时使用和headless mode?
Headless模式虽然不是我们愿意见到的,但事实上我们却常常需要在该模式下工作,尤其是服务器端程序开发者。因为服务器(如提供Web服务的主机)往往可能缺少前述设备,但又需要使用他们提供的功能,生成相应的数据,以提供给客户端(如浏览器所在的配有相关的显示设备、键盘和鼠标的主机)。
3. 如何使用和Headless mode?
一般是在程序开始激活headless模式,告诉程序,现在你要工作在Headless mode下,就不要指望硬件帮忙了,你得自力更生,依靠系统的计算能力模拟出这些特性来:
System.setProperty("java.awt.headless", "true");
现在知道了什么是headless,然后又在网上找到了在SpringBoot中如何解决HeadlessException的方法,那就是在SpringApplication启动类中修改为如下代码:
SpringApplicationBuilder builder = new SpringApplicationBuilder(YourApplication.class);
builder.headless(false).run(args);
可以看出,SpringBoot应该是默认开启了headless模式,所以要在项目启动加载时关闭。项目重新启动,运行一切正常,没有报错,现在开开心心,高高兴兴的打包,发布测试,然而项目原先的登录验证码不显示了。本地开发环境是Windows,发布环境是Linux,那么问题就是在这了,Windows下面关闭了headless模式,Java可以调用系统的相关类库实现图片的显示,因为Windows是有显示设备的,而Linux没有显示设备,此时,Java调用先关类库时就会抛出异常,那么说明我么不能只靠关闭headless来解决这个问题。
因此只能查询相关资料,查看imagej如何在headless模式下正常使用,从imagej官网中(Running Headless)我们可以看到imagej提供了headless的多种解决方法,但是这几种方式并不是我们能够使用的,于是参考官方在github上的源码(https://github.com/imagej/imagej/blob/master/src/test/java/net/imagej/app/MainTest.java)找到了如下源码:
final ImageJ ij = new ImageJ();
ij.launch("--headless");
简直是兴奋溢于言表啊,就这么简单的问题解决了,赶紧copy到自己项目中,然而报错,提示没有这个方法,又一场乐极生悲,那么再仔细看,仔细找,过程不多描述,最后发现,这个是imagej2开启headless的方式,而imagej1.x需要通过补丁的方式来解决,都怪没有仔细看官网介绍,其实在官网中有一点点描述,但是并没有明确方法,那么再从imagej1.x的源码下手(GitHub - imagej/ij1-patcher: Extension points for ImageJ via runtime patching to support (limited) headless operation and ImageJ2's legacy layer),里面详细介绍了如何启用headless模式:
<dependency>
<groupId>net.imagej</groupId>
<artifactId>ij1-patcher</artifactId>
<version>1.0.0</version>
</dependency>
// The first parameter is a class loader, asking for a new,
// special-purpose class loader to be created; the second parameter
// asks for headless mode.
LegacyEnvironment ij1 = new LegacyEnvironment(null, true);
ij1.runMacro("open('" + path + "');");
[... process the image ...]
ij1.runMacro("saveAs('jpeg', '" + outputPath + "');");
好了,现在再将此代码copy的项目中,现在项目中代码如下:
LegacyEnvironment ij1 = new LegacyEnvironment(null, true);
ij1.runMacro("open('" + openPath + "');", "");
ij1.run("Size...", "width=120 height=40 constrain interpolate");
ij1.run("Remove Outliers...", "radius=1 threshold=50 which=Dark");
ij1.run("Despeckle", "");
ij1.run("Make Binary", "");
ij1.runMacro("saveAs('jpeg', '" + savePath + "');", "");
请注意LegacyEnvironment的第二个参数true就是是否开启headless模式,一定要执行open和save等菜单操作的时候使用runMacro方法,去燥,二值等操作时使用run方法。此处应该是imagej在执行open和save等menu操作时,会用到headless相关方法,而其他图片处理时不会用到。
遗留问题:因为imagej1.x使用的是运行时修补的方式来解决这个headless问题,在代码中会getContextClassLoader,因此当图片处理抛出异常时,会导致整个SpringBoot工程关闭,所以有条件请尽量升级imagej2版本,直接launch来解决这个问题。