第三十三節,目標檢測之選擇性搜索-Selective Search

基於深度學習的目標檢測算法的綜述 那一節中咱們提到基於區域提名的目標檢測中普遍使用的選擇性搜索算法。而且該算法後來被應用到了R-CNN,SPP-Net,Fast R-CNN中。所以我認爲仍是有研究的必要。html

傳統的目標檢測算法大多數以圖像識別爲基礎。通常能夠在圖片上使用窮舉法或者滑動窗口選出全部物體可能出現的區域框,對這些區域框提取特徵並進行使用圖像識別分類方法,獲得全部分類成功的區域後,經過非極大值抑制輸出結果。git

在圖片上使用窮舉法或者滑動窗口選出全部物體可能出現的區域框,就是在原始圖片上進行不一樣尺度不一樣大小的滑窗,獲取每一個可能的位置。而這樣作的缺點也顯而易見,複雜度過高,產生了不少的冗餘候選區域,並且因爲不可能每一個尺度都兼顧到,所以獲得的目標位置也不可能那麼準,在現實當中不可行。而選擇性搜索有效地去除冗餘候選區域,使得計算量大大的減少。github

咱們先來看一組圖片,因爲咱們事先不知道須要檢測哪一個類別,所以第一張圖的桌子、瓶子、餐具都是一個個候選目標,而餐具包含在桌子這個目標內,勺子又包含在碗內。這張圖展現了目標檢測的層級關係以及尺度關係,那咱們如何去得到這些可能目標的位置呢。咱們能不能經過視覺特徵去減小候選框的數量並提升精確度呢。算法

可用的特徵有不少,到底什麼特徵是有用的呢?咱們看第二副圖片的兩隻貓咪,他們的紋理是同樣的,所以紋理特徵確定不行了。而若是經過顏色則能很好區分。可是第三幅圖變色龍可就不行了,這時候邊緣特徵、紋理特徵又顯得比較有用。而在最後一幅圖中,咱們很容易把車和輪胎看做是一個總體,可是其實這二者的特徵差距真的很明顯啊,不管是顏色仍是紋理或是邊緣都差的太遠了。而這這是幾種狀況,天然圖像那麼多,咱們經過什麼特徵去區分?應該區分到什麼尺度?數組

selective search的策略是,既然是不知道尺度是怎樣的,那咱們就儘量遍歷全部的尺度好了,可是不一樣於暴力窮舉,咱們能夠先利用基於圖的圖像分割的方法獲得小尺度的區域,而後一次次合併獲得大的尺寸就行了,這樣也符合人類的視覺認知。既然特徵不少,那就把咱們知道的特徵都用上,可是同時也要照顧下計算複雜度,否則和窮舉法也沒啥區別了。最後還要作的是可以對每一個區域進行排序,這樣你想要多少個候選我就產生多少個,否則老是產生那麼多你也用不完不是嗎?app

在深刻介紹Selective Search以前,先說說其須要考慮的幾個問題:
  •  適應不一樣尺度(Capture All Scales):窮舉搜索(Exhaustive Selective)經過改變窗口大小來適應物體的不一樣尺度,選擇搜索(Selective Search)一樣沒法避免這個問題。算法採用了圖像分割(Image Segmentation)以及使用一種層次算法(Hierarchical Algorithm)有效地解決了這個問題。
  • 多樣化(Diversification):單一的策略沒法應對多種類別的圖像。使用顏色(color)、紋理(texture)、大小(size)等多種策略對分割好的區域(region)進行合併。
  • 速度快(Fast to Compute):算法,就像功夫同樣,惟快不破!

一 選擇性搜索的具體算法(區域合併算法)

輸入: 一張圖片
輸出:候選的目標位置集合L

算法:
1: 利用切分方法獲得候選的區域集合R = {r1,r2,…,rn}
2: 初始化類似集合S = ϕ
3: foreach 遍歷鄰居區域對(ri,rj) do
4:     計算類似度s(ri,rj)
5:     S = S  ∪ s(ri,rj)
6: while S not=ϕ do
7:     從S中獲得最大的類似度s(ri,rj)=max(S)
8:     合併對應的區域rt = ri ∪ rj
9:     移除ri對應的全部類似度:S = S\s(ri,r*)
10:    移除rj對應的全部類似度:S = S\s(r*,rj)
11:    計算rt對應的類似度集合St
12:    S = S ∪ St
13:    R = R ∪ rt
14: L = R中全部區域對應的邊框

