1. 程式人生 > >第十八節、基於傳統圖像處理的目標檢測與識別(HOG+SVM附代碼)

第十八節、基於傳統圖像處理的目標檢測與識別(HOG+SVM附代碼)

當我 陰影 .fig 來源 end 映射 形狀 itl eee

其實在深度學習分類中我們已經介紹了目標檢測和目標識別的概念、為了照顧一些沒有學過深度學習的童鞋,這裏我重新說明一次:目標檢測是用來確定圖像上某個區域是否有我們要識別的對象,目標識別是用來判斷圖片上這個對象是什麽。識別通常只處理已經檢測到對象的區域,例如,人們總是會使在已有的人臉圖像的區域去識別人臉。

傳統的目標檢測方法與識別不同於深度學習方法,後者主要利用神經網絡來實現分類和回歸問題。在這裏我們主要介紹如何利用OpecnCv來實現傳統目標檢測和識別,在計算機視覺中有很多目標檢測和識別的技術,這裏我們主要介紹下面幾塊內容:

  • 方向梯度直方圖HOG(Histogram of Oriented Gradient);
  • 圖像金字塔;
  • 滑動窗口;

上面這三塊內容其實後面兩塊我們之前都已經介紹過,不過這裏會更加詳細介紹,下面我們從HOG說起。

一 HOG

HOG特征是一種在計算機視覺和圖像處理中用來進行物體檢測的特征描述子,是與SIFT、SURF、ORB屬於同一類型的描述符。HOG不是基於顏色值而是基於梯度來計算直方圖的,它通過計算和統計圖像局部區域的梯度方向直方圖來構建特征。HOG特征結合SVM分類器已經被廣泛應用到圖像識別中,尤其在行人檢測中獲得了極大的成功。

1、主要思想

此方法的基本觀點是:局部目標的外表和形狀可以被局部梯度或邊緣方向的分布很好的描述,即使我們不知道對應的梯度和邊緣的位置。(本質:梯度的統計信息,梯度主要存在於邊緣的地方)

2、實施方法

首先將圖像分成很多小的連通區域,我們把它叫做細胞單元,然後采集細胞單元中各像素點的梯度和邊緣方向,然後在每個細胞單元中累加出一個一維的梯度方向直方圖。

為了對光照和陰影有更好的不變性,需要對直方圖進行對比度歸一化,這可以通過把這些直方圖在圖像的更大的範圍內(我們把它叫做區間或者block)進行對比度歸一化。首先我們計算出各直方圖在這個區間中的密度,然後根據這個密度對區間中的各個細胞單元做歸一化。我們把歸一化的塊描述符叫作HOG描述子。

3、目標檢測

將檢測窗口中的所有塊的HOG描述子組合起來就形成了最終的特征向量,然後使用SVM分類器進行行人檢測。下圖描述了特征提取和目標檢測流程。檢測窗口劃分為重疊的塊,對這些塊計算HOG描述子,形成的特征向量放到線性SVM中進行目標/非目標的二分類。檢測窗口在整個圖像的所有位置和尺度上進行掃描,並對輸出的金字塔進行非極大值抑制來檢測目標。(檢測窗口的大小一般為$128\times{64}$)

技術分享圖片

二 算法的具體實現

1、圖像標準化(調節圖像的對比度)

為了減少光照因素的影響,降低圖像局部的陰影和光照變化所造成的影響,我們首先采用Gamma校正法對輸入圖像的顏色空間進行標準化(或者說是歸一化)。

所謂的Gamma校正可以理解為提高圖像中偏暗或者偏亮部分的圖像對比效果,能夠有效地降低圖像局部的陰影和光照變化。更詳細的內容可以點擊這裏查看圖像處理之gamma校正。

Gamma校正公式為:

$$f(I)=I^\gamma$$

其中$I$為圖像像素值,$\gamma$為Gamma校正系數。$\gamma$系數設定影響著圖像的調整效果,結合下圖,我們來看一下Gamma校正的作用:

