https://blueberry-kyu.tistory.com/11

 

3. 학습

 

학습을 하기 위해 필요한 파일은 3가지입니다

1. DATA 파일

2. cfg 파일

3. weights 파일

 

 

 

 

 

DATA 파일의 내부는

 

classes= 3 (클래스 개수)
train=custom/train.txt (학습 데이터 경로가 들어있는 텍스트 파일)
valid=custom/test.txt (검증 데이터 경로가 들어있는 텍스트 파일)
names=custom/custom.names (클래스 명이 적혀있는 파일)
backup=custom/backup/ (학습 시 백업 데이터가 저장되는 경로)

 

와 같이 이루어져 있는데

그중 train, test, names 파일을 만들어줘야 합니다

 

먼저 이전 포스트에서 만들어둔 YOLO_Formatted 폴더를 원하는 이름으로 바꾼 후 darknet-master\build\darknet\x64 안에 넣어줍니다

 

그 폴더 안에서 다음 코드를 실행해 줍니다

 

import os
import glob

classes = []

def createFolders(root):
    try:
        os.mkdir(root + '/custom')
        os.mkdir(root + '/custom' + '/backup')
    except:
        return
    return


def classesList(root):
    try:
        for file in os.listdir(root):
            if os.path.isfile(os.path.join(root, file)) == False:
                classes.append(file)
    except:
        return
    return

def createNames(root, file):
    try:
        fileName = open(root + '/' + file, "w")
        for name in classes:
            fileName.write(name + '\n')
    except:
        return
    return

def YOLOdataset(wd, root, ratio):
    try:
        fileTrain = open(root + '/train.txt', "w")
        fileValid = open(root + '/valid.txt', "w")

        for index in classes:
            labelDir = wd + '/' + index
            labelList = glob.glob(os.path.join(labelDir, '*.txt'))
            imageList = glob.glob(os.path.join(labelDir, '*.jpg'))
            count_train = int(float(len(labelList)) * ratio)
            count = 0

            for label in labelList:
                name, extension = os.path.splitext(label)
                imagelabel = name + '.jpg'
                if imagelabel not in imageList:
                    print('false : '+imagelabel)
                    continue
                if count < count_train:
                    fileTrain.write(imagelabel + '\n')
                    print('train : '+imagelabel)
                else:
                    fileValid.write(imagelabel + '\n')
                    print('valid : '+imagelabel)
                count = count + 1
    except:
        return
    return

def YOLOdatafile(root):
    try:
        file = open(root + '/detector.data', "w")
        file.write('classes=' + str(len(classes)) + '\n')
        file.write('train=custom/train.txt' + '\n')
        file.write('valid=custom/valid.txt' + '\n')
        file.write('names=custom/custom.names' + '\n')
        file.write('backup=custom/backup/' + '\n')
    except:
        return
    return


wd = os.getcwd()
x64 = os.path.dirname(wd)
createFolders(x64)
classesList(wd)

Dir = x64 + '/custom'
createNames(Dir, "custom.names")
YOLOdataset(wd, Dir, 0.8)
YOLOdatafile(Dir)

 

실행하셨으면 darknet-master\build\darknet\x64 폴더에 custom 폴더가 생성되고 custom폴 더안에 backup 폴더, names 파일 data 파일, train.txt, test.txt가 생성되어 있을 것이고 각각의 파일들의 상태는 다음과 같습니다

custom 폴더
names 파일

 

DATA 파일
train.txt
test.txt

 

 

 

 

cfg파일

 

먼저 오리지널 cfg파일을 다운로드합니다

https://raw.githubusercontent.com/AlexeyAB/darknet/master/cfg/yolov4-tiny.cfg

 

저는 yolov4-tiny.cfg을 사용하였습니다

cfg 파일을 메모장으로 열고 저희가 사용하는 custom data에 맞게 수정해 줍니다

 

 

batch = 64 [기본값 64]

subdivisions = 4 [기본 값 8]

 

한 번에 batch만큼 처리하고 batch를 subdivisions만큼 나누어 처리합니다

batch는 클수록 subdivisions는 작을수록 빠르지만 많은 GPU memory가 필요합니다

 

train 시 out of memory 가 뜨면 subdivisions를 올려봅시다 (8, 16...)

 

 

height = 416

width = 416

 

32의 배수여야 하며 416과 608을 많이 사용합니다

 

 

max_batches = 6200 [클래스 개수 * 2000 + 200??]

 

iterations

2000 대신 3000, 4000을 곱하기도 합니다

 

+200 하시는 경우가 많은데 이유는 잘 모르겠네요..??

 

 

steps = 4800, 5400 [max_batches * 0.8, max_batches * 0.9]

 

해당 step에 도달 시 learning rate를 scale 만큼 재조정합니다

 

 

classes = 3 [클래스 개수]

 

yolo 레이어에 있습니다

yolov4-tiny 기준 총 2개 (yolov4는 3개)

 

 

filters = 24 [(클래스 개수 + 5) * 3]

 

yolo 레이어 바로 위인 convolutional 레이어의 filters를 수정합니다

yolov4-tiny 기준 총 2개 (yolov4는 3개)

 

 

anchors = 기본 값 사용

 

darknet.exe detector calc_anchors DATA파일 -num_of_clusters 9 -width 416 -height 416을 실행하여 계산된 값 (anchors.txt가 생성됩니다)을 입력합니다

 

default로 설정돼있는 값을 이용해도 큰 차이는 없는 듯합니다

 

 

random = 0 [or 1]

 

1로 설정한다면 height, width의 값을 다른 여러 값으로 바꾸면서 진행합니다

(저는 608 사이즈에서 out of memory가 발생해서 안 했습니다)

 

 

 

전부 완료했다면 원본 cfg와 헷갈리지 않게 파일명을 바꾸고 저장합니다

 

 

 

 

 

 

weight 파일

 

https://github.com/AlexeyAB/darknet/releases/download/darknet_yolo_v4_pre/yolov4-tiny.conv.29

yolov4-tiny.conv.29을 다운로드합니다

 

 

 

 

 

 

진짜 학습하기

 

이제 다 왔습니다

cmd 상에서 build\darknet\x64에 진입 후 다음 명령어를 실행해 줍니다

 

darknet.exe detector train custom/detector.data custom/yolov4-tiny-custom.cfg custom/yolov4-tiny.conv.29

(darknet.exe detector train DATA파일경로 cfg파일경로 weight파일경로)

 

 

~~

~~

~~

~~

 

 

 

 

4. 결과 확인

 

학습이 완료되었으면 map를 확인해봅시다

darknet.exe detector map custom/detector.data custom/yolov4-tiny-custom.cfg custom/backup/yolov4-tiny-custom_best.weights

(darknet.exe detector map DATA파일경로 cfg파일경로 학습된weight파일경로)

 

 

이번엔 실제 사진으로 test 해봅시다

darknet.exe detector test custom/detector.data custom/yolov4-tiny-custom.cfg custom/backup/yolov4-tiny-custom_best.weights test/test01.jpg

(darknet.exe detector test DATA파일경로 cfg파일경로 학습된weight파일경로 사진파일경로)

 

동영상 파일을 확인하고 싶다면

darknet.exe detector demo DATA파일경로 cfg파일경로 학습된weight파일경로 동영상파일경로

(-out_filename 파일명.avi 으로 저장 가능)

 

웹캠으로 확인하고 싶다면

darknet.exe detector demo DATA파일경로 cfg파일경로 학습된weight파일경로

 

 

 


참조

https://github.com/kiyoshiiriemon/yolov4_darknet
https://webnautes.tistory.com/1423
http://daddynkidsmakers.blogspot.com/2020/05/yolo.html
https://naloblog.tistory.com/69
https://wingnim.tistory.com/56
https://ultrakid.tistory.com/17
https://velog.io/@springkim/YOLOv2
https://www.ccoderun.ca/programming/2020-09-25_Darknet_FAQ/

 

 