首先經過基於圖的圖像分割方法初始化原始區域,就是將圖像分割成不少不少的小塊。而後咱們使用貪心策略,計算每兩個相鄰的區域的類似度,而後每次合併最類似的兩塊,直到最終只剩下一塊完整的圖片。而後這其中每次產生的圖像塊包括合併的圖像塊咱們都保存下來,這樣就獲得圖像的分層表示了呢。那咱們如何計算兩個圖像塊的類似度呢?ide

二 保持多樣性的策略

區域合併採用了多樣性的策略,若是簡單採用一種策略很容易錯誤合併不類似的區域,好比只考慮紋理時,不一樣顏色的區域很容易被誤合併。選擇性搜索採用三種多樣性策略來增長候選區域以保證召回:函數

  • 多種顏色空間,考慮RGB、灰度、HSV及其變種等
  • 多種類似度度量標準,既考慮顏色類似度,又考慮紋理、大小、重疊狀況等。
  • 經過改變閾值初始化原始區域,閾值越大,分割的區域越少。

一、顏色空間變換

經過色彩空間變換,將原始色彩空間轉換到多達八中的色彩空間。做者採用了8中不一樣的顏色方式,主要是爲了考慮場景以及光照條件等。這個策略主要應用於中圖像分割算法中原始區域的生成(兩個像素點的類似度計算時,計算不一樣顏色空間下的兩點距離)。主要使用的顏色空間有:(1)RGB,(2)灰度I,(3)Lab,(4)rgI(歸一化的rg通道加上灰度),(5)HSV,(6)rgb(歸一化的RGB),(7)C,(8)H(HSV的H通道)post

 

二、區域類似度計算

咱們在計算多種類似度的時候,都是把單一類似度的值歸一化到[0,1]之間,1表示兩個區域之間類似度最大。性能

  • 顏色類似度

使用L1-norm歸一化獲取圖像每一個顏色通道的25 bins的直方圖,這樣每一個區域均可以獲得一個75維的向量,區域之間顏色類似度經過下面的公式計算:

上面這個公式可能你第一眼看過去看不懂,那我們打個比方,因爲是歸一化後值,每個顏色通道的直方圖累加和爲1.0,三個通道的累加和就爲3.0,若是區域ci和區域cj直方圖徹底同樣,則此時顏色類似度最大爲3.0,若是不同,因爲累加取兩個區域bin的最小值進行累加,當直方圖差距越大,累加的和就會越小,即顏色類似度越小。

在區域合併過程當中使用須要對新的區域進行計算其直方圖,計算方法:

  • 紋理類似度

這裏的紋理採用SIFT-Like特徵。具體作法是對每一個顏色通道的8個不一樣方向計算方差σ=1的高斯微分(Gaussian Derivative),使用L1-norm歸一化獲取圖像每一個顏色通道的每一個方向的10 bins的直方圖,這樣就能夠獲取到一個240(10x8x3)維的向量,區域之間紋理類似度計算方式和顏色類似度計算方式相似,合併以後新區域的紋理特徵計算方式和顏色特徵計算相同:

 

  • 優先合併小的區域

若是僅僅是經過顏色和紋理特徵合併的話,很容易使得合併後的區域不斷吞併周圍的區域,後果就是多尺度只應用在了那個局部,而不是全局的多尺度。所以咱們給小的區域更多的權重,這樣保證在圖像每一個位置都是多尺度的在合併。

上面的公式表示,兩個區域越小,其類似度越大,越接近1。

  • 區域的合適度距離

若是區域ri包含在rj內,咱們首先應該合併,另外一方面,若是ri很難與rj相接,他們之間會造成斷崖,不該該合併在一塊。這裏定義區域的合適度距離主要是爲了衡量兩個區域是否更加「吻合」,其指標是合併後的區域的Bounding Box(可以框住區域的最小矩形BBij)越小,其吻合度越高,即類似度越接近1。其計算方式:

  • 合併上面四種類似度

其中

三 給區域打分

經過上述的步驟咱們可以獲得不少不少的區域,可是顯然不是每一個區域做爲目標的可能性都是相同的,所以咱們須要衡量這個可能性,這樣就能夠根據咱們的須要篩選區域建議個數啦。

