본문 바로가기

개발자/Computer Vision

[OpenCV 딥러닝] 미리 학습된 파일을 OpenCV DNN 모듈로 딥러닝

반응형

OpenCV DNN(Deep Neural Network) 모듈

 미리 학습된 딥러닝 파일을 OpenCV DNN 모듈로 실행할 수 있습니다.

 순전파(foward), 추론(inference)만 가능하며 학습은 지원하지 않습니다.

 

1. 네트워크 불러오기 - cv2.dnn.readNet

 OpenCV로 딥러닝을 실행하기 위해서는 우선 cv2.dnn_Net 클래스 객체를 생성해야 합니다.

 객체 생성에는 훈련된 가중치와 네트워크 구성을 저장하고 있는 파일이 필요합니다.

 

cv2.dnn.readNet(model, config=None, framework=None) -> retval 

• model: 훈련된 가중치를 저장하고 있는 이진 파일 이름 

• config: 네트워크 구성을 저장하고 있는 텍스트 파일 이름, config가 없는 경우도 많습니다.

• framework: 명시적인 딥러닝 프레임워크 이름 

• retval: cv2.dnn_Net 클래스 객체
딥러닝 프레임워크 model 파일 확장자 config 파일 확장자 framework 문자열
카페 *.caffemodel *.prototxt "caffe"
텐서플로우 *.pb *.pbtxt "tensorflow"
토치 *.t7 또는 *.net   "torch"
다크넷 *.weights *.cfg "darknet"
DLDT *.bin *.xml "dldt"
ONNX *.onnx   "onnx"

2. 네트워크 입력 블롭(blob) 만들기 - cv2.dnn.blobFromImage

 입력 영상을 블롭(blob) 객체로 만들어서 추론을 진행해야 합니다.

 주의할 점은 인자들을 입력할 때 모델 파일이 어떻게 학습되었는지 파악하고 그에 맞게 입력을 해줘야 합니다.

 

 하나의 영상을 추론할 때는 cv2.dnn.blobFromImage 함수를 이용하여 1개의 블롭객체를 받고

 여러 개의 영상을 추론할 때는 cv2.dnn.blobFromImags 함수로 여러 개의 블롭 객체를 받아서 사용합니다.

 

cv2.dnn.blobFromImage(image, scalefactor=None, size=None, mean=None, swapRB=None, crop=None, ddepth=None) -> retval

• image: 입력 영상 

• scalefactor: 입력 영상 픽셀 값에 곱할 값. 기본값은 1. 

• size: 출력 영상의 크기. 기본값은 (0, 0). 

• mean: 입력 영상 각 채널에서 뺄 평균 값. 기본값은 (0, 0, 0, 0). 

• swapRB: R과 B 채널을 서로 바꿀 것인지를 결정하는 플래그. 기본값은 False. 

• crop: 크롭(crop) 수행 여부. 기본값은 False. 

• ddepth: 출력 블롭의 깊이. CV_32F 또는 CV_8U. 기본값은 CV_32F. 

• retval: 영상으로부터 구한 블롭 객체. numpy.ndarray. shape=(N,C,H,W). dtype=numpy.float32.

 scalefactor은 딥러닝 학습을 진행할 때 입력 영상을 0~255 픽셀값을 이용했는지, 0~1로 정규화해서 이용했는지에 맞게 지정해줘야 합니다. 0~1로 정규화하여 학습을 진행했으면 1/255를 입력해줘야 합니다.

 

 

 size : 학습할 때 사용한 영상의 크기를 입력합니다. 그 size로 resize를 해주어 출력합니다.

 

 

 mean : 학습할 때 mean 값을 빼서 계산한 경우 그와 동일한 mean 값을 지정합니다.

 

 swapRB : RGB에서 R값과 B값을 바꿀것인지 결정합니다.

 

 corp : 학습할 때 영상을 잘라서 학습하였으면 그와 동이하게 입력해야 합니다.

 

 ddept : 대부분의 경우 CV_32F를 사용합니다.

 

 반환값의 shape=(N,C,H,W)인데 N은 갯수, C는 채널 갯수, HW는 영상 크기를 의미합니다.

 

3. 네트워크 입력 설정하기 - cv2.dnn_Net.setInput

 readNet으로 만든 객체에 .setInput 함수로 적용할 수 있습니다.

 name은 입력 레이어 이름을 지정할 수 있지만 보통 스킵합니다.

 scalefactor, mean은 블롭을 생성할 때 지정해주었으므로 기본값을 이용합니다.

 

