【顯著性檢測】Saliency Detection via Graph-Based Manifold Ranking

Saliency Detection via Graph-Based Manifold Ranking

https://www.yuque.com/lart/papershtml

image.png

本文不是按照以前的論文那樣, 考慮顯著性目標與背景之間的對比度, 而是經過使用流形排序方法, 來使用前景/背景線索對圖像元素(像素或者區域)進行排序. node

在這種方法中, 圖像元素的顯著性是基於它們與給定種子/查詢的相關性來定義的.python

咱們將圖像表示爲一個以超像素爲節點的閉環圖。這些節點的排序是基於與背景和前景查詢的類似性,基於關聯矩陣(affinity matrices)。採用兩階段的顯著性檢測方法,有效地提取背景區域和前景顯著目標。git

同時本文也構建了一個大規模的數據集, 來進行顯著性檢測的測試評估.github

主要內容

本篇論文主要專一的是一種由下而上的方法. 算法

主要相關的幾個方法結論或者觀察.app

  1. 顯著目標檢測算法一般生成邊界框、二值前景和背景分割或顯著性圖表示每一個像素的顯著性似然。以前的大多數方法都是經過測量局部的中心和周圍的對比度整個圖像上特徵的罕見度來測量顯著性。相反,Gopalakrishnan等人將目標檢測問題表述爲圖形上的二值分割或標記任務。最顯著的種子(seeds)和數個背景種子(seeds)經過在徹底圖(complete graph)和k正則圖(k-regular graph)上的隨機漫步(random walks)的行爲來識別.
    1. 徹底圖: 是一個簡單的無向圖,其中每對不一樣的頂點之間都恰連有一條邊相連. 
    2. k正則圖: 每一個節點的度都是同樣的.(也就是鏈接的節點數量是同樣的.)
  2. 最近提出了一種使用背景先驗的方法. 主要的觀察是兩個背景區域的距離是要小於一個背景和一個顯著性目標之間的距離的. 基於此準則,將節點標記任務(顯著性對象或背景)描述爲能量最小化問題。(這個結論, 在文章後期是要用其進一步改進後的結論.)
    1. Y. C. Wei, F. Wen, W. J. Zhu, and J. Sun. Geodesic saliency using background priors. In ECCV, 2012
  3. 能夠觀察到, 背景一般具備局部或者全局的和四個邊界中的每一個的外觀連通性前景表現出外觀的連貫性和一致性**.

這篇文章中, 使用這些線索基於超像素的排序來計算像素的顯著性.less

論文方法流程

image.png

  1. 構造以超像素爲節點的閉環圖
  2. 將顯著性檢測問題建模爲一個流形排序問題
  3. 使用一個兩階段算法來進行圖的標記, 兩個階段有些流程相似, 可是實際上正好是個互補的過程.
    1. 第一階段, 利用邊界先驗, 利用圖像每一邊的節點做爲標記的背景查詢, 對於每一個標記的結果計算節點的顯著性, 基於它們與這些查詢的相關性做爲背景標籤. 四個標記圖而後用來生成一副顯著性圖.
    2. 第二階段, 對上一階段的結果進行二值分割, 將標記的前景節點做爲顯著性查詢. 每一個節點的顯著性基於其與前景查詢的相關性來計算. 以得到最終的顯著性圖.

從這個流程中, 能夠關注幾個點:dom

  • 閉環圖:
    • 什麼是閉環圖?
    • 爲何是閉環圖?
  • 流行排序問題:
    • 什麼是流行排序問題? 
    • 爲何要建模成流行排序問題?
    • 如何使用流行排序解決顯著性檢測問題?

基於圖結構的流行排序問題

基於圖的流形排序, 被描述爲: 給定一個節點做爲查詢. 剩餘節點基於與這個給定節點之間的相關性來進行排序. 目標是學習一個定義了未標記節點與查詢之間的相關性的流形函數.函數

也就是說, 文中的基於圖的流行排序實際上就是經過一個流行函數定義未標記節點與查詢節點之間的相關性. 如果有了這個與前景或者背景查詢節點的相關性, 就能夠用來排序, 與前景相關性高的就能夠用其表示顯著性, 與背景相關性高的, 就能夠用其表示非顯著性(1-f能夠用來表示顯著性).(具體細節見後).

論文[D. Zhou, J. Weston, A. Gretton, O. Bousquet, and B. Scholkopf. Ranking on data manifolds_. In NIPS, 2004_]提出了一種利用數據(如圖像)固有流形結構進行圖形標籤排序的方法.

