본문 바로가기

미정/Project

영상에서 숫자 인식하기

1. 기간

2022.07.04 ~ 2022.07.15

 

2. 목적

멸균기 LCD로부터 원하는 데이터를 추출하여 파일화

 

3. 내용

멸균기 LCD를 웹캠으로 촬영하여 원하는 부분의 이미지를 추출한 뒤 숫자로 인식시켜 파일로 저장한다.

 

4. 상세 역할

1)  OpenCV를 이용하여 웹캠으로 촬영한 영상에서 원하는 부분만 이미지로 저장

2)  추출한 이미지에 변형을 가해 다양한 이미지를 생성 및 카테고리로 분류

3)  CNN을 이용하여 분류된 이미지로 model 형성

4)  보정 작업을 통해 model 인식률 90%이상으로 형성

5)  인식한 숫자를 문자열화하여 csv파일로 저장

 

5. 사용언어 및 개발환경

Python, OpenCV, Keras

 

6. 코드

코드1 - 모델 형성

from PIL import Image
import os, glob
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import numpy as np

from sklearn.model_selection import train_test_split
from keras.models import Sequential
from keras.layers import MaxPooling2D
from keras.layers import Conv2D
from keras.layers import Activation, Dropout, Flatten, Dense

# --------데이터 수집--------------

# 분류 대상 카테고리 선택하기
number_dir = "C:\pythonworkspace\image"
categories = ["-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "x"]
nb_class = len(categories)

# 이미지 크기 지정
image_w = 50
image_h = 50

# 이미지 데이터 읽어 들이기
X = []
Y = []
for idx, cat in enumerate(categories):
    # 레이블 지정
    label = [0 for i in range(nb_class)]
    label[idx] = 1
    # 이미지 가져오기
    image_dir = number_dir + "\\" + cat
    files = glob.glob(image_dir + "\\*.jpg")
    # 이미지 변환
    for i, f in enumerate(files):
        img = Image.open(f)
        img = img.convert("RGB")
        img = img.resize((image_w, image_h))
        data = np.asarray(img) # (50*50*3)배열로 변환
        X.append(data)
        Y.append(label)
X = np.array(X)
Y = np.array(Y)

# 학습 전용 데이터와 테스트 전용 데이터 구분
X_train, X_test, y_train, y_test = train_test_split(X, Y)
xy = (X_train, X_test, y_train, y_test)

print('>>> data 저장중 ...')
np.save("./image/Stprj.npy", xy)

# --------CNN 모델 생성--------------

# 데이터 열기
X_train, X_test, y_train, y_test = np.load("./image/Stprj.npy", allow_pickle=True)

# 데이터 정규화하기(0~1사이로)
X_train = X_train.astype("float") / 256
X_test = X_test.astype("float") / 256
print('X_train shape:', X_train.shape)

# 모델 구조 정의
model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=X_train.shape[1:], padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))

model.add(Conv2D(64, (3, 3)))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

# 전결합층
model.add(Flatten())    # 벡터형태로 reshape
model.add(Dense(512))   # 출력
model.add(Activation('relu'))
model.add(Dropout(0.5))

model.add(Dense(nb_class))
model.add(Activation('softmax'))

# 모델 구축하기
model.compile(loss='categorical_crossentropy',   # 멀티 클래스 분류
    optimizer='rmsprop',
    metrics=['accuracy'])

# 모델 확인
print(model.summary())

# 모델 저장
hdf5_file = "./image/Stprj-model.h5"
model.fit(X_train, y_train, batch_size=50, epochs=40)
model.save(hdf5_file)

# 모델 평가하기 
score = model.evaluate(X_test, y_test)
print('loss=', score[0])        # loss
print('accuracy=', score[1])    # acc

 

1) 기능 소개

멀티 클래스 분류 모델 형성

 

2) 작업 내용

- 합성곱 층(Conv2D)와 Max pooling층 구성

- Dense layer 구성

- Model 구축 및 저장

 

코드2 - 추출한 이미지 인식

from tabnanny import verbose
import cv2
import datetime
import time
import numpy as np
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

from cv2 import resize
from PIL import Image
from keras.models import load_model


interval = 1    # 1초 시간 간격
count1 = 0      # 전체 이미지 갯수
count2 = 0      # 추출한 이미지 갯수

cap = cv2.VideoCapture(0)
fourcc = cv2.VideoWriter_fourcc(*'DIVX')
out = cv2.VideoWriter('save.avi', fourcc, 30.0, (640, 480))
test_list = [-9, 12, 32, 62, 135, 154, 184, 204]
categories = ["-", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "x"]

