img_utils.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # coding=utf-8
  2. import logging
  3. import simplejson as json
  4. from aip import AipOcr
  5. from django.conf import settings
  6. from django.core.files.uploadedfile import UploadedFile
  7. from apilib.exceptions import BaiDuApiImageError, BaiDuApiNetError, BaiDuApiSysError
  8. from apilib.utils import split_addr
  9. from apilib.utils_datetime import to_datetime
  10. from apps.web.utils import LimitAttemptsManager
  11. logger = logging.getLogger(__name__)
  12. def parse_identify_image(result):
  13. if not isinstance(result, dict):
  14. raise BaiDuApiImageError(u"图像识别错误")
  15. # 状态字段一定会有 一定需要判断图像的状态
  16. if result["image_status"] != "normal":
  17. if result["image_status"] == "reversed_side":
  18. raise BaiDuApiImageError(u"身份证正反面颠倒,请确认身份证的国徽面或人像面")
  19. if result["image_status"] == "non_idcard":
  20. raise BaiDuApiImageError(u"上传的图片中不包含身份证,请确认")
  21. if result["image_status"] == "blurred":
  22. raise BaiDuApiImageError(u"身份证模糊,请重新上传")
  23. if result["image_status"] == "other_type_card":
  24. raise BaiDuApiImageError(u"上传证件类型错误,请重新上传")
  25. if result["image_status"] == "over_exposure":
  26. raise BaiDuApiImageError(u"身份证信息识别错误,请重新上传")
  27. if result["image_status"] == "over_dark":
  28. raise BaiDuApiImageError(u"身份证图像亮度过低,识别错误,请重新上传")
  29. if result["image_status"] == "unknown":
  30. raise BaiDuApiImageError(u"识别错误,请重新上传")
  31. # 证号状态只有在人像面才有
  32. if "idcard_number_type" in result and result["idcard_number_type"] != 1:
  33. if result["idcard_number_type"] == -1:
  34. raise BaiDuApiImageError(u"身份证人像面信息识别错误,请检查")
  35. if result["idcard_number_type"] == 0:
  36. raise BaiDuApiImageError(u"身份证证号不合法")
  37. if result["idcard_number_type"] == 2:
  38. raise BaiDuApiImageError(u"身份证证号和性别、出生信息都不一致,请检查")
  39. if result["idcard_number_type"] == 3:
  40. raise BaiDuApiImageError(u"身份证证号和出生信息不一致,请检查")
  41. if result["idcard_number_type"] == 4:
  42. raise BaiDuApiImageError(u"身份证证号和性别信息不一致,请检查")
  43. # 证件方向 选填
  44. if "direction" in result and result["direction"] != 0:
  45. raise BaiDuApiImageError(u"请调整上传图像方向,将上传图片摆正(可参考右侧图片示例)。")
  46. # 证件风险 选填 防止复印件
  47. if "risk_type" in result and result["risk_type"] != "normal":
  48. if result["risk_type"] == "copy":
  49. raise BaiDuApiImageError(u"上传身份证为复印件")
  50. if result["risk_type"] == "temporary":
  51. raise BaiDuApiImageError(u"上传身份证为临时件")
  52. if result["risk_type"] == "screen":
  53. raise BaiDuApiImageError(u"上传身份证为翻拍件")
  54. if result["risk_type"] == "unknown":
  55. raise BaiDuApiImageError(u"上传身份证异常")
  56. # 证件质量检查
  57. if "card_quality" in result:
  58. if result["card_quality"]["IsClear"] != 1:
  59. raise BaiDuApiImageError(u"身份证图像不清晰")
  60. if result["card_quality"]["IsComplete"] != 1:
  61. raise BaiDuApiImageError(u"身份证图像不完整")
  62. if result["card_quality"]["IsNoCover"] != 1:
  63. raise BaiDuApiImageError(u"身份证图像被遮挡")
  64. # 最后处理下识别的字段结果 注意对各种数据进行下转换
  65. data = dict()
  66. if u"住址" in result["words_result"]:
  67. data["province"], data["city"], data["area"], data["addr"] = split_addr(result["words_result"][u"住址"]["words"])
  68. if u"出生" in result["words_result"]:
  69. data["birthday"] = result["words_result"][u"出生"]["words"]
  70. if u"姓名" in result["words_result"]:
  71. data["name"] = result["words_result"][u"姓名"]["words"]
  72. if u"公民身份号码" in result["words_result"]:
  73. data["code"] = result["words_result"][u"公民身份号码"]["words"]
  74. if u"性别" in result["words_result"]:
  75. data["sex"] = result["words_result"][u"性别"]["words"]
  76. if u"民族" in result["words_result"]:
  77. data["nation"] = result["words_result"][u"民族"]["words"]
  78. if u"签发日期" in result["words_result"]:
  79. data["startTime"] = to_datetime(result["words_result"][u"签发日期"]["words"], "%Y%m%d").strftime("%Y-%m-%d")
  80. if u"失效日期" in result["words_result"]:
  81. try:
  82. data["endTime"] = to_datetime(result["words_result"][u"失效日期"]["words"], "%Y%m%d").strftime("%Y-%m-%d")
  83. except ValueError:
  84. data["endTime"] = result["words_result"][u"失效日期"]["words"]
  85. return data
  86. def parse_bank_image(result):
  87. if not isinstance(result, dict):
  88. raise BaiDuApiImageError(u"图像识别错误")
  89. if "direction" in result and result["direction"] != 0:
  90. raise BaiDuApiImageError(u"请调整上传图像方向,将上传图片摆正(可参考右侧图片示例)。")
  91. if result["result"]["bank_card_type"] == 0:
  92. raise BaiDuApiImageError(u"无法识别您的银行卡类型,请重新上传")
  93. data = {
  94. "bankCode": "".join(result["result"]["bank_card_number"].split()),
  95. }
  96. return data
  97. def parse_business_image(result):
  98. if not isinstance(result, dict):
  99. raise BaiDuApiImageError(u"图像识别错误")
  100. if "direction" in result and result["direction"] != 0:
  101. raise BaiDuApiImageError(u"请调整上传图像方向,将上传图片摆正(可参考右侧图片示例)。")
  102. if "risk_type" in result and result["risk_type"] != "normal":
  103. raise BaiDuApiImageError(u"营业执照异常")
  104. wordsResult = result["words_result"]
  105. data = dict()
  106. if u"社会信用代码" in wordsResult:
  107. data["busCode"] = wordsResult[u"社会信用代码"]["words"]
  108. if u"单位名称" in wordsResult:
  109. data["busName"] = wordsResult[u"单位名称"]["words"]
  110. if u"成立日期" in wordsResult:
  111. data["startTime"] = to_datetime(wordsResult[u"成立日期"]["words"], u"%Y年%m月%d日").strftime("%Y-%m-%d")
  112. if u"有效期" in wordsResult:
  113. try:
  114. data["endTime"] = to_datetime(wordsResult[u"有效期"]["words"], u"%Y年%m月%d日").strftime("%Y-%m-%d")
  115. except ValueError:
  116. data["endTime"] = wordsResult[u"有效期"]["words"]
  117. if u"地址" in wordsResult:
  118. data["province"], data["city"], data["area"], data["addr"] = split_addr(wordsResult[u"地址"]["words"])
  119. if u"法人" in wordsResult:
  120. data["legalName"] = wordsResult[u"法人"]["words"]
  121. return data
  122. class IDCardSide(object):
  123. PERSONAL = "front"
  124. EMBLEM = "back"
  125. class ImageRecognizer(object):
  126. """
  127. OCR 识别工具
  128. helps: https://cloud.baidu.com/doc/OCR/s/7kibizyfm#%E8%BA%AB%E4%BB%BD%E8%AF%81%E8%AF%86%E5%88%AB
  129. 当前版本不支持url的形式上传
  130. """
  131. def __init__(self, visitor, appid=None, appKey=None, secretKey=None, imageData=None, inMemFile=None):
  132. limiter = LimitAttemptsManager(visitor, self.__class__.__name__, maxTimes=30 if not settings.DEBUG else 9999)
  133. if limiter.is_exceeded_limit():
  134. raise BaiDuApiNetError(u"访问频率超限")
  135. limiter.incr()
  136. self._appid = appid or settings.BAIDU_IMAGE_RECOGNIZE_APP_ID
  137. self._appKey = appKey or settings.BAIDU_IMAGE_RECOGNIZE_APP_KEY
  138. self._appSecret = secretKey or settings.BAIDU_IMAGE_RECOGNIZE_APP_SECRET
  139. if not all([self._appid, self._appKey, self._appSecret]):
  140. raise BaiDuApiNetError(u"识别初始化失败")
  141. if imageData and isinstance(imageData, bytes):
  142. self._content = imageData
  143. elif inMemFile and isinstance(inMemFile, UploadedFile):
  144. self._content = b""
  145. for _image in inMemFile.chunks():
  146. self._content += _image
  147. else:
  148. self._content = None
  149. if not self._content:
  150. raise BaiDuApiImageError(u"无效的图像数据")
  151. self._client = None
  152. @property
  153. def client(self): # type:() -> AipOcr
  154. if not self._client:
  155. self._client = AipOcr(
  156. appId=self._appid, apiKey=self._appKey, secretKey=self._appSecret
  157. )
  158. return self._client
  159. @staticmethod
  160. def _handle_result(result, callback):
  161. """
  162. 对错误作出一定的解析
  163. """
  164. if not isinstance(result, dict):
  165. raise BaiDuApiImageError(u"图像识别失败(10002)")
  166. if "error_code" not in result:
  167. return callback(result) if callback else result
  168. error_code = result["error_code"]
  169. # 此类错误是一般是对方服务器错误,需要通知系统管理员及时提交工单
  170. if error_code in [
  171. 1,
  172. 100, 110, 111,
  173. 282000,
  174. 216100, 216101, 216102, 216103,
  175. 216110,
  176. 216630, 216634,
  177. 282003,
  178. 282005, 282006,
  179. 282110, 282111, 282112, 282113, 282114,
  180. 282808, 282809, 282810
  181. ]:
  182. raise BaiDuApiImageError(u"图像识别错误,请重新上传({}-{})。".format(error_code, result["error_msg"]))
  183. if error_code == 216200:
  184. raise BaiDuApiImageError(u"未检测到上传图像,请重新上传。")
  185. if error_code == 216201:
  186. raise BaiDuApiImageError(u"图片格式上传错误,请重新上传(支持图像格式为 PNG、JPG、JPEG、BMP)。")
  187. if error_code == 216202:
  188. raise BaiDuApiImageError(u"图片尺寸过大,请重新上传(图像不得超过4Mb, 分辨率不高于4096*4096)。")
  189. if error_code == 216631:
  190. raise BaiDuApiImageError(u"银行卡识别错误,出现此问题的一般原因为:您上传的图片非银行卡正面,上传了异形卡的图片或上传的银行卡正品图片不完整。")
  191. if error_code == 216633:
  192. raise BaiDuApiImageError(u"身份证识别错误,出现此问题的一般原因为:您上传了非身份证图片或您上传的身份证图片不完整")
  193. # 流量或者QPS 超限的错误
  194. if error_code in [17, 18, 19]:
  195. raise BaiDuApiSysError(u"额度超出限制。")
  196. # 其余的错误直接抛出
  197. raise BaiDuApiImageError(u"图像识别错误,请重新上传({}-{})。".format(error_code, result["error_msg"]))
  198. def recognize_identify_card(self, idCardSide, detectDirection=True, detectRisk=False, detectQuality=False, detectPhoto=False, detectCard=False, callback=None):
  199. """
  200. 识别身份证 目前后三个付费功能没有开通
  201. :param idCardSide: 自动检测身份证正反面,如果传参指定方向与图片相反,支持正常识别,返回参数image_status字段为"reversed_side"
  202. :param detectDirection: 是否检测图像朝向,默认不检测,即:false。朝向是指输入图像是正常方向、逆时针旋转90/180/270度
  203. :param detectRisk: 是否开启身份证风险类型(身份证复印件、临时身份证、身份证翻拍、修改过的身份证)检测功能
  204. :param detectQuality: 是否开启身份证质量类型(边框/四角不完整、头像或关键字段被遮挡/马赛克)检测功能
  205. :param detectPhoto: 是否检测头像内容,默认不检测
  206. :param detectCard: 是否检测身份证进行裁剪,默认不检测
  207. :param callback: 回调处理函数
  208. """
  209. options = {
  210. "detect_direction": json.dumps(detectDirection),
  211. "detect_risk": json.dumps(detectRisk),
  212. "detect_quality": json.dumps(detectQuality),
  213. "detect_photo": json.dumps(detectPhoto),
  214. "detect_card": json.dumps(detectCard)
  215. }
  216. result = self.client.idcard(self._content, idCardSide, options)
  217. return self._handle_result(result, callback)
  218. def recognize_bank_card(self, detectDirection=True, callback=None):
  219. """
  220. 识别银行卡
  221. 两个注意:1不能识别银行开户许可证 2识别出来的银行卡号不连续 需要后续存储的时候去掉空格
  222. :param detectDirection: 检测方向
  223. :param callback:
  224. """
  225. options = {
  226. "detect_direction": json.dumps(detectDirection)
  227. }
  228. result = self.client.bankcard(self._content, options)
  229. return self._handle_result(result, callback)
  230. def recognize_business_license(self, detectDirection=True, riskWarn=False, callback=None):
  231. options = {
  232. "detect_direction": json.dumps(detectDirection),
  233. "risk_warn": json.dumps(riskWarn),
  234. }
  235. result = self.client.businessLicense(self._content, options)
  236. return self._handle_result(result, callback)