新增身份证识别能力

This commit is contained in:
dengwenjie
2026-03-29 15:28:18 +08:00
parent c8cda3f240
commit 8a6f671703
29 changed files with 2241 additions and 137 deletions

View File

@@ -12,9 +12,9 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smartjavaai.version>1.1.1</smartjavaai.version>
<smartjavaai.version>1.1.2</smartjavaai.version>
<!--如果打包运行需要替换成你的main-->
<exec.mainClass>smartai.examples.ocr.common.OcrRecognizeDemo</exec.mainClass>
<exec.mainClass>smartai.examples.ocr.common.OcrDetectionDemo</exec.mainClass>
<javacv.version>1.5.10</javacv.version>
@@ -90,19 +90,33 @@
<groupId>cn.smartjavaai</groupId>
<artifactId>ocr</artifactId>
<exclusions>
<exclusion>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
</exclusion>
<!-- <exclusion>-->
<!-- <groupId>org.openpnp</groupId>-->
<!-- <artifactId>opencv</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>com.microsoft.onnxruntime</groupId>-->
<!-- <artifactId>onnxruntime</artifactId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <groupId>org.bytedeco</groupId>-->
<!-- <artifactId>javacv</artifactId>-->
<!-- </exclusion>-->
</exclusions>
</dependency>
<dependency>
<groupId>com.microsoft.onnxruntime</groupId>
<artifactId>onnxruntime</artifactId>
<version>1.20.0</version>
<scope>runtime</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.microsoft.onnxruntime</groupId>-->
<!-- <artifactId>onnxruntime</artifactId>-->
<!-- <version>1.16.3</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.openpnp</groupId>-->
<!-- <artifactId>opencv</artifactId>-->
<!-- <version>3.4.2-2</version>-->
<!-- </dependency>-->
<dependency>
@@ -113,79 +127,10 @@
</dependency>
<!-- windows平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.windows-x86_64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.windows-x86_64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.windows-x86_64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.windows-x86_64}</classifier>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.windows-x86_64}</classifier>
<version>2.7.1</version>
<scope>runtime</scope>
</dependency>
<!-- linux x86 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.linux-x86_64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.linux-x86_64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.linux-x86_64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.linux-x86_64}</classifier>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.linux-x86_64}</classifier>
<version>2.7.1</version>
<scope>runtime</scope>
</dependency>
<!-- linux aarch64 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
@@ -223,42 +168,16 @@
<scope>runtime</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>ai.djl.pytorch</groupId>-->
<!-- <artifactId>pytorch-native-cpu-precxx11</artifactId>-->
<!-- <classifier>linux-aarch64</classifier>-->
<!-- <version>2.5.1</version>-->
<!-- <scope>runtime</scope>-->
<!-- </dependency>-->
<!-- macOS M系列 平台 (保留对应平台的配置,可以减小包大小)-->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacpp</artifactId>
<version>${javacv.version}</version>
<classifier>${javacv.platform.macosx-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg</artifactId>
<version>6.1.1-1.5.10</version>
<classifier>${javacv.platform.macosx-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>openblas</artifactId>
<version>0.3.26-1.5.10</version>
<classifier>${javacv.platform.macosx-arm64}</classifier>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-1.5.10</version>
<classifier>${javacv.platform.macosx-arm64}</classifier>
</dependency>
<dependency>
<groupId>ai.djl.pytorch</groupId>
<artifactId>pytorch-native-cpu</artifactId>
<classifier>${djl.platform.osx-aarch64}</classifier>
<version>2.7.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>

View File

@@ -0,0 +1,216 @@
package smartai.examples.ocr.idcard;
import ai.djl.modality.cv.Image;
import ai.djl.util.JsonUtils;
import cn.smartjavaai.common.config.Config;
import cn.smartjavaai.common.cv.SmartImageFactory;
import cn.smartjavaai.common.enums.DeviceEnum;
import cn.smartjavaai.common.utils.ImageUtils;
import cn.smartjavaai.ocr.config.DirectionModelConfig;
import cn.smartjavaai.ocr.config.OcrDetModelConfig;
import cn.smartjavaai.ocr.config.OcrRecModelConfig;
import cn.smartjavaai.ocr.config.OcrRecOptions;
import cn.smartjavaai.ocr.entity.IdCardBackInfo;
import cn.smartjavaai.ocr.entity.IdCardFrontInfo;
import cn.smartjavaai.ocr.entity.IdCardInfo;
import cn.smartjavaai.ocr.entity.OcrInfo;
import cn.smartjavaai.ocr.enums.CommonDetModelEnum;
import cn.smartjavaai.ocr.enums.CommonRecModelEnum;
import cn.smartjavaai.ocr.enums.DirectionModelEnum;
import cn.smartjavaai.ocr.factory.OcrModelFactory;
import cn.smartjavaai.ocr.idcard.DefaultIdCardRecognizer;
import cn.smartjavaai.ocr.idcard.IdCardPreprocessListener;
import cn.smartjavaai.ocr.model.common.detect.OcrCommonDetModel;
import cn.smartjavaai.ocr.model.common.direction.OcrDirectionModel;
import cn.smartjavaai.ocr.model.common.recognize.OcrCommonRecModel;
import lombok.extern.slf4j.Slf4j;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* 身份证识别 demo
* 使用说明:
* 1、先下载 OCR 模型
* 2、把下面的模型路径改成你自己的本地路径
* 3、把身份证图片路径改成你自己的图片路径
* 4、优先运行 recognizeFront() / recognizeBack() 查看结构化结果
*
* 模型下载地址https://pan.baidu.com/s/1MLfd73Vjdpnuls9-oqc9uw?pwd=1234 提取码: 1234
* 开发文档http://doc.smartjavaai.cn/
* @author dwj
*/
@Slf4j
public class IdCardRecDemo {
// 设备类型
public static DeviceEnum device = DeviceEnum.CPU;
// 下载模型后,请替换成你自己的模型路径
private static final String DET_MODEL_PATH =
"/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_server_det_infer/PP-OCRv5_server_det.onnx";
private static final String REC_MODEL_PATH =
"/Users/wenjie/Documents/develop/model/ocr/PP-OCRv5_server_rec_infer/PP-OCRv5_server_rec.onnx";
private static final String DIRECTION_MODEL_PATH =
"/Users/wenjie/Documents/develop/model/ocr/PP-LCNet_x0_25_textline_ori_infer/PP-LCNet_x0_25_textline_ori_infer.onnx";
// 这里改成你自己的身份证图片路径
private static final String FRONT_IMAGE_PATH = "src/main/resources/idcard/idcard_front1.png";
private static final String BACK_IMAGE_PATH = "src/main/resources/idcard/idcard_back1.png";
@BeforeClass
public static void beforeAll() throws IOException {
// 修改缓存路径
// Config.setCachePath("/Users/xxx/smartjavaai_cache");
}
/**
* 获取文本检测模型
* @return
*/
public OcrCommonDetModel getDetectionModel() {
OcrDetModelConfig config = new OcrDetModelConfig();
// 文本检测模型,切换模型需要同时修改 modelEnum 及 modelPath
config.setModelEnum(CommonDetModelEnum.PP_OCR_V4_SERVER_DET_MODEL);
// 下载模型并替换本地路径
config.setDetModelPath(DET_MODEL_PATH);
config.setDevice(device);
return OcrModelFactory.getInstance().getDetModel(config);
}
/**
* 获取方向检测模型
* @return
*/
public OcrDirectionModel getDirectionModel() {
DirectionModelConfig directionModelConfig = new DirectionModelConfig();
// 行文本方向检测模型,切换模型需要同时修改 modelEnum 及 modelPath
directionModelConfig.setModelEnum(DirectionModelEnum.PP_LCNET_X0_25);
// 下载模型并替换本地路径
directionModelConfig.setModelPath(DIRECTION_MODEL_PATH);
directionModelConfig.setTextDetModel(getDetectionModel());
directionModelConfig.setDevice(device);
return OcrModelFactory.getInstance().getDirectionModel(directionModelConfig);
}
/**
* 获取 OCR 识别模型
* @return
*/
public OcrCommonRecModel getRecModel() {
OcrRecModelConfig recModelConfig = new OcrRecModelConfig();
// 文本识别模型,切换模型需要同时修改 modelEnum 及 modelPath
recModelConfig.setRecModelEnum(CommonRecModelEnum.PP_OCR_V5_MOBILE_REC_MODEL);
// 下载模型并替换本地路径
recModelConfig.setRecModelPath(REC_MODEL_PATH);
recModelConfig.setTextDetModel(getDetectionModel());
recModelConfig.setDevice(device);
return OcrModelFactory.getInstance().getRecModel(recModelConfig);
}
/**
* 创建身份证识别器
* @return
*/
public DefaultIdCardRecognizer getIdCardRecognizer() {
return new DefaultIdCardRecognizer()
.setRecModel(getRecModel())
// 身份证识别建议按行返回,方向矫正交给预处理逻辑处理
.setRecOptions(new OcrRecOptions(false, true))
.setDirectionModel(getDirectionModel())
.setEnablePreprocess(true);
}
/**
* 身份证正面识别
* 可识别:姓名、性别、民族、出生日期、住址、身份证号
*/
@Test
public void recognizeFront() {
try {
DefaultIdCardRecognizer recognizer = getIdCardRecognizer();
Image image = SmartImageFactory.getInstance().fromFile(FRONT_IMAGE_PATH);
IdCardFrontInfo result = recognizer.recognizeFront(image);
log.info("身份证正面识别结果:{}", JsonUtils.toJson(result));
} catch (Exception e) {
log.error("身份证正面识别失败", e);
}
}
/**
* 身份证反面识别
* 可识别:签发机关、有效期开始时间、有效期结束时间
*/
@Test
public void recognizeBack() {
try {
DefaultIdCardRecognizer recognizer = getIdCardRecognizer();
Image image = SmartImageFactory.getInstance().fromFile(BACK_IMAGE_PATH);
IdCardBackInfo result = recognizer.recognizeBack(image);
log.info("身份证反面识别结果:{}", JsonUtils.toJson(result));
} catch (Exception e) {
log.error("身份证反面识别失败", e);
}
}
/**
* 同时识别身份证正反面
*/
@Test
public void recognizeBoth() {
try {
DefaultIdCardRecognizer recognizer = getIdCardRecognizer();
Image frontImage = SmartImageFactory.getInstance().fromFile(FRONT_IMAGE_PATH);
Image backImage = SmartImageFactory.getInstance().fromFile(BACK_IMAGE_PATH);
IdCardInfo result = recognizer.recognizeBoth(frontImage, backImage);
log.info("身份证正反面识别结果:{}", JsonUtils.toJson(result));
} catch (Exception e) {
log.error("身份证正反面识别失败", e);
}
}
/**
* 身份证识别并输出调试图片
* 适合排查“为什么没识别出来”,不适合生产环境使用
*/
@Test
public void recognizeFrontWithDebugImages() {
try {
Path debugDir = Paths.get("output/idcard_debug");
Files.createDirectories(debugDir);
String filePrefix = "idcard_front";
DefaultIdCardRecognizer recognizer = getIdCardRecognizer()
// .setPreprocessListener(createDebugListener(debugDir, "idcard_front"));
.setPreprocessListener(new IdCardPreprocessListener() {
@Override
public void onAfterDirection(Image image) {
saveDebugImage(image, debugDir.resolve(filePrefix + "_step1_direction.png"));
}
@Override
public void onAfterRecognize(Image image, OcrInfo ocrInfo) {
saveDebugImage(image, debugDir.resolve(filePrefix + "_step2_ocr_boxes.png"));
}
});
Image image = SmartImageFactory.getInstance().fromFile(FRONT_IMAGE_PATH);
IdCardFrontInfo result = recognizer.recognizeFront(image);
log.info("身份证正面识别结果:{}", JsonUtils.toJson(result));
log.info("调试图片已输出到:{}", debugDir.toAbsolutePath());
} catch (Exception e) {
log.error("身份证调试识别失败", e);
}
}
private void saveDebugImage(Image image, Path outputPath) {
try {
ImageUtils.save(image, outputPath, "png");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB