251205更新
This commit is contained in:
parent
00ff6a5577
commit
973ca8113c
|
|
@ -90,7 +90,7 @@ class MySQLClient:
|
||||||
class CacheClient:
|
class CacheClient:
|
||||||
"""缓存客户端"""
|
"""缓存客户端"""
|
||||||
|
|
||||||
def __init__(self, cache_ttl: int = 360, database: str = "Caches.db"):
|
def __init__(self, cache_ttl: int = 360, database: str = "SQLite.db"):
|
||||||
"""
|
"""
|
||||||
:param cache_ttl: 缓存生存时间,单位为天
|
:param cache_ttl: 缓存生存时间,单位为天
|
||||||
:param database: 缓存数据库名称
|
:param database: 缓存数据库名称
|
||||||
|
|
@ -154,20 +154,31 @@ class CacheClient:
|
||||||
:return: 缓存
|
:return: 缓存
|
||||||
"""
|
"""
|
||||||
with threading.Lock(): # 线程锁,保证并发安全
|
with threading.Lock(): # 线程锁,保证并发安全
|
||||||
with self.connection.cursor() as cursor:
|
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
|
# 创建游标
|
||||||
|
cursor = self.connection.cursor()
|
||||||
# 根据缓存唯一标识查询有效缓存
|
# 根据缓存唯一标识查询有效缓存
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT cache FROM caches WHERE guid = ? AND timestamp >= ?",
|
"SELECT cache FROM caches WHERE guid = ? AND timestamp >= ?",
|
||||||
(guid, time.time() - self.cache_ttl * 86400),
|
(guid, time.time() - self.cache_ttl * 86400),
|
||||||
)
|
)
|
||||||
if result := cursor.fetchone():
|
if result := cursor.fetchone():
|
||||||
|
print()
|
||||||
return json.loads(result[0])
|
return json.loads(result[0])
|
||||||
return None
|
return None
|
||||||
|
# 若发生异常则回滚事务并返回None
|
||||||
except Exception:
|
except Exception:
|
||||||
self.connection.rollback()
|
self.connection.rollback()
|
||||||
return None
|
return None
|
||||||
|
finally:
|
||||||
|
# 确保游标关闭(关键:释放资源)
|
||||||
|
if cursor:
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
cursor.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
def update(self, guid: str, cache: Dict) -> bool:
|
def update(self, guid: str, cache: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|
@ -177,9 +188,10 @@ class CacheClient:
|
||||||
:return: 成功返回True,失败返回False
|
:return: 成功返回True,失败返回False
|
||||||
"""
|
"""
|
||||||
with threading.Lock(): # 线程锁,保证并发安全
|
with threading.Lock(): # 线程锁,保证并发安全
|
||||||
with self.connection.cursor() as cursor:
|
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
try:
|
try:
|
||||||
|
# 创建游标
|
||||||
|
cursor = self.connection.cursor()
|
||||||
# 新增或覆盖缓存
|
# 新增或覆盖缓存
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"INSERT OR REPLACE INTO caches (guid, cache, timestamp) VALUES (?, ?, ?)",
|
"INSERT OR REPLACE INTO caches (guid, cache, timestamp) VALUES (?, ?, ?)",
|
||||||
|
|
@ -192,9 +204,18 @@ class CacheClient:
|
||||||
# 提交事务
|
# 提交事务
|
||||||
self.connection.commit()
|
self.connection.commit()
|
||||||
return True
|
return True
|
||||||
|
# 若发生异常则回滚事务并返回None
|
||||||
except Exception:
|
except Exception:
|
||||||
self.connection.rollback() # 异常回滚事务
|
self.connection.rollback()
|
||||||
return False
|
return False
|
||||||
|
finally:
|
||||||
|
# 确保游标关闭(关键:释放资源)
|
||||||
|
if cursor:
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
cursor.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
218
票据理赔自动化/main.py
218
票据理赔自动化/main.py
|
|
@ -5,24 +5,23 @@
|
||||||
功能清单
|
功能清单
|
||||||
https://liubiren.feishu.cn/docx/WFjTdBpzroUjQvxxrNIcKvGnneh?from=from_copylink
|
https://liubiren.feishu.cn/docx/WFjTdBpzroUjQvxxrNIcKvGnneh?from=from_copylink
|
||||||
"""
|
"""
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal, ROUND_HALF_UP
|
from decimal import Decimal, ROUND_HALF_UP
|
||||||
|
from hashlib import md5
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy
|
import numpy
|
||||||
import pandas
|
|
||||||
from dateutil.parser import parse
|
from dateutil.parser import parse
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from jionlp import parse_location
|
|
||||||
from zen import ZenDecision, ZenEngine
|
from zen import ZenDecision, ZenEngine
|
||||||
|
|
||||||
from utils.client import Authenticator, HTTPClient
|
from utils.client import Authenticator, HTTPClient, CacheClient
|
||||||
|
|
||||||
|
|
||||||
# from utils.ocr import fuzzy_match
|
# from utils.ocr import fuzzy_match
|
||||||
|
|
@ -33,111 +32,120 @@ from utils.client import Authenticator, HTTPClient
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
|
|
||||||
def image_read():
|
# noinspection PyShadowingNames
|
||||||
"""本地读取影像件"""
|
def image_read(
|
||||||
# 影像件读取(默认转为单通道灰度图,实际需下载影像件)
|
image_path: Path,
|
||||||
image_ndarray = cv2.imread(globals()["image_path"].as_posix(), cv2.IMREAD_GRAYSCALE)
|
) -> Tuple[numpy.ndarray | None, str | None, bytes | None]:
|
||||||
|
"""
|
||||||
|
本地打开并读取影像件
|
||||||
|
:param image_path: 影像件路径对象
|
||||||
|
:return: 影像件数组、影像件格式和影像件字节流
|
||||||
|
"""
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
# 影像件打开并读取(默认转为单通道灰度图)
|
||||||
|
image_ndarray = cv2.imread(image_path.as_posix(), cv2.IMREAD_GRAYSCALE)
|
||||||
if image_ndarray is None:
|
if image_ndarray is None:
|
||||||
return None
|
raise RuntimeError("影像件打开并读取发生异常")
|
||||||
|
|
||||||
# 编码为图像字节流
|
|
||||||
# noinspection PyShadowingNames
|
|
||||||
success, image_bytes = cv2.imencode(f".{globals()["image_format"]}", image_ndarray)
|
|
||||||
if not success or image_bytes is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return image_bytes
|
|
||||||
|
|
||||||
|
|
||||||
def images_compression(**kwargs) -> tuple[str | None, str | None]:
|
|
||||||
"""影像件压缩并BASE64编码"""
|
|
||||||
|
|
||||||
# 影像件格式
|
# 影像件格式
|
||||||
image_format = kwargs.get("image_format", globals()["image_format"])
|
image_format = image_path.suffix.lower()
|
||||||
if image_format is None:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# 影像件
|
# 按照影像件格式将影像件数组编码
|
||||||
image = kwargs.get("image", globals()["image"])
|
success, image_ndarray_encoded = cv2.imencode(image_format, image_ndarray)
|
||||||
if image is None:
|
if not success or image_ndarray_encoded is None:
|
||||||
return None, None
|
raise RuntimeError("编码为图像字节数组发生异常")
|
||||||
|
# 将编码后图像数组转为字节流
|
||||||
|
image_bytes = image_ndarray_encoded.tobytes()
|
||||||
|
|
||||||
# 编码为图像字节流
|
return image_ndarray, image_format, image_bytes
|
||||||
success, image_bytes = cv2.imencode(ext=f".{image_format}", img=image)
|
except Exception:
|
||||||
# 若发生异常则返回NONE
|
return None, None, None
|
||||||
if not success or image_bytes is None:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# 生成影像件唯一标识
|
|
||||||
image_guid = hashlib.md5(image_bytes.tobytes()).hexdigest().upper()
|
|
||||||
|
|
||||||
# BASE64编码
|
# noinspection PyShadowingNames
|
||||||
image_base64 = b64encode(image_bytes.tobytes()).decode("utf-8")
|
def images_compress(
|
||||||
|
image_ndarray, image_format, image_bytes, image_size_specified=2
|
||||||
|
) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
影像件压缩
|
||||||
|
:param image_ndarray: 影像件数组
|
||||||
|
:param image_format: 影像件格式
|
||||||
|
:param image_bytes: 影像件字节流
|
||||||
|
:param image_size_specified: 指定影像件大小,单位为兆字节(MB)
|
||||||
|
:return: 压缩后影像件BASE64编码
|
||||||
|
"""
|
||||||
|
# 将指定影像件大小单位由兆字节转为字节
|
||||||
|
image_size_specified = image_size_specified * 1024 * 1024
|
||||||
|
|
||||||
# 将指定影像件大小单位由MB转为B
|
# 影像件BASE64编码
|
||||||
image_size_specified = kwargs.get("image_size_specified", 2) * 1048576
|
image_base64 = b64encode(image_bytes).decode("utf-8")
|
||||||
|
|
||||||
# 若影像件BASE64编码后大小小于指定影像件大小则返回
|
|
||||||
if len(image_base64) <= image_size_specified:
|
if len(image_base64) <= image_size_specified:
|
||||||
return image_guid, image_base64
|
return image_base64
|
||||||
|
|
||||||
# 双循环压缩影像件
|
# 通过调整影像件质量和尺寸达到压缩影像件目的
|
||||||
# 外循环压缩:通过降低影像件质量实现压缩影像件大小
|
# 外循环压缩:通过调整影像件质量实现压缩影像件大小
|
||||||
for quality in range(90, 0, -10):
|
for quality in range(90, 50, -10):
|
||||||
|
image_ndarray_copy = image_ndarray.copy()
|
||||||
image_copy = image.copy()
|
# 内循环压缩:通过调整影像件尺寸实现压缩影像件大小
|
||||||
|
|
||||||
# 内循环压缩:通过等比例调整影像件尺寸实现压缩影像件大小
|
|
||||||
for i in range(25):
|
for i in range(25):
|
||||||
|
# 按照影像件格式和影像件质量将影像件数组编码
|
||||||
success, image_bytes = cv2.imencode(
|
success, image_ndarray_encoded = cv2.imencode(
|
||||||
ext=f".{image_format}",
|
image_format,
|
||||||
img=image_copy,
|
image_ndarray_copy,
|
||||||
params=(
|
params=(
|
||||||
[cv2.IMWRITE_PNG_COMPRESSION, 10 - quality // 10]
|
[cv2.IMWRITE_PNG_COMPRESSION, 10 - quality // 10]
|
||||||
if image_format == "png"
|
if image_format == "png"
|
||||||
else [cv2.IMWRITE_JPEG_QUALITY, quality]
|
else [cv2.IMWRITE_JPEG_QUALITY, quality]
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if not success or image_bytes is None:
|
if not success or image_ndarray_encoded is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
image_base64 = b64encode(image_bytes.tobytes()).decode("utf-8")
|
|
||||||
if len(image_base64) <= image_size_specified:
|
|
||||||
return image_guid, image_base64
|
|
||||||
|
|
||||||
# 调整影像件尺寸
|
|
||||||
image_copy = cv2.resize(
|
|
||||||
src=image_copy,
|
|
||||||
dsize=(int(image_copy.shape[0] * 0.9), int(image_copy.shape[1] * 0.9)),
|
|
||||||
interpolation=cv2.INTER_AREA,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 若调整后影像件尺寸中长或宽小于200像素则停止调整影像件尺寸
|
|
||||||
if min(image_copy.shape[:2]) < 200:
|
|
||||||
break
|
|
||||||
|
|
||||||
# 若仍未压缩至指定影像件大小则返回NONE
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
|
|
||||||
def images_classification(**kwargs) -> tuple[str | None, str | None]:
|
|
||||||
"""影像件分类"""
|
|
||||||
|
|
||||||
# 影像件全局唯一标识:优先使用关键词变量,其次使用全局变量,再次使用随机唯一标识
|
|
||||||
image_guid = kwargs.get(
|
|
||||||
"image_guid", globals().get("image_guid", uuid.uuid4().hex.upper())
|
|
||||||
)
|
|
||||||
|
|
||||||
# 影像件格式
|
|
||||||
image_format = kwargs.get("image_format", globals()["image_format"])
|
|
||||||
if image_format is None:
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
# 影像件BASE64编码
|
# 影像件BASE64编码
|
||||||
image_base64 = kwargs.get("image_base64", globals()["image_base64"])
|
image_base64 = b64encode(image_ndarray_encoded.tobytes()).decode("utf-8")
|
||||||
if image_base64 is None:
|
if len(image_base64) <= image_size_specified:
|
||||||
return None, None
|
return image_base64
|
||||||
|
|
||||||
|
# 调整影像件尺寸
|
||||||
|
image_ndarray_copy = cv2.resize(
|
||||||
|
image_ndarray_copy,
|
||||||
|
(
|
||||||
|
int(image_ndarray_copy.shape[0] * 0.9),
|
||||||
|
int(image_ndarray_copy.shape[1] * 0.9),
|
||||||
|
),
|
||||||
|
interpolation=cv2.INTER_AREA,
|
||||||
|
)
|
||||||
|
# 若调整后影像件尺寸中长或宽小于350像素则停止调整影像件尺寸
|
||||||
|
if min(image_ndarray_copy.shape[:2]) < 350:
|
||||||
|
break
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def images_classify(
|
||||||
|
image, image_ndarray, image_format, image_bytes
|
||||||
|
) -> tuple[str | None, str | None]:
|
||||||
|
"""
|
||||||
|
影像件分类并旋正
|
||||||
|
:param image: 影像件数据
|
||||||
|
:param image_ndarray: 影像件数组
|
||||||
|
:param image_format: 影像件格式
|
||||||
|
:param image_bytes: 影像件字节流
|
||||||
|
:return: 压缩后影像件BASE64编码
|
||||||
|
"""
|
||||||
|
# 影像件唯一标识
|
||||||
|
image_uuid = image["影像件唯一标识"]
|
||||||
|
with CacheClient() as client:
|
||||||
|
# 根据作业环节和影像件唯一标识生成缓存唯一标识
|
||||||
|
cache_guid = md5(("初审" + image_uuid).encode("utf-8")).hexdigest().upper()
|
||||||
|
cache = client.query(cache_guid)
|
||||||
|
|
||||||
|
if cache is not None:
|
||||||
|
# 影像件类型
|
||||||
|
image_type = cache["image_type"]
|
||||||
|
# 影像件方向
|
||||||
|
image_o
|
||||||
|
|
||||||
# 请求深圳快瞳影像件分类接口
|
# 请求深圳快瞳影像件分类接口
|
||||||
response = globals()["http_client"].post(
|
response = globals()["http_client"].post(
|
||||||
|
|
@ -1180,39 +1188,38 @@ if __name__ == "__main__":
|
||||||
for image_index, image_path in enumerate(
|
for image_index, image_path in enumerate(
|
||||||
sorted(
|
sorted(
|
||||||
[
|
[
|
||||||
image_path
|
x
|
||||||
for image_path in case_path.glob(pattern="*")
|
for x in case_path.glob(pattern="*")
|
||||||
if image_path.is_file()
|
if x.is_file() and x.suffix.lower() in [".jpg", ".jpeg", ".png"]
|
||||||
and image_path.suffix.lower() in [".jpg", ".jpeg", ".png"]
|
|
||||||
], # 实际作业亦仅支持JPG、JPEG或PNG
|
], # 实际作业亦仅支持JPG、JPEG或PNG
|
||||||
key=lambda x: x.name,
|
key=lambda x: x.stat().st_ctime, # 根据影像件创建时间顺序排序
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
):
|
):
|
||||||
# 初始化影像件数据
|
# 初始化影像件数据
|
||||||
image = {
|
image = {
|
||||||
"原始影像件": {
|
"原始影像件": {
|
||||||
"影像件地址": image_path.as_posix(),
|
"影像件地址": image_path.as_posix(), # 将影像件路径对象转为字符串
|
||||||
"影像件名称": (image_name := image_path.stem),
|
"影像件名称": (image_name := image_path.stem),
|
||||||
"影像件格式": (image_format := image_path.suffix.lower()),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# 本地读取影像件(实际从影像件服务器下载)
|
# 本地打开并读取影像件(实际作业为从作业系统的影像件服务器下载并读取影像件,因赔案签收时会转存影像件至影像件服务器,若下载并读取影像件发生异常则技术支持排查)
|
||||||
image_bytes = image_read()
|
image_ndarray, image_format, image_bytes = image_read(image_path)
|
||||||
if image_bytes is None:
|
# 若本地打开并读取影像件发生异常则跳过该影像件
|
||||||
# noinspection PyTypeChecker
|
if image_format is None or image_bytes is None:
|
||||||
image["影像件唯一标识"] = None
|
raise RuntimeError("本地打开并读取影像件发生异常")
|
||||||
continue
|
|
||||||
|
|
||||||
|
image["原始影像件"]["影像件格式"] = image_format
|
||||||
# 生成影像件唯一标识
|
# 生成影像件唯一标识
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
image["影像件唯一标识"] = (
|
image["影像件唯一标识"] = md5(image_bytes).hexdigest().upper()
|
||||||
image_guid := hashlib.md5(image_bytes.tobytes()).hexdigest().upper()
|
|
||||||
)
|
# 影像件分类
|
||||||
|
|
||||||
print(image)
|
print(image)
|
||||||
exit()
|
exit()
|
||||||
|
"""
|
||||||
|
|
||||||
# 影像件压缩(输出BASE64编码)
|
# 影像件压缩(输出BASE64编码)
|
||||||
image_guid, image_base64 = images_compression()
|
image_guid, image_base64 = images_compression()
|
||||||
|
|
@ -1586,3 +1593,4 @@ if __name__ == "__main__":
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
"""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue