roi_creator_mp.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. # -*- coding: utf-8 -*-
  2. """
  3. 代码一:定义数据并保存(支持多个ROI)
  4. 功能:
  5. 1. 加载图一。
  6. 2. 允许用户交互式地绘制多个多边形 ROI,并为每个ROI命名。
  7. 3. 允许用户交互式地绘制多个矩形屏蔽区。
  8. 4. 将所有ROI坐标、名称、屏蔽区坐标和相关参数打包保存到一个 JSON 文件中。
  9. """
  10. import cv2
  11. import numpy as np
  12. import json
  13. import os
  14. import base64
  15. # ========== 全局变量 ==========
  16. # --- ROI 绘制相关 ---
  17. current_roi_points = []
  18. all_rois = [] # 存储所有ROI及其名称
  19. drawing_complete = False
  20. drawing_image = None
  21. original_image = None
  22. # --- 屏蔽区绘制相关 ---
  23. ignore_polygons = []
  24. drawing_ignore = None
  25. drawing_rect = False
  26. rect_start_point = None
  27. # ========== 可配置参数 ==========
  28. CONFIG_PARAMS = {
  29. "MARGIN_RATIO": 0.25,
  30. "MIN_MATCH_COUNT": 10,
  31. "USE_CLAHE": True
  32. }
  33. # -------------------------------------------------
  34. # 1. 回调函数:选择 ROI
  35. # -------------------------------------------------
  36. def select_roi(event, x, y, flags, param):
  37. """用于绘制 ROI 多边形的回调函数"""
  38. global current_roi_points, drawing_complete, drawing_image
  39. if drawing_complete:
  40. return
  41. if event == cv2.EVENT_LBUTTONDOWN:
  42. current_roi_points.append((x, y))
  43. cv2.circle(drawing_image, (x, y), 4, (0, 255, 0), -1)
  44. if len(current_roi_points) > 1:
  45. cv2.line(drawing_image, current_roi_points[-2], current_roi_points[-1], (0, 255, 0), 2)
  46. cv2.imshow("Image 1 - Select ROI", drawing_image)
  47. elif event == cv2.EVENT_RBUTTONDOWN:
  48. if len(current_roi_points) < 3:
  49. print("[提示] ROI 至少需要 3 个点构成多边形!")
  50. return
  51. drawing_complete = True
  52. cv2.line(drawing_image, current_roi_points[-1], current_roi_points[0], (0, 255, 0), 2)
  53. cv2.imshow("Image 1 - Select ROI", drawing_image)
  54. print("[提示] 当前ROI选择完毕,按任意键继续...")
  55. # -------------------------------------------------
  56. # 2. 回调函数:选择屏蔽区
  57. # -------------------------------------------------
  58. def select_ignore_rect(event, x, y, flags, param):
  59. """用于绘制屏蔽矩形的回调函数"""
  60. global rect_start_point, drawing_rect, ignore_polygons, drawing_ignore
  61. if event == cv2.EVENT_LBUTTONDOWN:
  62. rect_start_point = (x, y)
  63. drawing_rect = True
  64. elif event == cv2.EVENT_MOUSEMOVE:
  65. if drawing_rect:
  66. preview_img = drawing_ignore.copy()
  67. cv2.rectangle(preview_img, rect_start_point, (x, y), (0, 0, 255), 2)
  68. cv2.imshow("Image 1 - Ignore Area", preview_img)
  69. elif event == cv2.EVENT_LBUTTONUP:
  70. if not drawing_rect:
  71. return
  72. drawing_rect = False
  73. x_start, y_start = rect_start_point
  74. x1, y1 = min(x_start, x), min(y_start, y)
  75. x2, y2 = max(x_start, x), max(y_start, y)
  76. if x2 - x1 > 5 and y2 - y1 > 5:
  77. cv2.rectangle(drawing_ignore, (x1, y1), (x2, y2), (0, 0, 255), 2)
  78. rect_as_poly = [(x1, y1), (x2, y1), (x2, y2), (x1, y2)]
  79. ignore_polygons.append(rect_as_poly)
  80. print(f"[提示] 已新增屏蔽区 #{len(ignore_polygons)},可继续拉框,完成后按 Enter 键。")
  81. cv2.imshow("Image 1 - Ignore Area", drawing_ignore)
  82. # -------------------------------------------------
  83. # 3. 工具函数
  84. # -------------------------------------------------
  85. def read_image_chinese(path):
  86. """读取可能包含中文路径的图片"""
  87. try:
  88. with open(path, 'rb') as f:
  89. data = np.frombuffer(f.read(), np.uint8)
  90. return cv2.imdecode(data, cv2.IMREAD_COLOR)
  91. except Exception as e:
  92. print(f"读取图片失败: {e}")
  93. return None
  94. def draw_all_rois(image, rois_list):
  95. """在图像上绘制所有已保存的ROI"""
  96. result = image.copy()
  97. colors = [(0, 255, 0), (255, 0, 0), (0, 0, 255), (255, 255, 0),
  98. (255, 0, 255), (0, 255, 255), (128, 255, 0), (255, 128, 0)]
  99. for idx, roi_data in enumerate(rois_list):
  100. color = colors[idx % len(colors)]
  101. points = roi_data["points"]
  102. name = roi_data["name"]
  103. # 绘制多边形
  104. cv2.polylines(result, [np.array(points, np.int32)], True, color, 2)
  105. # 计算中心点并显示名称
  106. center = np.mean(points, axis=0).astype(int)
  107. cv2.putText(result, name, tuple(center), cv2.FONT_HERSHEY_SIMPLEX,
  108. 0.7, color, 2, cv2.LINE_AA)
  109. return result
  110. # -------------------------------------------------
  111. # 4. 主函数
  112. # -------------------------------------------------
  113. def define_and_save_data(image1_path, output_json_path):
  114. """
  115. 主流程函数:加载图像,引导用户选择多个ROI并命名,最终保存数据。
  116. """
  117. global current_roi_points, drawing_complete, drawing_image, all_rois
  118. global drawing_ignore, ignore_polygons, original_image
  119. # --- 步骤 1: 加载图像 ---
  120. img1 = read_image_chinese(image1_path)
  121. if img1 is None:
  122. print(f"错误:无法加载图像 '{image1_path}',请检查路径!")
  123. return
  124. original_image = img1.copy()
  125. # --- 步骤 2: 循环选择多个ROI ---
  126. print("\n========== 开始选择ROI ==========")
  127. roi_count = 0
  128. while True:
  129. roi_count += 1
  130. current_roi_points = []
  131. drawing_complete = False
  132. # 显示已有的ROI
  133. if all_rois:
  134. drawing_image = draw_all_rois(original_image, all_rois)
  135. else:
  136. drawing_image = original_image.copy()
  137. cv2.namedWindow("Image 1 - Select ROI")
  138. cv2.setMouseCallback("Image 1 - Select ROI", select_roi)
  139. print(f"\n【ROI #{roi_count}】")
  140. print("用鼠标左键多点圈出ROI,右键闭合完成当前ROI。")
  141. print("按 ESC 键结束所有ROI选择,按其他键继续添加下一个ROI。")
  142. cv2.imshow("Image 1 - Select ROI", drawing_image)
  143. key = cv2.waitKey(0)
  144. if key == 27: # ESC键,结束ROI选择
  145. cv2.destroyWindow("Image 1 - Select ROI")
  146. if len(current_roi_points) >= 3:
  147. # 保存最后一个ROI
  148. roi_name = input(f"请输入ROI #{roi_count} 的名称: ").strip()
  149. if not roi_name:
  150. roi_name = f"ROI_{roi_count}"
  151. all_rois.append({
  152. "name": roi_name,
  153. "points": current_roi_points
  154. })
  155. print(f"已保存ROI: {roi_name}")
  156. break
  157. else:
  158. if len(current_roi_points) >= 3:
  159. # 为当前ROI输入名称
  160. roi_name = input(f"请输入ROI #{roi_count} 的名称: ").strip()
  161. if not roi_name:
  162. roi_name = f"ROI_{roi_count}"
  163. all_rois.append({
  164. "name": roi_name,
  165. "points": current_roi_points
  166. })
  167. print(f"已保存ROI: {roi_name}")
  168. else:
  169. print("当前ROI点数不足,跳过保存。")
  170. roi_count -= 1
  171. if not all_rois:
  172. print("错误:未选择任何ROI,程序退出。")
  173. return
  174. print(f"\n总共选择了 {len(all_rois)} 个ROI")
  175. # --- 步骤 3: 交互式选择屏蔽区 ---
  176. drawing_ignore = draw_all_rois(original_image, all_rois)
  177. cv2.namedWindow("Image 1 - Ignore Area")
  178. cv2.setMouseCallback("Image 1 - Ignore Area", select_ignore_rect)
  179. print("\n【步骤 2】在弹窗中,用鼠标左键拖拽拉框来选择屏蔽区,可画多个。")
  180. print(" 所有屏蔽区画完后,按 Enter 键保存并结束,或按 Esc 键退出。")
  181. cv2.imshow("Image 1 - Ignore Area", drawing_ignore)
  182. while True:
  183. key = cv2.waitKey(0)
  184. if key in [13, 10]: # Enter键
  185. break
  186. elif key == 27: # Esc
  187. cv2.destroyAllWindows()
  188. print("用户取消操作。")
  189. return
  190. cv2.destroyAllWindows()
  191. # --- 步骤 4: 封装数据并保存到 JSON ---
  192. try:
  193. with open(image1_path, 'rb') as f:
  194. image_bytes = f.read()
  195. image_base64 = base64.b64encode(image_bytes).decode('utf-8')
  196. image_filename = os.path.basename(image1_path)
  197. except Exception as e:
  198. print(f"\n[失败] 读取并编码图像文件时出错: {e}")
  199. return
  200. # 封装数据
  201. data_to_save = {
  202. "image1_filename": image_filename,
  203. "image1_data_base64": image_base64,
  204. "rois": all_rois, # 保存所有ROI及其名称
  205. "ignore_polygons": ignore_polygons,
  206. "parameters": CONFIG_PARAMS
  207. }
  208. try:
  209. with open(output_json_path, 'w', encoding='utf-8') as f:
  210. json.dump(data_to_save, f, indent=4, ensure_ascii=False)
  211. print(f"\n[成功] {len(all_rois)} 个ROI、屏蔽区和图像数据已成功保存到: {output_json_path}")
  212. except Exception as e:
  213. print(f"\n[失败] 保存 JSON 文件时出错: {e}")
  214. # -------------------------------------------------
  215. if __name__ == "__main__":
  216. IMG1_PATH = r"20250615000002.jpg"
  217. OUTPUT_JSON = "roi_json_dir/matching_data_multi_roi.json"
  218. # 创建输出目录
  219. os.makedirs(os.path.dirname(OUTPUT_JSON), exist_ok=True)
  220. define_and_save_data(IMG1_PATH, OUTPUT_JSON)