|
|
@@ -1,9 +1,8 @@
|
|
|
from fastapi import APIRouter, HTTPException, Depends, Query
|
|
|
from mysql.connector.pooling import PooledMySQLConnection
|
|
|
-import os
|
|
|
-import json
|
|
|
-import math
|
|
|
-from pathlib import Path
|
|
|
+
|
|
|
+import io
|
|
|
+from app.core.minio_client import minio_client
|
|
|
from typing import List, Dict, Any, Optional
|
|
|
from PIL import Image
|
|
|
|
|
|
@@ -16,9 +15,6 @@ from app.utils.scheme import ImageType
|
|
|
logger = get_logger(__name__)
|
|
|
router = APIRouter()
|
|
|
|
|
|
-# 定义新的缺陷图片存储路径
|
|
|
-DEFECT_IMAGE_DIR = settings.DEFECT_IMAGE_DIR
|
|
|
-
|
|
|
|
|
|
def _get_active_json(image_data: Any) -> Optional[Dict]:
|
|
|
"""获取有效的json数据,优先 modified_json"""
|
|
|
@@ -42,75 +38,57 @@ def _get_active_json(image_data: Any) -> Optional[Dict]:
|
|
|
|
|
|
def _crop_defect_image(original_image_path_str: str, min_rect: List, output_filename: str) -> str:
|
|
|
"""
|
|
|
- 切割缺陷图片为正方形
|
|
|
- min_rect 结构: [[center_x, center_y], [width, height], angle]
|
|
|
+ 通过 MinIO 切割缺陷图片为正方形并保存
|
|
|
"""
|
|
|
try:
|
|
|
- # 构建绝对路径
|
|
|
- # 假设 original_image_path_str 是 "/Data/..." 格式
|
|
|
- rel_path = original_image_path_str.lstrip('/\\')
|
|
|
- full_path = settings.BASE_PATH / rel_path
|
|
|
-
|
|
|
- if not full_path.exists():
|
|
|
- logger.warning(f"原图不存在: {full_path}")
|
|
|
+ # ★ 将进来的全路径 URL 剥离为相对路径 (如 /Data/xxx.jpg) 供 MinIO 读取
|
|
|
+ rel_path = original_image_path_str.replace(settings.DATA_HOST_URL, "")
|
|
|
+ rel_path = "/" + rel_path.lstrip('/\\')
|
|
|
+ object_name = f"{settings.MINIO_BASE_PREFIX}{rel_path}"
|
|
|
+
|
|
|
+ # 1. 从 MinIO 获取原图字节
|
|
|
+ try:
|
|
|
+ response = minio_client.get_object(settings.MINIO_BUCKET, object_name)
|
|
|
+ image_bytes = response.read()
|
|
|
+ response.close()
|
|
|
+ response.release_conn()
|
|
|
+ except Exception as e:
|
|
|
+ logger.warning(f"从MinIO获取原图失败: {object_name} -> {e}")
|
|
|
return ""
|
|
|
|
|
|
- with Image.open(full_path) as img:
|
|
|
+ # 2. 在内存中用 PIL 切图
|
|
|
+ with Image.open(io.BytesIO(image_bytes)) as img:
|
|
|
img_w, img_h = img.size
|
|
|
-
|
|
|
- # 解析 min_rect
|
|
|
- # min_rect[0] 是中心点 [x, y]
|
|
|
- # min_rect[1] 是宽高 [w, h]
|
|
|
center_x, center_y = min_rect[0]
|
|
|
rect_w, rect_h = min_rect[1]
|
|
|
+ side_length = max(max(rect_w, rect_h) * 1.5, 100)
|
|
|
+ half_side = side_length / 2
|
|
|
|
|
|
- # 确定裁剪的正方形边长:取宽高的最大值,并适当外扩 (例如 1.5 倍) 以展示周围环境
|
|
|
- # 如果缺陷非常小,设置一个最小尺寸(例如 100px),避免切图太模糊
|
|
|
- side_length = max(rect_w, rect_h) * 1.5
|
|
|
- side_length = max(side_length, 100)
|
|
|
+ left, top = max(0, center_x - half_side), max(0, center_y - half_side)
|
|
|
+ right, bottom = min(img_w, center_x + half_side), min(img_h, center_y + half_side)
|
|
|
|
|
|
- half_side = side_length / 2
|
|
|
+ cropped_img = img.crop((left, top, right, bottom))
|
|
|
+
|
|
|
+ # 3. 将切割后的图存入内存流,并上传到 MinIO
|
|
|
+ out_bytes = io.BytesIO()
|
|
|
+ cropped_img.save(out_bytes, format="JPEG", quality=95)
|
|
|
+ out_bytes.seek(0)
|
|
|
+
|
|
|
+ out_rel_path = f"/DefectImage/{output_filename}"
|
|
|
+ out_object_name = f"{settings.MINIO_BASE_PREFIX}{out_rel_path}"
|
|
|
+
|
|
|
+ minio_client.put_object(
|
|
|
+ settings.MINIO_BUCKET,
|
|
|
+ out_object_name,
|
|
|
+ out_bytes,
|
|
|
+ len(out_bytes.getvalue()),
|
|
|
+ content_type="image/jpeg"
|
|
|
+ )
|
|
|
|
|
|
- # 计算裁剪框 (left, top, right, bottom)
|
|
|
- left = center_x - half_side
|
|
|
- top = center_y - half_side
|
|
|
- right = center_x + half_side
|
|
|
- bottom = center_y + half_side
|
|
|
-
|
|
|
- # 边界检查,防止超出图片范围
|
|
|
- # 如果只是想保持正方形,超出部分可以填黑,或者简单的移动框的位置
|
|
|
- # 这里简单处理:如果超出边界,就移动框,实在移不动就截断
|
|
|
- if left < 0:
|
|
|
- right -= left # 往右移
|
|
|
- left = 0
|
|
|
- if top < 0:
|
|
|
- bottom -= top # 往下移
|
|
|
- top = 0
|
|
|
- if right > img_w:
|
|
|
- left -= (right - img_w) # 往左移
|
|
|
- right = img_w
|
|
|
- if bottom > img_h:
|
|
|
- top -= (bottom - img_h) # 往上移
|
|
|
- bottom = img_h
|
|
|
-
|
|
|
- # 再次检查防止负数(比如图片本身比框还小)
|
|
|
- left = max(0, left)
|
|
|
- top = max(0, top)
|
|
|
- right = min(img_w, right)
|
|
|
- bottom = min(img_h, bottom)
|
|
|
-
|
|
|
- crop_box = (left, top, right, bottom)
|
|
|
- cropped_img = img.crop(crop_box)
|
|
|
-
|
|
|
- # 保存
|
|
|
- save_path = DEFECT_IMAGE_DIR / output_filename
|
|
|
- cropped_img.save(save_path, quality=95)
|
|
|
-
|
|
|
- # 返回 URL 路径 (相对于项目根目录的 web 路径)
|
|
|
- return f"/DefectImage/{output_filename}"
|
|
|
+ return settings.get_full_url(out_rel_path)
|
|
|
|
|
|
except Exception as e:
|
|
|
- logger.error(f"切割图片失败: {e}")
|
|
|
+ logger.error(f"切割并上传图片失败: {e}")
|
|
|
return ""
|
|
|
|
|
|
|
|
|
@@ -196,9 +174,9 @@ def generate_rating_report(
|
|
|
|
|
|
# 设置主图 URL
|
|
|
if img_type == "front_ring":
|
|
|
- response_data["frontImageUrl"] = f"{settings.DATA_HOST_URL}{img.image_path}"
|
|
|
+ response_data["frontImageUrl"] = img.image_path
|
|
|
elif img_type == "back_ring":
|
|
|
- response_data["backImageUrl"] = f"{settings.DATA_HOST_URL}{img.image_path}"
|
|
|
+ response_data["backImageUrl"] = img.image_path
|
|
|
|
|
|
# 获取有效 JSON
|
|
|
json_data = _get_active_json(img)
|
|
|
@@ -296,7 +274,7 @@ def generate_rating_report(
|
|
|
"side": side,
|
|
|
"location": location_str,
|
|
|
"type": type_str,
|
|
|
- "defectImgUrl": f"{settings.DATA_HOST_URL}{defect_img_url}"
|
|
|
+ "defectImgUrl": defect_img_url
|
|
|
})
|
|
|
|
|
|
response_data["defectDetailList"] = final_defect_list
|