|
@@ -0,0 +1,172 @@
|
|
|
|
+# -*- encoding: utf-8 -*-
|
|
|
|
+"""
|
|
|
|
+@File : DrawBoxesOnImageController.py
|
|
|
|
+@Time : 2025/9/2 09:21
|
|
|
|
+@Author : stephen
|
|
|
|
+@Email : zhangdongming@asj6.wecom.work
|
|
|
|
+@Software: PyCharm
|
|
|
|
+"""
|
|
|
|
+import base64
|
|
|
|
+import io
|
|
|
|
+import json
|
|
|
|
+import os
|
|
|
|
+import time
|
|
|
|
+
|
|
|
|
+from PIL import Image, ImageDraw, ImageFont
|
|
|
|
+from django.views import View
|
|
|
|
+
|
|
|
|
+from AnsjerPush.config import BASE_DIR
|
|
|
|
+from Object.ResponseObject import ResponseObject
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+class DrawBoxesOnImageView(View):
|
|
|
|
+ def get(self, request, *args, **kwargs):
|
|
|
|
+ request.encoding = 'utf-8'
|
|
|
|
+ operation = kwargs.get('operation')
|
|
|
|
+ return self.validation(request.GET, request, operation)
|
|
|
|
+
|
|
|
|
+ def post(self, request, *args, **kwargs):
|
|
|
|
+ request.encoding = 'utf-8'
|
|
|
|
+ operation = kwargs.get('operation')
|
|
|
|
+ return self.validation(request.POST, request, operation)
|
|
|
|
+
|
|
|
|
+ def validation(self, request_dict, request, operation):
|
|
|
|
+ response = ResponseObject()
|
|
|
|
+ if operation is None:
|
|
|
|
+ return response.json(444, 'error path')
|
|
|
|
+ elif operation == 'drawBoxesFromRequest':
|
|
|
|
+ return self.draw_boxes_from_request(request_dict, response)
|
|
|
|
+ return response.json(414)
|
|
|
|
+
|
|
|
|
+ def draw_boxes_from_request(self, request_dict, response: ResponseObject):
|
|
|
|
+ """
|
|
|
|
+ 处理绘制边界框的API请求。
|
|
|
|
+ 预期请求体 (JSON):
|
|
|
|
+ {
|
|
|
|
+ "fileOne": "base64_string_1",
|
|
|
|
+ "fileTwo": "base64_string_2",
|
|
|
|
+ "fileThree": "base64_string_3", // 可选
|
|
|
|
+ "coordinates": { ... } // 包含所有图片坐标的JSON对象
|
|
|
|
+ }
|
|
|
|
+ """
|
|
|
|
+ # 1. 定义图片保存的相对路径和绝对路径
|
|
|
|
+ # 请确保 settings.BASE_DIR 指向您的Django项目根目录
|
|
|
|
+ relative_save_dir = os.path.join('static', 'ai')
|
|
|
|
+ absolute_save_dir = os.path.join(BASE_DIR, relative_save_dir)
|
|
|
|
+
|
|
|
|
+ # 确保目标目录存在
|
|
|
|
+ os.makedirs(absolute_save_dir, exist_ok=True)
|
|
|
|
+
|
|
|
|
+ # 2. 从请求中提取图片和坐标数据
|
|
|
|
+ base64_images = {
|
|
|
|
+ "file_0": request_dict.get("fileOne"),
|
|
|
|
+ "file_1": request_dict.get("fileTwo"),
|
|
|
|
+ "file_2": request_dict.get("fileThree"),
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ # 坐标可以直接是字典,也可以是JSON字符串,这里做兼容处理
|
|
|
|
+ coordinates_data = request_dict.get("coordinates")
|
|
|
|
+ if isinstance(coordinates_data, str):
|
|
|
|
+ try:
|
|
|
|
+ coordinates_data = json.loads(coordinates_data)
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
+ return response.json(400, "coordinates字段不是一个有效的JSON字符串")
|
|
|
|
+
|
|
|
|
+ if not coordinates_data or not isinstance(coordinates_data, dict):
|
|
|
|
+ return response.json(400, "缺少或无效的 'coordinates' 数据")
|
|
|
|
+
|
|
|
|
+ processed_files = []
|
|
|
|
+ errors = []
|
|
|
|
+
|
|
|
|
+ # 3. 循环处理每张图片
|
|
|
|
+ for i in range(3):
|
|
|
|
+ image_key = f"file_{i}"
|
|
|
|
+
|
|
|
|
+ base64_str = base64_images.get(image_key)
|
|
|
|
+ detections = coordinates_data.get(image_key)
|
|
|
|
+
|
|
|
|
+ # 如果图片和对应的坐标都存在,则进行处理
|
|
|
|
+ if base64_str and detections:
|
|
|
|
+ # 生成一个唯一的文件名以避免冲突
|
|
|
|
+ timestamp = int(time.time() * 1000)
|
|
|
|
+ output_filename = f"result_{image_key}_{timestamp}.jpg"
|
|
|
|
+ output_path = os.path.join(absolute_save_dir, output_filename)
|
|
|
|
+
|
|
|
|
+ # 调用绘图函数
|
|
|
|
+ success = self.draw_boxes_on_image(base64_str, detections, output_path)
|
|
|
|
+
|
|
|
|
+ if success:
|
|
|
|
+ # 返回可供前端访问的静态文件URL
|
|
|
|
+ static_url = os.path.join(BASE_DIR, 'static', 'ai', output_filename).replace("\\", "/")
|
|
|
|
+ processed_files.append({"source": image_key, "url": static_url})
|
|
|
|
+ else:
|
|
|
|
+ errors.append(f"处理 {image_key} 失败")
|
|
|
|
+
|
|
|
|
+ # 4. 根据处理结果返回响应
|
|
|
|
+ if not processed_files and not errors:
|
|
|
|
+ return response.json(400, "未提供任何有效的图片和坐标数据进行处理")
|
|
|
|
+ elif errors:
|
|
|
|
+ return response.json(500, {"errors": errors, "success": processed_files})
|
|
|
|
+ else:
|
|
|
|
+ return response.json(0, {"processed_files": processed_files})
|
|
|
|
+
|
|
|
|
+ def draw_boxes_on_image(self, base64_image_string: str, detections: list, output_path: str) -> bool:
|
|
|
|
+ """
|
|
|
|
+ 在一个Base64编码的图片上根据提供的检测坐标绘制边界框,并保存到指定路径。
|
|
|
|
+
|
|
|
|
+ Args:
|
|
|
|
+ base64_image_string (str): 图片的Base64编码字符串。
|
|
|
|
+ detections (list): 一个包含检测对象信息的字典列表。
|
|
|
|
+ 每个字典应包含 'class' 和 'Left', 'Top', 'Width', 'Height' 键。
|
|
|
|
+ output_path (str): 带有标注的图片的完整保存路径。
|
|
|
|
+
|
|
|
|
+ Returns:
|
|
|
|
+ bool: 如果成功保存图片则返回 True,否则返回 False。
|
|
|
|
+ """
|
|
|
|
+ if not base64_image_string or not detections:
|
|
|
|
+ print("错误:未提供图片数据或检测坐标。")
|
|
|
|
+ return False
|
|
|
|
+
|
|
|
|
+ try:
|
|
|
|
+ # 1. 解码Base64图片并加载
|
|
|
|
+ image_bytes = base64.b64decode(base64_image_string)
|
|
|
|
+ image = Image.open(io.BytesIO(image_bytes))
|
|
|
|
+ draw = ImageDraw.Draw(image)
|
|
|
|
+ img_width, img_height = image.size
|
|
|
|
+
|
|
|
|
+ # 2. 尝试加载字体,如果失败则使用默认字体
|
|
|
|
+ try:
|
|
|
|
+ # 确保服务器上有这个字体文件,或者换成一个您确定存在的字体路径
|
|
|
|
+ font = ImageFont.truetype("arial.ttf", size=20)
|
|
|
|
+ except IOError:
|
|
|
|
+ print("警告: 未找到arial.ttf字体,将使用默认字体。")
|
|
|
|
+ font = ImageFont.load_default()
|
|
|
|
+
|
|
|
|
+ colors = ["red", "blue", "green", "yellow", "purple", "orange", "cyan", "magenta"]
|
|
|
|
+
|
|
|
|
+ # 3. 遍历所有检测框并绘制
|
|
|
|
+ for idx, item in enumerate(detections):
|
|
|
|
+ label = item.get("class", "unknown")
|
|
|
|
+
|
|
|
|
+ left_ratio = float(item["Left"])
|
|
|
|
+ top_ratio = float(item["Top"])
|
|
|
|
+ width_ratio = float(item["Width"])
|
|
|
|
+ height_ratio = float(item["Height"])
|
|
|
|
+
|
|
|
|
+ x1 = int(left_ratio * img_width)
|
|
|
|
+ y1 = int(top_ratio * img_height)
|
|
|
|
+ x2 = int((left_ratio + width_ratio) * img_width)
|
|
|
|
+ y2 = int((top_ratio + height_ratio) * img_height)
|
|
|
|
+
|
|
|
|
+ color = colors[idx % len(colors)]
|
|
|
|
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
|
|
|
|
+ draw.text((x1 + 4, y1 + 2), label, fill=color, font=font)
|
|
|
|
+
|
|
|
|
+ # 4. 保存绘制好的图片
|
|
|
|
+ image.save(output_path)
|
|
|
|
+ print(f"检测结果图片已成功保存到: {output_path}")
|
|
|
|
+ return True
|
|
|
|
+
|
|
|
|
+ except Exception as e:
|
|
|
|
+ print(f"在绘制和保存图片时发生错误: {e}")
|
|
|
|
+ return False
|