728x90
반응형



People Detection with HOG(Histogram of Oriented Gradients) and SVM(Support Vector Machines)


[그림 1] HOG+SVM을 이용한 보행자 검출[참고자료 1]


보통 SVM을 이용하여 HOG 기반 객체 추적을 실행하려면 실행하고자 하는 데이터에 맞게 학습된 SVM 분류기가 필요하고, HOG feature가 필요하다. 기본 내장되어있는 opencv 로도 해결이 가능하지만 좀 더 빠른 성능을 위해서라면 CUDA 라이브러리를 이용한 GPU 프로그래밍을 하는 것이 좋다. 


그런데 CUDA 라이브러리는 아직 visual studio 2015 를 지원하지 않을 뿐만 아니라 opencv 만 쓰다가 다른걸 써보려니 적응이 안되고(?) 환경 설정에 예민한 단점이 있다... 시도는 해보았으나 너무 어려워서 잠시 중단한 상태이고, opencv 만으로 해결해보고자 하였다. 




HOG Feature


일반적으로 잘못알고 있는 생각 중 하나가 HOG(Histogram of Oriented Gradients)를 알고리즘으로 분류한다는 것이다. 이는 알고리즘이 아니라 특징 즉, Feature 중 하나이다. 샘플 데이터인 학습 시킬 데이터, 즉 찾아내고자 하는 특징을 담은 데이터(Positive, Good data)와 찾고싶지 않은 데이터(Negative, Bad data)를 SVM 등과 같은 학습 분류기로 학습을 시켜 하나의 특징을 만들어 내는 것이다. 이와 같은 특징 중 하나가 바로 HOG Feature 라고 할 수 있다. 또한 SIFT 객체 검출기와 비교하여 지역적인 특징들 보다는 전역적인 특징을 사용한다는 것이 특징이다. 


이는 이미지의 지역적인 Gradient(기울기 정보)를 해당영상의 특징으로 사용하는 방법이다. 여기서 Cell 이라던지 Block에 대한 개념은 숙지해 두고 있어야 한다. 간단히 말해서, 영상들은 기본적으로 픽셀들의 집합이고 이러한 픽셀들을 몇 개 묶어서 소그룹을 만들면 Cell이 된다. 이 Cell 들을 몇 개 묶어서 그룹을 만들게 되면 그게 Block이 되는 것이다.


[그림 2] HOG Feature 


HOG Feature를 계산하는 순서는 다음과 같다. 



1. 영상에서 Gradient를 구함


Gradient 즉, 영상에서 기울기를 구하기 위해서는 Edge를 먼저 계산해야 한다. 소벨 마스크, 로버츠 마스크, 프리윗 마스크 개념을 등을 숙지한 상태에서 이해를 해야한다. 실제 엣지 검출에서는 소벨 마스크를 제일 많이 사용한다고 한다. 어떤 마스크를 쓸 지는 영상에 따라 판단하여 써야 할 것 같다.


[그림 3] HOG, Egde 검출

 

엣지를 검출하기 위해서는 가장 간단한 1D Kernel을 사용한다. 쉽게 말해서 양 옆 픽셀 값을 하나씩 빼준다는 이야기 이다. X축 방향 엣지와 Y축 방향 엣지를 각각 계산했다면 두 값을 계산하여 Orientation(방향)을 계산한다. 삼각함수 중에서 arctan 함수를 이용하면 두 엣지에 의해 발생하는 Orientation을 구할 수 있다. 


그 다음 적당히 Quantization 을 해주어야 한다. 보통은 40도로 나뉘는 9개의 bin을 사용하게 되며, 15개의 bin을 사용해도 된다. 



2. 구해진 Gradient를 가지고 Local Histogram을 생성하여 이를 이어 붙이면 1D vector 생성


이렇게 구해진 기울기를 가지고 Orientation map을 만들게 되면 이를 Histogram으로 생성해 이어 붙이면 HOG feature가 되는 것이다. 히스토그램은 셀 단위로 만들어지게 되고, 이어 붙이는 방식은 블록 단위의 움직임에 의해 결정된다. 1번 과정에서 몇 개의 bin으로 나누었는지가 히스토그램의 Index가 되는 것이다. 




SVM(Support Vector machines)


이제 HOG Feature에 대해 이해를 하였다면 OpenCV를 이용한 SVM에 대해 알아보아야 한다. SVM과 같은 학습 분류기는 샘플데이터가 많을 수록 가장 좋은 성능을 보이는 것은 당연한 사실이다. 이는 간단히 말해서 데이터를 분리하는 초평면 중에서 데이터들과 거리가 가장 먼 초평면을 선택하여 분리하는 방법이다. 최대 마진 분류기라고도 한다. 또한 초평면에 가장 가까운 곳에 위치한 데이터를 Support Vector라고도 한다. 


