mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-12 12:48:57 +00:00
1、新增图片与视频活体检测
2、新增人脸属性识别(性别、年龄、口罩、姿态、眼睛状态) 3、优化检测返回与包结构 4、新增 dependencyManagement 统一依赖版本管理
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.11</version>
|
||||
<version>1.0.12</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-ocr</artifactId>
|
||||
@@ -41,7 +41,7 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<version>1.0.11</version>
|
||||
<version>1.0.12</version>
|
||||
<name>smartjavaai-ocr</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
* @author dwj
|
||||
*/
|
||||
public abstract class AbstractOcrModel implements OcrModel {
|
||||
@Override
|
||||
public void loadModel(OcrModelConfig config) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetectionResponse detect(String imagePath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detectAndDraw(String imagePath, String outputPath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,37 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
package cn.smartjavaai.ocr.detection;
|
||||
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
* @author dwj
|
||||
*/
|
||||
public interface OcrModel {
|
||||
public interface OcrDetModel {
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
*/
|
||||
void loadModel(OcrModelConfig config); // 加载模型
|
||||
|
||||
void loadModel(OcrDetModelConfig config); // 加载模型
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
DetectionResponse detect(String imagePath);
|
||||
default DetectionResponse detect(String imagePath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测并绘制结果
|
||||
* @param imagePath 图片输入路径(包含文件名称)
|
||||
* @param outputPath 图片输出路径(包含文件名称)
|
||||
*/
|
||||
void detectAndDraw(String imagePath, String outputPath);
|
||||
default void detectAndDraw(String imagePath, String outputPath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
package cn.smartjavaai.ocr.detection;
|
||||
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import lombok.Data;
|
||||
@@ -8,12 +8,12 @@ import lombok.Data;
|
||||
* @date 2025/4/22
|
||||
*/
|
||||
@Data
|
||||
public class OcrModelConfig {
|
||||
public class OcrDetModelConfig {
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private OcrModelEnum modelEnum;
|
||||
private OcrDetModelEnum modelEnum;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
@@ -1,11 +1,11 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
package cn.smartjavaai.ocr.detection;
|
||||
|
||||
/**
|
||||
* OCR模型枚举
|
||||
* @author dwj
|
||||
* @date 2025/4/4
|
||||
*/
|
||||
public enum OcrModelEnum {
|
||||
public enum OcrDetModelEnum {
|
||||
|
||||
PADDLEOCR_V4_DET_MODEL;
|
||||
|
||||
@@ -13,9 +13,9 @@ public enum OcrModelEnum {
|
||||
/**
|
||||
* 根据名称获取枚举 (忽略大小写和下划线变体)
|
||||
*/
|
||||
public static OcrModelEnum fromName(String name) {
|
||||
public static OcrDetModelEnum fromName(String name) {
|
||||
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
|
||||
for (OcrModelEnum model : values()) {
|
||||
for (OcrDetModelEnum model : values()) {
|
||||
if (model.name().replaceAll("_", "").equals(formatted)) {
|
||||
return model;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
package cn.smartjavaai.ocr.detection;
|
||||
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.ocr.exception.OcrException;
|
||||
import cn.smartjavaai.ocr.model.PaddleOCRV4DetectModel;
|
||||
import cn.smartjavaai.ocr.ppv4.model.PaddleOCRV4DetModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -14,25 +14,25 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class OcrModelFactory {
|
||||
public class OcrDetModelFactory {
|
||||
|
||||
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
|
||||
private static volatile OcrModelFactory instance;
|
||||
private static volatile OcrDetModelFactory instance;
|
||||
|
||||
private static final ConcurrentHashMap<String, OcrModel> modelMap = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<String, OcrDetModel> modelMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 算法注册表
|
||||
*/
|
||||
private static final Map<String, Class<? extends OcrModel>> registry =
|
||||
private static final Map<String, Class<? extends OcrDetModel>> registry =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public static OcrModelFactory getInstance() {
|
||||
public static OcrDetModelFactory getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (OcrModelFactory.class) {
|
||||
synchronized (OcrDetModelFactory.class) {
|
||||
if (instance == null) {
|
||||
instance = new OcrModelFactory();
|
||||
instance = new OcrDetModelFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public class OcrModelFactory {
|
||||
* @param name
|
||||
* @param clazz
|
||||
*/
|
||||
private static void registerModel(String name, Class<? extends OcrModel> clazz) {
|
||||
private static void registerModel(String name, Class<? extends OcrDetModel> clazz) {
|
||||
registry.put(name.toLowerCase(), clazz);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class OcrModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public OcrModel getModel(OcrModelConfig config) {
|
||||
public OcrDetModel getModel(OcrDetModelConfig config) {
|
||||
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
|
||||
throw new OcrException("未配置OCR模型");
|
||||
}
|
||||
@@ -70,14 +70,14 @@ public class OcrModelFactory {
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private OcrModel createFaceModel(OcrModelConfig config) {
|
||||
private OcrDetModel createFaceModel(OcrDetModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum().name().toLowerCase());
|
||||
if(clazz == null){
|
||||
throw new OcrException("Unsupported model");
|
||||
}
|
||||
OcrModel algorithm = null;
|
||||
OcrDetModel algorithm = null;
|
||||
try {
|
||||
algorithm = (OcrModel) clazz.newInstance();
|
||||
algorithm = (OcrDetModel) clazz.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new OcrException(e);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public class OcrModelFactory {
|
||||
|
||||
// 初始化默认算法
|
||||
static {
|
||||
registerModel("PADDLEOCR_V4_DET_MODEL", PaddleOCRV4DetectModel.class);
|
||||
registerModel("PADDLEOCR_V4_DET_MODEL", PaddleOCRV4DetModel.class);
|
||||
log.info("缓存目录:{}", Config.getCachePath());
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package cn.smartjavaai.ocr.model;
|
||||
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelZoo;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
import cn.smartjavaai.ocr.translator.PaddleOCRV4DetectionTranslator;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/4/21
|
||||
*/
|
||||
public class PaddleOCRV4Model {
|
||||
|
||||
|
||||
public void loadModel(){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,22 @@
|
||||
package cn.smartjavaai.ocr.model;
|
||||
package cn.smartjavaai.ocr.ppv4.model;
|
||||
|
||||
import ai.djl.MalformedModelException;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.ImageFactory;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ModelZoo;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
import ai.djl.translate.TranslateException;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||
import cn.smartjavaai.common.utils.FileUtils;
|
||||
import cn.smartjavaai.ocr.AbstractOcrModel;
|
||||
import cn.smartjavaai.ocr.OcrModelConfig;
|
||||
import cn.smartjavaai.ocr.detection.OcrDetModelConfig;
|
||||
import cn.smartjavaai.ocr.exception.OcrException;
|
||||
import cn.smartjavaai.ocr.translator.PaddleOCRV4DetectionTranslator;
|
||||
import cn.smartjavaai.ocr.detection.OcrDetModel;
|
||||
import cn.smartjavaai.ocr.ppv4.translator.PaddleOCRV4DetectTranslator;
|
||||
import cn.smartjavaai.ocr.utils.ImageUtils;
|
||||
import cn.smartjavaai.ocr.utils.OcrUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -40,14 +37,14 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @date 2025/4/21
|
||||
*/
|
||||
@Slf4j
|
||||
public class PaddleOCRV4DetectModel extends AbstractOcrModel {
|
||||
public class PaddleOCRV4DetModel implements OcrDetModel {
|
||||
|
||||
private ZooModel detectionModel;
|
||||
|
||||
private ObjectPool<Predictor<Image, NDList>> predictorPool;
|
||||
|
||||
@Override
|
||||
public void loadModel(OcrModelConfig config){
|
||||
public void loadModel(OcrDetModelConfig config){
|
||||
if(StringUtils.isBlank(config.getModelPath())){
|
||||
throw new OcrException("modelPath is null");
|
||||
}
|
||||
@@ -56,7 +53,7 @@ public class PaddleOCRV4DetectModel extends AbstractOcrModel {
|
||||
.optEngine("OnnxRuntime")
|
||||
.setTypes(Image.class, NDList.class)
|
||||
.optModelPath(Paths.get(config.getModelPath()))
|
||||
.optTranslator(new PaddleOCRV4DetectionTranslator(new ConcurrentHashMap<String, String>()))
|
||||
.optTranslator(new PaddleOCRV4DetectTranslator(new ConcurrentHashMap<String, String>()))
|
||||
.optProgress(new ProgressBar())
|
||||
.build();
|
||||
try{
|
||||
@@ -83,11 +80,6 @@ public class PaddleOCRV4DetectModel extends AbstractOcrModel {
|
||||
return detect(img);
|
||||
}
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param image
|
||||
* @return
|
||||
*/
|
||||
private DetectionResponse detect(Image image){
|
||||
Predictor<Image, NDList> predictor = null;
|
||||
try {
|
||||
@@ -120,7 +112,7 @@ public class PaddleOCRV4DetectModel extends AbstractOcrModel {
|
||||
try {
|
||||
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
|
||||
DetectionResponse detectionResponse = detect(img);
|
||||
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getRectangleList()) || detectionResponse.getRectangleList().isEmpty()){
|
||||
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
|
||||
throw new OcrException("未识别到文字");
|
||||
}
|
||||
ImageUtils.drawRect((Mat)img.getWrappedImage(), detectionResponse);
|
||||
@@ -0,0 +1,129 @@
|
||||
package cn.smartjavaai.ocr.ppv4.model;
|
||||
|
||||
import ai.djl.MalformedModelException;
|
||||
import ai.djl.inference.Predictor;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.ImageFactory;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import ai.djl.repository.zoo.Criteria;
|
||||
import ai.djl.repository.zoo.ModelNotFoundException;
|
||||
import ai.djl.repository.zoo.ModelZoo;
|
||||
import ai.djl.repository.zoo.ZooModel;
|
||||
import ai.djl.training.util.ProgressBar;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.pool.PredictorFactory;
|
||||
import cn.smartjavaai.common.utils.FileUtils;
|
||||
import cn.smartjavaai.ocr.detection.OcrDetModel;
|
||||
import cn.smartjavaai.ocr.detection.OcrDetModelConfig;
|
||||
import cn.smartjavaai.ocr.exception.OcrException;
|
||||
import cn.smartjavaai.ocr.ppv4.translator.PaddleOCRV4DetectTranslator;
|
||||
import cn.smartjavaai.ocr.recognition.OcrRecModel;
|
||||
import cn.smartjavaai.ocr.recognition.OcrRecModelConfig;
|
||||
import cn.smartjavaai.ocr.utils.ImageUtils;
|
||||
import cn.smartjavaai.ocr.utils.OcrUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
import org.opencv.core.Mat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* PaddleOCRV4 识别模型实现
|
||||
* @author dwj
|
||||
* @date 2025/4/21
|
||||
*/
|
||||
@Slf4j
|
||||
public class PaddleOCRV4RecModel implements OcrRecModel {
|
||||
|
||||
private ZooModel detectionModel;
|
||||
|
||||
private ObjectPool<Predictor<Image, NDList>> predictorPool;
|
||||
|
||||
@Override
|
||||
public void loadModel(OcrRecModelConfig config){
|
||||
if(StringUtils.isBlank(config.getModelPath())){
|
||||
throw new OcrException("modelPath is null");
|
||||
}
|
||||
Criteria<Image, NDList> criteria =
|
||||
Criteria.builder()
|
||||
.optEngine("OnnxRuntime")
|
||||
.setTypes(Image.class, NDList.class)
|
||||
.optModelPath(Paths.get(config.getModelPath()))
|
||||
.optTranslator(new PaddleOCRV4DetectTranslator(new ConcurrentHashMap<String, String>()))
|
||||
.optProgress(new ProgressBar())
|
||||
.build();
|
||||
try{
|
||||
detectionModel = ModelZoo.loadModel(criteria);
|
||||
// 创建池子:每个线程独享 Predictor
|
||||
this.predictorPool = new GenericObjectPool<>(new PredictorFactory<>(detectionModel));
|
||||
log.info("当前设备: " + detectionModel.getNDManager().getDevice());
|
||||
} catch (IOException | ModelNotFoundException | MalformedModelException e) {
|
||||
throw new OcrException("模型加载失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DetectionResponse detect(String imagePath){
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new OcrException("图像文件不存在");
|
||||
}
|
||||
Image img = null;
|
||||
try {
|
||||
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
|
||||
} catch (IOException e) {
|
||||
throw new OcrException("无效的图片", e);
|
||||
}
|
||||
return detect(img);
|
||||
}
|
||||
|
||||
private DetectionResponse detect(Image image){
|
||||
Predictor<Image, NDList> predictor = null;
|
||||
try {
|
||||
predictor = predictorPool.borrowObject();
|
||||
NDList result = predictor.predict(image);
|
||||
return OcrUtils.convertToDetectionResponse(result, image);
|
||||
} catch (Exception e) {
|
||||
throw new OcrException("OCR检测错误", e);
|
||||
}finally {
|
||||
if (predictor != null) {
|
||||
try {
|
||||
predictorPool.returnObject(predictor); //归还
|
||||
} catch (Exception e) {
|
||||
log.warn("归还Predictor失败", e);
|
||||
try {
|
||||
predictor.close(); // 归还失败才销毁
|
||||
} catch (Exception ex) {
|
||||
log.error("关闭Predictor失败", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detectAndDraw(String imagePath, String outputPath) {
|
||||
if(!FileUtils.isFileExists(imagePath)){
|
||||
throw new OcrException("图像文件不存在");
|
||||
}
|
||||
try {
|
||||
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
|
||||
DetectionResponse detectionResponse = detect(img);
|
||||
if(Objects.isNull(detectionResponse) || Objects.isNull(detectionResponse.getDetectionInfoList()) || detectionResponse.getDetectionInfoList().isEmpty()){
|
||||
throw new OcrException("未识别到文字");
|
||||
}
|
||||
ImageUtils.drawRect((Mat)img.getWrappedImage(), detectionResponse);
|
||||
Path output = Paths.get(outputPath);
|
||||
log.info("Saving to {}", output.toAbsolutePath().toString());
|
||||
img.save(Files.newOutputStream(output), "png");
|
||||
} catch (IOException e) {
|
||||
throw new OcrException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.smartjavaai.ocr.translator;
|
||||
package cn.smartjavaai.ocr.ppv4.translator;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.util.NDImageUtils;
|
||||
@@ -27,7 +27,7 @@ import java.util.Map;
|
||||
* @mail 179209347@qq.com
|
||||
* @website www.aias.top
|
||||
*/
|
||||
public class PaddleOCRV4DetectionTranslator implements Translator<Image, NDList> {
|
||||
public class PaddleOCRV4DetectTranslator implements Translator<Image, NDList> {
|
||||
// det_algorithm == "DB"
|
||||
private final float thresh = 0.3f;
|
||||
private final boolean use_dilation = false;
|
||||
@@ -44,7 +44,7 @@ public class PaddleOCRV4DetectionTranslator implements Translator<Image, NDList>
|
||||
private int img_height;
|
||||
private int img_width;
|
||||
|
||||
public PaddleOCRV4DetectionTranslator(Map<String, ?> arguments) {
|
||||
public PaddleOCRV4DetectTranslator(Map<String, ?> arguments) {
|
||||
limit_side_len =
|
||||
arguments.containsKey("limit_side_len")
|
||||
? Integer.parseInt(arguments.get("limit_side_len").toString())
|
||||
@@ -1,4 +1,4 @@
|
||||
package cn.smartjavaai.ocr.translator;
|
||||
package cn.smartjavaai.ocr.ppv4.translator;
|
||||
|
||||
import ai.djl.Model;
|
||||
import ai.djl.modality.cv.Image;
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.smartjavaai.ocr.recognition;
|
||||
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
|
||||
/**
|
||||
* OCR模型
|
||||
* @author dwj
|
||||
*/
|
||||
public interface OcrRecModel {
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
*/
|
||||
void loadModel(OcrRecModelConfig config); // 加载模型
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
default DetectionResponse detect(String imagePath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测并绘制结果
|
||||
* @param imagePath 图片输入路径(包含文件名称)
|
||||
* @param outputPath 图片输出路径(包含文件名称)
|
||||
*/
|
||||
default void detectAndDraw(String imagePath, String outputPath) {
|
||||
throw new UnsupportedOperationException("默认不支持该功能");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.smartjavaai.ocr.recognition;
|
||||
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/4/22
|
||||
*/
|
||||
@Data
|
||||
public class OcrRecModelConfig {
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private OcrRecModelEnum modelEnum;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
private DeviceEnum device;
|
||||
|
||||
/**
|
||||
* 模型路径
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.smartjavaai.ocr.recognition;
|
||||
|
||||
/**
|
||||
* OCR识别模型枚举
|
||||
* @author dwj
|
||||
* @date 2025/4/4
|
||||
*/
|
||||
public enum OcrRecModelEnum {
|
||||
|
||||
PADDLEOCR_V4_REC_MODEL;
|
||||
|
||||
|
||||
/**
|
||||
* 根据名称获取枚举 (忽略大小写和下划线变体)
|
||||
*/
|
||||
public static OcrRecModelEnum fromName(String name) {
|
||||
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
|
||||
for (OcrRecModelEnum model : values()) {
|
||||
if (model.name().replaceAll("_", "").equals(formatted)) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知模型名称: " + name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package cn.smartjavaai.ocr.recognition;
|
||||
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.ocr.exception.OcrException;
|
||||
import cn.smartjavaai.ocr.ppv4.model.PaddleOCRV4DetModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* OCR模型工厂
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class OcrRecModelFactory {
|
||||
|
||||
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
|
||||
private static volatile OcrRecModelFactory instance;
|
||||
|
||||
private static final ConcurrentHashMap<String, OcrRecModel> modelMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 算法注册表
|
||||
*/
|
||||
private static final Map<String, Class<? extends OcrRecModel>> registry =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public static OcrRecModelFactory getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (OcrRecModelFactory.class) {
|
||||
if (instance == null) {
|
||||
instance = new OcrRecModelFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 注册算法
|
||||
* @param name
|
||||
* @param clazz
|
||||
*/
|
||||
private static void registerModel(String name, Class<? extends OcrRecModel> clazz) {
|
||||
registry.put(name.toLowerCase(), clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取模型(通过配置)
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public OcrRecModel getModel(OcrRecModelConfig config) {
|
||||
if(Objects.isNull(config) || Objects.isNull(config.getModelEnum())){
|
||||
throw new OcrException("未配置OCR模型");
|
||||
}
|
||||
return modelMap.computeIfAbsent(config.getModelEnum().name(), k -> {
|
||||
return createFaceModel(config);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用ModelConfig创建算法
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
private OcrRecModel createFaceModel(OcrRecModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum().name().toLowerCase());
|
||||
if(clazz == null){
|
||||
throw new OcrException("Unsupported model");
|
||||
}
|
||||
OcrRecModel algorithm = null;
|
||||
try {
|
||||
algorithm = (OcrRecModel) clazz.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new OcrException(e);
|
||||
}
|
||||
algorithm.loadModel(config);
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
|
||||
// 初始化默认算法
|
||||
static {
|
||||
//registerModel("PADDLEOCR_V4_REC_MODEL", PaddleOCRV4DetModel.class);
|
||||
log.info("缓存目录:{}", Config.getCachePath());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.ImageFactory;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import cn.smartjavaai.common.entity.DetectionInfo;
|
||||
import cn.smartjavaai.common.entity.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.utils.OpenCVUtils;
|
||||
@@ -207,7 +208,8 @@ public class ImageUtils {
|
||||
*/
|
||||
public static void drawRect(Mat mat, DetectionResponse detectionResponse) {
|
||||
|
||||
for(DetectionRectangle detectionRectangle : detectionResponse.getRectangleList()){
|
||||
for(DetectionInfo detectionInfo : detectionResponse.getDetectionInfoList()){
|
||||
DetectionRectangle detectionRectangle = detectionInfo.getDetectionRectangle();
|
||||
// 左上角点
|
||||
Point topLeft = new Point(detectionRectangle.getX(), detectionRectangle.getY());
|
||||
// 右下角点
|
||||
|
||||
@@ -5,6 +5,7 @@ import ai.djl.modality.cv.output.BoundingBox;
|
||||
import ai.djl.modality.cv.output.DetectedObjects;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import cn.smartjavaai.common.entity.DetectionInfo;
|
||||
import cn.smartjavaai.common.entity.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -34,7 +35,7 @@ public class OcrUtils {
|
||||
return null;
|
||||
}
|
||||
DetectionResponse detectionResponse = new DetectionResponse();
|
||||
List<DetectionRectangle> rectangleList = new ArrayList<DetectionRectangle>();
|
||||
List<DetectionInfo> detectionInfoList = new ArrayList<DetectionInfo>();
|
||||
for(NDArray box : dt_boxes){
|
||||
DetectionRectangle rectangle = new DetectionRectangle();
|
||||
float[] points = box.toFloatArray();
|
||||
@@ -54,9 +55,9 @@ public class OcrUtils {
|
||||
rectangle.setY(y);
|
||||
rectangle.setHeight(height);
|
||||
rectangle.setWidth(width);
|
||||
rectangleList.add(rectangle);
|
||||
detectionInfoList.add(new DetectionInfo(rectangle));
|
||||
}
|
||||
detectionResponse.setRectangleList(rectangleList);
|
||||
detectionResponse.setDetectionInfoList(detectionInfoList);
|
||||
return detectionResponse;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user