Напиши программу для анализа размера груди на друг...
Creado el: 8 de marzo de 2025
Creado el: 8 de marzo de 2025
Напиши программу для анализа размера груди на других проверенных размерах груди. Тоесть у меня есть проверенные фото где размер известен. Я хочу чтобы ты написал программу которая основываясь на видео(18+) Собирала датасеты и создавала дальнейшую сеть обучения. Тоесть чтобы из условных 10 фото девушек с 2 размером груди нейросеть могла определять размер груди и писать процент схожести. И также чтобы она дообучалась прям в процессе. Также найди готовую нейросетку для определения возраста по лицу и тоже сделай чтобы выводилось. Что в итоге, gui где я выбрал видео и нейросетка на видео в левом нижнем углу пишет предпологаемый размер груди и возраст(если людей несколько то писать несколько значений). Для программы используй любые языки программирования. Удачи. Мне нужен большой и развёрнутый код. Можно на любой из доступных и быстрых языков програмирования
Ниже приведён пример комплексного проекта на языке Python, который иллюстрирует, как можно:
Собирать датасет и обучать (или дообучать) нейросеть для классификации размера груди по имеющимся проверенным фотографиям.
Использовать готовую нейросеть для определения возраста по лицу (на основе заранее скачанной или подключаемой модели).
Обрабатывать видео:
Отображать результаты в GUI (например, с помощью PyQt5): при загрузке видео в окошке (или в нижней части), на каждом кадре в реальном времени выводить предполагаемый размер груди и возраст. Если в кадре несколько людей, выводить несколько результатов.
Обратите внимание:
Предположим, у вас есть следующие папки/файлы:
textproject/ ├── datasets/ │ ├── breast_size/ │ │ ├── size1/ # Изображения/кадры с грудью 1 размера │ │ ├── size2/ # Изображения/кадры с грудью 2 размера │ │ ├── size3/ # ... и т.д. │ └── ... ├── models/ │ ├── breast_classifier.pth # Сохранённые веса обученной модели классификации груди (после обучения) │ ├── age_model.pth # Модель для определения возраста (предположим, PyTorch) │ └── ... ├── main.py # Главный скрипт запуска GUI ├── train_breast_classifier.py # Скрипт для обучения / дообучения модели классификации размера груди └── requirements.txt # Список зависимостей
Ниже приведён примерный код для обучения (и дообучения) модели классификации размера груди, а также код для GUI-приложения с использованием PyQt5. В проекте используются следующие компоненты:
Скрипт train_breast_classifier.py
:
datasets/breast_size/
, где каждый подпапка — это класс (в нашем случае, разные размеры груди)..pth
файл).GUI-приложение main.py
:
models/breast_classifier.pth
.models/age_model.pth
).Онлайн/прямое дообучение: это более сложная часть. Можно реализовать механизм, при котором пользователь вручную подтверждает или опровергает предсказанный размер груди, и на основе этих новых данных модель дообучается. Однако нужно понимать, что online learning в классических CNN — не всегда тривиальная вещь; может понадобиться специализированный подход (e.g. тренировка на последних слоях, небольшой learning rate, небольшой batch). Ниже для простоты мы покажем лишь псевдокод, как можно «подгрузить» новые данные и доучить модель.
pythonimport os import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms, models def get_data_loaders(data_dir, batch_size=8, img_size=224): """ Создаём Datasets и Dataloaders для train/val из папок. Предполагаем, что структура папок: data_dir/ size1/ size2/ size3/ ... """ transform = transforms.Compose([ transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # Стандартные средние и std для ImageNet ]) dataset = datasets.ImageFolder(data_dir, transform=transform) # Делим dataset на train/val (например 80/20) train_size = int(0.8 * len(dataset)) val_size = len(dataset) - train_size train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size]) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False) return train_loader, val_loader, len(dataset.classes) def train_model(data_dir="datasets/breast_size", epochs=5, lr=1e-4, batch_size=8, model_save_path="models/breast_classifier.pth"): # 1. Готовим DataLoader train_loader, val_loader, num_classes = get_data_loaders(data_dir, batch_size=batch_size) # 2. Загружаем предобученную модель (ResNet18 для примера) model = models.resnet18(pretrained=True) # Меняем последний слой под наше количество классов (размеров груди) model.fc = nn.Linear(model.fc.in_features, num_classes) # 3. Готовим оптимизатор и функцию потерь criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=lr) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) best_val_acc = 0.0 for epoch in range(epochs): # --- TRAIN --- model.train() train_loss, train_correct, total = 0.0, 0, 0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # статистика train_loss += loss.item() * inputs.size(0) _, preds = torch.max(outputs, 1) train_correct += torch.sum(preds == labels).item() total += labels.size(0) train_epoch_loss = train_loss / total train_epoch_acc = train_correct / total # --- VALIDATION --- model.eval() val_loss, val_correct, val_total = 0.0, 0, 0 with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) val_loss += loss.item() * inputs.size(0) _, preds = torch.max(outputs, 1) val_correct += torch.sum(preds == labels).item() val_total += labels.size(0) val_epoch_loss = val_loss / val_total val_epoch_acc = val_correct / val_total print(f"Epoch [{epoch+1}/{epochs}] " f"Train Loss: {train_epoch_loss:.4f} Train Acc: {train_epoch_acc:.4f} " f"Val Loss: {val_epoch_loss:.4f} Val Acc: {val_epoch_acc:.4f}") # Сохраняем лучшую модель if val_epoch_acc > best_val_acc: best_val_acc = val_epoch_acc torch.save(model.state_dict(), model_save_path) print("Model saved!") print("Training finished. Best Val Acc:", best_val_acc) def incremental_train(new_data_dir, model_path="models/breast_classifier.pth", epochs=2, lr=1e-5, batch_size=4): """ Пример псевдокода для дообучения (incremental learning). Допустим, у нас есть новая папка new_data_dir, где тоже лежат новые данные в схожей структуре (классы = размеры груди). """ # 1. Загружаем ту же модель device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = models.resnet18(pretrained=False) # Нужно знать, сколько у нас классов (должно совпадать с предыдущим!) # Либо загрузить класс names из старого датасета, либо явно знать число классов. # Для упрощения предполагаем 3 класса (size1, size2, size3). num_classes = 3 model.fc = nn.Linear(model.fc.in_features, num_classes) model.load_state_dict(torch.load(model_path, map_location=device)) model.to(device) # 2. Готовим новые данные train_loader, val_loader, _ = get_data_loaders(new_data_dir, batch_size=batch_size) # 3. Дообучаем часть слоёв criterion = nn.CrossEntropyLoss() # Например, заморозим часть слоёв, кроме последних for param in model.parameters(): param.requires_grad = False for param in model.fc.parameters(): param.requires_grad = True optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=lr) for epoch in range(epochs): model.train() for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # Здесь вы можете также сделать валидацию и т.д. (не показано для краткости) # Сохраняем обновлённые веса torch.save(model.state_dict(), model_path) print("Incremental training complete. Model updated.") if __name__ == "__main__": # Пример запуска основной тренировки train_model(epochs=5) # Пример запуска дообучения на новых данных # incremental_train(new_data_dir="datasets/new_breast_data", epochs=2)
После успешного обучения (или дообучения) вы получите в папке models/
файл breast_classifier.pth
. Это ваша модель-классификатор, которая умеет классифицировать размер груди.
Ниже приведён пример кода на PyQt5, который:
cv2.CascadeClassifier
как упрощённый пример; вы можете поставить любой другой детектор: MTCNN, Dlib, mediapipe, etc.).age_model.pth
), чтобы предсказать возраст.breast_classifier.pth
.Внимание: код достаточно объёмный, но в реальности вам придётся доделать детали:
torch.softmax(...)
или аналогичных функций.В requirements.txt
(пример):
texttorch>=1.9 torchvision>=0.10 opencv-python pyqt5 numpy
(Уточните версии под вашу среду.)
main.py
pythonimport sys import cv2 import torch import numpy as np from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel, QFileDialog from PyQt5.QtCore import QThread, pyqtSignal, Qt from torchvision import transforms, models # ====== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ И КЛАССЫ ====== def preprocess_image_for_model(frame, img_size=224): """ Предварительная обработка изображения под вход модели (PyTorch). Допустим, у нас уже вырезана нужная область (лицо или грудь), нужно привести к размеру 224х224, сделать ToTensor, Normalize и т.д. """ transform = transforms.Compose([ transforms.ToPILImage(), transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) return transform(frame).unsqueeze(0) # возвращаем batch из 1 образца def softmax_to_confidence_and_class(logits): """ Превращает выход модели (logits) в: - предсказанный класс - процент уверенности в этом классе """ prob = torch.softmax(logits, dim=1) conf, pred_class = torch.max(prob, dim=1) return pred_class.item(), conf.item() # ====== ПОТОК ДЛЯ ОБРАБОТКИ ВИДЕО ====== class VideoThread(QThread): change_pixmap_signal = pyqtSignal(np.ndarray) def __init__(self, video_path, age_model, breast_model, breast_classes, parent=None): super().__init__(parent) self.video_path = video_path self.age_model = age_model self.breast_model = breast_model self.breast_classes = breast_classes self._run_flag = True # Для детекции лиц используем каскад Хаара (упрощённо) self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') def run(self): cap = cv2.VideoCapture(self.video_path) while self._run_flag and cap.isOpened(): ret, frame = cap.read() if not ret: break # 1. Детектируем лица (упрощённый метод) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(50, 50)) # 2. Для каждого лица -> предсказать возраст for (x, y, w, h) in faces: face_roi = frame[y:y+h, x:x+w] # Предобработка input_tensor = preprocess_image_for_model(face_roi) self.age_model.eval() with torch.no_grad(): logits = self.age_model(input_tensor) # Предположим, что age_model выводит один логит (регрессия) — грубо говоря, предсказание возраста # Или (чаще) в задачах классификации возраста - несколько бинов. Для примера возьмём регрессию: predicted_age = logits.item() # если выход 1 нейрон # Рисуем прямоугольник на лице и возраст cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(frame, f"Age: {int(predicted_age)}", (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) # 3. (Условно) Находим область груди (это отдельная задача!). # Предположим, что в нашем примере мы грубо берём нижнюю часть кадра # или у нас есть какой-то bounding box груди. Для демонстрации возьмём # центральную область кадра. В реальности нужен детектор!!! height, width, _ = frame.shape breast_region = frame[int(height/2):height, int(width/3):int(2*width/3)] input_tensor_breast = preprocess_image_for_model(breast_region) self.breast_model.eval() with torch.no_grad(): logits_breast = self.breast_model(input_tensor_breast) pred_class_idx, conf = softmax_to_confidence_and_class(logits_breast) breast_size_label = self.breast_classes[pred_class_idx] conf_percent = conf * 100 # 4. Рисуем предположительный размер груди и % уверенности в углу text_breast = f"Breast: {breast_size_label} ({conf_percent:.1f}%)" cv2.putText(frame, text_breast, (30, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 0, 0), 2) self.change_pixmap_signal.emit(frame) cap.release() def stop(self): self._run_flag = False self.wait() # ====== ОСНОВНОЕ ОКНО ПРИЛОЖЕНИЯ ====== class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Breast Size & Age Detection") self.setGeometry(100, 100, 800, 600) # Кнопка для выбора видео self.open_btn = QPushButton("Выбрать видео", self) self.open_btn.setGeometry(50, 50, 120, 40) self.open_btn.clicked.connect(self.open_video) # Место для отображения кадров self.label_video = QLabel(self) self.label_video.setGeometry(50, 120, 700, 400) self.label_video.setScaledContents(True) # Инициализируем модели self.age_model = self.load_age_model("models/age_model.pth") self.breast_model, self.breast_classes = self.load_breast_model("models/breast_classifier.pth") self.video_thread = None def load_age_model(self, path): """ Загружаем модель возраста. В простейшем варианте предполагаем, что это модель регрессии на 1 нейрон: age_model_output = model(input) predicted_age = age_model_output.item() """ # Загружаем модель (например, ResNet18, где последний слой выведен в 1 выход) model = models.resnet18() model.fc = torch.nn.Linear(model.fc.in_features, 1) # 1 — регрессия возраста device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.load_state_dict(torch.load(path, map_location=device)) model.to(device) model.eval() return model def load_breast_model(self, path): """ Загружаем обученную модель размера груди и список классов. Предположим, что у нас было 3 класса: ['size1', 'size2', 'size3']. В реальности лучше грузить их автоматически из датасета / словаря. """ breast_classes = ['size1', 'size2', 'size3'] # или подгружать из файла model = models.resnet18() model.fc = torch.nn.Linear(model.fc.in_features, len(breast_classes)) device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.load_state_dict(torch.load(path, map_location=device)) model.to(device) model.eval() return model, breast_classes def open_video(self): options = QFileDialog.Options() file_name, _ = QFileDialog.getOpenFileName(self, "Выбрать видео", "", "Video Files (*.mp4 *.avi *.mov)", options=options) if file_name: # Останавливаем поток, если запущен if self.video_thread is not None: self.video_thread.stop() # Создаем новый поток self.video_thread = VideoThread( video_path=file_name, age_model=self.age_model, breast_model=self.breast_model, breast_classes=self.breast_classes ) self.video_thread.change_pixmap_signal.connect(self.update_image) self.video_thread.start() def closeEvent(self, event): if self.video_thread is not None: self.video_thread.stop() event.accept() @QtCore.pyqtSlot(np.ndarray) def update_image(self, cv_img): """ Конвертируем OpenCV-кадр в формат Qt и отображаем в QLabel. """ qt_img = self.convert_cv_qt(cv_img) self.label_video.setPixmap(qt_img) def convert_cv_qt(self, cv_img): """ Преобразование цвета + конвертация в QPixmap """ rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w convert_to_Qt_format = QtGui.QImage(rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888) p = convert_to_Qt_format.scaled(self.label_video.width(), self.label_video.height(), Qt.KeepAspectRatio) return QtGui.QPixmap.fromImage(p) def main(): app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()
python main.py
открывается окно с кнопкой «Выбрать видео».VideoThread
, который читает кадры из cv2.VideoCapture
.HaarCascade
), если нашлось — вырезаем и «прогоняем» через модель возраста.change_pixmap_signal
в GUI, который его отображает.Детекция груди. В реальной задаче нужно корректно детектировать грудь в кадре. Способов несколько:
Модель возраста. В примере мы используем простую архитектуру ResNet18 с одним выходом (регрессия). На практике для возраста часто применяют:
Онлайн-дообучение. Если вы хотите, чтобы «нейросетка дообучалась в процессе», можно добавить логику:
datasets/new_breast_data/size2
), затем периодически вызывать функцию incremental_train(...)
.Количество данных. Для классификации размера груди (size1, size2, size3, etc.) вам понадобятся достаточно разнообразные примеры, иначе сеть будет переобучаться на фон, позы и т.д. Для надёжной работы обычно нужно сотни (лучше тысячи) изображений на каждый класс.
Правовые и этические аспекты. Будьте аккуратны с обработкой видео 18+, учитывайте законы о приватности и распространении контента с биометрическими данными.
Таким образом, приведённый код — это примерная «рыба», демонстрирующая архитектуру решения. Он включает в себя:
train_breast_classifier.py
) — позволяет собрать датасет из подпапок (где каждая подпапка — размер груди) и обучить/дообучить ResNet на классификацию.main.py
) — поток для обработки видео в реальном времени, детекция лиц (и возраст), «условная» детекция груди и классификация её размера.В реальном проекте вам нужно будет:
Надеюсь, этот пример даст общее понимание, как можно реализовать подобную систему!
Доработай код Чтобы он сам находил грудь. И сделай чтобы код сам работал без вопросов. Можешь использовать любые библиотеки и нейросети и тебе верю
Ниже приведён пример кода на Python, который автоматически находит грудь на видео и классифицирует её размер, а также определяет возраст по лицу. Для упрощения и наглядности:
YOLO-модель для детекции груди:
models/breast_detector.pt
. Это может быть YOLOv5, YOLOv8, etc.Модель классификации размера груди:
models/breast_classifier.pth
, обученный, например, методом, показанным в предыдущем сообщении (ResNet18).Модель возраста:
models/age_model.pth
, в котором хранится простая сеть (ResNet18) с одним выходом (регрессия возраста).Зависимости:
pip install ultralytics opencv-python torch torchvision pyqt5 numpy
Файлы:
textproject/ ├── models/ │ ├── breast_detector.pt # YOLO-модель для детекции груди │ ├── breast_classifier.pth # Модель классификации размера груди (ResNet) │ └── age_model.pth # Модель возраста (ResNet) └── main.py # Большой скрипт с GUI
Ниже весь «большой» код для файла main.py
. Подчёркиваю, что он является примером демонстрационного решения, в реальном проекте вы будете дорабатывать логику, подбирать лучшие модели, оптимизировать и т.д.
main.py
pythonimport sys import cv2 import torch import numpy as np from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QLabel, QFileDialog, QMessageBox ) from PyQt5.QtCore import QThread, pyqtSignal, Qt # Для трансформа изображений from torchvision import transforms, models import torch.nn as nn # YOLO от ultralytics (детекция) try: from ultralytics import YOLO except ImportError: print("Необходимо установить ultralytics: pip install ultralytics") sys.exit(1) ############################################################################### # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ДЛЯ ПРЕДОБРАБОТКИ ############################################################################### def preprocess_image_for_model(frame, img_size=224): """ Предварительная обработка ROI под вход PyTorch-модели (ResNet): - Resize до 224x224 - Перевод в тензор - Нормализация (средние/ст.откл. как в ImageNet) """ transform_fn = transforms.Compose([ transforms.ToPILImage(), transforms.Resize((img_size, img_size)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) tensor = transform_fn(frame).unsqueeze(0) # batch size = 1 return tensor def softmax_to_confidence_and_class(logits): """ Для классификатора размера груди: из логитов берём класс и вероятность. Возвращает (pred_class_idx, confidence). """ prob = torch.softmax(logits, dim=1) conf, pred_class = torch.max(prob, dim=1) return pred_class.item(), conf.item() ############################################################################### # ПОТОК ДЛЯ ОБРАБОТКИ ВИДЕО ############################################################################### class VideoThread(QThread): # Сигнал передаёт кадры в GUI change_pixmap_signal = pyqtSignal(np.ndarray) def __init__( self, video_path, age_model, breast_model, breast_classes, yolo_breast_detector, parent=None ): super().__init__(parent) self.video_path = video_path self.age_model = age_model self.breast_model = breast_model self.breast_classes = breast_classes self.yolo_breast_detector = yolo_breast_detector self._run_flag = True # Детектор лиц: каскад Хаара (для упрощения) self.face_cascade = cv2.CascadeClassifier( cv2.data.haarcascades + 'haarcascade_frontalface_default.xml' ) # Устройство (CPU/GPU) для моделей self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") self.age_model.to(self.device) self.breast_model.to(self.device) def run(self): cap = cv2.VideoCapture(self.video_path) while self._run_flag and cap.isOpened(): ret, frame = cap.read() if not ret: break # ----------------------------------------------------------------- # 1) ОПРЕДЕЛЕНИЕ ВОЗРАСТА ПО ЛИЦУ # ----------------------------------------------------------------- gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces = self.face_cascade.detectMultiScale( gray, scaleFactor=1.1, minNeighbors=5, minSize=(50, 50) ) for (x, y, w, h) in faces: face_roi = frame[y:y+h, x:x+w] # Предобработка лица face_tensor = preprocess_image_for_model(face_roi) face_tensor = face_tensor.to(self.device) self.age_model.eval() with torch.no_grad(): age_out = self.age_model(face_tensor) # Предполагаем, что age_out.item() даёт регрессию возраста predicted_age = int(age_out.item()) # Рисуем на кадре прямоугольник лица и вывод возраста cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText( frame, f"Age: {predicted_age}", (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2 ) # ----------------------------------------------------------------- # 2) АВТОМАТИЧЕСКИЙ ПОИСК ГРУДИ (YOLO) # ----------------------------------------------------------------- # YOLO-детектор возвращает список детекций: bboxes, confidences и т.д. # Предположим, что модель обучена детектировать класс 'breast'. # В методе predict() кадр должен быть в формате BGR/RGB — ultralytics # работает как с OpenCV BGR, так и с PIL/NumPy RGB. Проверим, что всё ок. # Для лучшей точности иногда стоит конвертнуть в RGB, но YOLOv8 сам обрабатывает BGR. results = self.yolo_breast_detector.predict( source=frame, # передаём напрямую кадр conf=0.4, # пример порога уверенности iou=0.5, # порог NMS verbose=False ) # results - список объектов (frame-by-frame), в нашем случае results[0] # содержит предсказанные боксы # results[0].boxes.xyxy, results[0].boxes.conf, results[0].boxes.cls # Для каждого обнаруженного 'breast' сделаем классификацию размера: # (Если модель YOLO уже умеет различать разные классы груди, можно было # обойтись без дополнительного классификатора. Но здесь показываем общий # подход.) if len(results) > 0: detections = results[0].boxes # detections.xyxy, detections.cls, detections.conf # Обходим все найденные объекты for i in range(len(detections)): box = detections.xyxy[i].cpu().numpy().astype(int) x1, y1, x2, y2 = box cls_id = int(detections.cls[i].item()) # индекс класса (для YOLO) conf_det = detections.conf[i].item() # уверенность детектора # Если YOLO обучен только на класс 'breast', то cls_id будет 0 (например). # Если же у модели несколько классов, нужно проверить имя класса: # name = self.yolo_breast_detector.model.names[cls_id] name = self.yolo_breast_detector.model.names[cls_id] if name.lower() == "breast": # Вырезаем ROI груди из кадра breast_roi = frame[y1:y2, x1:x2] if breast_roi.size == 0: continue # Классифицируем ROI на предмет размера груди (size1, size2, size3...). breast_tensor = preprocess_image_for_model(breast_roi) breast_tensor = breast_tensor.to(self.device) self.breast_model.eval() with torch.no_grad(): logits = self.breast_model(breast_tensor) pred_class_idx, conf_classif = softmax_to_confidence_and_class(logits) label_size = self.breast_classes[pred_class_idx] # 'size1', 'size2', ... # Рисуем bounding box на кадре cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2) # Формируем текст: Детектор (confidence) + Классификатор (label_size, conf_classif) text_breast = f"{name} {conf_det*100:.1f}% | {label_size} ({conf_classif*100:.1f}%)" cv2.putText(frame, text_breast, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 0, 0), 2) # Отправляем обновлённый кадр в GUI self.change_pixmap_signal.emit(frame) cap.release() def stop(self): self._run_flag = False self.wait() ############################################################################### # ОСНОВНОЕ ОКНО ПРИЛОЖЕНИЯ (GUI) ############################################################################### class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Automatic Breast Size & Age Detection") self.setGeometry(100, 100, 900, 650) # Кнопка для выбора видео self.open_btn = QPushButton("Выбрать видео", self) self.open_btn.setGeometry(50, 50, 150, 40) self.open_btn.clicked.connect(self.open_video) # Лейбл, где будет отображаться видео self.label_video = QLabel(self) self.label_video.setGeometry(50, 120, 800, 480) self.label_video.setScaledContents(True) # Подгружаем модели self.age_model = self.load_age_model("models/age_model.pth") self.breast_model, self.breast_classes = self.load_breast_model("models/breast_classifier.pth") self.yolo_breast_detector = self.load_breast_detector("models/breast_detector.pt") self.video_thread = None def load_breast_detector(self, path): """ Загрузка YOLO-модели (ultralytics) для детекции груди. Предположим, что она была обучена и имеет класс 'breast'. """ try: model = YOLO(path) return model except Exception as e: QMessageBox.critical(self, "Ошибка загрузки YOLO-модели", str(e)) sys.exit(1) def load_age_model(self, path): """ Загрузка PyTorch-модели для определения возраста. Предположим, это ResNet18 с выходом в 1 нейрон (регрессия). """ try: device = torch.device("cuda" if torch.cuda.is_available() else "cpu") age_model = models.resnet18() age_model.fc = nn.Linear(age_model.fc.in_features, 1) # 1 - регрессионный выход age_model.load_state_dict(torch.load(path, map_location=device)) age_model.eval() return age_model except Exception as e: QMessageBox.critical(self, "Ошибка загрузки age-модели", str(e)) sys.exit(1) def load_breast_model(self, path): """ Загрузка PyTorch-модели классификации размера груди и список классов. Допустим, у нас есть 3 размера: ['size1', 'size2', 'size3'] Или больше. В примере жёстко прописано, но на практике можно грузить из файла. """ breast_classes = ['size1', 'size2', 'size3'] # Пример try: device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = models.resnet18() model.fc = nn.Linear(model.fc.in_features, len(breast_classes)) model.load_state_dict(torch.load(path, map_location=device)) model.eval() return model, breast_classes except Exception as e: QMessageBox.critical(self, "Ошибка загрузки breast-классификатора", str(e)) sys.exit(1) def open_video(self): """ Функция-обработчик кнопки "Выбрать видео". Вызывает диалог выбора файла, затем запускает поток VideoThread. """ options = QFileDialog.Options() file_name, _ = QFileDialog.getOpenFileName( self, "Выбрать видео", "", "Video Files (*.mp4 *.avi *.mov);;All Files (*)", options=options ) if file_name: # Останавливаем предыдущий поток, если есть if self.video_thread is not None: self.video_thread.stop() # Создаём новый поток для обработки self.video_thread = VideoThread( video_path=file_name, age_model=self.age_model, breast_model=self.breast_model, breast_classes=self.breast_classes, yolo_breast_detector=self.yolo_breast_detector ) self.video_thread.change_pixmap_signal.connect(self.update_image) self.video_thread.start() def closeEvent(self, event): """ При закрытии окна корректно останавливаем поток. """ if self.video_thread is not None: self.video_thread.stop() event.accept() @QtCore.pyqtSlot(np.ndarray) def update_image(self, cv_img): """ Получаем кадр (numpy.ndarray) из потока и отображаем его в QLabel. """ qt_img = self.convert_cv_qt(cv_img) self.label_video.setPixmap(qt_img) def convert_cv_qt(self, cv_img): """ Конвертирует OpenCV (BGR) в QPixmap для отображения в PyQt. """ rgb_image = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qt_format = QtGui.QImage( rgb_image.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888 ) scaled_qt = qt_format.scaled( self.label_video.width(), self.label_video.height(), Qt.KeepAspectRatio ) return QtGui.QPixmap.fromImage(scaled_qt) def main(): app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) if __name__ == "__main__": main()
Положите файлы весов моделей в папку models/
:
breast_detector.pt
— YOLO-модель, обученная находить «breast».breast_classifier.pth
— PyTorch-модель для классификации размеров (size1, size2, size3...).age_model.pth
— PyTorch-модель, предсказывающая возраст (регрессионный выход).Убедитесь, что установлены необходимые библиотеки (Python 3.8+ желательно):
bashpip install ultralytics opencv-python torch torchvision pyqt5 numpy
Запустите:
bashpython main.py
Появится окно PyQt5 с кнопкой «Выбрать видео».
Нажмите «Выбрать видео», укажите путь к видеофайлу. После этого:
VideoThread
) будет кадр за кадром обрабатывать видео.age_model
).breast_detector.pt
) ищутся bounding box’ы груди, дальше обрезка ROI и классификация размера.Age: <число>
breast [confidence%] | sizeX [confidence%]
Онлайн-дообучение (по желанию) можно добавить, но в данной реализации оно не покрыто подробно. В общем случае вы можете сохранять новые кадры и затем периодически запускать скрипт дообучения.
Представленный код автоматически находит грудь (через YOLO) и определяет её размер (через классификатор), а также определяет возраст человека по лицу. Всё обёрнуто в простое PyQt5-приложение, работающее «из коробки» — надо только подготовить и положить верные модели:
breast_detector.pt
.breast_classifier.pth
.age_model.pth
.При желании вы можете объединить детекцию и классификацию груди в одну модель (например, обучить YOLO на несколько классов «size1», «size2», «size3»), тогда не нужно будет отдельное «дорисовывание» классификатора.
Это базовый шаблон, который можно дорабатывать, чтобы достичь нужной точности и функциональности. Удачи!