123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- # -*- 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
|