귀퉁이 서재

OpenCV - 33. HOG(Histogram of Oriented Gradient) 디스크립터 본문

OpenCV

OpenCV - 33. HOG(Histogram of Oriented Gradient) 디스크립터

Baek Kyun Shin 2021. 3. 23. 00:33

이번 포스팅에서는 HOG 디스크립터에 대해 알아보겠습니다. 이번에도 '파이썬으로 만드는 OpenCV 프로젝트(이세우 저)'를 정리했습니다.

기울기 벡터(Gradient Vectors)

기울기 벡터란 영상 내 하나의 픽셀을 기준으로 주변 픽셀에 대한 기울기를 나타내는 벡터를 의미합니다. 말이 좀 어려운데 아래 예시 그림을 보겠습니다. 펭귄 머리 부분을 확대해보겠습니다.

출처: https://chrisjmccormick.wordpress.com/2013/05/07/gradient-vectors/

빨간 점으로 표시된 픽셀을 기준으로 왼쪽의 Gray Scale 값은 56이고, 오른쪽의 값은 94입니다. Gray Scale의 경우 0이면 검은색이고 255이면 흰색입니다. 따라서 Gray Scale 값 94가 56보다 밝습니다. 위 그림에서 실제로도 오른쪽이 밝죠? 이때 빨간 점으로 표시된 픽셀 입장에서 x축 방향의 변화량(gx)은 (94 - 56) = 38이 됩니다. 이 값이 x축 방향 기울기 변화량입니다. 

이번엔 y축 방향으로 생각해 보시죠. 빨간 점을 기준으로 위쪽 Gray Scale 값은 93이고, 아래쪽 값은 55입니다. 따라서 y축 방향의 기울기 변화량은 (93 - 55) = 38입니다. x축 방향 기울기 변화량, y축 방향 기울기 변화량이 모두 38이네요.

출처: https://chrisjmccormick.wordpress.com/2013/05/07/gradient-vectors/

x축 방향 기울기 변화량, y축 방향 기울기 변화량을 함께 표현한 값이 기울기 벡터(gradient vector)입니다. 따라서 빨간점 기준으로 기울기 벡터는 다음과 같습니다.

이 기울기 벡터의 크기와 방향(각도)은 다음과 같이 계산합니다. 

크기는 53.74이고 방향(각도)은 45도입니다. 크기와 방향을 벡터로 표시하면 다음과 같습니다.

픽셀(Pixels), 셀(Cells), 블록(Blocks)

HOG 디스크립터에 관해 살펴보기 전에 픽셀, 셀, 블록, 윈도의 개념을 알아야 합니다. 간단합니다. 픽셀은 말 그대로 영상 내 하나의 픽셀 값을 의미합니다. 이 픽셀들을 몇 개 묶어서 소그룹으로 만든 것이 셀입니다. 다시 셀을 몇 개 묶어서 그룹으로 만든 것이 블록입니다. 다시 말하면 하나의 블록 안에 셀 여러 개가 있고, 하나의 셀 안에 픽셀 여러 개가 있습니다.

HOG(Histogram of Oriented Gradient)

HOG는 보행자 검출을 위해 만들어진 특징 디스크립터입니다. HOG는 이미지 경계의 기울기 벡터 크기(magnitude)와 방향(direction)을 히스토그램으로 나타내 계산합니다. 

HOG 디스크립터를 만들기 위해서는 영상 속에서 검출하고자 하는 영역을 잘라내야 합니다. 이렇게 잘라낸 영역을 윈도(window)라고 합니다. 일반적으로 보행자를 검출하기 위해서는 윈도 사이즈를 64 x 128 픽셀 크기로 합니다. 해당 윈도에 소벨 필터를 적용해 경계의 기울기 gx, gy를 구하고, 기울기의 방향(direction)과 크기(magnitude)를 계산합니다. 이 과정을 코드로 나타내면 다음과 같습니다.

img = cv2.imread('img.png')
img = np.float(img)

gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
magnitude, angle = cv2.cartToPolar(gx, gy)