技術分享圖片

$\gamma<1$在低灰度值區域內,動態範圍變大,圖像對比度增加強;在高灰度值區域,動態範圍變小,圖像對比度降低,同時,圖像的整體灰度值變大;

$\gamma>1$在低灰度值區域內,動態範圍變小,圖像對比度降低;在高灰度值區域,動態範圍變大,圖像對比度提高同時,圖像的整體灰度值變小;

技術分享圖片

左邊的圖像為原圖,中間圖像的$\gamma=\frac{1}{2.2}$,右圖$\gamma=2.2$。

作者在他的博士論文裏有提到,對於涉及大量的類內顏色變化,如貓,狗和馬等動物,沒標準化的RGB圖效果更好,而牛,羊的圖做gamma顏色校正後效果更好。是否用gamma校正得分析具體的訓練集情況。

2、圖像平滑(具體視情況而定)

對於灰度圖像,一般為了去除噪點,所以會先利用高斯函數進行平滑:高斯函數在不同的平滑尺度下對灰度圖像進行平滑操作。Dalal等實驗表明moving from σ=0 to σ=2 reduces the recall rate from 89% to 80% at 10?4 FPPW,即不做高斯平滑人體檢測效果最佳,使得漏檢率縮小了約一倍。不做平滑操作,可能原因:HOG特征是基於邊緣的,平滑會降低邊緣信息的對比度,從而減少圖像中的有用信息。

3、邊緣方向計算

計算圖像每個像素點的梯度、包括方向和大小:

$$G_x(x,y)=I(x+1,y)-I(x-1,y)$$

$$G_y(x,y)=I(x,y+1)-I(x,y-1)$$

上式中$G_x(x,y)、G_y(x,y)$分別表示輸入圖像在像素點$(x,y)$處的水平方向梯度和垂直方向梯度,像素點在$(x,y)$的梯度幅值和梯度方向分別為:

$$G(x,y)=\sqrt{G_x(x,y)^2+G_y(x,y)^2}$$

$$\alpha=arctan\frac{G_y(x,y)}{G_x(x,y)}$$

4、直方圖計算

將圖像劃分成小的細胞單元(細胞單元可以是矩形的或者環形的),比如大小為$8\times{8}$,然後統計每一個細胞單元的梯度直方圖,即可以得到一個細胞單元的描述符,將幾個細胞單元組成一個block,例如$2\times{2}$個細胞單元組成一個block,將一個block內每個細胞單元的描述符串聯起來即可以得到一個block的HOG描述符。

在說到統計一個細胞單元的梯度直方圖時,我們一般考慮采用9個bin的直方圖來統計這$8\times{8}$個像素的梯度信息,即將cell的梯度方向0~180°(或0~360°,即考慮了正負)分成9個方向塊,如下圖所示:

技術分享圖片

如果cell中某一個像素的梯度方向是$20~40°$,直方圖第2個bin的計數就要加1,這樣對cell中的每一個像素用梯度方向在直方圖中進行加權投影(權值大小等於梯度幅值),將其映射到對應的角度範圍塊內,就可以得到這個cell的梯度方向直方圖了,就是該cell對應的9維特征向量。對於梯度方向位於相鄰bin的中心之間(如20°、40°等)需要進行方向和位置上的雙線性插值。

采用梯度幅值量級本身得到的檢測效果最佳,而使用二值的邊緣權值表示會嚴重降低效果。采用梯度幅值作為權重,可以使那些比較明顯的邊緣的方向信息對特征表達影響增大,這樣比較合理,因為HOG特征主要就是依靠這些邊緣紋理。

根據Dalal等人的實驗,在行人目標檢測中,在無符號方向角度範圍並將其平均分成9份(bins)能取得最好的效果,當bin的數目繼續增大效果改變不明顯,故一般在人體目標檢測中使用bin數目為9範圍0~180°的度量方式。

