Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package smartai.examples.face.headpose;

import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionInfo;
import cn.smartjavaai.common.entity.DetectionResponse;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.entity.face.HeadPose;
import cn.smartjavaai.face.config.FaceDetConfig;
import cn.smartjavaai.face.config.HeadPoseConfig;
import cn.smartjavaai.face.enums.FaceDetModelEnum;
import cn.smartjavaai.face.enums.HeadPoseModelEnum;
import cn.smartjavaai.face.factory.FaceDetModelFactory;
import cn.smartjavaai.face.factory.HeadPoseModelFactory;
import cn.smartjavaai.face.model.facedect.FaceDetModel;
import cn.smartjavaai.face.model.headpose.HeadPoseModel;
import ai.djl.modality.cv.Image;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

/**
* 人脸姿态检测 demo
* <p>
* 演示两种后端的使用方式:
* 1. SeetaFace6 PoseEstimator
* 2. SixDRepNet ONNX 模型
* </p>
*
* @author hyw
*/
@Slf4j
public class HeadPoseDetDemo {

@BeforeClass
public static void beforeAll() throws IOException {
SmartImageFactory.setEngine(SmartImageFactory.Engine.OPENCV);
}

/**
* 使用 SeetaFace6 进行人脸姿态检测(结合人脸检测)
*/
@Test
public void testSeetaFace6HeadPose() {
try {
// 需替换为实际模型存储路径
String seetaModelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";

// 1. 创建人脸姿态检测模型(SeetaFace6)
HeadPoseConfig headPoseConfig = new HeadPoseConfig();
headPoseConfig.setModelEnum(HeadPoseModelEnum.SEETA_FACE6_MODEL);
headPoseConfig.setModelPath(seetaModelPath);
HeadPoseModel headPoseModel = HeadPoseModelFactory.getInstance().getModel(headPoseConfig);

// 2. 创建人脸检测模型
FaceDetConfig faceDetConfig = new FaceDetConfig();
faceDetConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetConfig.setModelPath(seetaModelPath);
FaceDetModel faceDetModel = FaceDetModelFactory.getInstance().getModel(faceDetConfig);

// 3. 检测人脸
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<DetectionResponse> detectionResult = faceDetModel.detect(image);
if (!detectionResult.isSuccess() || detectionResult.getData() == null) {
log.info("人脸检测失败:{}", detectionResult.getMessage());
return;
}

DetectionResponse detectionResponse = detectionResult.getData();
if (detectionResponse.getDetectionInfoList() == null || detectionResponse.getDetectionInfoList().isEmpty()) {
log.info("未检测到人脸");
return;
}

// 4. 对每张人脸进行姿态检测
for (DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()) {
HeadPose headPose = headPoseModel.predict(image, detectionInfo.getDetectionRectangle());
log.info("SeetaFace6 姿态检测结果:pitch={}, yaw={}, roll={}",
headPose.getPitch(), headPose.getYaw(), headPose.getRoll());
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 使用 SixDRepNet ONNX 模型进行人脸姿态检测(结合人脸检测)
*/
@Test
public void testSixDRepNetHeadPose() {
try {
// SeetaFace6 模型路径(用于人脸检测)
String seetaModelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";
// SixDRepNet ONNX 模型路径
String sixDRepNetOnnxPath = "F:/人脸检测/未转换模型/人脸倾斜角度检测/6DRepNet/6drepnet.onnx";

// 1. 创建人脸姿态检测模型(SixDRepNet)
HeadPoseConfig headPoseConfig = new HeadPoseConfig();
headPoseConfig.setModelEnum(HeadPoseModelEnum.SIX_D_REP_NET_MODEL);
headPoseConfig.setModelPath(sixDRepNetOnnxPath);
HeadPoseModel headPoseModel = HeadPoseModelFactory.getInstance().getModel(headPoseConfig);

// 2. 创建人脸检测模型(使用 SeetaFace6 做人脸检测)
FaceDetConfig faceDetConfig = new FaceDetConfig();
faceDetConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetConfig.setModelPath(seetaModelPath);
FaceDetModel faceDetModel = FaceDetModelFactory.getInstance().getModel(faceDetConfig);

// 3. 检测人脸
Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<DetectionResponse> detectionResult = faceDetModel.detect(image);
if (!detectionResult.isSuccess() || detectionResult.getData() == null) {
log.info("人脸检测失败:{}", detectionResult.getMessage());
return;
}

DetectionResponse detectionResponse = detectionResult.getData();
if (detectionResponse.getDetectionInfoList() == null || detectionResponse.getDetectionInfoList().isEmpty()) {
log.info("未检测到人脸");
return;
}

// 4. 对每张人脸进行姿态检测
for (DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()) {
HeadPose headPose = headPoseModel.predict(image, detectionInfo.getDetectionRectangle());
log.info("SixDRepNet 姿态检测结果:pitch={}, yaw={}, roll={}",
headPose.getPitch(), headPose.getYaw(), headPose.getRoll());
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 使用 SixDRepNet 对裁剪后的人脸进行姿态检测
*/
@Test
public void testSixDRepNetCropedFace() {
try {
// SixDRepNet ONNX 模型路径
String sixDRepNetOnnxPath = "F:/人脸检测/未转换模型/人脸倾斜角度检测/6DRepNet/6drepnet.onnx";

HeadPoseConfig headPoseConfig = new HeadPoseConfig();
headPoseConfig.setModelEnum(HeadPoseModelEnum.SIX_D_REP_NET_MODEL);
headPoseConfig.setModelPath(sixDRepNetOnnxPath);
HeadPoseModel headPoseModel = HeadPoseModelFactory.getInstance().getModel(headPoseConfig);

// 从裁剪后的人脸图片检测姿态
HeadPose headPose = headPoseModel.predictCropedFace("src/main/resources/cropped_face.jpg");
log.info("SixDRepNet 裁剪人脸姿态检测结果:{}", JSONObject.toJSONString(headPose));
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 对比 SeetaFace6 和 SixDRepNet 两种模型的检测结果
*/
@Test
public void testCompareModels() {
try {
String seetaModelPath = "C:/Users/DengWenJie/Downloads/sf3.0_models/sf3.0_models";
String sixDRepNetOnnxPath = "F:/人脸检测/未转换模型/人脸倾斜角度检测/6DRepNet/6drepnet.onnx";

// 人脸检测
FaceDetConfig faceDetConfig = new FaceDetConfig();
faceDetConfig.setModelEnum(FaceDetModelEnum.SEETA_FACE6_MODEL);
faceDetConfig.setModelPath(seetaModelPath);
FaceDetModel faceDetModel = FaceDetModelFactory.getInstance().getModel(faceDetConfig);

Image image = SmartImageFactory.getInstance().fromFile("src/main/resources/iu_1.jpg");
R<DetectionResponse> detectionResult = faceDetModel.detect(image);
if (!detectionResult.isSuccess() || detectionResult.getData() == null) {
log.info("人脸检测失败");
return;
}

// SeetaFace6 姿态检测
HeadPoseConfig seetaConfig = new HeadPoseConfig();
seetaConfig.setModelEnum(HeadPoseModelEnum.SEETA_FACE6_MODEL);
seetaConfig.setModelPath(seetaModelPath);
HeadPoseModel seetaModel = HeadPoseModelFactory.getInstance().getModel(seetaConfig);

// SixDRepNet 姿态检测
HeadPoseConfig sixDConfig = new HeadPoseConfig();
sixDConfig.setModelEnum(HeadPoseModelEnum.SIX_DREP_NET_MODEL);
sixDConfig.setModelPath(sixDRepNetOnnxPath);
HeadPoseModel sixDModel = HeadPoseModelFactory.getInstance().getModel(sixDConfig);

DetectionResponse detectionResponse = detectionResult.getData();
if (detectionResponse.getDetectionInfoList() != null) {
for (DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()) {
HeadPose seetaPose = seetaModel.predict(image, detectionInfo.getDetectionRectangle());
HeadPose sixDPose = sixDModel.predict(image, detectionInfo.getDetectionRectangle());
log.info("===== 模型对比 =====");
log.info("SeetaFace6: pitch={}, yaw={}, roll={}", seetaPose.getPitch(), seetaPose.getYaw(), seetaPose.getRoll());
log.info("SixDRepNet: pitch={}, yaw={}, roll={}", sixDPose.getPitch(), sixDPose.getYaw(), sixDPose.getRoll());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

}
45 changes: 45 additions & 0 deletions face/src/main/java/cn/smartjavaai/face/config/HeadPoseConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cn.smartjavaai.face.config;

import cn.smartjavaai.common.config.ModelConfig;
import cn.smartjavaai.face.enums.HeadPoseModelEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
* 人脸姿态检测模型配置
* @author hyw
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class HeadPoseConfig extends ModelConfig {

/**
* 人脸姿态检测模型枚举
*/
private HeadPoseModelEnum modelEnum = HeadPoseModelEnum.SEETA_FACE6_MODEL;

/**
* 模型路径
* SeetaFace6: 模型目录路径(包含 pose_estimation.csta)
* SixDRepNet: ONNX 模型文件路径(如 6drepnet.onnx)
*/
private String modelPath;


public HeadPoseConfig() {
}

public HeadPoseConfig(HeadPoseModelEnum modelEnum) {
this.modelEnum = modelEnum;
}

public HeadPoseConfig(HeadPoseModelEnum modelEnum, String modelPath) {
this.modelEnum = modelEnum;
this.modelPath = modelPath;
}

public HeadPoseConfig(String modelPath) {
this.modelPath = modelPath;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cn.smartjavaai.face.enums;

/**
* 人脸姿态检测模型枚举
* @author hyw
* @date 2026/8/25
*/
public enum HeadPoseModelEnum {

SEETA_FACE6_MODEL("SeetaFace6Model"),

SIX_DREP_NET_MODEL("SixDRepNetModel");

private final String modelClassName;

HeadPoseModelEnum(String modelClassName) {
this.modelClassName = modelClassName;
}

public String getModelClassName() {
return modelClassName;
}

/**
* 根据名称获取枚举 (忽略大小写和下划线变体)
*/
public static HeadPoseModelEnum fromName(String name) {
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
for (HeadPoseModelEnum model : values()) {
if (model.name().replaceAll("_", "").equals(formatted)) {
return model;
}
}
throw new IllegalArgumentException("未知模型名称: " + name);
}

}
Loading