QtとUSBカメラでデスクトップパスポートスキャナを構築する


パスポートスキャナソフトウェアやMRZ Readerのソフトウェアを検索する場合は、それらの多くは、モバイルデバイスのみ利用可能です.警察官のために、彼らがパトロールしているとき、携帯機器によるパスポートをスキャンすることは便利です.しかし、税関や入国管理官の場合は、通常、デスクトップシステムとプロのパスポートスキャナや読者は、多くの費用は、乗客のパスポートの情報を確認します.DynamsoftのOCR SDKは、両方のモバイルおよびデスクトップのシナリオで利用可能です.本稿では、安いUSBウェブカメラ(20ドル未満)を使用する経済的方法を示します.Qt , and Dynamsoft MRZ SDK WindowsとLinux用のデスクトップパスポートスキャナアプリケーションを構築する.

必要条件

  • QT 5.12.11
  • Windows

  • Linux
    sudo apt-get install qt5-default
    

  • デスクトップパスポートスキャナ用のQtのC++プロジェクトの骨格
    開始する前に、最近実装したバーコードスキャンアプリケーションのコードベースを取得しましょう.
    git clone https://github.com/yushulx/Qt-desktop-barcode-reader.git
    
    CodeBaseは、ファイルの読み込みとカメラのビデオストリーミング機能を実装しています.私がする必要があるのは、バーコード認識SDKをMRZ認識SDKに置き換えることです.そのうえ、プロジェクトはOCRのために余分な性格モデル(カフェインによって訓練される)を輸入する必要があります、そして、MRZ認識パラメタを提供するためのテンプレートファイル.
    文字モデルファイル
    NumberUppercase.caffemodel
    NumberUppercase.prototxt
    NumberUppercase.txt
    NumberUppercase_Assist_1lIJ.caffemodel
    NumberUppercase_Assist_1lIJ.prototxt
    NumberUppercase_Assist_1lIJ.txt
    NumberUppercase_Assist_8B.caffemodel
    NumberUppercase_Assist_8B.prototxt
    NumberUppercase_Assist_8B.txt
    NumberUppercase_Assist_8BHR.caffemodel
    NumberUppercase_Assist_8BHR.prototxt
    NumberUppercase_Assist_8BHR.txt
    NumberUppercase_Assist_number.caffemodel
    NumberUppercase_Assist_number.prototxt
    NumberUppercase_Assist_number.txt
    NumberUppercase_Assist_O0DQ.caffemodel
    NumberUppercase_Assist_O0DQ.prototxt
    NumberUppercase_Assist_O0DQ.txt
    NumberUppercase_Assist_upcase.caffemodel
    NumberUppercase_Assist_upcase.prototxt
    NumberUppercase_Assist_upcase.txt
    
    テンプレートファイル
    {
       "CharacterModelArray" : [
        {
          "DirectoryPath": "CharacterModel",
          "FilterFilePath": "",
          "Name": "NumberUppercase"
        }
       ],
       "LabelRecognizerParameterArray" : [
          {
             "BinarizationModes" : [
                {
                   "BlockSizeX" : 0,
                   "BlockSizeY" : 0,
                   "EnableFillBinaryVacancy" : 1,
                   "LibraryFileName" : "",
                   "LibraryParameters" : "",
                   "Mode" : "BM_LOCAL_BLOCK",
                   "ThreshValueCoefficient" : 15
                }
             ],
             "CharacterModelName" : "NumberUppercase",
             "LetterHeightRange" : [ 5, 1000, 1 ],
             "LineStringLengthRange" : [44, 44],
             "MaxLineCharacterSpacing" : 130,
             "LineStringRegExPattern" : "(P[OM<][A-Z]{3}([A-Z<]{0,35}[A-Z]{1,3}[(<<)][A-Z]{1,3}[A-Z<]{0,35}<{0,35}){(39)}){(44)}|([A-Z0-9<]{9}[0-9][A-Z]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}",
             "MaxThreadCount" : 4,
             "Name" : "locr",
             "TextureDetectionModes" :[
                {
                    "Mode" : "TDM_GENERAL_WIDTH_CONCENTRATION",
                    "Sensitivity" : 8
                }
             ],
             "ReferenceRegionNameArray" : [ "DRRegion" ]
          }
       ],
       "LineSpecificationArray" : [
        {
            "Name":"L0",
            "LineNumber":"",
            "BinarizationModes" : [
                {
                   "BlockSizeX" : 30,
                   "BlockSizeY" : 30,
                   "Mode" : "BM_LOCAL_BLOCK"
                }
             ]
        }
        ],
       "ReferenceRegionArray" : [
          {
             "Localization" : {
                "FirstPoint" : [ 0, 0 ],
                "SecondPoint" : [ 100, 0 ],
                "ThirdPoint" : [ 100, 100 ],
                "FourthPoint" : [ 0, 100 ],
                "MeasuredByPercentage" : 1,
                "SourceType" : "LST_MANUAL_SPECIFICATION"
             },
             "Name" : "DRRegion",
             "TextAreaNameArray" : [ "DTArea" ]
          }
       ],
       "TextAreaArray" : [
          {
             "LineSpecificationNameArray" : ["L0"],
             "Name" : "DTArea",
             "FirstPoint" : [ 0, 0 ],
             "SecondPoint" : [ 100, 0 ],
             "ThirdPoint" : [ 100, 100 ],
             "FourthPoint" : [ 0, 100 ]
          }
       ]
    }
    

    用のデスクトップパスポートスキャナ
    我々はすでにCodeBaseを持っているので、アプリケーションの仕事を得るにはあまり時間がかかりません.

    ライブラリリンク
    ダウンロードしたアーカイブからライブラリファイルを取り出し、対応するフォルダに置きます.

  • Windows
    コピーDynamsoftLabelRecognizerx64.lib to platform/windows/lib .
    コピーDynamicPdfx64.dll , DynamsoftLabelRecognizerx64.dll , DynamsoftLicenseClientx64.dll and vcomp140.dll to platform/windows/bin .

  • Linux
    コピーlibDynamicPdf.so , libDynamsoftLabelRecognizer.so , and libDynamsoftLicenseClient.so to platform/linux .
  • その後、我々はCMakeLists.txt ライブラリとコピーモデルとテンプレートファイルをリンクするファイル
    if (CMAKE_HOST_WIN32)
        target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets Qt5::MultimediaWidgets "DynamsoftLabelRecognizerx64")    
    elseif(CMAKE_HOST_UNIX)
        target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets Qt5::MultimediaWidgets "DynamsoftLabelRecognizer")
    endif()
    
    # Copy DLLs
    if(CMAKE_HOST_WIN32)
        add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
            COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${PROJECT_SOURCE_DIR}/platform/windows/bin/"      
            $<TARGET_FILE_DIR:${PROJECT_NAME}>)
    endif()
    
    # Copy template
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
            COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${PROJECT_SOURCE_DIR}/template/"      
            $<TARGET_FILE_DIR:${PROJECT_NAME}>)
    
    # Copy model files
    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 
            COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${PROJECT_SOURCE_DIR}/CharacterModel"      
            $<TARGET_FILE_DIR:${PROJECT_NAME}>/CharacterModel)
    

    MRZ認識のためのコードを修正するステップ
    次はインポートDynamsoftLabelRecognizer.h and DynamsoftCore.h to mainwindow.h :
    #include "DynamsoftLabelRecognizer.h"
    #include "DynamsoftCore.h"
    
    インmainwindow.cpp , インボイスの行を探すDBR_DecodeFile() 次に、以下の行を置き換えます.
    int errorCode = DBR_DecodeFile(reader, fileName.toStdString().c_str(), "");
    
    パスポート情報を取得するには、次のコードブロックを追加します.
    DLR_Result **results = handler->results;
    for (int ri = 0; ri < handler->resultsCount; ++ri)
    {
        DLR_Result* result = handler->results[ri];
        int lCount = result->lineResultsCount;
        for (int li = 0; li < lCount; ++li)
        {
            DM_Point *points = result->lineResults[li]->location.points;
            int x1 = points[0].x, y1 = points[0].y;
            int x2 = points[1].x, y2 = points[1].y;
            int x3 = points[2].x, y3 = points[2].y;
            int x4 = points[3].x, y4 = points[3].y;
        }
    
        if (lCount < 2)
        {
            continue;
        }
        string line1 = result->lineResults[0]->text;
        string line2 = result->lineResults[1]->text;
        if (line1.length() != 44 || line2.length() != 44)
        {
            continue;
        }
        if (line1[0] != 'P')
            continue;
        else {
            // Type
            string tmp = "Type: ";
            tmp.insert(tmp.length(), 1, line1[0]);
            out += QString::fromStdString(tmp) + "\n";
    
            // Issuing country
            tmp = "Issuing country: "; line1.substr(2, 5);
            tmp += line1.substr(2, 3);      
            out += QString::fromStdString(tmp) + "\n";
    
            // Surname
            int index = 5;
            tmp = "Surname: ";
            for (; index < 44; index++)
            {
                if (line1[index] != '<')
                {
                    tmp.insert(tmp.length(), 1, line1[index]);
                }
                else 
                {
                    break;
                }
            }
            out += QString::fromStdString(tmp) + "\n";
    
            // Given names
            tmp = "Given Names: ";
            index += 2;
            for (; index < 44; index++)
            {
                if (line1[index] != '<')
                {
                    tmp.insert(tmp.length(), 1, line1[index]);
                }
                else 
                {
                    tmp.insert(tmp.length(), 1, ' ');
                }
            }
            out += QString::fromStdString(tmp) + "\n";
    
            // Passport number
            tmp = "Passport number: ";
            index = 0;
            for (; index < 9; index++)
            {
                if (line2[index] != '<')
                {
                    tmp.insert(tmp.length(), 1, line2[index]);
                }
                else 
                {
                    break;
                }
            }
            out += QString::fromStdString(tmp) + "\n";
    
            // Nationality
            tmp = "Nationality: ";
            tmp += line2.substr(10, 3);
            out += QString::fromStdString(tmp) + "\n";
    
            // Date of birth
            tmp = line2.substr(13, 6);
            tmp.insert(2, "/");
            tmp.insert(5, "/");
            tmp = "Date of birth (YYMMDD): " + tmp;
            out += QString::fromStdString(tmp) + "\n";
    
            // Sex
            tmp = "Sex: ";
            tmp.insert(tmp.length(), 1, line2[20]);
            out += QString::fromStdString(tmp) + "\n";
    
            // Expiration date of passport
            tmp = line2.substr(21, 6);
            tmp.insert(2, "/");
            tmp.insert(5, "/");
            tmp = "Expiration date of passport (YYMMDD): " + tmp;
            out += QString::fromStdString(tmp) + "\n";
    
            // Personal number
            if (line2[28] != '<')
            {
                tmp = "Personal number: ";
                for (index = 28; index < 42; index++)
                {
                    if (line2[index] != '<')
                    {
                        tmp.insert(tmp.length(), 1, line2[index]);
                    }
                    else 
                    {
                        break;
                    }
                }
                out += QString::fromStdString(tmp) + "\n";
            }
        }
    }
    DLR_FreeResults(&handler);
    
    これにより静的画像認識が完了する.以下では、カメラのビデオストリームでリアルタイムパスポートスキャンを実装します.
    MRZ認識情報を保存し、スレッド間で共有するには、新しいクラスを作成しますMRZInfo :
    #ifndef MRZINFO_H
    #define MRZINFO_H
    
    #include <QString>
    
    class MRZInfo
    {
    public:
        MRZInfo() = default;
    
        ~MRZInfo(){};
    
        bool isNull();
    
    public:
        QString text;
        int x1, y1, x2, y2, x3, y3, x4, y4, xx1, yy1, xx2, yy2, xx3, yy3, xx4, yy4;
    };
    
    #endif // MRZINFO_H
    
    オープンwork.h 新しいスロット関数を追加するにはdetectMRZ() , MRZを認識するためのワーカースレッドで動作します.
    void Work::detectMRZ()
    {
        while (m_bIsRunning)
        {
            QImage image;
            m_mutex.lock();
            // wait for QList
            if (queue.isEmpty())
            {
                m_listIsEmpty.wait(&m_mutex);
            }
    
            if (!queue.isEmpty())
            {
                image = queue.takeFirst();
            }
            m_mutex.unlock();
    
            if (!image.isNull())
            {
                // Detect MRZ
            }
        }
    }
    
    MRZを認識し、パスポート情報を抽出するにはQImage to ImageData それから呼び出しDLR_RecognizeByBuffer() メソッド:
    // Convert QImage to ImageData
    ImageData data;
    data.bytes = (unsigned char *)image.bits();
    data.width = image.width();
    data.height = image.height();
    data.stride = image.bytesPerLine();
    data.format = IPF_ARGB_8888;
    data.bytesLength = image.byteCount();
    
    QDateTime start = QDateTime::currentDateTime();
    int errorCode = DLR_RecognizeByBuffer(recognizer, &data, "locr");
    QDateTime end = QDateTime::currentDateTime();
    DLR_ResultArray *handler = NULL;
    DLR_GetAllResults(recognizer, &handler);
    std::vector<MRZInfo> all;
    QString out = "Elapsed time: " + QString::number(start.msecsTo(end)) + "ms\n\n";
    DLR_Result **results = handler->results;
    for (int ri = 0; ri < handler->resultsCount; ++ri)
    {
        DLR_Result* result = handler->results[ri];
        int lCount = result->lineResultsCount;
        if (lCount < 2)
        {
            continue;
        }
    
        DLR_LineResult *l1 = result->lineResults[0];
        DLR_LineResult *l2 = result->lineResults[1];
    
        string line1 = l1->text;
        string line2 = l2->text;
        if (line1.length() != 44 || line2.length() != 44)
        {
            continue;
        }
        if (line1[0] != 'P')
            continue;
    
        MRZInfo info;
    
        DM_Point *points = l1->location.points;
        int x1 = points[0].x, y1 = points[0].y;
        int x2 = points[1].x, y2 = points[1].y;
        int x3 = points[2].x, y3 = points[2].y;
        int x4 = points[3].x, y4 = points[3].y;
        DM_Point *points2 = l2->location.points;
        int xx1 = points2[0].x, yy1 = points2[0].y;
        int xx2 = points2[1].x, yy2 = points2[1].y;
        int xx3 = points2[2].x, yy3 = points2[2].y;
        int xx4 = points2[3].x, yy4 = points2[3].y;
    
        // Type
        string tmp = "Type: ";
        tmp.insert(tmp.length(), 1, line1[0]);
        out += QString::fromStdString(tmp) + "\n";
    
        // Issuing country
        tmp = "Issuing country: "; line1.substr(2, 5);
        tmp += line1.substr(2, 3);      
        out += QString::fromStdString(tmp) + "\n";
    
        // Surname
        int index = 5;
        tmp = "Surname: ";
        for (; index < 44; index++)
        {
            if (line1[index] != '<')
            {
                tmp.insert(tmp.length(), 1, line1[index]);
            }
            else 
            {
                break;
            }
        }
        out += QString::fromStdString(tmp) + "\n";
    
        // Given names
        tmp = "Given Names: ";
        index += 2;
        for (; index < 44; index++)
        {
            if (line1[index] != '<')
            {
                tmp.insert(tmp.length(), 1, line1[index]);
            }
            else 
            {
                tmp.insert(tmp.length(), 1, ' ');
            }
        }
        out += QString::fromStdString(tmp) + "\n";
    
        // Passport number
        tmp = "Passport number: ";
        index = 0;
        for (; index < 9; index++)
        {
            if (line2[index] != '<')
            {
                tmp.insert(tmp.length(), 1, line2[index]);
            }
            else 
            {
                break;
            }
        }
        out += QString::fromStdString(tmp) + "\n";
    
        // Nationality
        tmp = "Nationality: ";
        tmp += line2.substr(10, 3);
        out += QString::fromStdString(tmp) + "\n";
    
        // Date of birth
        tmp = line2.substr(13, 6);
        tmp.insert(2, "/");
        tmp.insert(5, "/");
        tmp = "Date of birth (YYMMDD): " + tmp;
        out += QString::fromStdString(tmp) + "\n";
    
        // Sex
        tmp = "Sex: ";
        tmp.insert(tmp.length(), 1, line2[20]);
        out += QString::fromStdString(tmp) + "\n";
    
        // Expiration date of passport
        tmp = line2.substr(21, 6);
        tmp.insert(2, "/");
        tmp.insert(5, "/");
        tmp = "Expiration date of passport (YYMMDD): " + tmp;
        out += QString::fromStdString(tmp) + "\n";
    
        // Personal number
        if (line2[28] != '<')
        {
            tmp = "Personal number: ";
            for (index = 28; index < 42; index++)
            {
                if (line2[index] != '<')
                {
                    tmp.insert(tmp.length(), 1, line2[index]);
                }
                else 
                {
                    break;
                }
            }
            out += QString::fromStdString(tmp) + "\n";
        }
    
        info.text = out;
        info.x1 = x1;
        info.y1 = y1;
        info.x2 = x2;
        info.y2 = y2;
        info.x3 = x3;
        info.y3 = y3;
        info.x4 = x4;
        info.y4 = y4;
        info.xx1 = xx1;
        info.yy1 = yy1;
        info.xx2 = xx2;
        info.yy2 = yy2;
        info.xx3 = xx3;
        info.yy3 = yy3;
        info.xx4 = xx4;
        info.yy4 = yy4;
    
        all.push_back(info);
    }
    
    DLR_FreeResults(&handler);
    surface->appendResult(all);
    

    Qt cmakeプロジェクトの構築方法
    cmakeのビルドコマンドはWindowsとLinuxの間で少し異なります.
    # Windows
    mkdir build
    cd build
    cmake -G "MinGW Makefiles" ..
    cmake --build .
    MRZRecognizer.exe
    
    # Linux
    mkdir build
    cd build
    cmake ..
    cmake --build .
    ./MRZRecognizer
    

    パスポートスキャナの走行
    プログラムを実行するときは、有効なライセンスキーを入力する必要があります.

    その後、静止画像やウェブカメラからパスポート情報をスキャンすることができます.


    ソースコード
    https://github.com/yushulx/passport-scanner