mirror of
https://gitee.com/open-visual/face-search.git
synced 2025-07-25 19:41:42 +08:00
commit
ba33c764eb
12
README.md
12
README.md
@ -32,9 +32,15 @@
|
||||
|
||||
    2、[PCN](https://github.com/Rock-100/FaceKit/tree/master/PCN)
|
||||
|
||||
### 版本1.1.0更新
|
||||
|
||||
* 1、修复已知BUG
|
||||
* 2、添加人脸比对1:1接口,详见文档:[05、人脸比对服务](https://gitee.com/open-visual/face-search/blob/dev-1.1.0/scripts/docs/doc-1.1.0.md#05%E4%BA%BA%E8%84%B8%E6%AF%94%E5%AF%B9%E6%9C%8D%E5%8A%A1)
|
||||
|
||||
|
||||
### 项目文档
|
||||
|
||||
* 在线文档:[文档-1.0.0](https://gitee.com/open-visual/face-search/blob/master/scripts/docs/doc-1.0.0.md)
|
||||
* 在线文档:[文档-1.1.0](https://gitee.com/open-visual/face-search/blob/dev-1.1.0/scripts/docs/doc-1.1.0.md)
|
||||
|
||||
* swagger文档:启动项目且开启swagger,访问:host:port/doc.html, 如 http://127.0.0.1:8080/doc.html
|
||||
|
||||
@ -45,12 +51,12 @@
|
||||
<dependency>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<artifactId>face-search-client</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
```
|
||||
* 其他语言依赖
|
||||
|
||||
   使用restful接口:[文档-1.0.0](https://gitee.com/open-visual/face-search/blob/master/scripts/docs/doc-1.0.0.md)
|
||||
   使用restful接口:[文档-1.1.0](https://gitee.com/open-visual/face-search/blob/dev-1.1.0/scripts/docs/doc-1.1.0.md)
|
||||
|
||||
|
||||
### 项目部署
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
<artifactId>face-search-client</artifactId>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>face-search</artifactId>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>face-search-core</artifactId>
|
||||
|
@ -9,7 +9,6 @@ import org.opencv.dnn.Dnn;
|
||||
import org.opencv.highgui.HighGui;
|
||||
import org.opencv.imgcodecs.Imgcodecs;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
import sun.misc.BASE64Decoder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
@ -20,6 +19,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* 图片加载工具
|
||||
@ -66,8 +66,11 @@ public class ImageMat implements Serializable {
|
||||
public static ImageMat fromBase64(String base64Str){
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
BASE64Decoder decoder = new BASE64Decoder();
|
||||
byte[] data = decoder.decodeBuffer(base64Str);
|
||||
// 新版本JDK被移除,替换为Base64.Decoder
|
||||
// BASE64Decoder decoder = new BASE64Decoder();
|
||||
// byte[] data = decoder.decodeBuffer(base64Str);
|
||||
Base64.Decoder decoder = Base64.getMimeDecoder();
|
||||
byte[] data = decoder.decode(base64Str);
|
||||
inputStream = new ByteArrayInputStream(data);
|
||||
return fromInputStream(inputStream);
|
||||
}catch (Exception e){
|
||||
|
@ -1,10 +1,10 @@
|
||||
package com.visual.face.search.core.utils;
|
||||
|
||||
import org.opencv.core.Mat;
|
||||
import sun.misc.BASE64Encoder;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MatUtil {
|
||||
@ -42,8 +42,11 @@ public class MatUtil {
|
||||
byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
ImageIO.write(matToBufferedImage(mat), "jpg", byteArrayOutputStream);
|
||||
byte[] bytes = byteArrayOutputStream.toByteArray();
|
||||
BASE64Encoder encoder = new BASE64Encoder();
|
||||
return encoder.encodeBuffer(Objects.requireNonNull(bytes));
|
||||
// 新版本JDK被移除,替换为Base64.Encoder
|
||||
// BASE64Encoder encoder = new BASE64Encoder();
|
||||
// return encoder.encodeBuffer(Objects.requireNonNull(bytes));
|
||||
Base64.Encoder encoder = Base64.getMimeEncoder();
|
||||
return encoder.encodeToString(Objects.requireNonNull(bytes));
|
||||
}catch (Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}finally {
|
||||
|
@ -29,6 +29,27 @@ public class Similarity {
|
||||
}
|
||||
return (float) cosineSimilarity;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 两个向量可以为任意维度,但必须保持维度相同,表示n维度中的两点
|
||||
* 欧式距离
|
||||
* @param vector1
|
||||
* @param vector2
|
||||
* @return 两点间距离
|
||||
*/
|
||||
public static float euclideanDistance(float[] vector1, float[] vector2) {
|
||||
double distance = 0;
|
||||
if (vector1.length == vector2.length) {
|
||||
for (int i = 0; i < vector1.length; i++) {
|
||||
double temp = Math.pow((vector1[i] - vector2[i]), 2);
|
||||
distance += temp;
|
||||
}
|
||||
distance = Math.sqrt(distance);
|
||||
}else {
|
||||
throw new RuntimeException("vector length not equal");
|
||||
}
|
||||
return (float) distance;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>face-search</artifactId>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>face-search-engine</artifactId>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>face-search</artifactId>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -38,7 +38,7 @@ public class SwaggerConfig implements WebMvcConfigurer {
|
||||
return new ApiInfoBuilder()
|
||||
.title("人脸搜索服务API")
|
||||
.description("人脸搜索服务API")
|
||||
.version("1.0.0")
|
||||
.version("1.1.0")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
@Api(tags="05、公共服务-健康检测")
|
||||
@Api(tags="06、公共服务-健康检测")
|
||||
@RestController("healthController")
|
||||
@RequestMapping("/common/health")
|
||||
public class HealthController {
|
||||
|
@ -0,0 +1,19 @@
|
||||
package com.visual.face.search.server.controller.server.api;
|
||||
|
||||
import com.visual.face.search.server.domain.common.ResponseInfo;
|
||||
import com.visual.face.search.server.domain.request.FaceCompareReqVo;
|
||||
import com.visual.face.search.server.domain.response.FaceCompareRepVo;
|
||||
|
||||
/**
|
||||
* 人脸1:1比对
|
||||
*/
|
||||
public interface FaceCompareControllerApi {
|
||||
|
||||
/**
|
||||
* 人脸比对1:1接口
|
||||
* @param compareReq
|
||||
* @return
|
||||
*/
|
||||
public ResponseInfo<FaceCompareRepVo> faceCompare(FaceCompareReqVo compareReq);
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.visual.face.search.server.controller.server.impl;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import com.visual.face.search.server.controller.base.BaseController;
|
||||
import com.visual.face.search.server.controller.server.api.FaceCompareControllerApi;
|
||||
import com.visual.face.search.server.domain.common.ResponseInfo;
|
||||
import com.visual.face.search.server.domain.request.FaceCompareReqVo;
|
||||
import com.visual.face.search.server.domain.response.FaceCompareRepVo;
|
||||
import com.visual.face.search.server.service.api.FaceCompareService;
|
||||
import com.visual.face.search.server.utils.ResponseBuilder;
|
||||
|
||||
|
||||
public class FaceCompareControllerImpl extends BaseController implements FaceCompareControllerApi {
|
||||
|
||||
@Resource
|
||||
private FaceCompareService faceCompareService;
|
||||
|
||||
@Override
|
||||
public ResponseInfo<FaceCompareRepVo> faceCompare(FaceCompareReqVo compareReq) {
|
||||
try {
|
||||
return ResponseBuilder.success(faceCompareService.faceCompare(compareReq));
|
||||
}catch (Exception e){
|
||||
logger.error("do faceCompare exception:", e);
|
||||
return ResponseBuilder.exception(e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package com.visual.face.search.server.controller.server.restful;
|
||||
|
||||
import com.visual.face.search.server.controller.server.impl.FaceCompareControllerImpl;
|
||||
import com.visual.face.search.server.domain.common.ResponseInfo;
|
||||
import com.visual.face.search.server.domain.request.FaceCompareReqVo;
|
||||
import com.visual.face.search.server.domain.response.FaceCompareRepVo;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Api(tags="05、人脸比对服务")
|
||||
@RestController("visualFaceCompareController")
|
||||
@RequestMapping("/visual/compare")
|
||||
public class FaceCompareController extends FaceCompareControllerImpl {
|
||||
|
||||
|
||||
@ApiOperation(value="1、人脸比对1:1", position = 1)
|
||||
@Override
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/do", method = RequestMethod.POST)
|
||||
public ResponseInfo<FaceCompareRepVo> faceCompare(FaceCompareReqVo compareReq) {
|
||||
return super.faceCompare(compareReq);
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
@RequestMapping("/visual/search")
|
||||
public class FaceSearchController extends FaceSearchControllerImpl {
|
||||
|
||||
@ApiOperation(value="1、人脸搜索1:N", position = 1)
|
||||
@ApiOperation(value="1、人脸搜索M:N", position = 1)
|
||||
@Override
|
||||
@ResponseBody
|
||||
@RequestMapping(value = "/do", method = RequestMethod.POST)
|
||||
|
@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/***
|
||||
@ -33,10 +34,10 @@ public class CollectVo<ExtendsVo extends CollectVo<ExtendsVo>> extends BaseVo {
|
||||
private Integer shardsNum;
|
||||
/**自定义的样本字段**/
|
||||
@ApiModelProperty(value="自定义的样本属性字段", position = 5,required = false)
|
||||
private List<FiledColumn> sampleColumns;
|
||||
private List<FiledColumn> sampleColumns = new ArrayList<>();
|
||||
/**自定义的人脸字段**/
|
||||
@ApiModelProperty(value="自定义的人脸属性字段", position = 6,required = false)
|
||||
private List<FiledColumn> faceColumns;
|
||||
private List<FiledColumn> faceColumns = new ArrayList<>();
|
||||
/**启用binlog同步**/
|
||||
@ApiModelProperty(value="启用binlog同步。扩展字段,暂不支持该功能。", position = 7,required = false)
|
||||
private Boolean syncBinLog;
|
||||
@ -101,7 +102,9 @@ public class CollectVo<ExtendsVo extends CollectVo<ExtendsVo>> extends BaseVo {
|
||||
}
|
||||
|
||||
public ExtendsVo setSampleColumns(List<FiledColumn> sampleColumns) {
|
||||
this.sampleColumns = sampleColumns;
|
||||
if(null != sampleColumns){
|
||||
this.sampleColumns = sampleColumns;
|
||||
}
|
||||
return (ExtendsVo) this;
|
||||
}
|
||||
|
||||
@ -110,7 +113,9 @@ public class CollectVo<ExtendsVo extends CollectVo<ExtendsVo>> extends BaseVo {
|
||||
}
|
||||
|
||||
public ExtendsVo setFaceColumns(List<FiledColumn> faceColumns) {
|
||||
this.faceColumns = faceColumns;
|
||||
if(null != faceColumns){
|
||||
this.faceColumns = faceColumns;
|
||||
}
|
||||
return (ExtendsVo) this;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,53 @@
|
||||
package com.visual.face.search.server.domain.extend;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
|
||||
public class CompareFace {
|
||||
|
||||
/**人脸质量分数**/
|
||||
@ApiModelProperty(value="A图片人脸分数:[0,100]", position = 1, required = true)
|
||||
private Float faceScoreA;
|
||||
/**人脸质量分数**/
|
||||
@ApiModelProperty(value="B图片人脸分数:[0,100]", position = 1, required = true)
|
||||
private Float faceScoreB;
|
||||
|
||||
/**人脸位置信息**/
|
||||
@ApiModelProperty(value="A图片人脸位置信息", position = 3, required = true)
|
||||
private FaceLocation locationA;
|
||||
/**人脸位置信息**/
|
||||
@ApiModelProperty(value="B图片人脸位置信息", position = 4, required = true)
|
||||
private FaceLocation locationB;
|
||||
|
||||
|
||||
public Float getFaceScoreA() {
|
||||
return faceScoreA;
|
||||
}
|
||||
|
||||
public void setFaceScoreA(Float faceScoreA) {
|
||||
this.faceScoreA = faceScoreA;
|
||||
}
|
||||
|
||||
public FaceLocation getLocationA() {
|
||||
return locationA;
|
||||
}
|
||||
|
||||
public void setLocationA(FaceLocation locationA) {
|
||||
this.locationA = locationA;
|
||||
}
|
||||
|
||||
public Float getFaceScoreB() {
|
||||
return faceScoreB;
|
||||
}
|
||||
|
||||
public void setFaceScoreB(Float faceScoreB) {
|
||||
this.faceScoreB = faceScoreB;
|
||||
}
|
||||
|
||||
public FaceLocation getLocationB() {
|
||||
return locationB;
|
||||
}
|
||||
|
||||
public void setLocationB(FaceLocation locationB) {
|
||||
this.locationB = locationB;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package com.visual.face.search.server.domain.request;
|
||||
|
||||
import com.visual.face.search.server.domain.base.BaseVo;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
public class FaceCompareReqVo extends BaseVo {
|
||||
|
||||
/**图像Base64编码值**/
|
||||
@NotNull(message = "imageBase64A cannot be empty")
|
||||
@ApiModelProperty(value="图像A的Base64编码值", position = 1,required = true)
|
||||
private String imageBase64A;
|
||||
|
||||
/**图像Base64编码值**/
|
||||
@NotNull(message = "imageBase64B cannot be empty")
|
||||
@ApiModelProperty(value="图像B的Base64编码值", position = 2,required = true)
|
||||
private String imageBase64B;
|
||||
|
||||
/**人脸质量分数阈值**/
|
||||
@Size(min = 0, max = 100, message = "faceScoreThreshold is not in the range")
|
||||
@ApiModelProperty(value="人脸质量分数阈值,范围:[0,100]:默认0。当设置为0时,会默认使用当前模型的默认值,该方法为推荐使用方式", position = 3,required = false)
|
||||
private Float faceScoreThreshold = 0f;
|
||||
|
||||
/**是否需要人脸信息**/
|
||||
@ApiModelProperty(value="是否需要人脸信息,默认为:true", position = 4,required = false)
|
||||
private Boolean needFaceInfo = true;
|
||||
|
||||
|
||||
public String getImageBase64A() {
|
||||
return imageBase64A;
|
||||
}
|
||||
|
||||
public void setImageBase64A(String imageBase64A) {
|
||||
this.imageBase64A = imageBase64A;
|
||||
}
|
||||
|
||||
public String getImageBase64B() {
|
||||
return imageBase64B;
|
||||
}
|
||||
|
||||
public void setImageBase64B(String imageBase64B) {
|
||||
this.imageBase64B = imageBase64B;
|
||||
}
|
||||
|
||||
public Float getFaceScoreThreshold() {
|
||||
return faceScoreThreshold;
|
||||
}
|
||||
|
||||
public void setFaceScoreThreshold(Float faceScoreThreshold) {
|
||||
if(null != faceScoreThreshold && faceScoreThreshold >= 0 && faceScoreThreshold <= 100){
|
||||
this.faceScoreThreshold = faceScoreThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
public Boolean getNeedFaceInfo() {
|
||||
return needFaceInfo;
|
||||
}
|
||||
|
||||
public void setNeedFaceInfo(Boolean needFaceInfo) {
|
||||
if(null != needFaceInfo){
|
||||
this.needFaceInfo = needFaceInfo;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.visual.face.search.server.domain.response;
|
||||
|
||||
import com.visual.face.search.server.domain.base.BaseVo;
|
||||
import com.visual.face.search.server.domain.extend.CompareFace;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
|
||||
public class FaceCompareRepVo extends BaseVo {
|
||||
/**向量的距离**/
|
||||
@ApiModelProperty(value="向量欧式距离:>=0", position = 1, required = true)
|
||||
private Float distance;
|
||||
/**转换后的置信度**/
|
||||
@ApiModelProperty(value="余弦距离转换后的置信度:[-100,100],值越大,相似度越高。", position = 2, required = true)
|
||||
private Float confidence;
|
||||
/**人脸信息**/
|
||||
@ApiModelProperty(value="人脸信息,参数needFaceInfo=false时,值为null", position = 3, required = false)
|
||||
private CompareFace faceInfo;
|
||||
|
||||
public Float getDistance() {
|
||||
return distance;
|
||||
}
|
||||
|
||||
public void setDistance(Float distance) {
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public Float getConfidence() {
|
||||
return confidence;
|
||||
}
|
||||
|
||||
public void setConfidence(Float confidence) {
|
||||
this.confidence = confidence;
|
||||
}
|
||||
|
||||
public CompareFace getFaceInfo() {
|
||||
return faceInfo;
|
||||
}
|
||||
|
||||
public void setFaceInfo(CompareFace faceInfo) {
|
||||
this.faceInfo = faceInfo;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.visual.face.search.server.service.api;
|
||||
|
||||
import com.visual.face.search.server.domain.request.FaceCompareReqVo;
|
||||
import com.visual.face.search.server.domain.response.FaceCompareRepVo;
|
||||
|
||||
public interface FaceCompareService {
|
||||
|
||||
public FaceCompareRepVo faceCompare(FaceCompareReqVo compareReq);
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package com.visual.face.search.server.service.impl;
|
||||
|
||||
import com.visual.face.search.core.domain.ExtParam;
|
||||
import com.visual.face.search.core.domain.FaceImage;
|
||||
import com.visual.face.search.core.domain.FaceInfo;
|
||||
import com.visual.face.search.core.domain.ImageMat;
|
||||
import com.visual.face.search.core.extract.FaceFeatureExtractor;
|
||||
import com.visual.face.search.core.utils.Similarity;
|
||||
import com.visual.face.search.server.domain.extend.CompareFace;
|
||||
import com.visual.face.search.server.domain.extend.FaceLocation;
|
||||
import com.visual.face.search.server.domain.request.FaceCompareReqVo;
|
||||
import com.visual.face.search.server.domain.response.FaceCompareRepVo;
|
||||
import com.visual.face.search.server.service.api.FaceCompareService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@Service("visualFaceCompareServiceImpl")
|
||||
public class FaceCompareServiceImpl implements FaceCompareService {
|
||||
|
||||
@Resource
|
||||
private FaceFeatureExtractor faceFeatureExtractor;
|
||||
|
||||
|
||||
@Override
|
||||
public FaceCompareRepVo faceCompare(FaceCompareReqVo compareReq) {
|
||||
FaceInfo faceInfoA = getFaceInfo(compareReq.getFaceScoreThreshold(), compareReq.getImageBase64A());
|
||||
if(null == faceInfoA){
|
||||
throw new RuntimeException("Image A is not face");
|
||||
}
|
||||
FaceInfo faceInfoB = getFaceInfo(compareReq.getFaceScoreThreshold(), compareReq.getImageBase64B());
|
||||
if(null == faceInfoB){
|
||||
throw new RuntimeException("Image B is not face");
|
||||
}
|
||||
//计算余弦相似度
|
||||
float simVal = Similarity.cosineSimilarity(faceInfoA.embedding.embeds, faceInfoB.embedding.embeds);
|
||||
float confidence = (float) Math.floor(simVal * 1000000)/10000;
|
||||
//欧式距离
|
||||
float euclideanDistance = Similarity.euclideanDistance(faceInfoA.embedding.embeds, faceInfoB.embedding.embeds);
|
||||
float distance = (float) Math.floor(euclideanDistance * 10000)/10000;
|
||||
//构建返回值
|
||||
FaceCompareRepVo faceCompareRep = new FaceCompareRepVo();
|
||||
faceCompareRep.setDistance(distance);
|
||||
faceCompareRep.setConfidence(confidence);
|
||||
if(compareReq.getNeedFaceInfo()){
|
||||
CompareFace compareFace = new CompareFace();
|
||||
compareFace.setFaceScoreA((float) Math.floor(faceInfoA.score * 1000000)/10000);
|
||||
compareFace.setFaceScoreB((float) Math.floor(faceInfoA.score * 1000000)/10000);
|
||||
FaceInfo.FaceBox boxA = faceInfoA.box;
|
||||
compareFace.setLocationA(FaceLocation.build(boxA.leftTop.x, boxA.leftTop.y, boxA.width(), boxA.height()));
|
||||
FaceInfo.FaceBox boxB = faceInfoB.box;
|
||||
compareFace.setLocationB(FaceLocation.build(boxB.leftTop.x, boxB.leftTop.y, boxB.width(), boxB.height()));
|
||||
faceCompareRep.setFaceInfo(compareFace);
|
||||
}
|
||||
//返回对象
|
||||
return faceCompareRep;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 图片检测并提取人脸特征
|
||||
* @param faceScoreThreshold
|
||||
* @param imageBase64
|
||||
* @return
|
||||
*/
|
||||
private FaceInfo getFaceInfo(float faceScoreThreshold, String imageBase64){
|
||||
faceScoreThreshold = faceScoreThreshold < 0 ? 0 : faceScoreThreshold;
|
||||
faceScoreThreshold = faceScoreThreshold > 100 ? 100 : faceScoreThreshold;
|
||||
faceScoreThreshold = faceScoreThreshold > 1 ? faceScoreThreshold / 100 : faceScoreThreshold;
|
||||
|
||||
ExtParam extParam = ExtParam.build().setMask(true).setScoreTh(faceScoreThreshold).setIouTh(0).setTopK(1);
|
||||
ImageMat imageMat = null;
|
||||
FaceImage faceImage = null;
|
||||
try {
|
||||
imageMat = ImageMat.fromBase64(imageBase64);
|
||||
faceImage = faceFeatureExtractor.extract(imageMat, extParam, new HashMap<>());
|
||||
}finally {
|
||||
if(null != imageMat){
|
||||
imageMat.release();
|
||||
}
|
||||
}
|
||||
if(null == faceImage){
|
||||
throw new RuntimeException("FeatureExtractor extract error");
|
||||
}
|
||||
|
||||
List<FaceInfo> faceInfos = faceImage.faceInfos();
|
||||
if(faceInfos.size() > 0){
|
||||
return faceInfos.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -123,7 +123,7 @@ public class FaceSearchServiceImpl extends BaseService implements FaceSearchServ
|
||||
FaceInfo.FaceBox box = faceInfos.get(i).box;
|
||||
FaceSearchRepVo vo = FaceSearchRepVo.build();
|
||||
vo.setLocation(FaceLocation.build(box.leftTop.x, box.leftTop.y, box.width(), box.height()));
|
||||
vo.setFaceScore((float)Math.floor(faceInfos.get(i).score * 10000)/100);
|
||||
vo.setFaceScore((float)Math.floor(faceInfos.get(i).score * 1000000)/10000);
|
||||
List<SampleFaceVo> match = new ArrayList<>();
|
||||
SearchResult searchResult = result.get(i);
|
||||
List<SearchDocument> documents = searchResult.getDocuments();
|
||||
@ -135,7 +135,7 @@ public class FaceSearchServiceImpl extends BaseService implements FaceSearchServ
|
||||
String faceVectorStr = MapUtils.getString(face, Constant.ColumnNameFaceVector);
|
||||
float[] faceVector = ValueUtil.convertVector(faceVectorStr);
|
||||
float simVal = Similarity.cosineSimilarity(faceInfos.get(i).embedding.embeds, faceVector);
|
||||
float confidence = (float) Math.floor(simVal * 10000)/100;
|
||||
float confidence = (float) Math.floor(simVal * 1000000)/10000;
|
||||
if(null != sampleId && sampleMapping.containsKey(sampleId) && confidence >= search.getConfidenceThreshold()){
|
||||
Map<String, Object> sample = sampleMapping.get(sampleId);
|
||||
SampleFaceVo faceVo = SampleFaceVo.build();
|
||||
@ -143,7 +143,7 @@ public class FaceSearchServiceImpl extends BaseService implements FaceSearchServ
|
||||
faceVo.setFaceId(document.getFaceId());
|
||||
faceVo.setFaceScore(faceScore);
|
||||
faceVo.setConfidence(confidence);
|
||||
faceVo.setDistance((float) Math.floor(document.getScore() * 100) / 100);
|
||||
faceVo.setDistance((float) Math.floor(document.getScore() * 10000) / 10000);
|
||||
faceVo.setFaceData(ValueUtil.getFieldKeyValues(face, ValueUtil.getFaceColumns(collection)));
|
||||
faceVo.setSampleData(ValueUtil.getFieldKeyValues(sample, ValueUtil.getSampleColumns(collection)));
|
||||
match.add(faceVo);
|
||||
|
@ -16,24 +16,27 @@ import java.util.Map;
|
||||
public class ValueUtil {
|
||||
|
||||
public static List<FiledColumn> getFaceColumns(Collection collection){
|
||||
if(null != collection.getSchemaInfo() && !collection.getSchemaInfo().isEmpty()){
|
||||
if(null != collection.getSchemaInfo() && !collection.getSchemaInfo().isEmpty()) {
|
||||
CollectRepVo collectVo = JsonUtil.toEntity(collection.getSchemaInfo(), CollectRepVo.class);
|
||||
return collectVo.getFaceColumns();
|
||||
}else{
|
||||
return new ArrayList<>();
|
||||
if (null != collectVo && null != collectVo.getFaceColumns()) {
|
||||
return collectVo.getFaceColumns();
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public static List<FiledColumn> getSampleColumns(Collection collection){
|
||||
if(null != collection.getSchemaInfo() && !collection.getSchemaInfo().isEmpty()){
|
||||
CollectRepVo collectVo = JsonUtil.toEntity(collection.getSchemaInfo(), CollectRepVo.class);
|
||||
return collectVo.getSampleColumns();
|
||||
}else{
|
||||
return new ArrayList<>();
|
||||
if (null != collectVo && null != collectVo.getSampleColumns()) {
|
||||
return collectVo.getSampleColumns();
|
||||
}
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
public static FieldKeyValues getFieldKeyValues(Map<String, Object> map , List<FiledColumn> columns){
|
||||
columns = null != columns ? columns : new ArrayList<>();
|
||||
Map<String, String> keyMap = new HashMap<>();
|
||||
for(FiledColumn column : columns){
|
||||
for(String dataKey : map.keySet()){
|
||||
|
@ -120,184 +120,3 @@ spring:
|
||||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 用户配置
|
||||
#user:
|
||||
# password:
|
||||
# # 密码错误{maxRetryCount}次锁定10分钟
|
||||
# maxRetryCount: 5
|
||||
#
|
||||
## Spring配置
|
||||
#spring:
|
||||
# # 模板引擎
|
||||
# thymeleaf:
|
||||
# mode: HTML
|
||||
# encoding: utf-8
|
||||
# # 禁用缓存
|
||||
# cache: false
|
||||
# # 资源信息
|
||||
# messages:
|
||||
# # 国际化资源文件路径
|
||||
# basename: static/i18n/messages
|
||||
# jackson:
|
||||
# time-zone: GMT+8
|
||||
# date-format: yyyy-MM-dd HH:mm:ss
|
||||
# # 文件上传
|
||||
# servlet:
|
||||
# multipart:
|
||||
# # 单个文件大小
|
||||
# max-file-size: 10MB
|
||||
# # 设置总上传的文件大小
|
||||
# max-request-size: 20MB
|
||||
# #数据源
|
||||
# datasource:
|
||||
# type: com.alibaba.druid.pool.DruidDataSource
|
||||
# driverClassName: com.mysql.cj.jdbc.Driver
|
||||
# druid:
|
||||
# # 主库数据源
|
||||
# master:
|
||||
# url: jdbc:mysql://127.0.0.1:3306/open_lab?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
# username: root
|
||||
# password: root
|
||||
# # 从库数据源
|
||||
# slave:
|
||||
# # 从数据源开关/默认关闭
|
||||
# enabled: false
|
||||
# url:
|
||||
# username:
|
||||
# password:
|
||||
# # 初始连接数
|
||||
# initialSize: 5
|
||||
# # 最小连接池数量
|
||||
# minIdle: 10
|
||||
# # 最大连接池数量
|
||||
# maxActive: 20
|
||||
# # 配置获取连接等待超时的时间
|
||||
# maxWait: 60000
|
||||
# # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
# timeBetweenEvictionRunsMillis: 60000
|
||||
# # 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
# minEvictableIdleTimeMillis: 300000
|
||||
# # 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||
# maxEvictableIdleTimeMillis: 900000
|
||||
# # 配置检测连接是否有效
|
||||
# validationQuery: SELECT 1 FROM DUAL
|
||||
# testWhileIdle: true
|
||||
# testOnBorrow: false
|
||||
# testOnReturn: false
|
||||
# webStatFilter:
|
||||
# enabled: true
|
||||
# statViewServlet:
|
||||
# enabled: true
|
||||
# # 设置白名单,不填则允许所有访问
|
||||
# allow:
|
||||
# url-pattern: /druid/*
|
||||
# # 控制台管理用户名和密码
|
||||
# login-username:
|
||||
# login-password:
|
||||
# filter:
|
||||
# stat:
|
||||
# enabled: true
|
||||
# # 慢SQL记录
|
||||
# log-slow-sql: true
|
||||
# slow-sql-millis: 1000
|
||||
# merge-sql: true
|
||||
# wall:
|
||||
# config:
|
||||
# multi-statement-allow: true
|
||||
# session:
|
||||
# store-type: redis
|
||||
# redis:
|
||||
# database: 2
|
||||
# host: 127.0.0.1
|
||||
# port: 6379
|
||||
# password: # 密码(默认为空)
|
||||
# timeout: 6000 # 连接超时时长(毫秒)
|
||||
# jedis:
|
||||
# pool:
|
||||
# max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
|
||||
# max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
# max-idle: 10 # 连接池中的最大空闲连接
|
||||
# min-idle: 5 # 连接池中的最小空闲连接
|
||||
#
|
||||
## MyBatis
|
||||
#mybatis:
|
||||
# # 搜索指定包别名
|
||||
# typeAliasesPackage: com.open.lab.manager.**.domain
|
||||
# # 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
# mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# # 加载全局的配置文件
|
||||
# configLocation: classpath:mybatis/mybatis-config.xml
|
||||
#
|
||||
## PageHelper分页插件
|
||||
#pagehelper:
|
||||
# helperDialect: mysql
|
||||
# reasonable: true
|
||||
# supportMethodsArguments: true
|
||||
# params: count=countSql
|
||||
#
|
||||
## Shiro
|
||||
#shiro:
|
||||
# cache:
|
||||
# storeEngine: redis
|
||||
# user:
|
||||
# # 登录地址
|
||||
# loginUrl: /login
|
||||
# # 权限认证失败地址
|
||||
# unauthorizedUrl: /unauth
|
||||
# # 首页地址
|
||||
# indexUrl: /index
|
||||
# # 验证码开关
|
||||
# captchaEnabled: true
|
||||
# # 验证码类型 math 数组计算 char 字符
|
||||
# captchaType: math
|
||||
# cookie:
|
||||
# # 设置Cookie的域名 默认空,即当前访问的域名
|
||||
# domain:
|
||||
# # 设置cookie的有效访问路径
|
||||
# path: /
|
||||
# # 设置HttpOnly属性
|
||||
# httpOnly: true
|
||||
# # 设置Cookie的过期时间,天为单位
|
||||
# maxAge: 30
|
||||
# # 设置密钥,务必保持唯一性(生成方式,直接拷贝到main运行即可)KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecretKey deskey = keygen.generateKey(); System.out.println(Base64.encodeToString(deskey.getEncoded()));
|
||||
# cipherKey: zSyK5Kp6PZAAjlT+eeNMlg==
|
||||
# session:
|
||||
# # Session超时时间,-1代表永不过期(默认30分钟)
|
||||
# expireTime: 30
|
||||
# # 同步session到数据库的周期(默认1分钟)
|
||||
# dbSyncPeriod: 1
|
||||
# # 相隔多久检查一次session的有效性,默认就是10分钟
|
||||
# validationInterval: 10
|
||||
# # 同一个用户最大会话数,比如2的意思是同一个账号允许最多同时两个人登录(默认-1不限制)
|
||||
# maxSession: -1
|
||||
# # 踢出之前登录的/之后登录的用户,默认踢出之前登录的用户
|
||||
# kickoutAfter: false
|
||||
#
|
||||
## 防止XSS攻击
|
||||
#xss:
|
||||
# # 过滤开关
|
||||
# enabled: true
|
||||
# # 排除链接(多个用逗号分隔)
|
||||
# excludes: /system/notice/*
|
||||
# # 匹配链接
|
||||
# urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
#
|
||||
## Swagger配置
|
||||
#swagger:
|
||||
# # 是否开启swagger
|
||||
# enabled: true
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<artifactId>face-search-test</artifactId>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<artifactId>face-search-client</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
2
pom.xml
2
pom.xml
@ -11,7 +11,7 @@
|
||||
</parent>
|
||||
<groupId>com.visual.face.search</groupId>
|
||||
<artifactId>face-search</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>1.1.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
|
@ -62,7 +62,7 @@ services:
|
||||
|
||||
visual-facesearch:
|
||||
container_name: face-search-server-standalone
|
||||
image: divenswu/face-search:1.0.0
|
||||
image: divenswu/face-search:1.1.0
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: 'jdbc:mysql://visual-mysql:3306/visual_face_search?useUnicode=true&characterEncoding=utf8'
|
||||
SPRING_DATASOURCE_USERNAME: root
|
||||
|
@ -30,7 +30,7 @@ services:
|
||||
|
||||
visual-facesearch:
|
||||
container_name: face-search-server-standalone
|
||||
image: divenswu/face-search:1.0.0
|
||||
image: divenswu/face-search:1.1.0
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: 'jdbc:mysql://visual-mysql:3306/visual_face_search?useUnicode=true&characterEncoding=utf8'
|
||||
SPRING_DATASOURCE_USERNAME: root
|
||||
|
@ -1,4 +1,4 @@
|
||||
version='1.0.0'
|
||||
version='1.1.0'
|
||||
SHELL_FOLDER=$(cd "$(dirname "$0")";pwd)
|
||||
cd ${SHELL_FOLDER}
|
||||
|
||||
|
1340
scripts/docs/doc-1.1.0.md
Normal file
1340
scripts/docs/doc-1.1.0.md
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user