1、FaceNet 特征提取新增人脸对齐

2、人脸检测新5点人脸关键点定位
3、特征提取接口支持多人脸和最佳人脸提取
4、修复人脸框边界精度问题
5、更新 Maven 发布的 groupId
This commit is contained in:
dengwenjie
2025-04-28 16:13:34 +08:00
parent 2fdc20f610
commit 42d2943a94
50 changed files with 3413 additions and 554 deletions

View File

@@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ink.numberone</groupId>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.10</version>
<version>1.0.11</version>
</parent>
<artifactId>smartjavaai-common</artifactId>

View File

@@ -1,5 +1,7 @@
package cn.smartjavaai.common.entity;
import java.util.List;
/**
* 检测结果-矩形区域
* @author dwj
@@ -11,9 +13,13 @@ public class DetectionRectangle {
public int width;
public int height;
public float score;
public String className;
/**
* 人脸关键点
*/
private List<Point> keyPoints;
public DetectionRectangle() {
}
@@ -81,4 +87,12 @@ public class DetectionRectangle {
public void setClassName(String className) {
this.className = className;
}
public List<Point> getKeyPoints() {
return keyPoints;
}
public void setKeyPoints(List<Point> keyPoints) {
this.keyPoints = keyPoints;
}
}

View File

@@ -10,27 +10,27 @@ import java.io.Serializable;
*/
public class Point implements Serializable {
private static final long serialVersionUID = 1L;
private int x;
private int y;
private double x;
private double y;
public Point(int x, int y) {
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public int getX() {
public double getX() {
return x;
}
public void setX(int x) {
public void setX(double x) {
this.x = x;
}
public int getY() {
public double getY() {
return y;
}
public void setY(int y) {
public void setY(double y) {
this.y = y;
}

View File

@@ -1,13 +1,23 @@
package cn.smartjavaai.common.utils;
import ai.djl.modality.cv.BufferedImageFactory;
import ai.djl.modality.cv.Image;
import ai.djl.ndarray.NDArray;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.DetectionResponse;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
//import java.awt.image.ColorConvertOp;
import java.awt.image.ComponentSampleModel;
import java.awt.image.ImageObserver;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 图片处理工具类
@@ -88,6 +98,86 @@ public class ImageUtils {
return image != null && image.getWidth() > 0 && image.getHeight() > 0;
}
/**
* 画检测框
*
* @param image
* @param x
* @param y
* @param width
* @param height
*/
public static void drawImageRect(BufferedImage image, int x, int y, int width, int height) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
g.setColor(new Color(0, 255, 0));
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);
} finally {
g.dispose();
}
}
/**
* 画检测框
*
* @param image
* @param x
* @param y
* @param width
* @param height
*/
public static void drawImageRect(Image image, int x, int y, int width, int height) {
// 将绘制图像转换为Graphics2D
BufferedImage bufferedImage = (BufferedImage)image.getWrappedImage();
Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
try {
g.setColor(new Color(0, 255, 0));
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
g.drawRect(x, y, width, height);
} finally {
g.dispose();
}
}
/**
* 画检测框
*
* @param image
* @param x
* @param y
* @param width
* @param height
*/
public static void drawImageRect(Image image, DetectionResponse detectionResponse) {
if(Objects.nonNull(detectionResponse) && Objects.nonNull(detectionResponse.getRectangleList()) && !detectionResponse.getRectangleList().isEmpty()){
// 将绘制图像转换为Graphics2D'
BufferedImage bufferedImage = (BufferedImage)image.getWrappedImage();
Graphics2D g = (Graphics2D) bufferedImage.getGraphics();
try {
g.setColor(new Color(0, 255, 0));
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
g.setStroke(bStroke);
for(DetectionRectangle detectionRectangle : detectionResponse.getRectangleList()){
g.drawRect(detectionRectangle.getX(), detectionRectangle.getY(), detectionRectangle.getWidth(), detectionRectangle.getHeight());
}
} finally {
g.dispose();
}
}
}

View File

@@ -0,0 +1,119 @@
package cn.smartjavaai.common.utils;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
/**
* OpenCV 工具类
*/
public class OpenCVUtils {
/**
* canny算法边缘检测
*
* @param src
* @return
*/
public static Mat canny(Mat src) {
Mat mat = src.clone();
Imgproc.Canny(src, mat, 100, 200);
return mat;
}
/**
* 画线
*
* @param mat
* @param point1
* @param point2
*/
public static void line(Mat mat, Point point1, Point point2) {
Imgproc.line(mat, point1, point2, new Scalar(255, 255, 255), 1);
}
/**
* NDArray to opencv_core.Mat
*
* @param manager
* @param srcPoints
* @param dstPoints
* @return
*/
public static Mat toOpenCVMat(NDManager manager, NDArray srcPoints, NDArray dstPoints) {
NDArray svdMat = SVDUtils.transformationFromPoints(manager, srcPoints, dstPoints);
double[] doubleArray = svdMat.toDoubleArray();
Mat newSvdMat = new Mat(2, 3, CvType.CV_64F);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
newSvdMat.put(i, j, doubleArray[i * 3 + j]);
}
}
return newSvdMat;
}
/**
* double[][] points array to Mat
* @param points
* @return
*/
public static Mat toOpenCVMat(double[][] points) {
Mat mat = new Mat(5, 2, CvType.CV_64F);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 2; j++) {
mat.put(i, j, points[i * 5 + j]);
}
}
return mat;
}
/**
* 变换矩阵的逆矩阵
*
* @param src
* @return
*/
public static Mat invertAffineTransform(Mat src) {
Mat dst = src.clone();
Imgproc.invertAffineTransform(src, dst);
return dst;
}
/**
* Mat to BufferedImage
*
* @param mat
* @return
*/
public static BufferedImage mat2Image(Mat mat) {
int width = mat.width();
int height = mat.height();
byte[] data = new byte[width * height * (int) mat.elemSize()];
Imgproc.cvtColor(mat, mat, 4);
mat.get(0, 0, data);
BufferedImage ret = new BufferedImage(width, height, 5);
ret.getRaster().setDataElements(0, 0, width, height, data);
return ret;
}
/**
* BufferedImage to Mat
*
* @param img
* @return
*/
public static Mat image2Mat(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
byte[] data = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
Mat mat = new Mat(height, width, CvType.CV_8UC3);
mat.put(0, 0, data);
return mat;
}
}

