在基於深度學習的目標檢測算法的綜述 那一節中我們提到基於區域提名的目標檢測中廣泛使用的選擇性搜索算法。並且該算法后來被應用到了R-CNN,SPP-Net,Fast R-CNN中。因此我認為還是有研究的必要。
傳統的目標檢測算法大多數以圖像識別為基礎。一般可以在圖片上使用窮舉法或者滑動窗口選出所有物體可能出現的區域框,對這些區域框提取特征並進行使用圖像識別分類方法,得到所有分類成功的區域后,通過非極大值抑制輸出結果。
在圖片上使用窮舉法或者滑動窗口選出所有物體可能出現的區域框,就是在原始圖片上進行不同尺度不同大小的滑窗,獲取每個可能的位置。而這樣做的缺點也顯而易見,復雜度太高,產生了很多的冗余候選區域,而且由於不可能每個尺度都兼顧到,因此得到的目標位置也不可能那么准,在現實當中不可行。而選擇性搜索有效地去除冗余候選區域,使得計算量大大的減小。
我們先來看一組圖片,由於我們事先不知道需要檢測哪個類別,因此第一張圖的桌子、瓶子、餐具都是一個個候選目標,而餐具包含在桌子這個目標內,勺子又包含在碗內。這張圖展示了目標檢測的層級關系以及尺度關系,那我們如何去獲得這些可能目標的位置呢。我們能不能通過視覺特征去減少候選框的數量並提高精確度呢。
可用的特征有很多,到底什么特征是有用的呢?我們看第二副圖片的兩只貓咪,他們的紋理是一樣的,因此紋理特征肯定不行了。而如果通過顏色則能很好區分。但是第三幅圖變色龍可就不行了,這時候邊緣特征、紋理特征又顯得比較有用。而在最后一幅圖中,我們很容易把車和輪胎看作是一個整體,但是其實這兩者的特征差距真的很明顯啊,無論是顏色還是紋理或是邊緣都差的太遠了。而這這是幾種情況,自然圖像那么多,我們通過什么特征去區分?應該區分到什么尺度?
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中所有區域對應的邊框
首先通過基於圖的圖像分割方法初始化原始區域,就是將圖像分割成很多很多的小塊。然后我們使用貪心策略,計算每兩個相鄰的區域的相似度,然后每次合並最相似的兩塊,直到最終只剩下一塊完整的圖片。然后這其中每次產生的圖像塊包括合並的圖像塊我們都保存下來,這樣就得到圖像的分層表示了呢。那我們如何計算兩個圖像塊的相似度呢?
二 保持多樣性的策略
區域合並采用了多樣性的策略,如果簡單采用一種策略很容易錯誤合並不相似的區域,比如只考慮紋理時,不同顏色的區域很容易被誤合並。選擇性搜索采用三種多樣性策略來增加候選區域以保證召回:
- 多種顏色空間,考慮RGB、灰度、HSV及其變種等
- 多種相似度度量標准,既考慮顏色相似度,又考慮紋理、大小、重疊情況等。
- 通過改變閾值初始化原始區域,閾值越大,分割的區域越少。
1、顏色空間變換
通過色彩空間變換,將原始色彩空間轉換到多達八中的色彩空間。作者采用了8中不同的顏色方式,主要是為了考慮場景以及光照條件等。這個策略主要應用於中圖像分割算法中原始區域的生成(兩個像素點的相似度計算時,計算不同顏色空間下的兩點距離)。主要使用的顏色空間有:(1)RGB,(2)灰度I,(3)Lab,(4)rgI(歸一化的rg通道加上灰度),(5)HSV,(6)rgb(歸一化的RGB),(7)C,(8)H(HSV的H通道)
2、區域相似度計算
我們在計算多種相似度的時候,都是把單一相似度的值歸一化到[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的公式表達為:


1、單一策略評估
- 使用RGB色彩空間(基於圖的圖像分割會利用不同的色彩進行圖像區域分割)
- 采用四種相似度計算的組合方式
- 設置圖像分割的閾值k=50
然后通過改變其中一個策略參數,獲取MABO性能指標如下表(第一列為改變的參數,第二列為MABO值,第三列為獲取的候選區的個數):
表中左側為不同的相似度組合,單獨的,我們可以看到紋理相似度表現最差,MABO為0.581,其他的MABO值介於0.63和0.64之間。當使用多種相似度組合時MABO性能優於單種相似度。表的右上角表名使用HSV顏色空間,有463個候選區域,而且MABO值最大為0.693。表的右下角表名使用較小的閾值,會得到更多的候選區和較高的MABO值。
2、多樣性策略組合
我們使用貪婪的搜索算法,把單一策略進行組合,會獲得較高的MABO,但是也會造成計算成本的增加。下表給出了三種組合的MABO性能指標:
上圖中的綠色邊框為對象的標記邊框,紅色邊框為我們使用 'Quality' Selective Search算法獲得的Overlap最高的候選框。可以看到我們這個候選框和真實標記非常接近。
下表為和其它算法在VOC 2007測試集上的比較結果:
下圖為各個算法在選取不同候選區數量,Recall和MABO性能的曲線圖,從計算成本、以及性能考慮,Selective Search Fast算法在2000個候選區時,效果較好。
五、代碼實現
我們可以通過下面命令直接安裝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
參考文章:
[1]圖像分割—基於圖的圖像分割(Graph-Based Image Segmentation)(附代碼)
[3]https://github.com/AlpacaDB/selectivesearch(代碼)
[4]Selective Search for Object Recognition(推薦)
[7]C++簡版代碼