【人脸识别】 新增多种人脸识别模型

【底层优化】 支持自由选择 OpenCV 或 BufferedImage 作为图像引擎

【通用图像】 全部模型启用 Image 输入,支持各类图片格式与 Image 的互转

【模型管理】 优化模型生命周期,关闭后可重新创建

【人脸识别】 支持在人脸查询结果中绘制姓名标注

【人脸检测】 新增人脸裁剪功能

【修复】 修复若干已知问题,提升系统稳定性
This commit is contained in:
dengwenjie
2025-10-02 16:26:42 +08:00
parent 1b50e2b943
commit dfa8cf9bb4
133 changed files with 6635 additions and 3532 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>cn.smartjavaai</groupId>
<artifactId>smartjavaai-parent</artifactId>
<version>1.0.24</version>
<version>1.0.25</version>
</parent>
<artifactId>ocr</artifactId>
@@ -42,7 +42,7 @@
</dependency>
</dependencies>
<version>1.0.24</version>
<version>1.0.25</version>
<name>ocr</name>
<description>SmartJavaAI</description>
<url>https://github.com/geekwenjie/SmartJavaAI</url>

View File

@@ -1,5 +1,6 @@
package cn.smartjavaai.ocr.entity;
import ai.djl.modality.cv.Image;
import lombok.Data;
import java.util.ArrayList;
@@ -20,7 +21,7 @@ public class OcrInfo {
private String fullText;
private String base64Img;
private transient Image drawnImage;
public OcrInfo(List<List<OcrItem>> lineList, String fullText) {

View File

@@ -158,6 +158,7 @@ public class OcrModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -179,6 +180,7 @@ public class OcrModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -199,6 +201,7 @@ public class OcrModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -220,4 +223,61 @@ public class OcrModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
commonDetModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
commonDetModelMap.clear();
commonRecModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
commonRecModelMap.clear();
directionModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
directionModelMap.clear();
}
/**
* 移除缓存的检测模型
* @param modelEnum
*/
public static void removeDetModelFromCache(CommonDetModelEnum modelEnum) {
commonDetModelMap.remove(modelEnum);
}
/**
* 移除缓存的识别模型
* @param modelEnum
*/
public static void removeRecModelFromCache(CommonRecModelEnum modelEnum) {
commonRecModelMap.remove(modelEnum);
}
/**
* 移除缓存的方向分类模型
* @param modelEnum
*/
public static void removeDirectionModelFromCache(DirectionModelEnum modelEnum) {
directionModelMap.remove(modelEnum);
}
}

View File

@@ -4,9 +4,7 @@ import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.ocr.config.PlateDetModelConfig;
import cn.smartjavaai.ocr.config.PlateRecModelConfig;
import cn.smartjavaai.ocr.config.TableStructureConfig;
import cn.smartjavaai.ocr.enums.PlateDetModelEnum;
import cn.smartjavaai.ocr.enums.PlateRecModelEnum;
import cn.smartjavaai.ocr.enums.TableStructureModelEnum;
import cn.smartjavaai.ocr.enums.*;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.model.plate.CRNNPlateRecModel;
import cn.smartjavaai.ocr.model.plate.PlateDetModel;
@@ -133,6 +131,7 @@ public class PlateModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -153,6 +152,7 @@ public class PlateModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -165,4 +165,44 @@ public class PlateModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
detModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
detModelMap.clear();
recModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
recModelMap.clear();
}
/**
* 移除缓存的检测模型
* @param modelEnum
*/
public static void removeDetModelFromCache(PlateDetModelEnum modelEnum) {
detModelMap.remove(modelEnum);
}
/**
* 移除缓存的识别模型
* @param modelEnum
*/
public static void removeRecModelFromCache(PlateRecModelEnum modelEnum) {
recModelMap.remove(modelEnum);
}
}

View File

@@ -103,6 +103,7 @@ public class TableRecModelFactory {
throw new OcrException(e);
}
model.loadModel(config);
model.setFromFactory(true);
return model;
}
@@ -116,4 +117,26 @@ public class TableRecModelFactory {
log.debug("缓存目录:{}", Config.getCachePath());
}
/**
* 关闭所有已加载的模型
*/
public void closeAll() {
tableStructureModelMap.values().forEach(model -> {
try {
model.close();
} catch (Exception e) {
e.printStackTrace();
}
});
tableStructureModelMap.clear();
}
/**
* 移除缓存的模型
* @param modelEnum
*/
public static void removeFromCache(TableStructureModelEnum modelEnum) {
tableStructureModelMap.remove(modelEnum);
}
}

View File

