AI Basic

yolov5 모델로 보행자 사람 자전거만 탐지하기/검증파일 실행해보기.

wandering developer 2023. 3. 22. 02:03

transfer learning을 공부하기 위해서 yolo를 공부하고 있음.

https://freddiekim.tistory.com/16

 

Transfer Learning Tutorial(전이학습)

전이학습이 어떤 방식으로 이루어 지는지 궁금해서 아래 예제로 공부하면서 코드를 작성해봤다. 재사용을 위해서 리팩토링을 했므으로 같은 함수이다. 출저 : https://9bow.github.io/PyTorch-tutorials-kr-0

freddiekim.tistory.com

위 예에서 난 resnet을 이용해서 250장 정도 train데이터를 이용해서 90%이상 벌과 개미를 구분하는 네트워크를 만들었음.

 

이번에는 coco데이터로 학습되어 있는 네트워크를 이용해서 

난 사람,자전거,보행자만 탐지하고 싶고 적은 장수를 학습하고 괜찮은 결과를 얻고 싶음.

그렇게 하기위해서는 노가다를 해야함.

 

coco데이터는 출력이 80개임. 그래서 3개로 줄일 생각임.

#   0: --> 0: person
#   1: --> 1: bicycle/ 3: motorcycle
#   2: --> 2: car/ 5: bus/ 7: truck

이렇게 하기 위해서는 우선 coco data를 받야함.

yolov5/data/scripts/get_coco.sh를 사용하면 쉽게 다운 받을 수 있음. 

코드를 보면 val 이 5천장으로 가장 작음.

 

#!/bin/bash
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Download COCO 2017 dataset http://cocodataset.org
# Example usage: bash data/scripts/get_coco.sh
# parent
# ├── yolov5
# └── datasets
#     └── coco  ← downloads here

# Arguments (optional) Usage: bash data/scripts/get_coco.sh --train --val --test --segments
if [ "$#" -gt 0 ]; then
  for opt in "$@"; do
    case "${opt}" in
    --train) train=true ;;
    --val) val=true ;;
    --test) test=true ;;
    --segments) segments=true ;;
    esac
  done
else
  train=false
  val=false
  test=false
  segments=false
fi

# Download/unzip labels
d='../datasets' # unzip directory
url=https://github.com/ultralytics/yolov5/releases/download/v1.0/
if [ "$segments" == "true" ]; then
  f='coco2017labels-segments.zip' # 168 MB
else
  f='coco2017labels.zip' # 46 MB
fi
echo 'Downloading' $url$f ' ...'
curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f &

# Download/unzip images
d='../datasets/coco/images' # unzip directory
url=http://images.cocodataset.org/zips/
if [ "$train" == "true" ]; then
  f='train2017.zip' # 19G, 118k images
  echo 'Downloading' $url$f '...'
  curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f &
fi
if [ "$val" == "true" ]; then
  f='val2017.zip' # 1G, 5k images
  echo 'Downloading' $url$f '...'
  curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f &
fi
if [ "$test" == "true" ]; then
  f='test2017.zip' # 7G, 41k images (optional)
  echo 'Downloading' $url$f '...'
  curl -L $url$f -o $f -# && unzip -q $f -d $d && rm $f &
fi
wait # finish background tasks

위 처럼 수정 후 bash get_coco.sh --val 치면 데이터가 다운이됨.


이제 데이터를 가져와서 5000장을 다 학습하기에 내 컴퓨터 성능이 좋지도 않고 시간이 많이 걸리므로 500장만 학습하기로 한다.

from glob import glob
import os 
import shutil

def write_line(f,class_id,idx):
    cnt = 0
    for str in class_id:
        if cnt > 0:
            f.write(' '+str)
        else:
            f.write(idx)
            cnt += 1
            
# 이미지들의 주소 리스트로 만들어줌
train_img_list = glob('./yolov5/data/datasets/coco/images/val2017/*.jpg')
valid_img_list = glob('./yolov5/data/datasets/coco/labels/val2017/*.txt')
 
# 폴더 생성
root_labels_path = "./yolov5/data/datasets/coco/labels/val2017/"

train_images = './yolov5/data/datasets/coco512/images/train2017/'
train_labels = './yolov5/data/datasets/coco512/labels/train2017/'