爲何要建模成流行排序問題?
爲了充分捕捉圖的內在結構信息,並在圖的標註中加入局部分組線索(local grouping),咱們利用流形排序技術(manifold ranking)來學習排序函數,這對於學習最優親和矩陣是相當重要的.

對於該問題的建模以下:

image.png

主要是將映射  表示爲一個排序問題. 賦予一個排序值到每一個點數據點  上, 彙總獲得的 ,做爲最終獲得的排序值. 


將圖片表示爲一個無向圖結構, 節點爲超像素, 而邊的權重整體表示爲關聯矩陣  , 對應的圖所對應的度矩陣表示爲  這裏的d對應於W特定行之和.  反映了和特定節點鏈接的其餘全部節點邊的權重之和. 這個也在必定程度上能夠認爲是一種節點之間的相關程度/關聯性. 這裏計算顯著性就是用它.

是否能夠表示爲有向圖, 這樣能夠表示更爲豐富的信息, 好比特徵之間的依賴性, 不過感受這個想法不適合與這樣的自下而上的傳統方法, 由於這種有向的依賴關係, 對應的特徵應該是更爲高級的語義信息.
這裏讓我想到了分割的一個結構, DD-RNN和DAG-RNN

爲了找到最好的一個排序, 這裏將這個優化問題表述爲:

image.png

從式子1能夠了解到

  • 這個式子的第一項, 被認爲是一個平滑約束, 也就是說, 一個好的排序函數不該該在相鄰點之間變化太多(平滑度約束). 也就是在相鄰節點(w對應會很大, 致使第一項的做用影響比較大)之間, 兩者的f差別不大.
  • 這個式子的第二項, 被認爲是一個擬合約束, 也就是說, 一個好的排序函數不該該與初始查詢賦值狀況(擬合約束)相差太多. 這一項實際上限制了, f應該趨近於此時查詢點的分配狀況.

最小解, 利用導數求解, 獲得結果以下, 式子2是最終的排序結果對應的函數. 而式子3使用的是未標準化的拉普拉斯矩陣.

image.png

具體參數可見前面圖片的定義. 實際使用的是式子3. 這裏是測試比較:

image.png
The figure shows that the ranking results with the unnormalized Laplacian matrix are better, and used in all the experiments.

這裏有一點, 文中沒有細說, 最終的排序是對f中的元素進行排序, 仍是說f中的元素實際上已經排好序了 (這裏劃掉的部分, 能夠如此理解並推翻: f的索引實際上與y的索引是對應的, 也就是說,** f表示的是特定節點到全部查詢點之間的相關性之和**. 因此說, 若是索引固定, 那麼這裏應該是基於獲得的f來計算對應的顯著性值.)

實際上這裏說是排序, 其實並無進行排序操做, 只是利用這個優化問題獲得的相關性解, 來進行顯著性值的計算. 雖然這個值在必定程度上反映了顯著性的排序狀況, 可是並無具體用到排序的操做

關於式子2, 3. 兩者的差異, 前者是用的是標準化參數, 後者使用的是更爲直接的參數. 而在計算顯著性的時候, 是須要進行標準化的, 因此使用3式的時候, 仍是要進行標準化處理的. .

這裏的標準化是將數據放縮到了0~1範圍, 能夠看下面的代碼示例:

bsalt=optAff*Yt;
bsalt=(bsalt-min(bsalt(:)))/(max(bsalt(:))-min(bsalt(:)));

image.png

傳統的排序問題, 查詢是要手動用真實標記的. 然而提出的算法選擇的查詢, 一些可能被標錯了. 所以須要對每一個查詢計算一個置信度(也就是顯著性值), 這個用它的排序得分, 這是經過其餘查詢計算出來的(排除自身). 

最後, 設定A中的對角元素爲0. 這個看似可有可無的過程對最終結果有很大的影響, 這裏 若是沒有設置A的對角元素爲0來進行計算每個查詢的顯著性的時候, 結果會包含查詢與自身的相關性, 這個一般很大 (是同一行其餘項的和的負數), 會嚴重抑制其餘查詢對於排序得分的貢獻. 

由於度矩陣D的對角元素是仿射矩陣W的每行元素的和. 若是D不設定爲0, 那麼差值以後, 就會減掉自身的影響.

關於權重w, 有以下設定:

image.png

節點之間的權重使用顏色空間的距離的鐘形函數來進行建模. 方差項用來控制權重的強度. 色彩距離(差別)越大, 賦予的權重也就越小.

圖結構的劃分

image.png