cv2.dnn_Net.setInput(blob, name=None, scalefactor=None, mean=None) -> None 

• blob: 블롭 객체 

• name: 입력 레이어 이름 

• scalefactor: 추가적으로 픽셀 값에 곱할 값 

• mean: 추가적으로 픽셀 값에서 뺄 평균 값

4. 네트워크 순방향 실행(추론) - cv2.dnn_Net.forward

 추론을 진행할 때 이용하는 함수입니다.

 네트워크를 어떻게 생성했냐에 따라 출력을 여러 개 지정할 수 있습니다. (outputNames)

 

cv2.dnn_Net.forward(outputName=None) -> retval 
cv2.dnn_Net.forward(outputNames=None, outputBlobs=None) -> outputBlobs

• outputName: 출력 레이어 이름 

• retval: 지정한 레이어의 출력 블롭. 네트워크마다 다르게 결정됨. 

• outputNames: 출력 레이어 이름 리스트 

• outputBlobs: 지정한 레이어의 출력 블롭 리스트

5. dnn 모듈을 이용하여 미리 학습된 파일을 불러와서 숫자 인식하기

 예제 코드는 황선규 박사님의 깃허브 홈페이지를 참고했습니다.

 예제 코드를 분석하고 실습해보았습니다.

 

 직접 손글씨로 숫자를 그리고 이를 위치 정규화하여 blob 객체를 만듭니다.

 이를 딥러닝 모델 파일로 추론을 하여 필기체 숫자를 인식하는 프로그램입니다.

 

oldx, oldy = -1, -1

# 그림을 그리기 위한 함수
def on_mouse(event, x, y, flags, _):
    global oldx, oldy
    
    if event == cv2.EVENT_LBUTTONDOWN:
        oldx, oldy = x, y

    elif event == cv2.EVENT_LBUTTONUP:
        oldx, oldy = -1, -1

    elif event == cv2.EVENT_MOUSEMOVE:
        if flags & cv2.EVENT_FLAG_LBUTTON:
            cv2.line(img, (oldx, oldy), (x, y), (255, 255, 255), 40, cv2.LINE_AA)
            oldx, oldy = x, y
            cv2.imshow('img', img)

# 영상의 위치 정규화
def norm_digit(img):
    # 무게 중심 좌표 추출
    m = cv2.moments(img)
    cx = m['m10'] / m['m00']
    cy = m['m01'] / m['m00']
    h, w = img.shape[:2]
    
    # affine 행렬 생성
    aff = np.array([[1, 0, w/2 - cx], [0, 1, h/2 - cy]], dtype=np.float32)
    
    # warpAffine을 이용해 기하학 변환
    dst = cv2.warpAffine(img, aff, (0, 0))
    return dst

# 네트워크 불러오기
net = cv2.dnn.readNet('mnist_cnn.pb')

if net.empty():
    print('Network load failed!')
    sys.exit()

# 그림을 그리기 위한 검은 영상 생성
img = np.zeros((400, 400), np.uint8)

cv2.imshow('img', img)
# 마우스 콜백 함수
cv2.setMouseCallback('img', on_mouse)

while True:
    c = cv2.waitKey()

    if c == 27:
        break
    elif c == ord(' '): # 스페이스바 입력
        # 그림을 그린 영상으로 blob 객체 생성
        blob = cv2.dnn.blobFromImage(norm_digit(img), 1/255., (28, 28))
        net.setInput(blob)
        prob = net.forward() # 확률 값 출력, 확률의 최댓값이 인식한 클래스 의미
        
        # 최댓값과 최댓값의 클래스 검출
        _, maxVal, _, maxLoc = cv2.minMaxLoc(prob)
        digit = maxLoc[0] # [0]은 클래스, [1]은 확률

        print(f'{digit} ({maxVal * 100:4.2f}%)')

        img.fill(0) # 0으로 채우기
        cv2.imshow('img', img)

cv2.destroyAllWindows()

 

검정색 화면에 숫자 그리기

 

 필기체 숫자 인식

 

 딥러닝으로 학습한 모델이 필기체 숫자를 3으로 인식하였습니다.

 


OpenCV 깃허브 사이트와 황선규 박사님의 깃허브 사이트를 참고하면서 작성했습니다.

반응형