Other/Computer vision

[CV] ImageNet을 이용한 인식

hyebin Lee 2021. 12. 17. 02:45
import numpy as np
from matplotlib import pyplot as plt
import json #json 파일 디코딩

#사전의 학습해 놓은 모델들
import keras
from keras.applications.vgg16 import VGG16, decode_predictions
from keras.applications.resnet50 import ResNet50
from keras.applications.inception_v3 import InceptionV3 

from keras.preprocessing.image import load_img
from keras.preprocessing.image import img_to_array

#파이썬 표준 이미지 라이브러리 , 이미지 위에 한글 출력
from PIL import ImageFont, ImageDraw, Image

#구글 번역기 
from googletrans import Translator  # pip install googletrans

 

VGG16

초기에 개발된 모델로 간단한 구조로 되어있다.

#이미지넷을 분류할 수 있는 초기에 개발된 모델
model = VGG16(weights='imagenet')
#사전의 학습되어있는 이미지 로딩
model.summary()
#전체적인 구조를 볼 수 있다.

네트워크 구조를 보면 ,

input 레이어는 영상크기가 224 * 244 이며, 컬러 영상을 입력으로 받고 64개의 필터를 사용하고

conv conv pool 이렇게 사용 conv할때는 크기가 줄지 않아 원본 크기와 동일하다→ 제로 패딩을 사용 pool영상크기가 반으로 줄어든다. (영상의 크기는 줄어들지만 필터수는 증가한다, 입력 영상이 224 -> 7)

1차원으로 펴서 25088, 히든레이어 4096 4096 마지막 출력층 1000 클래스 수가 1000개이기때문에!

영상 크기가 크기 때문에 깊은 네트워크로 설계해서 영상인식 수행 학습 해야하는 파라미터가 굉장히 많아서 이미지를 학습 시키는데 오래걸린다.

 

img_path = 'cat.png'  # naming.jpg   cat.png   mug.jpg
image = load_img(img_path, target_size=(224, 224))
#입력으로 이미지를 읽은 후 (224, 224) resize를 한다.

plt.imshow(image)
x = img_to_array(image)  # to numpy
print(x.shape)

(224, 224, 3)

 

x = np.expand_dims(x, axis=0) #3->4차원으로 바꿈
print(x.shape)

(1, 224, 224, 3)

  • CNN으로 할때는 항상 입력과 출력이 4차원

앞에 1은 영상 하나라는 의미

 

x = keras.applications.vgg16.preprocess_input(x)
#preprocess_input으로 이미지 전처리, 스케일이나 평균값을 빼줌

pred = model.predict(x)
print(pred.shape)

(1, 1000)

샘플 하나에 대해서 천개의 클래스 값, 즉 1000개의 확률값이 나옴

1000개는 소프트 맥스가 되어있기 때문에 다 더하면 1이된다.

 

plt.plot(pred[0,:])

pred는 2차원 행렬 pred[0,:] → 벡터형태 , 전체 1000개의 확률값이 나옴

그래프를 그리면 확률값이 가장 크게나오는 값이 있음

 

#확률값을 해석해서 레이블 값을 가져올 수 있음
prob = np.max(pred, axis=1) #큰 값을 읽어옴, 

id = np.argmax(pred, axis=1)
print(prob)#최대 확률값
print(id)#최대 확률값의 클래스 아이디

#둘다 벡터로 나오기 때문에 확률값만 뽑으려면 prob[0]을 줘야 값만 나올 수 있음

axis는 1을 주는데 pred가 n개의 이미지가 들어온다고 가정 시 여러개의 pred가 행렬로 나오기때문에 즉, 각 행(하나의 샘플)에서 최대값을 가져옴 axis = 1 row, 0이면 colum에서

 

print('Predicted:', decode_predictions(pred, top=3)[0])

예측한 아이디가 어떤 클래스 인지 확인 하기 위해 1000개의 레이블을 가져와야하는데 이때 decode_predictions함수를 사용함 top=3 가장 확률값이 높은 세개를 찾을 수 있음 레이블은 폴더에 있는 json 파일을 파싱해야함!

 

ResNet

modelResNet = ResNet50(weights='imagenet')
modelResNet.summary()

