圖像顯著性檢測-Saliency Detection via Graph-Based Manifold Ranking
顯著性檢測是很多計算機處理的預處理,有限的計算機資源來處理數以億計的圖片,不僅耗資巨大,而且往往時間復雜度高。
那么如果說將這些資源集中在圖片的顯著性區域,就往往可以取得更好的成果,最直接的就是可以摒棄掉一些背景信息,使得“重要部分”能夠凸顯出來。目前關於圖像顯著性檢測的論文有好多種類。
其中本人正在研究的一篇是《Saliency Detection via Graph-Based Manifold Ranking》,是2013年發表在IEEE上面的一篇文章,這篇文章很經典,被引用的次數也非常多,是經典的“自下而上”方法,值得我們大家反復研讀和思考,這篇paper提到了一種核心算法“流形排序”,這種算法可以算是“page rank”也就是佩奇算法的延展,效果不錯。下面來簡單介紹一下這篇paper。
這篇文章的出發點,是有關於前景也就是目標有着很強的內相關性,放在關聯矩陣中能夠明顯看出來和背景不一樣。如下圖所示藍、紅、綠色分別代表對象-內部,背景-內部,背景-目標,最高的藍色表示對象內部之間有着很強的關聯性,根據這個特點就可以在圖像內部將圖像前景和背景分離開。

其中最重要的也是最核心的算法就是“流形排序”。下面具體講一下算法過程:

首先將圖片利用SLIC方法分割成數量相對固定的超像素集合,將每個超像素看成一個節點,再設某些點為查詢點,將超像素節點作為圖G中的節點E,構造k-正則圖,根據其他節點和查詢點之間的關聯進行排序,根據排名結果重新賦予超像素灰度值,形成顯著圖。

以上邊界為例子解釋一下算法:首先以上邊界為種子節點(queries),然后每個節點都與其周圍兩圈節點相連,也就是說“每個節點不僅與它相鄰節點相連,而且與相鄰節點相連”,再將四周節點兩兩之間相連接,如上圖所示,以上邊界所有節點為種子點,其他的為未標記節點,由於連線太多影響觀看,因此僅僅展示部分連接,實際上是每個節點都按照上述規則進行連接。連接之后根據超像素在Lab色彩空間的平均值來賦予邊的權重。其中ci與cj是控制節點在CIE Lab色彩空間節點的平均值。

再通過優化下列問題來選取最佳排名。其中y是指示向量 [y1,y2,y3,...yn],如果每一個yi對應一個節點,如果該節點為種子點,則yi為1,否則為0。dii 與 djj 是度矩陣中的的項,D=diag{d11,d22,d33,...,dnn},其中dii為從 j=1到n, wij的和。fi與fj為第i,j個節點與其他所有節點的相關性和。

其模型化簡之后得到核心模型。阿爾法 為一常數,在代碼中設置為0.99。W是關聯矩陣,wij是 i節點和 j節點的邊的權重。

得到的 f* 是一個N維向量,每一個元素為一個節點個背景種子點的相關性,歸一化該向量 [0,1] ,節點的顯著性為:

其中i表示索引圖中的超像素節點, f*(i)表示歸一化向量。
類似地,我們使用底部,左側和右側圖像邊界作為種子點來計算其他三個邊界Sb,S1和Sr的顯著性映射。我們注意到,顯著圖是用不同的指標向量y計算的,而權重矩陣和度矩陣D是固定的。也就是說,我們需要為每個圖像計算一次矩陣的逆(D-αW)。由於超像素的數量很小,因此矩陣在方程3中的逆矩陣可以有效地計算。因此,四個映射的總計算負荷很低。通過以下過程整合四個顯著性圖:

由此可以得到第一階段的顯著圖。在第二階段,要根據第一階段產生的顯著圖得到的前景超像素節點,作為前景種子點,繼續進行流形排序,再將兩幅顯著圖結合,得到最終的顯著圖。使用SC方法生成顯著性圖有兩個原因。首先,不同側面的超像素通常不相似,應該具有較大的距離。如果我們同時使用所有邊界超像素作為種子點(即,指示這些超級像素是相似的),則標記結果通常不太理想,因為這些節點不可壓縮(如圖4)。其次,它減少了不精確種子點的影響,即參考真實的突出節點被無意中選擇作為后台種子點。如圖5的第二列所示,使用所有邊界節點生成的顯著性圖很差。由於標記結果不精確,具有顯著對象的像素具有低顯著性值。通過整合四個顯著性圖,可以識別對象的一些顯著部分(盡管整個對象未被均勻地突出顯示),這為第二階段檢測過程提供了足夠的提示。