아래 예시에서는 윈도 사이즈가 8 x 16 cells 입니다. 하나의 셀(cell)은 5 x 5 픽셀(pixels)로 구성되어 있습니다. 그러므로 윈도 사이즈는 (8x5) x (16x5) = 40 x 80 pixels입니다. 하나의 블록(block)은 2 x 2 셀(cells)로 구성되어 있습니다. 위에서 설명했다시피 픽셀이 모여서 셀이 되고, 셀이 모여서 블록이 됩니다.

그리고 각 픽셀을 기준으로 기울기 벡터를 구해야 합니다. 아래 그림에서 화살표로 표시된 것이 기울기 벡터입니다. 기울기 벡터의 크기와 방향을 히스토그램으로 나타낸 것도 볼 수 있습니다. 기울기 벡터의 방향을 계급(bin)으로 하고 크기를 값으로 누적한 히스토그램입니다. 계급(bin)은 180도를 20도씩 총 9개의 구간으로 나누어 사용합니다. 360도가 아닌 180도로 하는 이유는 기울기의 양수와 음수가 같은 방향을 나타내기 때문입니다. 이때 윈도 전체를 하나의 히스토그램으로 계산하는 것이 아니라 하나의 셀을 기준으로 히스토그램을 계산합니다. 

출처: https://www.researchgate.net/figure/Histogram-of-Oriented-Gradient-12_fig3_313369595

비슷한 이미지는 서로 비슷한 히스토그램을 그립니다. 서로 다른 이미지는 다른 모양의 히스토그램을 그리죠. 이렇게 히스토그램의 유사성을 바탕으로 비슷한 이미지를 검출합니다.

보행자 검출에서는 윈도 사이즈를 64 x 128 픽셀(pixels), 셀의 크기를 일반적으로 8 x 8 픽셀(pixels)로 정합니다.

히스토그램 계산을 마치면 정규화(normalization) 과정을 거쳐야 합니다. 경계 값 기울기는 밝기에 민감합니다. 민감성을 없애주려고 정규화를 하는 겁니다. 정규화를 적용하려면 윈도를 특정 크기로 나누어야 합니다. 이렇게 나누는 영역을 블록(block)이라고 합니다. 블록 크기는 일반적으로 셀 크기의 2배입니다.  8 x 8 픽셀의 셀을 가지고 있다면 블록은 16 x 16 픽셀로 정합니다. 각 블록은 전체 윈도를 순회하면서 정규화를 합니다. 이때 겹치는 부분이 발생하는데 이 부분을 블록 스트라이드(blcok stride)라고 합니다. 64 x 128 픽셀의 윈도에서 16 x 16 픽셀의 블록이 '8 x 8 픽셀의 블록 스트라이드'를 가졌다면, 정규화를 계산하는 횟수는 총 7 x 15= 105번입니다. {64/(16-8) - 1} x {128/(16-8) - 1} = 105이기 때문입니다.

종합하면, 원본 영상에서 보행자에 해당하는 부분을 자른 영역을 윈도(window)라고 합니다. 사람을 검출할 때는 보통 64 x 128 크기로 윈도를 정합니다. 이렇게 자른 윈도에서 각 픽셀에 대해 기울기 벡터를 구해줍니다. 그리고 특정 영역을 중심으로 기울기 벡터 크기와 방향에 대한 히스토그램을 구해야 합니다. 히스토그램을 계산하기 위한 영역을 셀(cell)이라고 합니다. 셀은 보통 8 x 8 크기로 정합니다. 히스토그램을 계산한 뒤 정규화해야 합니다. 정규화하려면 윈도를 다시 잘게 나누어야 하는데, 이를 블록(block)이라고 합니다. 블록 크기는 대게 셀 크기의 2배로 정합니다. 블록은 전체 윈도를 순회하면서 정규화하는데, 이때 블록이 한번에 이동하는 거리를 블록 스트라이드(block stride)라고 합니다.

HOG 디스크립터를 활용한 보행자 인식