5、對block歸一化

由於局部光照的變化,以及前景背景對比度的變化,使得梯度強度的變化範圍非常大,這就需要對梯度做局部對比度歸一化。歸一化能夠進一步對光照、陰影、邊緣進行壓縮,使得特征向量對光照、陰影和邊緣變化具有魯棒性。

具體的做法:將細胞單元組成更大的空間塊(block),然後針對每個塊進行對比度歸一化。最終的描述子是檢測窗口內所有塊內的細胞單元的直方圖構成的向量。事實上,塊之間是有重疊的,也就是說,每個細胞單元的直方圖都會被多次用於最終的描述子的計算。塊之間的重疊看起來有冗余,但可以顯著的提升性能 。

技術分享圖片

通常使用的HOG結構大致有三種:矩形HOG(簡稱為R-HOG),圓形HOG和中心環繞HOG。它們的單位都是Block(即塊)。Dalal的試驗證明矩形HOG和圓形HOG的檢測效果基本一致,而環繞形HOG效果相對差一些。

技術分享圖片

如上圖,一個塊由$2\times{2}$個cell組成,每一個cell包含$8\times{8}$個像素點,每個cell提取9個直方圖通道,因此一個塊的特征向量長度為$2\times{2}\times{9}$。

假設$v$是未經歸一化的特征向量。 $\|v\|_k$是$v$的$k$範數,$k=1,2$,是一個很小的常數,對塊的特征向量進行歸一化,一般有以下四種方法:

  • $L_2-norm$:$v←\frac{v}{\sqrt{\|v\|_2^2+\xi^2}}$($\xi$是一個很小的數,主要是為了防止分母為0);
  • $L_2-Hys$:先計算$L_2$範數,然後限制$v$的最大值為0.2,再進行歸一化;
  • $L_1-norm$:$v←\frac{v}{|v\|_1+\xi}$;
  • $L_1-sqrt$:$v←\sqrt{\frac{v}{\|v\|_1+\xi}}$;

在人體檢測系統中進行HOG計算時一般使用$L_2-norm$,Dalal的文章也驗證了對於人體檢測系統使用$L_2-norm$的時候效果最好。

6、樣本HOG特征提

最後一步就是對一個樣本中所有的塊進行HOG特征的手機,並將它們結合成最終的特征向量送入分類器。

那麽一個樣本可以提取多少個特征呢?之前我們已經說過HOG特征的提取過程:

  • 首先把樣本圖片分割為若幹個像素的單元,然後把梯度方向劃分為9個區間,在每個單元裏面對所有像素的梯度方向在各個方向區間進行直方圖統計,得到一個9維的特征向量;
  • 每相鄰4個單元構成一個塊,把一個塊內的特征向量串聯起來得到一個36維的特征向量;
  • 用塊對樣本圖像進行掃描,掃描步長為一個單元的大小,最後將所有的塊的特征串聯起來,就得到一個樣本的特征向量;

例如:對於$64\times{128}$的輸入圖片,每個塊由$x\times{2}$個cell組成,每個cell由$8\times{8}$個像素點組成,每個cell提取9個bin大小的直方圖,以1個cell大小為步長,那麽水平方向有7個掃描窗口,垂直方向有5個掃描窗口,也就是說,一共有$7*15*2*2*9=3780$個特征。

7、行人檢測HOG+SVM

這裏我們介紹一下Dalal等人的訓練方法:

  1. 提取正負樣本的HOG特征;
  2. 用正負樣本訓練一個初始的分類器,然後由分類器生產檢測器;
  3. 然後用初始分類器在負樣本原圖上進行行人檢測,檢測出來的矩形區域自然都是分類錯誤的負樣本,這就是所謂的難例(hard examples);
  4. 提取難例的HOG特征並結合第一步中的特征,重新訓練,生成最終的檢測器 ;