雖然顯著對象的大部分區域在第一階段突出顯示,但某些背景節點可能無法被充分抑制(參見圖4和圖5)。為了緩解這個問題並改善結果,特別是當對象出現在圖像邊界附近時,通過使用前景種子點進行排名來進一步改進顯著性圖。
clear all;close all;clc;
addpath('./function/');
%%------------------------設置參數---------------------%%
theta = 0.1; % 控制邊緣的權重
alpha = 0.99; % 控制流行排序成本函數兩個項的平衡
spnumber = 200; % 超像素的數量
imgRoot = './test/'; % 測試圖像的路徑
saldir = './saliencymap/'; % 顯著性圖像的輸出路徑
supdir = './superpixels/'; % 超像素標簽的文件路徑
mkdir(supdir);
mkdir(saldir);
imnames = dir([imgRoot '*' 'jpg']);
disp(imnames);
imname = [imgRoot imnames.name];
[input_im,w] = removeframe(imname); %預處理去掉邊框
[m,n,k] = size(input_im);
%%----------------------生成超像素--------------------%%
imname = [imname(1:end-4) '.bmp']; %SLIC軟件僅支持bmp格式的圖片
comm = ['SLICSuperpixelSegmentation' ' ' imname ' ' int2str(20) ' ' int2str(spnumber) ' ' supdir]; %<filename> <spatial_proximity_weight> <number_of_superpixels> <path_to_save_results>
system(comm);
spname = [supdir imnames.name(1:end-4) '.dat'];
%超像素標簽矩陣
fid = fopen(spname,'r');
A = fread(fid, m * n, 'uint32'); %fread(fid, N, 'str') N代表讀入元素個數, 'str'是格式類型
A = A+1; %把A變成正整數或邏輯值
B = reshape(A,[n, m]);
superpixels = B';
fclose(fid);
spnum = max(superpixels(:)); %實際的超像素數目
%%----------------------設計圖形模型--------------------------%%
%計算特征值 (mean color in lab color space)
%對於每個超像素
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); %rgbzhuan轉換成lab空間
seg_vals = reshape(lab_vals,spnum,3); % 每個超像素點的特征
% 求得邊界
%求鄰接矩陣
adjloop = zeros(spnum,spnum);
[m1 n1] = size(superpixels);
for i = 1:m1-1
for j = 1:n1-1
if(superpixels(i,j)~=superpixels(i,j+1))
adjloop(superpixels(i,j),superpixels(i,j+1)) = 1;
adjloop(superpixels(i,j+1),superpixels(i,j)) = 1;
end;
if(superpixels(i,j)~=superpixels(i+1,j))
adjloop(superpixels(i,j),superpixels(i+1,j)) = 1;
adjloop(superpixels(i+1,j),superpixels(i,j)) = 1;
end;
if(superpixels(i,j)~=superpixels(i+1,j+1))
adjloop(superpixels(i,j),superpixels(i+1,j+1)) = 1;
adjloop(superpixels(i+1,j+1),superpixels(i,j)) = 1;
end;
if(superpixels(i+1,j)~=superpixels(i,j+1))
adjloop(superpixels(i+1,j),superpixels(i,j+1)) = 1;
adjloop(superpixels(i,j+1),superpixels(i+1,j)) = 1;
end;
end;
end;
bd = unique([superpixels(1,:),superpixels(m,:),superpixels(:,1)',superpixels(:,n)']);
for i = 1:length(bd)
for j = i+1:length(bd)
adjloop(bd(i),bd(j)) = 1;
adjloop(bd(j),bd(i)) = 1;
end
end
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
% 計算關聯矩陣
valDistances = sqrt(sum((seg_vals(edges(:,1),:)-seg_vals(edges(:,2),:)).^2,2));
valDistances = normalize(valDistances); %Normalize to [0,1]
weights = exp(-valDistances/theta);
W=sparse([edges(:,1);edges(:,2)],[edges(:,2);edges(:,1)], ...
[weights;weights],spnum,spnum);
% 最優化關聯矩陣 (公式3)
dd = sum(W); D = sparse(1:spnum,1:spnum,dd); clear dd; %S = sparse(i,j,s,m,n,nzmax)由向量i,j,s生成一個m*n的含有nzmax個非零元素的稀疏矩陣S;即矩陣A中任何0元素被去除,非零元素及其下標組成矩陣S
optAff = eye(spnum)/(D-alpha*W);
mz = diag(ones(spnum,1));
mz = ~mz; %將A的對角元素設置為0
optAff = optAff.*mz;
%%-----------------------------顯著性檢測第一階段--------------------------%%
% 為每個超像素計算顯著性值
% 作為種子點的上邊界
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; %f*(i) 此向量中的每個元素表示節點與背景種子點的相關性
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(:)));
% 為每個像素分配顯著性值
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.name(1:end-4) '_stage1' '.png'];
imwrite(mapstage1,outname);
%%----------------------顯著性檢測第二階段-------------------------%%
% 自適應閾值二值化
th = mean(bsalc); %閾值被設置為整個顯著圖上的平均顯著性
bsalc(bsalc<th)=0;
bsalc(bsalc>=th)=1;
% 為每個超像素計算顯著性值
fsal = optAff*bsalc;
% 為每個像素分配顯著性值
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.name(1:end-4) '_stage2' '.png'];
imwrite(mapstage2,outname);