'딥러닝 > YOLO' 카테고리의 다른 글

custom data를 이용한 YOLO 학습 (1/2)  (0) 2021.08.17
Windows10 darknet 설치하기  (0) 2021.08.14

https://github.com/AlexeyAB/darknet

 

GitHub - AlexeyAB/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Da

YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Darknet ) - GitHub - AlexeyAB/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object ...

github.com

 

 

저는 Keras에서 만들때 쓰기도 했고 졸업작품에서도 쓰기위해 캔, 종이컵, 페트병 이미지를 수집하였습니다

 

yolov4-tiny 로 진행하였음을 미리 알려드립니다

 

사용한 버전

  • Python = 3.7.7
  • OpenCV = 4.5.1

 

1. 데이터 수집

 

먼저 데이터 수집입니다 대표적인 방법으로는 2가지가 있는데

 

- kaggle 등에서 dataset 을 구하기

다른 사람이 미리 만들어둔 dataset을 이용하는 방법입니다

힘들게 구하러 다닐 필요도 없는게 장점이지만 내가 원하는 dataset 찾기가 힘들죠

 

- 이미지 크롤링 하기

프로그램을 이용하거나 파이썬 코드로 크롤링을 하는 방법입니다

손으로 하는것 보다 빠르게 학습 데이터를 모을수 있습니다 다만 모은 데이터를 한번 정리는 해야겠죠

프로그램을 이용하는건 크롬의 Fatkun 같은 어플을 이용하시면 되고

파이썬 코드는 구글에 검색하면 매우 많이 있으니 스킵 하겠습니다

 

 

 

그리고 클래스별로 모든 사진을 512*512 정사각형 형태로 resize합니다

(512*512 가 아니여도 너무 작지만 않으면 상관없습니다)

(resize 하기전에 이름이 한글이거나 특수기호가 있을수도 있기때문에 이름을 전부 바꾸고 진행합시다)

 

 

다음 파이썬 코드를 사진들이 있는 폴더에서 실행시켜 줍니다

name은 원하는 이름으로

그럼 resize 라는 폴더만에 resize된 사진들이 저장 됩니다

import cv2
import numpy as np
import os

SIZE = 512
wd = os.getcwd()
os.mkdir(wd + '/resize')
save_root = wd +'/resize/'
name = 'paper'

def ImageList(root):
    img_list = []
    
    for file in os.listdir(root):
        try:
            img = cv2.imread(os.path.join(root, file))
            img_list.append((img, file))
        except:
            return
    return img_list


img_list = ImageList(wd)
  
for i, img in enumerate(img_list, start=1):
	i1 = str(i)
	try:
		img1 = cv2.resize(img, (SIZE,SIZE), cv2.INTER_LINEAR)
		cv2.imwrite(save_root + name +' (' + i1.zfill(4) + ').jpg', img1)
	except:
		continue

 

이렇게 클래스별로 2000장 씩

총 6000장의 이미지 파일을 준비했습니다

 

 

 

2. 라벨링

 

라벨링 방법에는 BBox Label Tool, LabelMe, Labelbox 등이 있고

제가 소개할 방법은 BBox Label Tool입니다

 

원본 BBox Label Tool을 이용할 수 도 있지만 여러 클래스를 동시에 라벨링 할 수 있고 변환까지 시켜주는 코드가 있길래 이 코드로 진행해 보겠습니다

https://github.com/andrewhu/Multiclass-BBox-Label-Tool

 

GitHub - andrewhu/Multiclass-BBox-Label-Tool: Multi-class bounding box labeling tool

Multi-class bounding box labeling tool. Contribute to andrewhu/Multiclass-BBox-Label-Tool development by creating an account on GitHub.

github.com

 

 

 

 

github에서 다운을 받은후 폴더를 열어보면  classes 라는 이름의 텍스트 파일이 있습니다

이 텍스트 파일을 본인이 원하는 클래스로 바꾸어 줍니다

그리고 Images, Labels 폴더 안에 클래스 별로 폴더를 하나씩 생성합니다

Images 폴더안에 있는 각 폴더에 해당 이미지들은 전부 넣습니다

 

※ 여기서 폴더는 라벨링 편하게 하기위해 임의로 나눈 것으로 can 폴더안에 paper 이미지가 하나 들어가있거나 동시에 들어있는 이미지가 있어도 상관없습니다

 

 

전부 완료하면 다음과 같은 형태가 되어있습니다

 

Images

- can

-- can사진들

- paper

-- paper 사진들

- plastic

-- plastic 사진들

Labels

- can

- paper

- plastic

classes.txt

main.py

convert.py

..

 

 

이제 main.py를 실행해 줍니다

image Dir에서 디렉토리를 선택하고 Class로 해당 라벨을 선택 후 사각형을 그려줍니다

A, D 버튼으로 라벨이 저장 되면서 해당 디렉토리의 이전, 다음 사진으로 넘어가게 됩니다,

(마지막 사진일 경우에도 D 한번 눌러야 합니다!)

 

 

 

 

 

이 작업이 상당히 오래걸립니다...  하다보면 눈이 엄청 아프니 중간중간 쉬면서 합시다!!

 

 

 

 

 

 

그렇게 작업을 모두 진행하면

Labels 폴더안에 Images 폴더의 사진과 똑같은 이름의 .txt 파일들이 생성된것을 볼수 있습니다

파일을 열어보면 다음과 같은 형태가 보이는데

 

 

이제 convert.py를 실행합니다

그럼 YOLO_Formatted 폴더가 생성되며 이미지와 텍스트 파일이 모두 옮겨지는데 텍스트 파일을 열어보면

 

좌상단 꼭지점 x, 좌상단 꼭지점 y, 우하단 꼭지점 x, 우하단 꼭지점 y, 이미지의 width, 이미지의 height, 클래스 명

의 형태였던 텍스트 파일이

 

클래스 (0, 1, 2),  박스 중앙 x, 박스 중앙 y, 박스 width, 박스 height

의 형태로 변환 된것을 볼 수 있습니다

 

 

https://blueberry-kyu.tistory.com/12

 

'딥러닝 > YOLO' 카테고리의 다른 글

custom data를 이용한 YOLO 학습 (2/2)  (0) 2021.08.21
Windows10 darknet 설치하기  (0) 2021.08.14

 

사용한 버전

  • Visual Studio 2019
  • CUDA = 11.2
  • cuDNN = 8.1.0
  • OpenCV = 4.5.1

 

CUDA

먼저 본인 그래픽 카드의 compute capability를 알아야 합니다

아래 링크로 들어가 줍니다

https://en.wikipedia.org/wiki/CUDA#Version_features_and_specifications

 

CUDA - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Parallel computing platform and programming model CUDA (an acronym for Compute Unified Device Architecture) is a parallel computing platform and application programming interface (API)

en.wikipedia.org

저의 경우 NVIDIA GeForce GTX 1650 이므로 7.5 네요

그리고 CUDA는 10.0 이상을 써야 하겠네요

 

 

 

 

cuDNN

다음은 cuDNN입니다 이 역시 CUDA의 버전에 맞는 버전으로 설치해야 합니다 (로그인해야 합니다)

https://developer.nvidia.com/rdp/cudnn-archive

 

cuDNN Archive

NVIDIA cuDNN is a GPU-accelerated library of primitives for deep neural networks.

developer.nvidia.com

 v8.1.0 for CUDA 11.2로 다운로드합니다

 

 

CUDA가 아래 경로에 설치되어 있을 겁니다

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v(버전)

 

 

cuDNN의 압축을 풀고 cuDNN의 CUDA폴더 안에 있는 bin, include, lib\x64 의 파일들을

각각 위경로의 bin, include, lib\x64의 폴더에 넣어줍니다

 

 

시스템 변수

다음으로 환경변수에 들어가 시스템 변수로 CUDA_PATH가 생성돼있는지 확인하고 안돼 있으면 설정해줍니다

변수 값(경로)
CUDA_PATH  C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2(각자버전)

 

 

OpenCV

다음은 OpenCV를 설치해 줍니다

OpenCV에서 YOLOv4의 가중치를 가져오려면 4.4.0 이상의 버전이 필요하므로 4.5.1로 설치하였습니다

 

OpenCV 역시 시스템 변수로 추가해 줍니다

변수 값(경로)
OPENCV (각자 설치한 경로)\opencv\build\x64\vc15\bin
path (각자 설치한 경로)\opencv\build\x64\vc15\bin



darknet

darknet을 다운로드하고 압축을 풀어줍니다

https://github.com/AlexeyAB/darknet

 

GitHub - AlexeyAB/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Da

YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object Detection (Windows and Linux version of Darknet ) - GitHub - AlexeyAB/darknet: YOLOv4 / Scaled-YOLOv4 / YOLO - Neural Networks for Object ...

github.com

 

 

darknet-master/build/darknet/darknet.sln 을 Visual Studio로 열고

 

구성 : Release

플랫폼 : x64 로 설정하고

 

Windsows SDK 버전을 10.0 (최근 설치된 버전)

플랫폼 도구 집합을 Visual Studio 2019 (v142)로 변경합니다

 

 

프로젝트 우클릭 -> 빌드 종속성 -> 사용자 정의 빌드에서 기존 파일 찾기로

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\extras\visual_studio_integration\MSBuildExtensions\CUDA 11.2.targets

을 추가하고 선택한다

 

 

다음은 헤더 파일 경로와 라이브러리 경로를 추가합니다

포함 디렉터리

C:\opencv451\build\include
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\include

라이브러리 디렉터리

C:\opencv451\build\x64\vc15\lib
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\lib\x64

 

 

빌드

시간이 꽤나 오래 걸립니다

빌드에 성공하면 darknet-master\build\darknet\x64 에 darknet.exe 가 생성됩니다

 

혹시라도 전부 진행하였고 다시 진행했는데도 문제가 생기신다면 제어판에서 NVIDIA 붙은 모든 걸 지우고 드라이버부터 다시 설치하는 걸 추천드립니다

 

저도 일주일 헤매다가 이렇게 해서 성공했습니다!

 

 

test

이제 실행해볼 차례입니다

 

사진을 test하는 명령어는

darknet.exe detector test (data file.data) (cfg 파일.cfg) (가중치 파일.weights) (사진 파일)

darknet.exe detector test cfg/coco.data cfg/yolov3.cfg yolov3.weights data/tennis.jpg

 

 

사진은 이번 도쿄올림픽 테니스 경기의 한 장면을 테스트 해봤습니다

 

 

 

이는 욜로에서 제공하는 coco data를 사용한 것으로 yolo에 없는 클래스의 물체라면 판별할 수 없습니다

이 사진을 예로 들면 신발이나 전광판을 인식하고 싶다면 coco data로는 불가능하죠

 

따라서 저희가 필요한 데이터를 인식하고 싶다면 데이터를 수집하여 학습을 시켜야 할 것입니다

 


참조

https://m.blog.naver.com/estern/221828977313

https://ultrakid.tistory.com/25

'딥러닝 > YOLO' 카테고리의 다른 글

custom data를 이용한 YOLO 학습 (2/2)  (0) 2021.08.21
custom data를 이용한 YOLO 학습 (1/2)  (0) 2021.08.17

가지고 있는 모델을 가지고

단일 이미지를 예측하는 predict 함수와 predict_classes 함수를 알아보고

간단한 활용 방법을 알고보고자 합니다

 

 

사용한 버전

  • tensorflow = 2.2.0
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.preprocessing import image
from tensorflow.python.keras.models import load_model


Path = '경로'
model = load_model('모델')

저는 이전에 만든

캔, 종이컵, 페트병을 분류하는 모델을 사용했습니다

index = ['can', 'paper', 'plastic']
image_size = (1, 224, 224, 3)


img = image.load_img(Path,target_size=image_size[1:3])	# 이미지 불러오기
img = image.img_to_array(img)				# (height, width, channel) (224, 224, 3)
img = img/255.						# 전처리

plt.imshow(img)
plt.show()

img = np.expand_dims(img, axis=0)			# (batch_size, height, width, channel) (1, 224, 224, 3)

이미지를 가져와 출력해보고

predict에 넣을 (batch_size, height, width, channel) 형식으로 맞춰줍니다

 

pred = model.predict(img)
pred_c = model.predict_classes(img)

print('pred\t\t\t{}'.format(pred))
print('pred_c\t\t\t{}\n'.format(pred_c))

print('pred[0]\t\t{}'.format(pred[0]))
print('pred_c[0]\t{}\n'.format(pred_c[0]))

print('index[pred_c[0]]\t{}'.format(index[pred_c[0]]))
print('pred[0][pred_c[0]]\t{}\n'.format(pred[0][pred_c[0]]))

pred                     [[6.3417815e-03 1.2435913e-07 9.9365813e-01]]

pred_c                  [2]

 

pred[0]                  [6.3417815e-03 1.2435913e-07 9.9365813e-01]

pred_c[0]               2

 

index[pred_c[0]]      plastic

pred[0][pred_c[0]]    0.9936581254005432

 

predict은 이미지에 대하여 각각의 모델의 label의 확률을 계산해 출력하고

predict_classes는 이미지에 대해 가장 높은 확률의 label을 반환합니다

 

 

 

그리고 다음과 같은 경고문이 나옵니다

 

WARNING:tensorflow:From <ipython-input-6-c3c078cb0bd4>:13: Sequential.predict_classes (from tensorflow.python.keras.engine.sequential) is deprecated and will be removed after 2021-01-01. Instructions for updating:
Please use instead:* `np.argmax(model.predict(x), axis=-1)`, 

if your model does multi-class classification   (e.g. if it uses a `softmax` last-layer activation).* `(model.predict(x) > 0.5).astype("int32")`,

if your model does binary classification   (e.g. if it uses a `sigmoid` last-layer activation).

 

predict_classes 대신

출력 활성화 함수에 softmax를 사용하는 다중분류인 경우 np.argmax(model.predict(x))를

출력 활성화 함수에 sigmoid를 사용하는 이진분류인 경우 model.predict(x)>0.5를 사용하라는데

 

 

np.argmax의 경우 원-핫 인코딩을 하게해주는 함수로 가장 높은 요소의 index를 반환해 줍니다

np.argmax([0.1 0.2 0.7]) == 2

np.argmax([0.5 0.4 0.1]) == 0

 

 

 

 

그렇다면 다음과 같이 predict_classes를 쓰지 않고 진행할 수 있을 것 입니다

print('np.argmax(pred[0])\t\t{}'.format(np.argmax(pred[0])))
print('index[np.argmax(pred[0])]\t{}'.format(index[np.argmax(pred[0])]))
print('pred[0][np.argmax(pred[0])]\t{}\n'.format(pred[0][np.argmax(pred[0])]))

np.argmax(pred[0])                2

index[np.argmax(pred[0])]       plastic

pred[0][np.argmax(pred[0])]     0.9936581254005432

 

 

 

 

 

전체코드

더보기
더보기
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.preprocessing import image
from tensorflow.python.keras.models import load_model

Path = "경로"
model = load_model('모델')

index = ['can', 'paper', 'plastic']
image_size = (1, 224, 224, 3)


img = image.load_img(Path+'img.jpg',target_size=image_size[1:3])
img = image.img_to_array(img)
img = img/255.

plt.imshow(img)
plt.show()

