DrawBoxesOnImageController.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. # -*- encoding: utf-8 -*-
  2. """
  3. @File : DrawBoxesOnImageController.py
  4. @Time : 2025/9/2 09:21
  5. @Author : stephen
  6. @Email : zhangdongming@asj6.wecom.work
  7. @Software: PyCharm
  8. """
  9. import base64
  10. import io
  11. import json
  12. import os
  13. import time
  14. from PIL import Image, ImageDraw, ImageFont
  15. from django.views import View
  16. from AnsjerPush.config import BASE_DIR
  17. from Object.ResponseObject import ResponseObject
  18. class DrawBoxesOnImageView(View):
  19. def get(self, request, *args, **kwargs):
  20. request.encoding = 'utf-8'
  21. operation = kwargs.get('operation')
  22. return self.validation(request.GET, request, operation)
  23. def post(self, request, *args, **kwargs):
  24. request.encoding = 'utf-8'
  25. operation = kwargs.get('operation')
  26. return self.validation(request.POST, request, operation)
  27. def validation(self, request_dict, request, operation):
  28. response = ResponseObject()
  29. if operation is None:
  30. return response.json(444, 'error path')
  31. elif operation == 'drawBoxesFromRequest':
  32. return self.draw_boxes_from_request(request_dict, response)
  33. return response.json(414)
  34. def draw_boxes_from_request(self, request_dict, response: ResponseObject):
  35. """
  36. 处理绘制边界框的API请求。
  37. 预期请求体 (JSON):
  38. {
  39. "fileOne": "base64_string_1",
  40. "fileTwo": "base64_string_2",
  41. "fileThree": "base64_string_3", // 可选
  42. "coordinates": { ... } // 包含所有图片坐标的JSON对象
  43. }
  44. """
  45. # 1. 定义图片保存的相对路径和绝对路径
  46. # 请确保 settings.BASE_DIR 指向您的Django项目根目录
  47. relative_save_dir = os.path.join('static', 'ai')
  48. absolute_save_dir = os.path.join(BASE_DIR, relative_save_dir)
  49. # 确保目标目录存在
  50. os.makedirs(absolute_save_dir, exist_ok=True)
  51. # 2. 从请求中提取图片和坐标数据
  52. base64_images = {
  53. "file_0": request_dict.get("fileOne"),
  54. "file_1": request_dict.get("fileTwo"),
  55. "file_2": request_dict.get("fileThree"),
  56. }
  57. # 坐标可以直接是字典,也可以是JSON字符串,这里做兼容处理
  58. coordinates_data = request_dict.get("coordinates")
  59. if isinstance(coordinates_data, str):
  60. try:
  61. coordinates_data = json.loads(coordinates_data)
  62. except json.JSONDecodeError:
  63. return response.json(400, "coordinates字段不是一个有效的JSON字符串")
  64. if not coordinates_data or not isinstance(coordinates_data, dict):
  65. return response.json(400, "缺少或无效的 'coordinates' 数据")
  66. processed_files = []
  67. errors = []
  68. # 3. 循环处理每张图片
  69. for i in range(3):
  70. image_key = f"file_{i}"
  71. base64_str = base64_images.get(image_key)
  72. detections = coordinates_data.get(image_key)
  73. # 如果图片和对应的坐标都存在,则进行处理
  74. if base64_str and detections:
  75. # 生成一个唯一的文件名以避免冲突
  76. timestamp = int(time.time() * 1000)
  77. output_filename = f"result_{image_key}_{timestamp}.jpg"
  78. output_path = os.path.join(absolute_save_dir, output_filename)
  79. # 调用绘图函数
  80. success = self.draw_boxes_on_image(base64_str, detections, output_path)
  81. if success:
  82. # 返回可供前端访问的静态文件URL
  83. static_url = os.path.join(BASE_DIR, 'static', 'ai', output_filename).replace("\\", "/")
  84. processed_files.append({"source": image_key, "url": static_url})
  85. else:
  86. errors.append(f"处理 {image_key} 失败")
  87. # 4. 根据处理结果返回响应
  88. if not processed_files and not errors:
  89. return response.json(400, "未提供任何有效的图片和坐标数据进行处理")
  90. elif errors:
  91. return response.json(500, {"errors": errors, "success": processed_files})
  92. else:
  93. return response.json(0, {"processed_files": processed_files})
  94. def draw_boxes_on_image(self, base64_image_string: str, detections: list, output_path: str) -> bool:
  95. """
  96. 在一个Base64编码的图片上根据提供的检测坐标绘制边界框,并保存到指定路径。
  97. Args:
  98. base64_image_string (str): 图片的Base64编码字符串。
  99. detections (list): 一个包含检测对象信息的字典列表。
  100. 每个字典应包含 'class' 和 'Left', 'Top', 'Width', 'Height' 键。
  101. output_path (str): 带有标注的图片的完整保存路径。
  102. Returns:
  103. bool: 如果成功保存图片则返回 True,否则返回 False。
  104. """
  105. if not base64_image_string or not detections:
  106. print("错误:未提供图片数据或检测坐标。")
  107. return False
  108. try:
  109. # 1. 解码Base64图片并加载
  110. image_bytes = base64.b64decode(base64_image_string)
  111. image = Image.open(io.BytesIO(image_bytes))
  112. draw = ImageDraw.Draw(image)
  113. img_width, img_height = image.size
  114. # 2. 尝试加载字体,如果失败则使用默认字体
  115. try:
  116. # 确保服务器上有这个字体文件,或者换成一个您确定存在的字体路径
  117. font = ImageFont.truetype("arial.ttf", size=20)
  118. except IOError:
  119. print("警告: 未找到arial.ttf字体,将使用默认字体。")
  120. font = ImageFont.load_default()
  121. colors = ["red", "blue", "green", "yellow", "purple", "orange", "cyan", "magenta"]
  122. # 3. 遍历所有检测框并绘制
  123. for idx, item in enumerate(detections):
  124. label = item.get("class", "unknown")
  125. left_ratio = float(item["Left"])
  126. top_ratio = float(item["Top"])
  127. width_ratio = float(item["Width"])
  128. height_ratio = float(item["Height"])
  129. x1 = int(left_ratio * img_width)
  130. y1 = int(top_ratio * img_height)
  131. x2 = int((left_ratio + width_ratio) * img_width)
  132. y2 = int((top_ratio + height_ratio) * img_height)
  133. color = colors[idx % len(colors)]
  134. draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
  135. draw.text((x1 + 4, y1 + 2), label, fill=color, font=font)
  136. # 4. 保存绘制好的图片
  137. image.save(output_path)
  138. print(f"检测结果图片已成功保存到: {output_path}")
  139. return True
  140. except Exception as e:
  141. print(f"在绘制和保存图片时发生错误: {e}")
  142. return False