這裏提到了兩點:

  • 無向圖
  • SLIC算法: http://www.javashuo.com/article/p-ntnsfdkc-gw.html
    • 算法大體思想是這樣的,將圖像從RGB顏色空間轉換到CIE-Lab顏色空間,對應每一個像素的(L,a,b)顏色值和(x,y)座標組成一個5維向量V[l, a, b, x, y],兩個像素的類似性便可由它們的向量距離來度量,距離越大,類似性越小。
    • 算法首先生成K個種子點,而後在每一個種子點的周圍空間裏搜索距離該種子點最近的若干像素,將他們歸爲與該種子點一類,直到全部像素點都歸類完畢。而後計算這K個超像素裏全部像素點的平均向量值,從新獲得K個聚類中心,而後再以這K箇中心去搜索其周圍與其最爲類似的若干像素,全部像素都歸類完後從新獲得K個超像素,更新聚類中心,再次迭代,如此反覆直到收斂。(相似與k均值聚類)

image.png

因爲相鄰節點可能具備類似的外觀和顯著性值,使用_k-regular_圖來利用空間關係。

  1. 首先,每一個節點不只鏈接到與其相鄰的節點,還鏈接到與其相鄰節點共享公共邊界的節點(參見圖2)。
    1. 經過擴展具備相同k度的節點鏈接範圍,咱們有效地利用了局部平滑線索。
  2. 其次,咱們強制鏈接圖像四邊的節點,即任何一對邊界節點都被認爲是相鄰的。
    1. 所以,咱們將該圖表示爲閉環圖。這種閉環約束能夠顯著提升算法的性能,由於它能夠減少類似超像素的測地線距離,從而改善了排序結果.
    2. 當顯著對象出如今圖像邊界附近或某些背景區域不相同時,這些閉環約束頗有效。

image.png

經過對邊的約束,能夠看出構造圖是稀疏鏈接的。也就是說,關聯矩陣  的大部分項都是零。

這裏爲何是稀疏鏈接的? 雖然從結果上來看確實是稀疏的. 可見參考連接裏的 SLIC算法測試 , 裏面有一些測試.
這裏能夠看python代碼計算W和D的部分, 有一段:

W = sp.exp(-1*W / self.weight_parameters['delta'])
# 歸屬於不一樣超像素的像素之間, 不相鄰的賦予0
W[adj.astype(np.bool)] = 0

這裏是反着取得鄰接矩陣, 因此, 不相鄰的部分對應的關聯矩陣W是要置0的. 而這裏的鄰接矩陣, 在這裏指定的關於鄰接的約束, 致使大部分節點之間是不相鄰的. 因此這裏adj是稀疏的, 致使W也是一個稀疏矩陣.

使用背景查詢排序

根據早期關於視覺顯著性[17]的注意理論,咱們將圖像邊界上的節點做爲背景種子,即,將標記的數據(查詢樣本)對全部其餘區域的相關性進行排序。具體地說,咱們使用邊界先驗構造了四個顯著性圖,而後將它們集成到最終的映射中,這被稱爲分離/組合(SC)方法.

image.png

以圖像頂部邊界爲例,使用這一側的節點做爲查詢,其餘節點做爲未標記的數據。所以, 指示向量y被給定, 全部的節點是基於式子3中的_f∗_排名, 這是一個N維向量(N是圖的節點總數)。此向量中的每一個元素表示節點與背景查詢節點的相關性, 其關於1的補數是顯著性測量.

相似的計算其餘三邊的狀況. 注意, 實際上, 在這個過程當中, 矩陣D, W只須要計算一次, 而y都是能夠肯定的.

最後進行集成:

image.png

使用相乘的方法來進行集成. 這裏有點相似於同一年的Graph-Regularized Saliency Detection
With Convex-Hull-Based Center Prior文章中集成對比度先驗與基於凸包的中心先驗的方法.

  • 這種經過相乘的方式來進行集成的方式來自哪裏? * 還有沒有其餘的集成的方式?

使用SC方法的主要的考慮是四邊同時使用的效果很差, 主要體如今兩個方面:

  1. 四邊上的超像素之間互相可能並不類似, 不可合併, 同時使用一般不是最優.
  2. 四邊同時使用對於查詢的標註準確度要求較高, 而拆分開, 可轉圜的餘地較大, 容錯性也較好.

對比結果以下圖:

image.png

使用前景查詢排序

進一步基於前景查詢排序來優化結果, 主要是爲了應對目標出如今靠近圖像邊緣的狀況.

