Files
webots-vision-2/README.md
2026-04-03 07:30:54 +03:00

231 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Модуль В. Система технического зрения с использованием инструментов ИИ
## Описание задачи
На специальной полке на соревновательном поле расположены N > 3 коробок с рисунками инструментов. РМК-1 с манипулятором смотрит на полку сверху (камера на манипуляторе направлена вертикально вниз). Эксперт с помощью генератора случайных чисел выбирает целевую деталь. Алгоритм должен:
1. Распознать все коробки в кадре камеры
2. Определить координаты целевой коробки относительно базы манипулятора (Base_link)
3. Направить манипулятор к коробке, схватить её и поднять в стартовое положение
4. Опустить коробку на свободное место на полке
## Классы объектов
| ID | Название | Описание |
|----|----------|----------|
| 1 | Молоток (hammer) | Коробка с изображением молотка |
| 2 | Гаечный ключ (wrench) | Коробка с изображением гаечного ключа |
| 3 | Пассатижи (pliers) | Коробка с изображением пассатижей |
## Архитектура решения
- **Модель**: YOLOv8n-cls (nano classification) — легковесная модель для классификации изображений
- **Фреймворк**: Ultralytics / PyTorch
- **Размер входа**: 224x224 px
- **Размер модели**: ~5.3 MB (PyTorch), ~5.5 MB (ONNX)
- **Скорость инференса**: ~1.6 мс на CPU
### Пайплайн обработки кадра
```
Кадр с камеры манипулятора
|
v
Сегментация коробок (OpenCV: пороговая фильтрация + контурный анализ)
|
v
Классификация каждой коробки (YOLOv8n-cls)
|
v
Поиск целевой коробки (по ID класса + порог уверенности > 0.7)
|
v
Пересчёт пиксельных координат в координаты Base_link
|
v
Отправка координат манипулятору (MoveIt2 API)
```
## Структура файлов
```
module_v/
├── images/ # Исходные изображения инструментов
│ ├── Hammer.jpg
│ ├── Wrench.jpg
│ └── Pliers.jpg
├── dataset/ # Сгенерированный датасет
│ ├── train/ # 900 изображений (300 на класс)
│ │ ├── hammer/
│ │ ├── wrench/
│ │ └── pliers/
│ └── val/ # 180 изображений (60 на класс)
│ ├── hammer/
│ ├── wrench/
│ └── pliers/
├── runs/module_v_cls/weights/ # Результаты обучения
│ ├── best.pt
│ └── best.onnx
├── best.pt # Обученная модель (копия)
├── generate_dataset.py # Скрипт генерации датасета
├── train_model.py # Скрипт обучения модели
├── detect_box.py # Основной скрипт инференса + ROS2
├── test_detect.py # Тестовый скрипт с визуализацией
├── README.md # Общая инструкция по симулятору
└── README_MODULE_V.md # Данный файл
```
## Установка зависимостей
```bash
pip install ultralytics opencv-python-headless Pillow albumentations
```
## Генерация датасета
Датасет генерируется из 3 исходных изображений с помощью аугментаций:
- Композитинг инструментов на различные фоны (имитация полки)
- Повороты, отражения, перспективные искажения
- Изменение яркости, контраста, насыщенности
- Размытие, шум, JPEG-компрессия
```bash
python3 generate_dataset.py
```
Результат: 900 train + 180 val изображений (по 300/60 на класс).
## Обучение модели
```bash
python3 train_model.py
```
Параметры обучения:
- Базовая модель: `yolov8n-cls.pt` (предобученная на ImageNet)
- Эпохи: до 80 (с early stopping, patience=15)
- Размер изображения: 224x224
- Batch size: 32
Результат: **100% top-1 accuracy** на валидации.
Модель автоматически экспортируется в ONNX формат.
## Запуск
### Тест на отдельном изображении
```bash
python3 detect_box.py --target 1 --image path/to/image.jpg
```
Где `--target` — ID целевой детали (1=молоток, 2=ключ, 3=пассатижи).
### Тест на всех исходных изображениях
```bash
python3 detect_box.py --target 2
```
### Тест с визуализацией на кадре с камеры
```bash
python3 test_detect.py
```
Результат сохраняется в `test_result.png` с аннотированными рамками и подписями классов.
### Запуск в режиме ROS2
Предварительно запустите симуляцию модуля В:
```bash
# Терминал 1: запуск симулятора
ros2 launch ar_webots_fms_ros2 module3.launch.py
# Терминал 2: запуск детектора (указать целевую деталь)
python3 detect_box.py --target 1 --ros
```
Скрипт подписывается на топик `/RMC1/arm95/camera_gripper/image_color`, распознаёт коробки и выводит координаты целевой коробки относительно Base_link.
## Результаты тестирования
### На исходных изображениях
| Изображение | Предсказание | Уверенность |
|-------------|-------------|-------------|
| Hammer.jpg | hammer (молоток) | 99.98% |
| Wrench.jpg | wrench (гаечный ключ) | 99.99% |
| Pliers.jpg | pliers (пассатижи) | 100.00% |
### На тестовом кадре с камеры (test.png)
5 коробок на полке (молоток, ключ, 2 пассатижей + объект на краю):
| Коробка | Класс | Уверенность |
|---------|-------|-------------|
| Box 1 | wrench | 99.51% |
| Box 2 | pliers | 100.00% |
| Box 3 | wrench | 65.81% (край кадра, фильтруется порогом) |
| Box 4 | hammer | 79.42% |
| Box 5 | pliers | 99.93% |
Рекомендуемый порог уверенности для фильтрации: **> 0.7**
## Интеграция с манипулятором
Пример полного цикла (распознавание + захват):
```python
import rclpy
from detect_box import load_model, find_target_box, pixel_to_base_link
from geometry_msgs.msg import PoseStamped
from tf_transformations import quaternion_from_euler
from moveit.planning import MoveItPy
# Инициализация
rclpy.init()
model = load_model("best.pt")
arm95 = MoveItPy(node_name="moveit_py", name_space="/RMC1/arm95")
arm95_arm = arm95.get_planning_component("arm95_group")
gripper = arm95.get_planning_component("gripper")
# 1. Получить кадр с камеры (через ROS2 subscriber)
# frame = ... (cv2 image from /RMC1/arm95/camera_gripper/image_color)
# 2. Найти целевую коробку
target_id = 1 # молоток
result = find_target_box(model, frame, target_id)
if result:
cx, cy, conf, bbox = result
h, w = frame.shape[:2]
wx, wy, wz = pixel_to_base_link(cx, cy, w, h)
# 3. Переместить манипулятор к коробке
q = quaternion_from_euler(3.14, 0.0, 1.57)
pose_goal = PoseStamped()
pose_goal.header.frame_id = "Base_link"
pose_goal.pose.orientation.x = q[0]
pose_goal.pose.orientation.y = q[1]
pose_goal.pose.orientation.z = q[2]
pose_goal.pose.orientation.w = q[3]
pose_goal.pose.position.x = wx
pose_goal.pose.position.y = wy
pose_goal.pose.position.z = 0.3 # высота над коробкой
arm95_arm.set_start_state_to_current_state()
arm95_arm.set_goal_state(pose_stamped_msg=pose_goal, pose_link="gripper_base")
plan_result = arm95_arm.plan()
if plan_result:
arm95.execute(plan_result.trajectory, controllers=[])
# 4. Захватить коробку
gripper.set_goal_state(configuration_name="closed")
plan_result = gripper.plan()
if plan_result:
arm95.execute(plan_result.trajectory, controllers=[])
```
**Важно**: координаты `pixel_to_base_link()` требуют калибровки параметров камеры (FOV, высота) под конкретную установку.