两幅图像的互信息和联合熵 - MATLAB

30

我有两张黑白图片,需要计算它们的互信息。

Image 1 = X 
Image 2 = Y

我知道互信息可以定义为:

MI = entropy(X) + entropy(Y) - JointEntropy(X,Y)

MATLAB已经内置了用于计算熵的函数,但没有用于计算联合熵的函数。我想真正的问题是:如何计算两幅图像的联合熵?

这里是我想要找到联合熵的图像的示例:

X =

0    0    0    0    0    0
0    0    1    1    0    0
0    0    1    1    0    0
0    0    0    0    0    0
0    0    0    0    0    0

Y =

0    0    0    0    0    0 
0    0    0.38 0.82 0.38 0.04 
0    0    0.32 0.82 0.68 0.17
0    0    0.04 0.14 0.11 0 
0    0    0    0    0    0

如果您的图像强度在[0,1]范围内,则仍然可以使用我下面编写的代码,但需要确保强度为整数。如果您有8位图像,请在执行我的操作之前使用im2uint8 - rayryeng
此外,如果您的图像是纯二进制的,则不要使用“im2uint8”进行转换,因为这将浪费空间。您可以直接使用现有代码,将其保留为一个带有4个联合箱的直方图,而不是256 x 256 - rayryeng
非常感谢您的帮助。最终我将我的图像乘以100,然后四舍五入到最接近的整数再加1 -> Im1 = round(100*Im0+1)。这样我解决了所有的问题:1)代码需要整数;2)它需要正值。 :D - Jorge
如果您的图像是8位图像,我不建议您这样做,因为会丢失二进制计数。 我仍然建议您使用im2uint8转换您的图像。 这将将您的图像转换为[0,255]。 此外,如果您查看MATLAB如何实现entropy命令,他们也使用im2uint8。 您需要在entropy和我们讨论的联合熵中使用相同数量的bin。 - rayryeng
1个回答

69
为了计算联合熵,您需要计算两个图像之间的联合直方图。 联合直方图本质上与普通的1D直方图相同,但第一维记录第一个图像的强度,第二维记录第二个图像的强度。 这非常类似于通常所称的共现矩阵。 在联合直方图的位置处,它告诉您我们遇到了多少个强度值,在第一个图像中具有强度i,在第二个图像中具有强度j。
重要的是,这记录了我们在相应位置同时看到这对强度的次数。 例如,如果我们有联合直方图计数<7,3> = 2,则意味着当我们扫描两个图像时,当我们在第二个图像的相应位置遇到强度为7时,我们遇到了强度为3的强度总共达到了2次。
构建联合直方图非常简单。
首先,创建一个256 x 256的矩阵(假设图像为无符号8位整数),并将它们初始化为全零。此外,您需要确保两个图像的大小相同(宽度和高度)。
完成后,查看每个图像的第一个像素,我们将其称为左上角。具体来说,查看该位置的第一张图像和第二张图像的强度。第一张图像的强度将作为行,而第二张图像的强度将作为列。
在矩阵中找到此位置,并将矩阵中的此位置增加1
重复此过程,直到图像中的所有位置都处理完毕。
完成后,将所有条目除以任一图像中的元素总数(记住它们应具有相同的大小)。这将为我们提供两个图像之间的联合概率分布。

我们倾向于使用for循环来实现此操作,但众所周知,for循环非常慢,应尽可能避免使用。然而,在MATLAB中,您可以轻松地完成以下操作无需循环。假设im1im2是您要比较的第一张和第二张图像。我们可以将im1im2转换为向量。然后,我们可以使用accumarray帮助我们计算联合直方图。 accumarray是MATLAB中最强大的函数之一。您可以将其视为微型MapReduce范例。简单地说,每个数据输入都有一个键和一个关联值。 accumarray的目标是将所有属于同一键的值进行分组,并对所有这些值执行某些操作。在我们的情况下,“键”将是强度值,而值本身是每个强度值的值1。然后,我们将要添加映射到同一bin的所有1的值,这正是我们计算直方图的方式。 accumarray的默认行为是将所有这些值相加。具体来说,accumarray的输出将是一个数组,其中每个位置计算映射到该键的所有值的总和。例如,第一个位置将是映射到键1的所有值的总和,第二个位置将是映射到键2的所有值的总和,依此类推。

然而,对于联合直方图,您需要确定哪些值映射到相同的强度对(i,j),因此这里的键将是一对2D坐标。因此,在两个图像之间共享相同空间位置的具有第一个图像中强度i和第二个图像中强度j的任何强度都转到同一个键。因此,在2D情况下,accumarray的输出将是一个2D矩阵,其中每个元素(i,j)包含映射到键(i,j)的所有值的总和,类似于先前提到的1D情况,这正是我们想要的。
换句话说:
indrow = double(im1(:)) + 1;
indcol = double(im2(:)) + 1; %// Should be the same size as indrow
jointHistogram = accumarray([indrow indcol], 1);
jointProb = jointHistogram / numel(indrow);

使用accumarray函数,第一个输入是键,第二个输入是值。需要注意的是,如果每个键都有相同的值,可以将常数分配给第二个输入,这就是我所做的,并且它是1。一般来说,这是与第一个输入具有相同行数的数组。还要特别注意前两行。图像中不可避免地会出现强度为0的情况,但由于MATLAB从1开始索引,因此我们需要将两个数组偏移1

