Rigion Proposal開山之作,其實網上已經有很多博客了,但是都是就其理論說明,沒有很好的直觀展示,有時候概念性的東西比較不容易腦補,晦澀難懂。
首先,給出代碼出處:
import selectivesearch
#如果沒有此庫,pip install selectivesearch 安裝
SS算法第一步需要進行的是圖像分割,即大名鼎鼎的felzenszwalb算法,下面先就這個算法進行講解:
其實在這個算法裏有這麼幾個可調參數,sigma,k,kernel,min_size,其中,sigma和kernel是GaussianBlur的參數
首先理解無向圖:
圖是若干個頂點(Vertices)和邊(Edges)相互連接組成的。邊僅由兩個頂點連接,並且沒有方向的圖稱爲無向圖。一說到圖就可能涉及到深度優先算法和廣度優先算法,這裏就不做說明,具體的都可以百度到。
假設我給出了兩個圖像(3x3x3),記爲圖像A和B,左A,右B,爲了後面好說明問題,如下圖所示:
我們就對這兩個圖像進行圖像分割,這裏先貼出分割後的結果,左圖C是A的結果,右圖D是B的結果:
如下圖是對建立無向圖的邊的一些簡單說明:
這裏假設了一些數據,這些數據就是上圖A和B的數據,因爲是3x3的矩陣,所以一共生成了24個邊,每個邊標記了連接的像素節點和和像素節點間的距離,圖中已經給出了計算方式,有了這些數據後,開始建立無向圖,先貼出代碼:
#mini_size = 3; 也可以設置成2,可以看下效果
# 構建無向圖(樹)
class universe(object):
def __init__(self, n, k):
self.f = np.array([i for i in range(n)]) # 樹
# [0 1 2 3 4 5 6 7 8]
self.r = np.zeros_like(self.f) # root
# [0 0 0 0 0 0 0 0 0]
self.s = np.ones((n)) # 存儲像素點的個數
# [1. 1. 1. 1. 1. 1. 1. 1. 1.]
self.t = np.ones((n)) * 60 # 存儲不相似度 這裏50是k值,爲了容易理解,就直接寫60
# [1. 1. 1. 1. 1. 1. 1. 1. 1.]
self.k = k
def find(self, x): # Find root of node x
if x == self.f[x]:
return x
return self.find(self.f[x])
def join(self, a, b): # Join two trees containing nodes n and m
if self.r[a] > self.r[b]:
self.f[b] = a
self.s[a] += self.s[b]
else:
self.f[a] = b
self.s[b] += self.s[a]
if self.r[a] == self.r[b]:
self.r[b] += 1
u = universe(num, k)
#第一個循環
for edge in edges:
a, b = u.find(int(edge[0])), u.find(int(edge[1]))
if ((a != b) and (edge[2] <= min(u.t[a], u.t[b]))):
# 更新類標號:將類a,b標號統一爲的標號a。更新該類的不相似度閾值爲:k / (u.s[a]+u.s[b])
u.join(a, b)
a = u.find(a)
u.t[a] = edge[2] + k / u.s[a]
#第二個循環
for edge in edges:
a, b = u.find(int(edge[0])), u.find(int(edge[1]))
if ((a != b) and ((u.s[a] < min_size) or u.s[b] < min_size)):
# 分割後會有很多小區域,當區域像素點的個數小於min_size時,選擇與其差異最小的區域合併
u.join(a, b)
那麼這段代碼具體說了個什麼意思呢?就是我先將這個edges矩陣按照距離方式從小到大排列,那麼我總是按照最小的距離值進行搜索,這裏就用到圖的概念了,下面舉一個比較直觀的例子,定義爲圖E:
我們先要根據像素點(節點)找到它的標號也就是代碼中的a和b值,圖E的例子是 (15, 14, 0),這個距離是最小的,爲0,如果a!=b 且距離<=t中的不相似度閾值,t的更新計算公式如代碼,有個超參數k是可調的,則合併像素點,並且在f中進行標記,那麼這是f的值爲[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 14 16 17],且在14的位置進行root標記,r的結果爲[0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0],存儲該位置的像素點的個數
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1.],不相似度矩陣t的結果爲[50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 0.5 50. 50. 50. ],合併14和15像素點。
下一個邊爲(14,15,0),因爲已經被標記過了跳過計算;
下一個邊爲(1,0,50),前提是能從14和15找到1,0嗎,顯然不行,所以要另起爐竈,將1和0像素點合併,f的值爲[ 0 0 2 3 4 5 6 7 8 9 10 11 12 13 14 14 16 17],r的值爲[1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0],s的值爲[2. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1.],t的值爲[50.5 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 0.5 50. 50. 50. ]
下一個邊爲(2,1,50),因爲此時節點1和0節點是相連的了,所以a和b的值爲(2,0),合併0和1和2像素,這裏我直接給出值:
[ 0 0 0 3 4 5 6 7 8 9 10 11 12 13 14 14 16 17]
[1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[3. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1.]
[50.33333333 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 0.5 50. 50. 50. ]
下一個邊爲(3,2,50),這裏我們看一下,3沒有與任何節點相連,2與節點0相連,那麼a和b的值爲(3,0),滿足合併條件繼續合併,也直接給出值:
[ 0 0 0 0 4 5 6 7 8 9 10 11 12 13 14 14 16 17]
[1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]
[4. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 2. 1. 1. 1.]
[50.25 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 50. 0.5 50. 50. 50. ]
下一個邊爲(5. 4. 50),這裏5和4顯然沒有與任何節點相連,那麼又要另起爐竈了,滿足和並條件合併,這裏就下不給值了,我們往下看
下一個邊爲(7,6,50),這裏7和6顯然沒有與任何節點相連,那麼又要另起爐竈了,滿足和並條件合併
下一個邊爲(8,7,50),7顯然與6相連,那麼a和b的值爲(8,6),合併6,7,8像素
下一個邊爲(10,9,50),另起爐竈合併
下一個邊爲(13,12,50),另起爐竈合併
[50.25 50. 50. 50. 50.5 50. 50.33333333 50. 50. 50.5 50. 50. 50.5 50. 0.5 50. 50. 50. ]
下一個邊爲(16,15,50),14與15已經合併,那麼a和b的值爲(16,14),等下貌似符合合併條件,但是,別忘了還有個min(t[a],t[b]),14位置的不相似度閾值已經是0.5了,所以50大於0.5,當然也不滿足合併條件樓
下一個邊爲(17,16,50),這兩個邊都沒有標記過,那麼我們就講究湊合着合併吧
下一個邊是(0,1,50),顯然0和1已經連接過了,a和b的值爲(0,0),那麼不符合a!=b的條件,統統放棄
至此所有的像素合併結束,其實就是按圖索驥。。。,這個例子似乎一直是從左到右的在合併,其實上下也在進行,只不過要看左右上下的距離排序,圖B就是先合併的上下。
#########################################
此時來到第二個循環,一句話理解,分割後會有很多的小區域,當區域像素點的個數小於min_size時,選擇與其差異最小的區域合併,用圖說話,圖E設置了min_size爲2,效果如上,如果將min_size設置爲3呢
設置爲4和100呢,100就成了一個顏色了,設置成1和2的效果是一樣的
至此,圖像分割算法就完成了,接着往下走,這次就拿真實圖片來講,左邊是真實圖片,右邊是分割完的圖片,直接是selectivesearch裏面產生的效果:
下面進入另一個函數,
def _extract_regions(img):
區域合併採取了多樣性的策略,如果簡單採用一種策略很容易錯誤合併不相似區域,比如只考慮紋理,不同顏色的區域很容易被錯誤合併,選擇性搜索採用三種多樣性策略來增加候選區域以保證找回;1.多種顏色空間,考慮RGB,灰度,HSV及其變種;2.多種相似度度量標準,即考慮顏色相似度,又考慮紋理、大小、重疊情況;3.通過改變閾值初始化原始區域,閾值越大,分割的區域越少。
由RBG顏色空間到HSV顏色空間
計算texture_gradient,效果如圖:
區域相似度計算:
顏色相似度:使用L1-normalize歸一化獲取每個顏色通道的25bins的直方圖,這樣每個區域都可以得到一個75維的向量,具體請看代碼
def _calc_colour_hist(img):
紋理相似度計算:這裏紋理採用SIFT-LIKE特徵,具體做法是對每個顏色通道的8個不同方向計算方差 delt = 1的高斯微分,使用L1-normalize歸一化獲取圖像每個顏色通道的每個方向的10bins的直方圖,這樣就可以獲取到一個240(10x8x3)維的向量,區域間紋理相似度與顏色相似度計算類似。具體請看代碼
def _calc_texture_hist(img):
區域合併:
優先合併小的區域,如果僅僅是通過顏色和紋理特徵合併的話,很容易使合併後的區域不斷吞噬周圍的區域,後果就是多尺度只應用在了那個局部,而不是全局的多尺度。因此我們給小的區域更多的權重,這樣保證在圖像每個位置都是多尺度合併。
代碼:
def _sim_size(r1, r2, imsize):
"""
calculate the size similarity over the image
"""
return 1.0 - (r1["size"] + r2["size"]) / imsize
如果區域ri包含在rj內,我們首先應該合併,另一方面,如果ri和rj相接,他們之間會形成斷崖,不應該合併在一塊。這裏定義區域的合適度距離主要是爲了衡量兩個區域是都更加吻合,其指標是合併後的bounding box越小,其相似度越近,代碼中是bbsize
代碼:
def _sim_fill(r1, r2, imsize):
"""
calculate the fill similarity over the image
"""
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):
return (_sim_colour(r1, r2) + _sim_texture(r1, r2)
+ _sim_size(r1, r2, imsize) + _sim_fill(r1, r2, imsize))
完畢!!關於直方圖可視化後續再加上吧!!!