平均图像压缩
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 3
在 x
方向循环,然后取平均值现场?
这听起来像解决方案吗?如果有人能写出一个伪算法,我会很高兴。
让我受益的是文件大小从 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));
总而言之,有很多方法可以尝试。只是实验!
我一直在阅读有关使用 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 fromMN
toMN/k^2
pixels.
我正在尝试想象如何编写我的算法。我是否必须先在 x
方向循环 k
像素,然后在 y
方向循环,让我们说 3 x 3
在 x
方向循环,然后取平均值现场?
这听起来像解决方案吗?如果有人能写出一个伪算法,我会很高兴。
让我受益的是文件大小从 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));
总而言之,有很多方法可以尝试。只是实验!