사용한 버전

  • 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 당 조금씩 변화한 데이터들로 학습을 진행하면서 조금이나마 다양한 데이터를 주려는 목적인 것 같습니다

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

사용한 버전

  • tensorflow = 2.2.0

 

kaggle에서 개와 고양이 데이터셋을 다운로드하여 진행했습니다

 

해당 폴더 안에 (이름은 자유)

 

cat_and_dog

ㄴ training_set

    ㄴ cats

    ㄴ dogs

ㄴ test_set

    ㄴ cats

    ㄴ dogs

 

와 같이 구성해 놓아야 합니다

 

validation data 는 아래에서 따로 지정할 것입니다

 

 

import os
from tensorflow.keras.preprocessing.image import ImageDataGenerator

rootPath = './datasets/cat_and_dog'

imageGenerator = ImageDataGenerator(
    rescale=1./255,
    rotation_range=20,
    width_shift_range=0.1,
    height_shift_range=0.1,
    brightness_range=[0.2, 1.3],
    horizontal_flip=True,
    validation_split=.2
)

trainGen = imageGenerator.flow_from_directory(
    os.path.join(rootPath, 'training_set'),
    target_size=(64, 64),
    subset='training'
)

validationGen = imageGenerator.flow_from_directory(
    os.path.join(rootPath, 'training_set'),
    target_size=(64, 64),
    subset='validation'
)

Found 6404 images belonging to 2 classes.

Found 1601 images belonging to 2 classes.

 

 

ImageDataGenerator로 전처리와 이미지 증폭 설정

 

ImageDataGenerator에 대한 보다 자세한 설명은 아래 글 참조

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

 

 

 

 

from tensorflow.keras.models import Sequential
from tensorflow.keras import layers

model = Sequential()

model.add(layers.InputLayer(input_shape=(64, 64, 3)))
model.add(layers.Conv2D(16, (3, 3), (1, 1), 'same', activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(rate=0.3))

model.add(layers.Conv2D(32, (3, 3), (1, 1), 'same', activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(rate=0.3))

model.add(layers.Conv2D(64, (3, 3), (1, 1), 'same', activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(rate=0.3))

model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(2, activation='sigmoid'))

model.summary()

CNN 구성

이진 분류이므로 마지막 layer에 sigmoid를 사용하였습니다

 

 

model.compile(
    optimizer='adam',
    loss='binary_crossentropy', 
    metrics=['acc'],
)

epochs = 50
history = model.fit(
    trainGen, 
    epochs=epochs,
    validation_data=validationGen,
)

optimizer는 일반적으로 많이 사용하는 adam

loss는 이진 분류 이므로 binary_crossentropy를 사용하였습니다

metrics는 acc (정확도, accuracy로 써도 똑같음!)

 

더보기
더보기

이전 포스트에선 x_train(이미지), y_train(레이블) 형태로 데이터를 다뤘는데

 

그쪽이 편하다면

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__())])

의 방법으로 변환하여 사용하면 됩니다.

 

validation data와 test data도 같은 방법을 사용

 

 

...

대충 30분 정도 걸린 듯

 

 

 

 

history.history.keys()

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

 

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()

 

 

testGenerator = ImageDataGenerator(
    rescale=1./255
)

testGen = imageGenerator.flow_from_directory(
    os.path.join(rootPath, 'test_set'),
    target_size=(64, 64),
)

#model.evaluate_generator(testGen)
model.evaluate(testGen)

Found 2023 images belonging to 2 classes.

64/64 [==============================] - 8s 122ms/step - loss: 0.4474 - acc: 0.7958

 

정확도는 거의 80 퍼

나쁘지 않다

 

 

※ model.evaluate_generator를 이용하는 경우 경고가 뜰 수 있습니다

evaluate_generator을 사용하여도 결과는 나오지만 evaluate로 바꾸려는 추세인 듯..?

 

 

 