조금 더 자세한 내용은 이전 포스팅을 참고하길 바란다. http://eehoeskrap.tistory.com/45


이와 같은 학습 분류기를 통하여 "사람이다" 또는 "사람이 아니다" 라는 것을 판별 할 수 있게 해준다. 





Object Detect


OpenCV에는 객체를 검출 할 수 있는 클래스들이 존재한다. gpu::HOGDescriptor 구조체는 HOG 기반 검출을 할 때 가장 많이 쓰이는 방법이다. 구조체는 아래와 같이 선언되어 있다. 또한 HOGDescriptor와 아래와 같은 함수들을 가장 많이 사용하여 검출을 한다. 


HOGDescriptor(win_size, block_size, block_stride, cell_size, nbins, win_sigma, threshold_L2hys, gamma_correction, nlevels)


- win_size : 탐지 윈도우 사이즈

- block_size : 픽셀에서의 블록 사이즈 , 현재 (16,16) 만 지원됨

- block_stride : 블록의 보폭, 셀 크기의 배수여야 함  

- cell_size : 셀 사이즈, 현재 (8, 8)만 지원됨

- nbins : bin의 갯수, 현재 9 bin 만 지원됨 

- win_sigma : 가우시안 스무딩 윈도우 파라미터, DEFAULT_WIN_SIGMA

- threshold_L2hys :  ????? L2-Hys normalization method shrinkage. 

- gamma_correction : 감마 보정 전처리가 필요한지, True, false

- nlevels : 탐지 창이 최대로 증가함 



setSVMDetector(detector)

- detector : 선형 SVM 분류기 



detect(img, found_locations, hit_threshold, win_stride, padding)


- img : 원본 이미지, CV_8UC1 and CV_8UC4 타입 만 지원

- found_locations : 감지된 객체 경계의 왼쪽 상단 코너점 

- hit_threshold : feature와 SVM 평면 간 사이에 대한 임계값, 보통 0, 자유계수, 생략됨 

- wind_stride : 윈도우의 폭, 블록 폭의 배수여야 함 

- padding : CPU 인터페이스 호환성을 유지하기 위한 파라미터, (0, 0)이어야 함 



detectMultiScale(img, found_locations, hit_threshold, win_stride, padding, scale0, group_threshold) 


[참고자료 8] 에 더 자세한 설명이 있음, 무엇보다 직접 실행해 보는 것이 중요 


- img : 원본 이미지, CV_8UC1 and CV_8UC4 타입 만 지원

- found_locations : 탐지된 객체 경계

- hit_threshold : feature와 SVM 평면 간 사이에 대한 임계값, 보통 0, 자유계수, 생략됨 

- win_stride : 윈도우의 폭, 블록 폭의 배수여야 함, 이를 작게 할 수록 탐지 시간이 늘어나며, 반대로 크게 하면 탐색 윈도우 숫자는 더 작아지고, 이는 탐지속도를 빠르게 해주지만 탐지를 못할 확률이 높아짐 

- padding : CPU 인터페이스 호환성을 유지하기 위한 파라미터, (0, 0)이어야 함

- scale0 : 작게 할수록 이미지 레이어의 계수를 증가시키고 계산 시간 증가(피라미드 형식)

- group_threshold : 비슷한 임계 값을 조절하는 계수, 감지 되면 일부 객체는 많은 사각형으로 덮일 수 있음, 0은 그룹화를 수행하지 않음



getDescriptors(img, win_stride, descriptors, descr_format)


- 나머지 위 설명 참조

- descriptors : 2D 배열 

- descr_format : DESCR_FORMAT_ROW_BY_ROW(행을 주로 다룸), DESCR_FORMAT_COL_BY_COL(열을 주로 다룸)



나머지 내용은 아래와 OpenCV 레퍼런스 참조 [참고자료 5]


struct CV_EXPORTS HOGDescriptor
{
    enum { DEFAULT_WIN_SIGMA = -1 };
    enum { DEFAULT_NLEVELS = 64 };
    enum { DESCR_FORMAT_ROW_BY_ROW, DESCR_FORMAT_COL_BY_COL };

    HOGDescriptor(Size win_size=Size(64, 128), Size block_size=Size(16, 16),
                  Size block_stride=Size(8, 8), Size cell_size=Size(8, 8),
                  int nbins=9, double win_sigma=DEFAULT_WIN_SIGMA,
                  double threshold_L2hys=0.2, bool gamma_correction=true,
                  int nlevels=DEFAULT_NLEVELS);

    size_t getDescriptorSize() const;
    size_t getBlockHistogramSize() const;

