修改相似度算法

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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.opencv.core.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
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.imgproc.Imgproc;
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
*
*
* @author yanzhu
* @date 2025-09-03
*
* URLBase64
*/
public class ImageSimilarityUtils {
private static final Logger log = LoggerFactory.getLogger(ImageSimilarityUtils.class);
// 相似度阈值,大于此值认为是同一个人
private static final double SIMILARITY_THRESHOLD = 0.75;
// 人脸识别场景下的考勤阈值(针对同一个人的不同照片)
private static final double ATTENDANCE_THRESHOLD = 0.70;
// OpenCV相关
// OpenCV人脸检测器
private static CascadeClassifier faceDetector;
static {
try {
// 加载OpenCV库
// 加载OpenCV库使用openpnp的OpenCV依赖
nu.pattern.OpenCV.loadLocally();
// 初始化人脸检测器
// 注意在实际部署时需要确保haarcascade_frontalface_alt.xml文件在classpath中
String cascadePath = "haarcascade_frontalface_alt.xml";
// 初始化人脸检测器(使用相对路径)
String cascadePath = ImageSimilarityUtils.class.getClassLoader().getResource("opencv/haarcascade_frontalface_default.xml").getPath();
faceDetector = new CascadeClassifier(cascadePath);
} catch (Exception e) {
log.error("OpenCV初始化失败", e);
System.err.println("OpenCV加载失败将使用基础相似度计算方法: " + e.getMessage());
faceDetector = null;
}
}
/**
*
*
* @param imagePath1 1URL
* @param imagePath2 2URL
* @return 0-11
*
* @param imageUrl1 URL
* @param imageUrl2 URL
* @return 0-1
*/
public static double calculateFaceSimilarity(String imagePath1, String imagePath2) {
public static double calculateFaceSimilarity(String imageUrl1, String imageUrl2) {
try {
// 检查参数
if (imagePath1 == null || imagePath2 == null) {
log.warn("图片路径不能为空");
// 下载图片
BufferedImage image1 = downloadImage(imageUrl1);
BufferedImage image2 = downloadImage(imageUrl2);
if (image1 == null || image2 == null) {
return 0.0;
}
// 读取图片
Mat image1 = readImageAsMat(imagePath1);
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;
// 提取特征并计算相似度
return calculateImageSimilarity(image1, image2);
} catch (Exception e) {
log.error("计算人脸图片相似度时发生错误", e);
e.printStackTrace();
return 0.0;
}
}
/**
*
*
* @param imagePath1 1
* @param imagePath2 2
* @return
* Base64
*
* @param imageBase64_1 Base64
* @param imageBase64_2 Base64
* @return 0-1
*/
public static boolean isFaceMatchForAttendance(String imagePath1, String imagePath2) {
double similarity = calculateFaceSimilarity(imagePath1, imagePath2);
return similarity >= ATTENDANCE_THRESHOLD;
}
/**
* OpenCVMat
*
* @param imagePath
* @return Mat
*/
private static Mat readImageAsMat(String imagePath) {
public static double calculateFaceSimilarityByBase64(String imageBase64_1, String imageBase64_2) {
try {
if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
// 处理网络图片
URL url = new URL(imagePath);
Path tempFile = Files.createTempFile("image_", ".jpg");
Files.copy(url.openStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
Mat mat = Imgcodecs.imread(tempFile.toAbsolutePath().toString());
Files.delete(tempFile); // 清理临时文件
return mat;
} else {
// 处理本地图片
return Imgcodecs.imread(imagePath);
// 解码Base64图片
BufferedImage image1 = decodeBase64Image(imageBase64_1);
BufferedImage image2 = decodeBase64Image(imageBase64_2);
if (image1 == null || image2 == null) {
return 0.0;
}
// 提取特征并计算相似度
return calculateImageSimilarity(image1, image2);
} catch (Exception e) {
log.error("读取图片失败: {}", imagePath, e);
return new Mat();
e.printStackTrace();
return 0.0;
}
}
/**
*
*
* @param image
* @return Mat
*
*
* @param imageUrl1 URL
* @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 {
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();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
Mat gray = new Mat();
Imgproc.cvtColor(mat, gray, Imgproc.COLOR_BGR2GRAY);
// 检测人脸
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();
// 如果检测到人脸,裁剪第一个人脸区域
if (faces.length > 0) {
Rect face = faces[0];
Mat croppedFace = new Mat(image, face);
grayImage.release();
faceDetections.release();
return croppedFace;
// 返回最大的人脸区域
return faces[0];
}
grayImage.release();
faceDetections.release();
return new Mat();
return null;
} 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();
}
}
/**
*
*
* @param originalImage
* @return
*
*
* @param image
* @param width
* @param height
* @return
*/
private static Mat preprocessForFaceRecognition(Mat originalImage) {
// 1. 调整大小到标准尺寸256x256
Mat resized = new Mat();
Imgproc.resize(originalImage, resized, new Size(256, 256));
private static BufferedImage scaleImage(BufferedImage image, int width, int height) {
BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
scaledImage.getGraphics().drawImage(image, 0, 0, width, height, null);
return scaledImage;
}
/**
*
*
* @param image
* @return 256
*/
private static int[] calculateHistogram(BufferedImage image) {
int[] histogram = new int[256];
int width = image.getWidth();
int height = image.getHeight();
// 2. 转换为灰度图(如果还不是的话)
Mat gray = new Mat();
if (resized.channels() == 3) {
Imgproc.cvtColor(resized, gray, Imgproc.COLOR_BGR2GRAY);
} else {
resized.copyTo(gray);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
// 转换为灰度值 (0.299*R + 0.587*G + 0.114*B)
int gray = (int)(0.299 * r + 0.587 * g + 0.114 * b);
histogram[gray]++;
}
}
// 3. 直方图均衡化增强对比度
Mat enhanced = new Mat();
Imgproc.equalizeHist(gray, enhanced);
// 释放中间资源
resized.release();
gray.release();
return enhanced;
return histogram;
}
/**
* 使
*
* @param image1 1
* @param image2 2
* @return 0-1
*
*
* @param vector1 1
* @param vector2 2
* @return 0-1
*/
private static double calculateHistogramSimilarity(Mat image1, Mat image2) {
// 计算直方图
Mat hist1 = new Mat();
Mat hist2 = new Mat();
private static double calculateCosineSimilarity(int[] vector1, int[] vector2) {
double dotProduct = 0.0;
double norm1 = 0.0;
double norm2 = 0.0;
// 设置直方图参数
MatOfInt histSize = new MatOfInt(256);
MatOfFloat ranges = new MatOfFloat(0f, 256f);
MatOfInt channels = new MatOfInt(0);
for (int i = 0; i < vector1.length; i++) {
dotProduct += vector1[i] * vector2[i];
norm1 += Math.pow(vector1[i], 2);
norm2 += Math.pow(vector2[i], 2);
}
// 计算直方图
Imgproc.calcHist(java.util.Arrays.asList(image1), channels, new Mat(), hist1, histSize, ranges);
Imgproc.calcHist(java.util.Arrays.asList(image2), channels, new Mat(), hist2, histSize, ranges);
// 避免除以零
if (norm1 == 0 || norm2 == 0) {
return 0.0;
}
// 归一化直方图
Core.normalize(hist1, hist1);
Core.normalize(hist2, hist2);
// 计算直方图相关性
double similarity = Imgproc.compareHist(hist1, hist2, Imgproc.HISTCMP_CORREL);
// 释放资源
hist1.release();
hist2.release();
histSize.release();
ranges.release();
channels.release();
// 确保相似度在0-1范围内
return Math.max(0.0, Math.min(1.0, similarity));
return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
}
public static void main(String[] args) {
String userPicture = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/836e5c21f83604894486069394dcd22e_20250903170015A004.jpg";
String attImg = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/c4e2c8fb9d9f66e03902f9e3fea17f99_20250903165717A003.jpg";
double similarity = ImageSimilarityUtils.calculateFaceSimilarity(userPicture, attImg);
System.out.println("人脸相似度: " + similarity);
System.out.println("考勤匹配: " + ImageSimilarityUtils.isFaceMatchForAttendance(userPicture, attImg));
System.out.println("开始测试人脸相似度算法...");
// 测试1同一个人的不同照片应该匹配
String userPicture1 = "https://xiangguan.sxyanzhu.com/statics/2025/09/03/836e5c21f83604894486069394dcd22e_20250903170015A004.jpg";
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测试完成。");
}
}