一:引言

     本文才用百度的PaddleOCR对身份证进行识别的处理,由于直接使用并未进行对跟多数据集进行训练,当前的效果是对非少数民族的身份证识别率可以达到85%以上,同时要求身份证图片是正面且相对清晰。否则效果不理想,本文主要介绍PaddleOCR是什么、安装总流程、PaddleOCR的简单使用、和对身份证识别结果的处理。经过处理后,在保证正面的情况且相对清晰的情况下身份证对非少数民族的识别可以达95%以上,对除了朝鲜族少数民族身份证外其他的也可达90%以上.

.PaddleOCR是什么?

看看官方怎么说:PaddleOCR/README_ch.md at release/2.6 · PaddlePaddle/PaddleOCR · GitHub

2.1:简介

          PaddleOCR是百度深度学习框架PaddlePaddle开源的OCR项目,旨在打造一套丰富、领先、且实用的OCR工具库,助力使用者训练出更好的模型,并应用落地。PaddleOCR包含丰富的文本检测、文本识别以及端到端算法。

 2.2:PaddleOCR特性:

  • 超轻量级中文OCR模型,总模型仅8.6M
  • 单模型支持中英文数字组合识别、竖排文本识别、长文本识别
  • 检测模型DB(4.1M)+识别模型CRNN(4.5M)
  • 实用通用中文OCR模型
  • 多种预测推理部署方案,包括服务部署和端侧部署
  • 多种文本检测训练算法,EAST、DB、SAST
  • 多种文本识别训练算法,Rosetta、CRNN、STAR-Net、RARE、SRN
  • 可运行于Linux、Windows、MacOS等多种系统

三:安装流程

3.1:paddlepaddle环境的安装

      官方安装链接:开始使用_飞桨-源于产业实践的开源深度学习平台

具体看链接的教程,其中有 CPU 版的 PaddlePaddle和 GPU 版的 PaddlePaddle需要注意

3.1.1:CPU 版、GPU 版的区别

         PaddlePaddle CPU 版本是指使用 CPU 进行推理和训练的版本。它可以在不需要 GPU 的情况下运行,对于没有 GPU 设备或者对成本要求较高的用户来说非常有用。

        在 CPU 版本中,模型的训练和推理速度比 GPU 版本慢,但是仍然可以解决一些简单的问题。另外,在开发阶段,CPU 版本也可以帮助用户验证模型的功能和效果,以便在之后在 GPU 上进行更深入的训练。

        因此,PaddlePaddle CPU 版本可以帮助用户在不需要 GPU 的情况下进行模型开发和评估,从而降低了开发成本。

3.1.2:cuda、cudnn安装可以参考

tensorflow【cpu/gpu、cuda、cudnn】全网最详细安装、常用python镜像源、tensorflow 深度学习强化学习教学_汀、的博客-CSDN博客_tensorflow深度学习

手把手教你 win10 安装Paddlepaddle-GPU_AI小鸭学院的博客-CSDN博客


3.2:安装PaddleOCR whl包

直接用以下命令下载速度贼快:

pip install --index-url https://pypi.douban.com/simple paddleocr==2.6.1.3   

2.6.1.3代表要下载的版本,不写下载最新版本

如果下载遇到有些库下载超时,可才用以下命令单独下载

 pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple      

四:PaddleOCR的使用

4.1:基本使用方法

from paddleocr import PaddleOCR

# 识别身份证
def findIdcardResult():
    # 定义图片路径
    img_path = r'C:\Users\Jewel\Desktop\身份证\蒙文.png'
    

    #加载预训练的模型

    # Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
    # 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
    # 这里 use_angle_cls=False 为不使用自定义训练集
    ocr = PaddleOCR(use_angle_cls=False , lang="ch")

    # use_angle_cls=True使用训练模型,模型放在models目录下
    # ocr = PaddleOCR(use_angle_cls=True,lang="ch",
    #                 rec_model_dir='../models/ch_PP-OCRv3_rec_slim_infer/',
    #                 cls_model_dir='../models/ch_ppocr_mobile_v2.0_cls_slim_infer/',
    #                 det_model_dir='../models/ch_PP-OCRv3_det_slim_infer/')

    # 识别图片中的文字
    result = ocr.ocr(img_path, cls=True)
    print(result)
  

