后端新增《蚕茧识别V2》模块
This commit is contained in:
+7
-4
@@ -4,6 +4,7 @@ from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from uvicorn import Config, Server
|
||||
|
||||
from config.yolo import YOLOSingleton
|
||||
from mcp_local.mcp_pipe import init_mcp_server
|
||||
from routers.Bot import botRouter
|
||||
from routers.Chat import chatRouter
|
||||
@@ -21,7 +22,7 @@ async def ai_lab():
|
||||
app = FastAPI(title="BBIT_AI")
|
||||
|
||||
origins = [
|
||||
"http://localhost:8090", # Vite dev 默认端口
|
||||
"http://localhost:8091", # Vite dev 默认端口
|
||||
"https://ai.ronsunny.cn:8090",
|
||||
"*", # ⚠️ 生产环境不要用
|
||||
]
|
||||
@@ -40,11 +41,11 @@ async def ai_lab():
|
||||
reportDataRouter,
|
||||
serviceRouter,
|
||||
botRouter,
|
||||
visionRouter,
|
||||
rqRouter,
|
||||
]
|
||||
for r in routers:
|
||||
app.include_router(r, prefix="/llm", tags=["llm"])
|
||||
app.include_router(visionRouter, prefix="/cv", tags=["cv"])
|
||||
app.include_router(publicRouter, prefix="/api/public", tags=["api"])
|
||||
config = Config(app=app, host="0.0.0.0", port=13011, log_level="info")
|
||||
server = Server(config)
|
||||
@@ -52,12 +53,14 @@ async def ai_lab():
|
||||
|
||||
|
||||
async def main():
|
||||
# 初始化模型
|
||||
YOLOSingleton.init_model()
|
||||
# 主干AI实验室FastAPI服务
|
||||
task_api = asyncio.create_task(ai_lab())
|
||||
|
||||
# MCP服务-ailab
|
||||
# endpoint_url = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
endpoint_url_ai_lab = "ws://ce_bot_mcp:8004/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
endpoint_url_ai_lab = "wss://ai.ronsunny.cn:8090/aimcp/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
# endpoint_url_ai_lab = "ws://ce_bot_mcp:8004/mcp_endpoint/mcp/?token=TsSP9lBq6Oa1WMkachHoS2TtNt4GKV/Gli24pk5Rjpk%3D"
|
||||
task_mcp1 = asyncio.create_task(init_mcp_server(endpoint_url_ai_lab))
|
||||
|
||||
# MCP服务-ql
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from langchain_milvus import Milvus
|
||||
|
||||
from config.llm import llmEmbeddings
|
||||
from utils.GlobalVariable import LOCAL_IP
|
||||
|
||||
URI = "http://ce_milvus:19530"
|
||||
URI = "http://" + LOCAL_IP + ":19530"
|
||||
|
||||
knVectorstore = Milvus(
|
||||
embedding_function=llmEmbeddings,
|
||||
|
||||
@@ -5,6 +5,8 @@ from contextlib import contextmanager
|
||||
import psycopg
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
from utils.GlobalVariable import LOCAL_IP
|
||||
|
||||
logger = logging.getLogger("PGPool")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@@ -66,7 +68,7 @@ class PGPool:
|
||||
|
||||
|
||||
pg_pool = PGPool(
|
||||
uri="postgresql://postgres:123456@ce_postgres/ktor2",
|
||||
uri="postgresql://postgres:123456@" + LOCAL_IP + "/ktor2",
|
||||
min_size=1,
|
||||
max_size=20,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
RABBIT_HOST = "ce_rabbitmq"
|
||||
from utils.GlobalVariable import LOCAL_IP
|
||||
|
||||
RABBIT_HOST = LOCAL_IP
|
||||
RABBIT_VHOST = "/bbit_ai"
|
||||
RABBIT_USER = "bbit_ai"
|
||||
RABBIT_PASSWORD = "123456"
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import logging
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from ultralytics import YOLO
|
||||
|
||||
from utils import MyUtils
|
||||
|
||||
|
||||
def draw_annotations(
|
||||
img_bgr,
|
||||
boxes,
|
||||
labels,
|
||||
confidences=None,
|
||||
font_path="/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
|
||||
):
|
||||
"""
|
||||
绘制带中文、置信度、不同颜色的标注框
|
||||
:param img_bgr: np.ndarray, BGR 图像
|
||||
:param boxes: list of xyxy
|
||||
:param labels: list of str
|
||||
:param confidences: list of float, 可选,用于显示置信度
|
||||
:param font_path: 字体路径
|
||||
:return: np.ndarray, BGR 标注图
|
||||
"""
|
||||
h, w, _ = img_bgr.shape
|
||||
line_width = max(2, int(w / 400))
|
||||
font_size = max(20, int(w / 40))
|
||||
|
||||
img = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
|
||||
pil_img = Image.fromarray(img)
|
||||
draw = ImageDraw.Draw(pil_img)
|
||||
font = ImageFont.truetype(font_path, font_size)
|
||||
|
||||
# 定义类别颜色映射
|
||||
color_map = {
|
||||
"正茧": "green",
|
||||
"双宫茧": "grey",
|
||||
"黄斑茧": "red",
|
||||
"毛茧": "white",
|
||||
"蛆壳茧": "purple",
|
||||
}
|
||||
|
||||
for idx, (b, label) in enumerate(zip(boxes, labels), start=1):
|
||||
conf = confidences[idx - 1] if confidences else None
|
||||
display_text = f"{label}#{idx}"
|
||||
if conf is not None:
|
||||
display_text += f" {conf:.2f}"
|
||||
|
||||
x1, y1, x2, y2 = map(int, b)
|
||||
box_color = color_map.get(label, "red")
|
||||
|
||||
# 画框
|
||||
draw.rectangle([x1, y1, x2, y2], outline=box_color, width=line_width)
|
||||
|
||||
# 文本边界
|
||||
bbox = draw.textbbox((0, 0), display_text, font=font)
|
||||
text_width = bbox[2] - bbox[0]
|
||||
text_height = bbox[3] - bbox[1]
|
||||
|
||||
# 文本背景
|
||||
draw.rectangle(
|
||||
[x1, y1 - text_height - 4, x1 + text_width + 4, y1], fill="black"
|
||||
)
|
||||
draw.text((x1 + 2, y1 - text_height - 2), display_text, fill="white", font=font)
|
||||
|
||||
annotated = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
||||
return annotated
|
||||
|
||||
|
||||
logger = logging.getLogger("yolo_service")
|
||||
|
||||
|
||||
class YOLOSingleton:
|
||||
_model = None
|
||||
_ready = False
|
||||
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
"""获取模型单例"""
|
||||
if cls._model is None:
|
||||
logger.warning("模型尚未初始化")
|
||||
return cls._model
|
||||
|
||||
@classmethod
|
||||
def is_ready(cls):
|
||||
return cls._ready
|
||||
|
||||
@classmethod
|
||||
def init_model(cls):
|
||||
"""初始化模型,失败时不抛出异常"""
|
||||
try:
|
||||
cls._model = YOLO("/app/models/yolo/yolo_silkworm_cocoon_detect_v1.pt")
|
||||
cls._model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
cls._ready = True
|
||||
logger.info("✅ YOLO 模型加载完成")
|
||||
except Exception as e:
|
||||
cls._model = None
|
||||
cls._ready = False
|
||||
logger.error(f"❌ YOLO 模型加载失败: {e}")
|
||||
|
||||
@classmethod
|
||||
def detect(cls, img_bytes: bytes):
|
||||
if not cls._ready or cls._model is None:
|
||||
raise RuntimeError("模型未加载或不可用")
|
||||
|
||||
label_map = {
|
||||
"normal": "正茧",
|
||||
"double_pupa": "双宫茧",
|
||||
"spot": "黄斑茧",
|
||||
"hairy": "毛茧",
|
||||
"maggot_shell": "蛆壳茧",
|
||||
}
|
||||
|
||||
img = cv2.imdecode(np.frombuffer(img_bytes, np.uint8), cv2.IMREAD_COLOR)
|
||||
results = cls._model(img, conf=0.45)
|
||||
r = results[0]
|
||||
boxes = r.boxes
|
||||
names = cls._model.names
|
||||
|
||||
total = len(boxes)
|
||||
class_counts = {}
|
||||
confidences = []
|
||||
box_list = []
|
||||
label_list = []
|
||||
|
||||
for idx, b in enumerate(boxes):
|
||||
cls_id = int(b.cls)
|
||||
conf = float(b.conf)
|
||||
label_en = names.get(cls_id, str(cls_id))
|
||||
label_cn = label_map.get(label_en, label_en)
|
||||
class_counts[label_cn] = class_counts.get(label_cn, 0) + 1
|
||||
confidences.append(conf)
|
||||
box_list.append(b.xyxy[0])
|
||||
label_list.append(label_cn)
|
||||
|
||||
# 用 PIL 绘制中文
|
||||
annotated = draw_annotations(img, box_list, label_list, confidences)
|
||||
|
||||
_, buffer = cv2.imencode(".jpg", annotated)
|
||||
img_bytes_out = buffer.tobytes()
|
||||
|
||||
result_json = {
|
||||
"total_objects": total,
|
||||
"class_counts": class_counts,
|
||||
"min_confidence": MyUtils.safe_round(min(confidences), 4),
|
||||
"max_confidence": MyUtils.safe_round(max(confidences), 4),
|
||||
"avg_confidence": (
|
||||
MyUtils.safe_round(sum(confidences) / len(confidences), 4)
|
||||
),
|
||||
"speed_ms": r.speed, # 直接来自 YOLO
|
||||
}
|
||||
|
||||
return img_bytes_out, result_json
|
||||
@@ -635,3 +635,121 @@ def get_license_image_list(user_id, page=1, page_size=10):
|
||||
)
|
||||
|
||||
return total, result
|
||||
|
||||
|
||||
# ————————————————————————————————————————————————————蚕茧质量识别———————————————————————————————
|
||||
def insert_sca_image(
|
||||
file_name,
|
||||
resolution,
|
||||
size,
|
||||
cocoon_count,
|
||||
max_confidence,
|
||||
min_confidence,
|
||||
average_confidence,
|
||||
other_info,
|
||||
preprocess_time_ms,
|
||||
inference_time_ms,
|
||||
postprocess_time_ms,
|
||||
name,
|
||||
image_pre,
|
||||
image_after,
|
||||
created_by,
|
||||
):
|
||||
with pg_pool.getConn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
other_info = json.dumps(other_info)
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO sca_images (
|
||||
upload_datetime, file_name, resolution, size, cocoon_count, max_confidence, min_confidence,
|
||||
average_confidence, other_info, preprocess_time_ms, inference_time_ms, postprocess_time_ms, name, image_pre, image_after, created_by
|
||||
)
|
||||
VALUES (NOW(), %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s )
|
||||
RETURNING id
|
||||
""",
|
||||
(
|
||||
file_name,
|
||||
resolution,
|
||||
size,
|
||||
cocoon_count,
|
||||
max_confidence,
|
||||
min_confidence,
|
||||
average_confidence,
|
||||
other_info,
|
||||
preprocess_time_ms,
|
||||
inference_time_ms,
|
||||
postprocess_time_ms,
|
||||
name,
|
||||
image_pre,
|
||||
image_after,
|
||||
created_by,
|
||||
),
|
||||
)
|
||||
new_id = cursor.fetchone()[0]
|
||||
conn.commit()
|
||||
return new_id
|
||||
|
||||
|
||||
def get_sca_image_list(user_id, name, page=1, page_size=10):
|
||||
"""
|
||||
获取用户已分析图片列表,带分页
|
||||
"""
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
with pg_pool.getConn() as conn:
|
||||
with conn.cursor() as cursor:
|
||||
# 1️⃣ 查询总条数
|
||||
# ✅ 改进版:支持 name 为空时统计全部,不为空时模糊统计
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM sca_images
|
||||
WHERE created_by = %s
|
||||
AND (%s = '' OR name LIKE '%%' || %s || '%%')
|
||||
""",
|
||||
(user_id, name, name),
|
||||
)
|
||||
|
||||
total = cursor.fetchone()[0]
|
||||
|
||||
# 2️⃣ 查询当前页数据
|
||||
# ✅ 改进版
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, name, upload_datetime, file_name, image_pre, image_after, resolution,
|
||||
size, cocoon_count, max_confidence, min_confidence, average_confidence, other_info, preprocess_time_ms, inference_time_ms, postprocess_time_ms
|
||||
FROM sca_images
|
||||
WHERE created_by = %s
|
||||
AND (%s = '' OR name LIKE '%%' || %s || '%%')
|
||||
ORDER BY upload_datetime DESC
|
||||
LIMIT %s OFFSET %s
|
||||
""",
|
||||
(user_id, name, name, page_size, offset),
|
||||
)
|
||||
|
||||
rows = cursor.fetchall()
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
result.append(
|
||||
{
|
||||
"id": row[0],
|
||||
"name": row[1],
|
||||
"upload_datetime": MyUtils.format_datetime(row[2]),
|
||||
"file_name": row[3],
|
||||
"image_pre": get_temp_url("image-sca", "raw/" + row[4]),
|
||||
"image_after": get_temp_url("image-sca", "ai/" + row[5]),
|
||||
"resolution": row[6],
|
||||
"size": MyUtils.safe_round(row[7] / 1024, 2),
|
||||
"cocoon_count": row[8],
|
||||
"max_confidence": row[9],
|
||||
"min_confidence": row[10],
|
||||
"average_confidence": row[11],
|
||||
"other_info": row[12],
|
||||
"preprocess_time_ms": MyUtils.safe_round(row[13], 4),
|
||||
"inference_time_ms": MyUtils.safe_round(row[14], 4),
|
||||
"postprocess_time_ms": MyUtils.safe_round(row[15], 4),
|
||||
}
|
||||
)
|
||||
|
||||
return total, result
|
||||
|
||||
@@ -21,6 +21,8 @@ pyzxing==1.1.1
|
||||
Pillow==11.3.0
|
||||
python-multipart==0.0.20
|
||||
aio_pika==9.5.7
|
||||
ultralytics==8.3.227
|
||||
|
||||
# MCP服务
|
||||
python-dotenv>=1.0.0
|
||||
websockets>=11.0.3
|
||||
|
||||
@@ -6,7 +6,11 @@ from config.app import F8_SERVER_USER_ID
|
||||
from models.BaseResponse import BaseResponse
|
||||
from models.F8ImageRequest import F8ImageRequest
|
||||
from models.F8ImageRequestV2 import F8ImageRequestV2
|
||||
from service.vision import process_ticket_image, process_license_image
|
||||
from service.vision import (
|
||||
process_ticket_image,
|
||||
process_license_image,
|
||||
process_silkworm_cocoon_image,
|
||||
)
|
||||
from utils import MyUtils
|
||||
|
||||
publicRouter = APIRouter()
|
||||
@@ -55,3 +59,22 @@ async def cocoon_license(data: F8ImageRequest):
|
||||
return BaseResponse(data=data)
|
||||
except Exception as e:
|
||||
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
|
||||
|
||||
|
||||
@publicRouter.post("/recognize-silkworm-cocoon")
|
||||
async def recognize_silkworm_cocoon(data: F8ImageRequest):
|
||||
input_data = data.image
|
||||
if "," in input_data:
|
||||
input_data = input_data.split(",")[1]
|
||||
try:
|
||||
img_bytes = base64.b64decode(input_data)
|
||||
json_data = await MyUtils.async_task(
|
||||
process_silkworm_cocoon_image,
|
||||
img_bytes,
|
||||
f"{data.title}.jpg",
|
||||
data.title,
|
||||
F8_SERVER_USER_ID,
|
||||
)
|
||||
return BaseResponse(data=json_data)
|
||||
except Exception as e:
|
||||
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
|
||||
|
||||
@@ -7,7 +7,11 @@ from config.security import get_user_id_from_token
|
||||
from llm.ticketLLM import *
|
||||
from models.BaseResponse import BaseResponse
|
||||
from models.ImageRequest import ImageRequest
|
||||
from service.vision import process_ticket_image, process_license_image
|
||||
from service.vision import (
|
||||
process_ticket_image,
|
||||
process_license_image,
|
||||
process_silkworm_cocoon_image,
|
||||
)
|
||||
from utils import MyUtils
|
||||
|
||||
visionRouter = APIRouter()
|
||||
@@ -25,6 +29,8 @@ def cocoonTicket(data: ImageRequest, user_id: UUID = Depends(get_user_id_from_to
|
||||
return BaseResponse(status=False, message="unknown error", data=None)
|
||||
|
||||
|
||||
# ————————————————————————————————仪评指标联识别任务————————————————————————————————————————————————
|
||||
# 一代
|
||||
@visionRouter.post("/createTicketImageTask")
|
||||
async def createTicketImageTask(
|
||||
file: UploadFile = File(...),
|
||||
@@ -44,13 +50,7 @@ async def createTicketImageTask(
|
||||
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
|
||||
|
||||
|
||||
@visionRouter.get("/getTicketImageList")
|
||||
def cocoonTicket(user_id: UUID = Depends(get_user_id_from_token)):
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
return BaseResponse(data=pg.get_ticket_image_list(user_id))
|
||||
|
||||
|
||||
# 二代
|
||||
@visionRouter.post("/createTicketImageTaskV2")
|
||||
async def createTicketImageTask(
|
||||
file: UploadFile = File(...),
|
||||
@@ -70,24 +70,15 @@ async def createTicketImageTask(
|
||||
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
|
||||
|
||||
|
||||
@visionRouter.post("/createTicketImageTaskV2")
|
||||
async def createTicketImageTask(
|
||||
file: UploadFile = File(...),
|
||||
projectName: str = Form(...),
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
):
|
||||
# 获取仪评指标联识别任务列表
|
||||
@visionRouter.get("/getTicketImageList")
|
||||
def cocoonTicket(user_id: UUID = Depends(get_user_id_from_token)):
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
try:
|
||||
contents = await file.read()
|
||||
json_data = await MyUtils.async_task(
|
||||
process_ticket_image, 2, True, contents, file.filename, projectName, user_id
|
||||
)
|
||||
return BaseResponse(data=json_data)
|
||||
except Exception as e:
|
||||
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
|
||||
return BaseResponse(data=pg.get_ticket_image_list(user_id))
|
||||
|
||||
|
||||
# ————————————————————————————————证件照片识别任务————————————————————————————————————————————————
|
||||
@visionRouter.post("/createLicenseImageTask")
|
||||
async def createLicenseImageTask(
|
||||
file: UploadFile = File(...),
|
||||
@@ -107,7 +98,7 @@ async def createLicenseImageTask(
|
||||
|
||||
|
||||
@visionRouter.get("/getLicenseImageList")
|
||||
def cocoonLicense(
|
||||
def getLicenseImageList(
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
@@ -121,3 +112,40 @@ def cocoonLicense(
|
||||
"items": items,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ————————————————————————————————蚕茧识别任务————————————————————————————————————————————————
|
||||
@visionRouter.post("/createSilkwormCocoonAnalysisTask")
|
||||
async def createSilkwormCocoonAnalysisTask(
|
||||
file: UploadFile = File(...),
|
||||
projectName: str = Form(...),
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
):
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
try:
|
||||
contents = await file.read()
|
||||
await MyUtils.async_task(
|
||||
process_silkworm_cocoon_image, contents, file.filename, projectName, user_id
|
||||
)
|
||||
return BaseResponse(data=None)
|
||||
except Exception as e:
|
||||
return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None)
|
||||
|
||||
|
||||
@visionRouter.get("/getSilkwormCocoonAnalysisTasks")
|
||||
def getSilkwormCocoonAnalysisTasks(
|
||||
user_id: UUID = Depends(get_user_id_from_token),
|
||||
name: str = "",
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(10, ge=1, le=100),
|
||||
):
|
||||
if not user_id:
|
||||
return {"error": "userId is required"}
|
||||
total, items = pg.get_sca_image_list(user_id, name, page=page, page_size=page_size)
|
||||
return BaseResponse(
|
||||
data={
|
||||
"total": total,
|
||||
"items": items,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import config.minIO as minIO
|
||||
import db.postgres as pg
|
||||
from agent.licenseImageAgent import get_license_response
|
||||
from config.minIO import minio_client
|
||||
from config.yolo import YOLOSingleton
|
||||
from llm.ticketLLM import *
|
||||
from llm.ticketLLMv2 import get_ticket_response_v2
|
||||
|
||||
@@ -106,3 +107,86 @@ def process_license_image(
|
||||
)
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
def process_silkworm_cocoon_image(
|
||||
img_bytes=None,
|
||||
file_name: str = None,
|
||||
project_name: str = None,
|
||||
user_id: UUID = None,
|
||||
):
|
||||
# 上传到 OSS,使用 UUID 做对象名
|
||||
if img_bytes is None:
|
||||
img_bytes = []
|
||||
pre_object_name = str(uuid.uuid4())
|
||||
after_object_name = str(uuid.uuid4())
|
||||
file_bytes = BytesIO(img_bytes)
|
||||
|
||||
bucket_name = "image-sca"
|
||||
if not minio_client.bucket_exists(bucket_name):
|
||||
minio_client.make_bucket(bucket_name)
|
||||
minIO.push_file(
|
||||
bucket_name, "raw/" + pre_object_name, file_bytes, img_bytes, "image/jpeg"
|
||||
)
|
||||
|
||||
# YOLO检测
|
||||
img_bytes_out, results_json = YOLOSingleton.detect(img_bytes)
|
||||
|
||||
# results_json = {
|
||||
# "total_objects": "",
|
||||
# "max_confidence": "",
|
||||
# "min_confidence": "",
|
||||
# "avg_confidence": "",
|
||||
# "class_counts": "",
|
||||
# "speed_ms": {
|
||||
# "preprocess": "",
|
||||
# "inference": "",
|
||||
# "postprocess": "",
|
||||
# },
|
||||
# }
|
||||
speed_json = results_json.get("speed_ms")
|
||||
|
||||
file_bytes_out = BytesIO(img_bytes_out)
|
||||
minIO.push_file(
|
||||
bucket_name,
|
||||
"ai/" + after_object_name,
|
||||
file_bytes_out,
|
||||
img_bytes_out,
|
||||
"image/jpeg",
|
||||
)
|
||||
|
||||
# 获取图片分辨率和大小
|
||||
img = Image.open(BytesIO(img_bytes))
|
||||
resolution = f"{img.width}x{img.height}"
|
||||
size_kb = round(len(img_bytes) / 1024, 2)
|
||||
|
||||
# 插入数据库
|
||||
pg.insert_sca_image(
|
||||
file_name=file_name,
|
||||
resolution=resolution,
|
||||
size=size_kb,
|
||||
cocoon_count=results_json.get("total_objects"),
|
||||
max_confidence=results_json.get("max_confidence"),
|
||||
min_confidence=results_json.get("min_confidence"),
|
||||
average_confidence=results_json.get("avg_confidence"),
|
||||
other_info=results_json.get("class_counts"),
|
||||
preprocess_time_ms=speed_json.get("preprocess"),
|
||||
inference_time_ms=speed_json.get("inference"),
|
||||
postprocess_time_ms=speed_json.get("postprocess"),
|
||||
name=project_name if project_name else pre_object_name[:8],
|
||||
image_pre=pre_object_name,
|
||||
image_after=after_object_name,
|
||||
created_by=user_id,
|
||||
)
|
||||
return {
|
||||
"resolution": resolution,
|
||||
"size": size_kb,
|
||||
"cocoon_count": results_json.get("total_objects"),
|
||||
"max_confidence": results_json.get("max_confidence"),
|
||||
"min_confidence": results_json.get("min_confidence"),
|
||||
"average_confidence": results_json.get("avg_confidence"),
|
||||
"preprocess_time_ms": speed_json.get("preprocess"),
|
||||
"inference_time_ms": speed_json.get("inference"),
|
||||
"postprocess_time_ms": speed_json.get("postprocess"),
|
||||
"details": results_json.get("class_counts"),
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
LOCAL_IP = "10.10.12.101"
|
||||
@@ -20,3 +20,7 @@ def format_datetime(dt: datetime, tz="Asia/Shanghai"):
|
||||
tz_obj = pytz.timezone(tz)
|
||||
dt = dt.astimezone(tz_obj)
|
||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def safe_round(value, ndigits=2, default=None):
|
||||
return round(value, ndigits) if value is not None else default
|
||||
|
||||
Reference in New Issue
Block a user