這種二次訓練的處理過程顯著提高了每個檢測器的表現,一般可以使得每個窗口的誤報率(FPPW False Positives Per Window)下降5%。

三 手動實現HOG特征

雖然opencv已經實現了HOG算法,但是手動實現的目的是為了加深我們對HOG的理解,本代碼參考了博客80行Python實現-HOG梯度特征提取並做了一些調整:

代碼主要包括以下步驟:

  1. 圖像灰度化,歸一化處理;
  2. 首先計算圖像每一個像素點的梯度幅值和角度;
  3. 計算輸入圖像的每個cell單元的梯度直方圖(註意,我們在實現梯度直方圖的時候,使用到的是雙線性插值,這和上面介紹的理論略微有區別),形成每個cell的descriptor,比如輸入圖像為$128\times{64}$ 可以得到$16\times{8}$個cell,每個cell由9個bin組成;
  4. 將$2\times{2}$個cell組成一個block,一個block內所有cell的特征串聯起來得到該block的HOG特征descriptor,並進行歸一化處理,將圖像內所有block的HOG特征descriptor串聯起來得到該圖像的HOG特征descriptor,這就是最終分類的特征向量;
# -*- coding: utf-8 -*-
"""
Created on Mon Sep 24 18:23:04 2018

@author: zy
"""

#代碼來源GitHub:https://github.com/PENGZhaoqing/Hog-feature
#https://blog.csdn.net/ppp8300885/article/details/71078555
#https://www.leiphone.com/news/201708/ZKsGd2JRKr766wEd.html

import cv2
import numpy as np
import math
import matplotlib.pyplot as plt