from tensorflow.keras.preprocessing.image import array_to_img
import numpy as np

index = ['cat', 'dog']
imgs = testGen.next()
arr = imgs[0][0]
img = array_to_img(arr).resize((128, 128))
plt.imshow(img)
result = model.predict_classes(arr.reshape(1, 64, 64, 3))
print('예측: {}'.format(index[result[0]]))
print('정답: {}'.format(index[np.argmax(imgs[1][0])]))

예측: cat

정답: cat

역시 셀을 반복 실행하면 다른 데이터를 볼 수 있습니다~

 

model.save('cat_dog_model.h5')

모델 저장하기

 

 

image와 label 분리

더보기
더보기

위의 방법은 ImageDataGenerator 를 이용한 데이터를 가지고 진행하였는데

image와 label을 따로 진행하는게 편하시다면

 

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__())])

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__())])

 

와 같은 방법으로 분리하여 진행할 수있습니다

처음 Classification을 접할 때 한 번씩은 해본다는 손글씨 분류 문제입니다

간단하게 트레이닝하는 방법은 여기저기 많으니

조금 살을 붙여서 천천히 확인하면서 진행해보려 합니다

 

 

 

 

사용한 버전

  • tensorflow = 2.2.0
import tensorflow as tf
from tensorflow.keras.datasets.mnist import load_data
from tensorflow.keras.models import Sequential
from tensorflow.keras import models
from tensorflow.keras.layers import Dense, Input, Flatten
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.utils import plot_model


from sklearn.model_selection import train_test_split
import numpy as np
import matplotlib.pyplot as plt

# mnist 불러오기
(x_train_full, y_train_full),(x_test, y_test) = load_data(path='mnist.npz')

# training data (70%), validation data (30%) 구분
x_train, x_val, y_train, y_val = train_test_split(x_train_full, y_train_full, test_size=0.3)

 

# data 갯수 확인
print('총 데이터: {}\t레이블: {}'.format(x_train_full.shape, y_train_full.shape))
print('학습 데이터: {}\t레이블: {}'.format(x_train.shape, y_train_full.shape))
print('검증 데이터: {}\t레이블: {}'.format(x_val.shape, y_val.shape))
print('테스트 데이터: {}\t레이블: {}'.format(x_test.shape, y_test.shape))

총 데이터: (60000, 28, 28) 레이블: (60000,)

학습 데이터: (42000, 28, 28) 레이블: (60000,)

검증 데이터: (18000, 28, 28) 레이블: (18000,)

테스트 데이터: (10000, 28, 28) 레이블: (10000,)

 

총 60000개의 28*28 이미지 데이터가 존재함을 확인할 수 있습니다

 

# 60000개의 데이터중 랜덤으로 5개 데이터 확인

plt.style.use('seaborn-white')
num_sample = 5

random_idxs = np.random.randint(60000, size=num_sample)

plt.figure(figsize=(num_sample*3, num_sample*2))
for i, idx in enumerate(random_idxs):
    img = x_train_full[idx, :]
    label = y_train_full[idx]
    
    plt.subplot(1, len(random_idxs), i+1)
    plt.imshow(img)
    plt.title("Index: {}, Label: {}".format(idx, label))

랜덤한 5개의 데이터

60000개의 데이터중 랜덤한 5개의 데이터를 출력해 보았고 셀을 반복하면 다른 5개의 데이터가 출력됩니다

출력되는 데이터의 개수를 조절하고 싶다면 num_sample을 조절해봅시다

 

 

 

 

# 데이터 전처리
x_train = x_train / 255.
x_val = x_val / 255.
x_test = x_test / 255.

y_train = to_categorical(y_train)
y_val = to_categorical(y_val)
y_test = to_categorical(y_test)

x값의 전처리는 28*28 의 각 픽셀을 0~255의 범위에서 0~1의 범위로 변환합니다

(가독성을 위해 소숫점 한 자리로 잘랐습니다)

 

