平均图像压缩

Average image compression

我一直在阅读有关使用 k x k 框进行图像压缩的文章,其中指出:

We split the image into k x k boxes, and form a new image by taking the average on each block. This way we reduce the file size from MN to MN/k^2 pixels.

我正在尝试想象如何编写我的算法。我是否必须先在 x 方向循环 k 像素,然后在 y 方向循环,让我们说 3 x 3x 方向循环,然后取平均值现场?

这听起来像解决方案吗?如果有人能写出一个伪算法,我会很高兴。

让我受益的是文件大小从 MN 箱减少到 MN/k^2 箱。这意味着您正在选择 k x k distinct 个框,找到每个块的平均值,然后将每个块的中心设置为平均值。

让我们从一个小的数值示例开始。假设我有以下 6 x 6 灰度图像,让我们制作 k = 3,这意味着您需要 3 x 3 个不同的框:

 7     5     4     8     7     1
 3    10     8     2     9     2
 3     7     5     2     8     5
 6     5     1     6     7    10
 8     4     4     6     8     9
 1     3     4     7     8     7

我特意选择了以下维度,以便我们可以计算出一个干净的示例。请注意,如果您的图像的行或列没有均匀地分成 k,您将不得不考虑当您的块不包含所有有效像素时会发生什么。有些人要么用零填充这些值,要么进行某种智能填充,但为了方便您,假设这些值为 0。

您必须将此图像分割成 3 x 3 个框。您必须垂直和水平循环并收集 3 x 3 个盒子。这意味着您总共会得到 4 个盒子。先水平扫描,再垂直扫描,我们得到以下方框。

方框 #1

 7     5     4
 3    10     8
 3     7     5

框 #2

 8     7     1
 2     9     2
 2     8     5

方框 #3

 6     5     1
 8     4     4
 1     3     4

方框 #4

 6     7    10
 6     8     9
 7     8     7

要计算输出图像,找到每个块的平均值,然后将平均值写入输出图像的新位置。 6 x 6 图像现在缩减为 6 / 3 x 6 / 3 = 2 x 2,其中每个位置找到每个不同图像块的平均值。中心位置标记如下:

 7     5     4     8     7     1
 3   |10|    8     2    |9|    2
 3     7     5     2     8     5
 6     5     1     6     7    10
 8    |4|     4     6   |8|    9
 1     3     4     7     8     7

我们现在找到每个块的平均值。对于第一个块的平均值,我们得到:

(7 + 5 + 4 + 3 + 10 + 8 + 3 + 7 + 5) / 9 = 5.7778

如果对其余块重复此操作,我们将得到以下输出图像:

5.7778    4.8889
4.0000    7.5556

现在我们已经建立了基础知识,您可以通过多种方式来做到这一点。最规范的方式就是您提到的方式。查看每个不同的块,找到平均值并将其写入输出图像中的写入位置。假设您的图像存储在 A:

中,您可能正在寻找这样的东西
A = imread('...'); %// Read in the image
k = 3; %// Change to whatever suits your needs

rows = size(A,1); cols = size(A,2); %// Get rows and columns of the image
channels = size(A,3); %// Total number of channels

%// Pad the image so that boxes at the end have zeroes
Apad = zeros(ceil(rows/k)*k, ceil(cols/k)*k, channels);
Apad(1:rows, 1:cols, :) = double(A);  %// Cast to double for precision

%// Create output image
B = zeros(ceil(rows/k), ceil(cols/k), channels);

%// Find the average of each block
for ii = 1 : size(B,1)
    for jj = 1 : size(B,2)
        for kk = 1 : size(B,3)
            block = Apad((ii-1)*k + 1 : ii*k, (jj-1)*k + 1 : jj*k, kk);
            B(ii,jj,kk) = mean(block(:));
        end
    end
end

%// Convert output image back to original input type
B = cast(B, class(A));

代码很容易解释。首先读入图像,selectk的值,然后得到行数、列数和通道数。然后我们创建一个新的填充图像,以防行和列不能被 k 整除。然后我们将原始图像放入这个新的填充图像中,并将图像转换为 double 以保持分割精度。

我们还为每个输出 k x k 块创建了大小合适的输出图像。之后,我们循环 select 每个不同的块并找到平均值。以我们如何对填充图像进行索引以获得正确的块为例。

完成此平均后,将输出图像转换回原始图像类型非常重要。如果你不这样做,那么使用 imshow 之类的东西来显示图像将使许多像素只呈现黑色和白色,因为 imshow 期望动态范围在 0 到 1 之间。


不过,我们可以通过更有效的方式来做到这一点。如果你刚开始,一定要保留上面的代码,但我要解决这个问题的一种方法是使用 im2col。这里会发生的是,像素邻域是以列为主格式构建的,因此每个像素邻域的列都堆叠成一个列。您可以将所有这些堆叠的列放入一个二维矩阵中。在我们的例子中,行数将为 9(即 3 x 3),而我们将拥有与有效图像块一样多的列。

块的获取方式再次采用列主要格式。从图像的左上角开始,3 x 3 个像素邻域按行向下收集。一旦我们到达矩阵的底部,我们就移动到下一列,然后再次向下移动行。这种 im2col 工作方式的行为对于此平均工作至关重要。

一旦我们得到这个矩阵,只需找到将产生单个向量的每一列的平均值,然后 reshape 将其返回到所需的输出矩阵。

想到了这样的事情。请注意,大部分代码保持不变,因为我们需要这样进行设置:

A = imread('...'); %// Read in the image
k = 3; %// Change to whatever suits your needs

rows = size(A,1); cols = size(A,2); %// Get rows and columns of the image
channels = size(A,3); %// Total number of channels

%// Pad the image so that boxes at the end have zeroes
Apad = zeros(ceil(rows/k)*k, ceil(cols/k)*k, channels);
Apad(1:rows, 1:cols, :) = double(A);  %// Cast to double for precision

%// Create output image
B = zeros(ceil(rows/k), ceil(cols/k), channels);

%// Do the average
for ii = 1 : channels
    M = im2col(Apad(:,:,ii), [k k], 'distinct');
    B(:,:,ii) = reshape(mean(M,1), [size(B,1), size(B,2)]);
end

%// Convert output image back to original input type 
B = cast(B, class(A));

请注意,我仍然必须遍历每个通道,因为 im2col 只接受二维矩阵,所以我们必须逐个平面地访问图像。


甚至更短,你可以用 blockproc:

B = blockproc(Apad, [3 3], @(x) mean(mean(x.data,2),1));

总而言之,有很多方法可以尝试。只是实验!