版本说明 paddleocr默认使用PP-OCRv3模型(--ocr_version PP-OCRv3),如需使用其他版本可通过设置参数--ocr_version,具体版本说明如下:

版本名称版本说明
PP-OCRv3支持中、英文检测和识别,方向分类器,支持多语种识别
PP-OCRv2支持中英文的检测和识别,方向分类器,多语言暂未更新
PP-OCR支持中、英文检测和识别,方向分类器,支持多语种识别

 paddleocr 总共需要三个模型,检测模型(det模型)、方向分类器(cls模型)、识别模型(rec模型)

如需新增自己训练的模型,可以在paddleocr中增加模型链接和字段,重新编译即可。

4.2:使用训练好的模型

举个例子,从官网飞桨AI Studio - 人工智能学习与实训社区下载一个库

  比如【PP-OCRv3】模型,下载到本地之后分别进行解压,创建一个models文件夹,把下载好的模型文件解压到该models文件夹中,并将models文件夹放入到PaddleOCR根目录下,如下所示:

 # use_angle_cls=True使用训练模型,模型放在models目录下
 ocr = PaddleOCR(use_angle_cls=True,lang="ch",
                     rec_model_dir='../models/ch_PP-OCRv3_rec_slim_infer/',
                    cls_model_dir='../models/ch_ppocr_mobile_v2.0_cls_slim_infer/',
                     det_model_dir='../models/ch_PP-OCRv3_det_slim_infer/')

ocr的模型通常已经在足够数据量的数据集上训练过,各类模型大致关系为:

  • 普通训练模型:大多数情况指没有经过太多训练的模型,适合在你自己有大量数据的情况下进行训练
  • 预训练模型:由官方已经训练过的模型,但可以供用户使用新的数据进行 finetune,适合在你的数据量不太多的情况下进行
  • 推理模型:官方已经调整好,可以直接用来做 ocr 的识别任务的模型,不适合于重新训练

4.3:身份证识别

from paddleocr import PaddleOCR
from common import IdCardStraight


# 识别身份证
def findIdcardResult(img_path):
    # 定义文件路径
    img_path = r'C:\Users\Jewel\Desktop\身份证\蒙文.png'
    # 初始化ocr模型和后处理模型
    ocr = PaddleOCR(use_angle_cls=False, lang="ch")
    # 获取模型检测结果
    result = ocr.ocr(img_path, cls=True)
    print(result)
    # 将检测到的文字放到一个列表中
    # txtArr = [line[1][0] for line in result[0]]
    txtArr = []
    for line in result[0]:
        txt = line[1][0]
        # 发现朝鲜文、彝文的身份证
        if (("姓" in txt and "性" in txt and "住" in txt) or ("名" in txt and "别" in txt and "生" in txt)) and line[1][1] < 0.75:
            continue
        else:
            txtArr.append(txt)

    print(txtArr)
    postprocessing = IdCardStraight(txtArr)
    # # 将结果送入到后处理模型中
    id_result = postprocessing.run()
    print(id_result)
    return id_result

对识别处理的数据进行处理 


import re
import json
import string


def verifyByIDCard(idcard):
    """
    验证身份证号码是否有效
    """
    sz = len(idcard)
    if sz != 18:
        return False

    weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
    validate = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
    sum = 0
    for i in range(len(weight)):
        sum += weight[i] * int(idcard[i])
    m = sum % 11
    return validate[m] == idcard[sz - 1]


