Обучение YOLOv8s на Google Colab: детектим дорожные знаки

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Всем привет! Решила я вернуться на Хабр с новым мини-проектом. Сегодня попробуем детектить дорожные знаки используя YOLOv8. Что ж, приступим!

1: Работа с Google Colab.

Первое что мы делаем это открываем Google colab и создаем New Notebook. После того как всё создалось, то сверху ищем <среду выполнения>. Дальше такие действия: Сменить среду выполнения -> T4 GPU -> Сохранить. (По незнанию мне пришлось запускать проект 2 раза, потому что у меня стояла среда выполнения CPU, поэтому сделаем сразу все важные моменты в самом начале).

P.s. меня забанили пока я писала статью, поэтому у вас есть 2 варианта (я не работала ещё ни разу с Google Colab, прошу не кидаться тапками):
1: переписать код и переключиться на GPU уже после написанного.
2: использовать готовый блокнот и переключиться на GPU, когда уже будете готовы запускать код.
Иначе получите это: Невозможно подключиться к ускорителю (GPU) из-за лимитов на использование в Colab.

Так-с, перейдём сразу к делу, проверим всё ли у нас правильно выбралось:

!nvcc --version

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2022 NVIDIA Corporation
Built on Wed_Sep_21_10:33:58_PDT_2022
Cuda compilation tools, release 11.8, V11.8.89
Build cuda_11.8.r11.8/compiler.31833905_0

!nvidia-smi

Sat Aug 12 08:27:04 2023
NVIDIA-SMI 525.105.17 Driver Version: 525.105.17 CUDA Version: 12.0
...

Если получим такие выводы, то всё отлично. Можно продолжать работать :)

Чтобы не путаться в последующих файлах, сразу удалим папку sample_data, а то она немного будет нам мешаться.

!rm -rf ./sample_data

2: Установка утилит.

2.1: Клонируем репозиторий.

!git clone https://github.com/ultralytics/JSON2YOLO

Есть несколько важных деталей, которые нужно сделать в репозитории:
1: В файле general_json2yolo.py меняем код в 274 строке на h, w, f = img['height'], img['width'], img['file_name'].split('/')[1] .

2: В general_json2yolo.py и labelbox_json2yolo.py, нужно добавить в перед каждым utils точку, чтобы было так from .utils import ...

Если у вас возникли вопросы почему так надо сделать, то объясню:
1: Метод .split('/') разделяет строку img['file_name'] по символу / и создает список подстрок. Затем [1] выбирает второй элемент этого списка, то есть вторую подстроку после разделителя /. Строка img['file_name'] содержит путь к файлу с одним или несколькими слешами /, и нам нужно получить имя файла без пути.

2: Я долго не понимала, что я делаю не так и почему у меня не импортится файл, который лежит в той же папке с файлами, которые мы используем. Пришлось лезть в документацию импортов и смотреть какие варианты вообще есть. На своем железе такой ошибки не было, но в Google Colab она почему-то вылезла. Если у вас есть какие-то объяснения этой проблемы, то напишите в комментариях, буду благодарна :)

Так-с, идём дальше!

2.2: Переходим к установке библиотек.

%pip install -r /content/JSON2YOLO/requirements.txt
%pip install kaggle ipywidgets widgetsnbextension

Если всё successfully installed, то переходим к самому датасету!

3: Работа с датасетом.

3.1: Скачиваем датасет.

import os

Дальше нам потребуется Kaggle. Если нет аккаунта -> создаём его, а если есть то переходим в Settings -> API -> Create New Token. Автоматически скачается json файл, там уже будут данные.

os.environ['KAGGLE_USERNAME'] = "сюда вписываем из json username"
os.environ['KAGGLE_KEY'] = "а сюда key"
!kaggle datasets download -d watchman/rtsd-dataset

Немного расскажу о самом датасете, пока он скачивается. Набор данных RTSD содержит кадры, предоставленные компанией "Геоцентр Консалтинг". Изображения получены с широкоформатного видеорегистратора, который снимает с частотой 5 кадров в секунду. Разрешения изображений от 1280×720 до 1920×1080. Фотографии сделаны в разное время года, в разное время суток и при различных погодных условиях . В наборе используется 155 знаков дорожного движения в формате разметки - COCO.

пример данных из датасета
пример данных из датасета

3.2: Распаковываем архив с данными.

import zipfile
archive = zipfile.ZipFile('rtsd-dataset.zip', 'r')
archive.extractall('.')

3.3: Ну и чтобы он нам не мешался, то можем его удалить.

os.remove('rtsd-dataset.zip')

