如何使用Windows和Linux的DynamSoft标签识别器制作Java MRZ检测器
#编程 #java #mrz #ocr

本文旨在帮助Java开发人员构建桌面和服务器端Java应用程序,以在护照,旅行文档和ID卡中检测机器可读区域( MRZ )。您将看到如何将Dynamsoft C++ OCR SDK封装到Java Jar软件包中,以及如何快速创建使用几行Java代码的命令行MRZ检测器。

Java的类和MRZ检测方法

创建NativeLabelRecognizer.javaNativeLoader.javaMrzResult.javaMrzParser.java

  • NativeLabelRecognizer.java是本机库的包装类。它加载本机库并调用本机方法。 NativeLabelRecognizer中定义的主要本机方法如下:

    public NativeLabelRecognizer() {
        nativePtr = nativeCreateInstance();
    }
    
    public void destroyInstance() {
        if (nativePtr != 0)
            nativeDestroyInstance(nativePtr);
    }
    
    public static int setLicense(String license) {
        return nativeInitLicense(license);
    }
    
    public ArrayList<MrzResult> detectFile(String fileName) {
        return nativeDetectFile(nativePtr, fileName);
    }
    
    public String getVersion() {
        return nativeGetVersion();
    }
    
    public int loadModel() throws IOException {
        ...
        return nativeLoadModel(nativePtr, targetPath);
    }
    
    private native static int nativeInitLicense(String license);
    
    private native long nativeCreateInstance();
    
    private native void nativeDestroyInstance(long nativePtr);
    
    private native ArrayList<MrzResult> nativeDetectFile(long nativePtr, String fileName);
    
    private native int nativeLoadModel(long nativePtr, String modelPath);
    

    loadModel()方法很特别。它需要动态更新模型路径

    根据JON形式的模板文件在JAR包装的提取路径中指定。 Gson可用于加载和更新JSON对象。

    public int loadModel() throws IOException {
        String modeFile = "MRZ.json";
        String tempFolder = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
        String targetPath = new File(tempFolder, modeFile).getAbsolutePath();
        // Modify the model path based on your own environment
        FileReader reader = new FileReader(targetPath);
        char[] chars = new char[1024];
        int len = 0;
        StringBuilder sb = new StringBuilder();
        while ((len = reader.read(chars)) != -1) {
            sb.append(new String(chars, 0, len));
        }
        String template = sb.toString();
        if (reader != null) {
            reader.close();
        }
    
        Gson gson = new Gson();
        JsonObject jsonObject = gson.fromJson(template, JsonObject.class);
        JsonArray array = jsonObject.get("CharacterModelArray").getAsJsonArray();
        JsonObject object = array.get(0).getAsJsonObject();
        String modelPath = object.get("DirectoryPath").getAsString();
    
        if (modelPath != null && modelPath.contains("model")) {
            object.addProperty("DirectoryPath", tempFolder);
        }
    
        FileWriter writer = new FileWriter(targetPath);
        writer.write(jsonObject.toString());
        writer.flush();
        writer.close();
    
        return nativeLoadModel(nativePtr, targetPath);
    }
    
  • NativeLoader.java是一个实用程序类,可从JAR软件包中提取MRZ OCR模型文件和C ++共享库文件,并加载本机库。所有资产将提取到用户操作系统的临时目录。 MD5校验和用于比较文件更改。

    private static boolean extractResourceFiles(String dlrNativeLibraryPath, String dlrNativeLibraryName,
            String tempFolder) throws IOException {
        String[] filenames = null;
        if (Utils.isWindows()) {
            filenames = new String[] {"DynamsoftLicenseClientx64.dll",
            "vcomp140.dll",
            "DynamicPdfx64.dll", "DynamsoftLabelRecognizerx64.dll", "dlr.dll"};
        }
        else if (Utils.isLinux()) {
            filenames = new String[] {"libDynamicPdf.so", "libDynamsoftLicenseClient.so", "libDynamsoftLabelRecognizer.so", "libdlr.so"};
        }
    
        boolean ret = true;
    
        for (String file : filenames) {
            ret &= extractAndLoadLibraryFile(dlrNativeLibraryPath, file, tempFolder);
        }
    
        // Extract model files
        String modelPath = "/model";
        filenames = new String[] {"MRZ.json", "MRZ.caffemodel", "MRZ.txt", "MRZ.prototxt"};
        for (String file : filenames) {
            ret &= extractAndLoadLibraryFile(modelPath, file, tempFolder);
        }
        return ret;
    }
    
    static String md5sum(InputStream input) throws IOException {
        BufferedInputStream in = new BufferedInputStream(input);
    
        try {
            MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
            DigestInputStream digestInputStream = new DigestInputStream(in, digest);
            for (; digestInputStream.read() >= 0;) {
    
            }
            ByteArrayOutputStream md5out = new ByteArrayOutputStream();
            md5out.write(digest.digest());
            return md5out.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("MD5 algorithm is not available: " + e);
        } finally {
            in.close();
        }
    }
    
  • MrzResult.java是java类,用于存储MRZ检测结果,包括检测信心,文本和坐标。

    public class MrzResult {
        public int confidence;
        public String text;
        public int x1, y1, x2, y2, x3, y3, x4, y4;
    
        public MrzResult(int confidence, String text, int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) {
            this.confidence = confidence;
            this.text = text;
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
            this.x3 = x3;
            this.y3 = y3;
            this.x4 = x4;
            this.y4 = y4;
        }
    }
    
  • MrzParser.java是解析MRZ检测结果并解码MRZ信息的Java类。 MRZ信息包括存储在com.google.gson.JsonObject对象中的文档类型,发行国家,文件编号,出生日期和到期日期。

    JsonObject mrzInfo = new JsonObject();
    ...
    // Get issuing State infomation
    String  nation = line1.substring(2, 7);
    pattern = Pattern.compile("[0-9]");
    matcher = pattern.matcher(nation);
    if (matcher.matches()) return null;
    if (nation.charAt(nation.length() - 1) == '<') {
        nation = nation.substring(0, 2);
    }
    mrzInfo.addProperty("nationality", nation);
    // Get surname information
    line1 = line1.substring(5);
    int pos = line1.indexOf("<<");
    String  surName = line1.substring(0, pos);
    pattern = Pattern.compile("[0-9]");
    matcher = pattern.matcher(surName);
    if (matcher.matches()) return null;
    surName = surName.replace("<", " ");
    mrzInfo.addProperty("surname", surName);
    // Get givenname information
    String  givenName = line1.substring(surName.length() + 2);
    pattern = Pattern.compile("[0-9]");
    matcher = pattern.matcher(givenName);
    if (matcher.matches()) return null;
    givenName = givenName.replace("<", " ");
    givenName = givenName.trim();
    mrzInfo.addProperty("givenname", givenName);
    // Get passport number information
    String  passportNumber = "";
    passportNumber = line2.substring(0, 9);
    passportNumber = passportNumber.replace("<", " ");
    mrzInfo.addProperty("passportnumber", passportNumber);
    ...
    

