Data_study/DATA_PROJECT

[Project] 휠체어 목발 이용자 인식 yolo v5 - 1(수집,증강)

onsemiro 2022. 8. 20. 02:37

종종 긴 횡단보도나 교차로를 신호등 시간 내에 이동하지 못하시거나, 이동하시는데 위험한 상황을 격으시는 교통약자분들을 본 적이 있었다. 이런 경험을 없애고 교통약자분들도 안전한 야외활동을 할 수 있도록 도와주자는 생각으로 이번 프로젝트를 진행하였다. 처음 시작할 때의 목표는 대표적인 교통약자분들을 모두 인식하여 도움을 주자는 생각을 하였지만, 부족한 데이터로 인해 낮은 정확도를 보일 바에는 우선 많은 데이터를 가져올 수 있는 label만 가지고 모델을 구성해보자는 목표를 변경하여 진행했다.

 

 

프로젝트 목표

휠체어와 목발 이용자들이 횡단보도를 이동하면 카메라가 인식하여, 신호등 시간을 추가시키도록 도와주자.

 

 

프로젝트 계획

1. 휠체어와 목발 데이터를 google에서 크롤링한다.

2. 이미지 데이터에 인식할 물체에 라벨링 작업을 진행하였다.

3. 크롤링한 데이터로 부족할 것이 분명하니, agumentation을 진행한다.

4. yolo v5 모델에 데이터를 넣어 결과를 확인해 본다.

5. 결과값이 아쉽다면, 데이터를 다시 정제하거나, 모델 파라미터를 바꾼 후 진행해본다.(반복)

6. 실제 유튜브 영상에서도 잘 인식하는지, 그리고 webcam에서도 인식을 잘하는 지 확인해본다.

 

 

Data Crawling

google 이미지에서 휠체어와 목발 사진을 크롤링하여 데이터를 수집하였다.

크롤링에는 selenium 을 활용하여 진행했다.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import urllib.request
import os


def createDirectory(directory):
    try:
        if not os.path.exists(directory):
            os.makedirs(directory)
    except OSError:
        print("Error: Failed to create the directory.")

def crawling_img(name):
    driver = webdriver.Chrome()
    driver.get("https://www.google.co.kr/imghp?hl=ko&tab=wi&authuser=0&ogbl")
    elem = driver.find_element_by_name("q")
    elem.send_keys(name)
    elem.send_keys(Keys.RETURN)

    #
    SCROLL_PAUSE_TIME = 1
    # Get scroll height
    last_height = driver.execute_script("return document.body.scrollHeight")  # 브라우저의 높이를 자바스크립트로 찾음
    while True:
        # Scroll down to bottom
        driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  # 브라우저 끝까지 스크롤을 내림
        # Wait to load page
        time.sleep(SCROLL_PAUSE_TIME)
        # Calculate new scroll height and compare with last scroll height
        new_height = driver.execute_script("return document.body.scrollHeight")
        if new_height == last_height:
            try:
                driver.find_element_by_css_selector(".mye4qd").click()
            except:
                break
        last_height = new_height

    imgs = driver.find_elements_by_css_selector(".rg_i.Q4LuWd")
    dir = "image_data/img/" + name

    createDirectory(dir) #폴더 생성
    count = 1
    for img in imgs:
        try:
            img.click()
            time.sleep(2)
            imgUrl = driver.find_element_by_xpath(
                "/html/body/div[2]/c-wiz/div[3]/div[2]/div[3]/div/div/div[3]/div[2]/c-wiz/div/div[1]/div[1]/div[3]/div/a/img").get_attribute(
                "src")
            path = "image_data/img/"
            urllib.request.urlretrieve(imgUrl, path + name + str(count) + ".jpg")
            count = count + 1

        except:
            pass
    driver.close()
idols = ["wheelchair patient"]

for idol in idols:
    crawling_img(idol)

 

Data Labeling

데이터 라벨링은 roboflow.com에서 진행하였다.

label은 1을 휠체어 0을 목발로 지정하고 라벨링했다.

 

https://roboflow.com

 

Roboflow: Give your software the power to see objects in images and video

With just a few dozen example images, you can train a working, state-of-the-art computer vision model in less than 24 hours

roboflow.com

 

 

Data Augmentation