這篇文章作法是,給予最早合併的圖片塊較大的權重,好比最後一塊完整圖像權重爲1,倒數第二次合併的區域權重爲2以此類推。可是當咱們策略不少,多樣性不少的時候呢,這個權重就會有太多的重合了,排序很差搞啊。文章作法是給他們乘以一個隨機數,畢竟3分看運氣嘛,而後對於相同的區域屢次出現的也疊加下權重,畢竟多個方法都說你是目標,也是有理由的嘛。這樣我就獲得了全部區域的目標分數,也就能夠根據本身的須要選擇須要多少個區域了。

四 選擇性搜索性能評估

天然地,經過算法計算獲得的包含物體的Bounding Boxes與真實狀況(ground truth)的窗口重疊越多,那麼算法性能就越好。這是使用的指標是平均最高重疊率ABO(Average Best Overlap)。對於每一個固定的類別 c,每一個真實狀況(ground truth)表示爲 ,令計算獲得的位置假設L中的每一個值lj,那麼 ABO的公式表達爲:

 
 
 
重疊率的計算方式:
 
上面結果給出的是一個類別的ABO,對於全部類別下的性能評價,很天然就是使用全部類別的ABO的平均值MABO(Mean Average Best Overlap)來評價。

 一、單一策略評估

咱們能夠經過改變多樣性策略中的任何一種,評估選擇性搜索的MABO性能指標。論文中採起的策略以下:
  • 使用RGB色彩空間(基於圖的圖像分割會利用不一樣的色彩進行圖像區域分割)
  • 採用四種類似度計算的組合方式
  • 設置圖像分割的閾值k=50

而後經過改變其中一個策略參數,獲取MABO性能指標以下表(第一列爲改變的參數,第二列爲MABO值,第三列爲獲取的候選區的個數):

表中左側爲不一樣的類似度組合,單獨的,咱們能夠看到紋理類似度表現最差,MABO爲0.581,其餘的MABO值介於0.63和0.64之間。當使用多種類似度組合時MABO性能優於單種類似度。表的右上角表名使用HSV顏色空間,有463個候選區域,並且MABO值最大爲0.693。表的右下角表名使用較小的閾值,會獲得更多的候選區和較高的MABO值。

二、多樣性策略組合

咱們使用貪婪的搜索算法,把單一策略進行組合,會得到較高的MABO,可是也會形成計算成本的增長。下表給出了三種組合的MABO性能指標:

上圖中的綠色邊框爲對象的標記邊框,紅色邊框爲咱們使用 'Quality' Selective Search算法得到的Overlap最高的候選框。能夠看到咱們這個候選框和真實標記很是接近。

下表爲和其它算法在VOC 2007測試集上的比較結果:

下圖爲各個算法在選取不一樣候選區數量,Recall和MABO性能的曲線圖,從計算成本、以及性能考慮,Selective Search Fast算法在2000個候選區時,效果較好。

 5、代碼實現

 咱們能夠經過下面命令直接安裝Selective Search包。

pip install selectivesearch

而後從https://github.com/AlpacaDB/selectivesearch下載源碼,運行example\example.py文件。效果以下:

# -*- coding: utf-8 -*-
from __future__ import (
    division,
    print_function,
)

import skimage.data
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import selectivesearch
import numpy as np


def main():

    # 加載圖片數據
    img = skimage.data.astronaut() 

    '''
    執行selective search,regions格式以下
    [
                {
                    'rect': (left, top, width, height),
                    'labels': [...],
                    'size': component_size
                },
                ...
    ]
    '''
    img_lbl, regions = selectivesearch.selective_search(
        img, scale=500, sigma=0.9, min_size=10)

    #計算一共分割了多少個原始候選區域
    temp = set()
    for i in range(img_lbl.shape[0]):
        for j in range(img_lbl.shape[1]):    
            temp.add(img_lbl[i,j,3]) 
    print(len(temp))       #286
    
    #計算利用Selective Search算法獲得了多少個候選區域
    print(len(regions))    #570
    #建立一個集合 元素不會重複,每個元素都是一個list(左上角x,左上角y,寬,高),表示一個候選區域的邊框
    candidates = set()
    for r in regions:
        #排除重複的候選區
        if r['rect'] in candidates:
            continue
        #排除小於 2000 pixels的候選區域(並非bounding box中的區域大小)  
        if r['size'] < 2000:
            continue
        #排除扭曲的候選區域邊框  即只保留近似正方形的
        x, y, w, h = r['rect']
        if w / h > 1.2 or h / w > 1.2:
            continue
        candidates.add(r['rect'])

    #在原始圖像上繪製候選區域邊框
    fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
    ax.imshow(img)
    for x, y, w, h in candidates:
        print(x, y, w, h)
        rect = mpatches.Rectangle(
            (x, y), w, h, fill=False, edgecolor='red', linewidth=1)
        ax.add_patch(rect)

    plt.show()
    

if __name__ == "__main__":
    main()

selective_search函數的定義以下:

def selective_search(
        im_orig, scale=1.0, sigma=0.8, min_size=50):
    '''Selective Search

    首先經過基於圖的圖像分割方法初始化原始區域,就是將圖像分割成不少不少的小塊
    而後咱們使用貪心策略,計算每兩個相鄰的區域的類似度
    而後每次合併最類似的兩塊,直到最終只剩下一塊完整的圖片
    而後這其中每次產生的圖像塊包括合併的圖像塊咱們都保存下來
    
    Parameters
    ----------
        im_orig : ndarray
            Input image
        scale : int
            Free parameter. Higher means larger clusters in felzenszwalb segmentation.
        sigma : float
            Width of Gaussian kernel for felzenszwalb segmentation.
        min_size : int
            Minimum component size for felzenszwalb segmentation.
    Returns
    -------
        img : ndarray
            image with region label
            region label is stored in the 4th value of each pixel [r,g,b,(region)]
        regions : array of dict
            [
                {
                    'rect': (left, top, width, height),
                    'labels': [...],
                    'size': component_size 候選區域大小,並非邊框的大小
                },
                ...
            ]
    '''
    assert im_orig.shape[2] == 3, "3ch image is expected"

    # load image and get smallest regions
    # region label is stored in the 4th value of each pixel [r,g,b,(region)] 
    #圖片分割 把候選區域標籤合併到最後一個通道上 height x width x 4  每個像素的值爲[r,g,b,(region)] 
    img = _generate_segments(im_orig, scale, sigma, min_size)

    if img is None:
        return None, {}

    #計算圖像大小
    imsize = img.shape[0] * img.shape[1]
    
    #dict類型,鍵值爲候選區域的標籤   值爲候選區域的信息,包括候選區域的邊框,以及區域的大小,顏色直方圖,紋理特徵直方圖等信息
    R = _extract_regions(img)

    #list類型 每個元素都是鄰居候選區域對(ri,rj)  (即兩兩相交的候選區域)
    neighbours = _extract_neighbours(R)

    # calculate initial similarities 初始化類似集合S = ϕ
    S = {}
    #計算每個鄰居候選區域對的類似度s(ri,rj)
    for (ai, ar), (bi, br) in neighbours:      
        #S=S∪s(ri,rj)  ai表示候選區域ar的標籤  好比當ai=1 bi=2 S[(1,2)就表示候選區域1和候選區域2的類似度
        S[(ai, bi)] = _calc_sim(ar, br, imsize)

    # hierarchal search 層次搜索 直至類似度集合爲空
    while S != {}:

        # get highest similarity  獲取類似度最高的兩個候選區域  i,j表示候選區域標籤
        i, j = sorted(S.items(), key=lambda i: i[1])[-1][0]   #按照類似度排序

        # merge corresponding regions  合併類似度最高的兩個鄰居候選區域 rt = ri∪rj ,R = R∪rt
        t = max(R.keys()) + 1.0
        R[t] = _merge_regions(R[i], R[j])

        # mark similarities for regions to be removed   獲取須要刪除的元素的鍵值        
        key_to_delete = []         
        for k, v in S.items():       #k表示鄰居候選區域對(i,j)  v表示候選區域(i,j)表示類似度
            if (i in k) or (j in k):
                key_to_delete.append(k)

        # remove old similarities of related regions 移除候選區域ri對應的全部類似度:S = S\s(ri,r*)  移除候選區域rj對應的全部類似度:S = S\s(r*,rj)
        for k in key_to_delete:
            del S[k]

        # calculate similarity set with the new region  計算候選區域rt對應的類似度集合St,S = S∪St
        for k in filter(lambda a: a != (i, j), key_to_delete):
            n = k[1] if k[0] in (i, j) else k[0]
            S[(t, n)] = _calc_sim(R[t], R[n], imsize)

    #獲取每個候選區域的的信息  邊框、以及候選區域size,標籤
    regions = []
    for k, r in R.items():
        regions.append({
            'rect': (
                r['min_x'], r['min_y'],
                r['max_x'] - r['min_x'], r['max_y'] - r['min_y']),
            'size': r['size'],
            'labels': r['labels']
        })
            
    #img:基於圖的圖像分割獲得的候選區域   regions:Selective Search算法獲得的候選區域
    return img, regions