当Java类完成后,我们可以通过运行:
自动生成JNI标头文件

cd src/main/java
javah -o ../../../jni/NativeLabelRecognizer.h com.dynamsoft.dlr.NativeLabelRecognizer

为Dynamoft C ++ OCR SDK编写JNI包装器

我们创建了一个CMAKE项目,以构建具有DynamSoft标签识别器SDK的JNI包装器。

这是CMakeLists.txt文件:

cmake_minimum_required (VERSION 2.6)
project (dlr)
MESSAGE( STATUS "PROJECT_NAME: " ${PROJECT_NAME} )

find_package(JNI REQUIRED)
include_directories(${JNI_INCLUDE_DIRS})

MESSAGE( STATUS "JAVA_INCLUDE: " ${JAVA_INCLUDE})

# Check lib
if (CMAKE_HOST_WIN32)
    set(WINDOWS 1)
elseif(CMAKE_HOST_APPLE)
    set(MACOS 1)
elseif(CMAKE_HOST_UNIX)
    set(LINUX 1)
endif()

# Set RPATH
if(CMAKE_HOST_UNIX)
    SET(CMAKE_CXX_FLAGS "-std=c++11 -O3 -Wl,-rpath=$ORIGIN")
    SET(CMAKE_INSTALL_RPATH "$ORIGIN")
    SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()

# Add search path for include and lib files
if(WINDOWS)
    link_directories("${PROJECT_SOURCE_DIR}/lib/win/" ${JNI_LIBRARIES}) 
