img_score_and_insert2_单背面.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. import asyncio
  2. import aiohttp
  3. import aiofiles
  4. import json
  5. import os
  6. from typing import Dict, Any, Tuple, List
  7. from datetime import datetime
  8. # --- 配置区域 (可根据需要修改) ---
  9. INFERENCE_SERVICE_URL = "http://192.168.77.249:7744"
  10. STORAGE_SERVICE_URL = "http://192.168.77.249:7745"
  11. # 固定的处理类型映射
  12. SCORE_TYPES = [
  13. "front_corner_edge",
  14. "front_face",
  15. "back_corner_edge",
  16. "back_face"
  17. ]
  18. SCORE_TO_IMAGE_TYPE_MAP = {
  19. "front_corner_edge": "front_edge",
  20. "front_face": "front_face",
  21. "back_corner_edge": "back_edge",
  22. "back_face": "back_face"
  23. }
  24. # --- 辅助功能函数 (内部逻辑) ---
  25. async def call_api_with_file(
  26. session: aiohttp.ClientSession,
  27. url: str,
  28. file_path: str,
  29. params: Dict[str, Any] = None,
  30. form_fields: Dict[str, Any] = None
  31. ) -> Tuple[int, bytes]:
  32. """通用的文件上传API调用函数"""
  33. form_data = aiohttp.FormData()
  34. if form_fields:
  35. for key, value in form_fields.items():
  36. form_data.add_field(key, str(value))
  37. # 这里的 file_path 必须保证有效,否则 open 会报错
  38. async with aiofiles.open(file_path, 'rb') as f:
  39. content = await f.read()
  40. form_data.add_field(
  41. 'file',
  42. content,
  43. filename=os.path.basename(file_path),
  44. content_type='image/jpeg'
  45. )
  46. try:
  47. async with session.post(url, data=form_data, params=params) as response:
  48. response_content = await response.read()
  49. if not response.ok:
  50. print(f"错误: 调用 {url} 失败, 状态码: {response.status}")
  51. return response.status, response_content
  52. except aiohttp.ClientConnectorError as e:
  53. print(f"错误: 无法连接到服务 {url} - {e}")
  54. return 503, b"Connection Error"
  55. async def process_single_image(
  56. session: aiohttp.ClientSession,
  57. image_path: str,
  58. score_type: str,
  59. is_reflect_card: str
  60. ) -> Dict[str, Any]:
  61. """处理单张图片:获取转正图和分数JSON"""
  62. print(f" 正在处理图片: {os.path.basename(image_path)} ({score_type})")
  63. # 1. 获取转正后的图片
  64. rectify_url = f"{INFERENCE_SERVICE_URL}/api/card_inference/card_rectify_and_center"
  65. rectify_status, rectified_image_bytes = await call_api_with_file(
  66. session, url=rectify_url, file_path=image_path
  67. )
  68. if rectify_status >= 300:
  69. raise Exception(f"获取转正图失败: {image_path}")
  70. # 2. 获取分数JSON
  71. score_url = f"{INFERENCE_SERVICE_URL}/api/card_score/score_inference"
  72. score_params = {
  73. "score_type": score_type,
  74. "is_reflect_card": is_reflect_card
  75. }
  76. score_status, score_json_bytes = await call_api_with_file(
  77. session,
  78. url=score_url,
  79. file_path=image_path,
  80. params=score_params
  81. )
  82. if score_status >= 300:
  83. raise Exception(f"获取分数JSON失败: {image_path}")
  84. score_json = json.loads(score_json_bytes)
  85. return {
  86. "score_type": score_type,
  87. "rectified_image": rectified_image_bytes,
  88. "score_json": score_json
  89. }
  90. async def create_card_set(session: aiohttp.ClientSession, card_name: str) -> int:
  91. """创建一个新的卡组并返回其ID"""
  92. url = f"{STORAGE_SERVICE_URL}/api/cards/created"
  93. params = {'card_name': card_name}
  94. print(f"\n[步骤 2] 创建卡组: '{card_name}'")
  95. try:
  96. async with session.post(url, params=params) as response:
  97. if response.ok:
  98. data = await response.json()
  99. card_id = data.get('id')
  100. if card_id is not None:
  101. print(f" -> 成功创建卡组 ID: {card_id}")
  102. return card_id
  103. raise Exception("响应中未找到 'id' 字段")
  104. else:
  105. raise Exception(f"状态码: {response.status}")
  106. except Exception as e:
  107. raise Exception(f"创建卡组失败: {e}")
  108. async def upload_processed_data(
  109. session: aiohttp.ClientSession,
  110. card_id: int,
  111. processed_data: Dict[str, Any]
  112. ):
  113. """上传单张转正图和对应的JSON"""
  114. score_type = processed_data['score_type']
  115. image_type = SCORE_TO_IMAGE_TYPE_MAP[score_type]
  116. url = f"{STORAGE_SERVICE_URL}/api/images/insert/{card_id}"
  117. form_data = aiohttp.FormData()
  118. form_data.add_field('image_type', image_type)
  119. form_data.add_field('json_data_str', json.dumps(processed_data['score_json'], ensure_ascii=False))
  120. form_data.add_field(
  121. 'image',
  122. processed_data['rectified_image'],
  123. filename='rectified.jpg',
  124. content_type='image/jpeg'
  125. )
  126. try:
  127. async with session.post(url, data=form_data) as response:
  128. if response.status == 201:
  129. print(f" -> 上传成功: {image_type} (CardID: {card_id})")
  130. else:
  131. print(f" -> 上传失败 ({image_type}): {response.status}")
  132. except Exception as e:
  133. print(f" -> 上传异常 ({image_type}): {e}")
  134. # --- 核心 API 函数 (已修改) ---
  135. async def process_card_images(
  136. is_reflect: bool,
  137. front_face_path: str | None,
  138. front_edge_path: str | None,
  139. back_face_path: str | None,
  140. back_edge_path: str | None,
  141. existing_card_id: int | None = None # <--- 新增参数:指定现有ID
  142. ) -> int:
  143. """
  144. 核心异步处理函数。
  145. :param existing_card_id: 如果不为None,则不创建新卡组,直接使用该ID上传图片。
  146. :return: 成功操作的 card_id,如果失败返回 -1
  147. """
  148. # 0. 数据准备
  149. is_reflect_str = "true" if is_reflect else "false"
  150. # 注意列表顺序:[正面边缘, 正面面, 背面边缘, 背面面]
  151. path_list = [
  152. front_edge_path, # 对应 front_corner_edge
  153. front_face_path, # 对应 front_face
  154. back_edge_path, # 对应 back_corner_edge
  155. back_face_path # 对应 back_face
  156. ]
  157. # 生成卡片名称 (如果需要新建的话)
  158. card_name = f"单面补全测试-卡 {datetime.now().strftime('%Y-%m-%d_%H:%M:%S')}"
  159. async with aiohttp.ClientSession() as session:
  160. try:
  161. # 步骤 1: 并发处理图片 (转正 + 评分)
  162. target_desc = f"现有ID {existing_card_id}" if existing_card_id else "新建卡组"
  163. print(f"--- 开始处理 ({target_desc}) ---")
  164. print("[步骤 1] 并发图像处理...")
  165. process_tasks = []
  166. for path, s_type in zip(path_list, SCORE_TYPES):
  167. if path is None:
  168. continue
  169. if not os.path.exists(path):
  170. print(f"警告: 文件不存在,跳过 -> {path}")
  171. continue
  172. task = process_single_image(session, path, s_type, is_reflect_str)
  173. process_tasks.append(task)
  174. if not process_tasks:
  175. print("错误: 没有提供任何有效的图片路径,流程终止。")
  176. return -1
  177. processed_results = await asyncio.gather(*process_tasks)
  178. # 步骤 2: 确定 Card ID
  179. card_id = -1
  180. if existing_card_id is not None:
  181. # --- 修改逻辑:直接使用传入的ID ---
  182. print(f"\n[步骤 2] 跳过创建,使用指定卡组 ID: {existing_card_id}")
  183. card_id = existing_card_id
  184. else:
  185. # --- 原逻辑:创建新卡组 ---
  186. card_id = await create_card_set(session, card_name)
  187. # 步骤 3: 并发上传结果
  188. print(f"\n[步骤 3] 上传数据到卡组 {card_id}...")
  189. upload_tasks = []
  190. for result in processed_results:
  191. task = upload_processed_data(session, card_id, result)
  192. upload_tasks.append(task)
  193. await asyncio.gather(*upload_tasks)
  194. print(f"--- 流程完成,卡组ID: {card_id} ---\n")
  195. return card_id
  196. except Exception as e:
  197. print(f"\n流程执行中发生错误: {e}")
  198. return -1
  199. # --- 同步封装函数 (已修改) ---
  200. def run_card_processing_sync(
  201. is_reflect: bool,
  202. front_face_path: str | None,
  203. front_edge_path: str | None,
  204. back_face_path: str | None,
  205. back_edge_path: str | None,
  206. existing_card_id: int | None = None # <--- 同步透传
  207. ):
  208. if os.name == 'nt':
  209. asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
  210. return asyncio.run(process_card_images(
  211. is_reflect,
  212. front_face_path,
  213. front_edge_path,
  214. back_face_path,
  215. back_edge_path,
  216. existing_card_id
  217. ))
  218. # --- 使用示例 (针对你的需求修改) ---
  219. if __name__ == "__main__":
  220. my_is_reflect = True
  221. # 正面图所在的文件夹路径
  222. front_base_path = r"C:\Code\ML\Image\Card\coaxial_front_20img"
  223. # 循环 1 到 20 (对应文件名)
  224. for img_num in range(2, 21):
  225. # 1. 计算目标的 Card ID (你的ID是 101 到 120)
  226. # img_num 1 -> 101
  227. # img_num 20 -> 120
  228. target_card_id = 100 + img_num
  229. # 2. 构造正面图路径 (p1)
  230. # 注意: 你的文件名格式是 big_one_img{img_num}.jpg
  231. front_img_path = os.path.join(front_base_path, f"big_one_img{img_num}.jpg")
  232. # 3. 设置参数
  233. # p1 = 正面面 (front_face) -> 传入 front_img_path
  234. # p3 = 背面面 (back_face) -> 这次不需要了,传入 None
  235. print(f"正在处理第 {img_num} 组 | 对应 CardID: {target_card_id} | 文件: {os.path.basename(front_img_path)}")
  236. if not os.path.exists(front_img_path):
  237. print(f" -> 错误: 找不到文件 {front_img_path}")
  238. continue
  239. final_card_id = run_card_processing_sync(
  240. is_reflect=my_is_reflect,
  241. front_face_path=front_img_path, # <--- p1: 正面面
  242. front_edge_path=None, # p2
  243. back_face_path=None, # p3: 背面这次不传
  244. back_edge_path=None, # p4
  245. existing_card_id=target_card_id # <--- 重点:指定 ID,不创建新卡组
  246. )
  247. if final_card_id != -1:
  248. print(f" -> 成功更新 ID: {final_card_id}")
  249. else:
  250. print(f" -> 处理失败 ID: {target_card_id}")