123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- # coding=utf-8
- import logging
- import simplejson as json
- from aip import AipOcr
- from django.conf import settings
- from django.core.files.uploadedfile import UploadedFile
- from apilib.exceptions import BaiDuApiImageError, BaiDuApiNetError, BaiDuApiSysError
- from apilib.utils import split_addr
- from apilib.utils_datetime import to_datetime
- from apps.web.utils import LimitAttemptsManager
- logger = logging.getLogger(__name__)
- def parse_identify_image(result):
- if not isinstance(result, dict):
- raise BaiDuApiImageError(u"图像识别错误")
- # 状态字段一定会有 一定需要判断图像的状态
- if result["image_status"] != "normal":
- if result["image_status"] == "reversed_side":
- raise BaiDuApiImageError(u"身份证正反面颠倒,请确认身份证的国徽面或人像面")
- if result["image_status"] == "non_idcard":
- raise BaiDuApiImageError(u"上传的图片中不包含身份证,请确认")
- if result["image_status"] == "blurred":
- raise BaiDuApiImageError(u"身份证模糊,请重新上传")
- if result["image_status"] == "other_type_card":
- raise BaiDuApiImageError(u"上传证件类型错误,请重新上传")
- if result["image_status"] == "over_exposure":
- raise BaiDuApiImageError(u"身份证信息识别错误,请重新上传")
- if result["image_status"] == "over_dark":
- raise BaiDuApiImageError(u"身份证图像亮度过低,识别错误,请重新上传")
- if result["image_status"] == "unknown":
- raise BaiDuApiImageError(u"识别错误,请重新上传")
- # 证号状态只有在人像面才有
- if "idcard_number_type" in result and result["idcard_number_type"] != 1:
- if result["idcard_number_type"] == -1:
- raise BaiDuApiImageError(u"身份证人像面信息识别错误,请检查")
- if result["idcard_number_type"] == 0:
- raise BaiDuApiImageError(u"身份证证号不合法")
- if result["idcard_number_type"] == 2:
- raise BaiDuApiImageError(u"身份证证号和性别、出生信息都不一致,请检查")
- if result["idcard_number_type"] == 3:
- raise BaiDuApiImageError(u"身份证证号和出生信息不一致,请检查")
- if result["idcard_number_type"] == 4:
- raise BaiDuApiImageError(u"身份证证号和性别信息不一致,请检查")
- # 证件方向 选填
- if "direction" in result and result["direction"] != 0:
- raise BaiDuApiImageError(u"请调整上传图像方向,将上传图片摆正(可参考右侧图片示例)。")
- # 证件风险 选填 防止复印件
- if "risk_type" in result and result["risk_type"] != "normal":
- if result["risk_type"] == "copy":
- raise BaiDuApiImageError(u"上传身份证为复印件")
- if result["risk_type"] == "temporary":
- raise BaiDuApiImageError(u"上传身份证为临时件")
- if result["risk_type"] == "screen":
- raise BaiDuApiImageError(u"上传身份证为翻拍件")
- if result["risk_type"] == "unknown":
- raise BaiDuApiImageError(u"上传身份证异常")
- # 证件质量检查
- if "card_quality" in result:
- if result["card_quality"]["IsClear"] != 1:
- raise BaiDuApiImageError(u"身份证图像不清晰")
- if result["card_quality"]["IsComplete"] != 1:
- raise BaiDuApiImageError(u"身份证图像不完整")
- if result["card_quality"]["IsNoCover"] != 1:
- raise BaiDuApiImageError(u"身份证图像被遮挡")
- # 最后处理下识别的字段结果 注意对各种数据进行下转换
- data = dict()
- if u"住址" in result["words_result"]:
- data["province"], data["city"], data["area"], data["addr"] = split_addr(result["words_result"][u"住址"]["words"])
- if u"出生" in result["words_result"]:
- data["birthday"] = result["words_result"][u"出生"]["words"]
- if u"姓名" in result["words_result"]:
- data["name"] = result["words_result"][u"姓名"]["words"]
- if u"公民身份号码" in result["words_result"]:
- data["code"] = result["words_result"][u"公民身份号码"]["words"]
- if u"性别" in result["words_result"]:
- data["sex"] = result["words_result"][u"性别"]["words"]
- if u"民族" in result["words_result"]:
- data["nation"] = result["words_result"][u"民族"]["words"]
- if u"签发日期" in result["words_result"]:
- data["startTime"] = to_datetime(result["words_result"][u"签发日期"]["words"], "%Y%m%d").strftime("%Y-%m-%d")
- if u"失效日期" in result["words_result"]:
- try:
- data["endTime"] = to_datetime(result["words_result"][u"失效日期"]["words"], "%Y%m%d").strftime("%Y-%m-%d")
- except ValueError:
- data["endTime"] = result["words_result"][u"失效日期"]["words"]
- return data
- def parse_bank_image(result):
- if not isinstance(result, dict):
- raise BaiDuApiImageError(u"图像识别错误")
- if "direction" in result and result["direction"] != 0:
- raise BaiDuApiImageError(u"请调整上传图像方向,将上传图片摆正(可参考右侧图片示例)。")
- if result["result"]["bank_card_type"] == 0:
- raise BaiDuApiImageError(u"无法识别您的银行卡类型,请重新上传")
- data = {
- "bankCode": "".join(result["result"]["bank_card_number"].split()),
- }
- return data
- def parse_business_image(result):
- if not isinstance(result, dict):
- raise BaiDuApiImageError(u"图像识别错误")
- if "direction" in result and result["direction"] != 0:
- raise BaiDuApiImageError(u"请调整上传图像方向,将上传图片摆正(可参考右侧图片示例)。")
- if "risk_type" in result and result["risk_type"] != "normal":
- raise BaiDuApiImageError(u"营业执照异常")
- wordsResult = result["words_result"]
- data = dict()
- if u"社会信用代码" in wordsResult:
- data["busCode"] = wordsResult[u"社会信用代码"]["words"]
- if u"单位名称" in wordsResult:
- data["busName"] = wordsResult[u"单位名称"]["words"]
- if u"成立日期" in wordsResult:
- data["startTime"] = to_datetime(wordsResult[u"成立日期"]["words"], u"%Y年%m月%d日").strftime("%Y-%m-%d")
- if u"有效期" in wordsResult:
- try:
- data["endTime"] = to_datetime(wordsResult[u"有效期"]["words"], u"%Y年%m月%d日").strftime("%Y-%m-%d")
- except ValueError:
- data["endTime"] = wordsResult[u"有效期"]["words"]
- if u"地址" in wordsResult:
- data["province"], data["city"], data["area"], data["addr"] = split_addr(wordsResult[u"地址"]["words"])
- if u"法人" in wordsResult:
- data["legalName"] = wordsResult[u"法人"]["words"]
- return data
- class IDCardSide(object):
- PERSONAL = "front"
- EMBLEM = "back"
- class ImageRecognizer(object):
- """
- OCR 识别工具
- helps: https://cloud.baidu.com/doc/OCR/s/7kibizyfm#%E8%BA%AB%E4%BB%BD%E8%AF%81%E8%AF%86%E5%88%AB
- 当前版本不支持url的形式上传
- """
- def __init__(self, visitor, appid=None, appKey=None, secretKey=None, imageData=None, inMemFile=None):
- limiter = LimitAttemptsManager(visitor, self.__class__.__name__, maxTimes=30 if not settings.DEBUG else 9999)
- if limiter.is_exceeded_limit():
- raise BaiDuApiNetError(u"访问频率超限")
- limiter.incr()
- self._appid = appid or settings.BAIDU_IMAGE_RECOGNIZE_APP_ID
- self._appKey = appKey or settings.BAIDU_IMAGE_RECOGNIZE_APP_KEY
- self._appSecret = secretKey or settings.BAIDU_IMAGE_RECOGNIZE_APP_SECRET
- if not all([self._appid, self._appKey, self._appSecret]):
- raise BaiDuApiNetError(u"识别初始化失败")
- if imageData and isinstance(imageData, bytes):
- self._content = imageData
- elif inMemFile and isinstance(inMemFile, UploadedFile):
- self._content = b""
- for _image in inMemFile.chunks():
- self._content += _image
- else:
- self._content = None
- if not self._content:
- raise BaiDuApiImageError(u"无效的图像数据")
- self._client = None
- @property
- def client(self): # type:() -> AipOcr
- if not self._client:
- self._client = AipOcr(
- appId=self._appid, apiKey=self._appKey, secretKey=self._appSecret
- )
- return self._client
- @staticmethod
- def _handle_result(result, callback):
- """
- 对错误作出一定的解析
- """
- if not isinstance(result, dict):
- raise BaiDuApiImageError(u"图像识别失败(10002)")
- if "error_code" not in result:
- return callback(result) if callback else result
- error_code = result["error_code"]
- # 此类错误是一般是对方服务器错误,需要通知系统管理员及时提交工单
- if error_code in [
- 1,
- 100, 110, 111,
- 282000,
- 216100, 216101, 216102, 216103,
- 216110,
- 216630, 216634,
- 282003,
- 282005, 282006,
- 282110, 282111, 282112, 282113, 282114,
- 282808, 282809, 282810
- ]:
- raise BaiDuApiImageError(u"图像识别错误,请重新上传({}-{})。".format(error_code, result["error_msg"]))
- if error_code == 216200:
- raise BaiDuApiImageError(u"未检测到上传图像,请重新上传。")
- if error_code == 216201:
- raise BaiDuApiImageError(u"图片格式上传错误,请重新上传(支持图像格式为 PNG、JPG、JPEG、BMP)。")
- if error_code == 216202:
- raise BaiDuApiImageError(u"图片尺寸过大,请重新上传(图像不得超过4Mb, 分辨率不高于4096*4096)。")
- if error_code == 216631:
- raise BaiDuApiImageError(u"银行卡识别错误,出现此问题的一般原因为:您上传的图片非银行卡正面,上传了异形卡的图片或上传的银行卡正品图片不完整。")
- if error_code == 216633:
- raise BaiDuApiImageError(u"身份证识别错误,出现此问题的一般原因为:您上传了非身份证图片或您上传的身份证图片不完整")
- # 流量或者QPS 超限的错误
- if error_code in [17, 18, 19]:
- raise BaiDuApiSysError(u"额度超出限制。")
- # 其余的错误直接抛出
- raise BaiDuApiImageError(u"图像识别错误,请重新上传({}-{})。".format(error_code, result["error_msg"]))
- def recognize_identify_card(self, idCardSide, detectDirection=True, detectRisk=False, detectQuality=False, detectPhoto=False, detectCard=False, callback=None):
- """
- 识别身份证 目前后三个付费功能没有开通
- :param idCardSide: 自动检测身份证正反面,如果传参指定方向与图片相反,支持正常识别,返回参数image_status字段为"reversed_side"
- :param detectDirection: 是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度
- :param detectRisk: 是否开启身份证风险类型(身份证复印件、临时身份证、身份证翻拍、修改过的身份证)检测功能
- :param detectQuality: 是否开启身份证质量类型(边框/四角不完整、头像或关键字段被遮挡/马赛克)检测功能
- :param detectPhoto: 是否检测头像内容,默认不检测
- :param detectCard: 是否检测身份证进行裁剪,默认不检测
- :param callback: 回调处理函数
- """
- options = {
- "detect_direction": json.dumps(detectDirection),
- "detect_risk": json.dumps(detectRisk),
- "detect_quality": json.dumps(detectQuality),
- "detect_photo": json.dumps(detectPhoto),
- "detect_card": json.dumps(detectCard)
- }
- result = self.client.idcard(self._content, idCardSide, options)
- return self._handle_result(result, callback)
- def recognize_bank_card(self, detectDirection=True, callback=None):
- """
- 识别银行卡
- 两个注意:1不能识别银行开户许可证 2识别出来的银行卡号不连续 需要后续存储的时候去掉空格
- :param detectDirection: 检测方向
- :param callback:
- """
- options = {
- "detect_direction": json.dumps(detectDirection)
- }
- result = self.client.bankcard(self._content, options)
- return self._handle_result(result, callback)
- def recognize_business_license(self, detectDirection=True, riskWarn=False, callback=None):
- options = {
- "detect_direction": json.dumps(detectDirection),
- "risk_warn": json.dumps(riskWarn),
- }
- result = self.client.businessLicense(self._content, options)
- return self._handle_result(result, callback)
|