img = np.expand_dims(img, axis=0)

pred = model.predict(img)
pred_c = model.predict_classes(img)


print('pred\t\t\t{}'.format(pred))
print('pred_c\t\t\t{}\n'.format(pred_c))

print('pred[0]\t\t\t{}'.format(pred[0]))
print('pred_c[0]\t\t{}\n'.format(pred_c[0]))

print('index[pred_c[0]]\t{}'.format(index[pred_c[0]]))
print('pred[0][pred_c[0]]\t{}\n'.format(pred[0][pred_c[0]]))

print('np.argmax(pred[0])\t\t{}'.format(np.argmax(pred[0])))
print('index[np.argmax(pred[0])]\t{}'.format(index[np.argmax(pred[0])]))
print('pred[0][np.argmax(pred[0])]\t{}\n'.format(pred[0][np.argmax(pred[0])]))

 

 

 

모델을 만들었다면 이를 사용하여 실제 예측을 해볼 수 있을 것입니다

그래서 webcam을 이용하여 실제 이미지 데이터를 불러온 후 판별하는 방법을 사용해볼 겁니다

 

 

 

그전에 생각해야 할 것이

이전 포스팅에서 저는 모델을 학습시킬 때

사진과 같은 방법으로 전처리를 하였습니다

 

물체가 중심에 있는 grayscale의 이미지입니다

 

그렇다면 webcam으로 이미지를 불러올 때도 사진과 같은 방법으로 이미지를 가져와야 할 것입니다

 

 

아무것도 없는 배경에 물체가 있다고 가정했을 때 그 물체를 찾고 정사각형으로 자른 후 자른 부분만을 판별하게 해야 합니다

 

 

사용한 버전

  • tensorflow = 2.2.0
  • OpenCV = 3.4.2

 

import cv2
import numpy as np

from tensorflow.python.keras.models import load_model
model = load_model('h5 model')

필요한 라이브러리를 import 하고

본인의 h5 모델을 로드합니다

 

Exist = False
trash_result = " "
image_size = (1, 224, 224, 3)

def ImageProcessing(img):
    imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img_blur = cv2.GaussianBlur(imgray, ksize=(5,5), sigmaX=0)
    
    img_cvt = cv2.adaptiveThreshold(
        img_blur,
        maxValue=255.0,
        adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C,
        thresholdType=cv2.THRESH_BINARY_INV,
        blockSize=19,
        C=7,
        )
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
    img_mor = cv2.morphologyEx(img_cvt, cv2.MORPH_DILATE, kernel=kernel, iterations=3)
    
    return img_mor

앞서 언급했다시피 깨끗한 배경 안에서 물체를 특정하는 방법에 대한 함수입니다

세세한 수치는 사용자의 환경에 따라 달라지며 다음과 같은 순서로 진행하였습니다

 