該函數是按照Selective Search算法實現的,算法的每一步都有相對應的代碼,而且把初始化候選區域,鄰居候選區域對的遍歷以及類似度計算,候選區域的合併都單獨封裝成了一個函數,因爲代碼比較長,就不一一介紹了,下面我把代碼附上,而且作了詳細的介紹,有興趣研究的童鞋看一下:

# -*- coding: utf-8 -*-
import skimage.io
import skimage.feature
import skimage.color
import skimage.transform
import skimage.util
import skimage.segmentation
import numpy


# "Selective Search for Object Recognition" by J.R.R. Uijlings et al.
#
#  - Modified version with LBP extractor for texture vectorization


def _generate_segments(im_orig, scale, sigma, min_size):
    """
        segment smallest regions by the algorithm of Felzenswalb and
        Huttenlocher
    """

    # open the Image   segment_mask : (width, height) ndarray  Integer mask indicating segment labels.
    im_mask = skimage.segmentation.felzenszwalb(
        skimage.util.img_as_float(im_orig), scale=scale, sigma=sigma,
        min_size=min_size)

    # merge mask channel to the image as a 4th channel 把類別合併到最後一個通道上 height x width x 4
    im_orig = numpy.append(
        im_orig, numpy.zeros(im_orig.shape[:2])[:, :, numpy.newaxis], axis=2)
    im_orig[:, :, 3] = im_mask

    return im_orig


def _sim_colour(r1, r2):
    """
    計算顏色類似度

    calculate the sum of histogram intersection of colour
    
    args:
        r1:候選區域r1
        r2:候選區域r2
        
    return:[0,3]之間的數值
                
    """
    return sum([min(a, b) for a, b in zip(r1["hist_c"], r2["hist_c"])])


def _sim_texture(r1, r2):
    """
    計算紋理特徵類似度
    
    calculate the sum of histogram intersection of texture
    
    args:
        r1:候選區域r1
        r2:候選區域r2
        
    return:[0,3]之間的數值
    
    """
    return sum([min(a, b) for a, b in zip(r1["hist_t"], r2["hist_t"])])


def _sim_size(r1, r2, imsize):
    """
    計算候選區域大小類似度
    
    calculate the size similarity over the image
    
    args:
        r1:候選區域r1
        r2:候選區域r2
        
    return:[0,1]之間的數值
    
    """
    return 1.0 - (r1["size"] + r2["size"]) / imsize


def _sim_fill(r1, r2, imsize):
    """
    計算候選區域的距離合適度類似度
    
    calculate the fill similarity over the image
    
    args:
        r1:候選區域r1
        r2:候選區域r2
        imsize:原圖像像素數
        
    return:[0,1]之間的數值
    
    """
    bbsize = (
        (max(r1["max_x"], r2["max_x"]) - min(r1["min_x"], r2["min_x"]))
        * (max(r1["max_y"], r2["max_y"]) - min(r1["min_y"], r2["min_y"]))
    )
    return 1.0 - (bbsize - r1["size"] - r2["size"]) / imsize


def _calc_sim(r1, r2, imsize):
    '''
    計算兩個候選區域的類似度,權重係數默認都是1
    
    args:
        r1:候選區域r1
        r2:候選區域r2
        imsize:原圖片像素數
    '''
    return (_sim_colour(r1, r2) + _sim_texture(r1, r2)
            + _sim_size(r1, r2, imsize) + _sim_fill(r1, r2, imsize))