    void setSVMDetector(const vector<float>& detector);

    static vector<float> getDefaultPeopleDetector();
    static vector<float> getPeopleDetector48x96();
    static vector<float> getPeopleDetector64x128();

    void detect(const GpuMat& img, vector<Point>& found_locations,
                double hit_threshold=0, Size win_stride=Size(),
                Size padding=Size());

    void detectMultiScale(const GpuMat& img, vector<Rect>& found_locations,
                          double hit_threshold=0, Size win_stride=Size(),
                          Size padding=Size(), double scale0=1.05,
                          int group_threshold=2);

    void getDescriptors(const GpuMat& img, Size win_stride,
                        GpuMat& descriptors,
                        int descr_format=DESCR_FORMAT_COL_BY_COL);

    Size win_size;
    Size block_size;
    Size block_stride;
    Size cell_size;
    int nbins;
    double win_sigma;
    double threshold_L2hys;
    bool gamma_correction;
    int nlevels;

private:
    // Hidden
}



이와 같은 구조체와 함수를 사용하여 객체 추적을 해보았다. 



[그림 4] HOG+SVM을 이용한 객체 추적 



hog.detectMultiScale(img, found, 0, Size(8, 8), Size(32, 32), 1.05, 2);


처음에는 아래와 같이 파라미터 값을 주었을 때 사각형 창이 아무것도 나타나지 않아서 찾아보니 OpenCV에 내장된 SVM에서 학습시킨 데이터들의 사이즈와 내가 테스트하고자하는 영상의 사이즈가 맞지않아서 생기는 문제였다. 이를 개선하려면 학습시킨 데이터들의 사이즈와 맞추거나, 나에게 맞는 학습 데이터들(Positive, Negative)을 모아 학습을 시킨 뒤 실행을 해야 한다. 


스텍오버플로우 [참고자료 6]에서 찾아 낼 수 있는 사실이었다.  


1. 먼저 Training Image (Positive Samples, Negative Samples) 을 준비한다.

2. 학습데이터의 HOG feature을 검출하고, 이를 사용하여 SVM Classifier를 사용할 수 있다(opencv)

3. HOGDescriptor::setSVMDetector() 에서 SVM 분류기의 계수를 이용한다. 




Cascade Classification


HOG feature와 짝꿍(?)으로 쓰이고 있는 알고리즘이다. 다시 말하지만 HOG는 특징 중 하나이고, Cascade는 알고리즘 중 하나이다. HOG 특징과도 쓰이지만 Haar 특징과 주로 쓰인다. 분류기는 약 백개의 각각의 객체(자동차, 사람, 사람얼굴 등) 샘플 데이터에 의해 학습된다. 또한 샘플데이터는 (예: 20x20)의 같은 데이터여야 하며, 긍정적인 사례(Positive data), 부정적인 사례(Negative)들로 구성되어 있어야 한다. 이는 아까 SVM 에서 했던 이야기와 일맥 상통한다.


[그림 5] Cascade Algorithm


Cascade(계단식) 이라는 것은 몇몇의 간단한 Classifiers(분류자) 로 구성되어있는 결과적인(resultant) 분류자이다. 이는 어떤 단계에서 후보자로 거부되거나 모든 단계에서 통과 될 때까지 관심영역에 적용된다. 즉 이는 부스트 알고리즘이며 아래 그림과 같이 입력 영상에서 후보자로 거부된 것은 Rejected sub-windows로 빠지고, 후보자로 걸러진 것은 G1, G2, G3 등을 지나 관심 영역인 Object Position에 적용되는 것이다. 






참고자료 1 : https://www.youtube.com/watch?v=b_DSTuxdGDw

참고자료 2 : http://jangjy.tistory.com/163

참고자료 3 : http://web.mit.edu/vondrick/ihog/

참고자료 4 : http://blog.daum.net/pg365/210

참고자료 5 : http://docs.opencv.org/2.4/modules/gpu/doc/object_detection.html

참고자료 6 : http://stackoverflow.com/questions/10769519/svm-classifier-based-on-hog-features-for-object-detection-in-opencv

참고자료 7 : http://cafe.naver.com/opencv

참고자료 8 : http://hamait.tistory.com/509




그 외 찾은 것들 (참고하면 좋을 것들...)


YOLO : http://pjreddie.com/darknet/yolov1/


파이썬 코드 (알고리즘) : 

http://www.pyimagesearch.com/2015/11/09/pedestrian-detection-opencv/


Using SVM with HOG object detector in OpenCV : 

http://blog-meerkatcv.rhcloud.com/2015/10/26/using-svm-with-hog-object-detector-in-opencv/

728x90
반응형