第一階段的顯著性映射是二值分割的(即,突出的前景和背景), 使用一個自適應閾值,這有助於選擇節點的前景顯著性目標做爲查詢節點。咱們指望選擇的查詢儘量地覆蓋突出的對象區域(即)。所以,閾值被設置爲整個顯著性映射的平均顯著性

相似的, 這裏則直接可使用與查詢節點計算相關性來得到顯著性值:

image.png

這裏就不用互補計算了, 由於這裏是算的與前景的相關性.

顯著目標區域一般是相對緊湊的(在空間分佈方面)和均勻的外觀(在特徵分佈方面),而背景區域正好相反。換句話說,對象內部的相關性(即,顯著對象的兩個節點)在統計上遠大於對象背景和背景內相關性,能夠從關聯矩陣A中推斷。爲了進一步顯示這個現象, 文章從數據集真值中, 進行了測試:

image.png

To show this phenomenon, we compute the average intra-object, intra-background and object-background relevance values in A for each of the 300 images sampled from a dataset with ground truth labels.

從真值標記統計出來, 明顯能夠看出來, 目標節點到全部真值的顯著性查詢的相關性之和要大於背景節點到全部查詢的相關性之和. 這個是最優狀態的A的結果對應的解釋.

可是這個可以說明這個方法中的背景是能夠被有效抑制的麼? 能夠的.

由於A是最終的的f的一個仿射變換矩陣, 其中的值對應着f的結果, 也就是對應着最終的顯著性結果.

這裏用實驗解釋了目標內部相關性是最大的. 而這個結論在使用前景查詢進行優化提高的時候, 在必定程度上能夠改善已經預測的顯著性區域. 由於_通常顯著性目標對象區域更爲緊湊, 均勻. 在計算距離的時候, 周圍的一致性更強, 鑑於色彩空間加權的做用, 最終與相關性值會更高一些. 而先前錯誤標記的部分, 相對而言值更小了_.

A是基於原始圖像的色彩的. 從一開始就是個定值. 查詢的選擇, 影響的是y.

算法流程總結

image.png

主要流程:

  1. 超像素構建, 獲得D和W矩陣.
  2. 得到A矩陣, 並將其對角元素設定爲0.
  3. 第一階段: 四邊分別構建向量y, 計算對應的顯著性圖在進行集成獲得第二階段的輸入.
  4. 第二階段: 二分割獲得前景查詢和y, 計算最終的顯著性圖.
  5. 輸出最終的顯著性圖.

參考代碼

https://github.com/huchuanlu/13_4/blob/master/demo.m
https://github.com/ruanxiang/mr_saliency/blob/master/MR/MR.py(主要從這裏入手理解), python版本的實現與matlab實現細節有些差別, 主要是在於裏面對於第一階段的計算中, python版本是反着來的, 它把背景(邊界)的指示值設定爲了1, 最後集成的時候又用1算了個補數. 而matlab的流程設定和論文是一致的.

% Demo for paper "Saliency Detection via Graph-Based Manifold Ranking" 
% by Chuan Yang, Lihe Zhang, Huchuan Lu, Ming-Hsuan Yang, and Xiang Ruan
% To appear in Proceedings of IEEE Conference on Computer Vision and Pattern Recognition (CVPR 2013), Portland, June, 2013.

clear all;
addpath('./others/');
%%------------------------set parameters---------------------%%
theta=10; % control the edge weight 
alpha=0.99;% control the balance of two items in manifold ranking cost function
spnumber=200;% superpixel number
imgRoot='./test/';% test image path
saldir='./saliencymap/';% the output path of the saliency map
supdir='./superpixels/';% the superpixel label file path
mkdir(supdir);
mkdir(saldir);
imnames=dir([imgRoot '*' 'jpg']);

% 迭代圖片
for ii=1:length(imnames)   
    disp(ii);
    imname=[imgRoot imnames(ii).name]; 
    [input_im,w]=removeframe(imname);% run a pre-processing to remove the image frame 
    [m,n,k] = size(input_im);

%%----------------------generate superpixels使用SLIC方法來生成超像素--------------------%%
    imname=[imname(1:end-4) '.bmp'];% the slic software support only the '.bmp' image
    comm=['SLICSuperpixelSegmentation' ' ' imname ' ' int2str(20) ' ' int2str(spnumber) ' ' supdir];
    system(comm);    
    spname=[supdir imnames(ii).name(1:end-4)  '.dat'];
    % 得到超像素標記矩陣
    superpixels=ReadDAT([m,n],spname); % superpixel label matrix
    spnum=max(superpixels(:));% the actual superpixel number

