test_aliyun_driving_license_ocr.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """本地驾驶证图片 → 阿里云 RecognizeDrivingLicense 冒烟测试。
  4. 用法(在 backend 目录):
  5. .ocr-venv/bin/python scripts/test_aliyun_driving_license_ocr.py
  6. .ocr-venv/bin/python scripts/test_aliyun_driving_license_ocr.py ../驾驶证1.png
  7. """
  8. from __future__ import annotations
  9. import io
  10. import json
  11. import os
  12. import re
  13. import sys
  14. from pathlib import Path
  15. from dotenv import load_dotenv
  16. from PIL import Image
  17. try:
  18. from alibabacloud_ocr_api20210707.client import Client as OcrClient
  19. from alibabacloud_ocr_api20210707 import models as ocr_models
  20. from alibabacloud_tea_openapi import models as open_api_models
  21. from alibabacloud_tea_util import models as util_models
  22. except ImportError as exc:
  23. print('请先安装 SDK: .ocr-venv/bin/pip install alibabacloud_ocr_api20210707 -i https://pypi.tuna.tsinghua.edu.cn/simple')
  24. raise SystemExit(1) from exc
  25. BACKEND_DIR = Path(__file__).resolve().parents[1]
  26. DEFAULT_IMAGE = BACKEND_DIR.parent / '驾驶证1.png'
  27. MAX_UPLOAD_BYTES = 1_500_000
  28. def load_ocr_settings() -> tuple[str, str, str]:
  29. load_dotenv(BACKEND_DIR / '.env')
  30. ak = (os.getenv('ALIYUN_OCR_ACCESS_KEY_ID') or '').strip()
  31. sk = (os.getenv('ALIYUN_OCR_ACCESS_KEY_SECRET') or '').strip()
  32. endpoint = (os.getenv('ALIYUN_OCR_ENDPOINT') or 'ocr-api.cn-hangzhou.aliyuncs.com').strip()
  33. if not ak or not sk:
  34. raise SystemExit('未配置 ALIYUN_OCR_ACCESS_KEY_ID / ALIYUN_OCR_ACCESS_KEY_SECRET')
  35. return ak, sk, endpoint
  36. def compress_image(path: Path) -> bytes:
  37. raw = path.read_bytes()
  38. if len(raw) <= MAX_UPLOAD_BYTES:
  39. print(f'图片大小 {len(raw)} bytes,直接上传')
  40. return raw
  41. print(f'图片较大 ({len(raw)} bytes),压缩到约 {MAX_UPLOAD_BYTES} bytes 以内…')
  42. img = Image.open(io.BytesIO(raw))
  43. if img.mode not in ('RGB', 'L'):
  44. img = img.convert('RGB')
  45. quality = 85
  46. width, height = img.size
  47. buf = io.BytesIO()
  48. while True:
  49. buf.seek(0)
  50. buf.truncate(0)
  51. img.save(buf, format='JPEG', quality=quality, optimize=True)
  52. size = buf.tell()
  53. if size <= MAX_UPLOAD_BYTES or quality <= 40:
  54. print(f'压缩后 {size} bytes (quality={quality}, size={img.size})')
  55. return buf.getvalue()
  56. quality -= 10
  57. if size > MAX_UPLOAD_BYTES * 1.5:
  58. width = int(width * 0.85)
  59. height = int(height * 0.85)
  60. img = img.resize((width, height), Image.Resampling.LANCZOS)
  61. def parse_expire_date(valid_period: str | None) -> str | None:
  62. if not valid_period:
  63. return None
  64. text = str(valid_period).strip()
  65. if '长期' in text:
  66. return None
  67. # 支持: 2017-01-26至2027-01-26 / 2017.01.26-2027.01.26 / 2017.01.26至2027.01.26
  68. m = re.search(
  69. r'(\d{4})[.\-/年](\d{1,2})[.\-/月](\d{1,2}).*?[至\-—~]\s*(\d{4})[.\-/年](\d{1,2})[.\-/月](\d{1,2})',
  70. text,
  71. )
  72. if m:
  73. y, mo, d = m.group(4), m.group(5), m.group(6)
  74. return f'{int(y):04d}-{int(mo):02d}-{int(d):02d}'
  75. m2 = re.search(r'(\d{4})[.\-/](\d{1,2})[.\-/](\d{1,2})\s*$', text)
  76. if m2:
  77. return f'{int(m2.group(1)):04d}-{int(m2.group(2)):02d}-{int(m2.group(3)):02d}'
  78. return None
  79. def main() -> None:
  80. image_path = Path(sys.argv[1]).expanduser().resolve() if len(sys.argv) > 1 else DEFAULT_IMAGE
  81. if not image_path.is_file():
  82. raise SystemExit(f'图片不存在: {image_path}')
  83. ak, sk, endpoint = load_ocr_settings()
  84. image_bytes = compress_image(image_path)
  85. client = OcrClient(
  86. open_api_models.Config(
  87. access_key_id=ak,
  88. access_key_secret=sk,
  89. endpoint=endpoint,
  90. )
  91. )
  92. request = ocr_models.RecognizeDrivingLicenseRequest(body=io.BytesIO(image_bytes))
  93. runtime = util_models.RuntimeOptions()
  94. print(f'调用 RecognizeDrivingLicense … endpoint={endpoint}')
  95. resp = client.recognize_driving_license_with_options(request, runtime)
  96. body = resp.body
  97. if getattr(body, 'code', None):
  98. print('识别失败:')
  99. print(' code:', body.code)
  100. print(' message:', body.message)
  101. print(' requestId:', getattr(body, 'request_id', None))
  102. raise SystemExit(2)
  103. raw_data = body.data
  104. data = json.loads(raw_data) if isinstance(raw_data, str) else raw_data
  105. print('\n===== 原始 Data =====')
  106. print(json.dumps(data, ensure_ascii=False, indent=2))
  107. face = (((data or {}).get('data') or {}).get('face') or {}).get('data') or {}
  108. cert_no = face.get('licenseNumber')
  109. valid_period = face.get('validPeriod')
  110. expire_date = parse_expire_date(valid_period)
  111. print('\n===== 业务回填字段(对应证书表)=====')
  112. print('证号 certNo :', cert_no)
  113. print('有效期原文 :', valid_period)
  114. print('有效期 expireDate:', expire_date)
  115. print('姓名 :', face.get('name'))
  116. print('准驾类型 :', face.get('approvedType'))
  117. print('requestId :', getattr(body, 'request_id', None))
  118. if __name__ == '__main__':
  119. main()