4: Преобразование датасета в YOLO-формат.

4.1: Импортим библиотеки.

import pandas as pd
from tqdm.notebook import tqdm
from shutil import copyfile, move
import sys
import json
from ipywidgets import FloatProgress

4.2: Переходим к конвертации.

Мы будем использовать готовый скрипт от Ultralytics, который скачали в самом начале. Это папка JSON2YOLO. Перейдём к конвертации СOCO-формата в YOLO-формат. Советую делать всё постепенно, если вы используете Google Colab. У меня возникали ошибки при выполнении, если я использовала код целиком.

from JSON2YOLO.general_json2yolo import convert_coco_json
sys.path.append('./JSON2YOLO')

test_path = 'test_annotation'
train_path = 'train_annotation'

os.makedirs(train_path, exist_ok=True)
os.makedirs(test_path, exist_ok=True)
move('train_anno.json', os.path.join(train_path, 'train_anno.json'))
move('val_anno.json', os.path.join(test_path, 'val_anno.json'))
for folder in ['labels', 'images']:
    for path in [test_path, train_path]:
        os.makedirs(os.path.join(path, folder), exist_ok=True)
convert_coco_json(train_path)
for file in tqdm(os.listdir(os.path.join('new_dir/labels/train_anno'))):
    move(os.path.join('new_dir/labels/train_anno', file), os.path.join(train_path, 'labels', file))

convert_coco_json('./test_annotation/')
for file in tqdm(os.listdir(os.path.join('new_dir/labels/val_anno'))):
    move(os.path.join('new_dir/labels/val_anno', file), os.path.join(test_path, 'labels', file))

Отлично, разметка у нас есть, не хватает лишь изображений, которые ей соответствуют. Добавим их :)

test_labels = os.listdir(os.path.join(test_path, 'labels'))
train_labels = os.listdir(os.path.join(train_path, 'labels'))

test_labels = set(map(lambda x: x.split('.')[0], test_labels))
train_labels = set(map(lambda x: x.split('.')[0], train_labels))
images = 'rtsd-frames/rtsd-frames'
for file in os.listdir(images):
    name = file.split('.')[0]
    if name in train_labels:
        move(os.path.join(images, file), os.path.join(train_path,'images', file))
    if name in test_labels:
        move(os.path.join(images, file), os.path.join(test_path,'images', file))

5: Работа с YAML файлом.

Создадим файл "trafic_signs.yaml" с описанием путей и классов, используемых в датасете. Это обязательное требование для YOLOv8.

5.1: Установим библиотеку для записи данных yaml в файл.

%pip install PyYAML==5.1

5.2: Импортим библитеку.

import yaml

5.3: Опишем пути и классы для обучения.

data = [{
    'train': '/content/train_annotation/images',
    'val': '/content/test_annotation/images',
    'nc': 155,
    'names': ['2_1', '1_23', '1_17', '3_24', '8_2_1', '5_20', '5_19_1', '5_16', '3_25',
              '6_16', '7_15', '2_2', '2_4', '8_13_1', '4_2_1', '1_20_3', '1_25', '3_4', '8_3_2',
              '3_4_1', '4_1_6', '4_2_3', '4_1_1', '1_33', '5_15_5', '3_27', '1_15', '4_1_2_1',
              '6_3_1', '8_1_1', '6_7', '5_15_3', '7_3', '1_19', '6_4', '8_1_4', '8_8', '1_16',
              '1_11_1', '6_6', '5_15_1', '7_2', '5_15_2', '7_12', '3_18', '5_6', '5_5', '7_4',
              '4_1_2', '8_2_2', '7_11', '1_22', '1_27', '2_3_2', '5_15_2_2', '1_8', '3_13',
              '2_3', '8_3_3', '2_3_3', '7_7', '1_11', '8_13', '1_12_2', '1_20', '1_12', '3_32',
              '2_5', '3_1', '4_8_2', '3_20', '3_2', '2_3_6', '5_22', '5_18', '2_3_5', '7_5',
              '8_4_1', '3_14', '1_2', '1_20_2', '4_1_4', '7_6', '8_1_3', '8_3_1', '4_3', '4_1_5',
              '8_2_3', '8_2_4', '1_31', '3_10', '4_2_2', '7_1', '3_28', '4_1_3', '5_4', '5_3',
              '6_8_2', '3_31', '6_2', '1_21', '3_21', '1_13', '1_14', '2_3_4', '4_8_3', '6_15_2',
              '2_6', '3_18_2', '4_1_2_2', '1_7', '3_19', '1_18', '2_7', '8_5_4', '5_15_7', '5_14',
              '5_21', '1_1', '6_15_1', '8_6_4', '8_15', '4_5', '3_11', '8_18', '8_4_4', '3_30',
              '5_7_1', '5_7_2', '1_5', '3_29', '6_15_3', '5_12', '3_16', '1_30', '5_11', '1_6',
              '8_6_2', '6_8_3', '3_12', '3_33', '8_4_3', '5_8', '8_14', '8_17', '3_6', '1_26',
              '8_5_2', '6_8_1', '5_17', '1_10', '8_16', '7_18', '7_14', '8_23']
}]
def write_yaml_to_file(py_obj, filename) :
    with open(f'{filename}.yaml', 'w+',) as f:
        yaml.dump_all(py_obj, f, sort_keys=False)
