添加人脸截取功能
This commit is contained in:
parent
4c9ddf7251
commit
801432202c
|
|
@ -155,6 +155,7 @@ public class Constants
|
|||
*/
|
||||
public static final String FILE_UPLOAD_WORKER = "pm_worker";
|
||||
|
||||
|
||||
/**
|
||||
* 人员入场表
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
package com.bonus.bmw.config;
|
||||
|
||||
import com.bonus.bmw.utils.FaceFeatureExtractorUtil;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class FaceServiceInitializer implements ApplicationRunner {
|
||||
|
||||
private final FaceServiceProperties properties;
|
||||
|
||||
public FaceServiceInitializer(FaceServiceProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 在 Spring 启动完成后,注入值到静态工具类
|
||||
if (properties.getBaseUrl() != null) {
|
||||
FaceFeatureExtractorUtil.setBaseUrl(properties.getBaseUrl());
|
||||
}
|
||||
if (properties.getFaceUrl() != null) {
|
||||
FaceFeatureExtractorUtil.setFaceUrl(properties.getFaceUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,6 @@ import org.springframework.context.annotation.Configuration;
|
|||
@ConfigurationProperties(prefix = "face.service")
|
||||
@Data
|
||||
public class FaceServiceProperties {
|
||||
private String baseUrl;
|
||||
|
||||
private String faceUrl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,9 @@ public class PmWorkerController extends BaseController {
|
|||
@Autowired
|
||||
private PmWorkerService service;
|
||||
|
||||
@Autowired
|
||||
FaceFeatureExtractorUtil faceFeatureExtractorUtil;
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
* @param o
|
||||
|
|
@ -193,8 +196,8 @@ public class PmWorkerController extends BaseController {
|
|||
}
|
||||
File fileData = null;
|
||||
try {
|
||||
fileData = FaceFeatureExtractorUtil.multipartToFile(file);
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.extractFeature(fileData);
|
||||
fileData = faceFeatureExtractorUtil.multipartToFile(file);
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.extractFeature(fileData);
|
||||
if (200 == faceResult.getCode()){
|
||||
return AjaxResult.success(faceResult.getMsg());
|
||||
}else{
|
||||
|
|
@ -224,8 +227,8 @@ public class PmWorkerController extends BaseController {
|
|||
}
|
||||
File fileData = null;
|
||||
try {
|
||||
fileData =FaceFeatureExtractorUtil.base64ToFile(base64file, "face_detection");;
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.extractFeature(fileData);
|
||||
fileData =faceFeatureExtractorUtil.base64ToFile(base64file, "face_detection");;
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.extractFeature(fileData);
|
||||
if (200 == faceResult.getCode()){
|
||||
return AjaxResult.success(faceResult.getMsg());
|
||||
}else{
|
||||
|
|
|
|||
|
|
@ -128,17 +128,26 @@ public class ZipDownloadController {
|
|||
*/
|
||||
@Scheduled(fixedRate = 3600000) // 每小时清理一次
|
||||
public void cleanOldZips() throws IOException {
|
||||
try (Stream<Path> files = Files.list(Paths.get("/tmp/downloads"))) {
|
||||
files.filter(p -> p.toString().endsWith(".zip"))
|
||||
.filter(p -> {
|
||||
try {
|
||||
return System.currentTimeMillis() - Files.getLastModifiedTime(p).toMillis() > 2 * 3600_000; // 2 小时过期
|
||||
} catch (IOException e) { return false; }
|
||||
})
|
||||
.forEach(p -> {
|
||||
try { Files.delete(p); } catch (IOException ignored) {}
|
||||
});
|
||||
String path="/tmp/downloads/";
|
||||
Path dirPath = Paths.get(path);
|
||||
// 2. 检查目录是否存在,不存在则创建(或直接跳过)
|
||||
if (!Files.exists(dirPath)) {
|
||||
log.warn("清理目录不存在:{},尝试创建目录", dirPath);
|
||||
}else{
|
||||
try (Stream<Path> files = Files.list(Paths.get(path))) {
|
||||
files.filter(p -> p.toString().endsWith(".zip"))
|
||||
.filter(p -> {
|
||||
try {
|
||||
return System.currentTimeMillis() - Files.getLastModifiedTime(p).toMillis() > 2 * 3600_000; // 2 小时过期
|
||||
} catch (IOException e) { return false; }
|
||||
})
|
||||
.forEach(p -> {
|
||||
try { Files.delete(p); } catch (IOException ignored) {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ public class AppRecognitionServiceImpl implements AppRecognitionService {
|
|||
@Autowired
|
||||
private FileUploadUtils fileUploadUtils;
|
||||
|
||||
@Autowired
|
||||
private FaceFeatureExtractorUtil faceFeatureExtractorUtil;
|
||||
|
||||
@Value("${face.groupId}")
|
||||
private Long groupId;
|
||||
|
||||
|
|
@ -59,7 +62,7 @@ public class AppRecognitionServiceImpl implements AppRecognitionService {
|
|||
@Override
|
||||
public String uploadFaceRecognition(MultipartFile facePhoto, FaceRecognitionBean bean) {
|
||||
try {
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(bean.getIdNumber(), groupId, FaceFeatureExtractorUtil.multipartToFile(facePhoto));
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumber(bean.getIdNumber(), groupId, faceFeatureExtractorUtil.multipartToFile(facePhoto));
|
||||
if(faceResult.getCode() == 200) {
|
||||
// 解析 data 数组
|
||||
return "人脸入库成功";
|
||||
|
|
@ -75,8 +78,8 @@ public class AppRecognitionServiceImpl implements AppRecognitionService {
|
|||
@Override
|
||||
public AjaxResult getFaceRecognition(MultipartFile facePhoto, String proId) {
|
||||
try {
|
||||
File file = FaceFeatureExtractorUtil.multipartToFile(facePhoto);
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.faceRecognition(groupId, file);
|
||||
File file = faceFeatureExtractorUtil.multipartToFile(facePhoto);
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.faceRecognition(groupId, file);
|
||||
BmWorkerEinUserVo sysFile = new BmWorkerEinUserVo();
|
||||
if(faceResult.getCode() == 200) {
|
||||
// 解析 data 数组
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ public class AppServiceImpl implements AppService {
|
|||
private BmWorkerWageCardMapper wageCardMapper;
|
||||
@Autowired
|
||||
private BmWorkerContractMapper contractMapper;
|
||||
@Autowired
|
||||
private FaceFeatureExtractorUtil faceFeatureExtractorUtil;
|
||||
|
||||
@Autowired
|
||||
private PmWorkerExitMapper pmWorkerExitMapper;
|
||||
|
|
@ -152,7 +154,7 @@ public class AppServiceImpl implements AppService {
|
|||
//下发人脸到人脸库
|
||||
UploadFileVo fileBast64 = fileUploadUtils.getFileBast64(record.getPhotoIds(), "", Constants.FILE_UPLOAD_WORKER, "");
|
||||
if (fileBast64 != null) {
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64());
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64());
|
||||
sb.append(faceResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
|
@ -231,7 +233,7 @@ public class AppServiceImpl implements AppService {
|
|||
FaceRecognitionBean faceRecognitionBean = new FaceRecognitionBean();
|
||||
UploadFileVo fileBast64 = fileUploadUtils.getFileBast64(record.getPhotoIds(), "", Constants.FILE_UPLOAD_WORKER, "");
|
||||
if (fileBast64 != null) {
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64());
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId,fileBast64.getBast64());
|
||||
sb.append(faceResult.getMsg());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.bonus.bmw.service.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import com.bonus.bmw.domain.dto.PmWorkerDto;
|
||||
import com.bonus.bmw.domain.dto.WebFileDto;
|
||||
import com.bonus.bmw.domain.face.FaceResult;
|
||||
|
|
@ -8,7 +9,9 @@ import com.bonus.bmw.domain.po.MapBeanPo;
|
|||
import com.bonus.bmw.domain.vo.*;
|
||||
import com.bonus.bmw.mapper.PmWorkerMapper;
|
||||
import com.bonus.bmw.service.*;
|
||||
import com.bonus.bmw.utils.CustomMultipartFile;
|
||||
import com.bonus.bmw.utils.FaceFeatureExtractorUtil;
|
||||
import com.bonus.bmw.utils.ImageCropper;
|
||||
import com.bonus.common.core.constant.Constants;
|
||||
import com.bonus.common.core.exception.ServiceException;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
|
|
@ -53,7 +56,7 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
private BmWorkerContractService contractService;
|
||||
|
||||
@Resource
|
||||
private AppRecognitionService appRecognitionService;
|
||||
private FaceFeatureExtractorUtil faceFeatureExtractorUtil;
|
||||
|
||||
@Autowired
|
||||
private TbProConfigService configService;
|
||||
|
|
@ -115,7 +118,9 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
}
|
||||
record.setCreateUser(SecurityUtils.getLoginUser().getSysUser().getUserName());
|
||||
int insert = mapper.insert(record);
|
||||
|
||||
if(insert > 0){
|
||||
File fileData=null;
|
||||
List<UploadFileVo> uploadFileVos = new ArrayList<>();
|
||||
boolean isBase64 = false;
|
||||
MultipartFile facePhoto = null;
|
||||
|
|
@ -127,16 +132,46 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
isBase64 = true;
|
||||
}else{
|
||||
List<WebFileDto> collect = fileMsg.stream().filter(data -> "faceImg".equals(data.getName())).collect(Collectors.toList());
|
||||
//组装数据
|
||||
MultipartFile[] workerFiles = new MultipartFile[collect.size()];
|
||||
String[] type = new String[collect.size()];
|
||||
for (int i = 0; i < collect.size(); i++) {
|
||||
workerFiles[i] = collect.get(i).getFile();
|
||||
type[i] = collect.get(i).getType();
|
||||
}
|
||||
facePhoto = collect.get(0).getFile();
|
||||
fileData = faceFeatureExtractorUtil.multipartToFile(facePhoto);
|
||||
Map<String,Double> faceResult = faceFeatureExtractorUtil.extractFaceImage(fileData);
|
||||
File file=null;
|
||||
if(faceResult!=null && !faceResult.isEmpty()){
|
||||
String filePath;
|
||||
String path=fileData.getPath();
|
||||
String newPath=null;
|
||||
if(StringUtils.isNotEmpty(path)){
|
||||
filePath=path;
|
||||
newPath=ImageCropper.addNewSuffixToFileName(path);
|
||||
}else{
|
||||
filePath=fileData.getAbsolutePath();
|
||||
newPath=ImageCropper.addNewSuffixToFileName(filePath);
|
||||
}
|
||||
if(newPath!=null){
|
||||
//临时文件
|
||||
ImageCropper.cropImage(filePath, newPath, faceResult.get("x1"), faceResult.get("y1"), faceResult.get("x2"), faceResult.get("y2"));
|
||||
file = new File(newPath);
|
||||
//转成file
|
||||
MultipartFile multipartFile = CustomMultipartFile.convert(file);
|
||||
if (file.exists()) {
|
||||
boolean delete = file.delete();
|
||||
System.out.println("删除临时文件--->"+delete);
|
||||
}
|
||||
workerFiles[0] = multipartFile;
|
||||
}
|
||||
}
|
||||
//人脸的数据添加
|
||||
uploadFileVos = fileUploadUtils.uploadFile(workerFiles, Constants.FILE_UPLOAD_WORKER, record.getId().toString(), type,"", "");
|
||||
facePhoto = collect.get(0).getFile();
|
||||
if (file!=null && file.exists()) {
|
||||
boolean delete = file.delete();
|
||||
System.out.println("删除临时文件--->"+delete);
|
||||
}
|
||||
}
|
||||
//入场相关数据添加
|
||||
addWorkerEinData(record,fileMsg);
|
||||
|
|
@ -150,17 +185,15 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
//下发人脸到人脸库
|
||||
if(!uploadFileVos.isEmpty()){
|
||||
if(isBase64){
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64());
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64());
|
||||
sb.append(faceResult.getMsg());
|
||||
}else{
|
||||
File file = null;
|
||||
try {
|
||||
file = FaceFeatureExtractorUtil.multipartToFile(facePhoto);
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, file);
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, fileData);
|
||||
sb.append(faceResult.getMsg());
|
||||
} finally {
|
||||
if (file != null && file.exists()) {
|
||||
boolean delete = file.delete();
|
||||
if (fileData != null && fileData.exists()) {
|
||||
boolean delete = fileData.delete();
|
||||
// 或使用 Hutool: FileUtil.del(file);
|
||||
}
|
||||
}
|
||||
|
|
@ -171,6 +204,8 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
return AjaxResult.success(sb.append("--基础数据添加成功").toString(),record.getId());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 基础数据添加完毕,在进行入场数据添加
|
||||
* @param record
|
||||
|
|
@ -433,6 +468,7 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
//人脸照片是否修改
|
||||
List<WebFileDto> collect = fileMsg.stream().filter(data -> "faceImg".equals(data.getName())).collect(Collectors.toList());
|
||||
if(!collect.isEmpty() || record.getFacePhotoBase64() != null){
|
||||
File fileData=null;
|
||||
String s = fileUploadUtils.delFileListById("", record.getId().toString(), Constants.FILE_UPLOAD_WORKER, "");
|
||||
List<UploadFileVo> uploadFileVos = new ArrayList<>();
|
||||
boolean isBase64 = false;
|
||||
|
|
@ -450,9 +486,39 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
workerFiles[i] = collect.get(i).getFile();
|
||||
type[i] = collect.get(i).getType();
|
||||
}
|
||||
|
||||
facePhoto = collect.get(0).getFile();
|
||||
fileData = faceFeatureExtractorUtil.multipartToFile(facePhoto);
|
||||
Map<String,Double> faceResult = faceFeatureExtractorUtil.extractFaceImage(fileData);
|
||||
File file=null;
|
||||
if(faceResult!=null && !faceResult.isEmpty()){
|
||||
String filePath;
|
||||
String path=fileData.getPath();
|
||||
String newPath=null;
|
||||
if(StringUtils.isNotEmpty(path)){
|
||||
filePath=path;
|
||||
newPath=ImageCropper.addNewSuffixToFileName(path);
|
||||
}else{
|
||||
filePath=fileData.getAbsolutePath();
|
||||
newPath=ImageCropper.addNewSuffixToFileName(filePath);
|
||||
}
|
||||
if(newPath!=null){
|
||||
//临时文件
|
||||
ImageCropper.cropImage(filePath, newPath, faceResult.get("x1"), faceResult.get("y1"), faceResult.get("x2"), faceResult.get("y2"));
|
||||
file = new File(newPath);
|
||||
//转成file
|
||||
MultipartFile multipartFile = CustomMultipartFile.convert(file);
|
||||
|
||||
workerFiles[0] = multipartFile;
|
||||
}
|
||||
}
|
||||
|
||||
//人脸的数据添加
|
||||
uploadFileVos = fileUploadUtils.uploadFile(workerFiles, Constants.FILE_UPLOAD_WORKER, record.getId().toString(), type,"", "");
|
||||
facePhoto = collect.get(0).getFile();
|
||||
if (file!=null &&file.exists()) {
|
||||
boolean delete = file.delete();
|
||||
System.out.println("删除临时文件--->"+delete);
|
||||
}
|
||||
}
|
||||
//下发人脸到考勤机
|
||||
try {
|
||||
|
|
@ -470,17 +536,15 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
//下发人脸到人脸库
|
||||
if(!uploadFileVos.isEmpty()){
|
||||
if(isBase64){
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64());
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumberBase64(record.getIdNumber(), groupId, record.getFacePhotoBase64());
|
||||
sb.append(faceResult.getMsg());
|
||||
}else{
|
||||
File file = null;
|
||||
try {
|
||||
file = FaceFeatureExtractorUtil.multipartToFile(facePhoto);
|
||||
FaceResult faceResult = FaceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, file);
|
||||
FaceResult faceResult = faceFeatureExtractorUtil.updateUserByIdNumber(record.getIdNumber(), groupId, fileData);
|
||||
sb.append(faceResult.getMsg());
|
||||
} finally {
|
||||
if (file != null && file.exists()) {
|
||||
boolean delete = file.delete();
|
||||
if (fileData != null && fileData.exists()) {
|
||||
boolean delete = fileData.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -508,6 +572,13 @@ public class PmWorkerServiceImpl implements PmWorkerService{
|
|||
return AjaxResult.success(sb.append("--基础数据更新成功").toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询人员列表
|
||||
* @param o
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package com.bonus.bmw.utils;
|
||||
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class CustomMultipartFile implements MultipartFile {
|
||||
|
||||
private final File file;
|
||||
private final String fileName;
|
||||
private final String contentType;
|
||||
|
||||
public CustomMultipartFile(File file) {
|
||||
this.file = file;
|
||||
this.fileName = file.getName();
|
||||
this.contentType = getContentTypeByFileName(this.fileName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "file"; // 自定义参数名
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOriginalFilename() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return file.length() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return file.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getBytes() throws IOException {
|
||||
return Files.readAllBytes(file.toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transferTo(File dest) throws IOException, IllegalStateException {
|
||||
Files.copy(file.toPath(), dest.toPath());
|
||||
}
|
||||
|
||||
// JDK 1.8兼容的Content-Type获取方法
|
||||
private static String getContentTypeByFileName(String fileName) {
|
||||
if (fileName == null) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
int lastDotIndex = fileName.lastIndexOf(".");
|
||||
String extension = lastDotIndex == -1 ? "" : fileName.substring(lastDotIndex + 1).toLowerCase();
|
||||
|
||||
if ("xlsx".equals(extension)) {
|
||||
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||
} else if ("xls".equals(extension)) {
|
||||
return "application/vnd.ms-excel";
|
||||
} else if ("jpg".equals(extension) || "jpeg".equals(extension)) {
|
||||
return "image/jpeg";
|
||||
} else if ("png".equals(extension)) {
|
||||
return "image/png";
|
||||
} else if ("pdf".equals(extension)) {
|
||||
return "application/pdf";
|
||||
} else {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
// 工具方法:创建CustomMultipartFile
|
||||
public static MultipartFile convert(File file) {
|
||||
if (file == null || !file.exists()) {
|
||||
throw new IllegalArgumentException("文件不存在");
|
||||
}
|
||||
return new CustomMultipartFile(file);
|
||||
}
|
||||
|
||||
// 测试
|
||||
public static void main(String[] args) {
|
||||
File file = new File("D:/test.png");
|
||||
MultipartFile multipartFile = CustomMultipartFile.convert(file);
|
||||
System.out.println("转换成功:" + multipartFile.getOriginalFilename());
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,18 @@ import cn.hutool.http.HttpResponse;
|
|||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.bonus.bmw.config.FaceServiceProperties;
|
||||
import com.bonus.bmw.domain.face.FaceResult;
|
||||
import com.bonus.bmw.domain.face.GroupInfo;
|
||||
import com.bonus.bmw.domain.face.UserInfo;
|
||||
import com.bonus.common.core.utils.StringUtils;
|
||||
import com.google.common.collect.Maps;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.checkerframework.checker.units.qual.C;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
|
|
@ -21,6 +28,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
|
@ -28,42 +36,37 @@ import java.util.regex.Pattern;
|
|||
* 人脸识别算法服务(Python 微服务)工具类
|
||||
* 统一管理 API 地址,使用 Hutool 封装 HTTP 调用
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class FaceFeatureExtractorUtil {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FaceFeatureExtractorUtil.class);
|
||||
// ========== 基础配置 ==========
|
||||
private static String BASE_URL = "http://192.168.0.37:18080/api";
|
||||
private static String FACE_URL = "http://192.168.0.37:18000";
|
||||
@Autowired
|
||||
public FaceServiceProperties properties;
|
||||
|
||||
|
||||
// ========== API 路径常量 ==========
|
||||
private static final String EXTRACT_FEATURE_PATH = "/api/extract_feature";
|
||||
|
||||
private static final String HEALTH_CHECK_PATH = "/health";
|
||||
|
||||
public static final String GROUP_ADD_PATH = "/groups/add";
|
||||
public static final String GROUP_LIST_PATH = "/groups/list";
|
||||
public static final String USER_ADD_PATH = "/users/add";
|
||||
public static final String USER_UPDATE_PATH = "/users/update";
|
||||
public static final String USER_SEARCH_PATH = "/users/search";
|
||||
public static final String USER_LIST_PATH = "/users/list";
|
||||
public static final String GROUP_ADD_PATH = "/api/groups/add";
|
||||
|
||||
public static final String GROUP_LIST_PATH = "/api/groups/list";
|
||||
|
||||
public static final String USER_ADD_PATH = "/api/users/add";
|
||||
/**
|
||||
* 更新人脸库
|
||||
*/
|
||||
public static final String USER_UPDATE_PATH = "/api/users/update";
|
||||
|
||||
public static final String USER_SEARCH_PATH = "/api/users/search";
|
||||
|
||||
public static final String USER_LIST_PATH = "/api/users/list";
|
||||
|
||||
/**
|
||||
* 设置算法服务的基础 URL
|
||||
* 获取图片人脸 -只需要人脸部分
|
||||
*/
|
||||
public static void setBaseUrl(String baseUrl) {
|
||||
if (baseUrl != null && !baseUrl.trim().isEmpty()) {
|
||||
BASE_URL = baseUrl.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置算法服务的人脸 URL
|
||||
*/
|
||||
public static void setFaceUrl(String faceUrl) {
|
||||
if (faceUrl != null && !faceUrl.trim().isEmpty()) {
|
||||
FACE_URL = faceUrl.trim();
|
||||
}
|
||||
}
|
||||
|
||||
public static final String FACE_IMAGE_SIZE = "/api/detect_face";
|
||||
|
||||
/**
|
||||
* 提取人脸特征向量
|
||||
|
|
@ -71,13 +74,13 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param imageFile 人脸图片文件(jpg/png)
|
||||
* @return ExtractResult 包含成功状态、特征向量、错误信息等
|
||||
*/
|
||||
public static FaceResult extractFeature(File imageFile) {
|
||||
public FaceResult extractFeature(File imageFile) {
|
||||
if (imageFile == null || !imageFile.exists()) {
|
||||
return new FaceResult(500, "Image file is null or not exists", null);
|
||||
}
|
||||
try {
|
||||
System.out.println("开始调用算法服务:"+FACE_URL + EXTRACT_FEATURE_PATH);
|
||||
HttpResponse response = HttpRequest.post(FACE_URL + EXTRACT_FEATURE_PATH).form("image", imageFile).timeout(10000) // 10秒超时
|
||||
System.out.println("开始调用算法服务:"+properties.getFaceUrl() + EXTRACT_FEATURE_PATH);
|
||||
HttpResponse response = HttpRequest.post(properties.getFaceUrl() + EXTRACT_FEATURE_PATH).form("image", imageFile).timeout(10000) // 10秒超时
|
||||
.execute();
|
||||
int status = response.getStatus();
|
||||
String body = response.body();
|
||||
|
|
@ -103,12 +106,61 @@ public class FaceFeatureExtractorUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public Map<String,Double> extractFaceImage(File imageFile) {
|
||||
Map<String,Double> maps= Maps.newHashMap();
|
||||
if (imageFile == null || !imageFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
System.out.println("开始调用算法服务:"+properties.getFaceUrl() + FACE_IMAGE_SIZE);
|
||||
HttpResponse response = HttpRequest.post(properties.getFaceUrl() + FACE_IMAGE_SIZE).form("image", imageFile).form("expand_scale", "0.3") .timeout(10000) // 10秒超时
|
||||
.execute();
|
||||
int status = response.getStatus();
|
||||
String body = response.body();
|
||||
if (status != 200) {
|
||||
return null;
|
||||
}
|
||||
JSONObject json = JSONUtil.parseObj(body);
|
||||
boolean success = json.getBool("success", false);
|
||||
String message = json.getStr("message", "Unknown error");
|
||||
if (!success) {
|
||||
return null;
|
||||
}
|
||||
// 解析 1024 维特征
|
||||
|
||||
JSONArray featureList = json.getJSONArray("faces");
|
||||
for (int i = 0; i < featureList.size(); i++) {
|
||||
JSONObject obj = featureList.getJSONObject(i);
|
||||
String x1 = obj.getStr("x1");
|
||||
String x2 = obj.getStr("x2");
|
||||
String y1 = obj.getStr("y1");
|
||||
String y2 = obj.getStr("y2");
|
||||
if (StringUtils.isNotEmpty(x1)) {
|
||||
maps.put("x1", obj.getDouble("x1"));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(x2)) {
|
||||
maps.put("x2", obj.getDouble("x2"));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(y1)) {
|
||||
maps.put("y1", obj.getDouble("y1"));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(y2)) {
|
||||
maps.put("y2", obj.getDouble("y2"));
|
||||
}
|
||||
}
|
||||
return maps;
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "Exception during feature extraction: " + ExceptionUtil.stacktraceToString(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
*/
|
||||
public static FaceResult healthCheck() {
|
||||
public FaceResult healthCheck() {
|
||||
try {
|
||||
HttpResponse response = HttpRequest.get(FACE_URL + HEALTH_CHECK_PATH).timeout(3000).execute();
|
||||
HttpResponse response = HttpRequest.get(properties.getFaceUrl() + HEALTH_CHECK_PATH).timeout(3000).execute();
|
||||
if (response.getStatus() == 200) {
|
||||
JSONObject json = JSONUtil.parseObj(response.body());
|
||||
return "healthy".equals(json.getStr("status")) ? new FaceResult(200, "服务正常", null): new FaceResult(500, "服务异常", null);
|
||||
|
|
@ -126,14 +178,14 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param description 分组描述(可选,可传 null)
|
||||
* @return 返回结果对象,包含 id、name、createTime 等
|
||||
*/
|
||||
public static FaceResult addGroup(String name, String description) {
|
||||
public FaceResult addGroup(String name, String description) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("分组名称不能为空");
|
||||
}
|
||||
// 构建请求体 JSON
|
||||
JSONObject requestBody = JSONUtil.createObj().set("name", name).set("description", description); // Hutool 会自动忽略 null 值(不会序列化)
|
||||
try {
|
||||
HttpResponse response = HttpRequest.post(BASE_URL + GROUP_ADD_PATH).body(requestBody.toString()) // 发送 JSON 字符串
|
||||
HttpResponse response = HttpRequest.post(properties.getFaceUrl() + GROUP_ADD_PATH).body(requestBody.toString()) // 发送 JSON 字符串
|
||||
.contentType("application/json") // 设置 Content-Type
|
||||
.timeout(5000) // 超时 5 秒
|
||||
.execute();
|
||||
|
|
@ -165,10 +217,10 @@ public class FaceFeatureExtractorUtil {
|
|||
*
|
||||
* @return 返回结果对象,包含 id、name、createTime 等
|
||||
*/
|
||||
public static FaceResult getGroupList() {
|
||||
public FaceResult getGroupList() {
|
||||
try {
|
||||
// 构建请求体 JSON
|
||||
HttpResponse response = HttpRequest.get(BASE_URL + GROUP_LIST_PATH).timeout(5000).execute();
|
||||
HttpResponse response = HttpRequest.get(properties.getFaceUrl() + GROUP_LIST_PATH).timeout(5000).execute();
|
||||
// 解析响应
|
||||
int status = response.getStatus();
|
||||
String body = response.body();
|
||||
|
|
@ -210,7 +262,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param photoFile 图片
|
||||
* @return 添加结果
|
||||
*/
|
||||
public static FaceResult addUser(String idNumber, Long groupId, File photoFile) {
|
||||
public FaceResult addUser(String idNumber, Long groupId, File photoFile) {
|
||||
if (idNumber == null || idNumber.trim().isEmpty()) {
|
||||
return new FaceResult(500, "人员主键不能为空", null);
|
||||
}
|
||||
|
|
@ -218,7 +270,7 @@ public class FaceFeatureExtractorUtil {
|
|||
return new FaceResult(500, "照片文件无效", null);
|
||||
}
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(BASE_URL + USER_ADD_PATH)
|
||||
HttpRequest request = HttpRequest.post(properties.getFaceUrl() + USER_ADD_PATH)
|
||||
.form("name", idNumber)
|
||||
// Hutool 自动处理 multipart
|
||||
.form("photo", photoFile);
|
||||
|
|
@ -260,7 +312,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param photoFile 新照片文件(可为 null,表示不修改)
|
||||
* @return 更新结果
|
||||
*/
|
||||
public static FaceResult updateUser(Long id, String idNumber, Long groupId, File photoFile) {
|
||||
public FaceResult updateUser(Long id, String idNumber, Long groupId, File photoFile) {
|
||||
if (id == null || id <= 0) {
|
||||
return new FaceResult(500, "人员ID无效", null);
|
||||
}
|
||||
|
|
@ -268,7 +320,7 @@ public class FaceFeatureExtractorUtil {
|
|||
return new FaceResult(500, "人员主键不能为空", null);
|
||||
}
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(BASE_URL + USER_UPDATE_PATH +"/"+ id)
|
||||
HttpRequest request = HttpRequest.post(properties.getFaceUrl() + USER_UPDATE_PATH +"/"+ id)
|
||||
.form("name", idNumber); // 必填
|
||||
if (groupId != null) {
|
||||
request.form("groupId", groupId.toString());
|
||||
|
|
@ -303,7 +355,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param groupId 分组ID(可为 null,表示不筛选)
|
||||
* @return 查询结果
|
||||
*/
|
||||
public static FaceResult faceRecognition(Long groupId, File photoFile) {
|
||||
public FaceResult faceRecognition(Long groupId, File photoFile) {
|
||||
if (photoFile == null || !photoFile.exists()) {
|
||||
return new FaceResult(500, "照片文件无效", null);
|
||||
}
|
||||
|
|
@ -311,7 +363,7 @@ public class FaceFeatureExtractorUtil {
|
|||
return new FaceResult(500, "分组Id不能为空", null);
|
||||
}
|
||||
try {
|
||||
HttpRequest request = HttpRequest.post(BASE_URL + USER_SEARCH_PATH)
|
||||
HttpRequest request = HttpRequest.post(properties.getFaceUrl() + USER_SEARCH_PATH)
|
||||
.form("photo", photoFile)
|
||||
.form("groupId", groupId);
|
||||
HttpResponse response = request.timeout(10000).execute();
|
||||
|
|
@ -349,7 +401,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param groupId 分组ID(可为 null,表示不筛选)
|
||||
* @return 查询结果
|
||||
*/
|
||||
public static FaceResult updateUserByIdNumber(String idNumber, Long groupId, File photoFile) {
|
||||
public FaceResult updateUserByIdNumber(String idNumber, Long groupId, File photoFile) {
|
||||
try {
|
||||
FaceResult faceResult = getFaceUserList(idNumber, groupId);
|
||||
if(faceResult.getCode() !=200){
|
||||
|
|
@ -376,9 +428,9 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param groupId 分组ID(可为 null,表示不筛选)
|
||||
* @return 查询结果
|
||||
*/
|
||||
public static FaceResult getFaceUserList(String idNumber, Long groupId) {
|
||||
public FaceResult getFaceUserList(String idNumber, Long groupId) {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.get(BASE_URL + USER_LIST_PATH);
|
||||
HttpRequest request = HttpRequest.get(properties.getFaceUrl() + USER_LIST_PATH);
|
||||
// 动态添加查询参数(仅当值有效时)
|
||||
if (idNumber != null && !idNumber.trim().isEmpty()) {
|
||||
request.form("name", idNumber.trim()); // Hutool GET 也支持 .form() 自动转 query
|
||||
|
|
@ -428,7 +480,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param base64Image Base64 编码的图片
|
||||
* @return 添加结果
|
||||
*/
|
||||
public static FaceResult addUserWithBase64(String idNumber, Long groupId, String base64Image) {
|
||||
public FaceResult addUserWithBase64(String idNumber, Long groupId, String base64Image) {
|
||||
if (base64Image == null || base64Image.trim().isEmpty()) {
|
||||
return new FaceResult(500, "Base64 图片为空", null);
|
||||
}
|
||||
|
|
@ -459,7 +511,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param base64Image Base64 编码的图片(可为 null)
|
||||
* @return 更新结果
|
||||
*/
|
||||
public static FaceResult updateUserWithBase64(Long id, String idNumber, Long groupId, String base64Image) {
|
||||
public FaceResult updateUserWithBase64(Long id, String idNumber, Long groupId, String base64Image) {
|
||||
File tempFile = null;
|
||||
try {
|
||||
if (base64Image != null && !base64Image.trim().isEmpty()) {
|
||||
|
|
@ -488,7 +540,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @param groupId 分组ID(可为 null,表示不筛选)
|
||||
* @return 查询结果
|
||||
*/
|
||||
public static FaceResult updateUserByIdNumberBase64(String idNumber, Long groupId, String photoFileBase64) {
|
||||
public FaceResult updateUserByIdNumberBase64(String idNumber, Long groupId, String photoFileBase64) {
|
||||
try {
|
||||
FaceResult faceResult = getFaceUserList(idNumber, groupId);
|
||||
if(faceResult.getCode() !=200){
|
||||
|
|
@ -516,7 +568,7 @@ public class FaceFeatureExtractorUtil {
|
|||
* @throws IllegalArgumentException Base64 格式非法
|
||||
* @throws IORuntimeException 写入临时文件失败
|
||||
*/
|
||||
public static File base64ToFile(String base64Image, String prefix) {
|
||||
public File base64ToFile(String base64Image, String prefix) {
|
||||
if (base64Image == null || base64Image.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -537,7 +589,7 @@ public class FaceFeatureExtractorUtil {
|
|||
return FileUtil.writeBytes(imageBytes, tempPath);
|
||||
}
|
||||
|
||||
public static File multipartToFile(MultipartFile multipartFile) throws IOException {
|
||||
public File multipartToFile(MultipartFile multipartFile) throws IOException {
|
||||
// 创建临时文件(自动在系统临时目录)
|
||||
File tempFile = File.createTempFile(
|
||||
"upload_",
|
||||
|
|
@ -548,7 +600,7 @@ public class FaceFeatureExtractorUtil {
|
|||
return tempFile;
|
||||
}
|
||||
// 辅助方法:获取文件扩展名
|
||||
private static String getExtension(String fileName) {
|
||||
private String getExtension(String fileName) {
|
||||
if (fileName == null || fileName.lastIndexOf(".") == -1) {
|
||||
return ""; // 无扩展名
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,61 @@ import java.awt.*;
|
|||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Java根据浮点型左上角/右下角坐标截图图片工具类
|
||||
*/
|
||||
public class ImageCropper {
|
||||
|
||||
|
||||
public static String addNewSuffixToFileName(String originalFilePath) {
|
||||
try{
|
||||
// 1. 非空校验
|
||||
if (originalFilePath == null || originalFilePath.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("文件路径不能为空");
|
||||
}
|
||||
|
||||
// 2. 解析文件路径为Path对象(跨平台兼容)
|
||||
Path originalPath = Paths.get(originalFilePath);
|
||||
// 获取文件名(如:upload_8693937242647132891.jpg)
|
||||
String originalFileName = originalPath.getFileName().toString();
|
||||
// 获取文件所在目录(如:C:\Users\86157\AppData\Local\Temp)
|
||||
String parentDir = originalPath.getParent() == null ? "" : originalPath.getParent().toString();
|
||||
|
||||
// 3. 拆分文件名和扩展名
|
||||
int lastDotIndex = originalFileName.lastIndexOf(".");
|
||||
String fileNameWithoutExt; // 无扩展名的文件名
|
||||
String extension; // 文件扩展名(含.)
|
||||
|
||||
if (lastDotIndex == -1) {
|
||||
// 无扩展名的情况(如:readme)
|
||||
fileNameWithoutExt = originalFileName;
|
||||
extension = "";
|
||||
} else {
|
||||
// 有扩展名的情况(如:upload_123.jpg 拆分为 upload_123 和 .jpg)
|
||||
fileNameWithoutExt = originalFileName.substring(0, lastDotIndex);
|
||||
extension = originalFileName.substring(lastDotIndex);
|
||||
}
|
||||
|
||||
// 4. 拼接新文件名(添加_new后缀)
|
||||
String newFileName = fileNameWithoutExt + "_new" + extension;
|
||||
|
||||
// 5. 拼接完整的新文件路径
|
||||
if (parentDir.isEmpty()) {
|
||||
return newFileName; // 无目录时直接返回新文件名
|
||||
} else {
|
||||
// 兼容Windows和Linux路径分隔符
|
||||
return Paths.get(parentDir, newFileName).toString();
|
||||
}
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 截取图片指定区域(适配浮点型坐标)
|
||||
* @param srcImagePath 源图片路径 (如: "D:/test.jpg")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
package com.bonus.urk.utils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Java根据浮点型左上角/右下角坐标截图图片工具类
|
||||
*/
|
||||
public class ImageCropper {
|
||||
|
||||
/**
|
||||
* 截取图片指定区域(适配浮点型坐标)
|
||||
* @param srcImagePath 源图片路径 (如: "D:/test.jpg")
|
||||
* @param destImagePath 截取后图片保存路径 (如: "D:/crop.jpg")
|
||||
* @param x1 截取区域左上角x坐标(浮点型)
|
||||
* @param y1 截取区域左上角y坐标(浮点型)
|
||||
* @param x2 截取区域右下角x坐标(浮点型)
|
||||
* @param y2 截取区域右下角y坐标(浮点型)
|
||||
* @throws IOException 图片读取/写入异常
|
||||
*/
|
||||
public static void cropImage(String srcImagePath, String destImagePath,
|
||||
double x1, double y1, double x2, double y2) throws IOException {
|
||||
// 1. 读取源图片
|
||||
File srcFile = new File(srcImagePath);
|
||||
if (!srcFile.exists()) {
|
||||
throw new IOException("源图片文件不存在: " + srcImagePath);
|
||||
}
|
||||
|
||||
BufferedImage srcImage = ImageIO.read(srcFile);
|
||||
|
||||
// 2. 获取图片原始尺寸
|
||||
int imageWidth = srcImage.getWidth();
|
||||
int imageHeight = srcImage.getHeight();
|
||||
|
||||
// 3. 浮点坐标转整数(四舍五入保留精度)
|
||||
int intX1 = (int) Math.round(x1);
|
||||
int intY1 = (int) Math.round(y1);
|
||||
int intX2 = (int) Math.round(x2);
|
||||
int intY2 = (int) Math.round(y2);
|
||||
|
||||
// 4. 校验坐标合法性
|
||||
// 检查左上角坐标
|
||||
if (intX1 < 0 || intY1 < 0 || intX1 >= imageWidth || intY1 >= imageHeight) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("左上角坐标越界!图片尺寸: %dx%d, 转换后坐标: (%d, %d) (原始: %.4f, %.4f)",
|
||||
imageWidth, imageHeight, intX1, intY1, x1, y1)
|
||||
);
|
||||
}
|
||||
// 检查右下角坐标
|
||||
if (intX2 < 0 || intY2 < 0 || intX2 >= imageWidth || intY2 >= imageHeight) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("右下角坐标越界!图片尺寸: %dx%d, 转换后坐标: (%d, %d) (原始: %.4f, %.4f)",
|
||||
imageWidth, imageHeight, intX2, intY2, x2, y2)
|
||||
);
|
||||
}
|
||||
// 检查坐标逻辑
|
||||
if (intX2 <= intX1) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("右下角x坐标必须大于左上角x坐标!转换后: x1=%d, x2=%d (原始: %.4f, %.4f)",
|
||||
intX1, intX2, x1, x2)
|
||||
);
|
||||
}
|
||||
if (intY2 <= intY1) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("右下角y坐标必须大于左上角y坐标!转换后: y1=%d, y2=%d (原始: %.4f, %.4f)",
|
||||
intY1, intY2, y1, y2)
|
||||
);
|
||||
}
|
||||
|
||||
// 5. 计算截取区域的宽高
|
||||
int cropWidth = intX2 - intX1;
|
||||
int cropHeight = intY2 - intY1;
|
||||
|
||||
// 6. 创建截取后的图片缓冲区
|
||||
BufferedImage destImage = new BufferedImage(cropWidth, cropHeight, srcImage.getType());
|
||||
|
||||
// 7. 绘制截取的区域
|
||||
Graphics2D g = destImage.createGraphics();
|
||||
// 增强截图清晰度(抗锯齿+插值)
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g.drawImage(srcImage, 0, 0, cropWidth, cropHeight, intX1, intY1, intX2, intY2, null);
|
||||
g.dispose(); // 释放资源
|
||||
|
||||
// 8. 获取目标文件格式
|
||||
String format = destImagePath.substring(destImagePath.lastIndexOf(".") + 1).toUpperCase();
|
||||
if (!format.matches("(JPG|JPEG|PNG|BMP)")) {
|
||||
format = "PNG";
|
||||
}
|
||||
|
||||
// 9. 保存截取后的图片
|
||||
File destFile = new File(destImagePath);
|
||||
File parentDir = destFile.getParentFile();
|
||||
if (parentDir != null && !parentDir.exists()) {
|
||||
parentDir.mkdirs();
|
||||
}
|
||||
|
||||
ImageIO.write(destImage, format, destFile);
|
||||
System.out.printf("截图成功!\n原始坐标: (%.4f,%.4f) -> (%.4f,%.4f)\n转换后坐标: (%d,%d) -> (%d,%d)\n保存路径: %s\n",
|
||||
x1, y1, x2, y2, intX1, intY1, intX2, intY2, destImagePath);
|
||||
}
|
||||
|
||||
// 测试方法(使用你提供的浮点坐标)
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 替换为你的实际图片路径
|
||||
String srcPath = "D:\\File\\dd.jpg";
|
||||
String destPath = "D:\\File\\dd3.jpg";
|
||||
|
||||
// 你提供的浮点坐标
|
||||
double x1 = 1300.4857177734375;
|
||||
double y1 = 722.174560546875;
|
||||
double x2 =1936.892578125;
|
||||
double y2 = 1726.121337890625;
|
||||
// 调用截图方法
|
||||
cropImage(srcPath, destPath, x1, y1, x2, y2);
|
||||
} catch (Exception e) {
|
||||
System.err.println("截图失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue