本代碼內容是關於Saliency Detection via Graph-Based Manifold Ranking的算法詳解,想要運行此代碼還需要一系列的文件,單純此代碼無法演示結果的。
可以在網上搜索完整文件,簡單來說就是基本的SLIC小程序,其余的不是必要項。
將數據集直接放入test文件夾中,運行sample或者demo都可以吧,我的是師兄傳給我的,跟各位下載的結果應該差別不大。
(為了避免大家浪費時間,這里貼出百度雲下載鏈接,鏈接:
關注並且私信我發鏈接
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]; %設置啟用SLIC.exe前參數
%<filename> <spatial_proximity_weight> <number_of_superpixels> <path_to_save_results>
system(comm); %啟用SLIC.exe,將每一個像素按照超像素的區域分類,同一區域的超像素賦相同的值,值與此區域的顏色亮度無關
spname = [supdir imnames.name(1:end-4) '.dat'];
%超像素標簽矩陣
fid = fopen(spname,'r');%fid是文件代號(句柄)
A = fread(fid, m * n, 'uint32'); %fread(fid, N, 'str') N代表讀入元素個數, 'str'是格式類型,將此文件順序讀出來,m*n個像素點逐行掃描
A = A+1; %把A變成正整數或邏輯值
B = reshape(A,[n, m]);%將順序讀出的值改為原圖像的二維格式
superpixels = B';%B的轉置矩陣,轉成m*n,從列開始,在同一超像素區域內的像素賦同一值,值僅作為計數用,值為n,表示第n個超像素
fclose(fid);
spnum = max(superpixels(:)); %實際的超像素數目
%%----------------------設計圖形模型--------------------------%%
%計算特征值 (mean color in lab color space)
%對於每個超像素
input_vals = reshape(input_im, m*n, k);%將原圖像按行掃描轉換為(m*n)*3的矩陣,即m*n行,3列的矩陣,分別為rgb
%input_im的值為0-1之間,是正常的rgb取值范圍,全0表示黑,全1表示白
rgb_vals = zeros(spnum,1,3);
inds = cell(spnum,1);
for i = 1:spnum%從1到spnum,找到superpixels值相同的像素,放到一個cell中
inds{i} = find(superpixels==i);
rgb_vals(i,1,:) = mean(input_vals(inds{i},:),1);%input_vals每一行代表一個像素,將inds{i}中的所有對應的input_vals中的第幾行,也就是
%第幾個像素,取平均值,rgb_vals(i,1,1)也就是第i個超像素的r值
end
lab_vals = colorspace('Lab<-', rgb_vals); %rgb轉換成lab空間
seg_vals = reshape(lab_vals,spnum,3); % 每個超像素點的特征,將三維向量lab_vals變為二維向量,
%也就是以每一個超像素(一行Lab為特征)為單位,seg_vals(1,1)表示第1個超像素的L值
% 求得邊界
%求鄰接矩陣
adjloop = zeros(spnum,spnum);%鄰接矩陣,不相連為0,相連為1,默認自身不相連
[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;%superpixels(i,j)的值代表第幾個超像素
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 %將邊界超像素賦值1,是將邊界超像素相連
edges = [];
for i = 1:spnum; %真實超像素總數
indext = [];
ind = find(adjloop(i,:)==1);%鄰接矩陣行i為1的項,也就是找到所有與第i個超像素相連接的超像素
for j = 1:length(ind)
indj = find(adjloop(ind(j),:)==1);%indj僅僅起到過渡作用,將與第i個超像素相連接的所有像素的連接情況一行一行的傳遞到indext中
indext = [indext,indj];
end
indext = [indext,ind];%indext是第i個超像素連接的超像素的連接情況,ind是第i個超像素的連接情況
indext = indext((indext>i));%只保留indext中值大於i的項
indext = unique(indext);%合並重復項,只保留不重復的項,即獲取矩陣indext的不同元素構成的向量
%其目的是將第i個超像素的兩圈節點與i連接,例如1超像素周圍一圈再加邊界一共52個與1相連,再加上這52個節點的周圍節點也與1相連接,一共92個節點與1相連
if(~isempty(indext))
ed = ones(length(indext),2);%
ed(:,2) = i*ed(:,2);
ed(:,1) = indext;%第一列是列舉了與i節點相連接的節點,第二列的值就全為i,因為1*i=i
edges = [edges;ed];%edges是加上第二圈后的連接邊矩陣,一共兩列,第一列是加上第二圈后與第i個節點相連接的所有節點(為避免重復運算增加運算速度,列舉出第i個節點的所有連接點后,列舉第i+1個節點的所有連接點時,去掉第i個節點),第二列是i
end
end
% 計算關聯矩陣
valDistances = sqrt(sum((seg_vals(edges(:,1),:)-seg_vals(edges(:,2),:)).^2,2));
%b=sum(a,dim); a表示矩陣;dim等於1或者2,1表示每一列進行求和,2表示每一行進行求和
%對於valDistances來說,valDistances(1)=sqrt(([1連接的超像素列的第一個的L]-[1的L])^2+[1連接的超像素列的第一個的a]-[1的a])^2+[1連接的超像素列的第一個的b]-[1的b])^2)
valDistances = normalize(valDistances); %Normalize to [0,1]
weights = exp(-valDistances/theta);%theta=0.1
W=sparse([edges(:,1);edges(:,2)],[edges(:,2);edges(:,1)], ...%將weight和edges合並,更加直觀
[weights;weights],spnum,spnum);
% sparse函數用法:
% 例如:
% 0, 0, 0, 0;
% 0, 0, 1, 0;
% 0, 0, 0, 0;
% 0, 1, 0, 2;
% 計算機存儲稀疏矩陣可以有兩種思路:
% 1.按照存儲一個普通矩陣一樣存儲一個稀疏矩陣,比如上面這個稀疏矩陣中總共十六個元素(三個非零元素),把這些元素全部放入存儲空間中。這種存儲方式,在matlab就叫做full storage organization。
% 2.只存儲非零元素,那么怎么存儲呢?
% (4,2) 1
% (2,3) 1
% (4,4) 2
% 最優化關聯矩陣 (公式3) f∗ = y/(D − αW) .
dd = sum(W); %sum函數不寫第二個參數默認為列求和
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);% eye函數返回單位矩陣,Y = eye(n):返回n*n單位矩陣
mz = diag(ones(spnum,1));%將mz設置為方陣,方陣大小為spnum*spnum,內容全為1
mz = ~mz; %將A的對角元素設置為0
optAff = optAff.*mz;%將optAff的對角元素歸零,因為超像素自身和自身之間的關聯性並不考慮,要考慮的話也應該是無限大,沒有數值,此矩陣用來表示各個超像素之間的關聯性
%%-----------------------------顯著性檢測第一階段--------------------------%%
% 為每個超像素計算顯著性值
% 作為種子點的上邊界
% top
Yt = zeros(spnum,1);%指示向量
bst = unique(superpixels(1,1:n));
Yt(bst) = 1;%將上邊界的超像素賦值為1
bsalt = optAff*Yt;%(spnum*spnum)*(spnum*1),表示從1到spnum的所有超像素對於上邊界超像素的關聯性和
bsalt = (bsalt-min(bsalt(:)))/(max(bsalt(:))-min(bsalt(:))); %括號內只有一個冒號表示歷遍所有元素,將所有元素放進【0,1】中,即正規化數據
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;%optAff是所有超像素對所有超像素的關聯性,只選取其中的前景部分,即所有前景的顯著性值
% 為每個像素分配顯著性值
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);
根據代碼內容我有感而發,f∗ = y/(D − αW)此函數算出所有超像素的關聯性后,稍微整理一下就可以當做顯著值賦予灰度值,至於論文中提到的兩次流形排序,
其實在代碼中只有一次用到了此函數計算了所有超像素之間的關聯性(127-135)。第一次和第二次都只是分別用了類似於指示向量的向量提取出來而已,本質上沒有進行兩次流形排序,
而是算了一次總的,而后再分別提取,算是簡化了過程吧,至於模型的化簡,仍然還是比較復雜的,需要摸索的,本文僅僅對代碼詳解注釋,希望對大家的理解過程有幫助。