val_images = './yolov5/data/datasets/coco512/images/val2017/'
val_labels = './yolov5/data/datasets/coco512/labels/val2017/'

if not os.path.isdir(train_images):
    os.makedirs(train_images)

if not os.path.isdir(train_labels):
    os.makedirs(train_labels)

if not os.path.isdir(val_images):
    os.makedirs(val_images)

if not os.path.isdir(val_labels):
    os.makedirs(val_labels)

train_cnt = 0 
val_cnt = 0
tht_cnt = 512
for f in train_img_list:
    name = os.path.basename(f)
    if train_cnt < tht_cnt:     
        label_name = name.split('.')[0] + '.txt'
        full_name = root_labels_path + label_name
        if os.path.isfile(full_name):
            shutil.copyfile(f,train_images+('%06d' % train_cnt)+'.jpg')
            f2 = open(full_name,'r')
            lines = f2.readlines()
            f2.close()   
            en_write = False
            with open(train_labels+('%06d' % train_cnt)+'.txt','w') as f:
                for line in lines:
                    class_id = line.split(' ')
                    if class_id[0] == '0':
                        write_line(f,class_id,'0')
                        en_write = True
                    elif class_id[0] == '1' or class_id[0] == '3':
                        write_line(f,class_id,'1')
                        en_write = True
                    elif class_id[0] == '2' or class_id[0] == '5' or class_id[0] == '5':
                        write_line(f,class_id,'2')
                        en_write = True
            
            if train_cnt%50 == 0:
                if en_write == False:
                    train_cnt += 1                
            else:
                if en_write == True:
                    train_cnt += 1
            
    elif val_cnt < tht_cnt:
        label_name = name.split('.')[0] + '.txt'
        full_name = root_labels_path + label_name
        if os.path.isfile(full_name):
            shutil.copyfile(f,val_images+('%06d' % val_cnt)+'.jpg')
            # shutil.copyfile(full_name,val_labels+('%06d' % val_cnt)+'.txt')
            f2 = open(full_name,'r')
            lines = f2.readlines()
            f2.close()   
            en_write = False
            with open(val_labels+('%06d' % val_cnt)+'.txt','w') as f:
                for line in lines:
                    class_id = line.split(' ')
                    if class_id[0] == '0':
                        write_line(f,class_id,'0')
                        en_write = True
                    elif class_id[0] == '1' or class_id[0] == '3':
                        write_line(f,class_id,'1')
                        en_write = True
                    elif class_id[0] == '2' or class_id[0] == '5' or class_id[0] == '5':
                        write_line(f,class_id,'2')  
                        en_write = True

            if val_cnt%50 == 0:
                if en_write == False:
                    val_cnt += 1                
            else:
                if en_write == True:
                    val_cnt += 1

위 코드를 실행하면 

훈련용 512 장 검증용 512장이 만들어 짐.

그리고 클래스 80개는 3개로 줄여서 할당됨.


coco512.yaml 만들기 학습을 하기위해서 yaml  파일을 수정해야함.

다운을 다 받았으므로 주석처리하고 아래 처럼 수정해서 새롭게 만듬.

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# COCO128 dataset https://www.kaggle.com/ultralytics/coco128 (first 128 images from COCO train2017) by Ultralytics
# Example usage: python train.py --data coco128.yaml
# parent
# ├── yolov5
# └── datasets
#     └── coco128  ← downloads here (7 MB)


# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: ./data/datasets/coco512  # dataset root dir
train: images/train2017  # train images (relative to 'path') 128 images
val: images/val2017  # val images (relative to 'path') 128 images
test:  # test images (optional)

# Classes
names:
  0: person
  1: bicycle
  2: car

# Download script/URL (optional)
# download: https://ultralytics.com/assets/coco128.zip

아래처럼 실행함. 이렇게 하는 이유는 디버깅을 할 수 있음.

import torch
from torchsummary import summary as summary
import subprocess
import os

os.chdir('./yolov5/')

cmd = "python train.py --data coco512.yaml --weights yolov5s.pt --img 640 --epochs 3"

print(cmd.split(' '))
subprocess.run(cmd.split(' '))

 