data Augmentation을 진행한 이유는 부족한 데이터의 문제점을 최대한 극복하기 위함이다. 기존에 갖고 있는 이미지 데이터에 affine 또는 Brightness , Blur 등을 적용하여 색다른 이미지를 제작함으로써 데이터를 증강했다.

 

활용한 패키지는 albumentations다.

 

import albumentations as A
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
import pandas as pd
from glob import glob

Data Augmentation을 진행하면서 필요한 패키지들을 다 불러온다.

 

yolo 모델에 적합한 좌표들로 구성되어 있기 때문에, augmentation을 진행한 후, 이미지를 출력할 때 box가 이상하게 잡히거나 작아서 잘 보이지 않을 것이다.

 

그 이유는,

yolo 좌표는 이미지 크기에 대한 비율 값으로 구성되어 있기 때문이다.

 

변환된 이미지가 잘 boxing이 되는지 확인하기 위해서, 아래와 같이 yolo 좌표에서 xy좌표로 변환하도록 해주는 함수를 구성했다.

 

def yolo_to_xml_bbox(bbox, image): 
    # x_center, y_center width, heigth
    h, w, c = image.shape
    w_half_len = (bbox[0][2] * w) / 2
    h_half_len = (bbox[0][3] * h) / 2
    xmin = int((bbox[0][0] * w) - w_half_len)
    ymin = int((bbox[0][1] * h) - h_half_len)
    xmax = int((bbox[0][0] * w) + w_half_len)
    ymax = int((bbox[0][1] * h) + h_half_len)
    im_class = int(int(bbox[0][-1]))
    return [(xmin, ymin, xmax, ymax, im_class)]
    

def setting_yolo_order(list_):
    a, b, c, d,e = list_
    return [e,a,b,c,d]

setting_yolo_order 함수는 augmentation을 위해 좌표 순서를 변경하였기 때문에, 증강 진행후 저장하기 전에 순서를 복구해놓기 위해 만들어 논 것이다.

 

이제 augmentation을 하는 코드입니다.

 

길지만, 차근차근 과정을 살펴보면 간단합니다.

images_names = glob('image_data/crut/*.jpg')
labels_names = glob('image_data/crut_label/*.txt')
images_names.sort()
labels_names.sort()

print(len(images_names),len(labels_names)) #두 리스트내 값들의 개수가 같은지 확인

