问题:
在开发过程中遇到一个问题,需要在图片上加上数据(原卷留痕),由于图片是灰度的,无法进行彩色编辑,需要将灰度图片转成RGB图片,才能进行彩色编辑,于是想到用opencv进行处理。
参考SpringBoot使用OpenCV总结 - ksfzhaohui的个人页面 - OSCHINA - 中文开源技术交流社区
灰度图片处理前 位深度8 无法进行彩色留痕
转成RGB图片后 位深度24 可以在上面进行彩色留痕
在开发过程中遇到一些坑
1、最开始我是去OpenCv官网OpenCV - Browse Files at SourceForge.net 下载的文件解压,使用相对路径加载dll文件,在test环境中测试成功了,后来启动springboot服务后,却报错了
Caused by: java.lang.UnsatisfiedLinkError
因为springboot服务会打成jar包运行,通过system.load并不能加载成功;如果使用绝对路径,就可以加载成功
2、由于我们使用的k8s管理微服务,使用绝对路径加载文件肯定不合适,然后参考其他博主的方案,
最后采用的方式是把读取的库文件,存放到系统的一个临时文件夹下,然后拿到库文件的绝对路径,这样就可以通过 system.load 直接去加载。
代码如下
package com.zhixinhuixue.opencv;
import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author Biscop
* @create 2023-01-05
* 动态链接库加载类
*/
public class NativeLoader {
/**
* 把opencv-460.jar/opencv_java460.dll/opencv_java460.so
* 放在resources下的opencv文件夹中
*/
private static final String NATIVE_PATH = "opencv";
/**
* 加载native dll/so
*
* @throws Exception
*/
public static void loader() throws Exception {
Enumeration<URL> dir = Thread.currentThread().getContextClassLoader().getResources(NATIVE_PATH);
String systemType = System.getProperty("os.name");
String ext = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so";
while (dir.hasMoreElements()) {
URL url = dir.nextElement();
String protocol = url.getProtocol();
if ("jar".equals(protocol)) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
// 遍历Jar包
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String entityName = jarEntry.getName();
if (jarEntry.isDirectory() || !entityName.startsWith(NATIVE_PATH)) {
continue;
}
if (entityName.endsWith(ext)) {
loadJarNative(jarEntry);
}
}
} else if ("file".equals(protocol)) {
File file = new File(url.getPath());
loadFileNative(file, ext);
}
}
}
private static void loadFileNative(File file, String ext) {
if (null == file) {
return;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
if (null != files) {
for (File f : files) {
loadFileNative(f, ext);
}
}
}
if (file.canRead() && file.getName().endsWith(ext)) {
try {
System.load(file.getPath());
System.out.println("加载native文件 :" + file + "成功!!");
} catch (UnsatisfiedLinkError e) {
System.out.println("加载native文件 :" + file + "失败!!请确认操作系统是X86还是X64!!!");
}
}
}
/**
* @param jarEntry
* @throws IOException
* @throws ClassNotFoundException
*/
private static void loadJarNative(JarEntry jarEntry) throws IOException, ClassNotFoundException {
File path = new File(".");
String rootOutputPath = path.getAbsoluteFile().getParent() + File.separator;
String entityName = jarEntry.getName();
String fileName = entityName.substring(entityName.lastIndexOf("/") + 1);
File tempFile = new File(rootOutputPath + File.separator + entityName);
if (!tempFile.getParentFile().exists()) {
tempFile.getParentFile().mkdirs();
}
if (tempFile.exists()) {
tempFile.delete();
}
InputStream in = null;
BufferedInputStream reader = null;
FileOutputStream writer = null;
try {
in = NativeLoader.class.getResourceAsStream(entityName);
if (in == null) {
in = NativeLoader.class.getResourceAsStream("/" + entityName);
if (null == in) {
return;
}
}
NativeLoader.class.getResource(fileName);
reader = new BufferedInputStream(in);
writer = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024];
while (reader.read(buffer) > 0) {
writer.write(buffer);
buffer = new byte[1024];
}
} finally {
if (in != null) {
in.close();
}
if (writer != null) {
writer.close();
}
}
try {
System.load(tempFile.getPath());
System.out.println("加载native文件 :" + tempFile + "成功!!");
} catch (UnsatisfiedLinkError e) {
System.out.println("加载native文件 :" + tempFile + "失败!!请确认操作系统是X86还是X64!!!");
}
}
}
启动时加载或者使用时加载都行
package com.zhixinhuixue.opencv;
import nu.pattern.OpenCV;
import org.springframework.context.annotation.Configuration;
/**
* @author Biscop
* @create 2023-01-05
*/
@Configuration
public class NativeConfig {
static {
try {
NativeLoader.loader();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 引入加载方式
* <dependency>
* <groupId>org.openpnp</groupId>
* <artifactId>opencv</artifactId>
* <version>4.6.0-0</version>
* </dependency>
*/
// static {
// OpenCV.loadLocally();
// }
}
3、引入依赖Maven Central: org.openpnp:opencv:4.6.0-0,已经集成了各个平台的本地库,以及加载本地库的封装类,自己只需要加载就行
调用这个方法OpenCV.loadLocally();
<dependency>
<groupId>org.openpnp</groupId>
<artifactId>opencv</artifactId>
<version>4.6.0-0</version>
</dependency>
我在Linux环境部署时,出现错误 /lib64/libm.so.6: version `GLIBC_2.27' not found,然后我换成3.4.2-2这个版本,但是要注意,有些API会发生变化,要自己去调试。
4、后来和同事讨论,可以通过图片合成的方法,在灰度图像上进行彩色编辑,通过ImageIO就可以实现,无需引入opencv,毕竟这个包很大
//下载图片内容
byte[] data = null;
//处理图片水印
BufferedImage image = null;
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
Image srcImg = ImageIO.read(new URL(imageUrl));
int imgWidth = srcImg.getWidth(null);
int imgHeight = srcImg.getHeight(null);
logger.info("下载图片{}", imageUrl);
//创建生成图片(尺寸,色彩)
image = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D) image.getGraphics();
// 写入原图
g2d.drawImage(srcImg, 0, 0, null);
coordinates.forEach(coordinate -> {
g2d.setColor(coordinate.getColorType() == 1 ? Color.red : Color.green);//画笔颜色
g2d.setStroke(new BasicStroke(5.0f));
if (coordinate.getType() == 2) {//正方形
g2d.drawRect(coordinate.getX(), coordinate.getY(), coordinate.getW(), coordinate.getH());//矩形框(原点x坐标,原点y坐标,矩形的长,矩形的宽)
} else {
Font font = new Font("宋体", Font.ITALIC, coordinate.getFontSize()); //水印字体
g2d.setFont(font); //水印字体
g2d.drawString(coordinate.getContent(), coordinate.getX(), coordinate.getY()); //水印位置
}
});
g2d.dispose();
ImageIO.write(image, "jpeg", os);
data = os.toByteArray();
logger.info("合成图片数据");
} catch (IOException e) {
logger.error("原卷编辑失败", e);
}