write_yaml_to_file(data, 'trafic_signs')

6: Доп. установка библиотек.

6.1: Устанавливаем библиотеки.

%pip install ultralytics

7: Обучение.

Переходим непосредственно к обучению модели, наконец-то :)

7.1: Импортим библиотеки.

from ultralytics import YOLO
import torch
import gc

Если спросите зачем импортить torch и gc, то дам такой ответ. Они нужны для очистки графической памяти, если вам нужно будет это сделать, то пропишите:

gc.collect()
torch.cuda.empty_cache()

7.2: Начинаем обучать модель.

model = YOLO('yolov8s.pt')

results = model.train(
   data='/content/trafic_signs.yaml',
   imgsz=1280,
   epochs=5,
   batch=6,
   device=0,
   name='YOLOv8s'
)

P.s. если хотите, то можете поколдовать над размером батчей, у меня стоит 5 для обучения на colab, т.к. там всего 15гб GPU_mem. Ну или можете взять и другую модель от YOLOv8, а можно и больше эпох сделать. Тут уже всё на ваш выбор :)

8: Итоги обучения.

Самое время посмотреть на результаты после обучение, давайте потестируем модельку :)

model = YOLO("путь к лучшему весу -> best.pt")
pre = model.predict(
    source="путь к фото / видео, где хотите предсказать", 
    show=True,                                                  
    imgsz=1280,                                                 
    hide_labels=True,                                           
    save=True,                                                  
    name="название папки для результатов",                                                 
    conf=0.1,                                                   
    )
тест 1
тест 1
тест 2
тест 2
тест 3
тест 3

Как итог могу сказать, что получилось у меня обучить модельку всего 4 эпохи (вышло практически 6 часов), на 5 эпохе Google colab решил, что я превысила время использования ресурсов и остановил обучение не дав скачать веса и удалив все файлы. Так что совет на будущее, если будете обучать на colab, то сохраняйте веса каждую эпоху, как это делала я, просто потом дообучите модельку и всё. Думаю, что я так и сделаю в ближайшее время, чтобы сравнить результаты.

Видно, что на 2 фотке некоторые знаки не получилось распознать из-за столба, но думаю что эту проблему можно решить. На 3 тесте тоже есть проблемы с распознаванием знака работы эвакуатора, хотя остальные 2 ей удалось найти. Итоги подведены, поэтому спасибо за прочтение статьи, надеюсь, она была полезной!

Я только начинаю изучать CV и ML, поэтому, если у вас есть рекомендации по материалам/проектам, то порекомендуйте что-нибудь, буду благодарна :)

Ну и оставлю ссылочки: GitHub, Telegram

Источник: https://habr.com/ru/articles/754206/


Интересные статьи

Интересные статьи

Привет, Хабр. Меня зовут Марат Хамадеев. Я — физик-теоретик, хотя кто-то, возможно, знает меня как научного журналиста, писавшего про физику для N + 1. Профессионально я рос в провинциальной академиче...
В данной статье рассмотрим создание расширения google chrome для записи экрана и камеры. Оно может записывать целый экран, отдельное окно или вкладку. В режиме записи экрана можно вставлять окно с фро...
Мир стоит вверх ногами. Корпорация добра печется о нашей безопасности. И в то же время делает все, чтобы наши секреты стали достоянием гласности. Это я о политике гугл в плане сохранения логин-паролей...
Аннотация. Статья про очень простой скрипт, формирующий из списка серверов когфиг для ssh Linux. Проверено на Ubunta 18, используется Goodle Cloud SDK, Python 2.7, Bash. После резкого увеличения...
2ГИС гордится точностью данных. Каждый рабочий день в каждом городе наши специалисты обходят целые районы, чтобы зафиксировать на карте все изменения — новые дома, дороги и даже тропинки. А е...