%%----------------------design the graph model 計算聚攏的像素在lab顏色空間中的平均值--------------------------%%
% compute the feature (mean color in lab color space) 
% for each node (superpixels)
    input_vals=reshape(input_im, m*n, k);
    rgb_vals=zeros(spnum,1,3);
    inds=cell(spnum,1);
    for i=1:spnum
        inds{i}=find(superpixels==i);
        rgb_vals(i,1,:)=mean(input_vals(inds{i},:),1);
    end  
    lab_vals = colorspace('Lab<-', rgb_vals); 
    seg_vals=reshape(lab_vals,spnum,3);% feature for each superpixel
 
 % get edges 獲取節點之間的邊
    adjloop=AdjcProcloop(superpixels,spnum);
    edges=[];
    for i=1:spnum
        indext=[];
        ind=find(adjloop(i,:)==1);
        for j=1:length(ind)
            indj=find(adjloop(ind(j),:)==1);
            indext=[indext,indj];
        end
        indext=[indext,ind];
        indext=indext((indext>i));
        indext=unique(indext);
        if(~isempty(indext))
            ed=ones(length(indext),2);
            ed(:,2)=i*ed(:,2);
            ed(:,1)=indext;
            edges=[edges;ed];
        end
    end

% compute affinity matrix 計算關聯矩陣, 表示了各類權重關係

    weights = makeweights(edges,seg_vals,theta);
    W = adjacency(edges,weights,spnum);

% learn the optimal affinity matrix (eq. 3 in paper) 學習最優的關聯矩陣
    % 這裏是設定了一個稀疏矩陣. 
    % 使用i=1:spnum和j=1:spnum指定的座標, 賦予特定的值dd, 也就是D(i(k), j(k))=dd(k)
    % 獲得的結果中,  
    dd = sum(W); D = sparse(1:spnum,1:spnum,dd); clear dd;
    
    optAff =(D-alpha*W)\eye(spnum); 
    mz=diag(ones(spnum,1));
    mz=~mz;
    optAff=optAff.*mz;
  
%%-----------------------------stage 1--------------------------%%
% compute the saliency value for each superpixel 
% with the top boundary as the query
    Yt=zeros(spnum,1);
    bst=unique(superpixels(1,1:n));
    Yt(bst)=1;
    bsalt=optAff*Yt;
    bsalt=(bsalt-min(bsalt(:)))/(max(bsalt(:))-min(bsalt(:)));
    bsalt=1-bsalt;

% down
    Yd=zeros(spnum,1);
    bsd=unique(superpixels(m,1:n));
    Yd(bsd)=1;
    bsald=optAff*Yd;
    bsald=(bsald-min(bsald(:)))/(max(bsald(:))-min(bsald(:)));
    bsald=1-bsald;
   
% right
    Yr=zeros(spnum,1);
    bsr=unique(superpixels(1:m,1));
    Yr(bsr)=1;
    bsalr=optAff*Yr;
    bsalr=(bsalr-min(bsalr(:)))/(max(bsalr(:))-min(bsalr(:)));
    bsalr=1-bsalr;
  
% left
    Yl=zeros(spnum,1);
    bsl=unique(superpixels(1:m,n));
    Yl(bsl)=1;
    bsall=optAff*Yl;
    bsall=(bsall-min(bsall(:)))/(max(bsall(:))-min(bsall(:)));
    bsall=1-bsall;   
   
% combine 
    bsalc=(bsalt.*bsald.*bsall.*bsalr);
    bsalc=(bsalc-min(bsalc(:)))/(max(bsalc(:))-min(bsalc(:)));
    
% 這時 ,bsalc是第一階段最終的顯著性標記 
% assign the saliency value to each pixel 爲每一個像素分配顯著性值    
     tmapstage1=zeros(m,n);
     for i=1:spnum
        tmapstage1(inds{i})=bsalc(i);
     end
     tmapstage1=(tmapstage1-min(tmapstage1(:)))/(max(tmapstage1(:))-min(tmapstage1(:)));
     
     mapstage1=zeros(w(1),w(2));
     mapstage1(w(3):w(4),w(5):w(6))=tmapstage1;
     mapstage1=uint8(mapstage1*255);  

     outname=[saldir imnames(ii).name(1:end-4) '_stage1' '.png'];
     imwrite(mapstage1,outname);

%%----------------------stage2-------------------------%%
% binary with an adaptive threhold (i.e. mean of the saliency map) 自適應閾值二值化, 閾值被設置爲整個顯著圖上的平均顯著性
    th=mean(bsalc);
    bsalc(bsalc<th)=0;
    bsalc(bsalc>=th)=1;
    