입력영상은 224*244로 컬러 입력을 받고 , 제로 패딩하고 conv 수행 conv2d를 하게 되면 반정도로 영상 크기가 줄어든다. 영상이 풀링을 하지 않고 반으로 줄어드는 이유는? 필터가 두칸씩 이동하기 때문에

BatchNormalizati은 정규화 과정으로 데이터 스케일링 과정이다. Activation으로 출력이 그대로 나옴

이와 같은 방식으로 영상 크기를 줄여나간다.

 

ResNet은 VGG16보다 깊이가 깊은 네트워크를 사용하지만 비슷한 구조를 사용한다. 컨볼루션 층이 매우 깊게 설계가 되어있고 필터수와 파라미터 수가 굉장히 많다.

GlovalAveragePooling2는 7*7있는 것을 하나로 다 더해서 2048을 만드는 것이다.

네트워크를 깊게 설계했지만 파라미터가 줄어든 것을 확인할 수 있다. 이유는 ? VGG16는 파라미터수가 많지 않지만, 대다두 파라미터는 첫번째 히든 레이어에있는 학습 데이터가 1억개이다. (대다수를 차지)

이미지가 77, 512 크기 만큼 1차원으로 줄어들기 때문에, 인풋 이미지가 25088차원이다. 이미지가 굉장히 커서 250884096을 곱한 값이 된다.

이런 비효율적인 면을 개선한 것이 resnet이다.

resnet은 많은 컨볼루션 필터를 사용하기 때문에 파라미터 수는 많지만 마지막 층의 파라미터는 작다.

img_path = 'cat.png'
image = load_img(img_path, target_size=(224, 224))
plt.imshow(image)
x = img_to_array(image)  # to numpy
x = np.expand_dims(x, axis=0)
x = keras.applications.resnet50.preprocess_input(x)
pred = modelResNet.predict(x)
print('Predicted:', decode_predictions(pred, top=3)[0])

VGG16과 예측값이 다른 이유는 훨씬 더 작은 크기의 네트워크, 적은 메모리와 계산속도 때문에 그만큼 더 빨라진다. 결과값은 다르지만 top1 값은 유사하다.

 

Inception_v3

modelInception = InceptionV3(weights='imagenet')
modelInception.summary()

VGG16에 비해 크기가 작고 네트워크도 상당히 깊게 구성되어있다. 컨볼루셔 레이어와 정규화를 통해 필터를 많이 사용한다. Conv2D과 - BatchNor 정규화 과정 - Activation - MaxPooling2D,으로 영상을 줄이거나 Conv2D로 영상을 줄이기도 한다. 최적의 값을 갖도록 모델을 섞어서 사용 전체적으로 구조는 비슷하다. 다양한 네트워크로, 여러가지를 조합해서 깊은 층의 CNN네트워크를 설계할 수 있다.

 

img_path = 'phone.jpg'
image = load_img(img_path, target_size=(299, 299))
plt.imshow(image)
x = img_to_array(image)  # to numpy
print(x.shape)
x = np.expand_dims(x, axis=0) #4차원으로 확장
print(x.shape)
x = keras.applications.inception_v3.preprocess_input(x) #전처리
preds = modelInception.predict(x)
print('Predicted:', decode_predictions(preds, top=3)[0])

 

OpenCV 연동

#img_path = 'elephant.jpg'
#img_path = 'mug.jpg'
#img_path = 'food.jpg'
img_path = 'phone.jpg'

#image = load_img(img_path, target_size=(224, 224))
#x = img_to_array(image)
#x = np.expand_dims(x, axis=0)

#opencv함수로 이미지를 읽기
img = cv2.resize(cv2.imread(img_path), (224, 224))
#imread 원본 이미지를 읽고 resize, 3채널은 유지하고 해상도만 줄임

imgrgb =  cv2.cvtColor(img, cv2.COLOR_BGR2RGB)#bgr->rgb (채널만 바꿈)
x = np.expand_dims(imgrgb, axis=0)#3->4차원으로 확장

x = keras.applications.vgg16.preprocess_input(x)
#preprocess_input 모델에 맞는 전처리 과정

pred = model.predict(x)
print('Predicted:', decode_predictions(pred, top=3)[0])
plt.imshow(imgrgb)

open cv에서 사용하는 컬러 채널 모델은 rgb가 아니라 bgr이다.

vgg16이나 지금까지 살펴본 이미지넷 등은 rgb채널을 사용한다.

 