class Hog_descriptor():
    ‘‘‘
    HOG描述符的實現
    ‘‘‘
    def __init__(self, img, cell_size=8, bin_size=9):
        ‘‘‘
        構造函數
            默認參數,一個block由2x2個cell組成,步長為1個cell大小 
        args:
            img:輸入圖像(更準確的說是檢測窗口),這裏要求為灰度圖像  圖像大小一般為128x64 即是輸入圖像上的一小塊裁切區域
            cell_size:細胞單元的大小 如8,表示8x8個像素
            bin_size:直方圖的bin個數
        ‘‘‘
        self.img = img
        ‘‘‘
        采用Gamma校正法對輸入圖像進行顏色空間的標準化(歸一化),目的是調節圖像的對比度,降低圖像局部
        的陰影和光照變化所造成的影響,同時可以抑制噪音。采用的gamma值為0.5。 f(I)=I^γ
        ‘‘‘
        self.img = np.sqrt(img*1.0 / float(np.max(img)))
        self.img = self.img * 255
        #print(‘img‘,self.img.dtype)   #float64
        #參數初始化
        self.cell_size = cell_size
        self.bin_size = bin_size
        self.angle_unit = 180 / self.bin_size  #這裏采用180°
        assert type(self.bin_size) == int, "bin_size should be integer,"
        assert type(self.cell_size) == int, "cell_size should be integer,"
        assert 180 % self.bin_size == 0, "bin_size should be divisible by 180"

    def extract(self):
        ‘‘‘
        計算圖像的HOG描述符,以及HOG-image特征圖
        ‘‘‘
        height, width = self.img.shape
        ‘‘‘
        1、計算圖像每一個像素點的梯度幅值和角度
        ‘‘‘
        gradient_magnitude, gradient_angle = self.global_gradient()
        gradient_magnitude = abs(gradient_magnitude)
        ‘‘‘
        2、計算輸入圖像的每個cell單元的梯度直方圖,形成每個cell的descriptor 比如輸入圖像為128x64 可以得到16x8個cell,每個cell由9個bin組成
        ‘‘‘
        cell_gradient_vector = np.zeros((int(height / self.cell_size), int(width / self.cell_size), self.bin_size))
        #遍歷每一行、每一列
        for i in range(cell_gradient_vector.shape[0]):
            for j in range(cell_gradient_vector.shape[1]):
                #計算第[i][j]個cell的特征向量
                cell_magnitude = gradient_magnitude[i * self.cell_size:(i + 1) * self.cell_size,
                                 j * self.cell_size:(j + 1) * self.cell_size]
                cell_angle = gradient_angle[i * self.cell_size:(i + 1) * self.cell_size,
                             j * self.cell_size:(j + 1) * self.cell_size]
                cell_gradient_vector[i][j] = self.cell_gradient(cell_magnitude, cell_angle)

        #將得到的每個cell的梯度方向直方圖繪出,得到特征圖
        hog_image = self.render_gradient(np.zeros([height, width]), cell_gradient_vector)
        
        ‘‘‘
        3、將2x2個cell組成一個block,一個block內所有cell的特征串聯起來得到該block的HOG特征descriptor
           將圖像image內所有block的HOG特征descriptor串聯起來得到該image(檢測目標)的HOG特征descriptor,
           這就是最終分類的特征向量
        ‘‘‘
        hog_vector = []
        #默認步長為一個cell大小,一個block由2x2個cell組成,遍歷每一個block
        for i in range(cell_gradient_vector.shape[0] - 1):
            for j in range(cell_gradient_vector.shape[1] - 1):
                #提取第[i][j]個block的特征向量
                block_vector = []
                block_vector.extend(cell_gradient_vector[i][j])
                block_vector.extend(cell_gradient_vector[i][j + 1])
                block_vector.extend(cell_gradient_vector[i + 1][j])
                block_vector.extend(cell_gradient_vector[i + 1][j + 1])
                ‘‘‘塊內歸一化梯度直方圖,去除光照、陰影等變化,增加魯棒性‘‘‘
                #計算l2範數
                mag = lambda vector: math.sqrt(sum(i ** 2 for i in vector))   
                magnitude = mag(block_vector) + 1e-5
                #歸一化
                if magnitude != 0:
                    normalize = lambda block_vector, magnitude: [element / magnitude for element in block_vector]
                    block_vector = normalize(block_vector, magnitude)
                hog_vector.append(block_vector)           
        return np.asarray(hog_vector), hog_image

    def global_gradient(self):
        ‘‘‘
        分別計算圖像沿x軸和y軸的梯度
        ‘‘‘
        gradient_values_x = cv2.Sobel(self.img, cv2.CV_64F, 1, 0, ksize=5)
        gradient_values_y = cv2.Sobel(self.img, cv2.CV_64F, 0, 1, ksize=5)
        #計算梯度幅值 這個計算的是0.5*gradient_values_x + 0.5*gradient_values_y
        #gradient_magnitude = cv2.addWeighted(gradient_values_x, 0.5, gradient_values_y, 0.5, 0)
        #計算梯度方向
        #gradient_angle = cv2.phase(gradient_values_x, gradient_values_y, angleInDegrees=True)
        gradient_magnitude, gradient_angle = cv2.cartToPolar(gradient_values_x,gradient_values_y,angleInDegrees=True)        
        #角度大於180°的,減去180度
        gradient_angle[gradient_angle>180.0] -= 180 
        #print(‘gradient‘,gradient_magnitude.shape,gradient_angle.shape,np.min(gradient_angle),np.max(gradient_angle))
        return gradient_magnitude, gradient_angle

    def cell_gradient(self, cell_magnitude, cell_angle):
        ‘‘‘
        為每個細胞單元構建梯度方向直方圖
        
        args:
            cell_magnitude:cell中每個像素點的梯度幅值
            cell_angle:cell中每個像素點的梯度方向
        return:
            返回該cell對應的梯度直方圖,長度為bin_size
        ‘‘‘
        orientation_centers = [0] * self.bin_size
        #遍歷cell中的每一個像素點
        for i in range(cell_magnitude.shape[0]):
            for j in range(cell_magnitude.shape[1]):
                #梯度幅值
                gradient_strength = cell_magnitude[i][j]
                #梯度方向
                gradient_angle = cell_angle[i][j]
                #雙線性插值
                min_angle, max_angle, weight = self.get_closest_bins(gradient_angle)
                orientation_centers[min_angle] += (gradient_strength * (1 - weight))
                orientation_centers[max_angle] += (gradient_strength *weight)
        return orientation_centers

    def get_closest_bins(self, gradient_angle):
        ‘‘‘
        計算梯度方向gradient_angle位於哪一個bin中,這裏采用的計算方式為雙線性插值
        具體參考:https://www.leiphone.com/news/201708/ZKsGd2JRKr766wEd.html
        例如:當我們把180°劃分為9個bin的時候,分別對應對應0,20,40,...160這些角度。
              角度是10,副值是4,因為角度10介於0-20度的中間(正好一半),所以把幅值
              一分為二地放到0和20兩個bin裏面去。
        args:
            gradient_angle:角度
        return:
            start,end,weight:起始bin索引,終止bin的索引,end索引對應bin所占權重
        ‘‘‘
        idx = int(gradient_angle / self.angle_unit)
        mod = gradient_angle % self.angle_unit
        return idx % self.bin_size, (idx + 1) % self.bin_size, mod / self.angle_unit

    def render_gradient(self, image, cell_gradient):
        ‘‘‘
        將得到的每個cell的梯度方向直方圖繪出,得到特征圖
        args:
            image:畫布,和輸入圖像一樣大 [h,w]
            cell_gradient:輸入圖像的每個cell單元的梯度直方圖,形狀為[h/cell_size,w/cell_size,bin_size]
        return:
            image:特征圖
        ‘‘‘
        cell_width = self.cell_size / 2
        max_mag = np.array(cell_gradient).max()
        #遍歷每一個cell
        for x in range(cell_gradient.shape[0]):
            for y in range(cell_gradient.shape[1]):
                #獲取第[i][j]個cell的梯度直方圖
                cell_grad = cell_gradient[x][y]
                #歸一化
                cell_grad /= max_mag
                angle = 0
                angle_gap = self.angle_unit
                #遍歷每一個bin區間
                for magnitude in cell_grad:
                    #轉換為弧度
                    angle_radian = math.radians(angle)
                    #計算起始坐標和終點坐標,長度為幅值(歸一化),繪制的越長線條越量
                    x1 = int(x * self.cell_size + cell_width + magnitude * cell_width * math.cos(angle_radian))
                    y1 = int(y * self.cell_size + cell_width + magnitude * cell_width * math.sin(angle_radian))
                    x2 = int(x * self.cell_size + cell_width - magnitude * cell_width * math.cos(angle_radian))
                    y2 = int(y * self.cell_size + cell_width - magnitude * cell_width * math.sin(angle_radian))
                    cv2.line(image, (y1, x1), (y2, x2), int(255 * math.sqrt(magnitude)))
                    angle += angle_gap
        return image

        
