Spaces:
Runtime error
Runtime error
| # -*- coding: utf-8 -*- | |
| """Модуль для работы с моделью YOLO и детекцией объектов""" | |
| import os | |
| from pathlib import Path | |
| import torch | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image, ImageDraw, ImageFont | |
| from PyQt5.QtCore import QThread, pyqtSignal | |
| try: | |
| from ultralytics import YOLO | |
| YOLO_AVAILABLE = True | |
| except ImportError: | |
| YOLO_AVAILABLE = False | |
| print("⚠️ Установите ultralytics: pip install ultralytics") | |
| def check_yolo_availability(): | |
| """Проверяет доступность библиотеки YOLO""" | |
| return YOLO_AVAILABLE | |
| class ModelLoader: | |
| """Класс для загрузки и управления моделью YOLO""" | |
| def __init__(self): | |
| self.model = None | |
| self.class_names = {} | |
| self.model_path = None | |
| def find_model_file(self, default_path="C:/PycharmProjects/XVL/src/model/best.pt"): | |
| """Ищет файл модели в различных местах""" | |
| possible_paths = [ | |
| Path(default_path), | |
| Path("model/best.pt"), | |
| Path("best.pt"), | |
| Path.cwd() / "best.pt", | |
| ] | |
| for path in possible_paths: | |
| if path.exists(): | |
| print(f"✅ Найдена модель: {path}") | |
| return str(path) | |
| return None | |
| def load_model(self, model_path=None): | |
| """Загружает модель YOLO""" | |
| if not YOLO_AVAILABLE: | |
| raise ImportError("Библиотека ultralytics не установлена!") | |
| if model_path: | |
| self.model_path = model_path | |
| else: | |
| self.model_path = self.find_model_file() | |
| if not self.model_path: | |
| raise FileNotFoundError("Файл модели best.pt не найден") | |
| try: | |
| self.model = YOLO(self.model_path) | |
| print(f"✅ Модель загружена: {self.model_path}") | |
| if hasattr(self.model, 'names'): | |
| self.class_names = self.model.names | |
| print(f"📊 Классы модели: {self.class_names}") | |
| return True | |
| except Exception as e: | |
| raise Exception(f"Не удалось загрузить модель: {str(e)}") | |
| def predict(self, image_path, confidence_threshold=0.25): | |
| """Выполняет предсказание на изображении""" | |
| if self.model is None: | |
| raise ValueError("Модель не загружена") | |
| # Загружаем изображение | |
| img = cv2.imread(str(image_path)) | |
| if img is None: | |
| raise FileNotFoundError(f"Не удалось загрузить изображение: {image_path}") | |
| img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | |
| # Выполняем предсказание | |
| results = self.model(img_rgb, conf=confidence_threshold) | |
| # Извлекаем результаты | |
| boxes = [] | |
| classes = [] | |
| confidences = [] | |
| if results and len(results) > 0: | |
| result = results[0] | |
| if result.boxes is not None: | |
| for box in result.boxes: | |
| # Координаты | |
| x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() | |
| boxes.append([x1, y1, x2, y2]) | |
| # Класс | |
| class_id = int(box.cls[0]) | |
| classes.append(class_id) | |
| # Уверенность | |
| conf = float(box.conf[0]) | |
| confidences.append(conf) | |
| return img_rgb, boxes, classes, confidences | |
| class DetectionThread(QThread): | |
| """Поток для выполнения детекции в фоновом режиме""" | |
| detection_finished = pyqtSignal(np.ndarray, list, list, list) # изображение, боксы, классы, уверенность | |
| detection_error = pyqtSignal(str) | |
| def __init__(self, model_loader, image_path, confidence_threshold=0.25): | |
| super().__init__() | |
| self.model_loader = model_loader | |
| self.image_path = image_path | |
| self.confidence_threshold = confidence_threshold | |
| def run(self): | |
| try: | |
| # Выполняем предсказание | |
| img_rgb, boxes, classes, confidences = self.model_loader.predict( | |
| self.image_path, | |
| self.confidence_threshold | |
| ) | |
| # Отправляем результаты | |
| self.detection_finished.emit(img_rgb, boxes, classes, confidences) | |
| except Exception as e: | |
| self.detection_error.emit(f"Ошибка детекции: {str(e)}") | |
| class ResultVisualizer: | |
| """Класс для визуализации результатов детекции""" | |
| COLORS = [ | |
| (255, 0, 0), # Красный | |
| (0, 255, 0), # Зеленый | |
| (0, 0, 255), # Синий | |
| (255, 255, 0), # Желтый | |
| (255, 0, 255), # Пурпурный | |
| (0, 255, 255), # Голубой | |
| (255, 165, 0), # Оранжевый | |
| (128, 0, 128), # Фиолетовый | |
| ] | |
| def draw_boxes(image, boxes, classes, confidences, class_names): | |
| """Рисует bounding boxes на изображении""" | |
| img_pil = Image.fromarray(image) | |
| draw = ImageDraw.Draw(img_pil) | |
| # Загружаем шрифт | |
| try: | |
| font = ImageFont.truetype("arial.ttf", 16) | |
| except: | |
| font = ImageFont.load_default() | |
| for i, (box, class_id, confidence) in enumerate(zip(boxes, classes, confidences)): | |
| x1, y1, x2, y2 = box | |
| # Выбираем цвет для класса | |
| color = ResultVisualizer.COLORS[class_id % len(ResultVisualizer.COLORS)] | |
| # Рисуем прямоугольник | |
| draw.rectangle([x1, y1, x2, y2], outline=color, width=3) | |
| # Подготовка текста | |
| class_name = class_names.get(class_id, f"Дефект {class_id}") | |
| label = f"{class_name}: {confidence:.1%}" | |
| # Рисуем фон для текста | |
| text_bbox = draw.textbbox((x1, y1), label, font=font) | |
| draw.rectangle(text_bbox, fill=color) | |
| # Рисуем текст | |
| draw.text((x1, y1), label, fill=(255, 255, 255), font=font) | |
| return np.array(img_pil) | |
| def get_statistics_text(classes, class_names): | |
| """Формирует текстовую статистику по результатам""" | |
| if not classes: | |
| return "Дефектов не обнаружено" | |
| class_counts = {} | |
| for class_id in classes: | |
| class_name = class_names.get(class_id, f"Дефект {class_id}") | |
| class_counts[class_name] = class_counts.get(class_name, 0) + 1 | |
| stats_parts = [f"{name}: {count}" for name, count in class_counts.items()] | |
| return " | ".join(stats_parts) |