mirror of
https://github.com/geekwenjie/SmartJavaAI.git
synced 2026-09-14 22:18:42 +00:00
1、FaceNet 特征提取新增人脸对齐
2、人脸检测新5点人脸关键点定位 3、特征提取接口支持多人脸和最佳人脸提取 4、修复人脸框边界精度问题 5、更新 Maven 发布的 groupId
This commit is contained in:
149
smartjavaai-ocr/pom.xml
Normal file
149
smartjavaai-ocr/pom.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-parent</artifactId>
|
||||
<version>1.0.11</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>smartjavaai-ocr</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<javacv.version>1.5.8</javacv.version>
|
||||
<javacv.ffmpeg.version>5.1.2-1.5.8</javacv.ffmpeg.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.smartjavaai</groupId>
|
||||
<artifactId>smartjavaai-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>javacpp</artifactId>
|
||||
<version>${javacv.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bytedeco</groupId>
|
||||
<artifactId>ffmpeg</artifactId>
|
||||
<version>${javacv.ffmpeg.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ai.djl.opencv</groupId>
|
||||
<artifactId>opencv</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<version>1.0.11</version>
|
||||
<name>smartjavaai-ocr</name>
|
||||
<description>SmartJavaAI</description>
|
||||
<url>https://github.com/geekwenjie/SmartJavaAI</url>
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>https://opensource.org/licenses/MIT</url>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.central</groupId>
|
||||
<artifactId>central-publishing-maven-plugin</artifactId>
|
||||
<version>0.4.0</version>
|
||||
<extensions>true</extensions>
|
||||
<configuration>
|
||||
<publishingServerId>dengwenjie</publishingServerId>
|
||||
<tokenAuth>true</tokenAuth>
|
||||
<deploymentName>${project.groupId}:${project.artifactId}:${project.version}</deploymentName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar-no-fork</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
|
||||
<doclint>none</doclint>
|
||||
<additionalJOptions>
|
||||
<additionalJOption>-Xdoclint:none</additionalJOption>
|
||||
</additionalJOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<!-- 必须添加:SCM信息 -->
|
||||
<scm>
|
||||
<connection>scm:git:git://github.com/geekwenjie/SmartJavaAI.git</connection>
|
||||
<developerConnection>scm:git:ssh://github.com/geekwenjie/SmartJavaAI.git</developerConnection>
|
||||
<url>http://github.com/geekwenjie/SmartJavaAI/tree/master</url>
|
||||
</scm>
|
||||
|
||||
|
||||
<distributionManagement>
|
||||
<snapshotRepository>
|
||||
<id>dengwenjie</id>
|
||||
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</snapshotRepository>
|
||||
<repository>
|
||||
<id>dengwenjie</id>
|
||||
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<name>dengwenjie</name>
|
||||
<email>775747758@qq.com</email>
|
||||
<roles>
|
||||
<role>Project Manager</role>
|
||||
<role>Architect</role>
|
||||
</roles>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,24 @@
|
||||
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("默认不支持该功能");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 人脸识别算法
|
||||
* @author dwj
|
||||
*/
|
||||
public interface OcrModel {
|
||||
|
||||
/**
|
||||
* 加载模型
|
||||
* @param config
|
||||
*/
|
||||
void loadModel(OcrModelConfig config); // 加载模型
|
||||
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param imagePath 图片路径
|
||||
* @return
|
||||
*/
|
||||
DetectionResponse detect(String imagePath);
|
||||
|
||||
/**
|
||||
* 检测并绘制结果
|
||||
* @param imagePath 图片输入路径(包含文件名称)
|
||||
* @param outputPath 图片输出路径(包含文件名称)
|
||||
*/
|
||||
void detectAndDraw(String imagePath, String outputPath);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
|
||||
import cn.smartjavaai.common.enums.DeviceEnum;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/4/22
|
||||
*/
|
||||
@Data
|
||||
public class OcrModelConfig {
|
||||
|
||||
/**
|
||||
* 模型名称
|
||||
*/
|
||||
private OcrModelEnum modelEnum;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
private DeviceEnum device;
|
||||
|
||||
/**
|
||||
* 模型路径
|
||||
*/
|
||||
private String modelPath;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
|
||||
/**
|
||||
* OCR模型枚举
|
||||
* @author dwj
|
||||
* @date 2025/4/4
|
||||
*/
|
||||
public enum OcrModelEnum {
|
||||
|
||||
PADDLEOCR_V4_DET_MODEL;
|
||||
|
||||
|
||||
/**
|
||||
* 根据名称获取枚举 (忽略大小写和下划线变体)
|
||||
*/
|
||||
public static OcrModelEnum fromName(String name) {
|
||||
String formatted = name.trim().toUpperCase().replaceAll("[-_]", "");
|
||||
for (OcrModelEnum model : values()) {
|
||||
if (model.name().replaceAll("_", "").equals(formatted)) {
|
||||
return model;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("未知模型名称: " + name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package cn.smartjavaai.ocr;
|
||||
|
||||
import cn.smartjavaai.common.config.Config;
|
||||
import cn.smartjavaai.ocr.exception.OcrException;
|
||||
import cn.smartjavaai.ocr.model.PaddleOCRV4DetectModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 人脸算法工厂
|
||||
* @author dwj
|
||||
*/
|
||||
@Slf4j
|
||||
public class OcrModelFactory {
|
||||
|
||||
// 使用 volatile 和双重检查锁定来确保线程安全的单例模式
|
||||
private static volatile OcrModelFactory instance;
|
||||
|
||||
private static final ConcurrentHashMap<String, OcrModel> modelMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 算法注册表
|
||||
*/
|
||||
private static final Map<String, Class<? extends OcrModel>> registry =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public static OcrModelFactory getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (OcrModelFactory.class) {
|
||||
if (instance == null) {
|
||||
instance = new OcrModelFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 注册算法
|
||||
* @param name
|
||||
* @param clazz
|
||||
*/
|
||||
private static void registerModel(String name, Class<? extends OcrModel> clazz) {
|
||||
registry.put(name.toLowerCase(), clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取模型(通过配置)
|
||||
* @param config
|
||||
* @return
|
||||
*/
|
||||
public OcrModel getModel(OcrModelConfig 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 OcrModel createFaceModel(OcrModelConfig config) {
|
||||
Class<?> clazz = registry.get(config.getModelEnum().name().toLowerCase());
|
||||
if(clazz == null){
|
||||
throw new OcrException("Unsupported model");
|
||||
}
|
||||
OcrModel algorithm = null;
|
||||
try {
|
||||
algorithm = (OcrModel) clazz.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new OcrException(e);
|
||||
}
|
||||
algorithm.loadModel(config);
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
|
||||
// 初始化默认算法
|
||||
static {
|
||||
registerModel("PADDLEOCR_V4_DET_MODEL", PaddleOCRV4DetectModel.class);
|
||||
log.info("缓存目录:{}", Config.getCachePath());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.smartjavaai.ocr.entity;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
/**
|
||||
* 图像信息
|
||||
*/
|
||||
public class ImageInfo {
|
||||
private String name;
|
||||
private Double prob;
|
||||
private Image image;
|
||||
private NDArray box;
|
||||
|
||||
public ImageInfo(Image image, NDArray box) {
|
||||
this.image = image;
|
||||
this.box = box;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Double getProb() {
|
||||
return prob;
|
||||
}
|
||||
|
||||
public void setProb(Double prob) {
|
||||
this.prob = prob;
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public NDArray getBox() {
|
||||
return box;
|
||||
}
|
||||
|
||||
public void setBox(NDArray box) {
|
||||
this.box = box;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.smartjavaai.ocr.entity;
|
||||
|
||||
import ai.djl.ndarray.NDArray;
|
||||
/**
|
||||
* 旋转检测框
|
||||
*/
|
||||
public class RotatedBox implements Comparable<RotatedBox> {
|
||||
private NDArray box;
|
||||
private String text;
|
||||
|
||||
public RotatedBox(NDArray box, String text) {
|
||||
this.box = box;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将左上角 Y 坐标升序排序
|
||||
*
|
||||
* @param o
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(RotatedBox o) {
|
||||
NDArray lowBox = this.getBox();
|
||||
NDArray highBox = o.getBox();
|
||||
float lowY = lowBox.toFloatArray()[1];
|
||||
float highY = highBox.toFloatArray()[1];
|
||||
return (lowY < highY) ? -1 : 1;
|
||||
}
|
||||
|
||||
public NDArray getBox() {
|
||||
return box;
|
||||
}
|
||||
|
||||
public void setBox(NDArray box) {
|
||||
this.box = box;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.smartjavaai.ocr.entity;
|
||||
|
||||
import ai.djl.ndarray.NDArray;
|
||||
/**
|
||||
* 旋转检测框 - 支持左上角 X 坐标升序排序
|
||||
*/
|
||||
public class RotatedBoxCompX implements Comparable<RotatedBoxCompX> {
|
||||
private NDArray box;
|
||||
private String text;
|
||||
|
||||
public RotatedBoxCompX(NDArray box, String text) {
|
||||
this.box = box;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将左上角 X 坐标升序排序
|
||||
*
|
||||
* @param o
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(RotatedBoxCompX o) {
|
||||
NDArray leftBox = this.getBox();
|
||||
NDArray rightBox = o.getBox();
|
||||
float leftX = leftBox.toFloatArray()[0];
|
||||
float rightX = rightBox.toFloatArray()[0];
|
||||
return (leftX < rightX) ? -1 : 1;
|
||||
}
|
||||
|
||||
public NDArray getBox() {
|
||||
return box;
|
||||
}
|
||||
|
||||
public void setBox(NDArray box) {
|
||||
this.box = box;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.smartjavaai.ocr.exception;
|
||||
|
||||
/**
|
||||
* 人脸检测异常
|
||||
* @author dwj
|
||||
* @date 2025/4/4
|
||||
*/
|
||||
public class OcrException extends RuntimeException{
|
||||
|
||||
public OcrException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public OcrException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public OcrException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public OcrException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public OcrException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package cn.smartjavaai.ocr.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.exception.OcrException;
|
||||
import cn.smartjavaai.ocr.translator.PaddleOCRV4DetectionTranslator;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/4/21
|
||||
*/
|
||||
@Slf4j
|
||||
public class PaddleOCRV4DetectModel extends AbstractOcrModel {
|
||||
|
||||
private ZooModel detectionModel;
|
||||
|
||||
private ObjectPool<Predictor<Image, NDList>> predictorPool;
|
||||
|
||||
@Override
|
||||
public void loadModel(OcrModelConfig 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 PaddleOCRV4DetectionTranslator(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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 人脸检测
|
||||
* @param image
|
||||
* @return
|
||||
*/
|
||||
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.getRectangleList()) || detectionResponse.getRectangleList().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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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(){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package cn.smartjavaai.ocr.opencv;
|
||||
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.MatOfPoint;
|
||||
import org.opencv.core.Point;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
/**
|
||||
* NDArray Utils
|
||||
*
|
||||
*/
|
||||
public class OcrNDArrayUtils {
|
||||
/**
|
||||
* Mat To MatOfPoint
|
||||
* @param mat
|
||||
* @return
|
||||
*/
|
||||
public static MatOfPoint matToMatOfPoint(Mat mat) {
|
||||
int rows = mat.rows();
|
||||
MatOfPoint matOfPoint = new MatOfPoint();
|
||||
|
||||
List<Point> list = new ArrayList<>();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
Point point = new Point((float) mat.get(i, 0)[0], (float) mat.get(i, 1)[0]);
|
||||
list.add(point);
|
||||
}
|
||||
matOfPoint.fromList(list);
|
||||
|
||||
return matOfPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* float NDArray To float[][] Array
|
||||
* @param ndArray
|
||||
* @return
|
||||
*/
|
||||
public static float[][] floatNDArrayToArray(NDArray ndArray) {
|
||||
int rows = (int) (ndArray.getShape().get(0));
|
||||
int cols = (int) (ndArray.getShape().get(1));
|
||||
float[][] arr = new float[rows][cols];
|
||||
|
||||
float[] arrs = ndArray.toFloatArray();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
arr[i][j] = arrs[i * cols + j];
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mat To double[][] Array
|
||||
* @param mat
|
||||
* @return
|
||||
*/
|
||||
public static double[][] matToDoubleArray(Mat mat) {
|
||||
int rows = mat.rows();
|
||||
int cols = mat.cols();
|
||||
|
||||
double[][] doubles = new double[rows][cols];
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
doubles[i][j] = mat.get(i, j)[0];
|
||||
}
|
||||
}
|
||||
|
||||
return doubles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mat To float[][] Array
|
||||
* @param mat
|
||||
* @return
|
||||
*/
|
||||
public static float[][] matToFloatArray(Mat mat) {
|
||||
int rows = mat.rows();
|
||||
int cols = mat.cols();
|
||||
|
||||
float[][] floats = new float[rows][cols];
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
floats[i][j] = (float) mat.get(i, j)[0];
|
||||
}
|
||||
}
|
||||
|
||||
return floats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mat To byte[][] Array
|
||||
* @param mat
|
||||
* @return
|
||||
*/
|
||||
public static byte[][] matToUint8Array(Mat mat) {
|
||||
int rows = mat.rows();
|
||||
int cols = mat.cols();
|
||||
|
||||
byte[][] bytes = new byte[rows][cols];
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
bytes[i][j] = (byte) mat.get(i, j)[0];
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* float NDArray To float[][] Array
|
||||
* @param ndArray
|
||||
* @param cvType
|
||||
* @return
|
||||
*/
|
||||
public static Mat floatNDArrayToMat(NDArray ndArray, int cvType) {
|
||||
int rows = (int) (ndArray.getShape().get(0));
|
||||
int cols = (int) (ndArray.getShape().get(1));
|
||||
Mat mat = new Mat(rows, cols, cvType);
|
||||
|
||||
float[] arrs = ndArray.toFloatArray();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
mat.put(i, j, arrs[i * cols + j]);
|
||||
}
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* float NDArray To Mat
|
||||
* @param ndArray
|
||||
* @return
|
||||
*/
|
||||
public static Mat floatNDArrayToMat(NDArray ndArray) {
|
||||
int rows = (int) (ndArray.getShape().get(0));
|
||||
int cols = (int) (ndArray.getShape().get(1));
|
||||
Mat mat = new Mat(rows, cols, CvType.CV_32F);
|
||||
|
||||
float[] arrs = ndArray.toFloatArray();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
mat.put(i, j, arrs[i * cols + j]);
|
||||
}
|
||||
}
|
||||
|
||||
return mat;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* uint8 NDArray To Mat
|
||||
* @param ndArray
|
||||
* @return
|
||||
*/
|
||||
public static Mat uint8NDArrayToMat(NDArray ndArray) {
|
||||
int rows = (int) (ndArray.getShape().get(0));
|
||||
int cols = (int) (ndArray.getShape().get(1));
|
||||
Mat mat = new Mat(rows, cols, CvType.CV_8U);
|
||||
|
||||
byte[] arrs = ndArray.toByteArray();
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
mat.put(i, j, arrs[i * cols + j]);
|
||||
}
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* float[][] Array To Mat
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
public static Mat floatArrayToMat(float[][] arr) {
|
||||
int rows = arr.length;
|
||||
int cols = arr[0].length;
|
||||
Mat mat = new Mat(rows, cols, CvType.CV_32F);
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
mat.put(i, j, arr[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* byte[][] Array To Mat
|
||||
* @param arr
|
||||
* @return
|
||||
*/
|
||||
public static Mat uint8ArrayToMat(byte[][] arr) {
|
||||
int rows = arr.length;
|
||||
int cols = arr[0].length;
|
||||
Mat mat = new Mat(rows, cols, CvType.CV_8U);
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
for (int j = 0; j < cols; j++) {
|
||||
mat.put(i, j, arr[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
/**
|
||||
* List To Mat
|
||||
* @param points
|
||||
* @return
|
||||
*/
|
||||
public static Mat toMat(List<ai.djl.modality.cv.output.Point> points) {
|
||||
Mat mat = new Mat(points.size(), 2, CvType.CV_32F);
|
||||
for (int i = 0; i < points.size(); i++) {
|
||||
ai.djl.modality.cv.output.Point point = points.get(i);
|
||||
mat.put(i, 0, (float) point.getX());
|
||||
mat.put(i, 1, (float) point.getY());
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cn.smartjavaai.ocr.opencv;
|
||||
|
||||
import org.opencv.core.CvType;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBufferByte;
|
||||
|
||||
/**
|
||||
* OpenCV Utils
|
||||
*
|
||||
*/
|
||||
public class OcrOpenCVUtils {
|
||||
|
||||
/**
|
||||
* 透视变换
|
||||
*
|
||||
* @param src
|
||||
* @param srcPoints
|
||||
* @param dstPoints
|
||||
* @return
|
||||
*/
|
||||
public static Mat perspectiveTransform(Mat src, Mat srcPoints, Mat dstPoints) {
|
||||
Mat dst = src.clone();
|
||||
Mat warp_mat = Imgproc.getPerspectiveTransform(srcPoints, dstPoints);
|
||||
Imgproc.warpPerspective(src, dst, warp_mat, dst.size());
|
||||
warp_mat.release();
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mat to BufferedImage
|
||||
*
|
||||
* @param mat
|
||||
* @return
|
||||
*/
|
||||
public static BufferedImage mat2Image(Mat mat) {
|
||||
int width = mat.width();
|
||||
int height = mat.height();
|
||||
byte[] data = new byte[width * height * (int) mat.elemSize()];
|
||||
Imgproc.cvtColor(mat, mat, 4);
|
||||
mat.get(0, 0, data);
|
||||
BufferedImage ret = new BufferedImage(width, height, 5);
|
||||
ret.getRaster().setDataElements(0, 0, width, height, data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* BufferedImage to Mat
|
||||
*
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public static Mat image2Mat(BufferedImage img) {
|
||||
int width = img.getWidth();
|
||||
int height = img.getHeight();
|
||||
byte[] data = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
|
||||
Mat mat = new Mat(height, width, CvType.CV_8UC3);
|
||||
mat.put(0, 0, data);
|
||||
return mat;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
package cn.smartjavaai.ocr.translator;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.util.NDImageUtils;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDArrays;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import ai.djl.ndarray.NDManager;
|
||||
import ai.djl.ndarray.index.NDIndex;
|
||||
import ai.djl.ndarray.types.DataType;
|
||||
import ai.djl.ndarray.types.Shape;
|
||||
import ai.djl.translate.Batchifier;
|
||||
import ai.djl.translate.Translator;
|
||||
import ai.djl.translate.TranslatorContext;
|
||||
import cn.smartjavaai.ocr.opencv.OcrNDArrayUtils;
|
||||
import org.opencv.core.*;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文字检测前后处理
|
||||
*
|
||||
* @author Calvin
|
||||
* @mail 179209347@qq.com
|
||||
* @website www.aias.top
|
||||
*/
|
||||
public class PaddleOCRV4DetectionTranslator implements Translator<Image, NDList> {
|
||||
// det_algorithm == "DB"
|
||||
private final float thresh = 0.3f;
|
||||
private final boolean use_dilation = false;
|
||||
private final String score_mode = "fast";
|
||||
private final String box_type = "quad";
|
||||
|
||||
private final int limit_side_len;
|
||||
private final int max_candidates;
|
||||
private final int min_size;
|
||||
private final float box_thresh;
|
||||
private final float unclip_ratio;
|
||||
private float ratio_h;
|
||||
private float ratio_w;
|
||||
private int img_height;
|
||||
private int img_width;
|
||||
|
||||
public PaddleOCRV4DetectionTranslator(Map<String, ?> arguments) {
|
||||
limit_side_len =
|
||||
arguments.containsKey("limit_side_len")
|
||||
? Integer.parseInt(arguments.get("limit_side_len").toString())
|
||||
: 960;
|
||||
max_candidates =
|
||||
arguments.containsKey("max_candidates")
|
||||
? Integer.parseInt(arguments.get("max_candidates").toString())
|
||||
: 1000;
|
||||
min_size =
|
||||
arguments.containsKey("min_size")
|
||||
? Integer.parseInt(arguments.get("min_size").toString())
|
||||
: 3;
|
||||
box_thresh =
|
||||
arguments.containsKey("box_thresh")
|
||||
? Float.parseFloat(arguments.get("box_thresh").toString())
|
||||
: 0.6f; // 0.5f
|
||||
unclip_ratio =
|
||||
arguments.containsKey("unclip_ratio")
|
||||
? Float.parseFloat(arguments.get("unclip_ratio").toString())
|
||||
: 1.6f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NDList processOutput(TranslatorContext ctx, NDList list) {
|
||||
NDManager manager = ctx.getNDManager();
|
||||
NDArray pred = list.get(0);
|
||||
pred = pred.squeeze();
|
||||
NDArray segmentation = pred.gt(thresh); // thresh=0.3 .mul(255f)
|
||||
|
||||
segmentation = segmentation.toType(DataType.UINT8, true);
|
||||
Shape shape = segmentation.getShape();
|
||||
int rows = (int) shape.get(0);
|
||||
int cols = (int) shape.get(1);
|
||||
|
||||
Mat newMask = new Mat();
|
||||
if (this.use_dilation) {
|
||||
Mat mask = new Mat();
|
||||
//convert from NDArray to Mat
|
||||
Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation);
|
||||
// size 越小,腐蚀的单位越小,图片越接近原图
|
||||
// Mat dilation_kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(2, 2));
|
||||
Mat dilation_kernel = OcrNDArrayUtils.uint8ArrayToMat(new byte[][]{{1, 1}, {1, 1}});
|
||||
/**
|
||||
* 膨胀说明: 图像的一部分区域与指定的核进行卷积, 求核的最`大`值并赋值给指定区域。 膨胀可以理解为图像中`高亮区域`的'领域扩大'。
|
||||
* 意思是高亮部分会侵蚀不是高亮的部分,使高亮部分越来越多。
|
||||
*/
|
||||
Imgproc.dilate(srcMat, mask, dilation_kernel);
|
||||
//destination Matrix
|
||||
Scalar scalar = new Scalar(255);
|
||||
Core.multiply(mask, scalar, newMask);
|
||||
// release Mat
|
||||
mask.release();
|
||||
srcMat.release();
|
||||
dilation_kernel.release();
|
||||
} else {
|
||||
Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation);
|
||||
//destination Matrix
|
||||
Scalar scalar = new Scalar(255);
|
||||
Core.multiply(srcMat, scalar, newMask);
|
||||
// release Mat
|
||||
srcMat.release();
|
||||
}
|
||||
|
||||
NDArray boxes = boxes_from_bitmap(manager, pred, newMask);
|
||||
|
||||
//boxes[:, :, 0] = boxes[:, :, 0] / ratio_w
|
||||
NDArray boxes1 = boxes.get(":, :, 0").div(ratio_w);
|
||||
boxes.set(new NDIndex(":, :, 0"), boxes1);
|
||||
//boxes[:, :, 1] = boxes[:, :, 1] / ratio_h
|
||||
NDArray boxes2 = boxes.get(":, :, 1").div(ratio_h);
|
||||
boxes.set(new NDIndex(":, :, 1"), boxes2);
|
||||
|
||||
NDList dt_boxes = this.filter_tag_det_res(boxes);
|
||||
|
||||
dt_boxes.detach();
|
||||
|
||||
// release Mat
|
||||
newMask.release();
|
||||
|
||||
return dt_boxes;
|
||||
}
|
||||
|
||||
|
||||
private NDList filter_tag_det_res(NDArray dt_boxes) {
|
||||
NDList boxesList = new NDList();
|
||||
|
||||
int num = (int) dt_boxes.getShape().get(0);
|
||||
for (int i = 0; i < num; i++) {
|
||||
NDArray box = dt_boxes.get(i);
|
||||
box = order_points_clockwise(box);
|
||||
box = clip_det_res(box);
|
||||
float[] box0 = box.get(0).toFloatArray();
|
||||
float[] box1 = box.get(1).toFloatArray();
|
||||
float[] box3 = box.get(3).toFloatArray();
|
||||
int rect_width = (int) Math.sqrt(Math.pow(box1[0] - box0[0], 2) + Math.pow(box1[1] - box0[1], 2));
|
||||
int rect_height = (int) Math.sqrt(Math.pow(box3[0] - box0[0], 2) + Math.pow(box3[1] - box0[1], 2));
|
||||
if (rect_width <= 3 || rect_height <= 3)
|
||||
continue;
|
||||
boxesList.add(box);
|
||||
}
|
||||
|
||||
return boxesList;
|
||||
}
|
||||
|
||||
private NDArray clip_det_res(NDArray points) {
|
||||
for (int i = 0; i < points.getShape().get(0); i++) {
|
||||
int value = Math.max((int) points.get(i, 0).toFloatArray()[0], 0);
|
||||
value = Math.min(value, img_width - 1);
|
||||
points.set(new NDIndex(i + ",0"), value);
|
||||
value = Math.max((int) points.get(i, 1).toFloatArray()[0], 0);
|
||||
value = Math.min(value, img_height - 1);
|
||||
points.set(new NDIndex(i + ",1"), value);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* sort the points based on their x-coordinates
|
||||
* 顺时针
|
||||
*
|
||||
* @param pts
|
||||
* @return
|
||||
*/
|
||||
|
||||
private NDArray order_points_clockwise(NDArray pts) {
|
||||
NDList list = new NDList();
|
||||
long[] indexes = pts.get(":, 0").argSort().toLongArray();
|
||||
|
||||
// grab the left-most and right-most points from the sorted
|
||||
// x-roodinate points
|
||||
Shape s1 = pts.getShape();
|
||||
NDArray leftMost1 = pts.get(indexes[0] + ",:");
|
||||
NDArray leftMost2 = pts.get(indexes[1] + ",:");
|
||||
NDArray leftMost = leftMost1.concat(leftMost2).reshape(2, 2);
|
||||
NDArray rightMost1 = pts.get(indexes[2] + ",:");
|
||||
NDArray rightMost2 = pts.get(indexes[3] + ",:");
|
||||
NDArray rightMost = rightMost1.concat(rightMost2).reshape(2, 2);
|
||||
|
||||
// now, sort the left-most coordinates according to their
|
||||
// y-coordinates so we can grab the top-left and bottom-left
|
||||
// points, respectively
|
||||
indexes = leftMost.get(":, 1").argSort().toLongArray();
|
||||
NDArray lt = leftMost.get(indexes[0] + ",:");
|
||||
NDArray lb = leftMost.get(indexes[1] + ",:");
|
||||
indexes = rightMost.get(":, 1").argSort().toLongArray();
|
||||
NDArray rt = rightMost.get(indexes[0] + ",:");
|
||||
NDArray rb = rightMost.get(indexes[1] + ",:");
|
||||
|
||||
list.add(lt);
|
||||
list.add(rt);
|
||||
list.add(rb);
|
||||
list.add(lb);
|
||||
|
||||
NDArray rect = NDArrays.concat(list).reshape(4, 2);
|
||||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get boxes from the binarized image predicted by DB
|
||||
*
|
||||
* @param manager
|
||||
* @param pred the binarized image predicted by DB.
|
||||
* @param bitmap new 'pred' after threshold filtering.
|
||||
*/
|
||||
private NDArray boxes_from_bitmap(NDManager manager, NDArray pred, Mat bitmap) {
|
||||
int dest_height = (int) pred.getShape().get(0);
|
||||
int dest_width = (int) pred.getShape().get(1);
|
||||
int height = bitmap.rows();
|
||||
int width = bitmap.cols();
|
||||
|
||||
List<MatOfPoint> contours = new ArrayList<>();
|
||||
Mat hierarchy = new Mat();
|
||||
// 寻找轮廓
|
||||
Imgproc.findContours(
|
||||
bitmap,
|
||||
contours,
|
||||
hierarchy,
|
||||
Imgproc.RETR_LIST,
|
||||
Imgproc.CHAIN_APPROX_SIMPLE);
|
||||
|
||||
int num_contours = Math.min(contours.size(), max_candidates);
|
||||
NDList boxList = new NDList();
|
||||
float[] scores = new float[num_contours];
|
||||
|
||||
for (int index = 0; index < num_contours; index++) {
|
||||
MatOfPoint contour = contours.get(index);
|
||||
MatOfPoint2f newContour = new MatOfPoint2f(contour.toArray());
|
||||
float[][] pointsArr = new float[4][2];
|
||||
int sside = get_mini_boxes(newContour, pointsArr);
|
||||
if (sside < this.min_size)
|
||||
continue;
|
||||
NDArray points = manager.create(pointsArr);
|
||||
float score = box_score_fast(manager, pred, points);
|
||||
if (score < this.box_thresh)
|
||||
continue;
|
||||
|
||||
NDArray box = unclip(manager, points); // TODO get_mini_boxes(box)
|
||||
|
||||
// box[:, 0] = np.clip(np.round(box[:, 0] / width * dest_width), 0, dest_width)
|
||||
NDArray boxes1 = box.get(":,0").div(width).mul(dest_width).round().clip(0, dest_width);
|
||||
box.set(new NDIndex(":, 0"), boxes1);
|
||||
// box[:, 1] = np.clip(np.round(box[:, 1] / height * dest_height), 0, dest_height)
|
||||
NDArray boxes2 = box.get(":,1").div(height).mul(dest_height).round().clip(0, dest_height);
|
||||
box.set(new NDIndex(":, 1"), boxes2);
|
||||
|
||||
boxList.add(box);
|
||||
scores[index] = score;
|
||||
|
||||
// release memory
|
||||
contour.release();
|
||||
newContour.release();
|
||||
}
|
||||
|
||||
NDArray boxes = NDArrays.stack(boxList);
|
||||
|
||||
// release
|
||||
hierarchy.release();
|
||||
|
||||
return boxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shrink or expand the boxaccording to 'unclip_ratio'
|
||||
*
|
||||
* @param points The predicted box.
|
||||
* @return uncliped box
|
||||
*/
|
||||
private NDArray unclip(NDManager manager, NDArray points) {
|
||||
points = order_points_clockwise(points);
|
||||
float[] pointsArr = points.toFloatArray();
|
||||
float[] lt = java.util.Arrays.copyOfRange(pointsArr, 0, 2);
|
||||
float[] lb = java.util.Arrays.copyOfRange(pointsArr, 6, 8);
|
||||
|
||||
float[] rt = java.util.Arrays.copyOfRange(pointsArr, 2, 4);
|
||||
float[] rb = java.util.Arrays.copyOfRange(pointsArr, 4, 6);
|
||||
|
||||
float width = distance(lt, rt);
|
||||
float height = distance(lt, lb);
|
||||
|
||||
if (width > height) {
|
||||
float k = (lt[1] - rt[1]) / (lt[0] - rt[0]); // y = k * x + b
|
||||
|
||||
float delta_dis = height;
|
||||
float delta_x = (float) Math.sqrt((delta_dis * delta_dis) / (k * k + 1));
|
||||
float delta_y = Math.abs(k * delta_x);
|
||||
|
||||
if (k > 0) {
|
||||
pointsArr[0] = lt[0] - delta_x + delta_y;
|
||||
pointsArr[1] = lt[1] - delta_y - delta_x;
|
||||
pointsArr[2] = rt[0] + delta_x + delta_y;
|
||||
pointsArr[3] = rt[1] + delta_y - delta_x;
|
||||
|
||||
pointsArr[4] = rb[0] + delta_x - delta_y;
|
||||
pointsArr[5] = rb[1] + delta_y + delta_x;
|
||||
pointsArr[6] = lb[0] - delta_x - delta_y;
|
||||
pointsArr[7] = lb[1] - delta_y + delta_x;
|
||||
} else {
|
||||
pointsArr[0] = lt[0] - delta_x - delta_y;
|
||||
pointsArr[1] = lt[1] + delta_y - delta_x;
|
||||
pointsArr[2] = rt[0] + delta_x - delta_y;
|
||||
pointsArr[3] = rt[1] - delta_y - delta_x;
|
||||
|
||||
pointsArr[4] = rb[0] + delta_x + delta_y;
|
||||
pointsArr[5] = rb[1] - delta_y + delta_x;
|
||||
pointsArr[6] = lb[0] - delta_x + delta_y;
|
||||
pointsArr[7] = lb[1] + delta_y + delta_x;
|
||||
}
|
||||
} else {
|
||||
float k = (lt[1] - rt[1]) / (lt[0] - rt[0]); // y = k * x + b
|
||||
|
||||
float delta_dis = width;
|
||||
float delta_y = (float) Math.sqrt((delta_dis * delta_dis) / (k * k + 1));
|
||||
float delta_x = Math.abs(k * delta_y);
|
||||
|
||||
if (k > 0) {
|
||||
pointsArr[0] = lt[0] + delta_x - delta_y;
|
||||
pointsArr[1] = lt[1] - delta_y - delta_x;
|
||||
pointsArr[2] = rt[0] + delta_x + delta_y;
|
||||
pointsArr[3] = rt[1] - delta_y + delta_x;
|
||||
|
||||
pointsArr[4] = rb[0] - delta_x + delta_y;
|
||||
pointsArr[5] = rb[1] + delta_y + delta_x;
|
||||
pointsArr[6] = lb[0] - delta_x - delta_y;
|
||||
pointsArr[7] = lb[1] + delta_y - delta_x;
|
||||
} else {
|
||||
pointsArr[0] = lt[0] - delta_x - delta_y;
|
||||
pointsArr[1] = lt[1] - delta_y + delta_x;
|
||||
pointsArr[2] = rt[0] - delta_x + delta_y;
|
||||
pointsArr[3] = rt[1] - delta_y - delta_x;
|
||||
|
||||
pointsArr[4] = rb[0] + delta_x + delta_y;
|
||||
pointsArr[5] = rb[1] + delta_y - delta_x;
|
||||
pointsArr[6] = lb[0] + delta_x - delta_y;
|
||||
pointsArr[7] = lb[1] + delta_y + delta_x;
|
||||
}
|
||||
}
|
||||
points = manager.create(pointsArr).reshape(4, 2);
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
private float distance(float[] point1, float[] point2) {
|
||||
float disX = point1[0] - point2[0];
|
||||
float disY = point1[1] - point2[1];
|
||||
float dis = (float) Math.sqrt(disX * disX + disY * disY);
|
||||
return dis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get boxes from the contour or box.
|
||||
*
|
||||
* @param contour The predicted contour.
|
||||
* @param pointsArr The predicted box.
|
||||
* @return smaller side of box
|
||||
*/
|
||||
private int get_mini_boxes(MatOfPoint2f contour, float[][] pointsArr) {
|
||||
// https://blog.csdn.net/qq_37385726/article/details/82313558
|
||||
// bounding_box[1] - rect 返回矩形的长和宽
|
||||
RotatedRect rect = Imgproc.minAreaRect(contour);
|
||||
Mat points = new Mat();
|
||||
Imgproc.boxPoints(rect, points);
|
||||
|
||||
float[][] fourPoints = new float[4][2];
|
||||
for (int row = 0; row < 4; row++) {
|
||||
fourPoints[row][0] = (float) points.get(row, 0)[0];
|
||||
fourPoints[row][1] = (float) points.get(row, 1)[0];
|
||||
}
|
||||
|
||||
float[] tmpPoint = new float[2];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = i + 1; j < 4; j++) {
|
||||
if (fourPoints[j][0] < fourPoints[i][0]) {
|
||||
tmpPoint[0] = fourPoints[i][0];
|
||||
tmpPoint[1] = fourPoints[i][1];
|
||||
fourPoints[i][0] = fourPoints[j][0];
|
||||
fourPoints[i][1] = fourPoints[j][1];
|
||||
fourPoints[j][0] = tmpPoint[0];
|
||||
fourPoints[j][1] = tmpPoint[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int index_1 = 0;
|
||||
int index_2 = 1;
|
||||
int index_3 = 2;
|
||||
int index_4 = 3;
|
||||
|
||||
if (fourPoints[1][1] > fourPoints[0][1]) {
|
||||
index_1 = 0;
|
||||
index_4 = 1;
|
||||
} else {
|
||||
index_1 = 1;
|
||||
index_4 = 0;
|
||||
}
|
||||
|
||||
if (fourPoints[3][1] > fourPoints[2][1]) {
|
||||
index_2 = 2;
|
||||
index_3 = 3;
|
||||
} else {
|
||||
index_2 = 3;
|
||||
index_3 = 2;
|
||||
}
|
||||
|
||||
pointsArr[0] = fourPoints[index_1];
|
||||
pointsArr[1] = fourPoints[index_2];
|
||||
pointsArr[2] = fourPoints[index_3];
|
||||
pointsArr[3] = fourPoints[index_4];
|
||||
|
||||
int height = rect.boundingRect().height;
|
||||
int width = rect.boundingRect().width;
|
||||
int sside = Math.min(height, width);
|
||||
|
||||
// release
|
||||
points.release();
|
||||
|
||||
return sside;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the score of box.
|
||||
*
|
||||
* @param bitmap The binarized image predicted by DB.
|
||||
* @param points The predicted box
|
||||
* @return
|
||||
*/
|
||||
private float box_score_fast(NDManager manager, NDArray bitmap, NDArray points) {
|
||||
NDArray box = points.get(":");
|
||||
long h = bitmap.getShape().get(0);
|
||||
long w = bitmap.getShape().get(1);
|
||||
// xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int), 0, w - 1)
|
||||
int xmin = box.get(":, 0").min().floor().clip(0, w - 1).toType(DataType.INT32, true).toIntArray()[0];
|
||||
int xmax = box.get(":, 0").max().ceil().clip(0, w - 1).toType(DataType.INT32, true).toIntArray()[0];
|
||||
int ymin = box.get(":, 1").min().floor().clip(0, h - 1).toType(DataType.INT32, true).toIntArray()[0];
|
||||
int ymax = box.get(":, 1").max().ceil().clip(0, h - 1).toType(DataType.INT32, true).toIntArray()[0];
|
||||
|
||||
NDArray mask = manager.zeros(new Shape(ymax - ymin + 1, xmax - xmin + 1), DataType.UINT8);
|
||||
|
||||
box.set(new NDIndex(":, 0"), box.get(":, 0").sub(xmin));
|
||||
box.set(new NDIndex(":, 1"), box.get(":, 1").sub(ymin));
|
||||
|
||||
//mask - convert from NDArray to Mat
|
||||
Mat maskMat = OcrNDArrayUtils.uint8NDArrayToMat(mask);
|
||||
|
||||
//mask - convert from NDArray to Mat - 4 rows, 2 cols
|
||||
Mat boxMat = OcrNDArrayUtils.floatNDArrayToMat(box, CvType.CV_32S);
|
||||
|
||||
// boxMat.reshape(1, new int[]{1, 4, 2});
|
||||
List<MatOfPoint> pts = new ArrayList<>();
|
||||
MatOfPoint matOfPoint = OcrNDArrayUtils.matToMatOfPoint(boxMat); // new MatOfPoint(boxMat);
|
||||
pts.add(matOfPoint);
|
||||
Imgproc.fillPoly(maskMat, pts, new Scalar(1));
|
||||
|
||||
|
||||
NDArray subBitMap = bitmap.get(ymin + ":" + (ymax + 1) + "," + xmin + ":" + (xmax + 1));
|
||||
Mat bitMapMat = OcrNDArrayUtils.floatNDArrayToMat(subBitMap);
|
||||
|
||||
Scalar score = Core.mean(bitMapMat, maskMat);
|
||||
float scoreValue = (float) score.val[0];
|
||||
// release
|
||||
maskMat.release();
|
||||
boxMat.release();
|
||||
bitMapMat.release();
|
||||
|
||||
return scoreValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NDList processInput(TranslatorContext ctx, Image input) {
|
||||
NDArray img = input.toNDArray(ctx.getNDManager());
|
||||
int h = input.getHeight();
|
||||
int w = input.getWidth();
|
||||
img_height = h;
|
||||
img_width = w;
|
||||
|
||||
// limit the max side
|
||||
float ratio = 1.0f;
|
||||
if (Math.max(h, w) > limit_side_len) {
|
||||
if (h > w) {
|
||||
ratio = (float) limit_side_len / (float) h;
|
||||
} else {
|
||||
ratio = (float) limit_side_len / (float) w;
|
||||
}
|
||||
}
|
||||
|
||||
int resize_h = (int) (h * ratio);
|
||||
int resize_w = (int) (w * ratio);
|
||||
|
||||
resize_h = Math.round((float) resize_h / 32f) * 32;
|
||||
resize_w = Math.round((float) resize_w / 32f) * 32;
|
||||
|
||||
ratio_h = resize_h / (float) h;
|
||||
ratio_w = resize_w / (float) w;
|
||||
|
||||
img = NDImageUtils.resize(img, resize_w, resize_h);
|
||||
|
||||
img = NDImageUtils.toTensor(img);
|
||||
|
||||
img =
|
||||
NDImageUtils.normalize(
|
||||
img,
|
||||
new float[]{0.485f, 0.456f, 0.406f},
|
||||
new float[]{0.229f, 0.224f, 0.225f});
|
||||
|
||||
img = img.expandDims(0);
|
||||
|
||||
return new NDList(img);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Batchifier getBatchifier() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package cn.smartjavaai.ocr.translator;
|
||||
|
||||
import ai.djl.Model;
|
||||
import ai.djl.modality.cv.Image;
|
||||
import ai.djl.modality.cv.util.NDImageUtils;
|
||||
import ai.djl.ndarray.NDArray;
|
||||
import ai.djl.ndarray.NDList;
|
||||
import ai.djl.ndarray.index.NDIndex;
|
||||
import ai.djl.ndarray.types.DataType;
|
||||
import ai.djl.ndarray.types.Shape;
|
||||
import ai.djl.translate.Batchifier;
|
||||
import ai.djl.translate.Translator;
|
||||
import ai.djl.translate.TranslatorContext;
|
||||
import ai.djl.util.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文字识别前后处理
|
||||
*
|
||||
*/
|
||||
public class PaddleOCRV4WordRecTranslator implements Translator<Image, String> {
|
||||
private List<String> table;
|
||||
private final boolean use_space_char;
|
||||
|
||||
public PaddleOCRV4WordRecTranslator(Map<String, ?> arguments) {
|
||||
use_space_char =
|
||||
arguments.containsKey("use_space_char")
|
||||
? Boolean.parseBoolean(arguments.get("use_space_char").toString())
|
||||
: true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepare(TranslatorContext ctx) throws IOException {
|
||||
Model model = ctx.getModel();
|
||||
try (InputStream is = model.getArtifact("dict.txt").openStream()) {
|
||||
table = Utils.readLines(is, true);
|
||||
table.add(0, "blank");
|
||||
if(use_space_char){
|
||||
table.add(" ");
|
||||
table.add(" ");
|
||||
}
|
||||
else{
|
||||
table.add("");
|
||||
table.add("");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String processOutput(TranslatorContext ctx, NDList list) throws IOException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
NDArray tokens = list.singletonOrThrow();
|
||||
|
||||
long[] indices = tokens.get(0).argMax(1).toLongArray();
|
||||
boolean[] selection = new boolean[indices.length];
|
||||
Arrays.fill(selection, true);
|
||||
for (int i = 1; i < indices.length; i++) {
|
||||
if (indices[i] == indices[i - 1]) {
|
||||
selection[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 字符置信度
|
||||
// float[] probs = new float[indices.length];
|
||||
// for (int row = 0; row < indices.length; row++) {
|
||||
// NDArray value = tokens.get(0).get(new NDIndex(""+ row +":" + (row + 1) +"," + indices[row] +":" + ( indices[row] + 1)));
|
||||
// probs[row] = value.toFloatArray()[0];
|
||||
// }
|
||||
|
||||
int lastIdx = 0;
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
if (selection[i] == true && indices[i] > 0 && !(i > 0 && indices[i] == lastIdx)) {
|
||||
sb.append(table.get((int) indices[i]));
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NDList processInput(TranslatorContext ctx, Image input) {
|
||||
NDArray img = input.toNDArray(ctx.getNDManager(), Image.Flag.COLOR);
|
||||
int imgC = 3;
|
||||
int imgH = 48;
|
||||
int imgW = 320;
|
||||
|
||||
float max_wh_ratio = (float) imgW / (float) imgH;
|
||||
|
||||
int h = input.getHeight();
|
||||
int w = input.getWidth();
|
||||
float wh_ratio = (float) w / (float) h;
|
||||
|
||||
max_wh_ratio = Math.max(max_wh_ratio,wh_ratio);
|
||||
imgW = (int)(imgH * max_wh_ratio);
|
||||
|
||||
int resized_w;
|
||||
if (Math.ceil(imgH * wh_ratio) > imgW) {
|
||||
resized_w = imgW;
|
||||
} else {
|
||||
resized_w = (int) (Math.ceil(imgH * wh_ratio));
|
||||
}
|
||||
NDArray resized_image = NDImageUtils.resize(img, resized_w, imgH);
|
||||
resized_image = resized_image.transpose(2, 0, 1).toType(DataType.FLOAT32,false);
|
||||
resized_image.divi(255f).subi(0.5f).divi(0.5f);
|
||||
NDArray padding_im = ctx.getNDManager().zeros(new Shape(imgC, imgH, imgW), DataType.FLOAT32);
|
||||
padding_im.set(new NDIndex(":,:,0:" + resized_w), resized_image);
|
||||
|
||||
padding_im = padding_im.flip(0);
|
||||
padding_im = padding_im.expandDims(0);
|
||||
return new NDList(padding_im);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Batchifier getBatchifier() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package cn.smartjavaai.ocr.utils;
|
||||
|
||||
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.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import cn.smartjavaai.common.utils.OpenCVUtils;
|
||||
import org.opencv.core.Mat;
|
||||
import org.opencv.core.Point;
|
||||
import org.opencv.core.Scalar;
|
||||
import org.opencv.imgproc.Imgproc;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 图像工具类
|
||||
*/
|
||||
public class ImageUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 保存BufferedImage图片
|
||||
*
|
||||
* @param img
|
||||
* @param name
|
||||
* @param path
|
||||
*/
|
||||
public static void saveImage(BufferedImage img, String name, String path) {
|
||||
Image djlImg = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(img)); // 支持多种图片格式,自动适配
|
||||
Path outputDir = Paths.get(path);
|
||||
Path imagePath = outputDir.resolve(name);
|
||||
// OpenJDK 不能保存 jpg 图片的 alpha channel
|
||||
try {
|
||||
djlImg.save(Files.newOutputStream(imagePath), "png");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存DJL图片
|
||||
*
|
||||
* @param img
|
||||
* @param name
|
||||
* @param path
|
||||
*/
|
||||
public static void saveImage(Image img, String name, String path) {
|
||||
Path outputDir = Paths.get(path);
|
||||
Path imagePath = outputDir.resolve(name);
|
||||
// OpenJDK 不能保存 jpg 图片的 alpha channel
|
||||
try {
|
||||
img.save(Files.newOutputStream(imagePath), "png");
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存图片,含检测框
|
||||
*
|
||||
* @param img
|
||||
* @param detection
|
||||
* @param name
|
||||
* @param path
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void saveBoundingBoxImage(
|
||||
Image img, DetectedObjects detection, String name, String path) throws IOException {
|
||||
// Make image copy with alpha channel because original image was jpg
|
||||
img.drawBoundingBoxes(detection);
|
||||
Path outputDir = Paths.get(path);
|
||||
Files.createDirectories(outputDir);
|
||||
Path imagePath = outputDir.resolve(name);
|
||||
// OpenJDK can't save jpg with alpha channel
|
||||
img.save(Files.newOutputStream(imagePath), "png");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 画检测框(有倾斜角)
|
||||
*
|
||||
* @param image
|
||||
* @param box
|
||||
*/
|
||||
public static void drawImageRect(BufferedImage image, NDArray box) {
|
||||
float[] points = box.toFloatArray();
|
||||
int[] xPoints = new int[5];
|
||||
int[] yPoints = new int[5];
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
xPoints[i] = (int) points[2 * i];
|
||||
yPoints[i] = (int) points[2 * i + 1];
|
||||
}
|
||||
xPoints[4] = xPoints[0];
|
||||
yPoints[4] = yPoints[0];
|
||||
|
||||
// 将绘制图像转换为Graphics2D
|
||||
Graphics2D g = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
g.setColor(new Color(0, 255, 0));
|
||||
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
|
||||
BasicStroke bStroke = new BasicStroke(4, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
|
||||
g.setStroke(bStroke);
|
||||
g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 画检测框(有倾斜角)和文本
|
||||
*
|
||||
* @param image
|
||||
* @param box
|
||||
* @param text
|
||||
*/
|
||||
public static void drawImageRectWithText(BufferedImage image, NDArray box, String text) {
|
||||
float[] points = box.toFloatArray();
|
||||
int[] xPoints = new int[5];
|
||||
int[] yPoints = new int[5];
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
xPoints[i] = (int) points[2 * i];
|
||||
yPoints[i] = (int) points[2 * i + 1];
|
||||
}
|
||||
xPoints[4] = xPoints[0];
|
||||
yPoints[4] = yPoints[0];
|
||||
|
||||
// 将绘制图像转换为Graphics2D
|
||||
Graphics2D g = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
int fontSize = 32;
|
||||
Font font = new Font("楷体", Font.PLAIN, fontSize);
|
||||
g.setFont(font);
|
||||
g.setColor(new Color(0, 0, 255));
|
||||
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
|
||||
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
|
||||
g.setStroke(bStroke);
|
||||
g.drawPolyline(xPoints, yPoints, 5); // xPoints, yPoints, nPoints
|
||||
g.drawString(text, xPoints[0], yPoints[0]);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 画检测框
|
||||
*
|
||||
* @param image
|
||||
* @param x
|
||||
* @param y
|
||||
* @param width
|
||||
* @param height
|
||||
*/
|
||||
public static void drawImageRect(BufferedImage image, int x, int y, int width, int height) {
|
||||
// 将绘制图像转换为Graphics2D
|
||||
Graphics2D g = (Graphics2D) image.getGraphics();
|
||||
try {
|
||||
g.setColor(new Color(0, 255, 0));
|
||||
// 声明画笔属性 :粗 细(单位像素)末端无修饰 折线处呈尖角
|
||||
BasicStroke bStroke = new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
|
||||
g.setStroke(bStroke);
|
||||
g.drawRect(x, y, width, height);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示文字
|
||||
*
|
||||
* @param image
|
||||
* @param text
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
public static void drawImageText(BufferedImage image, String text, int x, int y) {
|
||||
Graphics graphics = image.getGraphics();
|
||||
int fontSize = 32;
|
||||
Font font = new Font("楷体", Font.PLAIN, fontSize);
|
||||
try {
|
||||
graphics.setFont(font);
|
||||
graphics.setColor(new Color(0, 0, 255));
|
||||
int strWidth = graphics.getFontMetrics().stringWidth(text);
|
||||
graphics.drawString(text, x, y);
|
||||
} finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 画矩形
|
||||
*
|
||||
* @param mat
|
||||
* @param box
|
||||
*/
|
||||
public static void drawRect(Mat mat, DetectionResponse detectionResponse) {
|
||||
|
||||
for(DetectionRectangle detectionRectangle : detectionResponse.getRectangleList()){
|
||||
// 左上角点
|
||||
Point topLeft = new Point(detectionRectangle.getX(), detectionRectangle.getY());
|
||||
// 右下角点
|
||||
Point bottomRight = new Point(detectionRectangle.getX() + detectionRectangle.getWidth(), detectionRectangle.getY() + detectionRectangle.getHeight());
|
||||
// 绘制矩形(图像、左上角、右下角、颜色(BGR)、线宽)
|
||||
Imgproc.rectangle(mat, topLeft, bottomRight, new Scalar(0, 255, 0), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cn.smartjavaai.ocr.utils;
|
||||
|
||||
import ai.djl.modality.cv.Image;
|
||||
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.DetectionRectangle;
|
||||
import cn.smartjavaai.common.entity.DetectionResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author dwj
|
||||
* @date 2025/4/22
|
||||
*/
|
||||
@Slf4j
|
||||
public class OcrUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 转换为FaceDetectedResult
|
||||
* @param dt_boxes
|
||||
* @param img
|
||||
* @return
|
||||
*/
|
||||
public static DetectionResponse convertToDetectionResponse(NDList dt_boxes, Image img){
|
||||
if(Objects.isNull(dt_boxes) || dt_boxes.size() == 0){
|
||||
return null;
|
||||
}
|
||||
DetectionResponse detectionResponse = new DetectionResponse();
|
||||
List<DetectionRectangle> rectangleList = new ArrayList<DetectionRectangle>();
|
||||
for(NDArray box : dt_boxes){
|
||||
DetectionRectangle rectangle = new DetectionRectangle();
|
||||
float[] points = box.toFloatArray();
|
||||
log.info("points: {}", points);
|
||||
int x = (int)points[0];
|
||||
int y = (int)points[1];
|
||||
int width = new BigDecimal(points[4]).subtract(new BigDecimal(points[6])).intValue();
|
||||
int height = new BigDecimal(points[7]).subtract(new BigDecimal(points[1])).intValue();
|
||||
|
||||
// 修正边界,防止越界
|
||||
if (x < 0) x = 0;
|
||||
if (y < 0) y = 0;
|
||||
if (x + width > img.getWidth()) width = img.getWidth() - x;
|
||||
if (y + height > img.getHeight()) height = img.getHeight() - y;
|
||||
|
||||
rectangle.setX(x);
|
||||
rectangle.setY(y);
|
||||
rectangle.setHeight(height);
|
||||
rectangle.setWidth(width);
|
||||
rectangleList.add(rectangle);
|
||||
}
|
||||
detectionResponse.setRectangleList(rectangleList);
|
||||
return detectionResponse;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user