diff --git a/pom.xml b/pom.xml index 0e9142ba08ee9cd29daf6b09d5be7b00604092da..89325f88123371d575b1a16d76c0bd7cab3a01ab 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ 0.27.0 + org.springframework.boot spring-boot-starter @@ -222,6 +223,12 @@ 5.2.0-beta5 + + io.github.javpower + vectorex-starter + 1.5.2 + + diff --git a/src/main/java/com/github/javpower/javavision/controller/ImageController.java b/src/main/java/com/github/javpower/javavision/controller/ImageController.java index 13f976a2b226b9b3ec1d9f5b62d2c296e03c7e86..5d02a76cda94df71d16e2574a387323c807e4800 100644 --- a/src/main/java/com/github/javpower/javavision/controller/ImageController.java +++ b/src/main/java/com/github/javpower/javavision/controller/ImageController.java @@ -5,6 +5,7 @@ import com.github.javpower.javavision.es.response.SearchResult; import com.github.javpower.javavision.es.service.ImageSearchService; import com.github.javpower.javavision.milvus.service.ImageMilvusService; import com.github.javpower.javavision.service.IImageService; +import com.github.javpower.javavision.vectorex.service.ImageVectoRexService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; @@ -31,10 +32,10 @@ public class ImageController implements EnvironmentAware { private IImageService imageService; private String impl; - public ImageController(Environment environment,ImageSearchService imageSearchService,ImageMilvusService imageMilvusService) { + public ImageController(Environment environment,ImageSearchService imageSearchService,ImageMilvusService imageMilvusService,ImageVectoRexService imageVectoRexService) { // 从环境对象中获取配置项的值 this.impl = environment.getProperty("image.service"); - initializeService(imageSearchService, imageMilvusService); + initializeService(imageSearchService, imageMilvusService,imageVectoRexService); } @PostMapping("/add") @Operation(summary = "添加") @@ -46,15 +47,22 @@ public class ImageController implements EnvironmentAware { public List search(Integer k, MultipartFile file, HttpServletRequest request) throws Exception { return imageService.search(file.getInputStream(),k); } + @PostMapping("/del") + @Operation(summary = "删除") + public void del(String imageId, HttpServletRequest request) throws Exception { + imageService.del(imageId); + } @Override public void setEnvironment(Environment environment) { } // 根据配置项初始化服务 - private void initializeService(ImageSearchService imageSearchService, ImageMilvusService imageMilvusService) { + private void initializeService(ImageSearchService imageSearchService, ImageMilvusService imageMilvusService, ImageVectoRexService imageVectoRexService) { if ("milvus".equalsIgnoreCase(impl)) { this.imageService = imageMilvusService; + } if ("vectorex".equalsIgnoreCase(impl)) { + this.imageService = imageVectoRexService; } else { this.imageService = imageSearchService; } diff --git a/src/main/java/com/github/javpower/javavision/es/service/ImageSearchService.java b/src/main/java/com/github/javpower/javavision/es/service/ImageSearchService.java index 7962028624d3d6c763c0ae650d7066714caec093..07c67cd9a5916ecaba0b71bde9224be80ee225ef 100644 --- a/src/main/java/com/github/javpower/javavision/es/service/ImageSearchService.java +++ b/src/main/java/com/github/javpower/javavision/es/service/ImageSearchService.java @@ -86,6 +86,12 @@ public class ImageSearchService implements IImageService { } return list; } + + @Override + public void del(String imageId) { + + } + private void batchAdd(List imageSearchList) throws IOException, ModelException, TranslateException, OrtException { //批量上传请求 BulkRequest bulkRequest = new BulkRequest(EsConfig.IMAGE_SEARCH_INDEX); diff --git a/src/main/java/com/github/javpower/javavision/milvus/service/ImageMilvusService.java b/src/main/java/com/github/javpower/javavision/milvus/service/ImageMilvusService.java index cad8b63c5fc3b6d2071569828ab6773e33f7f1e9..612fa0a9d0300a898866171859e3332827eaf816 100644 --- a/src/main/java/com/github/javpower/javavision/milvus/service/ImageMilvusService.java +++ b/src/main/java/com/github/javpower/javavision/milvus/service/ImageMilvusService.java @@ -67,6 +67,12 @@ public class ImageMilvusService implements IImageService { } return res; } + + @Override + public void del(String imageId) { + imageMilvusMapper.deleteWrapper().eq(Image::getImageId,imageId).remove(); + } + private void batchAdd(List imageSearchList) throws IOException, ModelException, TranslateException, OrtException { //批量上传请求 for (Image imageSearch : imageSearchList) { diff --git a/src/main/java/com/github/javpower/javavision/service/IImageService.java b/src/main/java/com/github/javpower/javavision/service/IImageService.java index c74729901f6a01e9ae3760fee5d1013ba0a8bdc9..0ccb72c9f27cd4a728ab21f96a91bf9d2f3e6328 100644 --- a/src/main/java/com/github/javpower/javavision/service/IImageService.java +++ b/src/main/java/com/github/javpower/javavision/service/IImageService.java @@ -15,4 +15,5 @@ public interface IImageService { List search(InputStream input, int k) throws IOException, ModelException, TranslateException, OrtException; + void del(String imageId); } diff --git a/src/main/java/com/github/javpower/javavision/util/ImageFeatureUtil.java b/src/main/java/com/github/javpower/javavision/util/ImageFeatureUtil.java index 2268abaaa4ed42926d4e5a65d36a176c14032ad4..84537e5aa0f0e847f13febb8229d31b359fb239c 100644 --- a/src/main/java/com/github/javpower/javavision/util/ImageFeatureUtil.java +++ b/src/main/java/com/github/javpower/javavision/util/ImageFeatureUtil.java @@ -6,7 +6,6 @@ import ai.djl.modality.cv.Image; import ai.djl.modality.cv.ImageFactory; import ai.djl.repository.zoo.Criteria; import ai.djl.repository.zoo.ModelZoo; -import ai.djl.repository.zoo.ZooModel; import ai.djl.translate.TranslateException; import ai.onnxruntime.OrtException; import com.github.javpower.javavision.detect.ImageFeatureDetect; @@ -22,22 +21,62 @@ import java.nio.file.Paths; */ public class ImageFeatureUtil { private static final Logger logger = LoggerFactory.getLogger(ImageFeatureUtil.class); + private static volatile ImageFeatureUtil instance; + private Predictor predictor; - private ImageFeatureUtil() {} + // 单例的私有构造函数 + private ImageFeatureUtil() { + try { + ImageFeatureDetect imageFeatureDetect = new ImageFeatureDetect("image_feature.zip"); + Criteria criteria = imageFeatureDetect.criteria(); + this.predictor = ModelZoo.loadModel(criteria).newPredictor(); + } catch (IOException | ModelException | OrtException e) { + logger.error("Failed to initialize Predictor", e); + } + } + + // 获取单例实例的方法 + public static ImageFeatureUtil getInstance() { + if (instance == null) { + synchronized (ImageFeatureUtil.class) { + if (instance == null) { + instance = new ImageFeatureUtil(); + } + } + } + return instance; + } - //获取图片特征 + // 静态的runOcr方法,用于外部调用 public static float[] runOcr(String path) throws IOException, ModelException, TranslateException, OrtException { Path imageFile = Paths.get(path); Image image = ImageFactory.getInstance().fromFile(imageFile); return runOcr(image); } - public static float[] runOcr(Image image) throws IOException, ModelException, TranslateException, OrtException { - ImageFeatureDetect imageFeatureDetect = new ImageFeatureDetect("image_feature.zip"); - Criteria criteria = imageFeatureDetect.criteria(); - try (ZooModel model = ModelZoo.loadModel(criteria); - Predictor predictor = model.newPredictor()) { - float[] predict = predictor.predict(image); - return predict; + + public static float[] runOcr(Image image) throws ModelException, TranslateException, OrtException { + // 通过getInstance()获取单例实例,然后调用其predict方法 + return getInstance().predict(image); + } + + // 非静态的predict方法,仅供单例内部使用 + private float[] predict(Image image) throws ModelException, TranslateException, OrtException { + return predictor.predict(image); + } + + // 关闭Predictor的方法 + public void close() { + if (predictor != null) { + try { + predictor.close(); + } catch (Exception e) { + logger.error("Failed to close Predictor", e); + } } } + + // 添加关闭钩子 + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> getInstance().close())); + } } diff --git a/src/main/java/com/github/javpower/javavision/vectorex/mapper/FaceVectoRexMapper.java b/src/main/java/com/github/javpower/javavision/vectorex/mapper/FaceVectoRexMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..035feb574afaab364bcfd8eba76faf9a3e49ce4e --- /dev/null +++ b/src/main/java/com/github/javpower/javavision/vectorex/mapper/FaceVectoRexMapper.java @@ -0,0 +1,10 @@ +package com.github.javpower.javavision.vectorex.mapper; + +import com.github.javpower.javavision.vectorex.model.Face; +import io.github.javpower.vectorexbootstater.mapper.VectorRexMapper; +import org.springframework.stereotype.Component; + +@Component +public class FaceVectoRexMapper extends VectorRexMapper { + +} diff --git a/src/main/java/com/github/javpower/javavision/vectorex/mapper/ImageVectoRexMapper.java b/src/main/java/com/github/javpower/javavision/vectorex/mapper/ImageVectoRexMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..757eeb67f9968221b65f1cd62f110960f1a5bb4b --- /dev/null +++ b/src/main/java/com/github/javpower/javavision/vectorex/mapper/ImageVectoRexMapper.java @@ -0,0 +1,10 @@ +package com.github.javpower.javavision.vectorex.mapper; + +import com.github.javpower.javavision.vectorex.model.Image; +import io.github.javpower.vectorexbootstater.mapper.VectorRexMapper; +import org.springframework.stereotype.Component; + +@Component +public class ImageVectoRexMapper extends VectorRexMapper { + +} diff --git a/src/main/java/com/github/javpower/javavision/vectorex/model/Face.java b/src/main/java/com/github/javpower/javavision/vectorex/model/Face.java new file mode 100644 index 0000000000000000000000000000000000000000..066f2c9726c5c74db9eae3403dad623cf9da492a --- /dev/null +++ b/src/main/java/com/github/javpower/javavision/vectorex/model/Face.java @@ -0,0 +1,28 @@ +package com.github.javpower.javavision.vectorex.model; + +import io.github.javpower.vectorex.keynote.model.MetricType; +import io.github.javpower.vectorexcore.annotation.VectoRexCollection; +import io.github.javpower.vectorexcore.annotation.VectoRexField; +import io.github.javpower.vectorexcore.entity.DataType; +import lombok.Data; + +import java.util.List; + +@Data +@VectoRexCollection(name = "face_collection") +public class Face { + + @VectoRexField(name = "person_id",isPrimaryKey = true) + private String personId; // 人员的唯一标识符 + + @VectoRexField(name = "image_url") + private String url; + + + @VectoRexField(name = "person_name") + private String personName; // 人员姓名 + + + @VectoRexField(name = "face_vector", dataType = DataType.FloatVector, dimension = 256,metricType = MetricType.FLOAT_COSINE_DISTANCE) + private List faceVector; // 存储人脸特征的向量 +} \ No newline at end of file diff --git a/src/main/java/com/github/javpower/javavision/vectorex/model/Image.java b/src/main/java/com/github/javpower/javavision/vectorex/model/Image.java new file mode 100644 index 0000000000000000000000000000000000000000..08a186ccd9c5323b492cdfe96a25f53a7e61295f --- /dev/null +++ b/src/main/java/com/github/javpower/javavision/vectorex/model/Image.java @@ -0,0 +1,25 @@ +package com.github.javpower.javavision.vectorex.model; + +import io.github.javpower.vectorex.keynote.model.MetricType; +import io.github.javpower.vectorexcore.annotation.VectoRexCollection; +import io.github.javpower.vectorexcore.annotation.VectoRexField; +import io.github.javpower.vectorexcore.entity.DataType; +import lombok.Data; + +import java.util.List; + +@Data +@VectoRexCollection(name = "image_collection") +public class Image { + + @VectoRexField(name = "image_id",isPrimaryKey = true) + private String imageId; // 人员的唯一标识符 + + + @VectoRexField(name = "image_url") + private String url; + + + @VectoRexField(name = "image_vector", dataType = DataType.FloatVector, dimension = 1024,metricType = MetricType.FLOAT_COSINE_DISTANCE) + private List imageVector; // 存储人脸特征的向量 +} \ No newline at end of file diff --git a/src/main/java/com/github/javpower/javavision/vectorex/service/FaceVectoRexService.java b/src/main/java/com/github/javpower/javavision/vectorex/service/FaceVectoRexService.java new file mode 100644 index 0000000000000000000000000000000000000000..db079e9f8b2a025f95867095e4ec5e15e50f2e67 --- /dev/null +++ b/src/main/java/com/github/javpower/javavision/vectorex/service/FaceVectoRexService.java @@ -0,0 +1,70 @@ +package com.github.javpower.javavision.vectorex.service; + +import ai.djl.ModelException; +import ai.djl.translate.TranslateException; +import ai.onnxruntime.OrtException; +import com.github.javpower.javavision.arcsoft.FaceEngineService; +import com.github.javpower.javavision.arcsoft.entity.FaceDetectObject; +import com.github.javpower.javavision.entity.FaceParam; +import com.github.javpower.javavision.util.FileUtil; +import com.github.javpower.javavision.vectorex.mapper.FaceVectoRexMapper; +import com.github.javpower.javavision.vectorex.model.Face; +import io.github.javpower.vectorexbootstater.core.VectoRexResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.List; + +@Service +@Slf4j +public class FaceVectoRexService { + @Autowired + private FaceVectoRexMapper faceVectoRexMapper; + @Autowired + private FileUtil fileUtil; + @Autowired + private FaceEngineService faceEngineService; + + public void add(String faceId,String personName,MultipartFile file) throws IOException, ModelException, TranslateException, OrtException { + Face face=new Face(); + String path = fileUtil.getPath(file); + List faceDetect = faceEngineService.getFaceDetect(path); + face.setPersonId(faceId); + face.setUrl(path); + face.setPersonName(personName); + face.setFaceVector(faceDetect.get(0).getFaceFeature()); + faceVectoRexMapper.insert(face); + + } + //图片及展示k个结果 + public Face search(MultipartFile file) throws IOException{ + List faceDetect = faceEngineService.getFaceDetect(file); + List> data = faceVectoRexMapper.queryWrapper() + .vector(Face::getFaceVector, faceDetect.get(0).getFaceFeature()) + .topK(1).query(); + for (VectoRexResult datum : data) { + Face entity = datum.getEntity(); + return entity; + } + return null; + } + + public void update(FaceParam param, MultipartFile file, HttpServletRequest request) throws IOException { + String path = fileUtil.getPath(file); + List faceDetect = faceEngineService.getFaceDetect(path); + Face face=new Face(); + face.setPersonId(param.getPersonId()); + face.setPersonName(param.getPersonName()); + face.setFaceVector(faceDetect.get(0).getFaceFeature()); + face.setUrl(path); + faceVectoRexMapper.updateById(face); + } + + public void del(FaceParam param ,HttpServletRequest request) { + faceVectoRexMapper.removeById(param.getPersonId()); + } +} diff --git a/src/main/java/com/github/javpower/javavision/vectorex/service/ImageVectoRexService.java b/src/main/java/com/github/javpower/javavision/vectorex/service/ImageVectoRexService.java new file mode 100644 index 0000000000000000000000000000000000000000..508f4a4670260bad1813f2ec9c037fc29df4c1a7 --- /dev/null +++ b/src/main/java/com/github/javpower/javavision/vectorex/service/ImageVectoRexService.java @@ -0,0 +1,88 @@ +package com.github.javpower.javavision.vectorex.service; + +import ai.djl.ModelException; +import ai.djl.modality.cv.ImageFactory; +import ai.djl.translate.TranslateException; +import ai.onnxruntime.OrtException; +import com.github.javpower.javavision.es.response.SearchResult; +import com.github.javpower.javavision.service.IImageService; +import com.github.javpower.javavision.util.FileUtil; +import com.github.javpower.javavision.util.ImageFeatureUtil; +import com.github.javpower.javavision.util.ImageUtil; +import com.github.javpower.javavision.vectorex.mapper.ImageVectoRexMapper; +import com.github.javpower.javavision.vectorex.model.Image; +import io.github.javpower.vectorexbootstater.core.VectoRexResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +@Service("ImageVectoRexService") +@Slf4j +public class ImageVectoRexService implements IImageService { + @Autowired + private ImageVectoRexMapper imageVectoRexMapper; + @Autowired + private FileUtil fileUtil; + + public void add(String imageId,MultipartFile file) throws IOException, ModelException, TranslateException, OrtException { + List imageSearchList=new ArrayList<>(); + Image search=new Image(); + String path = fileUtil.getPath(file); + search.setImageId(imageId); + search.setUrl(path); + imageSearchList.add(search); + batchAdd(imageSearchList); + } + //图片及展示k个结果 + public List search(InputStream input,int k) throws IOException, ModelException, TranslateException, OrtException { + List res=new ArrayList<>(); + try (InputStream inputStream = input) { + ai.djl.modality.cv.Image image = ImageFactory.getInstance().fromInputStream(inputStream); + float[] vector = ImageFeatureUtil.runOcr(image); + List floatList = new ArrayList<>(); + // 遍历数组并将每个元素添加到列表中 + for (float f : vector) { + floatList.add(f); + } + List floats = ImageUtil.normalizeVector(floatList); + List> data = imageVectoRexMapper.queryWrapper() + .vector(Image::getImageVector, floats) + .topK(k).query(); + for (VectoRexResult datum : data) { + Image entity = datum.getEntity(); + String url = entity.getUrl(); + Float distance = datum.getScore(); + Object imageId = datum.getEntity().getImageId(); + SearchResult result=new SearchResult(url,imageId.toString(),distance); + res.add(result); + } + } + return res; + } + + @Override + public void del(String imageId) { + imageVectoRexMapper.removeById(imageId); + } + + private void batchAdd(List imageSearchList) throws IOException, ModelException, TranslateException, OrtException { + //批量上传请求 + for (Image imageSearch : imageSearchList) { + float[] vector = ImageFeatureUtil.runOcr(imageSearch.getUrl()); + List floatList = new ArrayList<>(); + // 遍历数组并将每个元素添加到列表中 + for (float f : vector) { + floatList.add(f); + } + List floats = ImageUtil.normalizeVector(floatList); + imageSearch.setImageVector(floats); + } + imageVectoRexMapper.insert(imageSearchList); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1443372604ce8763150d44f2721db5499e4c8211..5a5f42f5186eabca601dce909855b0ea5498b8c1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -24,7 +24,10 @@ spring: multipart: max-file-size: 20MB max-request-size: 200MB - + redis: + host: 127.0.0.1 + port: 6379 + password: ocr: # 文件上传相关配置 @@ -32,7 +35,7 @@ ocr: # 上传文件访问路径 accessPath: /file/** # 上传文件保存路径 - uploadPath: /opt/ocr/ + uploadPath: image/ vision: # swagger配置 openapi: @@ -57,14 +60,19 @@ es: milvus: uri: https://in03-a5357975ab80da7.api.gcp-us-west1.zillizcloud.com token: 6fab5641a3156d2666feba14390e4ef4b6d376b5dce91faed303eec91a4bdb82239b70b29eb252b981daa3170516245818d4ee12 - enable: true # 是否启用 + enable: false # 是否启用 packages: - com.github.javpower.javavision.milvus.model open-log: true - log-level: WARN +# log-level: WARN + +vectorex: + enable: true # 是否启用 + packages: + - com.github.javpower.javavision.vectorex.model image: - service: milvus # 或者 es + service: vectorex # 或者 es、milvus # 使用虹软人脸识别,需要申请sdk密钥(该项目示例搭配的的是milvus数据库) config: @@ -74,4 +82,22 @@ config: sdk-key: "xxxxxxxx" active_key: "#此处不要填写" detect-pool-size: 5 - compare-pool-size: 5 \ No newline at end of file + compare-pool-size: 5 + + + +#redisson: +# timeout: 3000 # 超时时间(毫秒) +# address: "154.201.90.228:6379" # Redis地址 +# password: "" # Redis密码 + +jedis: + nodes: + - 127.0.0.1:6379 + pool: + minIdle: 64 + maxIdle: 64 + maxTotal: 128 + timeout: 500 + +