ImageNet 클래스 레이블링

#레이블 데이터를 가져와서 값을 출력해봄
json_file = open("imagenet_class_index.json")
dict = json.load(json_file) 
#전체 데이터를 딕셔너리로 바꿔줌
#파이썬을 json을 직접적으로 사용할 수 없어 dic으로 변환하게 사용한다.
#키와 vlaue값을 얻을 수 있다.
#print(dict)

print(dict['0'])#문자열로 집어 넣어야함
id = np.argmax(pred, axis=1)[0] #아이디만 가져옴
name = dict[str(id)][1] #문자열을 넣고 value값을 가져옴
print(id, name)

pred : 예측한 1000개의 클래스 값

1000개의 확률값 중에서 가장 확률이 높은 인덱스를 가져오고, 가져온 인덱스를 문자열로 넣어줌

 

#전체 레이블 매번 딕셔너리로 가져오는 것 보다는 리스트 형태로 가지고 있는 것이 더 효율적이다.
labels = []
for  i  in range(len(dict))  : #1000개의 루프를 돈다.
    word = dict[str(i)][1]    #첫번째 데이터의 레이어를 가져옴
    word = word.replace("_", " ") #언더바 _ 제거
    word = word.replace("-", " ") #특수문자 - 제거
    labels.append(word)    
#print(labels) #1000개의 레이블
print(labels[id])

특수문자가 들어가 있으면 번역률이 낮아지기 때문에 제거하는 것이 좋음

인식된 아이디 값을 intger로 반환받아서 해당하는 아이디의 레이블 값을 가져오면 손쉽게 해당 레이블을 구할 수 있다.

 

#한글 레이블로 바꾸기
with open("labels_h.txt", 'r', -1, 'utf-8') as f:  #한글이 포함될 경우 utf-8
     labels_h = f.read().splitlines()            
print(labels_h)

labels_h.txt : 천개의 레이블이 한글로 변역되어서 구성

 

img_path = 'mug.jpg'

img = cv2.resize(cv2.imread(img_path), (224, 224))
imgrgb =  cv2.cvtColor(img, cv2.COLOR_BGR2RGB)    
x = np.expand_dims(imgrgb, axis=0)

#전처리과정
x = keras.applications.vgg16.preprocess_input(x)

pred = model.predict(x) #행렬의 예측값
id = np.argmax(pred, axis=1)[0] #1000개의 컬럼에서 최댓값의 아이디
prob = np.max(pred, axis=1)[0]
print('Predicted:', labels[id], labels_h[id], prob)
plt.imshow(imgrgb)

 

이미지에 글자 출력

img_path = 'mug.jpg'
img = cv2.resize(cv2.imread(img_path), (224, 224))
imgrgb =  cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # bgr->rgb

image = Image.fromarray(imgrgb)
#넘파이 이미지 타입을 내부적 파이썬 표준 이미지 라이브러리 타입으로 변환한다.

draw = ImageDraw.Draw(image)
draw.text((10, 20), "안녕하세요", font=ImageFont.truetype("KCCMurukmuruk(Windows용).ttf", 20), fill=(255,255,255))
plt.imshow(image)

open cv가 한글 출력이 안되기 때문에 한글 출력이 되는 라이브러리 사용

 

def drawText(img, x, y, text, size, color) :
    image = Image.fromarray(img)
    draw = ImageDraw.Draw(image)
    draw.text((x, y), text, font=ImageFont.truetype("KCCMurukmuruk(Windows용).ttf", size), fill=color)
    return image
#이때 image는 넘파이 타입

img_path = 'elephant.jpg'
img = cv2.resize(cv2.imread(img_path), (224, 224))
imgrgb =  cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 

image = drawText(imgrgb,  10, 20, "안녕 Hello~", 20, (255,255,0))
plt.imshow(image)

 

이미지에 인식 결과 출력

img_path = 'mug.jpg'
img = cv2.resize(cv2.imread(img_path), (224, 224))
imgrgb =  cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 

#전처리 과정
x = np.expand_dims(imgrgb, axis=0)
x = keras.applications.vgg16.preprocess_input(x)

preds = model.predict(x) #값 예측
id = np.argmax(preds, axis=1)[0] #0인 이유는 데이터가 하나이기때문에
prob = np.max(preds, axis=1)[0]
label = labels_h[id]

