Alex-Watchman commited on
Commit
fe35ddf
·
verified ·
1 Parent(s): 75435b9

Upload 3 files

Browse files
Files changed (3) hide show
  1. dialog.py +417 -0
  2. predictor.py +200 -0
  3. run.py +39 -0
dialog.py ADDED
@@ -0,0 +1,417 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Графический интерфейс приложения"""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+ from PyQt5.QtWidgets import (QApplication, QMainWindow, QPushButton,
7
+ QLabel, QVBoxLayout, QHBoxLayout, QWidget,
8
+ QFileDialog, QMessageBox)
9
+ from PyQt5.QtCore import Qt
10
+ from PyQt5.QtGui import QPixmap, QImage, QFont
11
+
12
+ from src import ModelLoader, DetectionThread, ResultVisualizer
13
+
14
+
15
+ class MainWindow(QMainWindow):
16
+ """Главное окно приложения"""
17
+
18
+ def __init__(self):
19
+ super().__init__()
20
+
21
+ # Настройка окна
22
+ self.setWindowTitle("XVL - Детекция дефектов сварки")
23
+ self.setGeometry(100, 100, 1200, 800)
24
+ self.setStyleSheet("background-color: rgb(0,0,0); color: rgb(0,255,0);")
25
+
26
+ # Инициализация компонентов
27
+ self.model_loader = ModelLoader()
28
+ self.result_visualizer = ResultVisualizer()
29
+ self.current_image_path = None
30
+ self.current_results = None
31
+
32
+ # Создание интерфейса
33
+ self._init_ui()
34
+
35
+ # Загрузка модели
36
+ self._load_model()
37
+
38
+ def _init_ui(self):
39
+ """Инициализация пользовательского интерфейса"""
40
+ # Центральный виджет
41
+ central_widget = QWidget()
42
+ self.setCentralWidget(central_widget)
43
+
44
+ # Главный вертикальный лейаут
45
+ main_layout = QVBoxLayout()
46
+ central_widget.setLayout(main_layout)
47
+
48
+ # Верхняя панель с кнопками
49
+ main_layout.addLayout(self._create_top_panel())
50
+
51
+ # Область отображения изображения
52
+ main_layout.addWidget(self._create_image_display(), 1)
53
+
54
+ # Панель статуса
55
+ main_layout.addLayout(self._create_status_panel())
56
+
57
+ # Нижняя панель
58
+ main_layout.addLayout(self._create_bottom_panel())
59
+
60
+ def _create_top_panel(self):
61
+ """Создает верхнюю панель с кнопками"""
62
+ top_layout = QHBoxLayout()
63
+
64
+ self.load_button = self._create_styled_button(
65
+ "📁 Загрузить фото",
66
+ "#4CAF50",
67
+ 150, 40,
68
+ self._load_image
69
+ )
70
+
71
+ self.reset_button = self._create_styled_button(
72
+ "🔄 Сбросить",
73
+ "#f0ad4e",
74
+ 120, 40,
75
+ self.reset_interface
76
+ )
77
+
78
+ self.info_button = self._create_styled_button(
79
+ "ℹ️ Информация",
80
+ "#5bc0de",
81
+ 120, 40,
82
+ self._show_info
83
+ )
84
+
85
+ top_layout.addWidget(self.load_button)
86
+ top_layout.addWidget(self.reset_button)
87
+ top_layout.addWidget(self.info_button)
88
+ top_layout.addStretch()
89
+
90
+ return top_layout
91
+
92
+ def _create_image_display(self):
93
+ """Создает область для отображения изображений"""
94
+ self.image_label = QLabel()
95
+ self.image_label.setAlignment(Qt.AlignCenter)
96
+ self.image_label.setMinimumSize(800, 600)
97
+ self.image_label.setStyleSheet("""
98
+ QLabel {
99
+ background-color: rgb(0,0,0);
100
+ border: 2px dashed #ccc;
101
+ border-radius: 10px;
102
+ }
103
+ """)
104
+ self.image_label.setText("Загрузите изображение для анализа")
105
+ self.image_label.setFont(QFont("Arial", 14))
106
+
107
+ return self.image_label
108
+
109
+ def _create_status_panel(self):
110
+ """Создает панель статуса"""
111
+ status_layout = QHBoxLayout()
112
+
113
+ self.status_label = QLabel("Готов к работе")
114
+ self.status_label.setFont(QFont("Arial", 10))
115
+
116
+ self.detection_label = QLabel("Дефектов не обнаружено")
117
+ self.detection_label.setFont(QFont("Arial", 10, QFont.Bold))
118
+ self.detection_label.setStyleSheet("color: #666;")
119
+
120
+ status_layout.addWidget(self.status_label)
121
+ status_layout.addStretch()
122
+ status_layout.addWidget(self.detection_label)
123
+
124
+ return status_layout
125
+
126
+ def _create_bottom_panel(self):
127
+ """Создает нижнюю панель с кнопками"""
128
+ bottom_layout = QHBoxLayout()
129
+
130
+ self.close_button = self._create_styled_button(
131
+ "✖ Закрыть и вернуться",
132
+ "#d9534f",
133
+ 200, 50,
134
+ self.reset_interface
135
+ )
136
+ self.close_button.setEnabled(False)
137
+
138
+ bottom_layout.addStretch()
139
+ bottom_layout.addWidget(self.close_button)
140
+ bottom_layout.addStretch()
141
+
142
+ return bottom_layout
143
+
144
+ def _create_styled_button(self, text, color, width, height, callback):
145
+ """Создает стилизованную кнопку"""
146
+ button = QPushButton(text)
147
+ button.setFixedSize(width, height)
148
+ button.setStyleSheet(f"""
149
+ QPushButton {{
150
+ background-color: {color};
151
+ color: white;
152
+ font-weight: bold;
153
+ border-radius: 5px;
154
+ font-size: {14 if height <= 40 else 16}px;
155
+ }}
156
+ QPushButton:hover {{
157
+ background-color: {self._darken_color(color)};
158
+ }}
159
+ QPushButton:pressed {{
160
+ background-color: {self._darken_color(color, 20)};
161
+ }}
162
+ """)
163
+ button.clicked.connect(callback)
164
+ return button
165
+
166
+ def _darken_color(self, hex_color, percent=10):
167
+ """Затемняет цвет на указанный процент"""
168
+ import colorsys
169
+ hex_color = hex_color.lstrip('#')
170
+ rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
171
+ hls = colorsys.rgb_to_hls(rgb[0]/255, rgb[1]/255, rgb[2]/255)
172
+ new_l = max(0, hls[1] - percent/100)
173
+ new_rgb = colorsys.hls_to_rgb(hls[0], new_l, hls[2])
174
+ return f'rgb({int(new_rgb[0]*255)},{int(new_rgb[1]*255)},{int(new_rgb[2]*255)})'
175
+
176
+ def _load_model(self):
177
+ """Загружает модель YOLO"""
178
+ try:
179
+ success = self.model_loader.load_model()
180
+ if success:
181
+ QMessageBox.information(
182
+ self,
183
+ "Успех",
184
+ f"Модель загружена успешно!\nКлассов: {len(self.model_loader.class_names)}"
185
+ )
186
+ except FileNotFoundError:
187
+ reply = QMessageBox.question(
188
+ self, "Модель не найдена",
189
+ "Файл модели best.pt не найден.\nХотите указать путь вручную?",
190
+ QMessageBox.Yes | QMessageBox.No
191
+ )
192
+ if reply == QMessageBox.Yes:
193
+ self._select_model_file()
194
+ else:
195
+ QMessageBox.warning(
196
+ self, "Внимание",
197
+ "Модель не загружена. Функционал детекции недоступен."
198
+ )
199
+ except Exception as e:
200
+ QMessageBox.critical(
201
+ self, "Ошибка загрузки модели",
202
+ f"Не удалось загрузить модель:\n{str(e)}"
203
+ )
204
+
205
+ def _select_model_file(self):
206
+ """Позволяет пользователю выбрать файл модели"""
207
+ file_path, _ = QFileDialog.getOpenFileName(
208
+ self, "Выберите файл модели",
209
+ str(Path.home()),
210
+ "Модели PyTorch (*.pt);;Все файлы (*)"
211
+ )
212
+
213
+ if file_path:
214
+ try:
215
+ self.model_loader.load_model(file_path)
216
+ except Exception as e:
217
+ QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить модель: {str(e)}")
218
+
219
+ def _load_image(self):
220
+ """Открывает проводник для выбора изображения"""
221
+ file_path, _ = QFileDialog.getOpenFileName(
222
+ self, "Выберите изображение",
223
+ str(Path.home()),
224
+ "Изображения (*.jpg *.jpeg *.png *.bmp *.tiff);;Все файлы (*)"
225
+ )
226
+
227
+ if file_path:
228
+ self._process_image(file_path)
229
+
230
+ def _process_image(self, image_path):
231
+ """Обрабатывает выбранное изображение"""
232
+ self.current_image_path = image_path
233
+
234
+ # Обновляем статус
235
+ self._update_status("Анализ изображения...", "#f0ad4e")
236
+
237
+ # Загружаем и показываем оригинальное изображение
238
+ self._display_image(image_path)
239
+
240
+ # Запускаем детекцию в отдельном потоке
241
+ if self.model_loader.model is not None:
242
+ self.detection_thread = DetectionThread(
243
+ self.model_loader,
244
+ image_path,
245
+ confidence_threshold=0.25
246
+ )
247
+ self.detection_thread.detection_finished.connect(self._on_detection_finished)
248
+ self.detection_thread.detection_error.connect(self._on_detection_error)
249
+ self.detection_thread.start()
250
+ else:
251
+ QMessageBox.warning(self, "Ошибка", "Модель не загружена!")
252
+ self._update_status("Модель не загружена", "#d9534f")
253
+
254
+ def _display_image(self, image_path):
255
+ """Отображает изображение в интерфейсе"""
256
+ pixmap = QPixmap(image_path)
257
+
258
+ if not pixmap.isNull():
259
+ scaled_pixmap = pixmap.scaled(
260
+ self.image_label.size() * 0.9,
261
+ Qt.KeepAspectRatio,
262
+ Qt.SmoothTransformation
263
+ )
264
+ self.image_label.setPixmap(scaled_pixmap)
265
+ self.image_label.setStyleSheet("border: 2px solid #4CAF50; border-radius: 10px;")
266
+
267
+ def _on_detection_finished(self, image, boxes, classes, confidences):
268
+ """Обрабатывает завершение детекции"""
269
+ # Сохраняем результаты
270
+ self.current_results = (boxes, classes, confidences)
271
+
272
+ # Рисуем bounding boxes на изображении
273
+ result_image = self.result_visualizer.draw_boxes(
274
+ image, boxes, classes, confidences, self.model_loader.class_names
275
+ )
276
+
277
+ # Отображаем результат
278
+ self._display_result_image(result_image)
279
+
280
+ # Обновляем статус
281
+ num_defects = len(boxes)
282
+ if num_defects > 0:
283
+ self._update_status(
284
+ f"✅ Анализ завершен. Обнаружено дефектов: {num_defects}",
285
+ "#4CAF50"
286
+ )
287
+
288
+ # Показываем статистику
289
+ stats_text = self.result_visualizer.get_statistics_text(
290
+ classes, self.model_loader.class_names
291
+ )
292
+ self.detection_label.setText(stats_text)
293
+ self.detection_label.setStyleSheet("color: #d9534f; font-weight: bold;")
294
+ else:
295
+ self._update_status(
296
+ "✅ Анализ завершен. Дефекты не обнаружены",
297
+ "#4CAF50"
298
+ )
299
+ self.detection_label.setText("Дефектов не обнаружено")
300
+ self.detection_label.setStyleSheet("color: #5bc0de; font-weight: bold;")
301
+
302
+ # Активируем кнопку закрыть
303
+ self.close_button.setEnabled(True)
304
+
305
+ def _on_detection_error(self, error_message):
306
+ """Обрабатывает ошибки детекции"""
307
+ self._update_status("❌ Ошибка анализа", "#d9534f")
308
+ self.detection_label.setText("Ошибка")
309
+
310
+ QMessageBox.critical(self, "Ошибка анализа", error_message)
311
+
312
+ # Сбрасываем изображение
313
+ self.image_label.setText("Ошибка при анализе изображения")
314
+ self.image_label.setStyleSheet("""
315
+ QLabel {
316
+ background-color: #f5f5f5;
317
+ border: 2px dashed #d9534f;
318
+ border-radius: 10px;
319
+ color: #d9534f;
320
+ }
321
+ """)
322
+
323
+ def _display_result_image(self, image_array):
324
+ """Отображает результат в виде QPixmap"""
325
+ height, width, channel = image_array.shape
326
+ bytes_per_line = 3 * width
327
+ q_image = QImage(image_array.data, width, height, bytes_per_line, QImage.Format_RGB888)
328
+ pixmap = QPixmap.fromImage(q_image)
329
+
330
+ # Масштабируем для отображения
331
+ scaled_pixmap = pixmap.scaled(
332
+ self.image_label.size() * 0.9,
333
+ Qt.KeepAspectRatio,
334
+ Qt.SmoothTransformation
335
+ )
336
+
337
+ self.image_label.setPixmap(scaled_pixmap)
338
+
339
+ def _update_status(self, text, color):
340
+ """Обновляет текст статуса"""
341
+ self.status_label.setText(text)
342
+ self.status_label.setStyleSheet(f"color: {color}; font-weight: bold;")
343
+
344
+ def reset_interface(self):
345
+ """Сбрасывает интерфейс к начальному состоянию"""
346
+ self.current_image_path = None
347
+ self.current_results = None
348
+
349
+ self.image_label.clear()
350
+ self.image_label.setText("Загрузите изображение для анализа")
351
+ self.image_label.setStyleSheet("""
352
+ QLabel {
353
+ background-color: rgb(0,0,0);
354
+ border: 2px dashed #ccc;
355
+ border-radius: 10px;
356
+ }
357
+ """)
358
+ self.image_label.setFont(QFont("Arial", 14))
359
+
360
+ self._update_status("Готов к работе", "rgb(0,255,0)")
361
+
362
+ self.detection_label.setText("Дефектов не обнаружено")
363
+ self.detection_label.setStyleSheet("color: #666;")
364
+
365
+ self.close_button.setEnabled(False)
366
+
367
+ def _show_info(self):
368
+ """Показывает информацию о программе"""
369
+ info_text = """
370
+ <h3>XVL - Детекция дефектов сварки</h3>
371
+ <p>Программа для обнаружения дефектов сварки с использованием YOLOv8.</p>
372
+
373
+ <h4>��нструкция:</h4>
374
+ <ol>
375
+ <li>Нажмите кнопку "Загрузить фото"</li>
376
+ <li>Выберите изображение с дефектами сварки</li>
377
+ <li>Дождитесь завершения анализа</li>
378
+ <li>Просмотрите результат с отмеченными дефектами</li>
379
+ <li>Нажмите "Закрыть и вернуться" для анализа нового изображения</li>
380
+ </ol>
381
+
382
+ <h4>Информация о модели:</h4>
383
+ """
384
+
385
+ if self.model_loader.model_path:
386
+ info_text += f"<p>Модель: {Path(self.model_loader.model_path).name}</p>"
387
+
388
+ if self.model_loader.class_names:
389
+ info_text += "<p>Классы:</p><ul>"
390
+ for class_id, class_name in self.model_loader.class_names.items():
391
+ info_text += f"<li>{class_id}: {class_name}</li>"
392
+ info_text += "</ul>"
393
+
394
+ QMessageBox.information(self, "Информация", info_text)
395
+
396
+ def closeEvent(self, event):
397
+ """Обрабатывает закрытие окна"""
398
+ reply = QMessageBox.question(
399
+ self, "Выход",
400
+ "Вы уверены, что хотите выйти?",
401
+ QMessageBox.Yes | QMessageBox.No,
402
+ QMessageBox.No
403
+ )
404
+
405
+ if reply == QMessageBox.Yes:
406
+ event.accept()
407
+ else:
408
+ event.ignore()
409
+
410
+
411
+ # Для прямого запуска GUI (если нужно)
412
+ if __name__ == "__main__":
413
+ app = QApplication(sys.argv)
414
+ app.setStyle('Fusion')
415
+ window = MainWindow()
416
+ window.show()
417
+ sys.exit(app.exec_())
predictor.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Модуль для работы с моделью YOLO и детекцией объектов"""
3
+
4
+ import os
5
+ from pathlib import Path
6
+ import torch
7
+ import cv2
8
+ import numpy as np
9
+ from PIL import Image, ImageDraw, ImageFont
10
+ from PyQt5.QtCore import QThread, pyqtSignal
11
+
12
+ try:
13
+ from ultralytics import YOLO
14
+ YOLO_AVAILABLE = True
15
+ except ImportError:
16
+ YOLO_AVAILABLE = False
17
+ print("⚠️ Установите ultralytics: pip install ultralytics")
18
+
19
+
20
+ def check_yolo_availability():
21
+ """Проверяет доступность библиотеки YOLO"""
22
+ return YOLO_AVAILABLE
23
+
24
+
25
+ class ModelLoader:
26
+ """Класс для загрузки и управления моделью YOLO"""
27
+
28
+ def __init__(self):
29
+ self.model = None
30
+ self.class_names = {}
31
+ self.model_path = None
32
+
33
+ def find_model_file(self, default_path="C:/PycharmProjects/XVL/src/model/best.pt"):
34
+ """Ищет файл модели в различных местах"""
35
+ possible_paths = [
36
+ Path(default_path),
37
+ Path("model/best.pt"),
38
+ Path("best.pt"),
39
+ Path.cwd() / "best.pt",
40
+ ]
41
+
42
+ for path in possible_paths:
43
+ if path.exists():
44
+ print(f"✅ Найдена модель: {path}")
45
+ return str(path)
46
+
47
+ return None
48
+
49
+ def load_model(self, model_path=None):
50
+ """Загружает модель YOLO"""
51
+ if not YOLO_AVAILABLE:
52
+ raise ImportError("Библиотека ultralytics не установлена!")
53
+
54
+ if model_path:
55
+ self.model_path = model_path
56
+ else:
57
+ self.model_path = self.find_model_file()
58
+
59
+ if not self.model_path:
60
+ raise FileNotFoundError("Файл модели best.pt не найден")
61
+
62
+ try:
63
+ self.model = YOLO(self.model_path)
64
+ print(f"✅ Модель загружена: {self.model_path}")
65
+
66
+ if hasattr(self.model, 'names'):
67
+ self.class_names = self.model.names
68
+ print(f"📊 Классы модели: {self.class_names}")
69
+
70
+ return True
71
+
72
+ except Exception as e:
73
+ raise Exception(f"Не удалось загрузить модель: {str(e)}")
74
+
75
+ def predict(self, image_path, confidence_threshold=0.25):
76
+ """Выполняет предсказание на изображении"""
77
+ if self.model is None:
78
+ raise ValueError("Модель не загружена")
79
+
80
+ # Загружаем изображение
81
+ img = cv2.imread(str(image_path))
82
+ if img is None:
83
+ raise FileNotFoundError(f"Не удалось загрузить изображение: {image_path}")
84
+
85
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
86
+
87
+ # Выполняем предсказание
88
+ results = self.model(img_rgb, conf=confidence_threshold)
89
+
90
+ # Извлекаем результаты
91
+ boxes = []
92
+ classes = []
93
+ confidences = []
94
+
95
+ if results and len(results) > 0:
96
+ result = results[0]
97
+ if result.boxes is not None:
98
+ for box in result.boxes:
99
+ # Координаты
100
+ x1, y1, x2, y2 = box.xyxy[0].cpu().numpy()
101
+ boxes.append([x1, y1, x2, y2])
102
+
103
+ # Класс
104
+ class_id = int(box.cls[0])
105
+ classes.append(class_id)
106
+
107
+ # Уверенность
108
+ conf = float(box.conf[0])
109
+ confidences.append(conf)
110
+
111
+ return img_rgb, boxes, classes, confidences
112
+
113
+
114
+ class DetectionThread(QThread):
115
+ """Поток для выполнения детекции в фоновом режиме"""
116
+ detection_finished = pyqtSignal(np.ndarray, list, list, list) # изображение, боксы, классы, уверенность
117
+ detection_error = pyqtSignal(str)
118
+
119
+ def __init__(self, model_loader, image_path, confidence_threshold=0.25):
120
+ super().__init__()
121
+ self.model_loader = model_loader
122
+ self.image_path = image_path
123
+ self.confidence_threshold = confidence_threshold
124
+
125
+ def run(self):
126
+ try:
127
+ # Выполняем предсказание
128
+ img_rgb, boxes, classes, confidences = self.model_loader.predict(
129
+ self.image_path,
130
+ self.confidence_threshold
131
+ )
132
+
133
+ # Отправляем результаты
134
+ self.detection_finished.emit(img_rgb, boxes, classes, confidences)
135
+
136
+ except Exception as e:
137
+ self.detection_error.emit(f"Ошибка детекции: {str(e)}")
138
+
139
+
140
+ class ResultVisualizer:
141
+ """Класс для визуализации результатов детекции"""
142
+
143
+ COLORS = [
144
+ (255, 0, 0), # Красный
145
+ (0, 255, 0), # Зеленый
146
+ (0, 0, 255), # Синий
147
+ (255, 255, 0), # Желтый
148
+ (255, 0, 255), # Пурпурный
149
+ (0, 255, 255), # Голубой
150
+ (255, 165, 0), # Оранжевый
151
+ (128, 0, 128), # Фиолетовый
152
+ ]
153
+
154
+ @staticmethod
155
+ def draw_boxes(image, boxes, classes, confidences, class_names):
156
+ """Рисует bounding boxes на изображении"""
157
+ img_pil = Image.fromarray(image)
158
+ draw = ImageDraw.Draw(img_pil)
159
+
160
+ # Загружаем шрифт
161
+ try:
162
+ font = ImageFont.truetype("arial.ttf", 16)
163
+ except:
164
+ font = ImageFont.load_default()
165
+
166
+ for i, (box, class_id, confidence) in enumerate(zip(boxes, classes, confidences)):
167
+ x1, y1, x2, y2 = box
168
+
169
+ # Выбираем цвет для класса
170
+ color = ResultVisualizer.COLORS[class_id % len(ResultVisualizer.COLORS)]
171
+
172
+ # Рисуем прямоугольник
173
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
174
+
175
+ # Подготовка текста
176
+ class_name = class_names.get(class_id, f"Дефект {class_id}")
177
+ label = f"{class_name}: {confidence:.1%}"
178
+
179
+ # Рисуем фон для текста
180
+ text_bbox = draw.textbbox((x1, y1), label, font=font)
181
+ draw.rectangle(text_bbox, fill=color)
182
+
183
+ # Рисуем текст
184
+ draw.text((x1, y1), label, fill=(255, 255, 255), font=font)
185
+
186
+ return np.array(img_pil)
187
+
188
+ @staticmethod
189
+ def get_statistics_text(classes, class_names):
190
+ """Формирует текстовую статистику по результатам"""
191
+ if not classes:
192
+ return "Дефектов не обнаружено"
193
+
194
+ class_counts = {}
195
+ for class_id in classes:
196
+ class_name = class_names.get(class_id, f"Дефект {class_id}")
197
+ class_counts[class_name] = class_counts.get(class_name, 0) + 1
198
+
199
+ stats_parts = [f"{name}: {count}" for name, count in class_counts.items()]
200
+ return " | ".join(stats_parts)
run.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Главный файл приложения для детекции дефектов сварки"""
3
+
4
+ import sys
5
+ from PyQt5.QtWidgets import QApplication, QMessageBox
6
+ from src import MainWindow
7
+ from src import check_yolo_availability
8
+
9
+
10
+ def main():
11
+ """Главная функция приложения"""
12
+ app = QApplication(sys.argv)
13
+ app.setStyle('Fusion')
14
+
15
+ # Проверяем доступность библиотек
16
+ if not check_yolo_availability():
17
+ msg = QMessageBox()
18
+ msg.setIcon(QMessageBox.Warning)
19
+ msg.setWindowTitle("Предупреждение")
20
+ msg.setText("Библиотека ultralytics не установлена!")
21
+ msg.setInformativeText(
22
+ "Для работы программы требуется установить библиотеку ultralytics.\n\n"
23
+ "Установите её, выполнив команду:\n"
24
+ "pip install ultralytics\n\n"
25
+ "Продолжить без детекции?"
26
+ )
27
+ msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
28
+
29
+ if msg.exec_() == QMessageBox.No:
30
+ sys.exit(1)
31
+
32
+ window = MainWindow()
33
+ window.show()
34
+
35
+ sys.exit(app.exec_())
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()