% compute the saliency value for each superpixel
    fsal=optAff*bsalc;    
    
% assign the saliency value to each pixel
    tmapstage2=zeros(m,n);
    for i=1:spnum
        tmapstage2(inds{i})=fsal(i);    
    end
    tmapstage2=(tmapstage2-min(tmapstage2(:)))/(max(tmapstage2(:))-min(tmapstage2(:)));

    mapstage2=zeros(w(1),w(2));
    mapstage2(w(3):w(4),w(5):w(6))=tmapstage2;
    mapstage2=uint8(mapstage2*255);
    outname=[saldir imnames(ii).name(1:end-4) '_stage2' '.png'];   
    imwrite(mapstage2,outname);
  
end
####################################################################
## Author:
##       Xiang Ruan
##       httpr://ruanxiang.net
##       ruanxiang@gmail.com
## License:
##       GPL 2.0
##       NOTE: the algorithm itself is patented by OMRON, co, Japan
##             my previous employer, so please do not use the algorithm in
##             any commerical product
## Version:
##       1.0
##
## ----------------------------------------------------------------
## A python implementation of manifold ranking saliency
## Usage:
##      import MR
##      import matplotlib.pyplot as plt
##      mr = MR.MR_saliency()
##      sal = mr.saliency(img)
##      plt.imshow(sal)
##      plt.show()
##
## Check paper.pdf for algorithm details 
## I leave all th parameters open to maniplating, however, you don't
## have to do it, default values work pretty well, unless you really
## know what you want to do to modify the parameters


import scipy as sp
import numpy as np
import cv2
from skimage.segmentation import slic
from skimage.segmentation import mark_boundaries
from skimage.data import camera
from scipy.linalg import inv
import matplotlib.pyplot as plt

cv_ver = int(cv2.__version__.split('.')[0])
_cv2_LOAD_IMAGE_COLOR = cv2.IMREAD_COLOR if cv_ver >= 3 else cv2.CV_LOAD_IMAGE_COLOR

