我有一个关于矩阵的有趣问题。在吉布斯分布中,吉布斯能量 U(x)可以计算为
是所有可能的派系C(右图)上的集团电位Vc(x)的总和。集团c被定义为S中的站点子集(x-蓝色像素的邻域是左图中的黄色像素的邻居),其中每对不同的站点是邻居,除了单站点集团
其中 V(x)的计算方法如下:
我的目标是如何计算 U(x)。在上面的示例中, x = {1,2} 。但是,我有一些卡在图像角落的像素,只有少于8个邻居(正常情况下像素通常有8个相邻像素)。为了解决这个问题,我通过带有掩码的卷积图像在边角处添加了零。但我认为它可能会对U(x)产生影响。从上面的定义,当我考虑图像角落处的像素(灰色)时,你能帮我找到U(x = 1)和U(x = 2)吗?我尝试通过以下解决方案解决它,但我认为我的解决方案太长了,我不确定是否正确。
这就是我的工作
Imlabel =[ 1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 2 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 2 2 2 1 1 1 1 1;
1 1 2 2 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 2 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1];
mask=[ 0 0 0;0 1 0;0 0 0];
Imlabel=conv2(Imlabel,mask); % To solve pixel in corner
num_class=2; % x={1,2}
beta=1;
for label=1:num_class
for i=2:size(Imlabel,1)-1
for j=2:size(Imlabel,2)-1
sum_V=0;
%North, south, east and west neighbors
if(label==Imlabel(i-1,j)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i,j+1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i+1,j)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i,j-1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
%% Diagonal pixels
if(label==Imlabel(i-1,j-1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i-1,j+1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i+1,j+1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i+1,j-1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
%% Save
U(i-1,j-1,label)=sum_V; %% Because Index is extended before
end
end
end
更新:规范化 目前,根据我对here的理解。我计算了归一化项以及gibbs分布为
P=zeros(size(U));
Z=sum(exp(-U),3);
for i=1:num_class
P(:,:,i)=exp(-U(:,:,i))./Z;
end
答案 0 :(得分:3)
您的解释和代码一样有意义。但是,你可以通过聪明地想要使用的各种函数来完成这个矢量化。
对于感兴趣的读者,根据您的源代码,您要做的是以下(伪代码):
创建U
大的矩阵size(Imlabel,1) x size(Imlabel,2) x num_class
。该矩阵的每个切片将确定该切片中每个坐标(i,j)
处的8个相邻像素的吉布斯能量成本。
对于每个班级标签x
...
一个。对于图像中的每个像素(i,j)
...
查找在(i,j)
的8像素邻域中定义的等于x
并将其设置为beta
在(i,j)
的8像素邻域中定义的那些位置不等于x
并将其设置为-beta
计算此8像素邻域中所有这些beta
和-beta
值的总和,并将其设置在U(i,j,x)
因此,我要做的是创建 3D矩阵成本...让我们调用此C
,其中每个切片y
具有相同的尺寸为size(Imlabel,1) x size(Imlabel,2)
但切片中的每个位置(i,j)
如果beta
则为Imlabel(i,j) == y
,否则为-beta
。执行此操作后,您可以对此矩阵执行N-D卷积,但使用2D内核,以便您可以分别计算每个切片的8像素邻域的总和。
今天的魔术菜单由bsxfun
,convn
和permute
的副订单组成,以便加油。
要获取费用矩阵C
,您可以执行此操作。这是假设Imlabel
未沿边界用零填充:
Imlabel =[ 1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 2 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 2 2 2 1 1 1 1 1;
1 1 2 2 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 2 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1];
C = double(bsxfun(@eq, Imlabel, permute(1:num_class, [1 3 2])));
C(C == 0) = -beta;
C(C == 1) = beta;
permute
函数用于创建单个3D向量,该向量从1到最多已定义的类。之所以需要这样做是因为bsxfun
执行了所谓的广播。在这种情况下会发生什么,你的矩阵Imlabel
,使用3D矢量将结合eq
或等于函数创建一个3D成本矩阵。此成本矩阵的每个切片都会为您提供与相关标签相等的位置,或者它不是。
您可以验证我们对bsxfun
输出的位置矩阵的含义是否正确:
>> C = double(bsxfun(@eq, Imlabel, permute(1:num_class, [1 3 2])))
C(:,:,1) =
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0 1 1
1 1 1 1 1 1 1 1 1 1
1 1 0 0 0 1 1 1 1 1
1 1 0 0 1 1 1 1 1 1
1 1 1 1 1 1 1 1 0 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
C(:,:,2) =
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 1 1 0 0 0 0 0
0 0 1 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
正如您所看到的,对于每个切片,我们可以看到哪个位置共享该切片所枚举的标签以及那些不共享相同标签的位置。获得这些位置后,我们需要将其转换为double
,以便我们可以将其修改为费用矩阵,其中-beta
是一个不等于特定标签的位置切片和beta
是。这是通过bsxfun
调用后的两个赋值语句完成的。
获得此费用矩阵后,您可以像以前一样填充边界,但要确保边界设置为-beta
,就像您在代码中所做的那样:
mask = zeros(3,3); mask(2,2) = 1;
Cpad = convn(C, mask);
Cpad(Cpad == 0) = -beta;
第一行代码表示您在代码中定义的[0 0 0; 0 1 0; 0 0 0]
掩码。现在,要独立填充每个切片以使其具有零边框,我们可以使用convn
来帮助我们执行此操作。
现在我们有了这个设置,你所要做的就是执行Gibbs'每片独立的能量:
mask2 = ones(3,3); mask2(2,2) = 0;
out = convn(Cpad, mask2, 'valid');
第一行代码表示一个掩码,除了中间值之外,每个值都是1,它是0,它是一个3 x 3掩码。您希望这样做的原因是,这会替换双if/else
循环逻辑中的for
语句。你正在做的基本上是一个卷积,你将所有8个邻居加在一起,除了中间本身。这可以通过使用除中心像素之外的所有1的掩码来实现,并将其设置为0。
接下来,我们使用convn
来计算Gibbs'每个切片独立的能量,但使用'valid'
标志,以便我们可以删除开头包含的零填充边框。
现在,如果您查看out
,则会将其与U
的内容进行比较,但由于您将U
编入索引的方式,因此会略有不同。尽管如此,使用out
的上述输出,您可以验证我们所拥有的是正确的,这将很好地处理边界情况:
>> out
out(:,:,1) =
-2 2 2 2 2 2 2 2 2 -2
2 8 8 8 8 8 6 6 6 2
2 8 8 8 8 8 6 8 6 2
2 6 4 2 4 6 6 6 6 2
2 4 2 0 4 6 8 8 8 2
2 4 2 0 2 6 8 6 6 0
2 6 4 4 6 8 8 6 8 0
2 8 8 8 8 8 8 6 6 0
-2 2 2 2 2 2 2 2 2 -2
out(:,:,2) =
-8 -8 -8 -8 -8 -8 -8 -8 -8 -8
-8 -8 -8 -8 -8 -8 -6 -6 -6 -8
-8 -8 -8 -8 -8 -8 -6 -8 -6 -8
-8 -6 -4 -2 -4 -6 -6 -6 -6 -8
-8 -4 -2 0 -4 -6 -8 -8 -8 -8
-8 -4 -2 0 -2 -6 -8 -6 -6 -6
-8 -6 -4 -4 -6 -8 -8 -6 -8 -6
-8 -8 -8 -8 -8 -8 -8 -6 -6 -6
-8 -8 -8 -8 -8 -8 -8 -8 -8 -8
例如,如果我们看一下第一个切片的左上角,我们会看到东,南,东南角的标签都是1,所以自beta = 1
以来,我们总和将是3,但是我们没有考虑其他5个方向,并且在您的代码中,您将其设置为-beta
,因此3 - 5 = -2
。
让我们看看第6行第4列,让我们来看看第二个标签或切片。这意味着任何等于标签2的基本方向,我们应该将这些与beta / 1
相加,而不等于标签2的任何内容都是-beta / -1
。正如你所看到的,正好有4个1和4个标签的标签为2,所以这些应该取消并给我们一个0。
您可以验证我们为此矩阵中的所有其他位置所做的操作会导致正确的计算。
完整的代码只是:
Imlabel =[ 1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 2 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 2 2 2 1 1 1 1 1;
1 1 2 2 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 2 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1];
C = double(bsxfun(@eq, Imlabel, permute(1:num_class, [1 3 2])));
C(C == 0) = -beta;
C(C == 1) = beta;
mask = zeros(3,3); mask(2,2) = 1;
Cpad = convn(C, mask);
Cpad(Cpad == 0) = -beta;
mask2 = ones(3,3); mask2(2,2) = 0;
out = convn(Cpad, mask2, 'valid');
在计时的情况下,我们可以检查上述方法与原始循环代码的比较速度。我使用timeit
来帮助促进这一点。
这是我设置的脚本,用于设置所有相关变量,并测量您的代码和我的代码所花费的时间:
function test_clique_timing
% Setup
Imlabel_orig =[ 1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 2 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1;
1 1 2 2 2 1 1 1 1 1;
1 1 2 2 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 2 1;
1 1 1 1 1 1 1 1 1 1;
1 1 1 1 1 1 1 1 1 1];
num_class=2; % x={1,2}
beta = 1;
function test_orig
mask=[ 0 0 0;0 1 0;0 0 0];
Imlabel=conv2(Imlabel_orig,mask); % To solve pixel in corner
beta=1;
for label=1:num_class
for i=2:size(Imlabel,1)-1
for j=2:size(Imlabel,2)-1
sum_V=0;
%North, south, east and west neighbors
if(label==Imlabel(i-1,j)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i,j+1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i+1,j)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i,j-1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
%% Diagonal pixels
if(label==Imlabel(i-1,j-1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i-1,j+1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i+1,j+1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
if(label==Imlabel(i+1,j-1)) sum_V=sum_V+beta;
else sum_V=sum_V-beta;end
%% Save
U(i-1,j-1,label)=sum_V; %% Because Index is extended before
end
end
end
end
function test_conv
C = double(bsxfun(@eq, Imlabel_orig, permute(1:num_class, [1 3 2])));
C(C == 0) = -beta;
C(C == 1) = beta;
mask = zeros(3,3); mask(2,2) = 1;
Cpad = convn(C, mask);
Cpad(Cpad == 0) = -beta;
mask2 = ones(3,3); mask2(2,2) = 0;
out = convn(Cpad, mask2, 'valid');
end
time1 = timeit(@test_orig);
time2 = timeit(@test_conv);
fprintf('Loop code time: %f seconds\n', time1);
fprintf('Vectorized code time: %f seconds\n', time2);
fprintf('Speedup factor: %f', time1/time2);
end
运行上面的脚本,我有时会得到这些:
Loop code time: 0.000049 seconds
Vectorized code time: 0.000060 seconds
Speedup factor: 0.816667
这是在几秒钟内完成的,我在Mac OS Mavericks 10.10.5下使用MATLAB R2013a完成了这项工作。加速并不是那么好看。事实上,这是一个小于1的因素,这很糟糕。但是,您所展示的是如此小的数据集。我们应该尝试使用更大的数据集,看看它是否仍然存在。让我们制作矩阵2500 x 2500,带有10个类标签。我打算用以下代码替换Imlabel
矩阵:
rng(123); %// Set seed for reproducibility
num_class = 10;
Imlabel_orig = randi(10,2500,2500);
这在2500 x 2500矩阵中产生1到10的随机整数。这样做并再次运行代码会给出:
Loop code time: 17.553669 seconds
Vectorized code time: 3.321950 seconds
Speedup factor: 5.284146
是的......那好多了。在2500 x 2500矩阵的10个标签上,计算需要大约17.5秒,而带有bsxfun / convn / permute
的矢量化代码优于原始循环代码几乎5.2倍。
关于归入你的Gibbs'的归一化术语。能量计算,你现在有这个:
P=zeros(size(U));
Z=sum(exp(-U),3);
for i=1:num_class
P(:,:,i)=exp(-U(:,:,i))./Z;
end
如果你利用bsxfun
,你可以这样做:
Z = sum(exp(-out),3)); %// out is U in my code
P = bsxfun(@rdivide, exp(-out), Z);
这实现了代码所做的一样。对于P
中的每个切片,我们找到exp
并将切片否定为输入,并将每个项除以Z
。对于bsxfun
,Z
矩阵的复制次数与out
中的切片一样多,并且元素划分与循环代码非常相似。