image = drawText(imgrgb,  10, 5, f"{label}/{prob:.3f}", 20, (255,0,0))
#.3f는 소숫점 셋째자리까지

plt.imshow(image)

 

동영상

동영상으로 영상 인식하고 인식된 결과를 지속적으로 보여주는 방법

capture = cv2.VideoCapture(0)
#동영상 또는 카메라로부터 영상을 입력 받을 수 있다.

capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

#카메라 입력을 계속 받아야 해서 무한루프를 돈다.
while True: 
    ret, frame = capture.read() 
#ret는 캡쳐한 영상이 있으면 true, 없으면 false
#이미지를 받아오고, frame은 넘파이타입   
    img = cv2.resize(frame, (224, 224))    
    cv2.imshow("VideoFrame", img)
    
    if cv2.waitKey(1) == ord('q'): break

capture.release()
cv2.destroyAllWindows()
capture = cv2.VideoCapture(0)

capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

while True: 
    #영상 전처리
    ret, frame = capture.read()    
    img = cv2.resize(frame, (224, 224))
    imgrgb =  cv2.cvtColor(img, cv2.COLOR_BGR2RGB)    
    x = np.expand_dims(imgrgb, axis=0)
    x = keras.applications.vgg16.preprocess_input(x)
    
    #예측, 확률값
    preds = model.predict(x)
    id = np.argmax(preds, axis=1)[0]
    prob = np.max(preds, axis=1)[0]    
    
    #레이어
    label = labels_h[id]
    tmp = drawText(imgrgb,  10, 5, f"{label}/{prob:.3f}", 20, (255,0,0))
		#open cv로 출력해야 할 수 있기 때문에 tmp 를 넘파이 타입으로    
		imgrgb = np.array(tmp)    #넘파이 타입으로 변환, tmp는 rgb이므로
    image =  cv2.cvtColor(imgrgb, cv2.COLOR_RGB2BGR) #rgb->bgr
    cv2.imshow("VideoFrame", image)
    
    if cv2.waitKey(1) == ord('q'): break

capture.release()
cv2.destroyAllWindows()

 

영어레이블 한글로 번역

translator  = Translator()
#구글의 번역 api, Translatorr객체 생성
res = translator.translate("hello", dest='ko')
#translate함수 사용, 인터넷 연결 되어있어야함, 영어를 한글로 번역

print(res)
print(res.text)
res = translator.translate(["test", "happy"], dest='ko')
#두개의 단어도 가능

print(res)
print(len(res))
print(res[0].text)
print(res[1].text)
res = translator.translate(labels[0:50], dest='ko')   
#레이블을 한꺼번에 번역 가능
for r in res :
    print(r.text)
#한꺼번에 번역한 레이블 출력 가능

1000개의 단어를 더하기 위해서 extend 이해!

 

a = [1,2,3]
a.extend([4,5,6])
print(a)

extend 일 경우 : [1,2,3,4,5,6]

append일 경 우 : [1,2,3,[4,5,6]]

 

labels_h = []   # 한글레이블을 빈 리스트로 , 
translator  = Translator()
for  i  in range(16, 20) :    #루프를 돈다.
    res = translator.translate(labels[i*50:(i+1)*50], dest='ko')   
    for r in res :
        labels_h.append(r.text)
    print(i)
#출력 시 에러가 나면 에러 다음 번호부터 시작해서 다시 루프를 실행
#에러가 나면 labels_h = []는 주석 처리 하기! 초기화 되기 때문
print(labels_h)
#한글 레이블을 저장하는 방법
with open("labels.txt", "w") as outfile:
    outfile.write("\\n".join(labels)) #join함수로 각 단어 끝에 엔터를 넣음
with open("labels_h.txt", "w",  -1, 'utf-8') as outfile:
    outfile.write("\\n".join(labels_h))
#파일을 저장하고 불러오는 방법
with open("labels.txt", 'r') as f:    
     labels2 = f.read().splitlines()    #splitlines() 라인 단위로 끊어서 리스트를 만들어줌
with open("labels_h.txt", 'r', -1, 'utf-8') as f:    
     labels_h2 = f.read().splitlines()            
#print(labels2)        
#print(labels_h2)
#단어 하나씩을 하나의 원소로 저장되어 있는 것을 확인 가능!