OCR 识别小工具(2)护照识别
busyboxs 发布于2020-12 浏览:5761 回复:0
0
收藏

功能介绍

支持对中国大陆护照个人资料页所有15个字段进行结构化识别,包括国家码、护照号、姓名、姓名拼音、性别、出生地点、出生日期、签发地点、签发日期、有效期、签发机关、护照类型、国籍、MRZCode1、MRZCode2。

应用场景

  • 境外旅游:使用护照识别技术,实现对用户护照信息的结构化识别和录入,可应用于境外旅游产品预订、酒店入住登记等场景,满足护照信息自动录入的需求,有效提升信息录入效率,降低用户输入成本,提升用户使用体验
  • 留学信息登记:使用护照识别技术,实现对用户护照信息的结构化识别和录入,可应用于留学机构信息收集或个人留学手续办理等场景,满足护照信息自动录入的需求,有效提升信息录入效率,降低用户输入成本,提升用户使用体验

特色优势

  • 字段齐全:支持对护照所有字段的结构化识别,能够实现不同业务场景下对任意字段信息的提取,满足各类相关需求
  • 准确率高:依托百度优秀的图像处理技术,对护照反光、底纹等情况进行专项优化,关键字段识别准确率超过95%
  • 服务稳定:依托百度云技术实力,提供高可靠性、弹性可伸缩、高并发承载的文字识别服务,服务可用性高达99.99%

接口描述

支持对中国大陆护照个人资料页所有15个字段进行结构化识别,包括国家码、护照号、姓名、姓名拼音、性别、出生地点、出生日期、签发地点、签发日期、有效期、签发机关、护照类型、国籍、MRZCode1、MRZCode2。

请求说明

  • HTTP 方法: POST
  • 请求 URL: https://aip.baidubce.com/rest/2.0/ocr/v1/passport
  • URL参数: access_token
  • Header 参数: Content-Type = application/x-www-form-urlencoded
  • Body 参数:见下表

返回说明

返回参数如下表:

返回示例如下:

{
    "log_id": 7377468409496932872,
    "words_result_num": 14,
    "words_result": {
        "国家码": {
            "location": {
                "width": 59,
                "top": 200,
                "left": 762,
                "height": 26
            },
            "words": "CHN"
        },
        "护照签发地点": {
            "location": {
                "width": 236,
                "top": 505,
                "left": 558,
                "height": 43
            },
            "words": "山东/SHANDONG"
        },
        "MRZCode2": {
            "location": {
                "width": 1252,
                "top": 797,
                "left": 145,
                "height": 88
            },
            "words": "E898657303CHNSDMO7O2<<<<<<<<<<<<<"
        },
        "有效期至": {
            "location": {
                "width": 287,
                "top": 528,
                "left": 955,
                "height": 46
            },
            "words": "20261004"
        },
        "签发机关": {
            "location": {
                "width": 271,
                "top": 583,
                "left": 552,
                "height": 42
            },
            "words": "公安部出入境管理局"
        },
        "MRZCode1": {
            "location": {
                "width": 1201,
                "top": 781,
                "left": 162,
                "height": 45
            },
            "words": "PONSUN<

C++ 代码实现调用

这里假设前置准备已经做好了,如果没有,请阅读以下文章;如果有,则直接跳过;

(基础篇 01)在控制台创建对应的应用(https://yangshun.win/blogs/dea770b9/)
(基础篇 02)Windows 下使用 Vcpkg 配置百度 AI 图像识别 C++开发环境(VS2017)(https://yangshun.win/blogs/3b103680/)
(基础篇 03)C++ 获取 access token(https://yangshun.win/blogs/49f400d2/)
(基础篇 04)C++ base64 编解码原理及实现(https://yangshun.win/blogs/3f2fcf2e/)
为了方便,首先根据返回参数定义了一个结构体,该结构体包括了返回参数中的参数,如下:

struct PassportInfo
{
    uint64_t logID{};
    uint32_t resultNumber{};
    std::map wordsResult{};

    void print()
    {
        std::cout << "log id: " << logID << '\n';
        std::cout << "words result number: " << resultNumber << '\n';

        for (auto& [name, res] : wordsResult)
        {
            std::cout << "\t" << UTF8ToGB(name.c_str()) << ": ";
            res.print();
        }
    }

    void draw(cv::Mat& img)
    {
        for (auto& [name, res] : wordsResult)
        {
            res.draw(img);
        }
    }

};

然后定义了一个类来调用接口并获取结果

class Passport
{
public:
    Passport();
    ~Passport();

    Json::Value request(std::string imgBase64, std::map& options);

    void getResult(PassportInfo& result);

private:
    Json::Value m_obj;
    std::string m_url;
    // file to save token key
    std::string m_filename;
};

类中的私有成员 m_obj 表示返回结果对应的 json 对象。m_url 表示请求的 url,m_filename 表示用于存储 access token 的文件的文件名。

request 函数输入请求图像的 base64 编码以及请求参数,返回一个 json 对象,json 对象中包含请求的结果。

getResult 将请求的结果进行解析为自定义的结构体数据类型。以便用于后序的打印和绘图等。

完整代码如下

Passport.h 代码如下: screenshot/functions/Passport.h(https://github.com/busyboxs/screenshot/blob/master/functions/Passport.h)

#pragma once
#include 
#include 
#include 
#include 
#include 
#include "util.h"
#include "customVariables.h"

struct PassportInfo
{
    uint64_t logID{};
    uint32_t resultNumber{};
    std::map wordsResult{};

    void print()
    {
        std::cout << "log id: " << logID << '\n';
        std::cout << "words result number: " << resultNumber << '\n';

        for (auto& [name, res] : wordsResult)
        {
            std::cout << "\t" << UTF8ToGB(name.c_str()) << ": ";
            res.print();
        }
    }

    void draw(cv::Mat& img)
    {
        for (auto& [name, res] : wordsResult)
        {
            res.draw(img);
        }
    }

};

class Passport
{
public:
    Passport();
    ~Passport();

    Json::Value request(std::string imgBase64, std::map& options);

    void getResult(PassportInfo& result);

private:
    Json::Value m_obj;
    std::string m_url;
    // file to save token key
    std::string m_filename;
};

void PassportTest();
PassportInfo PassPortDetect(std::string imgPath);

Passport.cpp 代码如下: screenshot/functions/Passport.cpp(https://github.com/busyboxs/screenshot/blob/master/functions/Passport.cpp)

#include "Passport.h"

Passport::Passport()
{
    m_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/passport";
    m_filename = "tokenKey";
}

Passport::~Passport()
{
}

Json::Value Passport::request(std::string imgBase64, std::map& options)
{
    std::string response;
    Json::Value obj;
    std::string token;

    // 1. get HTTP post body
    std::string body;
    mergeHttpPostBody(body, imgBase64, options);

    // 2. get HTTP url with access token
    std::string url = m_url;
    getHttpPostUrl(url, m_filename, token);

    // 3. post request, response store the result
    int status_code = httpPostRequest(url, body, response);
    if (status_code != CURLcode::CURLE_OK) {
        obj["curl_error_code"] = status_code;
        m_obj = obj;
        return obj; // TODO: maybe should exit 
    }

    // 4. make string to json object
    generateJson(response, obj);

    // if access token is invalid or expired, we will get a new one
    if (obj["error_code"].asInt() == 110 || obj["error_code"].asInt() == 111) {
        token = getTokenKey();
        writeFile(m_filename, token);
        return request(imgBase64, options);

    }

    m_obj = obj;

    //checkErrorWithExit(obj);

    return obj;
}

void Passport::getResult(PassportInfo& result)
{
    if (m_obj.get("error_code", "null"))
    {
        result.wordsResult["error_code"].words = m_obj.get("error_code", "null").asString();
        result.wordsResult["error_msg"].words = m_obj.get("error_msg", "null").asString();
        return;
    }

    result.logID = m_obj["log_id"].asUInt64();
    result.resultNumber = m_obj["words_result_num"].asUInt();
    Json::Value::Members keys = m_obj["words_result"].getMemberNames();

    for (auto it = keys.begin(); it != keys.end(); ++it)
    {
        ResultPart resultPart;
        getResultPart(m_obj["words_result"][*it], resultPart);
        result.wordsResult[*it] = resultPart;
    }
}

void PassportTest()
{
    std::string img_file = "./images/passport_test.png";
    std::string out;
    readImageFile(img_file.c_str(), out);
    std::string img_base64 = base64_encode(out.c_str(), (int)out.size());

    // set options
    std::map options;

    Json::Value obj;
    Passport passportObj;
    PassportInfo result;
    obj = passportObj.request(img_base64, options);

    passportObj.getResult(result);
    result.print();

    cv::Mat img = cv::imread("./images/passport_test.png");
    result.draw(img);
    cv::namedWindow("passport", cv::WINDOW_NORMAL);
    cv::imshow("passport", img);
    cv::imwrite("./images/passport.jpg", img);
    cv::waitKey();
}

PassportInfo PassPortDetect(std::string imgPath)
{
    std::string out;
    readImageFile(imgPath.c_str(), out);
    std::string img_base64 = base64_encode(out.c_str(), (int)out.size());

    // set options
    std::map options;

    Json::Value obj;
    Passport passportObj;
    PassportInfo result;
    obj = passportObj.request(img_base64, options);

    passportObj.getResult(result);

    return result;
}

util.h 代码如下: screenshot/functions/util.h(https://github.com/busyboxs/screenshot/blob/master/functions/util.h)

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "base64.h"
#include "customVariables.h"

size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* userdata);
std::string getTokenKey();
int readImageFile(const char* filename, std::string& out);
void imgToBase64(std::string& imgFile, std::string& imgBase64);
int writeFile(const std::string& fileString, const std::string& str);
int readFile(const std::string& fileString, std::string& str);
std::string UTF8ToGB(const char* str);
void mergeHttpPostBody(std::string& body, std::string imgBase64, std::map& options);
void getHttpPostUrl(std::string& url, std::string& filename, std::string& token);
int httpPostRequest(std::string& url, std::string& body, std::string& response);
void generateJson(std::string& response, Json::Value& obj);
void checkErrorWithExit(Json::Value& obj);

void getLocation(Json::Value& loc, Location& location);
void getResultPart(Json::Value& res, ResultPart& resultPart);
void getWordsOnlyPart(Json::Value& res, WordsOnlyPart& wordsOnlyPart);

util.cpp 代码如下: screenshot/functions/util.cpp(https://github.com/busyboxs/screenshot/blob/master/functions/util.cpp)

使用时请替换 getTokenKey 中的 apikey 和 secritkey。

#include "util.h"

// callback function for curl
size_t writeCallback(void* ptr, size_t size, size_t nmemb, void* userdata)
{
    std::string* str = dynamic_cast((std::string*)userdata);
    str->append((char*)ptr, size * nmemb);
    return size * nmemb;
}

// get access token from server by get method
std::string getTokenKey() {
    std::string url = "https://aip.baidubce.com/oauth/2.0/token";
    std::string apikey = "nXb3wUW1xSPav1vMN3BMetAl";
    std::string secritkey = "DSlxiE0W1I3Sp3hvhCEcCg7IWE0LBSkd";
    std::map params;
    std::string response;

    params["grant_type"] = "client_credentials";
    params["client_id"] = apikey;
    params["client_secret"] = secritkey;

    // append url with parameters
    for (auto it = params.begin(); it != params.end(); ++it) {
        url += (it == params.begin() ? "?" : "&") + it->first + "=" + it->second;
    }

    CURL* curl = curl_easy_init();

    struct curl_slist* slist = NULL;

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);  // set callback function
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); // set var to receive return info from callback function
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, false);

    int status_code = curl_easy_perform(curl);

    curl_easy_cleanup(curl);
    curl_slist_free_all(slist);

    Json::Value obj;
    if (status_code != CURLcode::CURLE_OK) {
        obj["curl_error_code"] = status_code;
        return obj.toStyledString();
    }

    // parse json string
    JSONCPP_STRING error;
    Json::CharReaderBuilder builder;
    const std::unique_ptr reader(builder.newCharReader());
    reader->parse(response.data(), response.data() + response.size(), &obj, &error);
    std::string access_token = obj["access_token"].asString();

    return access_token;
}

// read image file [https://stackoverflow.com/questions/9612121/how-to-read-image-files-and-store-it-in-memorystdstring-in-c]
int readImageFile(const char* filename, std::string& out) {
    std::ifstream in(filename, std::ios::in | std::ios::binary);
    if (in) {
        std::ostringstream oss;
        oss << in.rdbuf();
        out.assign(oss.str());
        return 0;
    }
    else {
        std::cerr << "Can't open image!" << std::endl;
        return -1;
    }
}

void imgToBase64(std::string& imgFile, std::string& imgBase64) {
    // read image and encode to base64
    std::string out;
    readImageFile(imgFile.c_str(), out);
    imgBase64 = base64_encode(out.c_str(), (int)out.size());
}

int writeFile(const std::string& fileString, const std::string& str) {
    std::ofstream out(fileString, std::ios::binary);
    if (out.is_open()) {
        out << str;
        out.close();
    }

    return 0;
}

int readFile(const std::string& fileString, std::string& str) {
    std::ifstream in(fileString);
    if (!in.is_open()) {
        str = "";
        return -1;
    }

    char buffer[256];
    while (!in.eof()) {
        in.getline(buffer, sizeof(buffer));
    }

    str = buffer;
    return 0;
}


std::string UTF8ToGB(const char* str)
{
    std::string result;
    WCHAR* strSrc;
    LPSTR szRes;

    int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
    strSrc = new WCHAR[i + 1];
    MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);

    i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
    szRes = new CHAR[i + 1];
    WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);

    result = szRes;
    delete[]strSrc;
    delete[]szRes;
    return result;
}

void mergeHttpPostBody(std::string& body, std::string imgBase64, std::map& options) {
    body = "image=" + std::string(curl_escape(imgBase64.c_str(), int(imgBase64.size()))) + "&";
    // append body with options
    for (auto it = options.begin(); it != options.end(); ++it) {
        body += std::string(curl_escape(it->first.c_str(), (int)it->first.size()))
            + "=" + std::string(curl_escape(it->second.c_str(), (int)it->second.size())) + "&";
    }
}

// first get token from file (if exist), then add token to url
void getHttpPostUrl(std::string& url, std::string& filename, std::string& token) {
    // if token file is not exist, a new one should be create
    if (readFile(filename, token) < 0) {
        token = getTokenKey();
        writeFile(filename, token);
    }

    url = url + "?access_token=" + token;
}

int httpPostRequest(std::string& url, std::string& body, std::string& response) {
    struct curl_slist* slist = NULL;

    CURL* curl = curl_easy_init();

    // set headers, actually this is used by default
    std::string headers = "Content-Type:application/x-www-form-urlencoded";
    slist = curl_slist_append(slist, headers.c_str());

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
    curl_easy_setopt(curl, CURLOPT_POST, true);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.data());
    curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, body.size());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&response);
    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, true);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_easy_setopt(curl, CURLOPT_VERBOSE, false);

    int status_code = curl_easy_perform(curl);

    curl_easy_cleanup(curl);
    curl_slist_free_all(slist);

    return status_code;
}

void generateJson(std::string& response, Json::Value& obj) {
    // parse json string
    JSONCPP_STRING error;
    Json::CharReaderBuilder builder;
    const std::unique_ptr reader(builder.newCharReader());
    reader->parse(response.data(), response.data() + response.size(), &obj, &error);
}

void checkErrorWithExit(Json::Value& obj)
{
    if (obj.get("error_code", "null")) {
        std::cerr << obj.get("error_code", "null") << " : " << obj.get("error_msg", "null") << std::endl;
        system("pause");
        exit(EXIT_FAILURE);
    }
}

void getLocation(Json::Value& loc, Location& location)
{
    location.left = loc["left"].asInt();
    location.top = loc["top"].asInt();
    location.width = loc["width"].asInt();
    location.height = loc["height"].asInt();
}

void getResultPart(Json::Value& res, ResultPart& resultPart)
{
    getLocation(res["location"], resultPart.location);
    resultPart.words = res["words"].asString().c_str();
}

void getWordsOnlyPart(Json::Value& res, WordsOnlyPart& wordsOnlyPart)
{
    wordsOnlyPart.words = res["words"].asString().c_str();
}

运行结果

测试图像

测试结果

收藏
点赞
0
个赞
TOP
切换版块