def _calc_colour_hist(img):
    """
    使用L1-norm歸一化獲取圖像每一個顏色通道的25 bins的直方圖,這樣每一個區域均可以獲得一個75維的向量
    
    calculate colour histogram for each region

    the size of output histogram will be BINS * COLOUR_CHANNELS(3)

    number of bins is 25 as same as [uijlings_ijcv2013_draft.pdf]

    extract HSV
    
    args:
        img:ndarray類型, 形狀爲候選區域像素數 x 3(h,s,v)
        
    return:一維的ndarray類型,長度爲75
            
    """

    BINS = 25
    hist = numpy.array([])

    for colour_channel in (0, 1, 2):

        # extracting one colour channel
        c = img[:, colour_channel]

        # calculate histogram for each colour and join to the result  
        #計算每個顏色通道的25 bins的直方圖 而後合併到一個一維數組中
        hist = numpy.concatenate(
            [hist] + [numpy.histogram(c, BINS, (0.0, 255.0))[0]])

    # L1 normalize  len(img):候選區域像素數
    hist = hist / len(img)

    return hist


def _calc_texture_gradient(img):
    """
    原文:對每一個顏色通道的8個不一樣方向計算方差σ=1的高斯微分(Gaussian Derivative,這裏使用LBP替代
    
    calculate texture gradient for entire image

    The original SelectiveSearch algorithm proposed Gaussian derivative
    for 8 orientations, but we use LBP instead.

    output will be [height(*)][width(*)]
    
    args:
        img: ndarray類型,形狀爲height x width x 4,每個像素的值爲 [r,g,b,(region)]
        
    return:紋理特徵,形狀爲height x width x 4
    """
    ret = numpy.zeros((img.shape[0], img.shape[1], img.shape[2]))

    for colour_channel in (0, 1, 2):
        ret[:, :, colour_channel] = skimage.feature.local_binary_pattern(
            img[:, :, colour_channel], 8, 1.0)

    return ret


def _calc_texture_hist(img):
    """
    使用L1-norm歸一化獲取圖像每一個顏色通道的每一個方向的10 bins的直方圖,這樣就能夠獲取到一個240(10x8x3)維的向量
    
    calculate texture histogram for each region

    calculate the histogram of gradient for each colours
    the size of output histogram will be
        BINS * ORIENTATIONS * COLOUR_CHANNELS(3)
        
    args:
        img:候選區域紋理特徵   形狀爲候選區域像素數 x 4(r,g,b,(region))
        
    return:一維的ndarray類型,長度爲240
    """
    BINS = 10

    hist = numpy.array([])

    for colour_channel in (0, 1, 2):

        # mask by the colour channel
        fd = img[:, colour_channel]

        # calculate histogram for each orientation and concatenate them all
        # and join to the result
        hist = numpy.concatenate(
            [hist] + [numpy.histogram(fd, BINS, (0.0, 1.0))[0]])

    # L1 Normalize   len(img):候選區域像素數
    hist = hist / len(img)

    return hist


def _extract_regions(img):
    '''
    提取每個候選區域的信息   好比類別(region)爲5的區域表示的是一隻貓的選區,這裏就是提取這隻貓的邊界框,左上角後右下角座標
    
    args:
        img: ndarray類型,形狀爲height x width x 4,每個像素的值爲 [r,g,b,(region)]
    
    return : 
        R:dict 每個元素對應一個候選區域, 每一個元素也是一個dict類型
                              {min_x:邊界框的左上角x座標,
                              min_y:邊界框的左上角y座標,
                              max_x:邊界框的右下角x座標,
                              max_y:邊界框的右下角y座標,
                              size:像素個數,
                              hist_c:顏色的直方圖,
                              hist_t:紋理特徵的直方圖,} 
    '''
    #保存全部候選區域的bounding box  每個元素都是一個dict {最小x座標值,最小y座標值,最大x座標值,最大y座標值,類別}
    #                            經過上面四個參數肯定一個邊界框
    R = {}

    # get hsv image   RGB轉換爲HSV色彩空間 height x width x 3
    hsv = skimage.color.rgb2hsv(img[:, :, :3])

    # pass 1: count pixel positions 遍歷每個像素
    for y, i in enumerate(img):    #y = 0 -> height - 1

        for x, (r, g, b, l) in enumerate(i):  # x = 0 -> height - 1

            # initialize a new region
            if l not in R:
                R[l] = {
                    "min_x": 0xffff, "min_y": 0xffff,
                    "max_x": 0, "max_y": 0, "labels": [l]}

            # bounding box
            if R[l]["min_x"] > x:
                R[l]["min_x"] = x
            if R[l]["min_y"] > y:
                R[l]["min_y"] = y
            if R[l]["max_x"] < x:
                R[l]["max_x"] = x
            if R[l]["max_y"] < y:
                R[l]["max_y"] = y

    # pass 2: calculate texture gradient  紋理特徵提取 利用LBP算子 height x width x 4
    tex_grad = _calc_texture_gradient(img)

    # pass 3: calculate colour histogram of each region  計算每個候選區域(注意不是bounding box圈住的區域)的直方圖
    for k, v in R.items():

        # colour histogram   height x width x 3 -> 候選區域k像素數 x 3(img[:, :, 3] == k返回的是一個二維座標的集合)
        masked_pixels = hsv[:, :, :][img[:, :, 3] == k]
        #print(type(masked_pixels),masked_pixels.shape)
        R[k]["size"] = len(masked_pixels / 4)                  #候選區域k像素數
        #在hsv色彩空間下,使用L1-norm歸一化獲取圖像每一個顏色通道的25 bins的直方圖,這樣每一個區域均可以獲得一個75維的向量
        R[k]["hist_c"] = _calc_colour_hist(masked_pixels)    

         #在rgb色彩空間下,使用L1-norm歸一化獲取圖像每一個顏色通道的每一個方向的10 bins的直方圖,這樣就能夠獲取到一個240(10x8x3)維的向量
        R[k]["hist_t"] = _calc_texture_hist(tex_grad[:, :][img[:, :, 3] == k])   #tex_grad[:, :][img[:, :, 3] == k]形狀爲候選區域像素數 x 4

    return R