View File

@@ -0,0 +1,119 @@
package cn.smartjavaai.common.utils;
import Jama.Matrix;
import Jama.SingularValueDecomposition;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
/**
* 仿射变换处理工具
*/
public class SVDUtils {
/**
* 计算仿射变换矩阵
* Calculate affine transformation matrix
*
* @param manager
* @param points1
* @param points2
* @return
*/
public static NDArray transformationFromPoints(
NDManager manager, NDArray points1, NDArray points2) {
// 按列计算均值
// Calculate column-wise mean
NDArray c1 = points1.mean(new int[]{0}); // axis=0 列操作 - axis=0 column operation
NDArray c2 = points2.mean(new int[]{0}); // axis=0 列操作 - axis=0 column operation
// 按列减去均值
// Subtract column-wise mean
points1 = points1.sub(c1);
points2 = points2.sub(c2);
// 计算全局标准差
// Calculate global standard deviation
double s1 = std(points1);
double s2 = std(points2);
// 矩阵除以全局标准差
// Matrix divided by global standard deviation
NDArray djl_s1 = manager.create(s1);
NDArray djl_s2 = manager.create(s2);
points1 = points1.div(djl_s1);
points2 = points2.div(djl_s2);
double[] points1D = points1.toDoubleArray();
double[] points2D = points2.toDoubleArray();
// DJL 格式转换成Jamma格式
// Convert DJL format to Jama format
double[][] m1 = new double[5][2];
double[][] m2 = new double[5][2];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 2; j++) {
m1[i][j] = points1D[i * 2 + j];
}
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 2; j++) {
m2[i][j] = points2D[i * 2 + j];
}
}
Matrix p1 = new Matrix(m1);
Matrix p2 = new Matrix(m2);
// 进行奇异值分解
// Perform singular value decomposition
Matrix p3 = p1.transpose().times(p2);
SingularValueDecomposition s = p3.svd();
Matrix U = s.getU();
Matrix S = s.getS();
Matrix V = s.getV();
// TODO 为什么第2列的符号是反的
// Why is the sign of the second column opposite?
m1 = U.getArray();
m1[0][1] = -m1[0][1];
m1[1][1] = -m1[1][1];
m2 = V.getArray();
m2[0][1] = -m2[0][1];
m2[1][1] = -m2[1][1];
Matrix R = (U.times(V)).transpose();
double[][] rArray = R.getArray();
NDArray newR = manager.create(rArray);
// np.vstack([np.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T)), np.matrix([0.,0., 1.])])
// (s2 / s1) * R
NDArray leftPart = djl_s2.div(djl_s1).mul(newR);
// c2.T - (s2 / s1) * R * c1.T)
NDArray rightPart = c2.reshape(2, 1).sub(leftPart.matMul(c1.reshape(2, 1)));
// numpy.hstack(((s2 / s1) * R, c2.T - (s2 / s1) * R * c1.T))
NDArray upPart = leftPart.concat(rightPart, 1);
// np.matrix([0.,0., 1.])
double[] downArray = {0d, 0d, 1d};
NDArray downPart = manager.create(downArray).reshape(1, 3);
NDArray all = upPart.concat(downPart, 0);
// System.out.println("all: " + all);
return upPart;
}
/**
* 计算全局标准差
* Calculate global standard deviation
*
* @param points
* @return
*/
public static double std(NDArray points) {
points = points.square();
double[] doubleResult = points.toDoubleArray();
double std = 0;
for (int i = 0; i < doubleResult.length; i++) {
std = std + doubleResult[i];
}
std = (float) Math.sqrt(std / doubleResult.length);
return std;
}
}