diff --git a/README.md b/README.md
index 9d91506..361ecea 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
## 人脸搜索M:N
-* 本项目是阿里云视觉智能开放平台的人脸1:N的开源替代,项目中使用的模型均为开源模型,项目支持milvus和proxima向量存储库,并具有较高的自定义能力。
+* 本项目是阿里云视觉智能开放平台的人脸1:N的开源替代,项目中使用的模型均为开源模型,项目支持opensearch(1.x版本支持milvus和proxima)向量存储库,并具有较高的自定义能力。
* 项目使用纯Java开发,免去使用Python带来的服务不稳定性。
@@ -22,9 +22,7 @@
2、[onnx](https://github.com/onnx/onnx)
- 3、[milvus](https://github.com/milvus-io/milvus/)
-
- 4、[proxima](https://github.com/alibaba/proximabilin)
+ 3、[opensearch](https://opensearch.org/)
* 深度学习模型
@@ -32,15 +30,14 @@
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/v1.2.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)
+### 版本2.2.0更新
+* 1、添加对opensearch的支持,删除对proxima与milvus向量引擎的支持
+* 2、更新:删除搜索结果中的距离指标,仅保留置信度指标(余弦相似度)
### 项目文档
-* 在线文档:[文档-1.2.0](https://gitee.com/open-visual/face-search/blob/v1.1.0/scripts/docs/doc-1.1.0.md)
+* 在线文档:[文档-2.0.0](scripts/docs/2.0.0.md
* swagger文档:启动项目且开启swagger,访问:host:port/doc.html, 如 http://127.0.0.1:8080/doc.html
@@ -51,23 +48,30 @@
com.visual.face.search
face-search-client
- 1.1.0
+ 2.0.0
```
* 其他语言依赖
- 使用restful接口:[文档-1.2.0](https://gitee.com/open-visual/face-search/blob/v1.2.0/scripts/docs/doc-1.1.0.md)
+ 使用restful接口:[文档-2.0.0](scripts/docs/2.0.0.md)
### 项目部署
* docker部署,脚本目录:face-search/scripts
```
-1、使用milvus作为向量搜索引擎
- docker-compose -f docker-compose-milvus.yml --compatibility up -d
+1、配置环境变量:FACESEARCH_VOLUME_DIRECTORY,指定当前的挂载根路径,默认为当前路径
-2、使用proxima作为向量搜索引擎
- docker-compose -f docker-compose-proxima.yml --compatibility up -d
+2、对opensearch的挂载目录进行赋权:
+ 新建目录:${FACESEARCH_VOLUME_DIRECTORY:-.}/volumes-face-search/opensearch/data
+ 目录赋权:chmod 777 ${FACESEARCH_VOLUME_DIRECTORY:-.}/volumes-face-search/opensearch/data
+
+3、使用opensearch作为向量搜索引擎
+ docker-compose -f docker-compose-opensearch.yml --compatibility up -d
+
+4、服务访问:
+ opensearch自带的可视化工具:http://127.0.0.1:5601
+ facesearch的swagger文档: http://127.0.0.1:56789/doc.html
```
* 项目编译
@@ -101,17 +105,11 @@
* 项目中为了提高人脸的检出率,使用了主要和次要的人脸检测模型,目前实现了两种人脸检测模型insightface和PCN,在docker的服务中,默认主服务为PCN,备用服务为insightface。insightface的效率高,但针对于旋转了大角度的人脸检出率不高,而pcn则可以识别大角度旋转的图片,但效率低一些。若图像均为正脸的图像,建议使用insightface为主模型,pcn为备用模型,如何切换,请查看部署参数。
-* 在测试过程中,针对milvus和proxima,发现proxima的速度比milvus稍快,但稳定性没有milvus好,线上服务使用时,还是建议使用milvus作为向量检索引擎。
-
### 项目演示
-* 1.1.0 测试用例:face-search-test[测试用例-FaceSearchExample](https://gitee.com/open-visual/face-search/blob/master/face-search-test/src/main/java/com/visual/face/search/valid/exps/FaceSearchExample.java)
+* 2.0.0 测试用例(做了优化,增强了搜索结果的区分度):face-search-test[测试用例-FaceSearchExample](https://gitee.com/open-visual/face-search/blob/master/face-search-test/src/main/java/com/visual/face/search/valid/exps/FaceSearchExample.java)
-* 
-
-* 1.2.0 测试用例(做了优化,增强了搜索结果的区分度):face-search-test[测试用例-FaceSearchExample](https://gitee.com/open-visual/face-search/blob/master/face-search-test/src/main/java/com/visual/face/search/valid/exps/FaceSearchExample.java)
-
-* 
+* 
### 交流群
diff --git a/face-search-client/pom.xml b/face-search-client/pom.xml
old mode 100755
new mode 100644
index ce948ee..b03096e
--- a/face-search-client/pom.xml
+++ b/face-search-client/pom.xml
@@ -2,12 +2,11 @@
-
4.0.0
- face-search-client
com.visual.face.search
- 1.1.0
+ face-search-client
+ 2.0.0
1.8
diff --git a/face-search-client/src/main/java/com/visual/face/search/handle/CollectHandler.java b/face-search-client/src/main/java/com/visual/face/search/handle/CollectHandler.java
index 4648707..5248f9f 100755
--- a/face-search-client/src/main/java/com/visual/face/search/handle/CollectHandler.java
+++ b/face-search-client/src/main/java/com/visual/face/search/handle/CollectHandler.java
@@ -46,7 +46,6 @@ public class CollectHandler extends BaseHandler{
.setMaxDocsPerSegment(collect.getMaxDocsPerSegment())
.setSampleColumns(collect.getSampleColumns())
.setFaceColumns(collect.getFaceColumns())
- .setSyncBinLog(collect.isSyncBinLog())
.setShardsNum(collect.getShardsNum())
.setStorageFaceInfo(collect.getStorageFaceInfo())
.setStorageEngine(collect.getStorageEngine());
diff --git a/face-search-client/src/main/java/com/visual/face/search/model/Collect.java b/face-search-client/src/main/java/com/visual/face/search/model/Collect.java
index d2e9f02..d08eb54 100755
--- a/face-search-client/src/main/java/com/visual/face/search/model/Collect.java
+++ b/face-search-client/src/main/java/com/visual/face/search/model/Collect.java
@@ -18,8 +18,6 @@ public class Collect> implements Serializab
private List sampleColumns = new ArrayList<>();
/**自定义的人脸字段**/
private List faceColumns = new ArrayList<>();
- /**启用binlog同步**/
- private Boolean syncBinLog = false;
/**是否保留图片及人脸信息**/
private Boolean storageFaceInfo = false;
/**保留图片及人脸信息的存储组件**/
@@ -86,17 +84,6 @@ public class Collect> implements Serializab
return (ExtendsVo) this;
}
- public boolean isSyncBinLog() {
- return null == syncBinLog ? false : syncBinLog;
- }
-
- public ExtendsVo setSyncBinLog(Boolean syncBinLog) {
- if(null != syncBinLog){
- this.syncBinLog = syncBinLog;
- }
- return (ExtendsVo) this;
- }
-
public boolean getStorageFaceInfo() {
return null == storageFaceInfo ? false : storageFaceInfo;
}
diff --git a/face-search-client/src/main/java/com/visual/face/search/model/SampleFace.java b/face-search-client/src/main/java/com/visual/face/search/model/SampleFace.java
index 37b4ffe..acf9881 100755
--- a/face-search-client/src/main/java/com/visual/face/search/model/SampleFace.java
+++ b/face-search-client/src/main/java/com/visual/face/search/model/SampleFace.java
@@ -11,8 +11,6 @@ public class SampleFace implements Comparable, Serializable {
/**人脸人数质量**/
private Float faceScore;
/**转换后的置信度**/
- private Float distance;
- /**转换后的置信度**/
private Float confidence;
/**样本扩展的额外数据**/
private KeyValues sampleData;
@@ -67,14 +65,6 @@ public class SampleFace implements Comparable, Serializable {
this.faceScore = faceScore;
}
- public Float getDistance() {
- return distance;
- }
-
- public void setDistance(Float distance) {
- this.distance = distance;
- }
-
public Float getConfidence() {
return confidence;
}
diff --git a/face-search-core/pom.xml b/face-search-core/pom.xml
old mode 100755
new mode 100644
index bfef8d5..69af6cb
--- a/face-search-core/pom.xml
+++ b/face-search-core/pom.xml
@@ -5,8 +5,9 @@
face-search
com.visual.face.search
- 1.2.0
+ 2.0.0
+
4.0.0
face-search-core
@@ -30,6 +31,6 @@
com.alibaba
fastjson
-
+
\ No newline at end of file
diff --git a/face-search-core/src/main/java/com/visual/face/search/core/domain/FaceInfo.java b/face-search-core/src/main/java/com/visual/face/search/core/domain/FaceInfo.java
index 742d285..58b9174 100755
--- a/face-search-core/src/main/java/com/visual/face/search/core/domain/FaceInfo.java
+++ b/face-search-core/src/main/java/com/visual/face/search/core/domain/FaceInfo.java
@@ -123,9 +123,9 @@ public class FaceInfo implements Comparable, Serializable {
* @return 旋转后的角
*/
public Point rotation(Point center, float angle){
- double k = new Float(Math.toRadians(angle));
- float nx1 = new Float((this.x-center.x)*Math.cos(k) +(this.y-center.y)*Math.sin(k)+center.x);
- float ny1 = new Float(-(this.x-center.x)*Math.sin(k) + (this.y-center.y)*Math.cos(k)+center.y);
+ double k = Math.toRadians(angle);
+ float nx1 = (float) ((this.x - center.x) * Math.cos(k) + (this.y - center.y) * Math.sin(k) + center.x);
+ float ny1 = (float) (-(this.x - center.x) * Math.sin(k) + (this.y - center.y) * Math.cos(k) + center.y);
return new Point(nx1, ny1);
}
diff --git a/face-search-core/src/main/java/com/visual/face/search/core/models/InsightArcFaceRecognition.java b/face-search-core/src/main/java/com/visual/face/search/core/models/InsightArcFaceRecognition.java
index 1e12caa..38fb9de 100755
--- a/face-search-core/src/main/java/com/visual/face/search/core/models/InsightArcFaceRecognition.java
+++ b/face-search-core/src/main/java/com/visual/face/search/core/models/InsightArcFaceRecognition.java
@@ -42,7 +42,7 @@ public class InsightArcFaceRecognition extends BaseOnnxInfer implements FaceRec
.to4dFloatOnnxTensorAndDoReleaseMat(true);
output = getSession().run(Collections.singletonMap(getInputName(), tensor));
float[][] embeds = (float[][]) output.get(0).getValue();
- return FaceInfo.Embedding.build(image.toBase64AndNoReleaseMat(), embeds[0]);
+ return Embedding.build(image.toBase64AndNoReleaseMat(), embeds[0]);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
diff --git a/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java b/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java
index 3af30f0..9b6c328 100755
--- a/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java
+++ b/face-search-core/src/main/java/com/visual/face/search/core/models/PcnNetworkFaceDetection.java
@@ -66,7 +66,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect
imgPad = pad_img_not_release_mat(mat);
float[] iouThs = iouTh <= 0 ? defIouThs : new float[]{iouTh, iouTh, 0.3f};
float[] scoreThs = scoreTh <= 0 ? defScoreThs : new float[]{0.375f * scoreTh, 0.5f * scoreTh, scoreTh};
- List willis = detect(this.getSessions(), mat, imgPad, scoreThs, iouThs);
+ List willis = detect(this.getSessions(), mat, imgPad, scoreThs, iouThs);
return trans_window(mat, imgPad, willis);
} catch (Exception e) {
throw new RuntimeException(e);
@@ -573,7 +573,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect
* @throws OrtException
* @throws IOException
*/
- private static List detect(OrtSession[] sessions, Mat img, Mat imgPad, float[] scoreThs, float iouThs[]) throws OrtException, IOException {
+ private static List detect(OrtSession[] sessions, Mat img, Mat imgPad, float[] scoreThs, float iouThs[]) throws OrtException, IOException {
Mat img180 = new Mat();
Core.flip(imgPad, img180, 0);
@@ -583,7 +583,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect
Mat imgNeg90 = new Mat();
Core.flip(img90, imgNeg90, 0);
- List winlist = stage1(img, imgPad, sessions[0], scoreThs[0]);
+ List winlist = stage1(img, imgPad, sessions[0], scoreThs[0]);
winlist = NMS(winlist, true, iouThs[0]);
winlist = stage2(imgPad, img180, sessions[1], scoreThs[1], 24, winlist);
@@ -604,7 +604,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect
/**
* 临时的人脸框
*/
- private static class Window2 implements Comparable{
+ private static class Window2 implements Comparable{
public int x;
public int y;
public int w;
@@ -624,7 +624,7 @@ public class PcnNetworkFaceDetection extends BaseOnnxInfer implements FaceDetect
}
@Override
- public int compareTo(PcnNetworkFaceDetection.Window2 o) {
+ public int compareTo(Window2 o) {
if(o.conf == this.conf){
return new Integer(this.y).compareTo(o.y);
}else{
diff --git a/face-search-core/src/main/java/com/visual/face/search/core/utils/Similarity.java b/face-search-core/src/main/java/com/visual/face/search/core/utils/Similarity.java
index 98dcbc7..0952be9 100644
--- a/face-search-core/src/main/java/com/visual/face/search/core/utils/Similarity.java
+++ b/face-search-core/src/main/java/com/visual/face/search/core/utils/Similarity.java
@@ -76,4 +76,20 @@ public class Similarity {
return Double.valueOf(sim).floatValue();
}
+ /**
+ * 对cos的原始值进行进行增强
+ * @param cos
+ * @return
+ */
+ public static float cosEnhance(float cos){
+ double sim = cos;
+ if(cos >= 0.5){
+ sim = cos + 2 * (cos - 0.5) * (1 - cos);
+ }else if(cos >= 0){
+ sim = cos - 2 * (cos - 0.5) * (0 - cos);
+ }
+ return Double.valueOf(sim).floatValue();
+ }
+
+
}
diff --git a/face-search-engine/libs/milvus-java-sdk-2.0.0.jar b/face-search-engine/libs/milvus-java-sdk-2.0.0.jar
deleted file mode 100755
index b763a76..0000000
Binary files a/face-search-engine/libs/milvus-java-sdk-2.0.0.jar and /dev/null differ
diff --git a/face-search-engine/libs/milvus-java-sdk-2.0.4.jar b/face-search-engine/libs/milvus-java-sdk-2.0.4.jar
deleted file mode 100755
index fbc2259..0000000
Binary files a/face-search-engine/libs/milvus-java-sdk-2.0.4.jar and /dev/null differ
diff --git a/face-search-engine/libs/proxima-be-java-sdk-0.2.0.jar b/face-search-engine/libs/proxima-be-java-sdk-0.2.0.jar
deleted file mode 100755
index 4c8f7fc..0000000
Binary files a/face-search-engine/libs/proxima-be-java-sdk-0.2.0.jar and /dev/null differ
diff --git a/face-search-engine/pom.xml b/face-search-engine/pom.xml
old mode 100755
new mode 100644
index d6cd44d..385c937
--- a/face-search-engine/pom.xml
+++ b/face-search-engine/pom.xml
@@ -5,118 +5,22 @@
face-search
com.visual.face.search
- 1.2.0
+ 2.0.0
4.0.0
+
face-search-engine
-
- UTF-8
- 1.36.0
- 3.12.0
- 3.12.0
- 4.3
- 1.8
- 1.8
-
-
-
-
-
- io.grpc
- grpc-bom
- ${grpc.version}
- pom
- import
-
-
-
-
-
- io.grpc
- grpc-netty-shaded
- 1.36.0
-
-
- io.grpc
- grpc-stub
- 1.36.0
-
-
- io.grpc
- grpc-protobuf
- 1.36.0
-
-
- com.google.guava
- guava
- 21.0
-
-
- org.projectlombok
- lombok
- 1.16.16
-
-
- com.google.protobuf
- protobuf-java
- 3.14.0
-
-
- org.slf4j
- slf4j-api
- 1.7.30
-
-
- org.apache.logging.log4j
- log4j-slf4j-impl
- 2.12.1
-
-
- io.milvus
- milvus-java-sdk
- 2.0.4
- system
- ${project.basedir}/libs/milvus-java-sdk-2.0.4.jar
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.6
-
-
- com.google.protobuf
- protobuf-java-util
- ${protobuf.version}
-
-
- org.apache.commons
- commons-text
- 1.6
-
org.apache.commons
commons-collections4
- ${commons-collections4.version}
-
-
- org.json
- json
- 20190722
- com.alibaba.proxima
- proxima-be-java-sdk
- 0.2.0
- system
- ${project.basedir}/libs/proxima-be-java-sdk-0.2.0.jar
+ org.opensearch.client
+ opensearch-rest-high-level-client
-
-
\ No newline at end of file
diff --git a/face-search-engine/src/main/java/com/visual/face/search/engine/api/SearchEngine.java b/face-search-engine/src/main/java/com/visual/face/search/engine/api/SearchEngine.java
new file mode 100755
index 0000000..c2f234d
--- /dev/null
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/api/SearchEngine.java
@@ -0,0 +1,30 @@
+package com.visual.face.search.engine.api;
+
+
+import com.visual.face.search.engine.model.MapParam;
+import com.visual.face.search.engine.model.SearchResponse;
+
+import java.util.List;
+
+public interface SearchEngine {
+
+ public Object getEngine();
+
+ public boolean exist(String collectionName);
+
+ public boolean dropCollection(String collectionName);
+
+ public boolean createCollection(String collectionName, MapParam param);
+
+ public boolean insertVector(String collectionName, String sampleId, String faceId, float[] vectors);
+
+ public boolean deleteVectorByKey(String collectionName, String faceId);
+
+ public boolean deleteVectorByKey(String collectionName, List faceIds);
+
+ public SearchResponse search(String collectionName, float[][] features, String algorithm, int topK);
+
+ public float searchMinScoreBySampleId(String collectionName, String sampleId,float[] feature, String algorithm);
+
+ public float searchMaxScoreBySampleId(String collectionName, String sampleId,float[] feature, String algorithm);
+}
diff --git a/face-search-engine/src/main/java/com/visual/face/search/engine/conf/Constant.java b/face-search-engine/src/main/java/com/visual/face/search/engine/conf/Constant.java
new file mode 100755
index 0000000..9144894
--- /dev/null
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/conf/Constant.java
@@ -0,0 +1,13 @@
+package com.visual.face.search.engine.conf;
+
+public class Constant {
+
+ public final static String IndexShardsNum = "shardsNum";
+ public final static String IndexReplicasNum = "replicasNum";
+
+ public final static String ColumnNameFaceId = "face_id";
+ public final static String ColumnNameSampleId = "sample_id";
+ public final static String ColumnNameFaceVector = "face_vector";
+ public final static String ColumnNameFaceScore = "face_score";
+
+}
diff --git a/face-search-engine/src/main/java/com/visual/face/search/engine/exps/SearchEngineException.java b/face-search-engine/src/main/java/com/visual/face/search/engine/exps/SearchEngineException.java
new file mode 100644
index 0000000..8d7b3a8
--- /dev/null
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/exps/SearchEngineException.java
@@ -0,0 +1,25 @@
+package com.visual.face.search.engine.exps;
+
+
+public class SearchEngineException extends RuntimeException{
+
+ public SearchEngineException() {
+ }
+
+ public SearchEngineException(String message) {
+ super(message);
+ }
+
+ public SearchEngineException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SearchEngineException(Throwable cause) {
+ super(cause);
+ }
+
+ public SearchEngineException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+}
diff --git a/face-search-engine/src/main/java/com/visual/face/search/engine/impl/OpenSearchEngine.java b/face-search-engine/src/main/java/com/visual/face/search/engine/impl/OpenSearchEngine.java
new file mode 100644
index 0000000..aefe96a
--- /dev/null
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/impl/OpenSearchEngine.java
@@ -0,0 +1,247 @@
+package com.visual.face.search.engine.impl;
+
+import com.visual.face.search.engine.api.SearchEngine;
+import com.visual.face.search.engine.conf.Constant;
+import com.visual.face.search.engine.exps.SearchEngineException;
+import com.visual.face.search.engine.model.*;
+import org.apache.commons.collections4.MapUtils;
+import org.opensearch.action.DocWriteResponse;
+import org.opensearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.opensearch.action.delete.DeleteRequest;
+import org.opensearch.action.delete.DeleteResponse;
+import org.opensearch.action.index.IndexRequest;
+import org.opensearch.action.index.IndexResponse;
+import org.opensearch.action.search.MultiSearchRequest;
+import org.opensearch.action.search.MultiSearchResponse;
+import org.opensearch.action.search.SearchRequest;
+import org.opensearch.client.RequestOptions;
+import org.opensearch.client.RestHighLevelClient;
+import org.opensearch.client.indices.CreateIndexRequest;
+import org.opensearch.client.indices.CreateIndexResponse;
+import org.opensearch.client.indices.GetIndexRequest;
+import org.opensearch.common.settings.Settings;
+import org.opensearch.index.query.*;
+import org.opensearch.index.query.functionscore.ScriptScoreQueryBuilder;
+import org.opensearch.index.reindex.BulkByScrollResponse;
+import org.opensearch.index.reindex.DeleteByQueryRequest;
+import org.opensearch.rest.RestStatus;
+import org.opensearch.script.Script;
+import org.opensearch.search.SearchHit;
+import org.opensearch.search.builder.SearchSourceBuilder;
+import java.io.IOException;
+import java.util.*;
+
+public class OpenSearchEngine implements SearchEngine {
+
+ private RestHighLevelClient client;
+ private MapParam params = new MapParam();
+
+ public OpenSearchEngine(RestHighLevelClient client){
+ this(client, null);
+ }
+
+ public OpenSearchEngine(RestHighLevelClient client, MapParam params){
+ this.client = client;
+ if(null != params) { this.params = params; }
+ }
+
+ @Override
+ public Object getEngine() {
+ return this.client;
+ }
+
+ @Override
+ public boolean exist(String collectionName) {
+ try {
+ GetIndexRequest request = new GetIndexRequest(collectionName);
+ return this.client.indices().exists(request, RequestOptions.DEFAULT);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean dropCollection(String collectionName) {
+ try {
+ DeleteIndexRequest request = new DeleteIndexRequest(collectionName);
+ return this.client.indices().delete(request, RequestOptions.DEFAULT).isAcknowledged();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean createCollection(String collectionName, MapParam param) {
+ try {
+ //构建请求
+ CreateIndexRequest createIndexRequest = new CreateIndexRequest(collectionName);
+ createIndexRequest.settings(Settings.builder()
+ .put("index.number_of_shards", param.getIndexShardsNum())
+ .put("index.number_of_replicas", param.getIndexReplicasNum())
+ );
+ HashMap properties = new HashMap<>();
+ properties.put(Constant.ColumnNameSampleId, Map.of("type", "keyword"));
+ properties.put(Constant.ColumnNameFaceVector, Map.of("type", "knn_vector", "dimension", "512"));
+ createIndexRequest.mapping(Map.of("properties", properties));
+ //创建集合
+ CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
+ return createIndexResponse.isAcknowledged();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean insertVector(String collectionName, String sampleId, String faceId, float[] vectors) {
+ try {
+ //构建请求
+ IndexRequest request = new IndexRequest(collectionName)
+ .id(faceId)
+ .source(Map.of(
+ Constant.ColumnNameSampleId, sampleId,
+ Constant.ColumnNameFaceVector, vectors
+ ));
+ //插入数据
+ IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
+ DocWriteResponse.Result result = indexResponse.getResult();
+ return DocWriteResponse.Result.CREATED == result || DocWriteResponse.Result.UPDATED == result;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean deleteVectorByKey(String collectionName, String faceId) {
+ try {
+ DeleteRequest deleteDocumentRequest = new DeleteRequest(collectionName, faceId);
+ DeleteResponse deleteResponse = client.delete(deleteDocumentRequest, RequestOptions.DEFAULT);
+ return DocWriteResponse.Result.DELETED == deleteResponse.getResult();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean deleteVectorByKey(String collectionName, List keyIds) {
+ try {
+ String[] idArray = new String[keyIds.size()]; idArray = keyIds.toArray(idArray);
+ QueryBuilder queryBuilder = new BoolQueryBuilder().must(QueryBuilders.idsQuery().addIds(idArray));
+ DeleteByQueryRequest request = new DeleteByQueryRequest(collectionName).setQuery(queryBuilder);
+ BulkByScrollResponse response = client.deleteByQuery(request, RequestOptions.DEFAULT);
+ return response.getBulkFailures() != null && response.getBulkFailures().size() == 0;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public SearchResponse search(String collectionName, float[][] features, String algorithm, int topK) {
+ try {
+ //构建搜索请求
+ MultiSearchRequest multiSearchRequest = new MultiSearchRequest();
+ for(float[] feature : features){
+ QueryBuilder queryBuilder = new MatchAllQueryBuilder();
+ Map params = new HashMap<>();
+ params.put("field", Constant.ColumnNameFaceVector);
+ params.put("space_type", algorithm);
+ params.put("query_value", feature);
+ Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, "knn", "knn_score", params);
+ ScriptScoreQueryBuilder scriptScoreQueryBuilder = new ScriptScoreQueryBuilder(queryBuilder, script);
+ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
+ .query(scriptScoreQueryBuilder).size(topK)
+ .fetchSource(null, Constant.ColumnNameFaceVector); //是否需要向量字段
+ SearchRequest searchRequest = new SearchRequest(collectionName).source(searchSourceBuilder);
+ multiSearchRequest.add(searchRequest);
+ }
+ //查询索引
+ MultiSearchResponse response = this.client.msearch(multiSearchRequest, RequestOptions.DEFAULT);
+ MultiSearchResponse.Item[] responses = response.getResponses();
+ if(features.length != responses.length){
+ throw new SearchEngineException("features.length != responses.length");
+ }
+ //解析数据
+ List result = new ArrayList<>();
+ for(MultiSearchResponse.Item item : response.getResponses()){
+ List documents = new ArrayList<>();
+ SearchHit[] searchHits = item.getResponse().getHits().getHits();
+ if(searchHits != null){
+ for(SearchHit searchHit : searchHits){
+ String faceId = searchHit.getId();
+ float score = searchHit.getScore()-1;
+ Map sourceMap = searchHit.getSourceAsMap();
+ String sampleId = MapUtils.getString(sourceMap, Constant.ColumnNameSampleId);
+ Object faceVector = MapUtils.getObject(sourceMap, Constant.ColumnNameFaceVector);
+ SearchDocument document = SearchDocument.build(sampleId, faceId, score).setVectors(faceVector);
+ documents.add(document);
+ }
+ }
+ result.add(SearchResult.build(documents));
+ }
+ //返回结果信息
+ return SearchResponse.build(SearchStatus.build(0, "success"), result);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public float searchMinScoreBySampleId(String collectionName, String sampleId,float[] feature, String algorithm) {
+ try {
+ //构建请求
+ QueryBuilder queryBuilder = new MatchQueryBuilder(Constant.ColumnNameSampleId, sampleId);
+ Map params = new HashMap<>();
+ params.put("field", Constant.ColumnNameFaceVector);
+ params.put("space_type", algorithm);
+ params.put("query_value", feature);
+ Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, "knn", "knn_score", params);
+ ScriptScoreQueryBuilder scriptScoreQueryBuilder = new ScriptScoreQueryBuilder(queryBuilder, script);
+ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
+ .query(scriptScoreQueryBuilder)
+ .fetchSource(false).size(10000); //是否需要索引字段
+ SearchRequest searchRequest = new SearchRequest(collectionName).source(searchSourceBuilder);
+ //搜索请求
+ org.opensearch.action.search.SearchResponse response = this.client.search(searchRequest, RequestOptions.DEFAULT);
+ if(RestStatus.OK == response.status()){
+ SearchHit[] searchHits = response.getHits().getHits();
+ Double minScore = Arrays.stream(searchHits).mapToDouble(SearchHit::getScore).min().orElse(2f);
+ return minScore.floatValue()-1;
+ }else{
+ throw new RuntimeException("get score error!");
+ }
+ } catch (Exception e) {
+ throw new SearchEngineException(e);
+ }
+ }
+
+ @Override
+ public float searchMaxScoreBySampleId(String collectionName, String sampleId,float[] feature, String algorithm) {
+ try {
+ //构建请求
+ QueryBuilder queryBuilder = new BoolQueryBuilder()
+ .mustNot(new MatchQueryBuilder(Constant.ColumnNameSampleId, sampleId));
+ Map params = new HashMap<>();
+ params.put("field", Constant.ColumnNameFaceVector);
+ params.put("space_type", algorithm);
+ params.put("query_value", feature);
+ Script script = new Script(Script.DEFAULT_SCRIPT_TYPE, "knn", "knn_score", params);
+ ScriptScoreQueryBuilder scriptScoreQueryBuilder = new ScriptScoreQueryBuilder(queryBuilder, script);
+ SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
+ .query(scriptScoreQueryBuilder)
+ .fetchSource(false).size(1); //是否需要索引字段
+ SearchRequest searchRequest = new SearchRequest(collectionName).source(searchSourceBuilder);
+ //搜索请求
+ org.opensearch.action.search.SearchResponse response = this.client.search(searchRequest, RequestOptions.DEFAULT);
+ if(RestStatus.OK == response.status()){
+ SearchHit[] searchHits = response.getHits().getHits();
+ Double maxScore = Arrays.stream(searchHits).mapToDouble(SearchHit::getScore).max().orElse(1f);
+ return maxScore.floatValue()-1;
+ }else{
+ throw new RuntimeException("get score error!");
+ }
+ } catch (Exception e) {
+ throw new SearchEngineException(e);
+ }
+ }
+
+
+}
diff --git a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/MapParam.java b/face-search-engine/src/main/java/com/visual/face/search/engine/model/MapParam.java
similarity index 80%
rename from face-search-server/src/main/java/com/visual/face/search/server/engine/model/MapParam.java
rename to face-search-engine/src/main/java/com/visual/face/search/engine/model/MapParam.java
index 2eead7e..595adfa 100755
--- a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/MapParam.java
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/model/MapParam.java
@@ -1,6 +1,6 @@
-package com.visual.face.search.server.engine.model;
+package com.visual.face.search.engine.model;
-import com.visual.face.search.server.engine.conf.Constant;
+import com.visual.face.search.engine.conf.Constant;
import org.apache.commons.collections4.MapUtils;
import java.util.concurrent.ConcurrentHashMap;
@@ -11,7 +11,6 @@ public class MapParam extends ConcurrentHashMap {
return new MapParam();
}
-
public MapParam put(String key, Object value){
if(null != key && null != value){
super.put(key, value);
@@ -60,15 +59,15 @@ public class MapParam extends ConcurrentHashMap {
}
/******************************************************************************************************************/
- public Long getMaxDocsPerSegment(){
- Long maxDocsPerSegment = this.getLong(Constant.ParamKeyMaxDocsPerSegment, 0L);
+ public Long getIndexReplicasNum(){
+ Long maxDocsPerSegment = this.getLong(Constant.IndexReplicasNum, 1L);
maxDocsPerSegment = (null == maxDocsPerSegment || maxDocsPerSegment < 0) ? 0 : maxDocsPerSegment;
return maxDocsPerSegment;
}
- public Integer getShardsNum(){
- Integer shardsNum = this.getInteger(Constant.ParamKeyShardsNum, 0);
- shardsNum = (null == shardsNum || shardsNum <= 0) ? 2 : shardsNum;
+ public Integer getIndexShardsNum(){
+ Integer shardsNum = this.getInteger(Constant.IndexShardsNum, 4);
+ shardsNum = (null == shardsNum || shardsNum <= 0) ? 4 : shardsNum;
return shardsNum;
}
diff --git a/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchDocument.java b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchDocument.java
new file mode 100755
index 0000000..601b3b4
--- /dev/null
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchDocument.java
@@ -0,0 +1,72 @@
+package com.visual.face.search.engine.model;
+
+import java.io.Serializable;
+import com.visual.face.search.engine.utils.NumberUtils;
+
+public class SearchDocument implements Serializable {
+ private float score;
+ private String faceId;
+ private String sampleId;
+ private Float[] vectors;
+
+ public SearchDocument(){}
+
+ public SearchDocument(String sampleId, String faceId, float score) {
+ this(sampleId, faceId, score, null);
+ }
+
+ public SearchDocument(String sampleId, String faceId, float score, Float[] vectors) {
+ this.score = score;
+ this.faceId = faceId;
+ this.sampleId = sampleId;
+ this.vectors = vectors;
+ }
+
+ public static SearchDocument build(String sampleId, String faceId, float score){
+ return build(sampleId, faceId, score, null);
+ }
+
+ public static SearchDocument build(String sampleId, String faceId, float score, Float[] vectors){
+ return new SearchDocument(sampleId, faceId, score, vectors);
+ }
+
+ public float getScore() {
+ return score;
+ }
+
+ public SearchDocument setScore(float score) {
+ this.score = score;
+ return this;
+ }
+
+ public String getFaceId() {
+ return faceId;
+ }
+
+ public SearchDocument setFaceId(String faceId) {
+ this.faceId = faceId;
+ return this;
+ }
+
+ public String getSampleId() {
+ return sampleId;
+ }
+
+ public SearchDocument setSampleId(String sampleId) {
+ this.sampleId = sampleId;
+ return this;
+ }
+
+ public Float[] getVectors() {
+ return vectors;
+ }
+
+ public SearchDocument setVectors(Object vectors) {
+ if(vectors == null){
+ return this;
+ }else{
+ this.vectors = NumberUtils.getFloatArray(vectors);
+ }
+ return this;
+ }
+}
diff --git a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchResponse.java b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchResponse.java
similarity index 94%
rename from face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchResponse.java
rename to face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchResponse.java
index b0fd1fd..1fc99eb 100755
--- a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchResponse.java
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchResponse.java
@@ -1,4 +1,4 @@
-package com.visual.face.search.server.engine.model;
+package com.visual.face.search.engine.model;
import java.util.ArrayList;
import java.util.List;
diff --git a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchResult.java b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchResult.java
similarity index 92%
rename from face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchResult.java
rename to face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchResult.java
index ee9265b..25b18d8 100755
--- a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchResult.java
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchResult.java
@@ -1,4 +1,4 @@
-package com.visual.face.search.server.engine.model;
+package com.visual.face.search.engine.model;
import java.util.ArrayList;
diff --git a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchStatus.java b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchStatus.java
similarity index 88%
rename from face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchStatus.java
rename to face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchStatus.java
index 9d29b67..d8aa1a0 100755
--- a/face-search-server/src/main/java/com/visual/face/search/server/engine/model/SearchStatus.java
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/model/SearchStatus.java
@@ -1,8 +1,8 @@
-package com.visual.face.search.server.engine.model;
+package com.visual.face.search.engine.model;
public class SearchStatus {
private int code;
- private String reason;
+ private String reason;
public SearchStatus(){}
diff --git a/face-search-engine/src/main/java/com/visual/face/search/engine/utils/NumberUtils.java b/face-search-engine/src/main/java/com/visual/face/search/engine/utils/NumberUtils.java
new file mode 100644
index 0000000..a77ea65
--- /dev/null
+++ b/face-search-engine/src/main/java/com/visual/face/search/engine/utils/NumberUtils.java
@@ -0,0 +1,74 @@
+package com.visual.face.search.engine.utils;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.List;
+
+public class NumberUtils {
+
+ public static Number getNumber(Object value) {
+ if (value != null) {
+ if (value instanceof Number) {
+ return (Number)value;
+ }
+ if (value instanceof String) {
+ try {
+ String text = (String)value;
+ return NumberFormat.getInstance().parse(text);
+ } catch (ParseException var4) {
+
+ }
+ }
+ }
+ return null;
+ }
+
+ public static Float getFloat(Object value) {
+ Number answer = getNumber(value);
+ if (answer == null) {
+ return null;
+ } else {
+ return answer instanceof Float ? (Float)answer : answer.floatValue();
+ }
+ }
+
+
+ public static Float[] getFloatArray(Object values) {
+ if(null != values){
+ if(values.getClass().isArray()){
+ return getFloatArray((Object[]) values);
+ }else if(values instanceof List){
+ return getFloatArray((List)values);
+ }else{
+ throw new RuntimeException("type error for:"+values.getClass());
+ }
+ }else{
+ return null;
+ }
+ }
+
+ public static Float[] getFloatArray(List