Numpy 图像切片返回黑色补丁/错误值

Numpy image slicing returning black patches/ wrong values

最终目标是拍摄图像并将其切成我保存的样本。问题是我的切片随机返回黑色/不正确的补丁。下面是一个小示例程序。

import scipy.ndimage as ndimage
import scipy.misc as misc
import numpy as np

image32 = misc.imread("work0.png")
patches = np.zeros((36, 8, 8))
for i in range(4):
  for j in range(4):
    patches[i*4 + j] = image32[i:i+8,j:j+8]
    misc.imsave("{0}{1}.png".format(i,j), patches[i*4 + j])

我的形象的一个例子是:

8x8 补丁产量的 0,0 补丁:

两件事:

  1. 您将补丁矩阵初始化为错误的数据类型。默认情况下,numpy 将使补丁矩阵成为 np.float64 类型,如果您在保存时使用它,您将不会得到您期望的结果。具体来说,如果您查阅 ,实际上会对浮点图像执行一些缩放,其中图像的最小值和最大值分别缩放为黑色和白色,因此如果您的图像在背景,最小值和最大值都相同,并且会显示为黑色。因此,最好的办法是尊重原始图像的数据类型,即将补丁矩阵的 dtype 设置为 np.uint8.

  2. 从您的 for 循环索引判断,您想要提取 非重叠 的 8 x 8 块。这意味着如果你有一个 32 x 32 的图像和 8 x 8 的补丁,你总共有 16 个补丁 排列在一个 4 x 4 的网格中。

因此,您需要更改 patches 语句,使其在第一维中有 16 个,而不是 36 个。此外,您还必须调整对图像进行索引的方式,以提取出 8 x 8 的补丁,因为现在这些补丁是重叠的。具体来说,您希望使图像补丁索引从 8*i8*(i+1) 的行和 8*j8*(j+1) 的列。如果您自己替换 ij 的样本值,您会看到我们为图像中的每个网格获得了唯一的 8 x 8 块。


结合以上两点,修改后的代码应该是:

import scipy.ndimage as ndimage
import scipy.misc as misc
import numpy as np

image32 = misc.imread('work0.png')

patches = np.zeros((16,8,8), dtype=np.uint8) # Change

for i in range(4):
    for j in range(4):
        patches[i*4 + j] = image32[8*i:8*(i+1),8*j:8*(j+1)] # Change
        misc.imsave("{0}{1}.png".format(i,j), patches[i*4 + j])

当我这样做并查看输出图像时,我得到了我期望的结果。


为了绝对确定,让我们使用 matplotlib 绘制线段。您已经方便地保存了 patches 中的所有补丁,所以显示我们需要的应该不是问题。但是,我会在注释中放置一些代码,以便您可以读取使用上述代码从磁盘保存的图像,这样您就可以验证它是否仍然有效,无论查看 patches 还是磁盘上的图像:

import matplotlib.pyplot as plt

plt.figure()
for i in range(4):
    for j in range(4):
        plt.subplot(4, 4, 4*i + j + 1)
        img = patches[4*i + j]
        # or you can do this:
        # img = misc.imread('{0}{1}.png'.format(i,j))
        img = np.dstack([img, img, img])
        plt.imshow(img)

plt.show()

关于 matplotlib.pyplot.imshow 的奇怪之处在于,如果您的图像是单通道的(例如您的情况)并且周围具有相同的强度,则无论颜色图是什么,它都会显示为黑色是的,就像我们在 imsave 中所经历的一样。因此,我不得不人为地将其设为 RGB 图像,但所有通道都相同,以便在我们显示图像之前将其可视化为灰度。

我们得到:

根据 this answer,问题是 imsave 对数据进行归一化,以便计算出的最小值 定义 为黑色(并且,如果有不同的最大值,即定义为白色)。

这让我开始深入研究为什么建议使用 uint8 确实可以创建所需的输出。事实证明,在源代码中有一个名为 bytescale 的函数在内部调用。

其实imsave本身就是一个very thin wrapper around toimage followed by save (from the image object). Inside of toimage if mode is None (which it is by default), that's when bytescale gets invoked.

事实证明 that bytescale has an if statement that checks for the uint8 data type,如果数据采用该格式,则 return 数据未更改。但如果不是,则根据最大和最小变换对数据进行缩放(其中 0 和 255 是要比较的默认低像素值和高像素值)。

这是上面链接的完整代码片段:

if data.dtype == uint8:
    return data

if high < low:
    raise ValueError("`high` should be larger than `low`.")

if cmin is None:
    cmin = data.min()
if cmax is None:
    cmax = data.max()

cscale = cmax - cmin
if cscale < 0:
    raise ValueError("`cmax` should be larger than `cmin`.")
elif cscale == 0:
    cscale = 1

scale = float(high - low) / cscale
bytedata = (data * 1.0 - cmin) * scale + 0.4999
bytedata[bytedata > high] = high
bytedata[bytedata < 0] = 0
return cast[uint8](bytedata) + cast[uint8](low)

对于你的数据都是255的块,cscale会是0,会被检查改成1,然后行

bytedata = (data * 1.0 - cmin) * scale + 0.4999

将导致整个图像块的浮点值为 0.4999,因此在下一段代码中明确设置为 0(当从浮点数转换为 uint8 时),例如:

In [102]: np.cast[np.uint8](0.4999)
Out[102]: array(0, dtype=uint8)

您可以在 bytescale 的正文中看到 return 只有两种可能的方法:要么您的数据类型为 uint8 并且 return 编辑为-是,否则它会经历这种愚蠢的缩放过程。所以最后,对于通过这些函数专门从图像格式加载或保存到图像格式的代码片段使用 uint8 确实是正确的,也是很好的做法。

所以这一系列的事情就是为什么你在输出的图像文件中得到全零以及为什么使用 dtype=np.uint8 的其他建议实际上对你有帮助。这并不是因为您 需要 避免图像的浮点数据,只是因为 imsave.

这种检查和缩放数据的奇怪约定