def Contour(img, img_b):
    global Exist
    
    _, contours, _ = cv2.findContours(img_b, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    #Biggist Contour select
    count = 0
    biggist_cnt = 0
    x, y = 0, 0
    for i in contours:
        cnt = contours[count]
        x,y,w,h = cv2.boundingRect(cnt)
        S = w*h
        if S >= biggist_cnt:
            biggist_cnt = S
            cnt_count = count
        count += 1
        
    #Biggist Contour
    if biggist_cnt > 20000:
        cnt = contours[cnt_count]
    
    #Draw Rectangle, Dot
        x,y,w,h = cv2.boundingRect(cnt)
        y += 70
        cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
        
        cx = x + (w//2)
        cy = y + (h//2)
        cv2.line(img, (cx, cy), (cx, cy), (0, 0, 255), 10)
        Exist = True
        rect = (x,y,x+w,y+h)
        
    else:
        Exist = False
        rect = (0, 0, 0, 0)
        
    return img, rect

앞선 과정을 거쳤으면 결과 이미지에서 물체를 감싸는 contour를 찾을 수 있을 것이고

그중에서 가장 큰 contour가 저희가 찾고자 하는 물체의 범위일 것입니다

 

이렇게 물체가 있는 범위로 추정되는 contour를 반환할 수 있습니다

추가로 contour와 중앙점도 표시했습니다

 

 

def learning_result(image):
    global image_size
    
    image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    image = image/255.
    image = image.reshape(image_size)
    pred = model.predict(image)
    
    trash_result = index[np.argmax(pred[0])]
    percent = pred[0][np.argmax(pred[0])]
    
    if percent < 0.8:
        trash_result = " "
    
    return trash_result
    
    
def CropROI_result(image, contour):
    global Exist
    x1, y1, x2, y2 = contour
    
    WID = x2-x1
    HEI = y2-y1
    if HEI > WID:
        SIZE = HEI
    else:
        SIZE = WID
    
    if Exist == True:
        try:
            image = image[y1 + HEI//2 - SIZE//2:y1 +HEI//2 + SIZE//2,
                            x1 + WID//2 - SIZE//2:x1 + WID//2 + SIZE//2]
            ROI_image = cv2.resize(image, (224, 224), cv2.INTER_AREA)
            trash = learning_result(ROI_image)
            
        except Exception as e:
            trash = " "
    else :
        trash = " "
        
    return trash

image와 contour가 주어지면 image에서 contour를 기준으로 하는 정사각형을 crop 하고 학습시킨 모델을 이용하여 crop image가 어떤 물체인지 판별할 수 있을 것입니다

 

또한 predict 결과가 0.8 이상일 경우만 표시하도록 하였습니다

 

 

# open webcam
webcam = cv2.VideoCapture(1)
 
if not webcam.isOpened():
    print("Could not open webcam")
    exit()

index = ['can', 'paper', 'plastic']

while webcam.isOpened():
    
    # read frame from webcam 
    status, frame = webcam.read()
 
    img=frame.copy()
        
    # image processing
    img_pro = ImageProcessing(img)
        
    # findcontour
    img_mor = img_pro[70:410, : ] # 테두리 자르기
    display, cnt_rect = Contour(img, img_mor)
                
    if Exist==True:
        
        # predict learning result
        framegray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        result = CropROI_result(framegray, cnt_rect)

        # display learning result 
        cnt_x,cnt_y,_,_ = cnt_rect
        cv2.putText(display, result, (cnt_x, cnt_y-10), 2, 1, (255,0,0), 1)
        
    # display processed image, webcam image
    img_BW = cv2.cvtColor(img_mor, cv2.COLOR_GRAY2BGR)
    add = cv2.vconcat([img_BW, display[70:410,:,:]])
    cv2.imshow('display', add)
 
    # press "q" to stop
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

webcam.release()
cv2.destroyAllWindows()

메인 코드입니다

 

노트북 내장 웹캠 이용 시 cv2.VideoCapture(0)로 바꿔주시면 됩니다

 

height을 [70:410,:,:]으로 자른 건 제 웹캠이 위아래가 검은색 띠가 쳐져있어서 imageprocessing 시

이런 식으로 나오기 때문에 잘랐습니다

 

 

 

실행 영상입니다

 

 

 

 

전체 코드

더보기
import cv2
import numpy as np

from tensorflow.python.keras.models import load_model
model = load_model('h5 model')

Exist = False
trash_result = " "
image_size = (1, 224, 224, 3)


def ImageProcessing(img):
    imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # grayscale
    img_blur = cv2.GaussianBlur(imgray, ksize=(5,5), sigmaX=0) # GaussianBlur
    
    img_cvt = cv2.adaptiveThreshold( #adaptiveThreshold
        img_blur,
        maxValue=255.0,
        adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C,
        thresholdType=cv2.THRESH_BINARY_INV,
        blockSize=19,
        C=7,
        )
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
    img_mor = cv2.morphologyEx(img_cvt, cv2.MORPH_DILATE, kernel=kernel, iterations=3)
    

    return img_mor



def Contour(img, img_b):
    global Exist
    
    _, contours, _ = cv2.findContours(img_b, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    #Biggist Contour select
    count = 0
    biggist_cnt = 0
    x, y = 0, 0
    for i in contours:
        cnt = contours[count]
        x,y,w,h = cv2.boundingRect(cnt)
        S = w*h
        if S >= biggist_cnt:
            biggist_cnt = S
            cnt_count = count
        count += 1
        
    #Biggist Contour
    if biggist_cnt > 20000:
        cnt = contours[cnt_count]
    
    #Draw Rectangle, Dot
        x,y,w,h = cv2.boundingRect(cnt)
        y += 70
        cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
        
        cx = x + (w//2)
        cy = y + (h//2)
        cv2.line(img, (cx, cy), (cx, cy), (0, 0, 255), 10)
        Exist = True
        rect = (x,y,x+w,y+h)
        
    else:
        Exist = False
        rect = (0, 0, 0, 0)
        
    return img, rect



def learning_result(image):
    global image_size
    
    image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    image = image/255.
    image = image.reshape(image_size)
    pred = model.predict(image)
    
    trash_result = index[np.argmax(pred[0])]
    percent = pred[0][np.argmax(pred[0])]
    
    if percent < 0.8:
        trash_result = " "
    
    return trash_result



def CropROI_result(image, contour):
    global Exist
    x1, y1, x2, y2 = contour
    
    WID = x2-x1
    HEI = y2-y1
    if HEI > WID:
        SIZE = HEI
    else:
        SIZE = WID
    
    if Exist == True:
        try:
            image = image[y1 + HEI//2 - SIZE//2:y1 +HEI//2 + SIZE//2,
                            x1 + WID//2 - SIZE//2:x1 + WID//2 + SIZE//2]
            ROI_image = cv2.resize(image, (224, 224), cv2.INTER_AREA)
            trash = learning_result(ROI_image)
            
        except Exception as e:
            trash = " "
    else :
        trash = " "
        
    return trash
    
    
# open webcam
webcam = cv2.VideoCapture(1)
 
if not webcam.isOpened():
    print("Could not open webcam")
    exit()

index = ['can', 'paper', 'plastic']

while webcam.isOpened():
    
    # read frame from webcam 
    status, frame = webcam.read()
 
    img=frame.copy()
        
    # image processing
    img_pro = ImageProcessing(img)
        
    # findcontour
    img_mor = img_pro[70:410, : ] # 테두리 자르기
    display, cnt_rect = Contour(img, img_mor)
                
    if Exist==True:
        
        # predict learning result
        framegray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        result = CropROI_result(framegray, cnt_rect)

        # display learning result 
        cnt_x,cnt_y,_,_ = cnt_rect
        cv2.putText(display, result, (cnt_x, cnt_y-10), 2, 1, (255,0,0), 1)
        
    # display processed image, webcam image
    img_BW = cv2.cvtColor(img_mor, cv2.COLOR_GRAY2BGR)
    add = cv2.vconcat([img_BW, display[70:410,:,:]])
    cv2.imshow('display', add)
 
    # press "q" to stop
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

webcam.release()
cv2.destroyAllWindows()

 

 


추가

 

여기선 PC상에서 진행되지만

Python 기반이기 때문에 라즈베리 파이나 젯슨 나노 등에서도 사용할 수 있을 것이고

여러 센서와 모터 등과 결합하면 딥러닝 기반의 시스템을 만들 수 있겠죠

 

위의 코드를 응용해서 라즈베리파이와 젯슨 나노에서 컨베리이어 위를 지나가는 물체를 판별하는 영상입니다

 

 

인식하는 범위를 컨베이어 벨트 위 (노란 선 안쪽)로 한정하였고

물체의 중앙점 (빨간 점)이 화면을 지나갈 때 사각형 (초록 정사각형)을 판별하도록 하였습니다

 

 

우리가 이미지를 학습시킬 때 많은 양을 이미지를 필요로 하기 때문에

데이터셋을 받거나 크롤링을 하여 데이터를 수집할 텐데

 

그중에선 배경이 너무 크거나

너무 많은 다른 정보가 있을 수도 있습니다

 

이런 이미지들은 학습 시 방해가 될 수도 있기 때문에

원활한 학습을 위해 이미지 전처리를 해보려고 합니다

 

먼저 간단하게 설명하자면

마우스 컨트롤을 이용하여 원하는 부분을 지정하여

지정한 부분을 중심으로 하는 정사각형의 이미지를 crop 하여

 

 

 

 

더보기

다만 이런 식으로 전처리를 할 경우 test data 역시 이와 같은 방법으로 전처리를 해야 합니다

 

또한 실제 시스템에 적용하려 할 때도 물건의 위치를 파악하는 object detecting을 고려해야 할 것입니다

 

 

 

사용한 버전

  • Python = 3.6
  • OpenCV = 4.5.1

 

전체 코드입니다

 

import os
import cv2
import imutils
import numpy as np

Path = '경로'

drawing = False
ix, iy, xd, yd = (0, 0, 0, 0)


def ImageList():

    img_list = []

    for file in os.listdir(Path):
        if file.endswith(".jpg" or ".jpeg"):
            img = cv2.imread(os.path.join(Path, file))
            img_list.append((img, file))
    return img_list


def draw_mouse(event, x, y, flags, param):
    global ix, iy, xd, yd, drawing, img, img2

    if event == cv2.EVENT_LBUTTONDOWN:  		# 마우스를 누른 상태
        drawing = True
        ix, iy = x, y

    elif event == cv2.EVENT_MOUSEMOVE:  		# 마우스 이동
        if drawing == True:  					# 마우스를 누른 상태 일경우
            img2 = img.copy()
            cv2.rectangle(img2, (ix, iy), (x, y), (255, 0, 0), 1)

    elif event == cv2.EVENT_LBUTTONUP:			# 마우스를 떼면 상태 변경
        drawing = False  				
        cv2.rectangle(img2, (ix, iy), (x, y), (255, 0, 0), 1)
        ix, xd = min(ix, x), max(ix, x)
        iy, yd = min(iy, y), max(iy, y)


def InToArray(image, ARRSIZE, IMGSIZE):
    array = np.ones((ARRSIZE, ARRSIZE, 3), np.uint8)

    if image is None:
        print('Image load failed!')

    image_h, image_w, _ = image.shape
    if image_h > image_w:
        AREA_HEI = IMGSIZE
        AREA_WID = None
    else:
        AREA_HEI = None
        AREA_WID = IMGSIZE

    image_area = imutils.resize(image.copy(), height=AREA_HEI, width=AREA_WID, inter=cv2.INTER_AREA)
    AREA_HEI, AREA_WID, _ = image_area.shape

    # ARRAY 의 안에 IMAGE 잘라넣기
    if AREA_HEI == IMGSIZE:
        array[ARRSIZE // 2 - IMGSIZE // 2:ARRSIZE // 2 + IMGSIZE // 2,
        ARRSIZE // 2 - AREA_WID // 2:ARRSIZE // 2 - AREA_WID // 2 + AREA_WID, :] = image_area
    elif AREA_WID == IMGSIZE:
        array[ARRSIZE // 2 - AREA_HEI // 2:ARRSIZE // 2 - AREA_HEI // 2 + AREA_HEI,
        ARRSIZE // 2 - IMGSIZE // 2:ARRSIZE // 2 + IMGSIZE // 2, :] = image_area

    return array


def CutToImage(image):
    global ix, iy, xd, yd

    WID = xd - ix
    HEI = yd - iy
    if HEI > WID:
        SIZE = HEI
    else:
        SIZE = WID

    cut_image = image[iy + HEI // 2 - SIZE // 2:iy + HEI // 2 + SIZE // 2,
                ix + WID // 2 - SIZE // 2:ix + WID // 2 + SIZE // 2]
    return cut_image


if __name__ == '__main__':

    img_list = ImageList()

    ARRSIZE = 512
    IMGSIZE = 400
    RESULTSIZE = 224

    c = 1
    for img, file in img_list:
        ix, iy, xd, yd = (0, 0, 0, 0)
        img = InToArray(img, ARRSIZE=ARRSIZE, IMGSIZE=IMGSIZE)

        while True:
            img2 = img.copy()
            cv2.namedWindow(file)
            cv2.setMouseCallback(file, draw_mouse)
            cv2.moveWindow(file, 200, 180)

            while True:
                cv2.imshow(file, img2)
                k = cv2.waitKey(1) & 0xFF
                if k == ord('q'):  # q를 누르면 종료
                    break
            cv2.destroyAllWindows()

            img_cut = CutToImage(img)
            result_ = cv2.cvtColor(img_cut, cv2.COLOR_BGR2GRAY)
            
            try:
                result = cv2.resize(result_, (RESULTSIZE, RESULTSIZE), cv2.INTER_AREA)
                cv2.destroyAllWindows()
                cv2.namedWindow('result ' + file)
                cv2.imshow('result ' + file, result)

                k = cv2.waitKey(0) & 0xFF
                if k == ord('q'):       # q를 누르면 저장
                    cv2.imwrite(Path + '/img_' + str(c) + '.jpg', result)
                    cv2.destroyAllWindows()
                    break
                elif k == ord('w'):     # q를 누르면 다시
                    cv2.destroyAllWindows()
                else:                   # 그외는 건너뛰기
                    print('break ' + file)
                    cv2.destroyAllWindows()
                    break
            except Exception as e:
                print('break ' + file)
        c += 1

 

 

 


 

코드 세부내용입니다

 

import os
import cv2
import imutils
import numpy as np

Path = '경로'

drawing = False
ix, iy, xd, yd = (0, 0, 0, 0)

필요한 라이브러리를 import 하고

경로를 설정합니다

 

drawing - 마우스의 on/off

ix, iy - 마우스 컨트롤 변수, 좌상단 좌표

xd, yd - 마우스 컨트롤 변수, 우하단 좌표

 

def ImageList():
    img_list = []

    for file in os.listdir(Path):
        if file.endswith(".jpg" or ".jpeg"):
            img = cv2.imread(os.path.join(Path, file))
            img_list.append((img, file))
    return img_list

처음 설정한 경로 내의 jpg, jpeg 파일을 모두 불러

img_list 변수에 리스트 형태로 저장하는 함수입니다

 

def draw_mouse(event, x, y, flags, param):
    global ix, iy, xd, yd, drawing, img, img2

    if event == cv2.EVENT_LBUTTONDOWN: 		 # 마우스를 누른 상태
        drawing = True
        ix, iy = x, y

    elif event == cv2.EVENT_MOUSEMOVE:  	 # 마우스 이동
        if drawing == True:  					 # 마우스를 누른 상태 일경우
            img2 = img.copy()
            cv2.rectangle(img2, (ix, iy), (x, y), (255, 0, 0), 1)

    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False                      	# 마우스를 떼면 상태 변경
        cv2.rectangle(img2, (ix, iy), (x, y), (255, 0, 0), 1)
        ix, xd = min(ix, x), max(ix, x)
        iy, yd = min(iy, y), max(iy, y)

마우스 작업 함수입니다

 

처음 마우스를 클릭한 지점과 마우스는 떼는 지점을 저장하고

그로 인해 만들어지는 사각형의 좌상단 좌표와 우상단 좌표를 (ix, iy, xd, yd)에 저장합니다

 

마우스는 이동하는 동안 img에서 카피한 img2에 현재의 사각형을 그립니다

 

 

def InToArray(image, ARRSIZE, IMGSIZE):
    array = np.ones((ARRSIZE, ARRSIZE, 3), np.uint8)

    if image is None:
        print('Image load failed!')

    image_h, image_w, _ = image.shape
    if image_h > image_w:
        AREA_HEI = IMGSIZE
        AREA_WID = None
    else:
        AREA_HEI = None
        AREA_WID = IMGSIZE

    image_area = imutils.resize(image.copy(), height=AREA_HEI, width=AREA_WID, inter=cv2.INTER_AREA)
    AREA_HEI, AREA_WID, _ = image_area.shape

    # ARRAY 의 안에 IMAGE 잘라넣기
    if AREA_HEI == IMGSIZE:
        array[ARRSIZE // 2 - IMGSIZE // 2:ARRSIZE // 2 + IMGSIZE // 2,
        ARRSIZE // 2 - AREA_WID // 2:ARRSIZE // 2 - AREA_WID // 2 + AREA_WID, :] = image_area
    elif AREA_WID == IMGSIZE:
        array[ARRSIZE // 2 - AREA_HEI // 2:ARRSIZE // 2 - AREA_HEI // 2 + AREA_HEI,
        ARRSIZE // 2 - IMGSIZE // 2:ARRSIZE // 2 + IMGSIZE // 2, :] = image_area
        
    return array

ARRSIZE*ARRSIZE (여기서는 512*512) 크기의 빈 화면 안에

긴 쪽의 길이가 IMGSIZE가 되도록 resize 한 image를 잘라 넣는 함수입니다

 

 

def CutToImage(image):
    global ix, iy, xd, yd

    WID = xd - ix
    HEI = yd - iy
    if HEI > WID:
        SIZE = HEI
    else:
        SIZE = WID

    cut_image = image[iy + HEI // 2 - SIZE // 2:iy + HEI // 2 + SIZE // 2,
                ix + WID // 2 - SIZE // 2:ix + WID // 2 + SIZE // 2]
    return cut_image

image에서 특정 부분을 잘라내는 함수입니다

이때 사각형 (ix, iy, xd, yd)에서 긴 부분을 기준으로

중앙에서 정사각형 모양으로 잘라냅니다

 

 

if __name__ == '__main__':

    img_list = ImageList()

    ARRSIZE = 512
    IMGSIZE = 400
    RESULTSIZE = 224

    c = 1
    for img, file in img_list:
        ix, iy, xd, yd = (0, 0, 0, 0)
        img = InToArray(img, ARRSIZE=ARRSIZE, IMGSIZE=IMGSIZE)

        while True:
            img2 = img.copy()
            cv2.namedWindow(file)
            cv2.setMouseCallback(file, draw_mouse)
            cv2.moveWindow(file, 200, 180)

            while True:
                cv2.imshow(file, img2)			# 아래 사진 참조
                k = cv2.waitKey(1) & 0xFF
                if k == ord('q'):  # q를 누르면 종료
                    break
            cv2.destroyAllWindows()

메인 함수입니다

 

ImageList 함수를 이용하여 img_list에 모든 imagefmf 저장하고 for문을 이용하여 image 하나씩 진행합니다

InToArray 함수를 이용하여 image를 빈 array에 넣고 이를 출력합니다

cv2.setMouseCallback 함수를 이용하여 마우스 작업 함수인 draw_mouse를 콜백 합니다

 

사각형을 그리고 q 를 누르면 다음 화면으로 넘어갑니다

 

 

            img_cut = CutToImage(img)
            result_ = cv2.cvtColor(img_cut, cv2.COLOR_BGR2GRAY)
            
            try:
                result = cv2.resize(result_, (RESULTSIZE, RESULTSIZE), cv2.INTER_AREA)
                cv2.destroyAllWindows()
                cv2.namedWindow('result ' + file)
                cv2.imshow('result ' + file, result)	# 아래 이미지 참조

                k = cv2.waitKey(0) & 0xFF
                if k == ord('q'):       # q를 누르면 저장
                    cv2.imwrite(Path + '/img_' + str(c) + '.jpg', result)
                    cv2.destroyAllWindows()
                    break
                elif k == ord('w'):     # q를 누르면 다시
                    cv2.destroyAllWindows()
                else:                   # 그외는 건너뛰기
                    print('break ' + file)
                    cv2.destroyAllWindows()
                    break
            except Exception as e:
                print('break ' + file)
        c += 1

CutToImage 함수를 이용하여 정사각형을 잘라내고

grayscale로 변환합니다 (grayscale로 안 하시려면 빼도 됩니다)

 

그 후 다음 결과창이 출력되고

q를 누르면 저장이 되고 다음 image로 넘어가고

w를 누르면 사각형을 다시 그릴 수 있으며

그 외 버튼을 누르면 저장이 되지 않고 다음 image로 넘어갑니다

 

결과적으로 224*224의 grayscale인 image가 생성됩니다

 

 

 

 


+ 추가

 

 

지금 보니까 아예 이미지에서 관심영역을 지정하면 튜플로 반환해주는 함수가 있네요;;

cv2.selectROI(img)

https://www.kite.com/python/docs/cv2.selectROI

 

이번 포스팅에서는 캔, 페트병, 종이컵의 custom data를 이용한

전이 학습 transfer learning을 진행해 보려고 합니다

 

 

 

전이학습의 포인트는 기존에 학습되어 있는 모델을 가지고 자신의 모델을 학습 키시는 것인데

여기서 원래 학습되어있는 부분을 무너트리지 않는 선에서 미세조정을 해야한다는 것입니다

 

 

저의 경우 데이터는 적지만 모델이 어느정도 유사성이 있다고 판단하였고

특징을 추출하는 convolution base 부분은 전부 동결시키고

이미지 분류를 실시하는 Classifier 부분만 학습을 진행할 예정입니다

 

 

진행을 위해 224*224의 gray scale 이미지 1860장을 수집하였고

1650 장을 training + validation data

210 장을 test data로 이용하였습니다

 

더보기

모든 이미지를 물체가 중앙에 오도록 전처리하였습니다

전처리를 진행할 때 생각해 두셔야 하는 것이

 

이 모델을 실제로 써야 할 때 역시 같은 방법으로 전처리를 해야 한다는 것입니다

 

전처리 과정은 이 쪽

https://blueberry-kyu.tistory.com/7

 

pre-trained model로는 DenseNet201

가중치는 imagenet을 이용하였습니다

 

 

사용한 버전

  • tensorflow = 2.2.0

 

import tensorflow as tf
size = (224, 224, 3)

base_model = tf.keras.applications.DenseNet201(
    include_top=False,
    pooling='None',
    weights='imagenet',
    input_shape=size,
)
# base_model.summary()

pre-trained model 가져오기

summary() 이용해서 모델 구성 확인 가능 (너무 길다)

 

import tensorflow as tf
from tensorflow.keras import models, layers

model = models.Sequential()
model.add(base_model)
model.add(layers.Flatten(name='Flatten'))
model.add(layers.Dense(256, activation='relu', name='Dense_1'))
model.add(layers.Dropout(0.5, name='Dropout'))
model.add(layers.Dense(3, activation='softmax', name='Dense_2'))

model.summary()

model.summary()

print(len(model.trainable_weights))
base_model.trainable = False
print(len(model.trainable_weights))

606

4

pre-trained model 부분만 동결시키기

직접 추가한 Fully Conected layer 만 남은 것을 알 수 있습니다

 

from tensorflow.keras.preprocessing.image import ImageDataGenerator
import os
import numpy as np

Path = "경로설정"

imageGenerator = ImageDataGenerator(
    rescale=1./255,
    validation_split=0.2,
    rotation_range=30,
    zoom_range=[1.0,1.4],
    shear_range=0.1,
    brightness_range=[0.9,1.2],
    horizontal_flip=True,
    vertical_flip=True,
    fill_mode='nearest',
)

trainGen = imageGenerator.flow_from_directory(
    os.path.join(Path, 'training_set'),
    target_size=size[0:1],
    shuffle=False,
    subset='training'
)

validationGen = imageGenerator.flow_from_directory(
    os.path.join(Path, 'training_set'),
    target_size=size[0:1],
    shuffle=False,
    subset='validation'
)
x_train=np.concatenate([trainGen.next()[0] for i in range(trainGen.__len__())])
y_train=np.concatenate([trainGen.next()[1] for i in range(trainGen.__len__())])
x_val=np.concatenate([validationGen.next()[0] for i in range(validationGen.__len__())])
y_val=np.concatenate([validationGen.next()[1] for i in range(validationGen.__len__())])
import matplotlib.pyplot as plt

num_sample = 3

random_idxs = np.random.randint(trainGen.samples, size=num_sample)

def Trash(label):
    if label == 0: trash = 'can'
    elif label == 1: trash = 'paper'
    else: trash = 'plastic'
    return trash
    
plt.figure(figsize=(num_sample*3, num_sample*2))
for i, idx in enumerate(random_idxs):
    img = x_train[idx, :]
    label = y_train[idx]
    
    plt.subplot(1, len(random_idxs), i+1)
    plt.imshow(img)
    label_num = np.argmax(label)
    trash = Trash(label_num)
    
    plt.title("Index: {}, Label: {}".format(idx, trash))

training data 에서 랜덤 하게 3가지를 확인할 수 있습니다

셀을 반복 실행하면 다른 값도 볼 수 있어요

 

from tensorflow.keras import optimizers
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping

model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['acc'])
              
early_stopping = EarlyStopping(patience=3, monitor='val_loss',
                                 restore_best_weights=True)

optimizer는 안정적인 학습을 위해 SGD를 사용하였고

이미 학습이 되어있는 부분을 망치지 않기 위해 작은 learning rate를 사용하였습니다

 

또한 과적합이 되는 것을 방지하기 위해 EarlyStopping을 이용하여 val_loss가 3번 이상 나아지지 않으면 학습을 중단시키도록 하였습니다

 

베스트 모델을 저장시키는 ModelCheckpoint를 사용하셔도 좋습니다

 

epochs = 20

history = model.fit(x_train, y_train,epochs=epochs,
                   validation_data=(x_val, y_val),
                    callbacks=[early_stopping]
                    )

...

13 epoch 째 이후 3번 이상 더 나아지지 않아 학습이 중단되었습니다

 

history.history.keys()

dict_keys(['loss', 'acc', 'val_loss', 'val_acc'])

 

import matplotlib.pyplot as plt

history_dict = history.history

loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(loss)+1)
fig = plt.figure(figsize=(12, 6))

ax1 = fig.add_subplot(1, 2, 1)
ax1.plot(epochs, loss, color='blue', label='train_loss')
ax1.plot(epochs, val_loss, color='red', label='val_loss')
ax1.set_title('Train ans Validation loss')
ax1.set_xlabel('Epochs')
ax1.set_ylabel('loss')
ax1.grid()
ax1.legend()

accuracy = history_dict['acc']
val_accuracy = history_dict['val_acc']

ax2 = fig.add_subplot(1, 2, 2)
ax2.plot(epochs, accuracy, color='blue', label='train_accuracy')
ax2.plot(epochs, val_accuracy, color='red', label='val_accuracy')
ax2.set_title('Train ans Validation Accuracy')
ax2.set_xlabel('Epochs')
ax2.set_ylabel('Accuracy')
ax2.grid()
ax2.legend()

plt.show()

확실히 transfer learning을 이용하니 초반부터 높은 정확도와 낮은 손실이 나옴을 볼 수 있습니다

 

testGenerator = ImageDataGenerator(
    rescale=1./255
)

testGen = testGenerator.flow_from_directory(
    os.path.join(Path, 'test_set'),
    target_size=size[0:1],
    shuffle=False,
)
x_test=np.concatenate([testGen.next()[0] for i in range(testGen.__len__())])
y_test=np.concatenate([testGen.next()[1] for i in range(testGen.__len__())])

test data 가져오기

 

model.evaluate(x_test, y_test)

7/7 [==============================] - 19s 3s/step - loss: 0.0778 - acc: 0.9619

[0.07779338210821152, 0.961904764175415]

 

96%의 정확도가 나왔습니다 와!

 

num_sample = 3

random_idxs = np.random.randint(teatGen.samples, size=num_sample)

plt.figure(figsize=(num_sample*3, num_sample*2))
for i, idx in enumerate(random_idxs):
    img = x_test[idx, :]
    label = y_test[idx]

    pred_ysi = model.predict(x_test[random_idxs])
    arg_pred_yi = np.argmax(pred_ysi, axis=1)
    lab_trash = Trash(np.argmax(label))
    pred_trash = Trash(arg_pred_yi[i])
    
    plt.subplot(1, len(random_idxs), i+1)
    plt.imshow(img)
    plt.title("Label: {}, pred: {}".format(lab_trash, pred_trash))

test data 에서 랜덤 하게 3가지를 예측하고 정답과 함께 출력합니다

셀을 반복 실행하면 다른 값을 볼 수 있습니다

우측과 같이 틀린 예측도 있음을 볼 수 있습니다

 

y_true = np.argmax(y_test, axis=-1)

pred_ys = model.predict(x_test)
y_pred = np.argmax(pred_ys, axis=-1)

def Labels(num_list):
    label_list = []
    for num in num_list:
        if num==0:
            label_list.append('can')
        elif num==1:
            label_list.append('paper')
        else:
            label_list.append('plastic')
    return label_list

y_true = Labels(y_true)
y_pred = Labels(y_pred)
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns

plt.figure(figsize=(9, 9))
cm = confusion_matrix(y_true, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('predicted Label')
plt.ylabel('True Label')
plt.show()

0 == can

1 == paper

2 == plastic

confusion matrix는 다음과 같이 그려졌습니다

클래스당 70개의 test data 이니

 

캔은 모두 정확히 판단 하였는데

페트병을 캔으로 인식한 error가 많네요

 

model.save('recycling_model.h5')

모델 저장하기~

우리가 이미지 데이터를 가지고 와서 딥러닝을 진행할 때

Kaggle 등에서 데이터 셋을 가지고 오는 게 아니라면 직접 데이터를 수집해야 합니다 (노가다...)

 

데이터가 많다면 좋겠지만 우리처럼 개인이 하는 경우 데이터가 부족해서 문제일 것인데

그럴 때 필요한 것이 Image Augmentation 데이터 증폭입니다

 

데이터가 충분한데? 싶더라도

컴퓨터는 밝기를 다르게 한 이미지나 회전시킨 데이터를 기존의 데이터와 다른 데이터라고 인식합니다

 

 

 

 

Keras에서 전처리와 데이터 증폭을 시키는 클래스 ImageDataGenertor를 제공합니다

https://keras.io/ko/preprocessing/image/

 

Image Preprocessing - Keras Documentation

이미지 전처리 [source] ImageDataGenerator 클래스 keras.preprocessing.image.ImageDataGenerator(featurewise_center=False, samplewise_center=False, featurewise_std_normalization=False, samplewise_std_normalization=False, zca_whitening=False, zca_epsi

keras.io

사용한 버전

  • tensorflow = 2.2.0

 

 

 

여러 인수가 있지만 많이 쓰이는 인수만 정리하면

  • rescale
    • 전처리에 사용되는 인수
    • =1./255로 (0~255 -> 0~1) 전처리
  • rotaion_range
    • 좌 또는 우로 범위 내에서 회전시키는 인수
    • 단위는 도
  • zoom_range
    • 범위 내에서 확대 또는 축소시키는 인수
    • [x.x, x.x]로 쓰거나 0.x로 쓸 수 있음 ([0.8, 1.2] == 0.2)
  • brightness_range
    • 범위 내에서 밝기를 조절시키는 인수
    • zoom_range와 같은 방법으로 쓸 수 있음
  • horizontal_flip, vertical_flip
    • 불리언
    • True 라면 랜덤 하게 좌우, 상하반전
  • fill_mode
    • 회전, 축소 등의 처리 후 남는 공간의 처리
    • constant : kkkkkkkk|abcd|kkkkkkkk (k = (0,0,0))
    • nearest : aaaaaaaa|abcd|dddddddd (default)
    • reflect : abcddcba|abcd|dcbaabcd
    • wrap : abcdabcd|abcd|abcdabcd
  • validation_split
    • validation data로 사용할 비율
    • 0~1
imageGenerator = ImageDataGenerator(
    rescale=1./255,
    rotation_range=20,
    brightness_range=[0.8, 1.3],
    horizontal_flip=True,
    validation_split=0.2
)

 

위와 같이 사용하면 됩니다

 

그래서 저 증폭한 데이터는 어떻게 변했나??

 

여러분의 귀여운 고양이가 

 

이렇게 되어있을 수도 있어요...

 

그래서 imagedatagenerator를 사용할 때 너무 극단적인 인수는 넣지 않는 것을 추천하고

 

데이터를 확인하고 싶다면

학습 전에 다음의 코드를 실행해보시길 추천드립니다 

import os

import numpy as np
from keras.preprocessing.image import ImageDataGenerator, img_to_array

Path = '경로설정'

size_wh = 64

# 원하는 증폭 값 설정
imageGenerator = ImageDataGenerator(
    rescale=1./255,
    rotation_range=20,
    zoom_range=[0.8,1.2],
    shear_range=0.2,
    brightness_range=[0.9,1.2],
    horizontal_flip=True,
    vertical_flip=False,
    fill_mode='nearest',
)
import matplotlib.pyplot as plt
batch_size = 32

trainGen = imageGenerator.flow_from_directory(
    os.path.join(Path, 'training_set'),
    target_size=(size_wh,size_wh),
    batch_size=batch_size
)
def Augmentation(num_random_sample=5, num_range=3):
    random_idxs = np.random.randint(batch_size, size=num_random_sample)
    plt.figure(figsize=(num_random_sample*3, num_range*4))
    
    for i in range(num_range):
        img = trainGen.next()
    
        for j, ran in enumerate(random_idxs):
            plt.subplot(num_range, len(random_idxs), i*len(random_idxs)+j+1)
            plt.imshow(img[0][j])

next()가 한번 일어날 때마다 batch_size만큼의 데이터를 가져오므로

num_random_sample의 값은 batch_size보다 작거나 같아야 합니다

 

가용 메모리가 작아서 batch_size가 작은 경우

num_random_sample을 줄이고 num_range를 늘려보자

 

Augmentation()

셀을 반복 실행하면 또 다른 데이터가 나옵니다

 

 

 

이와 같이 ImageDataGenerator를 이용하여 증폭시킨 데이터들을 확인해보고

쓸만한 인수로 수정하여 넣는 것을 추천한다

 

 

 

 

 

+ 추가

궁금하던 것이

그래서 ImageDataGenerator를 통해 몇 개의 데이터가 더 생성되는 것일까?인데

 

https://stackoverflow.com/questions/51748514/does-imagedatagenerator-add-more-images-to-my-dataset

 

Does ImageDataGenerator add more images to my dataset?

I'm trying to do image classification with the Inception V3 model. Does ImageDataGenerator from Keras create new images which are added onto my dataset? If I have 1000 images, will using this funct...

stackoverflow.com

이 글에 의하면 딱 몇 개 증폭 이런 범위는 아니고

한 epochs 당 조금씩 변화한 데이터들로 학습을 진행하면서 조금이나마 다양한 데이터를 주려는 목적인 것 같습니다

어차피 변환한 데이터들도 원본과 엄청난 차이가 있는 건 아니기에...

+ Recent posts