import logging import cv2 import numpy as np import torch from ultralytics import YOLO import os from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont import numpy as np import cv2 import io # 设置工作目录 os.chdir("/home/bbit/mine/yolo/") from PIL import Image, ImageDraw, ImageFont import cv2 import numpy as np 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("runs/detect/train13/weights/best.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": round(min(confidences), 4) if confidences else 0.0, "max_confidence": round(max(confidences), 4) if confidences else 0.0, "avg_confidence": ( round(sum(confidences) / len(confidences), 4) if confidences else 0.0 ), "speed_ms": r.speed, # 直接来自 YOLO } return img_bytes_out, result_json import logging from pathlib import Path # 配置日志 logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s" ) logger = logging.getLogger("yolo_test") import os @staticmethod def save_image(img_bytes: bytes, save_path: str): """ 将检测后的字节流保存为图片文件 :param img_bytes: 图片的二进制字节(通常来自 detect() 返回) :param save_path: 保存路径(例如 '/home/bbit/mine/yolo/output/result.jpg') """ # 确保目录存在 os.makedirs(os.path.dirname(save_path), exist_ok=True) # 写入文件 with open(save_path, "wb") as f: f.write(img_bytes) def main(): # 初始化模型 YOLOSingleton.init_model() if not YOLOSingleton.is_ready(): logger.error("模型未初始化成功,退出") return # 测试图片路径 img_path = Path("/home/bbit/mine/yolo/valid/raw_1.jpg") if not img_path.exists(): logger.error(f"图片不存在: {img_path}") return # 读取图片为字节 with open(img_path, "rb") as f: img_bytes = f.read() # 执行检测 try: img_bytes_out,result = YOLOSingleton.detect(img_bytes) logger.info("检测结果:") import json print(json.dumps(result, indent=4)) save_image(img_bytes_out,'/home/bbit/mine/yolo/output/result.jpg') except Exception as e: logger.error(f"检测失败: {e}") if __name__ == "__main__": main()