elseif(LINUX)
    link_directories("${PROJECT_SOURCE_DIR}/lib/linux/" ${JNI_LIBRARIES})
endif()
include_directories("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/include/")


# Add the library
add_library(dlr SHARED NativeLabelRecognizer.cxx)
if(WINDOWS)
    target_link_libraries (${PROJECT_NAME} "DynamsoftLabelRecognizerx64")
else()
    target_link_libraries (${PROJECT_NAME} "DynamsoftLabelRecognizer" pthread)
endif()

# Set installation directory
set(CMAKE_INSTALL_PREFIX "${PROJECT_SOURCE_DIR}/../src/main/")
set(LIBRARY_PATH "java/com/dynamsoft/dlr/native")
if(WINDOWS)
    install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/win/" DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/win")
    install (TARGETS dlr DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/win")
elseif(LINUX)
    install (DIRECTORY "${PROJECT_SOURCE_DIR}/lib/linux/" DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/linux")
    install (TARGETS dlr DESTINATION "${CMAKE_INSTALL_PREFIX}${LIBRARY_PATH}/linux")
endif()

这是一个共享库项目。 dlr库是由NativeLabelRecognizer.cxx文件构建的。构建后,所有共享库将安装到src/main/java/com/dynamsoft/dlr/native目录:

mkdir build
cd build
cmake .. 
cmake --build . --config Release --target install

JNI方法在NativeLabelRecognizer.cxx文件中实现:

  • 初始化许可证:

    JNIEXPORT jint JNICALL Java_com_dynamsoft_dlr_NativeLabelRecognizer_nativeInitLicense(JNIEnv *env, jclass, jstring license)
        {
            const char *pszLicense = env->GetStringUTFChars(license, NULL);
            char errorMsgBuffer[512];
            // Click https://www.dynamsoft.com/customer/license/trialLicense/?product=dlr to get a trial license.
            int ret = DLR_InitLicense(pszLicense, errorMsgBuffer, 512);
            printf("DLR_InitLicense: %s\n", errorMsgBuffer);
            env->ReleaseStringUTFChars(license, pszLicense);
            return ret;
        }
    
  • 创建DynamSoft标签识别器的实例:

    JNIEXPORT jlong JNICALL Java_com_dynamsoft_dlr_NativeLabelRecognizer_nativeCreateInstance(JNIEnv *, jobject)
    {
        return (jlong)DLR_CreateInstance();
    }
    
  • 破坏Dynamsoft标签识别器的实例:

    JNIEXPORT void JNICALL Java_com_dynamsoft_dlr_NativeLabelRecognizer_nativeDestroyInstance(JNIEnv *, jobject, jlong handler)
    {
        if (handler)
        {
            DLR_DestroyInstance((void *)handler);
        }
    }
    
  • 加载模型文件:

    JNIEXPORT jint JNICALL Java_com_dynamsoft_dlr_NativeLabelRecognizer_nativeLoadModel(JNIEnv *env, jobject, jlong handler, jstring filename) 
    {
        const char *pFileName = env->GetStringUTFChars(filename, NULL);
        char errorMsgBuffer[512];
        int ret = DLR_AppendSettingsFromFile((void*)handler, pFileName, errorMsgBuffer, 512);
        printf("Load MRZ model: %s\n", errorMsgBuffer);
        env->ReleaseStringUTFChars(filename, pFileName);
        return ret;
    }
    
  • 从图像文件中检测MRZ并返回MRZ结果列表:

    JNIEXPORT jobject JNICALL Java_com_dynamsoft_dlr_NativeLabelRecognizer_nativeDetectFile(JNIEnv *env, jobject, jlong handler, jstring filename)
    {
        jobject arrayList = NULL;
    
        jclass mrzResultClass = env->FindClass("com/dynamsoft/dlr/MrzResult");
        if (NULL == mrzResultClass)
            printf("FindClass failed\n");
    
        jmethodID mrzResultConstructor = env->GetMethodID(mrzResultClass, "<init>", "(ILjava/lang/String;IIIIIIII)V");
        if (NULL == mrzResultConstructor)
            printf("GetMethodID failed\n");
    
        jclass arrayListClass = env->FindClass("java/util/ArrayList");
        if (NULL == arrayListClass)
            printf("FindClass failed\n");
    
        jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
        if (NULL == arrayListConstructor)
            printf("GetMethodID failed\n");
    
        jmethodID arrayListAdd = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
        if (NULL == arrayListAdd)
            printf("GetMethodID failed\n");
    
        const char *pFileName = env->GetStringUTFChars(filename, NULL);
        int ret = DLR_RecognizeByFile((void *)handler, pFileName, "locr");
        if (ret)
        {
            printf("Detection error: %s\n", DLR_GetErrorString(ret));
        }
    
        DLR_ResultArray *pResults = NULL;
        DLR_GetAllResults((void *)handler, &pResults);
        if (!pResults)
        {
            return NULL;
        }
    
        int count = pResults->resultsCount;
        arrayList = env->NewObject(arrayListClass, arrayListConstructor);
    
        for (int i = 0; i < count; i++)
        {
            DLR_Result *mrzResult = pResults->results[i];
            int lCount = mrzResult->lineResultsCount;
            for (int j = 0; j < lCount; j++)
            {
                DM_Point *points = mrzResult->lineResults[j]->location.points;
                int x1 = points[0].x;
                int y1 = points[0].y;
                int x2 = points[1].x;
                int y2 = points[1].y;
                int x3 = points[2].x;
                int y3 = points[2].y;
                int x4 = points[3].x;
                int y4 = points[3].y;
    
                jobject object = env->NewObject(mrzResultClass, mrzResultConstructor, mrzResult->lineResults[j]->confidence, env->NewStringUTF(mrzResult->lineResults[j]->text), x1, y1, x2, y2, x3, y3, x4, y4);
                env->CallBooleanMethod(arrayList, arrayListAdd, object);
            }
        }
    
        // Release memory
        DLR_FreeResults(&pResults);
    
        env->ReleaseStringUTFChars(filename, pFileName);
        return arrayList;
    }
    

用资源和依赖项构建Java Jar包装

目标软件包应包括Java类,C ++库文件,模型文件和依赖项。默认情况下,Maven仅包括Java类。要包括C ++库文件,模型文件和依赖项,我们需要将以下配置添加到pom.xml文件:

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <excludes>
                <exclude>**/*.md</exclude>
                <exclude>**/*.h</exclude>
                <exclude>**/*.lib</exclude>
                <exclude>**/*.java</exclude>
            </excludes>
        </resource>
        <resource>
            <directory>res</directory>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
            </configuration>
        </plugin>
    </plugins>
</build>
  • src/main/java是包含本地库文件的目录,该目录是在构建JNI包装器后安装的。
  • res是包含模型文件的目录。它的结构如下:

    res
    │
    └───model
        ├───MRZ.caffemodel
        ├───MRZ.json   
        ├───MRZ.prototxt
        └───MRZ.txt
    
  • maven-assembly-plugin用于将依赖项构建到目标软件包中以容易部署。

最后,运行mvn install assembly:assembly命令生成dlr-1.0.0-jar-with-dependencies.jar文件。

在Java建造MRZ检测器的步骤

现在,让我们创建一个带有几行代码的Java MRZ检测器。

  1. 获取Dynamsoft标签识别器的30-day FREE trial license,并在Java代码中激活许可证。

    NativeLabelRecognizer.setLicense("DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==");
    
  2. 创建一个NativeLabelRecognizer实例。

    NativeLabelRecognizer labelRecognizer = new NativeLabelRecognizer();
    
  3. 加载MRZ检测模型:

    labelRecognizer.loadModel();
    
  4. 从图像文件中检测MRZ:

    ArrayList<MrzResult> results = (ArrayList<MrzResult>)labelRecognizer.detectFile(fileName);
    
  5. 通过解码MRZ线获取MRZ信息:

    String[] lines = new String[results.size()];
    for (int i = 0; i < results.size(); i++) {
        lines[i] = results.get(i).text;
    }
    JsonObject info = MrzParser.parse(lines);
    

尝试示例代码

java -cp target/dlr-1.0.0-jar-with-dependencies.jar  com.dynamsoft.dlr.Test images/1.png

MRZ detector with Java OCR SDK

源代码

https://github.com/yushulx/java-mrz-ocr-sdk