背景:之前使用了java+opencv进行人脸识别,精度上基本达不到要求,估计还是得使用第三方的api接口,比如阿里的人脸识别、百度的人脸识别等,当然,都是要钱的,哈哈哈。下面给大家介绍以下图片合成的两种方式。
方法一,基于opencv进行图片合成,这边主要使用到的是Core、Mat、Rect、Imgcodecs这几个基类。(ps:看过我之前写的那篇关于人脸识别的博客的小伙伴,如果已经配置的opencv,那推荐使用这个方法哦)
package org.Litluecat.utils;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.Rect;
import org.opencv.imgcodecs.Imgcodecs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 基于opencv4.1.0进行图片合成
* @author Litluecat
*/
public class ImageOverlapUtils {
private static final Logger log = LoggerFactory.getLogger(ImageOverlapUtils.class);
/**
* 第一张图权重
*/
private static final double bottomImgAlpha = 1.0;
/**
* 第二张图权重
*/
private static final double topBetaImgAlpha = 1.0;
/**
* 额外添加权重
*/
private static final double gamma = 0;
/**
* 默认合成起始x坐标为0
*/
private static final int x = 0;
/**
* 默认合成起始y坐标为0
*/
private static final int y = 0;
/**
* 图片覆盖重叠合成
* @param bottomImgUrl 底部图片,必须大等于顶部图片
* @param topImgUrl 顶部图片
* @param saveImgUrl 最终合成图片保存位置
* @param x 顶部图片合成的点x坐标
* @param y 顶部图片合成的点y坐标
* @return 合成成功,返回true
*/
public static boolean imgOverlap(String bottomImgUrl, String topImgUrl, String saveImgUrl, int x, int y){
log.info("开始图片合成。。。");
long begin = System.currentTimeMillis();
boolean isTrue = false;
Mat bottomImage = Imgcodecs.imread(bottomImgUrl);
Mat topImage = Imgcodecs.imread(topImgUrl);
if(bottomImage.empty() || topImage.empty()){
log.info("请确定待合成图片文件存在,bottomImage={},topImage={}", bottomImage, topImage);
}else{
if(bottomImage.cols() >= topImage.cols() && bottomImage.rows() >= topImage.rows()){
Mat imageROI = new Mat(bottomImage,(new Rect(x,y,topImage.cols(),topImage.rows())));
Core.addWeighted(imageROI, bottomImgAlpha, topImage, topBetaImgAlpha, gamma, imageROI);
Imgcodecs.imwrite(saveImgUrl , bottomImage);
isTrue = true;
}else{
log.info("请确定底部图片大于顶部图片,bottomImage={},topImage={}", bottomImage, topImage);
}
}
log.info("图片合成结束,耗时:{}ms,saveImgUrl={}", (System.currentTimeMillis() - begin),saveImgUrl);
return isTrue;
}
/**
* 图片覆盖重叠合成,默认合成起始坐标为(0,0)
* @param bottomImgUrl 底部图片,必须大等于顶部图片
* @param topImgUrl 顶部图片
* @param saveImgUrl 最终合成图片保存位置
* @return 合成成功,返回true
*/
public static boolean imgOverlap(String bottomImgUrl, String topImgUrl, String saveImgUrl){
return imgOverlap(bottomImgUrl,topImgUrl,saveImgUrl,x,y);
}
//测试类
public static void main(String[] args) {
String endImgUrl = "图片地址";
//图片合成
ImageOverlapUtils.imgOverlap(endImgUrl+"imageROI.jpg", endImgUrl+"img1.png", endImgUrl+"img2.png");
}
}
方法二,是直接使用java的Graphics2D基类,好处就是不需要额外搭配环境,只需要简单的引入几个包就行了。(ps:这边还提供了一个base64编码转图片的功能,不需要的小伙伴可以不调用它,直接调用图片合成的方法就行了。)
package org.Litluecat.utils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.ImageIO;
/**
* 基于java的图片合成重绘
* @author Litluecat
*/
public class ImageUtils {
private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
/**
* 合成图片的后缀名
*/
private static String formatName = "png";
/**
* 将图片写入缓存
* @param imgName 图片地址
* @return 返回图片缓存
*/
private static BufferedImage loadImageLocal(String imgName) {
try {
return ImageIO.read(new File(imgName));
} catch (IOException e) {
log.info("图片写入缓存失败,img={},失败原因:{}", imgName, e.getMessage());
}
return null;
}
/**
* 将合成后图片保存到本地
* @param newImage 保存地址
* @param img 合成后的图片缓存
*/
private static void writeImageLocal(String newImage, BufferedImage img) {
if (null != newImage && null != img) {
try {
File outPutFile = new File(newImage);
ImageIO.write(img, formatName, outPutFile);
log.info("图片合成结束,新图片地址:{}", newImage);
} catch (IOException e) {
log.info("图片保存失败,失败原因:{}", e.getMessage());
}
}
}
/**
* 照片合成重绘
* @param t 顶部照片
* @param b 底部照片
* @return 返回重绘后的照片
*/
private static BufferedImage modifyImagetogeter(BufferedImage t, BufferedImage b) {
try {
int topW = t.getWidth();
int topH = t.getHeight();
int backgroundW = b.getWidth();
int backgroundH = b.getHeight();
if(backgroundW >= topW && backgroundH >= topH){
int x = (backgroundW-topW) / 2;
int y = (backgroundH-topH) / 2;
Graphics2D g = b.createGraphics();
g.drawImage(t, (x-1), (y-1), topW, topH, null);
g.dispose();
}else{
log.info("底部照片大小比顶部照片小,请确定入参是否正确");
}
} catch (Exception e) {
log.info("照片合成重绘失败,失败原因:{}",e.getMessage());
}
return b;
}
/**
* 照片合成工具类
* @param topImg 顶部照片
* @param backgroundImg 底部照片
* @param endImg 合成后照片存储地址
*/
public static void imgOverlap(String topImg, String backgroundImg, String endImg){
BufferedImage t = loadImageLocal(topImg);
BufferedImage b = loadImageLocal(backgroundImg);
if(null != t && null != b){
writeImageLocal(endImg, modifyImagetogeter(t, b));
}else{
log.info("请确定图片缓存区不为空,topImgBuf={}, backgroundBuf={}", t, b);
}
}
/**
* 对字节数组字符串进行Base64解码并生成图片
* @param imgStr 图片的base64编码
* @param imgFilePath base64转图片的保存地址
* @return
*/
public static boolean GenerateImage(String imgStr, String imgFilePath) {
// 图像数据为空
if (StringUtils.isBlank(imgStr)) {
log.info("图片的base64编码为空,imgStr={}", imgStr);
return false;
}
BASE64Decoder decoder = new BASE64Decoder();
try {
// Base64解码
byte[] bytes = decoder.decodeBuffer(imgStr);
for (int i = 0; i < bytes.length; ++i) {
if (bytes[i] < 0) {
// 调整异常数据
bytes[i] += 256;
}
}
OutputStream out = new FileOutputStream(imgFilePath);
out.write(bytes);
out.flush();
out.close();
return true;
} catch (Exception e) {
log.info("图片的base64编码解析异常,异常原因:{}", e.getMessage());
return false;
}
}
/**
* 测试类
* @param args
*/
public static void main(String[] args) {
log.info("开始图片合成");
long begin = System.currentTimeMillis();
//签名图片地址
String signatureImgUtl = "C:\\Users\\lenovo\\Desktop\\qm.jpg";
//签名图片的base64编码
String imgBase64 = "base64编码";
//banse64编码转图片
if(ImageUtils.GenerateImage(imgBase64, signatureImgUtl)){
//指纹图片地址
String fingerprintImgUrl = "C:\\Users\\lenovo\\Desktop\\img1.png";
//合成后的图片保存地址
String newImgUrl = "C:\\Users\\lenovo\\Desktop\\img5.png";
//开始图片合成
ImageUtils.imgOverlap(fingerprintImgUrl, signatureImgUtl, newImgUrl);
}else{
log.info("图片的base64编码解析异常");
}
log.info("图片合成结束,耗时:{}ms", (System.currentTimeMillis() - begin));
}
}
总结:如果已经配置了opencv,就尽量去使用它,功能还是蛮多了,可以去多熟悉它。如果只是为了实现图片合成这个功能,那就没必要特意去配置opencv,性价比很低,直接使用java进行图片合成就行了。