class IdCardStraight:
    """
    身份证OCR返回结果,正常的身份证大概率识别没有什么问题,少数名族身份证,壮文、藏文、蒙文基本识别也没问题
    """
    nation_list = ["汉", "蒙古", "回", "藏", "维吾尔", "苗", "彝", "壮", "布依", "朝鲜", "满", "侗", "瑶", "白",
                   "土家", "哈尼", "哈萨克", "傣", "黎", "傈僳", "佤", "畲", "高山", "拉祜", "水", "东乡", "纳西",
                   "景颇", "柯尔克孜", "土", "达斡尔", "仫佬", "羌", "布朗", "撒拉", "毛难", "仡佬", "锡伯", "阿昌",
                   "普米", "塔吉克", "怒", "乌孜别克", "俄罗斯", "鄂温克", "崩龙", "保安", "裕固", "京", "塔塔尔",
                   "独龙", "鄂伦春", "赫哲", "门巴", "珞巴", "基诺"]

    def __init__(self, result):
        self.result = [
            i.replace(" ", "").translate(str.maketrans("", "", string.punctuation))
            for i in result
        ]
        print(self.result)
        self.out = {"result": {}}
        self.res = self.out["result"]
        self.res["name"] = ""
        self.res["idNumber"] = ""
        self.res["address"] = ""
        self.res["gender"] = ""
        self.res["nationality"] = ""

    def birth_no(self):
        """
        身份证号码
        """
        for i in range(len(self.result)):
            txt = self.result[i]

            # 身份证号码
            if "X" in txt or "x" in txt:
                res = re.findall("\d*[X|x]", txt)
            else:
                res = re.findall("\d{18}", txt)

            if len(res) > 0:
                # 验证身份证号码是否有效  因为像藏文会出现刷出的身份证有超过18位数字的情况
                if verifyByIDCard(res[0]):
                    self.res["idNumber"] = res[0]
                    self.res["gender"] = "男" if int(res[0][16]) % 2 else "女"
                    break

    def full_name(self):
        """
        身份证姓名
        """
        # 如果姓名后面有跟文字,则取名后面的字段,如果"名"不存在,那肯定也就没有"姓名",所以在没有"名"的情况下只要判断是否有"姓"就可以了
        # 名字限制是2位以上,所以至少这个集合得3位数,才进行"名"或"姓"的判断
        for i in range(len(self.result)):
            txt = self.result[i]
            if ("姓名" in txt or "名" in txt or "姓" in txt) and len(txt) > 3:
                resM = re.findall("名[\u4e00-\u9fa5]+", txt)
                resX = re.findall("姓[\u4e00-\u9fa5]+", txt)
                if len(resM) > 0:
                    name = resM[0].split("名")[-1]
                elif len(resX) > 0:
                    name = resX[0].split("姓")[-1]
                if len(name) > 1:
                    self.res["name"] = name
                    self.result[i] = "temp"  # 避免身份证姓名对地址造成干扰
                    return

        # 如果姓名或名后面没有跟文字,但是有名 或姓名这个字段出现过的,去后面的集合为名字
        # 如果取的一个几个只有一个字,则接着取后面的集合,一般最多取2个集合就够了
        # 由于像新疆文、彝文这种类型的身份证,识别处理的集合值可能是英文,要进行去除
        indexName = -1
        for i in range(len(self.result)):
            txt = self.result[i]
            if "姓名" in txt or "名" in txt:
                indexName = i
                break
        if indexName == -1:
            for i in range(len(self.result)):
                txt = self.result[i]
                if "姓" in txt:
                    indexName = i
                    break
        if indexName == -1:
            return
        resName = self.result[indexName + 1]
        if len(resName) < 2:
            resName = resName + self.result[indexName + 2]
            self.res["name"] = resName
            self.result[indexName + 2] = "temp"  # 避免身份证姓名对地址造成干扰
        else:
            self.res["name"] = resName
            self.result[indexName + 1] = "temp"  # 避免身份证姓名对地址造成干扰

    def sex(self):
        """
        性别女民族汉
        """
        for i in range(len(self.result)):
            txt = self.result[i]
            if "男" in txt:
                self.res["gender"] = "男"

            elif "女" in txt:
                self.res["gender"] = "女"

    def national(self):
        # 性别女民族汉
        # 先判断是否有"民族xx"或"族xx"或"民xx"这种类型的数据,有的话获取xx的数据,然后在56个名族的字典里判断是否包含某个民族,包含则取对应的民族
        for i in range(len(self.result)):
            txt = self.result[i]
            if ("民族" in txt or "族" in txt or "民" in txt) and len(txt) > 2:
                resZ = re.findall("族[\u4e00-\u9fa5]+", txt)
                resM = re.findall("民[\u4e00-\u9fa5]+", txt)
                if len(resZ) > 0:
                    nationOcr = resZ[0].split("族")[-1]
                elif len(resM) > 0:
                    nationOcr = resM[0].split("民")[-1]

                for nation in self.nation_list:
                    if nation in nationOcr:
                        self.res["nationality"] = nation
                        self.result[i] = "nation"  # 避免民族对特殊情况下名字造成干扰
                        return
        # 如果 "民族" 或 "族" 和对应的民族是分开的,则记录对应对应的位置,取后一位的字符,同样去字典里判断
        indexNational = -1
        for i in range(len(self.result)):
            txt = self.result[i]
            if "族" in txt:
                indexNational = i
                break
        # 如果没有"民族"或 "族" ,则去判断是否含有"民",有则记录对应的位置,取后一位的字符,同样去字典里判断
        if indexNational == -1:
            for i in range(len(self.result)):
                txt = self.result[i]
                if "民" in txt:
                    indexNational = i
                    break
        if indexNational == -1:
            return
        national = self.result[indexNational + 1]
        for nation in self.nation_list:
            if nation in national:
                self.res["nationality"] = nation
                self.result[indexNational + 1] = "nation"  # 避免民族对特殊情况下名字造成干扰
                break

    def address(self):
        """
        地址
        """
        addString = []
        for i in range(len(self.result)):
            txt = self.result[i]
            # 这步的操作是去除下”公民身份号码“里的号对地址的干扰
            txt = txt.replace("号码", "")
            if "公民" in txt:
                txt = "temp"
            # 身份证地址    盟,旗,苏木,嘎查  蒙语行政区划  ‘大学’有些大学集体户的地址会写某某大学

            if (
                    "住址" in txt
                    or "址" in txt
                    or "省" in txt
                    or "市" in txt
                    or "县" in txt
                    or "街" in txt
                    or "乡" in txt
                    or "村" in txt
                    or "镇" in txt
                    or "区" in txt
                    or "城" in txt
                    or "室" in txt
                    or "组" in txt
                    or "号" in txt
                    or "栋" in txt
                    or "巷" in txt
                    or "盟" in txt
                    or "旗" in txt
                    or "苏木" in txt
                    or "嘎查" in txt
                    or "大学" in txt
            ):
                # 默认地址至少是在集合的第2位以后才会出现,避免经过上面的名字识别判断未能识别出名字,
                # 且名字含有以上的这些关键字照成被误以为是地址,默认地址的第一行的文字长度要大于7,只有取到了第一行的地址,才会继续往下取地址
                if i < 2 or len(addString) < 1 and len(txt) < 7:
                    continue
                    # 如果字段中含有"住址"、"省"、"址"则认为是地址的第一行,同时通过"址"
                # 这个字分割字符串
                if "住址" in txt or "省" in txt or "址" in txt:
                    # 通过"址"这个字分割字符串,取集合中的倒数第一个元素
                    addString.insert(0, txt.split("址")[-1])
                else:
                    addString.append(txt)
                self.result[i] = "temp"

        if len(addString) > 0:
            self.res["address"] = "".join(addString)
        else:
            self.res["address"] = ""

    def predict_name(self):
        """
        如果PaddleOCR返回的不是姓名xx连着的,则需要去猜测这个姓名,此处需要改进
        """
        for i in range(len(self.result)):
            txt = self.result[i]
            if self.res["name"] == "":
                if 1 < len(txt) < 5:
                    if (
                            "性别" not in txt
                            and "姓名" not in txt
                            and "民族" not in txt
                            and "住址" not in txt
                            and "出生" not in txt
                            and "号码" not in txt
                            and "身份" not in txt
                            and "nation" not in txt
                    ):
                        result = re.findall("[\u4e00-\u9fa5]{2,4}", txt)
                        if len(result) > 0:
                            self.res["name"] = result[0]
                            break
        for i in range(len(self.result)):
            txt = self.result[i]

    def run(self):
        self.full_name()
        self.sex()
        self.national()
        self.birth_no()
        self.address()
        self.predict_name()
        return json.dumps(self.out, ensure_ascii=False)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