@@ -27,6 +27,8 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default List<OcrBox> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -37,6 +39,7 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default List<OcrBox> detect(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -47,6 +50,7 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default List<OcrBox> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -74,15 +78,26 @@ public interface OcrCommonDetModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default BufferedImage detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制结果
* @param sourceImage
* @return
*/
default Image detectAndDraw(Image sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 文本检测(批量)
* @param imageList BufferedImage
* @return
*/
@Deprecated
default List<List<OcrBox>> batchDetect(List<BufferedImage> imageList) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -100,5 +115,9 @@ public interface OcrCommonDetModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -11,13 +11,15 @@ 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 cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.criteria.OcrCommonDetCriterialFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
import lombok.extern.slf4j.Slf4j;
@@ -81,12 +83,12 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
} catch (IOException e) {
throw new OcrException("无效的图片", e);
}
List<OcrBox> ocrBoxList = detect(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrBoxList;
}
@@ -103,16 +105,15 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
throw new OcrException("图像文件不存在");
}
try {
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
Image img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
List<OcrBox> boxList = detect(img);
if(Objects.isNull(boxList) || boxList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRect((Mat)img.getWrappedImage(), boxList);
Path output = Paths.get(outputPath);
log.debug("Saving to {}", output.toAbsolutePath().toString());
img.save(Files.newOutputStream(output), "png");
((Mat) img.getWrappedImage()).release();
OcrUtils.drawOcrDetResult(img, boxList, 12);
//4通道保存jpg会有问题
ImageUtils.save(img, Paths.get(outputPath), "png");
ImageUtils.releaseOpenCVMat(img);
} catch (IOException e) {
throw new OcrException(e);
}
@@ -121,12 +122,12 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
@Override
public List<OcrBox> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
List<OcrBox> ocrBoxList = detect(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrBoxList;
}
@@ -145,29 +146,30 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
@Override
public BufferedImage detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
List<OcrBox> ocrBoxList = detect(img);
if(Objects.isNull(ocrBoxList) || ocrBoxList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRect((Mat)img.getWrappedImage(), ocrBoxList);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 调用 save 方法将 Image 写入字节流
img.save(outputStream, "png");
// 将字节流转换为 BufferedImage
byte[] imageBytes = outputStream.toByteArray();
return ImageIO.read(new ByteArrayInputStream(imageBytes));
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
} finally {
if (img != null){
((Mat) img.getWrappedImage()).release();
}
OcrUtils.drawOcrDetResult(img, ocrBoxList, 12);
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
ImageUtils.releaseOpenCVMat(img);
return bufferedImage;
}
@Override
public Image detectAndDraw(Image sourceImage) {
List<OcrBox> ocrBoxList = detect(sourceImage);
if(Objects.isNull(ocrBoxList) || ocrBoxList.isEmpty()){
throw new OcrException("未检测到文字");
}
Image img = ImageUtils.copy(sourceImage);
OcrUtils.drawOcrDetResult(img, ocrBoxList, 12);
return img;
}
@Override
@@ -175,13 +177,13 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
List<Image> djlImageList = new ArrayList<>(imageList.size());
try {
for (BufferedImage bufferedImage : imageList) {
djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage)));
djlImageList.add(SmartImageFactory.getInstance().fromBufferedImage(bufferedImage));
}
return batchDetectDJLImage(djlImageList);
} catch (Exception e) {
throw new OcrException(e);
} finally {
djlImageList.forEach(image -> ((Mat)image.getWrappedImage()).release());
djlImageList.forEach(image -> ImageUtils.releaseOpenCVMat(image));
}
}
@@ -215,6 +217,10 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
}
}
@Override
public GenericObjectPool<Predictor<Image, NDList>> getPool() {
return detPredictorPool;
@@ -222,6 +228,9 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
@Override
public void close() throws Exception {
if (fromFactory) {
OcrModelFactory.removeDetModelFromCache(config.getModelEnum());
}
try {
if (detPredictorPool != null) {
detPredictorPool.close();
@@ -238,5 +247,15 @@ public class OcrCommonDetModelImpl implements OcrCommonDetModel{
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -12,7 +12,8 @@ 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 cn.smartjavaai.common.utils.DJLCommonUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import org.opencv.core.*;
import org.opencv.imgproc.Imgproc;
@@ -98,10 +99,10 @@ public class PPOCRDetTranslator implements Translator<Image, NDList> {
if (this.use_dilation) {
Mat mask = new Mat();
//convert from NDArray to Mat
Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation);
Mat srcMat = DJLCommonUtils.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}});
Mat dilation_kernel = OpenCVUtils.uint8ArrayToMat(new byte[][]{{1, 1}, {1, 1}});
/**
* 膨胀说明: 图像的一部分区域与指定的核进行卷积, 求核的最`大`值并赋值给指定区域。 膨胀可以理解为图像中`高亮区域`的'领域扩大'。
* 意思是高亮部分会侵蚀不是高亮的部分,使高亮部分越来越多。
@@ -115,7 +116,7 @@ public class PPOCRDetTranslator implements Translator<Image, NDList> {
srcMat.release();
dilation_kernel.release();
} else {
Mat srcMat = OcrNDArrayUtils.uint8NDArrayToMat(segmentation);
Mat srcMat = DJLCommonUtils.uint8NDArrayToMat(segmentation);
//destination Matrix
Scalar scalar = new Scalar(255);
Core.multiply(srcMat, scalar, newMask);
@@ -462,20 +463,20 @@ public class PPOCRDetTranslator implements Translator<Image, NDList> {
box.set(new NDIndex(":, 1"), box.get(":, 1").sub(ymin));
//mask - convert from NDArray to Mat
Mat maskMat = OcrNDArrayUtils.uint8NDArrayToMat(mask);
Mat maskMat = DJLCommonUtils.uint8NDArrayToMat(mask);
//mask - convert from NDArray to Mat - 4 rows, 2 cols
Mat boxMat = OcrNDArrayUtils.floatNDArrayToMat(box, CvType.CV_32S);
Mat boxMat = DJLCommonUtils.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);
MatOfPoint matOfPoint = OpenCVUtils.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);
Mat bitMapMat = DJLCommonUtils.floatNDArrayToMat(subBitMap);
Scalar score = Core.mean(bitMapMat, maskMat);
float scoreValue = (float) score.val[0];

View File

@@ -40,6 +40,7 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default List<OcrItem> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -50,6 +51,7 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default List<OcrItem> detect(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -60,6 +62,7 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default List<OcrItem> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -103,12 +106,26 @@ public interface OcrDirectionModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default BufferedImage detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制结果
* @param sourceImage
* @return
*/
default Image detectAndDraw(Image sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, DirectionInfo>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -1,44 +1,35 @@
package cn.smartjavaai.ocr.model.common.direction;
import ai.djl.Device;
import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
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 cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.entity.*;
import cn.smartjavaai.ocr.enums.AngleEnum;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.model.common.direction.criteria.DirectionCriteriaFactory;
import cn.smartjavaai.ocr.model.common.direction.translator.PpWordRotateTranslator;
import cn.smartjavaai.ocr.utils.OcrUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
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 javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.Collections;
@@ -63,6 +54,8 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
private OcrCommonDetModel textDetModel;
public static final int FONT_SIZE = 45;
@Override
public void loadModel(DirectionModelConfig config){
@@ -100,14 +93,12 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return detect(img);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
}finally {
if(img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -122,7 +113,7 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
if(Objects.isNull(boxeList) || boxeList.isEmpty()){
throw new OcrException("未检测到文本");
}
Mat srcMat = (Mat) image.getWrappedImage();
Mat srcMat = ImageUtils.toMat(image);
return detect(boxeList, srcMat);
}
@@ -171,7 +162,7 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
// }
@Override
public List<OcrItem> detect(List<OcrBox> boxList,Mat srcMat){
public List<OcrItem> detect(List<OcrBox> boxList, Mat srcMat){
if(Objects.isNull(boxList) || boxList.isEmpty()){
throw new OcrException("boxList为空");
}
@@ -189,32 +180,30 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
List<OcrItem> itemList = detect(img);
if(Objects.isNull(itemList) || itemList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRectWithText((Mat) img.getWrappedImage(), itemList);
Path output = Paths.get(outputPath);
log.debug("Saving to {}", output.toAbsolutePath().toString());
img.save(Files.newOutputStream(output), "png");
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawOcrResult(bufferedImage, itemList, FONT_SIZE);
log.debug("Saving to {}", outputPath);
BufferedImageUtils.saveImage(bufferedImage, outputPath);
} catch (IOException e) {
throw new OcrException(e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@Override
public List<OcrItem> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
List<OcrItem> ocrItemList = detect(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrItemList;
}
@@ -223,39 +212,41 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
if(Objects.isNull(imageData)){
throw new OcrException("图像无效");
}
Image img = null;
try {
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
return detect(image);
img = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new OcrException("错误的图像", e);
throw new RuntimeException(e);
}
List<OcrItem> ocrItemList = detect(img);
ImageUtils.releaseOpenCVMat(img);
return ocrItemList;
}
@Override
public BufferedImage detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
List<OcrItem> ocrItemList = detect(img);
if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){
throw new OcrException("未检测到文字");
}
OcrUtils.drawRectWithText((Mat) img.getWrappedImage(), ocrItemList);
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 调用 save 方法将 Image 写入字节流
img.save(outputStream, "png");
// 将字节流转换为 BufferedImage
byte[] imageBytes = outputStream.toByteArray();
return ImageIO.read(new ByteArrayInputStream(imageBytes));
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
} finally {
if (img != null){
((Mat) img.getWrappedImage()).release();
}
BufferedImage drawImage = BufferedImageUtils.copyBufferedImage(sourceImage);
OcrUtils.drawOcrResult(drawImage, ocrItemList, FONT_SIZE);
return drawImage;
}
@Override
public Image detectAndDraw(Image sourceImage) {
List<OcrItem> ocrItemList = detect(sourceImage);
if(Objects.isNull(ocrItemList) || ocrItemList.isEmpty()){
throw new OcrException("未检测到文字");
}
Image drawImage = ImageUtils.copy(sourceImage);
OcrUtils.drawOcrResult(drawImage, ocrItemList, FONT_SIZE);
return drawImage;
}
@Override
@@ -291,7 +282,7 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
//高宽比 > 1.5 纵向
if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) {
//旋转图片90度
subImg = OcrUtils.rotateImg(manager, subImg);
subImg = ImageUtils.rotateImg(manager, subImg);
isRotatedList.add(true);
imageList.add(subImg);
}else{
@@ -303,6 +294,8 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
}
List<List<OcrItem>> result = new ArrayList<>();
List<DirectionInfo> directionInfos = batchDetect(imageList);
//释放
imageList.forEach(image -> ImageUtils.releaseOpenCVMat(image));
if(CollectionUtils.isEmpty(directionInfos)){
throw new OcrException("方向检测失败");
}
@@ -378,6 +371,9 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
@Override
public void close() throws Exception {
if (fromFactory) {
OcrModelFactory.removeDirectionModelFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -393,4 +389,14 @@ public class PPOCRMobileV2ClsModel implements OcrDirectionModel {
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -47,6 +47,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default OcrInfo recognize(String imagePath, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,6 +67,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default OcrInfo recognize(BufferedImage image, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -76,6 +78,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default OcrInfo recognize(byte[] imageData, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -90,11 +93,22 @@ public interface OcrCommonRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 识别并绘制结果
* @param image
* @return
*/
@Deprecated
default OcrInfo recognizeAndDraw(Image image, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 识别并绘制结果
* @param sourceImage
* @return
*/
@Deprecated
default BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -103,6 +117,7 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default String recognizeAndDrawToBase64(byte[] imageData, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -112,10 +127,12 @@ public interface OcrCommonRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default OcrInfo recognizeAndDraw(byte[] imageData, int fontSize, OcrRecOptions options){
throw new UnsupportedOperationException("默认不支持该功能");
}
@Deprecated
default List<OcrInfo> batchRecognize(List<BufferedImage> imageList, OcrRecOptions options) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -128,4 +145,8 @@ public interface OcrCommonRecModel extends AutoCloseable{
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -4,7 +4,6 @@ import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDManager;
import ai.djl.repository.zoo.Criteria;
@@ -12,14 +11,16 @@ import ai.djl.repository.zoo.ModelNotFoundException;
import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.hutool.core.img.ImgUtil;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.OcrRecModelConfig;
import cn.smartjavaai.ocr.config.OcrRecOptions;
import cn.smartjavaai.ocr.entity.*;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel;
import cn.smartjavaai.ocr.model.common.recognize.criteria.OcrCommonRecCriterialFactory;
@@ -33,7 +34,6 @@ import org.opencv.core.Mat;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
@@ -95,14 +95,12 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return recognize(img, options);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
if (img != null) {
((Mat) img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -138,7 +136,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
//高宽比 > 1.5
if (subImg.getHeight() * 1.0 / subImg.getWidth() > 1.5) {
//旋转图片90度
subImg = OcrUtils.rotateImg(manager, subImg);
subImg = ImageUtils.rotateImg(manager, subImg);
//ImageUtils.saveImage(subImg, i + "rotate.png", "build/output");
}
imageList.add(subImg);
@@ -236,30 +234,31 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
if (!FileUtils.isFileExists(imagePath)) {
throw new OcrException("图像文件不存在");
}
Image img = null;
try {
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
OcrInfo ocrInfo = recognize(img, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
Mat wrappedImage = (Mat) img.getWrappedImage();
BufferedImage bufferedImage = OpenCVUtils.mat2Image(wrappedImage);
OcrUtils.drawRectWithText(bufferedImage, ocrInfo, fontSize);
ImageUtils.saveImage(bufferedImage, outputPath);
wrappedImage.release();
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawOcrResult(bufferedImage, ocrInfo, fontSize);
BufferedImageUtils.saveImage(bufferedImage, outputPath);
} catch (IOException e) {
throw new OcrException(e);
}finally {
ImageUtils.releaseOpenCVMat(img);
}
}
@Override
public OcrInfo recognize(BufferedImage image, OcrRecOptions options) {
if (!ImageUtils.isImageValid(image)) {
if (!BufferedImageUtils.isImageValid(image)) {
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
OcrInfo ocrInfo = recognize(img, options);
((Mat) img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return ocrInfo;
}
@@ -278,15 +277,15 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
@Override
public BufferedImage recognizeAndDraw(BufferedImage sourceImage, int fontSize, OcrRecOptions options) {
if (!ImageUtils.isImageValid(sourceImage)) {
if (!BufferedImageUtils.isImageValid(sourceImage)) {
throw new OcrException("图像无效");
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
OcrInfo ocrInfo = recognize(img, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
return sourceImage;
}
@@ -301,7 +300,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
try {
BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageData));
OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
return ImgUtil.toBase64(sourceImage, "png");
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
@@ -313,18 +312,21 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
if (Objects.isNull(imageData)) {
throw new OcrException("图像无效");
}
OcrInfo ocrInfo = recognize(imageData, options);
Image img = null;
try {
img = SmartImageFactory.getInstance().fromBytes(imageData);
} catch (IOException e) {
throw new RuntimeException(e);
}
OcrInfo ocrInfo = recognize(img, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
try {
BufferedImage sourceImage = ImageIO.read(new ByteArrayInputStream(imageData));
OcrUtils.drawRectWithText(sourceImage, ocrInfo, fontSize);
ocrInfo.setBase64Img(ImgUtil.toBase64(sourceImage, "png"));
return ocrInfo;
} catch (IOException e) {
throw new OcrException("导出图片失败", e);
}
//opencv中文乱码使用BufferedImage
BufferedImage sourceImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
ocrInfo.setDrawnImage(SmartImageFactory.getInstance().fromBufferedImage(sourceImage));
return ocrInfo;
}
@Override
@@ -332,13 +334,13 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
List<Image> djlImageList = new ArrayList<>(imageList.size());
try {
for (BufferedImage bufferedImage : imageList) {
djlImageList.add(ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(bufferedImage)));
djlImageList.add(SmartImageFactory.getInstance().fromBufferedImage(bufferedImage));
}
return batchRecognizeDJLImage(djlImageList, options);
} catch (Exception e) {
throw new OcrException(e);
} finally {
djlImageList.forEach(image -> ((Mat) image.getWrappedImage()).release());
djlImageList.forEach(image -> ImageUtils.releaseOpenCVMat(image));
}
}
@@ -370,7 +372,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
throw new OcrException("请配置方向模型");
}
List<Mat> matList = imageList.stream()
.map(image -> (Mat) image.getWrappedImage())
.map(image -> ImageUtils.toMat(image))
.collect(Collectors.toList());
List<List<OcrItem>> ocrItemList = directionModel.batchDetect(boxeList, matList);
if (CollectionUtils.isEmpty(ocrItemList) || ocrItemList.size() != imageList.size()) {
@@ -378,7 +380,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
allImageAlignList = new ArrayList<Image>();
for (int i = 0; i < ocrItemList.size(); i++) {
Mat srcMat = (Mat) imageList.get(i).getWrappedImage();
Mat srcMat = ImageUtils.toMat(imageList.get(i));
List<Image> imageAlignList = batchAlignWithDirection(ocrItemList.get(i), srcMat, manager);
// for(int j = 0; j < imageAlignList.size(); j++){
// ImageUtils.saveImage(imageAlignList.get(j),"dir-"+i+"-"+j+".png","/Users/xxx/Downloads/testing33");
@@ -387,10 +389,10 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
} else {
for (int i = 0; i < boxeList.size(); i++) {
Mat srcMat = (Mat) imageList.get(i).getWrappedImage();
Mat srcMat = ImageUtils.toMat(imageList.get(i));
List<Image> imageAlignList = batchAlign(boxeList.get(i), srcMat, manager);
// for(int j = 0; j < imageAlignList.size(); j++){
// ImageUtils.saveImage(imageAlignList.get(j),i+"-"+j+".png","/Users/xxx/Downloads/testing33");
// ImageUtils.saveImage(imageAlignList.get(j),i+"-"+j+".png","/Users/wenjie/Downloads/testing33");
// }
allImageAlignList.addAll(imageAlignList);
}
@@ -435,7 +437,7 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
try {
predictor = recPredictorPool.borrowObject();
List<String> textList = predictor.batchPredict(imageAlignList);
imageAlignList.forEach(subImg -> ((Mat) subImg.getWrappedImage()).release());
imageAlignList.forEach(subImg -> ImageUtils.releaseOpenCVMat(subImg));
return textList;
} catch (Exception e) {
throw new OcrException("OCR检测错误", e);
@@ -455,6 +457,19 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
}
}
@Override
public OcrInfo recognizeAndDraw(Image image, int fontSize, OcrRecOptions options) {
OcrInfo ocrInfo = recognize(image, options);
if (Objects.isNull(ocrInfo) || Objects.isNull(ocrInfo.getLineList()) || ocrInfo.getLineList().isEmpty()) {
throw new OcrException("未检测到文字");
}
BufferedImage sourceImage = ImageUtils.toBufferedImage(image);
OcrUtils.drawOcrResult(sourceImage, ocrInfo, fontSize);
ocrInfo.setDrawnImage(SmartImageFactory.getInstance().fromBufferedImage(sourceImage));
return ocrInfo;
}
@Override
public void setTextDetModel(OcrCommonDetModel detModel) {
this.textDetModel = detModel;
@@ -482,6 +497,9 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
@Override
public void close() throws Exception {
if (fromFactory) {
OcrModelFactory.removeRecModelFromCache(config.getRecModelEnum());
}
try {
if (recPredictorPool != null) {
recPredictorPool.close();
@@ -497,4 +515,14 @@ public class OcrCommonRecModelImpl implements OcrCommonRecModel {
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -4,7 +4,6 @@ import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
import ai.djl.inference.Predictor;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.repository.zoo.Criteria;
@@ -13,19 +12,18 @@ import ai.djl.repository.zoo.ModelZoo;
import ai.djl.repository.zoo.ZooModel;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.generator.UUIDGenerator;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.ocr.config.PlateDetModelConfig;
import cn.smartjavaai.ocr.config.PlateRecModelConfig;
import cn.smartjavaai.ocr.entity.PlateInfo;
import cn.smartjavaai.ocr.entity.PlateResult;
import cn.smartjavaai.ocr.enums.PlateType;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.model.plate.criteria.PlateDetCriterialFactory;
import cn.smartjavaai.ocr.model.plate.criteria.PlateRecCriterialFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
@@ -97,13 +95,13 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<List<PlateInfo>> plateResult = recognize(img);
return plateResult;
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -118,12 +116,12 @@ public class CRNNPlateRecModel implements PlateRecModel{
@Override
public R<List<PlateInfo>> recognize(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
R<List<PlateInfo>> plateResult = recognize(img);
((Mat)img.getWrappedImage()).release();
ImageUtils.releaseOpenCVMat(img);
return plateResult;
}
@@ -140,7 +138,7 @@ public class CRNNPlateRecModel implements PlateRecModel{
if(Objects.isNull(config.getPlateDetModel())){
return R.fail(R.Status.PARAM_ERROR.getCode(), "未指定车牌检测模型");
}
DetectedObjects detectedObjects = config.getPlateDetModel().detect(image);
DetectedObjects detectedObjects = config.getPlateDetModel().detectCore(image);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
@@ -151,23 +149,26 @@ public class CRNNPlateRecModel implements PlateRecModel{
for (PlateInfo plateInfo : plateInfoList){
DetectionRectangle detectionRectangle = plateInfo.getDetectionRectangle();
// Image subImage = image.getSubImage(detectionRectangle.getX(), detectionRectangle.getY(), detectionRectangle.getWidth(), detectionRectangle.getHeight());
Mat imageMat = ImageUtils.toMat(image);
//透视变换
Image subImage = OcrUtils.transformAndCrop((Mat)image.getWrappedImage(), plateInfo.getBox());
Mat subMat = OcrUtils.transformAndCropToMat(imageMat, plateInfo.getBox());
//双层车牌
if(plateInfo.getPlateType() == PlateType.DOUBLE){
Mat mergeImage = getSplitMerge((Mat)subImage.getWrappedImage());
subImage = ImageFactory.getInstance().fromImage(mergeImage);
subMat = getSplitMerge(subMat);
}
Image subImage = SmartImageFactory.getInstance().fromMat(subMat);
PlateResult plateResult = predictor.predict(subImage);
if(Objects.nonNull(plateResult)){
plateInfo.setPlateNumber(plateResult.getPlateNo());
plateInfo.setPlateColor(plateResult.getPlateColor());
}
ImageUtils.releaseOpenCVMat(subImage);
}
return R.ok(plateInfoList);
} catch (Exception e) {
throw new OcrException("车牌识别错误", e);
}finally {
if (predictor != null) {
try {
recPredictorPool.returnObject(predictor); //归还
@@ -247,14 +248,12 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromInputStream(inputStream);
img = SmartImageFactory.getInstance().fromInputStream(inputStream);
return recognize(img);
} catch (IOException e) {
throw new OcrException("无效图片输入流", e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -265,7 +264,7 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<List<PlateInfo>> plateResult = recognize(img);
if(!plateResult.isSuccess()){
return R.fail(plateResult.getCode(), plateResult.getMessage());
@@ -273,22 +272,20 @@ public class CRNNPlateRecModel implements PlateRecModel{
if(CollectionUtils.isEmpty(plateResult.getData())){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
BufferedImage bufferedImage = OpenCVUtils.mat2Image((Mat)img.getWrappedImage());
BufferedImage bufferedImage = ImageUtils.toBufferedImage(img);
OcrUtils.drawPlateInfo(bufferedImage, plateResult.getData());
ImageIO.write(bufferedImage, "png", new File(outputPath));
return R.ok();
} catch (IOException e) {
throw new OcrException(e);
} finally {
if (img != null){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@Override
public R<BufferedImage> recognizeAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
try {
@@ -306,6 +303,25 @@ public class CRNNPlateRecModel implements PlateRecModel{
}
}
@Override
public R<Image> recognizeAndDraw(Image image) {
try {
R<List<PlateInfo>> plateResult = recognize(image);
if(!plateResult.isSuccess()){
return R.fail(plateResult.getCode(), plateResult.getMessage());
}
if(CollectionUtils.isEmpty(plateResult.getData())){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
//opencv中文乱码使用BufferedImage
BufferedImage sourceImage = ImageUtils.toBufferedImage(image);
OcrUtils.drawPlateInfo(sourceImage, plateResult.getData());
Image drawImage = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
return R.ok(drawImage);
} catch (Exception e) {
throw new OcrException("导出图片失败", e);
}
}
@Override
public GenericObjectPool<Predictor<Image, PlateResult>> getPool() {
@@ -314,6 +330,9 @@ public class CRNNPlateRecModel implements PlateRecModel{
@Override
public void close() throws Exception {
if (fromFactory) {
PlateModelFactory.removeRecModelFromCache(config.getModelEnum());
}
try {
if (recPredictorPool != null) {
recPredictorPool.close();
@@ -329,4 +348,14 @@ public class CRNNPlateRecModel implements PlateRecModel{
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -32,6 +32,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -42,6 +43,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param inputStream
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(InputStream inputStream) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -52,6 +54,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<List<PlateInfo>> detectBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -62,6 +65,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -72,6 +76,7 @@ public interface PlateDetModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default R<List<PlateInfo>> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -81,7 +86,17 @@ public interface PlateDetModel extends AutoCloseable{
* @param image DJL Image
* @return
*/
default DetectedObjects detect(Image image){
default DetectedObjects detectCore(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 车牌检测
* @param image DJL Image
* @return
*/
default R<List<PlateInfo>> detect(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -100,16 +115,28 @@ public interface PlateDetModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<BufferedImage> detectAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
/**
* 检测并绘制结果
* @param image
* @return
*/
default Image detectAndDraw(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, DetectedObjects>> getPool(){
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -31,6 +31,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -41,6 +42,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param inputStream
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(InputStream inputStream) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -51,6 +53,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param base64Image
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognizeBase64(String base64Image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -61,6 +64,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param image BufferedImage
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(BufferedImage image) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -71,6 +75,7 @@ public interface PlateRecModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default R<List<PlateInfo>> recognize(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -107,12 +112,21 @@ public interface PlateRecModel extends AutoCloseable{
* @param sourceImage
* @return
*/
@Deprecated
default R<BufferedImage> recognizeAndDraw(BufferedImage sourceImage){
throw new UnsupportedOperationException("默认不支持该功能");
}
default R<Image> recognizeAndDraw(Image image){
throw new UnsupportedOperationException("默认不支持该功能");
}
default GenericObjectPool<Predictor<Image, PlateResult>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -4,7 +4,6 @@ import ai.djl.MalformedModelException;
import ai.djl.engine.Engine;
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;
@@ -12,17 +11,17 @@ 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 cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.Base64ImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.config.PlateDetModelConfig;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.entity.PlateInfo;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.model.common.detect.criteria.OcrCommonDetCriterialFactory;
import cn.smartjavaai.ocr.model.plate.criteria.PlateDetCriterialFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
@@ -90,17 +89,15 @@ public class Yolov5PlateDetModel implements PlateDetModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
R<List<PlateInfo>> plateInfoList = detect(img);
return plateInfoList;
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
ImageUtils.releaseOpenCVMat(img);
}
DetectedObjects detectedObjects = detect(img);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, img);
((Mat)img.getWrappedImage()).release();
return R.ok(plateInfoList);
}
@Override
@@ -114,17 +111,13 @@ public class Yolov5PlateDetModel implements PlateDetModel{
@Override
public R<List<PlateInfo>> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
DetectedObjects detectedObjects = detect(img);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, img);
((Mat)img.getWrappedImage()).release();
return R.ok(plateInfoList);
Image img = SmartImageFactory.getInstance().fromBufferedImage(image);
R<List<PlateInfo>> plateInfoList = detect(img);
ImageUtils.releaseOpenCVMat(img);
return plateInfoList;
}
@Override
@@ -136,7 +129,7 @@ public class Yolov5PlateDetModel implements PlateDetModel{
}
@Override
public DetectedObjects detect(Image image) {
public DetectedObjects detectCore(Image image) {
Predictor<Image, DetectedObjects> predictor = null;
try {
predictor = detPredictorPool.borrowObject();
@@ -164,14 +157,15 @@ public class Yolov5PlateDetModel implements PlateDetModel{
if(Objects.isNull(inputStream)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
Image img = ImageFactory.getInstance().fromInputStream(inputStream);
DetectedObjects detection = detect(img);
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detection, img);
((Mat)img.getWrappedImage()).release();
return R.ok(plateInfoList);
img = SmartImageFactory.getInstance().fromInputStream(inputStream);
R<List<PlateInfo>> plateInfoList = detect(img);
return plateInfoList;
} catch (IOException e) {
throw new OcrException("无效图片输入流", e);
} finally {
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -181,8 +175,8 @@ public class Yolov5PlateDetModel implements PlateDetModel{
return R.fail(R.Status.FILE_NOT_FOUND);
}
try {
Image img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detectedObjects = detect(img);
Image img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
DetectedObjects detectedObjects = detectCore(img);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -198,11 +192,11 @@ public class Yolov5PlateDetModel implements PlateDetModel{
@Override
public R<BufferedImage> detectAndDraw(BufferedImage sourceImage) {
if(!ImageUtils.isImageValid(sourceImage)){
if(!BufferedImageUtils.isImageValid(sourceImage)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(sourceImage));
DetectedObjects detectedObjects = detect(img);
Image img = SmartImageFactory.getInstance().fromBufferedImage(sourceImage);
DetectedObjects detectedObjects = detectCore(img);
if(Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_FACE_DETECTED);
}
@@ -219,6 +213,27 @@ public class Yolov5PlateDetModel implements PlateDetModel{
}
}
@Override
public R<List<PlateInfo>> detect(Image image) {
DetectedObjects detectedObjects = detectCore(image);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
return R.fail(R.Status.NO_OBJECT_DETECTED);
}
List<PlateInfo> plateInfoList = OcrUtils.convertToPlateInfo(detectedObjects, image);
return R.ok(plateInfoList);
}
@Override
public Image detectAndDraw(Image image) {
DetectedObjects detectedObjects = detectCore(image);
if (Objects.isNull(detectedObjects) || detectedObjects.getNumberOfObjects() == 0){
throw new OcrException("未检测到车牌");
}
Image img = ImageUtils.copy(image);
img.drawBoundingBoxes(detectedObjects);
return img;
}
@Override
public GenericObjectPool<Predictor<Image, DetectedObjects>> getPool() {
return detPredictorPool;
@@ -226,6 +241,9 @@ public class Yolov5PlateDetModel implements PlateDetModel{
@Override
public void close() throws Exception {
if (fromFactory) {
PlateModelFactory.removeDetModelFromCache(config.getModelEnum());
}
try {
if (detPredictorPool != null) {
detPredictorPool.close();
@@ -241,4 +259,14 @@ public class Yolov5PlateDetModel implements PlateDetModel{
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -11,8 +11,10 @@ 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 cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.pool.PredictorFactory;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
@@ -22,6 +24,8 @@ import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.entity.OcrItem;
import cn.smartjavaai.ocr.entity.TableStructureResult;
import cn.smartjavaai.ocr.exception.OcrException;
import cn.smartjavaai.ocr.factory.PlateModelFactory;
import cn.smartjavaai.ocr.factory.TableRecModelFactory;
import cn.smartjavaai.ocr.model.table.criteria.StructureCriteriaFactory;
import cn.smartjavaai.ocr.utils.OcrUtils;
import lombok.extern.slf4j.Slf4j;
@@ -49,8 +53,11 @@ public class CommonTableStructureModel implements TableStructureModel{
private GenericObjectPool<Predictor<Image, TableStructureResult>> predictorPool;
private TableStructureConfig config;
@Override
public void loadModel(TableStructureConfig config) {
this.config = config;
if(StringUtils.isBlank(config.getModelPath())){
throw new OcrException("modelPath is null");
}
@@ -74,19 +81,17 @@ public class CommonTableStructureModel implements TableStructureModel{
@Override
public R<TableStructureResult> detect(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
img = SmartImageFactory.getInstance().fromBufferedImage(image);
return detect(img);
} catch (Exception e) {
throw new OcrException(e);
} finally {
if(Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -97,14 +102,12 @@ public class CommonTableStructureModel implements TableStructureModel{
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return detect(img);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
if (Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -153,6 +156,9 @@ public class CommonTableStructureModel implements TableStructureModel{
@Override
public void close() throws Exception {
if (fromFactory) {
TableRecModelFactory.removeFromCache(config.getModelEnum());
}
try {
if (predictorPool != null) {
predictorPool.close();
@@ -168,4 +174,14 @@ public class CommonTableStructureModel implements TableStructureModel{
log.warn("关闭 model 失败", e);
}
}
private boolean fromFactory = false;
@Override
public void setFromFactory(boolean fromFactory) {
this.fromFactory = fromFactory;
}
public boolean isFromFactory() {
return fromFactory;
}
}

View File

@@ -1,18 +1,12 @@
package cn.smartjavaai.ocr.model.table;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.output.Rectangle;
import ai.djl.translate.TranslateException;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.DetectionRectangle;
import cn.smartjavaai.common.entity.R;
import cn.smartjavaai.common.utils.BufferedImageUtils;
import cn.smartjavaai.common.utils.FileUtils;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.ocr.config.OcrRecModelConfig;
import cn.smartjavaai.ocr.config.OcrRecOptions;
import cn.smartjavaai.ocr.entity.OcrBox;
import cn.smartjavaai.ocr.entity.OcrInfo;
@@ -28,7 +22,6 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.opencv.core.Mat;
import javax.imageio.ImageIO;
import java.awt.*;
@@ -107,19 +100,17 @@ public class TableRecognizer {
* @return
*/
public R<TableStructureResult> recognize(BufferedImage image) {
if(!ImageUtils.isImageValid(image)){
if(!BufferedImageUtils.isImageValid(image)){
return R.fail(R.Status.INVALID_IMAGE);
}
Image img = null;
try {
img = ImageFactory.getInstance().fromImage(OpenCVUtils.image2Mat(image));
img = SmartImageFactory.getInstance().fromBufferedImage(image);
return recognize(img);
} catch (Exception e) {
throw new OcrException(e);
} finally {
if(Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -134,14 +125,12 @@ public class TableRecognizer {
}
Image img = null;
try {
img = ImageFactory.getInstance().fromFile(Paths.get(imagePath));
img = SmartImageFactory.getInstance().fromFile(Paths.get(imagePath));
return recognize(img);
} catch (IOException e) {
throw new OcrException("无效的图片", e);
} finally {
if (Objects.nonNull(img)){
((Mat)img.getWrappedImage()).release();
}
ImageUtils.releaseOpenCVMat(img);
}
}
@@ -176,9 +165,13 @@ public class TableRecognizer {
for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){
OcrItem item = tableStructureResult.getOcrItemList().get(i);
DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle();
ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED);
BufferedImageUtils.drawRectAndText(image, detectionRectangle, i + "", Color.RED);
}
try {
BufferedImageUtils.saveImage(image, savePath);
} catch (IOException e) {
throw new OcrException(e);
}
ImageUtils.saveImage(image, savePath);
}
@@ -195,7 +188,25 @@ public class TableRecognizer {
for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){
OcrItem item = tableStructureResult.getOcrItemList().get(i);
DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle();
ImageUtils.drawImageRectWithText(image, detectionRectangle, i + "", Color.RED);
BufferedImageUtils.drawRectAndText(image, detectionRectangle, i + "", Color.RED);
}
return image;
}
/**
* 绘制表格
* @param tableStructureResult
* @param image
* @return
*/
public Image drawTable(TableStructureResult tableStructureResult, Image image){
if(Objects.isNull(tableStructureResult) || CollectionUtils.isEmpty(tableStructureResult.getTableTagList())){
throw new OcrException("表格结构为空");
}
for (int i = 0; i < tableStructureResult.getOcrItemList().size(); i++){
OcrItem item = tableStructureResult.getOcrItemList().get(i);
DetectionRectangle detectionRectangle = item.getOcrBox().toDetectionRectangle();
ImageUtils.drawRectAndText(image, detectionRectangle, i + "");
}
return image;
}

View File

@@ -31,6 +31,7 @@ public interface TableStructureModel extends AutoCloseable{
* @param image
* @return
*/
@Deprecated
default R<TableStructureResult> detect(BufferedImage image){
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -40,6 +41,7 @@ public interface TableStructureModel extends AutoCloseable{
* @param imagePath 图片路径
* @return
*/
@Deprecated
default R<TableStructureResult> detect(String imagePath) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -49,6 +51,7 @@ public interface TableStructureModel extends AutoCloseable{
* @param imageData 图片字节数组
* @return
*/
@Deprecated
default R<TableStructureResult> detect(byte[] imageData) {
throw new UnsupportedOperationException("默认不支持该功能");
}
@@ -66,4 +69,10 @@ public interface TableStructureModel extends AutoCloseable{
default GenericObjectPool<Predictor<Image, TableStructureResult>> getPool() {
throw new UnsupportedOperationException("默认不支持该功能");
}
default void setFromFactory(boolean fromFactory){
throw new UnsupportedOperationException("默认不支持该功能");
}
}

View File

@@ -1,228 +0,0 @@
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;
}
}

View File

@@ -1,7 +1,6 @@
package cn.smartjavaai.ocr.utils;
import ai.djl.modality.cv.Image;
import ai.djl.modality.cv.ImageFactory;
import ai.djl.modality.cv.output.BoundingBox;
import ai.djl.modality.cv.output.DetectedObjects;
import ai.djl.modality.cv.output.Landmark;
@@ -10,16 +9,14 @@ import ai.djl.ndarray.NDArray;
import ai.djl.ndarray.NDList;
import ai.djl.ndarray.NDManager;
import ai.djl.opencv.OpenCVImageFactory;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.entity.*;
import cn.smartjavaai.common.entity.Point;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.common.utils.OpenCVUtils;
import cn.smartjavaai.common.utils.PointUtils;
import cn.smartjavaai.common.utils.*;
import cn.smartjavaai.ocr.entity.*;
import cn.smartjavaai.ocr.entity.RotatedBox;
import cn.smartjavaai.ocr.enums.AngleEnum;
import cn.smartjavaai.ocr.enums.PlateType;
import cn.smartjavaai.ocr.opencv.OcrNDArrayUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.opencv.core.Mat;
@@ -28,7 +25,6 @@ import org.opencv.imgproc.Imgproc;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.math.BigDecimal;
import java.util.*;
import java.util.List;
@@ -79,33 +75,6 @@ public class OcrUtils {
/**
* 图片旋转
*
* @param manager
* @param image
* @return
*/
public static Image rotateImg(NDManager manager, Image image) {
NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), 1);
return ImageFactory.getInstance().fromNDArray(rotated);
}
/**
* 逆时针旋转图片
*
* @param image
* @param times
* @return
*/
public static Image rotateImg(Image image, int times) {
try (NDManager manager = NDManager.newBaseManager()) {
NDArray rotated = NDImageUtils.rotate90(image.toNDArray(manager), times);
return OpenCVImageFactory.getInstance().fromNDArray(rotated);
}
}
/**
* 逆时针旋转图片
*
@@ -199,66 +168,21 @@ public class OcrUtils {
/**
* 透视变换 + 裁剪
* @param srcMat
* @param landMarks
* @return
*/
public static Image transformAndCrop(Mat srcMat, List<ai.djl.modality.cv.output.Point> landMarks){
if (landMarks == null || landMarks.size() != 4) {
throw new IllegalArgumentException("必须提供4个关键点");
}
// 步骤 1排序为 左上、右上、右下、左下
List<ai.djl.modality.cv.output.Point> ordered = PointUtils.orderPoints(landMarks);
ai.djl.modality.cv.output.Point lt = ordered.get(0);
ai.djl.modality.cv.output.Point rt = ordered.get(1);
ai.djl.modality.cv.output.Point rb = ordered.get(2);
ai.djl.modality.cv.output.Point lb = ordered.get(3);
// 步骤 2计算目标图像尺寸宽、高
int img_crop_width = (int) Math.max(
PointUtils.distance(lt, rt),
PointUtils.distance(rb, lb)
);
int img_crop_height = (int) Math.max(
PointUtils.distance(lt, lb),
PointUtils.distance(rt, rb)
);
// 步骤 3构造目标坐标点
List<ai.djl.modality.cv.output.Point> dstPoints = Arrays.asList(
new ai.djl.modality.cv.output.Point(0, 0),
new ai.djl.modality.cv.output.Point(img_crop_width, 0),
new ai.djl.modality.cv.output.Point(img_crop_width, img_crop_height),
new ai.djl.modality.cv.output.Point(0, img_crop_height)
);
// 步骤 4透视变换
Mat srcPoint2f = OcrNDArrayUtils.toMat(ordered);
Mat dstPoint2f = OcrNDArrayUtils.toMat(dstPoints);
Mat cvMat = OpenCVUtils.perspectiveTransform(srcMat, srcPoint2f, dstPoint2f);
// 步骤 5转为 DJL Image + 裁剪
Image subImg = OpenCVImageFactory.getInstance().fromImage(cvMat);
subImg = subImg.getSubImage(0, 0, img_crop_width, img_crop_height);
// 释放资源
cvMat.release();
srcPoint2f.release();
dstPoint2f.release();
return subImg;
}
/**
* 透视变换+裁剪
* @param srcMat
* @param box
* @return
*/
public static Image transformAndCrop(Mat srcMat, OcrBox box){
Mat subImg = transformAndCropToMat(srcMat, box);
return SmartImageFactory.getInstance().fromMat(subImg);
}
/**
* 透视变换 + 裁剪
* @param srcMat
* @param box
* @return
*/
public static Mat transformAndCropToMat(Mat srcMat, OcrBox box){
float[] pointsArr = box.toFloatArray();
float[] lt = java.util.Arrays.copyOfRange(pointsArr, 0, 2);
float[] rt = java.util.Arrays.copyOfRange(pointsArr, 2, 4);
@@ -276,18 +200,15 @@ public class OcrUtils {
dstPoints.add(new ai.djl.modality.cv.output.Point(img_crop_width, 0));
dstPoints.add(new ai.djl.modality.cv.output.Point(img_crop_width, img_crop_height));
dstPoints.add(new ai.djl.modality.cv.output.Point(0, img_crop_height));
Mat srcPoint2f = OcrNDArrayUtils.toMat(srcPoints);
Mat dstPoint2f = OcrNDArrayUtils.toMat(dstPoints);
Mat srcPoint2f = DJLCommonUtils.toMat(srcPoints);
Mat dstPoint2f = DJLCommonUtils.toMat(dstPoints);
//透视变换
Mat cvMat = OpenCVUtils.perspectiveTransform(srcMat, srcPoint2f, dstPoint2f);
Image subImg = OpenCVImageFactory.getInstance().fromImage(cvMat);
//ImageUtils.saveImage(subImg, i + ".png", "build/output");
//变换后裁剪
subImg = subImg.getSubImage(0, 0, img_crop_width, img_crop_height);
cvMat.release();
srcPoint2f.release();
dstPoint2f.release();
return subImg;
Mat result = OpenCVUtils.getSubImage(cvMat, 0, 0, img_crop_width, img_crop_height);
cvMat.release();
return result;
}
/**
@@ -307,65 +228,116 @@ public class OcrUtils {
/**
* 绘制文本框及文本
* @param image
* @param ocrInfo
* 将 OCR 结果转换为多边形标签列表
* @param ocrItemList OCR 识别结果
* @return PolygonLabel 列表
*/
public static void drawRectWithText(BufferedImage image, OcrInfo ocrInfo, int fontSize) {
// 将绘制图像转换为Graphics2D
Graphics2D g = (Graphics2D) image.getGraphics();
try {
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);
List<OcrItem> ocrItemList = ocrInfo.getOcrItemList();
if(CollectionUtils.isNotEmpty(ocrInfo.getLineList())){
ocrItemList = ocrInfo.flattenLines();
}
for(OcrItem item : ocrItemList){
OcrBox box = item.getOcrBox();
int[] xPoints = {
(int)box.getTopLeft().getX(),
(int)box.getTopRight().getX(),
(int)box.getBottomRight().getX(),
(int)box.getBottomLeft().getX(),
(int)box.getTopLeft().getX()
};
int[] yPoints = {
(int)box.getTopLeft().getY(),
(int)box.getTopRight().getY(),
(int)box.getBottomRight().getY(),
(int)box.getBottomLeft().getY(),
(int)box.getTopLeft().getY()
};
g.drawPolyline(xPoints, yPoints, 5);
g.drawString(item.getText(), xPoints[0], yPoints[0]);
}
} finally {
g.dispose();
public static List<PolygonLabel> toPolygonLabelList(List<OcrItem> ocrItemList) {
List<PolygonLabel> polygonLabelList = new ArrayList<>();
if (ocrItemList == null || ocrItemList.isEmpty()) {
return polygonLabelList;
}
for (OcrItem item : ocrItemList) {
if (item.getOcrBox() == null) continue;
List<cn.smartjavaai.common.entity.Point> points = Arrays.asList(
item.getOcrBox().getTopLeft(),
item.getOcrBox().getTopRight(),
item.getOcrBox().getBottomRight(),
item.getOcrBox().getBottomLeft()
);
String text = null;
//角度
if(item.getAngle() != null){
text = item.getAngle().getValue();
}else{
text = item.getText();
}
PolygonLabel label = new PolygonLabel(points, text);
polygonLabelList.add(label);
}
return polygonLabelList;
}
/**
* 将 OCR 结果转换为多边形标签列表
* @param boxList
* @return PolygonLabel 列表
*/
public static List<PolygonLabel> ocrBoxtoPolygonLabelList(List<OcrBox> boxList) {
List<PolygonLabel> polygonLabelList = new ArrayList<>();
if (boxList == null || boxList.isEmpty()) {
return polygonLabelList;
}
for (OcrBox item : boxList) {
List<cn.smartjavaai.common.entity.Point> points = Arrays.asList(
item.getTopLeft(),
item.getTopRight(),
item.getBottomRight(),
item.getBottomLeft()
);
PolygonLabel label = new PolygonLabel(points);
polygonLabelList.add(label);
}
return polygonLabelList;
}
/**
* OCR 结果绘制
*/
public static void drawOcrResult(BufferedImage image, OcrInfo ocrInfo, int fontSize) {
List<OcrItem> ocrItemList = ocrInfo.getOcrItemList();
if (CollectionUtils.isNotEmpty(ocrInfo.getLineList())) {
ocrItemList = ocrInfo.flattenLines();
}
List<PolygonLabel> polygonLabelList = toPolygonLabelList(ocrItemList);
BufferedImageUtils.drawPolygonWithText(image, polygonLabelList, fontSize);
}
/**
* OCR 结果绘制
*/
public static void drawOcrResult(BufferedImage image, List<OcrItem> ocrItemList, int fontSize) {
List<PolygonLabel> polygonLabelList = toPolygonLabelList(ocrItemList);
BufferedImageUtils.drawPolygonWithText(image, polygonLabelList, fontSize);
}
/**
* OCR 结果绘制
*/
public static void drawOcrDetResult(Image image, List<OcrBox> ocrBoxList, int fontSize) {
List<PolygonLabel> polygonLabelList = ocrBoxtoPolygonLabelList(ocrBoxList);
ImageUtils.drawPolygonWithText(image, polygonLabelList, fontSize);
}
/**
* OCR 结果绘制
*/
public static void drawOcrResult(Image image, List<OcrItem> ocrItemList, int fontSize) {
List<PolygonLabel> polygonLabelList = toPolygonLabelList(ocrItemList);
ImageUtils.drawPolygonWithText(image, polygonLabelList, fontSize);
}
/**
* 绘制文本框及文本
* @param srcMat
* @param itemList
*/
public static void drawRectWithText(Mat srcMat, List<OcrItem> itemList) {
for(OcrItem item : itemList){
OcrBox ocrBox = item.getOcrBox();
Imgproc.line(srcMat, ocrBox.getTopLeft().toCvPoint(), ocrBox.getTopRight().toCvPoint(), new Scalar(0, 255, 0), 1);
Imgproc.line(srcMat, ocrBox.getTopRight().toCvPoint(), ocrBox.getBottomRight().toCvPoint(), new Scalar(0, 255, 0),1);
Imgproc.line(srcMat, ocrBox.getBottomRight().toCvPoint(), ocrBox.getBottomLeft().toCvPoint(), new Scalar(0, 255, 0),1);
Imgproc.line(srcMat, ocrBox.getBottomLeft().toCvPoint(), ocrBox.getTopLeft().toCvPoint(), new Scalar(0, 255, 0), 1);
// 中文乱码
Imgproc.putText(srcMat, item.getAngle().getValue(), ocrBox.getTopLeft().toCvPoint(), Imgproc.FONT_HERSHEY_SCRIPT_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1);
}
}
// public static void drawRectWithText(Mat srcMat, List<OcrItem> itemList) {
// for(OcrItem item : itemList){
// OcrBox ocrBox = item.getOcrBox();
// Imgproc.line(srcMat, ocrBox.getTopLeft().toCvPoint(), ocrBox.getTopRight().toCvPoint(), new Scalar(0, 255, 0), 1);
// Imgproc.line(srcMat, ocrBox.getTopRight().toCvPoint(), ocrBox.getBottomRight().toCvPoint(), new Scalar(0, 255, 0),1);
// Imgproc.line(srcMat, ocrBox.getBottomRight().toCvPoint(), ocrBox.getBottomLeft().toCvPoint(), new Scalar(0, 255, 0),1);
// Imgproc.line(srcMat, ocrBox.getBottomLeft().toCvPoint(), ocrBox.getTopLeft().toCvPoint(), new Scalar(0, 255, 0), 1);
// // 中文乱码
// Imgproc.putText(srcMat, item.getAngle().getValue(), ocrBox.getTopLeft().toCvPoint(), Imgproc.FONT_HERSHEY_SIMPLEX, 1.0, new Scalar(0, 255, 0), 1);
// }
// }
public static List<PlateInfo> convertToPlateInfo(DetectedObjects detectedObjects, Image image) {
List<PlateInfo> plateInfoList = new ArrayList<>();
@@ -414,7 +386,7 @@ public class OcrUtils {
Imgproc.line(srcMat, ocrBox.getBottomRight().toCvPoint(), ocrBox.getBottomLeft().toCvPoint(), new Scalar(0, 0, 255),1);
Imgproc.line(srcMat, ocrBox.getBottomLeft().toCvPoint(), ocrBox.getTopLeft().toCvPoint(), new Scalar(0, 0, 255), 1);
// 中文乱码
ImageUtils.putTextWithBackground(srcMat, plateInfo.getPlateNumber() + " " + plateInfo.getPlateColor(), ocrBox.getTopLeft().toCvPoint(), new Scalar(255, 255, 255), new Scalar(0, 0, 0), 1);
OpenCVUtils.putTextWithBackground(srcMat, plateInfo.getPlateNumber() + " " + plateInfo.getPlateColor(), ocrBox.getTopLeft().toCvPoint(), new Scalar(255, 255, 255), new Scalar(0, 0, 0), 1);
}
}
@@ -424,33 +396,19 @@ public class OcrUtils {
public static void drawPlateInfo(BufferedImage image, List<PlateInfo> plateInfoList) {
// 将绘制图像转换为Graphics2D
Graphics2D graphics = (Graphics2D) image.getGraphics();
try {
graphics.setColor(Color.RED);// 边框颜色
graphics.setStroke(new BasicStroke(2)); // 线宽2像素
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON); // 抗锯齿
int stroke = 2;
for(PlateInfo plateInfo : plateInfoList){
DetectionRectangle rectangle = plateInfo.getDetectionRectangle();
graphics.setColor(Color.RED);// 边框颜色
//绘制车牌框
graphics.drawRect(rectangle.getX(), rectangle.getY(), rectangle.getWidth(), rectangle.getHeight());
graphics.setColor(Color.BLACK);// 字体颜色
ImageUtils.drawText(graphics, plateInfo.getPlateNumber() + " " + plateInfo.getPlateColor(), rectangle.getX(), rectangle.getY(), stroke, 4);
OcrBox ocrBox = plateInfo.getBox();
//绘制关键点
graphics.setColor(Color.BLUE);
graphics.drawRect((int)ocrBox.getTopLeft().getX(), (int)ocrBox.getTopLeft().getY(), 2, 2);
graphics.setColor(Color.GREEN);
graphics.drawRect((int)ocrBox.getTopRight().getX(), (int)ocrBox.getTopRight().getY(), 2, 2);
graphics.setColor(Color.RED);
graphics.drawRect((int)ocrBox.getBottomLeft().getX(), (int)ocrBox.getBottomLeft().getY(), 2, 2);
graphics.setColor(Color.CYAN);
graphics.drawRect((int)ocrBox.getBottomRight().getX(), (int)ocrBox.getBottomRight().getY(), 2, 2);
}
} finally {
graphics.dispose();
for(PlateInfo plateInfo : plateInfoList){
DetectionRectangle rectangle = plateInfo.getDetectionRectangle();
OcrBox ocrBox = plateInfo.getBox();
String text = plateInfo.getPlateNumber() + " " + plateInfo.getPlateColor();
BufferedImageUtils.drawRectAndText(graphics, rectangle, text, Color.BLACK);
List<Point> keyPoints = new ArrayList<Point>();
keyPoints.add(ocrBox.getTopLeft());
keyPoints.add(ocrBox.getTopRight());
keyPoints.add(ocrBox.getBottomRight());
keyPoints.add(ocrBox.getBottomLeft());
BufferedImageUtils.drawKeyPoints(graphics, keyPoints, Color.GREEN);
}
graphics.dispose();
}