231 lines
9.5 KiB
Markdown
231 lines
9.5 KiB
Markdown
# Модуль В. Система технического зрения с использованием инструментов ИИ
|
||
|
||
## Описание задачи
|
||
|
||
На специальной полке на соревновательном поле расположены 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, высота) под конкретную установку.
|