for i in range(len(images_names)):
    image = plt.imread(images_names[i])
    label = list(open(labels_names[i]))
    
    #이미지 내에 객체가 1개일 경우
    if len(label) == 1:
        label_list = label[0].split()
        bbox = [float(x) for x in label_list]
        class_, x, y, width, height = bbox
        bbox = [[x, y, width, height,class_]]

        #이미지 변환
        transform = A.Compose([
            A.RandomBrightnessContrast(brightness_limit=(-0.3, 0.3), contrast_limit=(-0.3, 0.3), p=1), #밝기 정도
            A.Blur(p=1, blur_limit=(5, 10)), #흐림 정도
            A.Affine(
                         translate_px={'x':(-10, 10), 'y':(-10, 10)},
                         scale = (0.5, 1),
                         rotate=(10, 10)) #affine 변환 - 위치 회전 (선형변환에 위치까지 변환시킨다)
        ], bbox_params=A.BboxParams(format = 'yolo'))

        transformed = transform(image=image, bboxes=bbox)
        transformed_image = transformed['image']
        transformed_bboxes = transformed['bboxes']
        

        transformed_bbox = setting_yolo_order(transformed_bboxes[0]) #이중리스트로 구성되어있어서 0번째 리스트를 출력하는 것임.
        transformed_bbox = np.array(transformed_bbox).reshape(1, 5)
        
        #변환 이미지 데이터, box 좌표 저장
        np.savetxt('image_data/crut_aug_label/'+'aug_cr.'+labels_names[i].split('/')[-1], transformed_bbox, delimiter = ' ', fmt = '%lf')
        array_img = Image.fromarray(transformed_image)
        array_img.save('image_data/crut_aug/'+'aug_cr.'+images_names[i].split('/')[-1])
     
        #변환 이미지 확인
        trans_xml_bboxs = yolo_to_xml_bbox(transformed_bboxes, transformed_image)
        # 이미지 출력
        plt.imshow(transformed_image)

        # bounding box 그리기
        for trans_xml_bbox in trans_xml_bboxs:
            print(trans_xml_bbox)
            xmin, ymin, xmax, ymax, category = trans_xml_bbox
            rect = patches.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin, linewidth=2, edgecolor='red', fill=False)
            ax = plt.gca()
            ax.add_patch(rect)

        plt.xticks([]); plt.yticks([])
        plt.show()
        
    #이미지 내에 객채가 2개 이상일 경우
    else:
        bbox = []
        for x in range(len(label)):
            str_label = label[x].split()
            bbox_float = [float(y) for y in str_label]
            class_, x, y, width, height = bbox_float
            bbox.append([x,y,width,height,class_])
        
        #이미지 변환
        transform = A.Compose([
            A.RandomBrightnessContrast(brightness_limit=(-0.3, 0.3), contrast_limit=(-0.3, 0.3), p=1),
            A.Blur(p=1, blur_limit=(5, 10)),
            A.Affine(
                         translate_px={'x':(-10, 10), 'y':(-10, 10)},
                         scale = (0.5, 1),
                         rotate=(10, 10))
        ], bbox_params=A.BboxParams(format='yolo'))

        transformed = transform(image=image, bboxes=bbox)
        transformed_image = transformed['image']
        transformed_bboxes = transformed['bboxes']

        transformed_bboxes_list = []
        for b in range(len(transformed_bboxes)):
            transformed_bboxes_list.append(setting_yolo_order(transformed_bboxes[b]))

        transformed_bbox = np.array(transformed_bboxes_list).reshape(len(transformed_bboxes_list), 5)

        np.savetxt('image_data/crut_aug_label/'+'aug_cr.'+labels_names[i].split('/')[-1], transformed_bbox, delimiter = ' ', fmt = '%lf') 
        array_img = Image.fromarray(transformed_image)
        array_img.save('image_data/crut_aug/'+'aug_cr.'+images_names[i].split('/')[-1])
        
        #변환 이미지 확인
        #객체가 많기 때문에 리스트에 각 boxing 좌표를 넣어 그리도록 한다.
        trans_xml_bboxs = []
        for xml in range(len(label)):
            trans_xml_bboxs.append(yolo_to_xml_bbox([transformed_bboxes[xml]], transformed_image))

        # 이미지 출력
        plt.imshow(transformed_image)

        # bounindg box 그리기
        for trans_xml_bbox in trans_xml_bboxs:
            print(trans_xml_bbox[0])
            
            xmin, ymin, xmax, ymax, category = trans_xml_bbox[0]
            rect = patches.Rectangle((xmin, ymin), xmax-xmin, ymax-ymin, linewidth=2, edgecolor='red', fill=False)
            ax = plt.gca()
            ax.add_patch(rect)

        plt.xticks([]); plt.yticks([])
        plt.show()

<위 코드 과정>

 

1. 폴더에 있는 파일의 데이터를 모두 가져와 리스트에 넣어두었다.

2. 이 리스트를 통해 하나하나 이미지와 boxing 좌표를 가져와 증강을 시켜준다.

3. 증강시킨 이미지와 좌표를 폴더에 다른 이름으로 저장해준다.

4. 따로 증강이 잘 됐는지 확인하기 위해, 이미지를 그려본다.

 

한 이미지에 한개 이상의 box가 쳐져있을 시에는 리스트를 통해 각 box의 좌표를 넣어두고 하나씩 증강시켜야하기때문에, if else 문으로 나눠서 진행하여 코드가 길어보인다.

 

증강을 위해 사용한 패키지인 albumentation는 x,y,weight,hight,class 순이기 때문에 먼저 순서를 바꿔줬다.

 

어떤 증강이 가장 우리 주제에 적합할까 고민하면서, 기능을 선택했다. 횡단보도에서 생각해보면, 흐릿한 경우도 있을 것이고 밝은 날도 있을 것이기 때문에 증강을 할 때, 이 부분을 건들여봤다.

 

데이터를 증강할 때 가장 주의할 점은 overfitting이다. 기존 데이터를 변형하여 추가하는 것이기 때문에 비슷해보이는 이미지들이 많이 존재할 수 있기 때문이다. 이점을 유의하며 데이터를 증강해야한다.

 

Data augmentation

 

반응형