x_test[0] 의 전처리

 

y값의 전처리에 쓰인 to_categorical() 함수는

0으로 된 배열을 만들고 해당 위치에만 1을 넣는 함수입니다.

 

y_test[0] 의 전처리

 

 

 

# 모델 구성
model = Sequential([Input(shape=(28, 28), name='input'),
                  Flatten(input_shape=(28, 28), name='flatten'),
                  Dense(100, activation='relu', name='dense1'),
                  Dense(64, activation='relu', name='dense2'),
                  Dense(32, activation='relu', name='dense3'),
                  Dense(10, activation='softmax', name='output')])

 

Input layer 에서 flatten을 이용하여 1차원 배열로 변환 후

Dense를 이용하여 Fully connected layer를 구성하였습니다

 

활성화 함수는 중간층은 모두 relu를 이용하였고

다중 분류이므로 출력층은 softmax를 이용했습니다

 

 

 

model.summary()

summary() 함수를 사용하여 파라미터 수를 확인할 수 있습니다

 

 

plot_model(model)

plot_model() 함수를 사용하여 모델을 시각화하여 볼 수 있습니다

 

 

 

model.compile(loss='categorical_crossentropy',
             optimizer='sgd',
             metrics=['accuracy'])

모델을 어떻게 학습시키는지에 대한 설정입니다

 

loss (손실 함수)는 categorical_crossentropy

optimizer는 sgd

metrics (평가지표)는 accuracy를 이용하였습니다

 

loss는 개/고양이 같은 이중 분류의 경우 binary_crossentropy를 이용하기도 하며

optimizer는 모델에 따라 adam 등 다양한 다른 optimizer를 이용하기도 합니다

 

 

 

# 모델학습
history =  model.fit(x_train, y_train,
                    epochs=30,
                    batch_size=128,
                    validation_data=(x_val, y_val))

...

30 epochs를 트레이닝하였고

batch_size는 128로 설정하였습니다

 

진행중~~~

 

30초 멍때리면 끝

 

 

 

history.history.keys()

dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

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['accuracy']
val_accuracy = history_dict['val_accuracy']

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()

validation인 빨간색 선을 주목해야합니다

loss는 계속 감소하고 accuracy는 계속 증가하는 것을 보아 완전히 학습되진 않은 것으로 추정됩니다 (underfitting 상태)

그래도 꽤 높은 값이니 그냥 진행해봅시다

 

 

 

# 모델 평가
model.evaluate(x_test, y_test) 
print(x_test.shape, y_test.shape)

313/313 [==============================] - 0s 1ms/step - loss: 0.1464 - accuracy: 0.9565 (10000, 28, 28) (10000, 10)

 

test data로 모델을 평가해보았을 때

loss : 0.1464  accuracy : 0.9565로 확인되었습니다

 

num_sample = 4

random_idxs = np.random.randint(10000, 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)
    
    plt.subplot(1, len(random_idxs), i+1)
    plt.imshow(img)
    plt.title("Label: {}, predicted label: {}".format(np.argmax(label), arg_pred_yi[i]))

랜덤한 4개의 예측 데이터

이 셀을 반복적으로 run 하여 다른 랜덤 한 데이터를 볼 수도 있습니다

물론 틀린 값도 나올 수 있어요

 

# Confusin Maxtrix (혼동행렬)
from sklearn.metrics import confusion_matrix
import seaborn as sns
sns.set(style='white')

plt.figure(figsize=(9, 9))
pred_ys = model.predict(x_test)
arg_pred_y = np.argmax(pred_ys, axis=1)
cm = confusion_matrix(np.argmax(y_test, axis=-1), np.argmax(pred_ys, axis=-1))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('predicted Label')
plt.ylabel('True Label')
plt.show()

싸이킷런 (sklearn)을 import 해서 confusion martrix를 확인할 수도 있습니다

 

model.save('mnist_model.h5')

모델 저장 하기

+ Recent posts