现在我们拥有联合直方图,计算联合熵非常简单。它类似于1D中的熵,只是现在我们只是在整个联合概率矩阵上求和。请记住,您的联合直方图很可能会有许多0条目。我们需要确保跳过那些或者log2操作将未定义。现在让我们消除任何零条目:

indNoZero = jointHistogram ~= 0;
jointProb1DNoZero = jointProb(indNoZero);

请注意,我搜索的是联合直方图而不是联合概率矩阵。这是因为联合直方图由整数组成,而联合概率矩阵将位于01之间。由于除法的问题,我想避免将此矩阵中的任何条目与0进行比较,以避免数值舍入误差和不稳定性。上述方法还将我们的联合概率矩阵转换为堆叠的一维向量,这是可以接受的。

因此,联合熵可以计算如下:

jointEntropy = -sum(jointProb1DNoZero.*log2(jointProb1DNoZero));

如果我对在MATLAB中计算图像熵的理解是正确的,那么它应该会计算256个bin上的直方图/概率分布,因此你可以使用刚刚计算出来的联合熵函数。

如果我们有浮点数据呢?

到目前为止,我们假设你处理的图像具有整数值强度。那么如果我们有浮点数据呢?accumarray假定您正在尝试使用整数索引访问输出数组,但是我们仍然可以通过一些简单的方法达到想要的效果。您需要做的就是将每个图像中的浮点值都分配一个唯一的ID。因此,您将使用这些ID而不是整数来使用accumarray。为了方便地分配ID,请使用unique——特别是函数的第三个输出。您需要将每个图像放入unique中,并将其作为输入传递给accumarray。换句话说,您需要执行以下操作:
[~,~,indrow] = unique(im1(:)); %// Change here
[~,~,indcol] = unique(im2(:)); %// Change here

%// Same code
jointHistogram = accumarray([indrow indcol], 1);
jointProb = jointHistogram / numel(indrow);
indNoZero = jointHistogram ~= 0;
jointProb1DNoZero = jointProb(indNoZero);
jointEntropy = -sum(jointProb1DNoZero.*log2(jointProb1DNoZero));

请注意,使用indrowindcol,我们直接将unique的第三个输出分配给这些变量,然后使用先前计算的相同联合熵代码。我们也不必像以前那样将变量偏移1,因为unique将从1开始分配ID。

旁白

您实际上可以使用联合概率矩阵单独为每个图像计算直方图或概率分布。如果您想计算第一张图像的直方图/概率分布,您只需要累加每行的所有列。要为第二张图像执行此操作,您只需要累加每列的所有行。因此,您可以执行:
histogramImage1 = sum(jointHistogram, 1);
histogramImage2 = sum(jointHistogram, 2);

之后,您可以自行计算它们的熵。为了双重确认,请确保将它们都转换为PDF文件,然后使用标准公式(如上所示)计算熵。

如何计算互信息?

要计算互信息,你需要两个图像的熵。你可以使用MATLAB内置的entropy函数,但这假定有256个唯一级别。如果你想应用于存在N个不同级别的情况,你可以使用我们在上面对联合直方图的处理方法,然后计算每个图像的直方图并计算每个图像的熵。你只需重复联合熵计算所使用的熵计算方法,但是将其分别应用于每个图像:

%// Find non-zero elements for first image's histogram
indNoZero = histogramImage1 ~= 0;

%// Extract them out and get the probabilities
prob1NoZero = histogramImage1(indNoZero);
prob1NoZero = prob1NoZero / sum(prob1NoZero);

%// Compute the entropy
entropy1 = -sum(prob1NoZero.*log2(prob1NoZero));

%// Repeat for the second image
indNoZero = histogramImage2 ~= 0;
prob2NoZero = histogramImage2(indNoZero);
prob2NoZero = prob2NoZero / sum(prob2NoZero);
entropy2 = -sum(prob2NoZero.*log2(prob2NoZero));

%// Now compute mutual information
mutualInformation = entropy1 + entropy2 - jointEntropy;

希望这可以帮到你!

2
使用 for 循环的不错替代方案。 - eigenchris
5
这几乎是一个垃圾评论,但是我必须说这是我在SO上遇到的最好的答案之一。它简明地解释了代码背后的背景和原理,并从易懂的整数示例逐步过渡到较难的实际情况(实数)。此外,这是使用accumarray的最干净的示例之一。 - AruniRC
@ArunIRC 非常感谢 :) 这实际上是我开始回答问题时的早期答案之一。从未想过这会如此受欢迎。实际上,您现在看到的当前版本与最初版本相比进行了许多编辑。我只解决了整数情况,并且仅解决了联合直方图。随着时间的推移,我进行了编辑,包括浮点数情况和最终计算互信息。无论如何,感谢您的评论 :) 非常感激。 - rayryeng
1
@Keivan 感谢您的评论。实际上,我没有发现一个错误,在您的示例中变得明显。我刚刚修复了它。现在运行它,应该是正确的。基本上,我没有正确地创建两个信号之间的概率分布。但是,回答您的问题,两个信号之间的熵为0,这意味着它们之间的互信息也为0。这是因为所有值都只被映射到1个bin中,因此该值出现的概率始终为1,因此其log2为0。 - rayryeng
1
@Keivan,你使用的代码是否假定了双精度?你需要使用适合数据类型的代码。 - rayryeng
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接