OpenCV에서는 HOG 디스크립터를 계산하기 위해 아래 함수를 제공합니다.

  • descriptor = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins): HOG 디스크립터 추출기 생성
    winSize: 윈도 크기, HOG 추출 영역
    blockSize: 블록 크기, 정규화 영역
    blockStride: 정규화 블록 겹침 크기
    cellSize: 셀 크기, 히스토그램 계산 영역
    nbins: 히스토그램 계급 수
    descriptor: HOG 특징 디스크립터 추출기
  • hog = descriptor.compute(img): HOG 계산
    img: 계산 대상 이미지
    hog: HOG 특징 디스크립터 결과

OpenCV는 보행자 인식을 위한 사전 훈련 API를 제공합니다. cv2.HOGDescriptor는 HOG 디스크립터를 계산해 줄 수도 있고, 미리 훈련된 SVM 모델(pretrained SVM model)을 전달받아 보행자를 추출해줄 수도 있습니다.

  • scvmdetector = cv2.HOGDescriptor_getDefaultPeopleDetector(): 64 x 128 윈도 크기로 훈련된 모델
  • scvmdetector = cv2.HOGDescriptor_getDaimlerPeopleDetector(): 48 x 96 윈도 크기로 훈련된 모델
  • descriptor = cv2.HOGDescriptor(winSize, blockSize, blockStride, cellSize, nbins): HOG 생성
  • descriptor.setSVMDetector(svmdetector): 훈련된 SVM 모델 설정
  • rects, weights = descriptor.detectMultiScale(img): 객체 검출
    img: 검출하고자 하는 이미지
    rects: 검출된 결과 영역 좌표 N x 4 (x, y, w, h)
    weights: 검출된 결과 계수 N x 1

HOG 디스크립터를 이용해 보행자를 인식해보겠습니다.

# HOG-SVM 보행자 검출 (svm_hog_pedestrian.py)

import cv2

# default 디덱터를 위한 HOG 객체 생성 및 설정--- ①
hogdef = cv2.HOGDescriptor()
hogdef.setSVMDetector(cv2.HOGDescriptor_getDefaultPeopleDetector())

# dailer 디덱터를 위한 HOG 객체 생성 및 설정--- ②
hogdaim  = cv2.HOGDescriptor((48,96), (16,16), (8,8), (8,8), 9)
hogdaim.setSVMDetector(cv2.HOGDescriptor_getDaimlerPeopleDetector())

cap = cv2.VideoCapture('../img/walking.avi')
mode = True  # 모드 변환을 위한 플래그 변수 
print('Toggle Space-bar to change mode.')
while cap.isOpened():
    ret, img = cap.read()
    if ret :
        if mode:
            # default 디텍터로 보행자 검출 --- ③
            found, _ = hogdef.detectMultiScale(img)
            for (x,y,w,h) in found:
                cv2.rectangle(img, (x,y), (x+w, y+h), (0,255,255))
        else:
            # daimler 디텍터로 보행자 검출 --- ④
            found, _ = hogdaim.detectMultiScale(img)
            for (x,y,w,h) in found:
                cv2.rectangle(img, (x,y), (x+w, y+h), (0,255,0))
        cv2.putText(img, 'Detector:%s'%('Default' if mode else 'Daimler'), \
                        (10,50 ), cv2.FONT_HERSHEY_DUPLEX,1, (0,255,0),1)
        cv2.imshow('frame', img)
        key = cv2.waitKey(1) 
        if key == 27:
            break
        elif key == ord(' '):
            mode = not mode
    else:
        break
cap.release()
cv2.destroyAllWindows()

Default 보행자 검출기와 Daimler 보행자 검출기를 이용해 보행자를 검출해봤습니다. 스페이스 바를 누르면 검출기를 바꿀 수 있습니다. 노란색 박스로 보행자를 인식한 게 Default 보행자 검출기이고, 초록색 박스로 보행자를 인식한 게 Daimler 보행자 검출기입니다. Default 보행자 검출기는 불필요한 검출이 적은 대신 멀리 있는 작은 보행자는 검출하지 못합니다. 반면 Daimler 보행자 검출기는 멀리 있는 작은 보행자도 검출하지만 삼각대나 건물 그림자도 보행자로 인식하네요. 둘 다 장단점이 있습니다.

Comments