修改相似度算法

dev_xd
lj7788 2025-09-03 23:14:26 +08:00
parent b6055e3cbb
commit ccd0e6cd49
3 changed files with 33699 additions and 24534 deletions

View File

@ -1,253 +1,454 @@
package com.yanzhu.common.core.utils; package com.yanzhu.common.core.utils;
import org.slf4j.Logger; import java.awt.image.BufferedImage;
import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream;
import org.opencv.core.*; import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Base64;
import javax.imageio.ImageIO;
import org.opencv.core.Core;
import org.opencv.core.Mat;
import org.opencv.core.MatOfRect;
import org.opencv.core.Rect;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc; import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.CascadeClassifier; import org.opencv.objdetect.CascadeClassifier;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
/** /**
* OpenCV *
* * URLBase64
*
* @author yanzhu
* @date 2025-09-03
*/ */
public class ImageSimilarityUtils { public class ImageSimilarityUtils {
private static final Logger log = LoggerFactory.getLogger(ImageSimilarityUtils.class); // 相似度阈值,大于此值认为是同一个人
private static final double SIMILARITY_THRESHOLD = 0.75;
// 人脸识别场景下的考勤阈值(针对同一个人的不同照片) // OpenCV人脸检测器
private static final double ATTENDANCE_THRESHOLD = 0.70;
// OpenCV相关
private static CascadeClassifier faceDetector; private static CascadeClassifier faceDetector;
static { static {
try { try {
// 加载OpenCV库 // 加载OpenCV库使用openpnp的OpenCV依赖
nu.pattern.OpenCV.loadLocally(); nu.pattern.OpenCV.loadLocally();
// 初始化人脸检测器(使用相对路径)
// 初始化人脸检测器 String cascadePath = ImageSimilarityUtils.class.getClassLoader().getResource("opencv/haarcascade_frontalface_default.xml").getPath();
// 注意在实际部署时需要确保haarcascade_frontalface_alt.xml文件在classpath中
String cascadePath = "haarcascade_frontalface_alt.xml";
faceDetector = new CascadeClassifier(cascadePath); faceDetector = new CascadeClassifier(cascadePath);
} catch (Exception e) { } catch (Exception e) {
log.error("OpenCV初始化失败", e); System.err.println("OpenCV加载失败将使用基础相似度计算方法: " + e.getMessage());
faceDetector = null;
} }
} }
/** /**
* *
* *
* @param imagePath1 1URL * @param imageUrl1 URL
* @param imagePath2 2URL * @param imageUrl2 URL
* @return 0-11 * @return 0-1
*/ */
public static double calculateFaceSimilarity(String imagePath1, String imagePath2) { public static double calculateFaceSimilarity(String imageUrl1, String imageUrl2) {
try { try {
// 检查参数 // 下载图片
if (imagePath1 == null || imagePath2 == null) { BufferedImage image1 = downloadImage(imageUrl1);
log.warn("图片路径不能为空"); BufferedImage image2 = downloadImage(imageUrl2);
if (image1 == null || image2 == null) {
return 0.0; return 0.0;
} }
// 读取图片 // 提取特征并计算相似度
Mat image1 = readImageAsMat(imagePath1); return calculateImageSimilarity(image1, image2);
Mat image2 = readImageAsMat(imagePath2);
if (image1.empty() || image2.empty()) {
log.warn("无法读取图片文件: {} 或 {}", imagePath1, imagePath2);
return 0.0;
}
// 检测并裁剪人脸区域
Mat face1 = detectAndCropFace(image1);
Mat face2 = detectAndCropFace(image2);
// 如果无法检测到人脸,则使用整张图片
if (face1.empty()) face1 = image1;
if (face2.empty()) face2 = image2;
// 为人脸识别场景预处理
Mat processedImage1 = preprocessForFaceRecognition(face1);
Mat processedImage2 = preprocessForFaceRecognition(face2);
// 使用OpenCV计算直方图相似度
double similarity = calculateHistogramSimilarity(processedImage1, processedImage2);
log.debug("计算人脸图片相似度完成: {} 和 {}, 相似度: {}",
imagePath1, imagePath2, similarity);
// 释放资源
image1.release();
image2.release();
face1.release();
face2.release();
processedImage1.release();
processedImage2.release();
return similarity;
} catch (Exception e) { } catch (Exception e) {
log.error("计算人脸图片相似度时发生错误", e); e.printStackTrace();
return 0.0; return 0.0;
} }
} }
/** /**
* * Base64
* *
* @param imagePath1 1 * @param imageBase64_1 Base64
* @param imagePath2 2 * @param imageBase64_2 Base64
* @return * @return 0-1
*/ */
public static boolean isFaceMatchForAttendance(String imagePath1, String imagePath2) { public static double calculateFaceSimilarityByBase64(String imageBase64_1, String imageBase64_2) {
double similarity = calculateFaceSimilarity(imagePath1, imagePath2);
return similarity >= ATTENDANCE_THRESHOLD;
}
/**
* OpenCVMat
*
* @param imagePath
* @return Mat
*/
private static Mat readImageAsMat(String imagePath) {
try { try {
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) { // 解码Base64图片
// 处理网络图片 BufferedImage image1 = decodeBase64Image(imageBase64_1);
URL url = new URL(imagePath); BufferedImage image2 = decodeBase64Image(imageBase64_2);
Path tempFile = Files.createTempFile("image_", ".jpg");
Files.copy(url.openStream(), tempFile, StandardCopyOption.REPLACE_EXISTING); if (image1 == null || image2 == null) {
Mat mat = Imgcodecs.imread(tempFile.toAbsolutePath().toString()); return 0.0;
Files.delete(tempFile); // 清理临时文件
return mat;
} else {
// 处理本地图片
return Imgcodecs.imread(imagePath);
} }
// 提取特征并计算相似度
return calculateImageSimilarity(image1, image2);
} catch (Exception e) { } catch (Exception e) {
log.error("读取图片失败: {}", imagePath, e); e.printStackTrace();
return new Mat(); return 0.0;
} }
} }
/** /**
* *
* *
* @param image * @param imageUrl1 URL
* @return Mat * @param imageUrl2 URL
* @return
*/ */
private static Mat detectAndCropFace(Mat image) { public static boolean isFaceMatchForAttendance(String imageUrl1, String imageUrl2) {
double similarity = calculateFaceSimilarity(imageUrl1, imageUrl2);
return similarity >= SIMILARITY_THRESHOLD;
}
/**
* Base64
*
* @param imageBase64_1 Base64
* @param imageBase64_2 Base64
* @return
*/
public static boolean isFaceMatchForAttendanceByBase64(String imageBase64_1, String imageBase64_2) {
double similarity = calculateFaceSimilarityByBase64(imageBase64_1, imageBase64_2);
return similarity >= SIMILARITY_THRESHOLD;
}
/**
*
*
* @param imageUrl URL
* @return BufferedImage
*/
private static BufferedImage downloadImage(String imageUrl) {
try { try {
URL url = new URL(imageUrl);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
return ImageIO.read(connection.getInputStream());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Base64
*
* @param base64Image Base64
* @return BufferedImage
* @throws IOException
*/
private static BufferedImage decodeBase64Image(String base64Image) throws IOException {
// 移除可能存在的Base64前缀如data:image/jpeg;base64,
if (base64Image.contains(",")) {
base64Image = base64Image.split(",")[1];
}
byte[] imageBytes = Base64.getDecoder().decode(base64Image);
return ImageIO.read(new java.io.ByteArrayInputStream(imageBytes));
}
/**
* Base64
*
* @param image
* @param formatName "jpg""png"
* @return Base64
* @throws IOException
*/
private static String encodeImageToBase64(BufferedImage image, String formatName) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, formatName, baos);
byte[] imageBytes = baos.toByteArray();
return Base64.getEncoder().encodeToString(imageBytes);
}
/**
*
* 使OpenCV
*
* @param image1
* @param image2
* @return 0-1
*/
private static double calculateImageSimilarity(BufferedImage image1, BufferedImage image2) {
// 如果OpenCV初始化失败直接使用基础方法
if (faceDetector == null) {
return calculateImageSimilarityBasic(image1, image2);
}
try {
// 检测人脸区域
Rect face1 = detectFace(image1);
Rect face2 = detectFace(image2);
// 如果检测到人脸,使用人脸区域进行比较
if (face1 != null && face2 != null) {
BufferedImage faceImage1 = cropFace(image1, face1);
BufferedImage faceImage2 = cropFace(image2, face2);
// 将人脸区域缩放到相同大小
int width = 100;
int height = 100;
BufferedImage scaledFace1 = scaleImage(faceImage1, width, height);
BufferedImage scaledFace2 = scaleImage(faceImage2, width, height);
// 计算人脸区域的灰度直方图
int[] histogram1 = calculateHistogram(scaledFace1);
int[] histogram2 = calculateHistogram(scaledFace2);
// 计算直方图相似度(余弦相似度)
return calculateCosineSimilarity(histogram1, histogram2);
}
// 如果未检测到人脸,使用整个图片进行比较
int width = 100;
int height = 100;
BufferedImage scaledImage1 = scaleImage(image1, width, height);
BufferedImage scaledImage2 = scaleImage(image2, width, height);
// 计算图片的灰度直方图
int[] histogram1 = calculateHistogram(scaledImage1);
int[] histogram2 = calculateHistogram(scaledImage2);
// 计算直方图相似度(余弦相似度)
return calculateCosineSimilarity(histogram1, histogram2);
} catch (Exception e) {
e.printStackTrace();
// 发生异常时使用原始方法
return calculateImageSimilarityBasic(image1, image2);
}
}
/**
* 使OpenCV
* 使
*
* @param image1
* @param image2
* @return 0-1
*/
private static double calculateImageSimilarityBasic(BufferedImage image1, BufferedImage image2) {
// 将图片缩放到相同大小
int width = 100;
int height = 100;
BufferedImage scaledImage1 = scaleImage(image1, width, height);
BufferedImage scaledImage2 = scaleImage(image2, width, height);
// 计算图片的灰度直方图
int[] histogram1 = calculateHistogram(scaledImage1);
int[] histogram2 = calculateHistogram(scaledImage2);
// 计算直方图相似度(余弦相似度)
return calculateCosineSimilarity(histogram1, histogram2);
}
/**
*
*
* @param image
* @return null
*/
private static Rect detectFace(BufferedImage image) {
try {
// 将BufferedImage转换为OpenCV Mat
Mat mat = bufferedImageToMat(image);
// 转换为灰度图 // 转换为灰度图
Mat grayImage = new Mat(); Mat gray = new Mat();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY); Imgproc.cvtColor(mat, gray, Imgproc.COLOR_BGR2GRAY);
// 检测人脸 // 检测人脸
MatOfRect faceDetections = new MatOfRect(); MatOfRect faceDetections = new MatOfRect();
faceDetector.detectMultiScale(grayImage, faceDetections); faceDetector.detectMultiScale(gray, faceDetections, 1.1, 3, 0, new Size(30, 30), new Size());
Rect[] faces = faceDetections.toArray(); Rect[] faces = faceDetections.toArray();
// 如果检测到人脸,裁剪第一个人脸区域
if (faces.length > 0) { if (faces.length > 0) {
Rect face = faces[0]; // 返回最大的人脸区域
Mat croppedFace = new Mat(image, face); return faces[0];
grayImage.release();
faceDetections.release();
return croppedFace;
} }
grayImage.release(); return null;
faceDetections.release();
return new Mat();
} catch (Exception e) { } catch (Exception e) {
log.warn("人脸检测失败,使用整张图片", e); e.printStackTrace();
return null;
}
}
/**
*
*
* @param image
* @param faceRect
* @return
*/
private static BufferedImage cropFace(BufferedImage image, Rect faceRect) {
// 确保人脸区域在图片范围内
int x = Math.max(0, faceRect.x);
int y = Math.max(0, faceRect.y);
int width = Math.min(image.getWidth() - x, faceRect.width);
int height = Math.min(image.getHeight() - y, faceRect.height);
return image.getSubimage(x, y, width, height);
}
/**
* BufferedImageOpenCV Mat
*
* @param image BufferedImage
* @return OpenCV Mat
*/
private static Mat bufferedImageToMat(BufferedImage image) {
// 创建临时文件
try {
File tempFile = File.createTempFile("opencv", ".jpg");
ImageIO.write(image, "jpg", tempFile);
// 读取为Mat
Mat mat = Imgcodecs.imread(tempFile.getAbsolutePath());
// 删除临时文件
tempFile.delete();
return mat;
} catch (Exception e) {
e.printStackTrace();
return new Mat(); return new Mat();
} }
} }
/** /**
* *
* *
* @param originalImage * @param image
* @return * @param width
* @param height
* @return
*/ */
private static Mat preprocessForFaceRecognition(Mat originalImage) { private static BufferedImage scaleImage(BufferedImage image, int width, int height) {
// 1. 调整大小到标准尺寸256x256 BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Mat resized = new Mat(); scaledImage.getGraphics().drawImage(image, 0, 0, width, height, null);
Imgproc.resize(originalImage, resized, new Size(256, 256)); return scaledImage;
// 2. 转换为灰度图(如果还不是的话)
Mat gray = new Mat();
if (resized.channels() == 3) {
Imgproc.cvtColor(resized, gray, Imgproc.COLOR_BGR2GRAY);
} else {
resized.copyTo(gray);
}
// 3. 直方图均衡化增强对比度
Mat enhanced = new Mat();
Imgproc.equalizeHist(gray, enhanced);
// 释放中间资源
resized.release();
gray.release();
return enhanced;
} }
/** /**
* 使 *
* *
* @param image1 1 * @param image
* @param image2 2 * @return 256
* @return 0-1
*/ */
private static double calculateHistogramSimilarity(Mat image1, Mat image2) { private static int[] calculateHistogram(BufferedImage image) {
// 计算直方图 int[] histogram = new int[256];
Mat hist1 = new Mat(); int width = image.getWidth();
Mat hist2 = new Mat(); int height = image.getHeight();
// 设置直方图参数 for (int y = 0; y < height; y++) {
MatOfInt histSize = new MatOfInt(256); for (int x = 0; x < width; x++) {
MatOfFloat ranges = new MatOfFloat(0f, 256f); int rgb = image.getRGB(x, y);
MatOfInt channels = new MatOfInt(0); int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
// 计算直方图 // 转换为灰度值 (0.299*R + 0.587*G + 0.114*B)
Imgproc.calcHist(java.util.Arrays.asList(image1), channels, new Mat(), hist1, histSize, ranges); int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b);
Imgproc.calcHist(java.util.Arrays.asList(image2), channels, new Mat(), hist2, histSize, ranges); histogram[gray]++;
}
}
// 归一化直方图 return histogram;
Core.normalize(hist1, hist1); }
Core.normalize(hist2, hist2);
// 计算直方图相关性 /**
double similarity = Imgproc.compareHist(hist1, hist2, Imgproc.HISTCMP_CORREL); *
*
* @param vector1 1
* @param vector2 2
* @return 0-1
*/
private static double calculateCosineSimilarity(int[] vector1, int[] vector2) {
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 释放资源 for (int i = 0; i < vector1.length; i++) {
hist1.release(); dotProduct += vector1[i] * vector2[i];
hist2.release(); norm1 += Math.pow(vector1[i], 2);
histSize.release(); norm2 += Math.pow(vector2[i], 2);
ranges.release(); }
channels.release();
// 确保相似度在0-1范围内 // 避免除以零
return Math.max(0.0, Math.min(1.0, similarity)); if (norm1 == 0 || norm2 == 0) {
return 0.0;
}
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
} }
public static void main(String[] args) { public static void main(String[] args) {
String userPicture = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/836e5c21f83604894486069394dcd22e_20250903170015A004.jpg"; System.out.println("开始测试人脸相似度算法...");
String attImg = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/c4e2c8fb9d9f66e03902f9e3fea17f99_20250903165717A003.jpg";
double similarity = ImageSimilarityUtils.calculateFaceSimilarity(userPicture, attImg); // 测试1同一个人的不同照片应该匹配
System.out.println("人脸相似度: " + similarity); String userPicture1 = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/836e5c21f83604894486069394dcd22e_20250903170015A004.jpg";
System.out.println("考勤匹配: " + ImageSimilarityUtils.isFaceMatchForAttendance(userPicture, attImg)); String userPicture2 = "http://62.234.3.186/statics/2025/06/11/a28457e2847333886c8e0f4fd9cd24bc_20250611101313A331.jpg";
String userPicture3 = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/c4e2c8fb9d9f66e03902f9e3fea17f99_20250903165717A003.jpg";
String userPicture4 = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/8c7dd922ad47494fc02c388e12c00eac_20250903132353A852.png";
String userPicture5 = "http://62.234.3.186/statics/2025/06/11/87052f8fa3eaa8840bc2e4fe556a825e_20250611101011A328.jpg";
String img1=userPicture4;
String img2=userPicture5;
System.out.println("\n测试1:不同人的不同照片");
System.out.println("照片2: " + img1);
System.out.println("照片5: " + img2);
double similarity1 = ImageSimilarityUtils.calculateFaceSimilarity(img1, img2);
System.out.println("人脸相似度: " + similarity1);
System.out.println("考勤匹配: " + ImageSimilarityUtils.isFaceMatchForAttendance(img1, img2));
// System.out.println("\n测试2同一个人的另一组照片");
// System.out.println("照片1: " + userPicture1);
// System.out.println("照片4: " + userPicture4);
// double similarity2 = ImageSimilarityUtils.calculateFaceSimilarity(userPicture1, userPicture4);
// System.out.println("人脸相似度: " + similarity2);
// System.out.println("考勤匹配: " + ImageSimilarityUtils.isFaceMatchForAttendance(userPicture1, userPicture4));
// System.out.println("\n测试2同一个人的另一组照片");
// System.out.println("照片1: " + userPicture1);
// System.out.println("照片2: " + userPicture2);
// double similarity3 = ImageSimilarityUtils.calculateFaceSimilarity(userPicture1, userPicture2);
// System.out.println("人脸相似度: " + similarity3);
// System.out.println("考勤匹配: " + ImageSimilarityUtils.isFaceMatchForAttendance(userPicture1, userPicture2));
// // 测试Base64图片人脸相似度计算
// // 实际使用时这里应该是真实的Base64编码图片字符串
// System.out.println("\n测试3Base64图片相似度测试");
// try {
// // 将URL图片转为Base64进行测试
// BufferedImage img1 = downloadImage(userPicture1);
// BufferedImage img2 = downloadImage(userPicture3);
// if (img1 != null && img2 != null) {
// String base64Image1 = encodeImageToBase64(img1, "jpg");
// String base64Image2 = encodeImageToBase64(img2, "jpg");
// double base64Similarity = calculateFaceSimilarityByBase64(base64Image1, base64Image2);
// System.out.println("Base64人脸相似度: " + base64Similarity);
// boolean base64IsMatch = isFaceMatchForAttendanceByBase64(base64Image1, base64Image2);
// System.out.println("Base64是否匹配: " + base64IsMatch);
// } else {
// System.out.println("无法下载图片进行Base64测试");
// }
// } catch (Exception e) {
// System.out.println("Base64图片测试失败: " + e.getMessage());
// }
System.out.println("\n测试完成。");
} }
} }