if __name__ == __main__:
    #加載圖像
    img = cv2.imread(./image/person.jpg)        
    width = 64
    height = 128
    img_copy = img[320:320+height,570:570+width][:,:,::-1]    
    gray_copy = cv2.cvtColor(img_copy,cv2.COLOR_BGR2GRAY)
    
    #顯示原圖像
    plt.figure(figsize=(6.4,2.0*3.2))
    plt.subplot(1,2,1)
    plt.imshow(img_copy)
    
    #HOG特征提取
    hog = Hog_descriptor(gray_copy, cell_size=8, bin_size=9)    
    hog_vector, hog_image = hog.extract()
    print(hog_vector,hog_vector.shape)
    print(hog_image,hog_image.shape)
    
    #繪制特征圖
    plt.subplot(1,2,2)
    plt.imshow(hog_image, cmap=plt.cm.gray)    
    plt.show()

程序運行結果為:

技術分享圖片

我們可以看到當輸入圖像大小為$128\times{64}$時,得到的HOG特征向量為$105\times{36}=3780$,這和我們計算的一樣,左邊的圖為需要提取HOG特征的原圖,右圖為所提取得到的特征圖,我們使用線段長度表示每一個cell中每一個bin的幅值大小(同時線段的亮度也與幅值大小成正比),線段傾斜角度表示cell中每一個bin的角度,從右圖上我們可以大致觀察到這個人的邊緣信息以及梯度變化,因此利用該特征可以很容易的識別出人的主要結構。