class MR_saliency(object):
    """Python implementation of manifold ranking saliency"""
    weight_parameters = {'alpha':0.99,
                         'delta':0.1}
    superpixel_parameters = {'segs':200,
                             'compactness':10,
                             'max_iter':10,
                             'sigma':1,
                             'spacing':None,
                             'multichannel':True,
                             'convert2lab':None,
                             'enforce_connectivity':False,
                             'min_size_factor':0.5,
                             'max_size_factor':3,
                             'slic_zero':False}
    binary_thre = None

    def __init__(self, alpha = 0.99, delta = 0.1,
                 segs = 200, compactness = 10,
                 max_iter = 10, sigma = 1,
                 spacing = None, multichannel = True,
                 convert2lab = None, enforce_connectivity = False,
                 min_size_factor = 0.5, max_size_factor = 3,
                 slic_zero = False):
        self.weight_parameters['alpha'] = alpha
        self.weight_parameters['delta'] = delta
        self.superpixel_parameters['segs'] = segs
        self.superpixel_parameters['compactness'] = compactness
        self.superpixel_parameters['max_iter'] = max_iter
        self.superpixel_parameters['sigma'] = sigma
        self.superpixel_parameters['spacing'] = spacing
        self.superpixel_parameters['multichannel'] = multichannel
        self.superpixel_parameters['convert2lab'] = convert2lab
        self.superpixel_parameters['enforce_connectivity'] = enforce_connectivity
        self.superpixel_parameters['min_size_factor'] = min_size_factor
        self.superpixel_parameters['max_size_factor'] = max_size_factor
        self.superpixel_parameters['slic_zero'] = slic_zero

    def saliency(self,img):
        """
        主要的處理函數, 反映了算法的主要流程
        """
        # read image
        img = self.__MR_readimg(img)
        # superpixel
        # labels獲得的是什麼: 對於各個像素的超像素劃分的標記
        labels = self.__MR_superpixel(img)
        # affinity matrix
        aff = self.__MR_affinity_matrix(img,labels)
        # first round
        first_sal = self.__MR_first_stage_saliency(aff,labels)
        # second round
        fin_sal = self.__MR_final_saliency(first_sal, labels,aff)
        return self.__MR_fill_superpixel_with_saliency(labels,fin_sal)

    
    def __MR_superpixel(self,img):
        """
        超像素劃分
        """
        return slic(img,
                    self.superpixel_parameters['segs'],
                    self.superpixel_parameters['compactness'],
                    self.superpixel_parameters['max_iter'],
                    self.superpixel_parameters['sigma'],
                    self.superpixel_parameters['spacing'],
                    self.superpixel_parameters['multichannel'],
                    self.superpixel_parameters['convert2lab'],
                    self.superpixel_parameters['enforce_connectivity'],
                    self.superpixel_parameters['min_size_factor'],
                    self.superpixel_parameters['max_size_factor'],
                    self.superpixel_parameters['slic_zero'])

    def __MR_superpixel_mean_vector(self,img,labels):
        """
        返回關於關於每一個超像素的三個顏色通道的均值.
        """      
        s = sp.amax(labels)+1
        vec = sp.zeros((s,3)).astype(float)
        # 每一個超像素的獲得的顏色均值是三個值, 三個通道各自一個均值.
        for i in range(s):
            mask = labels == i
            # img[mask]表示圖像上被標記爲同一個超像素的像素值
            super_v = img[mask].astype(float)
            mean = sp.mean(super_v,0)
            vec[i] = mean
        return vec

    def __MR_affinity_matrix(self,img,labels):   
        """
        得到關聯矩陣A
        """        
        W,D = self.__MR_W_D_matrix(img,labels)
        # 得到矩陣A
        aff = inv(D-self.weight_parameters['alpha']*W)
        aff[sp.eye(sp.amax(labels)+1).astype(bool)] = 0.0 # diagonal elements to 0
        return aff

    def __MR_saliency(self,aff,indictor):
        """
        計算A*y
        """
        return sp.dot(aff,indictor)

    def __MR_W_D_matrix(self,img,labels):
        """
        得到矩陣W和D
        """      
        s = sp.amax(labels)+1
        vect = self.__MR_superpixel_mean_vector(img,labels)
        
        # 獲取超像素之間的鄰接矩陣, 這裏鄰接關係對應着False
        adj = self.__MR_get_adj_loop(labels)
        
        W = sp.spatial.distance.squareform(sp.spatial.distance.pdist(vect))
        W = sp.exp(-1*W / self.weight_parameters['delta'])
        # 歸屬於不一樣超像素的像素之間, 不相鄰的賦予0
        W[adj.astype(np.bool)] = 0
    
        D = sp.zeros((s,s)).astype(float)
        for i in range(s):
            D[i, i] = sp.sum(W[i])

        return W,D

    def __MR_boundary_indictor(self,labels):
        """
        這裏將四個邊界像素所在超像素指示值都設定爲0
        """
        s = sp.amax(labels)+1
        up_indictor = (sp.ones((s,1))).astype(float)
        right_indictor = (sp.ones((s,1))).astype(float)
        low_indictor = (sp.ones((s,1))).astype(float)
        left_indictor = (sp.ones((s,1))).astype(float)
    
        upper_ids = sp.unique(labels[0,:]).astype(int)
        right_ids = sp.unique(labels[:,labels.shape[1]-1]).astype(int)
        low_ids = sp.unique(labels[labels.shape[0]-1,:]).astype(int)
        left_ids = sp.unique(labels[:,0]).astype(int)

        up_indictor[upper_ids] = 0.0
        right_indictor[right_ids] = 0.0
        low_indictor[low_ids] = 0.0
        left_indictor[left_ids] = 0.0

        return up_indictor,right_indictor,low_indictor,left_indictor

    def __MR_get_adj_loop(self, labels):
        """
        獲取超像素的鄰接矩陣
        """      
        # 總的超像素數量, amax方法會返回最大的類別值
        s = sp.amax(labels) + 1
        # 超像素鄰接矩陣預約義
        adj = np.ones((s, s), np.bool)
                
        # 對圖像的各個像素的超像素標記進行遍歷
        for i in range(labels.shape[0] - 1):
            for j in range(labels.shape[1] - 1):
                # 下面的四個判斷, 檢查了以(i,j)爲左上角的一個2x2像素區域四個像素之間的連通關係
                if labels[i, j] != labels[i+1, j]:
                    # (i,j)與(i+1,j)不位於同一個超像素中, 就在超像素鄰接矩陣中對應位置置爲False
                    # 注意, 有兩個位置, 由於鄰接矩陣能夠表示有向圖.
                    adj[labels[i, j]  ,   labels[i+1, j]]                = False
                    adj[labels[i+1, j],   labels[i, j]]                  = False
                if labels[i, j] != labels[i, j + 1]:
                    # (i,j)與(i,j+1)
                    adj[labels[i, j]  ,   labels[i, j+1]]                = False
                    adj[labels[i, j+1],   labels[i, j]]                  = False
                if labels[i, j] != labels[i + 1, j + 1]:
                    # (i,j)與(i+1,j+1)
                    adj[labels[i, j]    ,  labels[i+1, j+1]]             = False
                    adj[labels[i+1, j+1],  labels[i, j]]                 = False
                if labels[i + 1, j] != labels[i, j + 1]:
                    # (i+1,j)與(i,j+1)
                    adj[labels[i+1, j],   labels[i, j+1]]                = False
                    adj[labels[i, j+1],   labels[i+1, j]]                = False
        # 這裏循環結束後, 獲得的adj中的True表示的是圖像像素的超像素標記並非相鄰的.(這兩個超像素的
        # 像素之間並不相鄰), 而對應的False表示的是超像素的元素之間是相鄰的.
        
        # 這裏肯定了四個邊上的像素對應的超像素標記, 這裏會查找特定向量的惟一超像素標記的集合
        upper_ids = sp.unique(labels[0,:]).astype(int)
        right_ids = sp.unique(labels[:,labels.shape[1]-1]).astype(int)
        low_ids = sp.unique(labels[labels.shape[0]-1,:]).astype(int)
        left_ids = sp.unique(labels[:,0]).astype(int)
        
        # np.append會拼接指定的向量. 四個邊拼接起來, 獲得的bd表示被認爲是背景的超像素標記
        bd = np.append(upper_ids, right_ids)
        bd = np.append(bd, low_ids)
        bd = sp.unique(np.append(bd, left_ids))
        
        for i in range(len(bd)):
            for j in range(i + 1, len(bd)):
                # 任意兩個包含邊界像素的超像素, 對應的鄰接關係也被設置爲False
                adj[bd[i], bd[j]] = False
                adj[bd[j], bd[i]] = False
                # 這裏的循環結束後, adj表示的是全部包含邊界像素的超像素之間都認爲是元素相鄰的, 這裏設定爲False
        
        return adj
        
    def __MR_fill_superpixel_with_saliency(self,labels,saliency_score):
        """
        爲各個超像素區域賦予對應的顯著性得分
        """      
        sa_img = labels.copy().astype(float)
        for i in range(sp.amax(labels)+1):
            mask = labels == i
            sa_img[mask] = saliency_score[i]
        return cv2.normalize(sa_img,None,0,255,cv2.NORM_MINMAX)

    def __MR_first_stage_saliency(self,aff,labels):
        """
        獲取邊界查詢(種子)對應的圖得分.
        """
        # 邊界指示對應爲0
        up,right,low,left = self.__MR_boundary_indictor(labels)
        
        # 計算的是節點到非邊界元素的相關性的補數, 也就是非顯著性圖
        up_sal = 1-self.__MR_saliency(aff,up) # sp.dot(aff, up)
        up_img = self.__MR_fill_superpixel_with_saliency(labels,up_sal)
    
        right_sal = 1-self.__MR_saliency(aff,right)
        right_img = self.__MR_fill_superpixel_with_saliency(labels,right_sal)

        low_sal = 1-self.__MR_saliency(aff,low)
        low_img = self.__MR_fill_superpixel_with_saliency(labels,low_sal)
    
        left_sal = 1-self.__MR_saliency(aff,left)
        left_img = self.__MR_fill_superpixel_with_saliency(labels,left_sal)

        # 使用非顯著性圖乘積的補數來做爲顯著性圖
        return 1-up_img*right_img*low_img*left_img

    def __MR_second_stage_indictor(self,saliency_img_mask,labels):
        s = sp.amax(labels)+1
        # get ids from labels image
        ids = sp.unique(labels[saliency_img_mask]).astype(int)
        # indictor
        indictor = sp.zeros((s,1)).astype(float)
        indictor[ids] = 1.0
        return indictor

    def __MR_final_saliency(self,integrated_sal, labels, aff):
        # get binary image
        if self.binary_thre == None:
            thre = sp.median(integrated_sal.astype(float))

        mask = integrated_sal > thre
        # get indicator
        ind = self.__MR_second_stage_indictor(mask,labels)
    
        return self.__MR_saliency(aff,ind)

    # read image
    def __MR_readimg(self,img):
        if isinstance(img,str): # a image path
            img = cv2.imread(img, _cv2_LOAD_IMAGE_COLOR)
        img = cv2.cvtColor(img,cv2.COLOR_RGB2LAB).astype(float)/255
        h = 100
        w = int(float(h)/float(img.shape[0])*float(img.shape[1]))
        return cv2.resize(img,(w,h))

參考連接

相關文章
相關標籤/搜索