이렇게 실행을 다하면 결과가 

      Epoch    GPU_mem   box_loss   obj_loss   cls_loss  Instances       Size
        0/2         0G     0.1053    0.05411    0.03405        138        640: 100%|██████████| 32/32 [03:50<00:00,  7.21s/it]
                 Class     Images  Instances          P          R      mAP50   mAP50-95: 100%|██████████| 16/16 [01:25<00:00,  5.35s/it]
                   all        512       2238      0.138      0.118     0.0592     0.0181

      Epoch    GPU_mem   box_loss   obj_loss   cls_loss  Instances       Size
        1/2         0G    0.08058    0.05103    0.01677        177        640: 100%|██████████| 32/32 [03:51<00:00,  7.24s/it]
                 Class     Images  Instances          P          R      mAP50   mAP50-95: 100%|██████████| 16/16 [01:09<00:00,  4.33s/it]
                   all        512       2238      0.797      0.148      0.176      0.061

      Epoch    GPU_mem   box_loss   obj_loss   cls_loss  Instances       Size
        2/2         0G    0.06951    0.04642    0.01233        121        640: 100%|██████████| 32/32 [03:40<00:00,  6.90s/it]
                 Class     Images  Instances          P          R      mAP50   mAP50-95: 100%|██████████| 16/16 [00:58<00:00,  3.63s/it]
                   all        512       2238      0.579      0.332      0.297      0.121

3 epochs completed in 0.250 hours.
Optimizer stripped from runs/train/exp7/weights/last.pt, 14.4MB
Optimizer stripped from runs/train/exp7/weights/best.pt, 14.4MB

Validating runs/train/exp7/weights/best.pt...
Fusing layers... 
Model summary: 157 layers, 7018216 parameters, 0 gradients, 15.8 GFLOPs
                 Class     Images  Instances          P          R      mAP50   mAP50-95: 100%|██████████| 16/16 [00:55<00:00,  3.45s/it]
                   all        512       2238      0.582      0.328      0.297      0.121
                person        512       1760      0.195       0.71      0.449      0.203
               bicycle        512        123          1          0      0.156      0.067
                   car        512        355       0.55      0.273      0.285     0.0929

데이터도 적게 사용하고  epoch도 3번밖에 안해서 성능이 좋지 않지않게 나옴. 

 

참고로 freeze기능을 이용해서 weight고정시키고 뒷단만 학습했지만 결과는 많이 좋아지지 않았음.

 

그리고 

 

yolov5/runs/train 에 순차적으로 저장됨을 확인 할 수 있음.

 

다음장에서 validation 파일을 실행해보면서 얼마나  fine tunning이 잘되었는지 확인해보자!

결론은 학습을 많이 안해서 잘안됨. 그러나 쉬운 예는 아래처럼 어느정도 됨.

그래도 쉬운 시나리오는 잘됨.

 

 


마지막으로 그냥 검증 파일을 실행해보자.

자동화가 잘되어 있다.

아래코드를 치면 데이터도 자동으로 받고 실행한다.

python val.py --weights yolov5s.pt --data coco128.yaml --img 640

아래처럼 결과가 나오는것을 볼 수 있다.

즉 학습된 모델로 80개 클래스 출력 128개 데이터를 검증한것이다.

                 Class     Images  Instances          P          R      mAP50   mAP50-95: 100%|██████████| 4/4 [00:17<00:00,  4.38s/it]
                   all        128        929      0.709      0.634      0.713      0.475
Speed: 1.8ms pre-process, 123.0ms inference, 3.9ms NMS per image at shape (32, 3, 640, 640)

 128개 이미지에서 929개 탐지된 물품이 있고 그중 0.709 즉 70%정도 탐지했다는 의미이다.

여기서 mAP50 즉50%이상 겹쳐있는 items이 71.3%라는 의미이다.

 

만일 수동을로 데이터를 받고 싶으면 아래처럼 치면된다.

cd yolov5/data/scripts

bash get_coco128.sh

data/datasets/coco128 폴더가 생성되고 거기에 이미지와 정답지가 같이 다운 되어 있음.

 


당연한 얘기지만

추가적으로 coco데이터는 80개 클래스 출력이고 내가 수정한 데이터는 3개 클래스 출력이라서 단위가 맞지 않아서 validation이 되지 않는다. 모델을 fine tunning 으로 수정 후 해야함.

resnet 으로 공부할때는 바로바로 이해가 되었는데 yolo는 추상화가 많이되어 있어서 힘들었음.

반응형