def _extract_neighbours(regions):
    '''
    提取 鄰居候選區域對(ri,rj)(即兩兩相交)
    
    args:
        regions:dict 每個元素都對應一個候選區域
    return:
        返回一個list,每個元素都對應一個鄰居候選區域對
    '''
    #判斷兩個候選區域是否相交
    def intersect(a, b):
        if (a["min_x"] < b["min_x"] < a["max_x"]
                and a["min_y"] < b["min_y"] < a["max_y"]) or (
            a["min_x"] < b["max_x"] < a["max_x"]
                and a["min_y"] < b["max_y"] < a["max_y"]) or (
            a["min_x"] < b["min_x"] < a["max_x"]
                and a["min_y"] < b["max_y"] < a["max_y"]) or (
            a["min_x"] < b["max_x"] < a["max_x"]
                and a["min_y"] < b["min_y"] < a["max_y"]):
            return True
        return False

    #轉換爲list 每個元素 (l,regions[l])
    R = list(regions.items())
    #保存兩兩相交候選區域對
    neighbours = []
    #每次抽取兩個候選區域 兩兩組合,判斷是否相交
    for cur, a in enumerate(R[:-1]):
        for b in R[cur + 1:]:
            if intersect(a[1], b[1]):
                neighbours.append((a, b))

    return neighbours


def _merge_regions(r1, r2):
    '''
    合併兩個候選區域
    
    args:
        r1:候選區域1
        r2:候選區域2
        
    return:
        返回合併後的候選區域rt
    '''
    new_size = r1["size"] + r2["size"]
    rt = {
        "min_x": min(r1["min_x"], r2["min_x"]),
        "min_y": min(r1["min_y"], r2["min_y"]),
        "max_x": max(r1["max_x"], r2["max_x"]),
        "max_y": max(r1["max_y"], r2["max_y"]),
        "size": new_size,
        "hist_c": (
            r1["hist_c"] * r1["size"] + r2["hist_c"] * r2["size"]) / new_size,
        "hist_t": (
            r1["hist_t"] * r1["size"] + r2["hist_t"] * r2["size"]) / new_size,
        "labels": r1["labels"] + r2["labels"]
    }
    return rt