四 目標檢測中的問題

雖然我們已經介紹了HOG特征的提取,但是在想把HOG特征應用到目標檢測上,我們還需考慮兩個問題:

  • 尺度:對於這個問題可以通過舉例說明:假如要檢測的目標(比如人)是較大圖像中的一部分,要把要檢測的圖像和訓練圖像比較。如果在比較中找不到一組相同的梯度,則檢測就會失敗(即使兩張圖像都有人)。
  • 位置:在解決了尺度問題後,還有另一個問題:要檢測的目標可能位於圖像上的任一個地方,所以需要掃描圖像的每一個地方,以取保找到感興趣的區域,並且嘗試在這些區域檢測目標。即使待檢測的圖像中的目標和訓練圖像中的目標一樣大,也需要通過某種方式讓opencv定位該目標。因此,只對有可能存在目標的區域進行比較,而該圖像的其余地方會被丟棄。

1、圖像金字塔

圖像金字塔有助於解決不用尺度下的目標檢測問題,圖像金字塔使圖像的多尺度表示,如下圖所示:

技術分享圖片

構建圖像金字塔一般包含以下步驟(詳細內容可以參考尺度空間理論):

  1. 獲取圖像;
  2. 使用任意尺度的參數來調整(縮小)圖像的大小;
  3. 平滑圖像(使用高斯模糊);
  4. 如果圖像比最小尺度還大,從第一步開會重復這個過程;

在人臉檢測之Haar分類器這一節我們利用haar特征和級聯分類器Adaboost檢測人臉時我們使用過一個函數detectMultiScale(),這個函數就涉及這些內容,級聯分類器對象嘗試在輸入圖像的不同尺度下檢測對象,該函數有一個比較重要的參數scaleFactor(一般設置為1.3),表示一個比率:即在每層金字塔中所獲得的圖像與上一層圖像的比率,scaleFactor越小,金字塔的層數就越多,計算就越慢,計算量也會更大,但是計算結果相對更結果。

2、滑動窗口

滑動窗口是用在計算機視覺的一種技術,它包括圖像中要移動部分(滑動窗口)的檢查以及使用圖像金字塔對各部分進行檢測。這是為了在多尺度下檢測對象。

滑動窗口通過掃描較大圖像的較小區域來解決定位問題,進而在同一圖像的不同尺度下重復掃描。

使用這種方法進行目標檢測會出現一個問題:區域重疊,針對區域重疊問題,我們可以利用非極大值抑制(詳細內容可以參考第二十七節,IOU和非極大值抑制),來消除重疊的窗口。

五 使用opencv檢測人

下面我們介紹使用OpenCV自帶的HOGDescriptor()函數對人進行檢測:

# -*- coding: utf-8 -*-
"""
Created on Mon Sep 24 16:43:37 2018

@author: zy
"""

‘‘‘
HOG檢測人
‘‘‘
import  cv2
import numpy as np

def is_inside(o,i):
    ‘‘‘
    判斷矩形o是不是在i矩形中
    
    args:
        o:矩形o  (x,y,w,h)
        i:矩形i  (x,y,w,h)
    ‘‘‘
    ox,oy,ow,oh = o
    ix,iy,iw,ih = i
    return ox > ix and oy > iy and ox+ow < ix+iw and oy+oh < iy+ih
    
