|
|
@@ -0,0 +1,193 @@
|
|
|
+package com.ktg.common.utils.face;
|
|
|
+
|
|
|
+import cn.hutool.core.lang.Assert;
|
|
|
+import com.ktg.common.config.RuoYiConfig;
|
|
|
+import com.ktg.common.utils.StringUtils;
|
|
|
+import com.ktg.common.vo.FaceCutVO;
|
|
|
+import org.opencv.core.*;
|
|
|
+import org.opencv.imgcodecs.Imgcodecs;
|
|
|
+import org.opencv.imgproc.Imgproc;
|
|
|
+import org.opencv.objdetect.CascadeClassifier;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.nio.file.Paths;
|
|
|
+import java.util.Collections;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 面部裁剪工具
|
|
|
+ *
|
|
|
+ * @author cgj
|
|
|
+ */
|
|
|
+public class FaceCutUtil1
|
|
|
+{
|
|
|
+
|
|
|
+ @Value("${ktg-mes.prod}")
|
|
|
+ private static String prodApi;
|
|
|
+
|
|
|
+
|
|
|
+ // 初始化人脸探测器
|
|
|
+ static CascadeClassifier faceDetector;
|
|
|
+ /**
|
|
|
+ * 图片人脸检测
|
|
|
+ */
|
|
|
+ public static FaceCutVO imageFaceDetection(MultipartFile file, Long userId, String url) throws IOException {
|
|
|
+ // ------------------------加载OpenCV本地库--------------------------------------------
|
|
|
+ // System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
|
|
|
+ String loadPath = "/guoruan/app/opencv/build/java/x64/opencv_java470.dll";
|
|
|
+ if (StringUtils.isBlank(prodApi)) {
|
|
|
+ loadPath = "C:/work/app/install/opencv/build/java/x64/opencv_java470.dll";
|
|
|
+ }
|
|
|
+ System.load(loadPath);
|
|
|
+ // 从配置文件lbpcascade_frontalface.xml中创建一个人脸识别器,文件位于opencv安装目录中
|
|
|
+ String faceDetectorPath = "/guoruan/app/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
|
|
|
+ if (StringUtils.isBlank(prodApi)) {
|
|
|
+ faceDetectorPath = "C:/work/app/install/opencv/sources/data/haarcascades/haarcascade_frontalface_alt.xml";
|
|
|
+ }
|
|
|
+ faceDetector = new CascadeClassifier(faceDetectorPath);
|
|
|
+
|
|
|
+ // -------------------- 定义文件的基础属性----------------------------
|
|
|
+ // 人脸存储基础路径
|
|
|
+ String profile = RuoYiConfig.getProfile();
|
|
|
+ String basePath = profile + "/face/" + userId +"/";
|
|
|
+ if (StringUtils.isBlank(prodApi)) {
|
|
|
+ basePath = "C:" + basePath;
|
|
|
+ }
|
|
|
+ // 原文件裁剪后的路径
|
|
|
+ String content = null;
|
|
|
+ // 原文件转存后的路径
|
|
|
+ String imagePath;
|
|
|
+ // 原文件转存后的前端请求路径
|
|
|
+ String imageUrl = null;
|
|
|
+
|
|
|
+ // 时间戳
|
|
|
+ long currentTimestamp = System.currentTimeMillis();
|
|
|
+ // 设置文件路径
|
|
|
+ String filePath = basePath ;
|
|
|
+ // 确保目录存在,不存在则创建一个
|
|
|
+ Path uploadPath = Paths.get(filePath);
|
|
|
+ if (!Files.exists(uploadPath)) {
|
|
|
+ Files.createDirectories(uploadPath);
|
|
|
+ }
|
|
|
+ // 获取文件名并构建目标路径
|
|
|
+ String fileName = userId + "_" + currentTimestamp + "_" + file.getOriginalFilename();
|
|
|
+ Path targetLocation = uploadPath.resolve(fileName);
|
|
|
+ imagePath = filePath + fileName;
|
|
|
+ // 获取前端的请求路径地址
|
|
|
+ if (imagePath.contains("C:" + profile)) {
|
|
|
+ imageUrl = imagePath.replace("C:" + profile, url + "/profile");
|
|
|
+ } else if (imagePath.contains(profile)) {
|
|
|
+ imageUrl = imagePath.replace(profile, url + "/prod-api" + "/profile");
|
|
|
+ }
|
|
|
+ // 将文件写入目标路径
|
|
|
+ file.transferTo(targetLocation.toFile());
|
|
|
+
|
|
|
+ // 读取测试图片
|
|
|
+ Mat image = Imgcodecs.imread(imagePath);
|
|
|
+ if (image.empty()) {
|
|
|
+ throw new RuntimeException("图片内存为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检测脸部
|
|
|
+ MatOfRect face = new MatOfRect();
|
|
|
+ // 检测图像中的人脸
|
|
|
+ faceDetector.detectMultiScale(image, face);
|
|
|
+ // 匹配Rect矩阵
|
|
|
+ Rect[] rects = face.toArray();
|
|
|
+ System.out.println("识别人脸个数: " + rects.length);
|
|
|
+ Assert.isFalse(rects.length > 1, "识别到多张人脸,请重新调整位置!");
|
|
|
+
|
|
|
+ // 识别图片中的所有人脸并分别保存
|
|
|
+ // int i = 1;
|
|
|
+ // 上面做了限制,现在其实只会有一张人脸
|
|
|
+ FaceCutVO faceCutVO = new FaceCutVO();
|
|
|
+ for (Rect rect : face.toArray()) {
|
|
|
+ Imgproc.rectangle(image, new Point(rect.x, rect.y), new Point(rect.x + rect.width, rect.y + rect.height), new Scalar(0, 255, 0), 3);
|
|
|
+ // 进行图片裁剪
|
|
|
+ String cutImgName = userId + "_" + currentTimestamp + "_cut.jpg";
|
|
|
+ content = filePath + cutImgName;
|
|
|
+ imageCut(imagePath, content, rect.x, rect.y, rect.width, rect.height);
|
|
|
+ // i++;
|
|
|
+ faceCutVO.setUserId(userId);
|
|
|
+ faceCutVO.setType("2");
|
|
|
+ faceCutVO.setContent(content);
|
|
|
+ faceCutVO.setImageUrl(imageUrl);
|
|
|
+ faceCutVO.setImagePath(imagePath);
|
|
|
+ }
|
|
|
+ // ---------------------以下为前端的裁剪动画展示--------------------
|
|
|
+ // 图片中人脸画框保存到本地
|
|
|
+ // Imgcodecs.imwrite("C:/work/file/test1.png", image);
|
|
|
+ // 展示图片
|
|
|
+ // HighGui.imshow("人脸识别", image);
|
|
|
+ // HighGui.waitKey(0);
|
|
|
+
|
|
|
+ // ---------------------------开始计算裁剪后的人脸直方图-----------------
|
|
|
+ Mat mat2 = conv_Mat(content); // 将路径对应的图像转换为灰度图像的Mat对象
|
|
|
+ if (mat2 == null) {
|
|
|
+ return null; // 如果转换失败,返回null
|
|
|
+ }
|
|
|
+ Mat hist2 = computeHistogram(mat2); // 计算第二个图像的直方图
|
|
|
+ System.out.println("hist111112--------/n" + hist2.dump());
|
|
|
+ faceCutVO.setMatData(MatChangeUtil.matToStr(hist2));
|
|
|
+ faceCutVO.setMatRows(hist2.rows());
|
|
|
+ faceCutVO.setMatCols(hist2.cols());
|
|
|
+ faceCutVO.setMatType(hist2.type());
|
|
|
+ return faceCutVO;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 裁剪人脸
|
|
|
+ *
|
|
|
+ * @param readPath 读取文件路径
|
|
|
+ * @param outPath 写出文件路径
|
|
|
+ * @param x 坐标X
|
|
|
+ * @param y 坐标Y
|
|
|
+ * @param width 截图宽度
|
|
|
+ * @param height 截图长度
|
|
|
+ */
|
|
|
+ public static void imageCut(String readPath, String outPath, int x, int y, int width, int height) {
|
|
|
+ // 原始图像
|
|
|
+ Mat image = Imgcodecs.imread(readPath);
|
|
|
+ // 截取的区域
|
|
|
+ Rect rect = new Rect(x, y, width, height);
|
|
|
+ // Mat sub = new Mat(image,rect);
|
|
|
+ Mat sub = image.submat(rect);
|
|
|
+ Mat mat = new Mat();
|
|
|
+ Size size = new Size(width, height);
|
|
|
+ // 人脸进行截图并保存
|
|
|
+ Imgproc.resize(sub, mat, size);
|
|
|
+ Imgcodecs.imwrite(outPath, mat);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 灰度化人脸并返回Mat对象
|
|
|
+ */
|
|
|
+ public static Mat conv_Mat(String imgPath) {
|
|
|
+ Mat mat1 = Imgcodecs.imread(imgPath); // 读取图像文件并转换为Mat对象
|
|
|
+ Mat mat2 = new Mat(); // 创建一个新的Mat对象用于存储灰度图像
|
|
|
+ Imgproc.cvtColor(mat1, mat2, Imgcodecs.IMREAD_COLOR); // 将彩色图像转换为灰度图像
|
|
|
+ MatOfRect faceDetections = new MatOfRect(); // 创建用于存储检测到的人脸区域的MatOfRect对象
|
|
|
+ faceDetector.detectMultiScale(mat1, faceDetections); // 检测人脸区域
|
|
|
+ for (Rect rect : faceDetections.toArray()) { // 遍历检测到的人脸区域
|
|
|
+ return new Mat(mat1, rect); // 返回包含人脸区域的子Mat对象
|
|
|
+ }
|
|
|
+ return null; // 如果没有检测到人脸,返回null
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算直方图
|
|
|
+ */
|
|
|
+ private static Mat computeHistogram(Mat mat) {
|
|
|
+ Mat hist = new Mat(); // 创建用于存储直方图的Mat对象
|
|
|
+ MatOfFloat ranges = new MatOfFloat(0f, 256f); // 定义直方图的范围
|
|
|
+ MatOfInt histSize = new MatOfInt(100); // 定义直方图的大小(越大越精确,但计算速度越慢)
|
|
|
+ Imgproc.calcHist(Collections.singletonList(mat), new MatOfInt(0), new Mat(), hist, histSize, ranges); // 计算直方图
|
|
|
+ Core.normalize(hist, hist, 0, 1, Core.NORM_MINMAX); // 归一化直方图,使其值在0到1之间
|
|
|
+ return hist; // 返回计算好的直方图
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|