def selective_search(
        im_orig, scale=1.0, sigma=0.8, min_size=50):
    '''Selective Search

    首先經過基於圖的圖像分割方法初始化原始區域,就是將圖像分割成不少不少的小塊
    而後咱們使用貪心策略,計算每兩個相鄰的區域的類似度
    而後每次合併最類似的兩塊,直到最終只剩下一塊完整的圖片
    而後這其中每次產生的圖像塊包括合併的圖像塊咱們都保存下來
    
    Parameters
    ----------
        im_orig : ndarray
            Input image
        scale : int
            Free parameter. Higher means larger clusters in felzenszwalb segmentation.
        sigma : float
            Width of Gaussian kernel for felzenszwalb segmentation.
        min_size : int
            Minimum component size for felzenszwalb segmentation.
    Returns
    -------
        img : ndarray
            image with region label
            region label is stored in the 4th value of each pixel [r,g,b,(region)]
        regions : array of dict
            [
                {
                    'rect': (left, top, width, height),
                    'labels': [...],
                    'size': component_size 候選區域大小,並非邊框的大小
                },
                ...
            ]
    '''
    assert im_orig.shape[2] == 3, "3ch image is expected"

    # load image and get smallest regions
    # region label is stored in the 4th value of each pixel [r,g,b,(region)] 
    #圖片分割 把候選區域標籤合併到最後一個通道上 height x width x 4  每個像素的值爲[r,g,b,(region)] 
    img = _generate_segments(im_orig, scale, sigma, min_size)

    if img is None:
        return None, {}

    #計算圖像大小
    imsize = img.shape[0] * img.shape[1]
    
    #dict類型,鍵值爲候選區域的標籤   值爲候選區域的信息,包括候選區域的邊框,以及區域的大小,顏色直方圖,紋理特徵直方圖等信息
    R = _extract_regions(img)

    #list類型 每個元素都是鄰居候選區域對(ri,rj)  (即兩兩相交的候選區域)
    neighbours = _extract_neighbours(R)

    # calculate initial similarities 初始化類似集合S = ϕ
    S = {}
    #計算每個鄰居候選區域對的類似度s(ri,rj)
    for (ai, ar), (bi, br) in neighbours:      
        #S=S∪s(ri,rj)  ai表示候選區域ar的標籤  好比當ai=1 bi=2 S[(1,2)就表示候選區域1和候選區域2的類似度
        S[(ai, bi)] = _calc_sim(ar, br, imsize)

    # hierarchal search 層次搜索 直至類似度集合爲空
    while S != {}:

        # get highest similarity  獲取類似度最高的兩個候選區域  i,j表示候選區域標籤
        i, j = sorted(S.items(), key=lambda i: i[1])[-1][0]   #按照類似度排序

        # merge corresponding regions  合併類似度最高的兩個鄰居候選區域 rt = ri∪rj ,R = R∪rt
        t = max(R.keys()) + 1.0
        R[t] = _merge_regions(R[i], R[j])

        # mark similarities for regions to be removed   獲取須要刪除的元素的鍵值        
        key_to_delete = []         
        for k, v in S.items():       #k表示鄰居候選區域對(i,j)  v表示候選區域(i,j)表示類似度
            if (i in k) or (j in k):
                key_to_delete.append(k)

        # remove old similarities of related regions 移除候選區域ri對應的全部類似度:S = S\s(ri,r*)  移除候選區域rj對應的全部類似度:S = S\s(r*,rj)
        for k in key_to_delete:
            del S[k]

        # calculate similarity set with the new region  計算新的候選區域rt對應的類似度集合St,S = S∪St
        for k in filter(lambda a: a != (i, j), key_to_delete):  #過濾除了(i,j)以外的候選區域
            n = k[1] if k[0] in (i, j) else k[0]
            #計算新的候選區域t與候選區域n之間的類似度
            S[(t, n)] = _calc_sim(R[t], R[n], imsize)

    #獲取每個候選區域的的信息  邊框、以及候選區域size,標籤
    regions = []
    for k, r in R.items():
        regions.append({
            'rect': (
                r['min_x'], r['min_y'],
                r['max_x'] - r['min_x'], r['max_y'] - r['min_y']),
            'size': r['size'],
            'labels': r['labels']
        })
            
    #img:ndarray 基於圖的圖像分割獲得的候選區域   regions:list Selective Search算法獲得的候選區域
    return img, regions
View Code

參考文章:

[1]圖像分割—基於圖的圖像分割(Graph-Based Image Segmentation)(附代碼)

[2]目標檢測(1)-Selective Search

[3]https://github.com/AlpacaDB/selectivesearch(代碼)

[4]Selective Search for Object Recognition(推薦)

[5]J.R. Uijlings, K.E. vandeSande, T. Gevers, and A.W. Smeulders. Selective search for object recognition. IJCV, 2013.

[6]相關源代碼(matlab)

[7]C++簡版代碼

相關文章
相關標籤/搜索