def draw_person(img,person):
    ‘‘‘
    在img圖像上繪制矩形框person
    
    args:
        img:圖像img
        person:人所在的邊框位置 (x,y,w,h)
    ‘‘‘
    x,y,w,h = person
    cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,255),2)
    

def detect_test():
    ‘‘‘
    檢測人
    ‘‘‘
    img = cv2.imread(./image/person.jpg)
    rows,cols = img.shape[:2]
    sacle = 1.0
    #print(‘img‘,img.shape)
    img = cv2.resize(img,dsize=(int(cols*sacle),int(rows*sacle)))
    #print(‘img‘,img.shape)
    
    #創建HOG描述符對象
    hog = cv2.HOGDescriptor()
    detector = cv2.HOGDescriptor_getDefaultPeopleDetector()
    print(detector,type(detector),detector.shape)    
    hog.setSVMDetector(detector)
    
    #多尺度檢測,found是一個數組,每一個元素都是對應一個矩形,即檢測到的目標框
    found,w = hog.detectMultiScale(img)
    print(found,type(found),found.shape)
    
    #過濾一些矩形,如果矩形o在矩形i中,則過濾掉o
    found_filtered = []
    for ri,r in enumerate(found):
        for qi,q in enumerate(found):
            #r在q內?
            if ri != qi and is_inside(r,q):
                break
            else:
                found_filtered.append(r)
            
    for person in found_filtered:
        draw_person(img,person)
        
    cv2.imshow(img,img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
if __name__==__main__:
    detect_test()

輸出如下:

技術分享圖片

其中有一點我們需要註意,opencv自帶的檢測大小是3781維度的,這是因為我們從$128\times{64}$的檢測窗口中提取的特征向量為3780維度,而我們的檢測器采用的是支持向量機,最終的檢測方法是基於線性判別函數$wx+b=0$。在訓練檢測器時,當把特征維度為3780的特征送到SVM中訓練,得到的$w$維度也為3780,另外還有一個偏置$b$,因此檢測器的維度為3781。

 detector = cv2.HOGDescriptor_getDefaultPeopleDetector()
 print(detector,type(detector),detector.shape)    

另外我在啰嗦一下:在訓練的時候,我們的正負樣本圖像大小都應該是$128\times{64}$的,然後提取樣本圖像的HOG特征,也就是3780維度的特征向量,送入到SVM進行訓練,最終的目的就是得到這3781維度的檢測器。

在測試的時,檢測窗口(大小為$128\times{64}$)在整個圖像的所有位置和尺度上進行掃描,然後提取提取每一個窗口的HOG特征,送入檢測器進行判別,最後還需要對輸出的金字塔進行非極大值抑制。例如:這裏有張圖是$720\times{475}$的,我們選$200\times{100}$大小的patch,把這個patch從圖片裏面摳出來,然後再把大小調整成$128\times{64}$,計算HOG特征,並送入檢測器判別是否包含目標

技術分享圖片

參考文章:

[1]Dalal N, Triggs B. Histograms of oriented gradients for human detection[C]//Computer Vision and Pattern Recognition, 2005. CVPR 2005. IEEE Computer Society Conference on. IEEE, 2005, 1: 886-893.(2016:Google Citation: 14046)

[2]Dalal N. Finding people in images and videos[D]. Institut National Polytechnique de Grenoble-INPG, 2006.(Google Citation: 337)

[3]HOG:用於人體檢測的梯度方向直方圖 Histograms of Oriented Gradients for Human Detection(原文翻譯)

[4]HOG特征(Histogram of Gradient)學習總結

[5]圖像處理之gamma校正

[6]目標檢測的圖像特征提取之(一)HOG特征

[7]80行Python實現-HOG梯度特征提取

[8]圖像學習之如何理解方向梯度直方圖(Histogram Of Gradient)

[9]尺度空間理論

[10]OpenCV 3計算機視覺

第十八節、基於傳統圖像處理的目標檢測與識別(HOG+SVM附代碼)