model = load_model('./image/Stprj-model.h5')
file = open('prj01.csv', 'wt')

while(cap.isOpened()):
    now = datetime.datetime.now()
    ret, frame = cap.read()

    if(ret) :
        out.write(frame)

        if(interval >= 1):
            pre_time = now.second
            now_time = now.second

            cv2.imwrite('test/photo%d.jpg' % count1, frame)
            img_ori = cv2.imread('test/photo%d.jpg' % count1)
            cv2.imshow("Img_Original", img_ori)
            str = ""

            #보정
            if count1 == 0:
                cor = 0
            if count1 == 800:
                cor = 1
            if count1 == 1600:
                cor = 2
            if count1 == 2400:
                cor = 3
            if count1 == 3200:
                cor = 4
            if count1 == 4000:
                cor = 5
            if count1 == 4800:
                cor = 6
            
            for i in test_list:
                x = 197 + i + cor; y = 281;
                w = 18; h = 35;
                img_trim = img_ori[y:y+h, x:x+w]
                resize_img = cv2.resize(img_trim, (50,50))
                cv2.imwrite('test/res%d-%d.jpg' % (count1, count2), resize_img)

                img = Image.open('test/res%d-%d.jpg' % (count1, count2))
                img = img.convert("RGB")
                img = img.resize((50,50))
                data = np.asarray(img)
                X = np.array(data)
                X = X.astype("float") / 256
                X = X.reshape(-1, 50, 50,3)
                
                pred = model.predict(X, verbose = 0)
                result = [np.argmax(value) for value in pred]   # 예측 값중 가장 높은 클래스 반환
                
                if(categories[result[0]] != 'x'):
                    str += categories[result[0]]

                    if(count2 == 3):
                        str += ","
                
                count2 += 1
            
            print(str)
            file.write(str + '\n')
            count1 += 1
            count2 = 0 
        else:
            now_time = now.second
    
        interval = (now_time - pre_time) % 60

        if cv2.waitKey(1) == ord('q'): # ord는 문자의 유니코드 값을 돌려준다.
            break

cap.release()
out.release()
file.close()
cv2.destroyAllWindows()

 

1) 기능 소개

이미지로 숫자를 구분

 

2) 작업 내용

- 원하는 부분의 이미지 추출 및 저장

- 이미지를 숫자로 인식하여 문자열화

 

7. 결과

 

8. 고찰

프로젝트를 진행하면서 숫자를 인식하는 부분에 많은 시간을 소모했다. 여기에는 크게 세 가지 어려움이 있었는데 첫 번째는 데이터 셋 구성이다. 이미지에서 내가 원하는 숫자를 인식시키기 위해서는 카테고리에 맞는 수 많은 이미지가 필요했다. 하지만 동일한 이미지가 너무 많으면 그 외 이미지는 인식하지 못했고, 반대로 너무 다양한 이미지는 인식률의 변동이 심했다. 따라서 최대한 다양한 이미지를 준비하되 조금씩의 변형이 필요했다. 두 번째는 외부 환경 조성이다. 이는 첫 번째 어려움에서 언급한 다양한 이미지는 인식률의 변동이 심하다는 내용과 관련이 있다. 대표적인 외부 환경으로는 빛과 카메라의 위치가 있는데 이런 외부 환경에 의해서 이미지의 다양성이 너무 넓어졌다. 즉, 고려해야 할 것들이 너무 많아져서 수집해야 할 이미지는 훨씬 늘어났으며 이미지를 확보한다고 해도 인식률이 좋지 않았다. 따라서 가능한 외부 환경에서 오는 변수들을 제거해야 했다. 그래야 알맞은 범위 내에서 다양한 이미지를 짧은 시간 내에 확보하는 것이 가능했다. 마지막으로는 모델을 훈련하는데 필요한 batch_size와 epoch 설정이다. 이 값들은 어떻게 주느냐에 따라서 인식률에 큰 영향을 미친다. epoch를 너무 많이 주면 확보한 이미지만 인식하며, 너무 적으면 인식률의 변동이 크다. 따라서 계속되는 시험으로 적절한 값을 찾아야 했다.

 

프로젝트를 진행하며 한 가지 아쉬운 부분이 있다면 이미지를 추출하는 방법이다. 여기서는 프레임의 좌표를 지정해서 이미지를 추출했다. 하지만 이는 외부의 작은 흔들림에도 취약함을 보였다. 따라서 좌표를 지정하는 것이 아닌 객체를 인식하는 것으로 이미지를 추출했다면 프로젝트의 완성도가 더 높아졌을 것 같다.