img_score_and_insert.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. import asyncio
  2. import aiohttp
  3. import aiofiles
  4. import json
  5. import os
  6. from typing import Dict, Any, Tuple
  7. from datetime import datetime
  8. # --- 配置区域 ---
  9. # 1. 服务 URL
  10. # INFERENCE_SERVICE_URL = "http://127.0.0.1:7744"
  11. # STORAGE_SERVICE_URL = "http://127.0.0.1:7745"
  12. INFERENCE_SERVICE_URL = "http://192.168.77.78:7744"
  13. STORAGE_SERVICE_URL = "http://192.168.77.78:7745"
  14. # 2. 要处理的卡片信息
  15. formate_time = datetime.now().strftime("%Y-%m-%d_%H:%M")
  16. CARD_NAME = f"卡 {formate_time}"
  17. # 是不是反光卡
  18. is_reflect = True
  19. if is_reflect:
  20. is_reflect_card = "true"
  21. else:
  22. is_reflect_card = "false"
  23. # 3. 四张卡片图片的本地路径
  24. front_face_img_path = r"C:\Code\ML\Image\Card\_250915_many_capture_img\_250915_1743_reflect_nature_defrct\2_front_coaxial_1_0.jpg"
  25. front_edge_img_path = r"C:\Code\ML\Image\Card\_250915_many_capture_img\_250915_1743_reflect_nature_defrct\2_front_ring_0_1.jpg"
  26. back_face_img_path = r"C:\Code\ML\Image\Card\_250915_many_capture_img\_250915_1743_reflect_nature_defrct\2_back_coaxial_1_0.jpg"
  27. back_edge_img_path = r"C:\Code\ML\Image\Card\_250915_many_capture_img\_250915_1743_reflect_nature_defrct\2_back_ring_0_1.jpg"
  28. IMAGE_PATHS = [
  29. front_edge_img_path,
  30. front_face_img_path,
  31. back_edge_img_path,
  32. back_face_img_path
  33. ]
  34. # 4. 推理服务需要的 score_type 参数
  35. SCORE_TYPES = [
  36. "front_corner_edge",
  37. "front_face",
  38. "back_corner_edge",
  39. "back_face"
  40. ]
  41. SCORE_TO_IMAGE_TYPE_MAP = {
  42. "front_corner_edge": "front_edge",
  43. "front_face": "front_face",
  44. "back_corner_edge": "back_edge",
  45. "back_face": "back_face"
  46. }
  47. # --- 脚本主逻辑 ---
  48. async def call_api_with_file(
  49. session: aiohttp.ClientSession,
  50. url: str,
  51. file_path: str,
  52. params: Dict[str, Any] = None,
  53. form_fields: Dict[str, Any] = None
  54. ) -> Tuple[int, bytes]:
  55. """通用的文件上传API调用函数 (从文件路径读取)"""
  56. form_data = aiohttp.FormData()
  57. if form_fields:
  58. for key, value in form_fields.items():
  59. form_data.add_field(key, str(value))
  60. async with aiofiles.open(file_path, 'rb') as f:
  61. content = await f.read()
  62. form_data.add_field(
  63. 'file',
  64. content,
  65. filename=os.path.basename(file_path),
  66. content_type='image/jpeg'
  67. )
  68. try:
  69. async with session.post(url, data=form_data, params=params) as response:
  70. response_content = await response.read()
  71. if not response.ok:
  72. print(f"错误: 调用 {url} 失败, 状态码: {response.status}")
  73. print(f" 错误详情: {response_content.decode(errors='ignore')}")
  74. return response.status, response_content
  75. except aiohttp.ClientConnectorError as e:
  76. print(f"错误: 无法连接到服务 {url} - {e}")
  77. return 503, b"Connection Error"
  78. async def process_single_image(
  79. session: aiohttp.ClientSession,
  80. image_path: str,
  81. score_type: str,
  82. is_reflect_card: str
  83. ) -> Dict[str, Any]:
  84. """处理单张图片:获取转正图和分数JSON"""
  85. print(f" 正在处理图片: {image_path} (类型: {score_type})")
  86. # 1. 获取转正后的图片
  87. rectify_url = f"{INFERENCE_SERVICE_URL}/api/card_inference/card_rectify_and_center"
  88. rectify_status, rectified_image_bytes = await call_api_with_file(
  89. session, url=rectify_url, file_path=image_path
  90. )
  91. if rectify_status >= 300:
  92. raise Exception(f"获取转正图失败: {image_path}")
  93. print(f" -> 已成功获取转正图")
  94. # 2. 获取分数JSON
  95. score_url = f"{INFERENCE_SERVICE_URL}/api/card_score/score_inference"
  96. score_params = {
  97. "score_type": score_type,
  98. "is_reflect_card": is_reflect_card
  99. }
  100. score_status, score_json_bytes = await call_api_with_file(
  101. session,
  102. url=score_url,
  103. file_path=image_path,
  104. params=score_params
  105. )
  106. if score_status >= 300:
  107. raise Exception(f"获取分数JSON失败: {image_path}")
  108. score_json = json.loads(score_json_bytes)
  109. print(f" -> 已成功获取分数JSON")
  110. return {
  111. "score_type": score_type,
  112. "rectified_image": rectified_image_bytes,
  113. "score_json": score_json
  114. }
  115. async def create_card_set(session: aiohttp.ClientSession, card_name: str) -> int:
  116. """创建一个新的卡组并返回其ID"""
  117. url = f"{STORAGE_SERVICE_URL}/api/cards/created"
  118. params = {'card_name': card_name}
  119. print(f"\n[步骤 2] 正在创建卡组,名称: '{card_name}'...")
  120. try:
  121. async with session.post(url, params=params) as response:
  122. if response.ok:
  123. data = await response.json()
  124. card_id = data.get('id')
  125. if card_id is not None:
  126. print(f" -> 成功创建卡组, ID: {card_id}")
  127. return card_id
  128. else:
  129. raise Exception("创建卡组API的响应中未找到'id'字段")
  130. else:
  131. error_text = await response.text()
  132. raise Exception(f"创建卡组失败, 状态码: {response.status}, 详情: {error_text}")
  133. except aiohttp.ClientConnectorError as e:
  134. raise Exception(f"无法连接到存储服务 {url} - {e}")
  135. # 【修改点】: 修正此函数
  136. async def upload_processed_data(
  137. session: aiohttp.ClientSession,
  138. card_id: int,
  139. processed_data: Dict[str, Any]
  140. ):
  141. """上传单张转正图和对应的JSON到存储服务"""
  142. score_type = processed_data['score_type']
  143. image_type_for_storage = SCORE_TO_IMAGE_TYPE_MAP[score_type]
  144. print(f" 正在上传图片, 类型: {image_type_for_storage}...")
  145. url = f"{STORAGE_SERVICE_URL}/api/images/insert/{card_id}"
  146. # 直接构建FormData,因为图片数据已经在内存中 (processed_data['rectified_image'])
  147. form_data = aiohttp.FormData()
  148. form_data.add_field('image_type', image_type_for_storage)
  149. form_data.add_field('json_data_str', json.dumps(processed_data['score_json'], ensure_ascii=False))
  150. form_data.add_field(
  151. 'image',
  152. processed_data['rectified_image'],
  153. filename='rectified.jpg',
  154. content_type='image/jpeg'
  155. )
  156. try:
  157. async with session.post(url, data=form_data) as response:
  158. if response.status == 201:
  159. print(f" -> 成功上传并关联图片: {image_type_for_storage}")
  160. else:
  161. error_text = await response.text()
  162. print(
  163. f" -> 错误: 上传失败! 类型: {image_type_for_storage}, 状态码: {response.status}, 详情: {error_text}")
  164. except aiohttp.ClientConnectorError as e:
  165. print(f" -> 错误: 无法连接到存储服务 {url} - {e}")
  166. async def main():
  167. """主执行函数"""
  168. async with aiohttp.ClientSession() as session:
  169. # 步骤 1: 并发处理所有图片, 获取转正图和分数
  170. print("[步骤 1] 开始并发处理所有图片...")
  171. process_tasks = []
  172. for path, s_type in zip(IMAGE_PATHS, SCORE_TYPES):
  173. if not os.path.exists(path):
  174. print(f"错误:文件不存在,请检查路径配置: {path}")
  175. return
  176. task = asyncio.create_task(process_single_image(session, path, s_type, is_reflect_card))
  177. process_tasks.append(task)
  178. try:
  179. processed_results = await asyncio.gather(*process_tasks)
  180. print(" -> 所有图片处理完成!")
  181. except Exception as e:
  182. print(f"\n在处理图片过程中发生错误: {e}")
  183. return
  184. # 步骤 2: 创建卡组
  185. try:
  186. card_id = await create_card_set(session, CARD_NAME)
  187. except Exception as e:
  188. print(f"\n创建卡组时发生严重错误: {e}")
  189. return
  190. # 步骤 3: 并发上传所有处理好的数据
  191. print(f"\n[步骤 3] 开始为卡组ID {card_id} 并发上传图片和数据...")
  192. upload_tasks = []
  193. for result in processed_results:
  194. task = asyncio.create_task(upload_processed_data(session, card_id, result))
  195. upload_tasks.append(task)
  196. await asyncio.gather(*upload_tasks)
  197. print(" -> 所有数据上传完成!")
  198. print("\n====================")
  199. print("所有流程执行完毕!")
  200. print("====================")
  201. if __name__ == "__main__":
  202. if len(IMAGE_PATHS) != 4 or len(SCORE_TYPES) != 4:
  203. print("错误: IMAGE_PATHS 和 SCORE_TYPES 列表的长度必须为4,请检查配置。")
  204. else:
  205. # 在 Windows 上使用 ProactorEventLoop 可能会更稳定
  206. if os.name == 'nt':
  207. asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
  208. asyncio.run(main())