可以使用np.lib.stride_tricks.as_strided()方法,基本上可以通过数组创建滑动窗口数组。假设我们有这两张图片:
picture.png:
tile_pattern.png:
以下是您可以如何使用上述方法:
import cv2
import numpy as np
tile = cv2.imread('./tile_pattern.png')
photo = cv2.imread('./picture.png')
x_off = 10
y_off = 10
p_h, p_w, _ = photo.shape
t_h, t_w, _ = tile.shape
shape = (p_h // (t_h - 1 + y_off),
p_w // (t_w - 1 + x_off), t_h, t_w, 3)
strides = ((t_h + y_off) * 3 * p_w,
(t_w + x_off) * 3, p_w * 3, 3, 1)
np.lib.stride_tricks.as_strided(photo, shape, strides)[:] = tile
cv2.imshow('photo', photo)
cv2.imwrite('./modified.png', photo)
cv2.waitKey(0)
cv2.destroyAllWindows()
输出:
np.lib.stride_tricks.as_strided()方法的参数说明:
np.lib.stride_tricks.as_strided() 方法接受一个位置参数x:一个数组和几个关键字参数。我在上面的代码中使用的 2 个关键字参数是 shape:方法应该返回的数组的形状,以及 strides。
strides 关键字参数可能会令人困惑,因此让我们看几个示例:
>>> import numpy as np
>>> arr = np.array([4, 9, 3, 11])
>>> arr.strides
(8,)
8 表示从数组中的第一个元素移动到下一个元素所需的字节数。那么让我们试试吧:
>>> np.lib.stride_tricks.as_strided(arr, (4,), (8,))
array([ 4, 9, 3, 11])
所以上面的代码行接受arr 数组,返回一个形状为(4,) 的数组,并移动8 字节以到达每个其他元素。现在让我们试试:
>>> np.lib.stride_tricks.as_strided(arr, (4,), (4,))
array([ 4, 38654705664, 9, 12884901888])
我们没有告诉方法移动8 字节来获取其他所有元素,而是使用4。如果您从arr.strides 回忆起,它已经表明每个元素都相隔8 字节,因此4 将使结果数组中的每隔两个元素都来自原始数组中的连续元素。您在 2 个数字之间看到的数字是不属于 arr 的随机内存片段。
我们已经可以从arr创建一个滑动窗口,像这样:
>>> np.lib.stride_tricks.as_strided(arr, (3, 2), (8, 8))
array([[ 4, 9],
[ 9, 3],
[ 3, 11]])
strides 关键字参数中的第一个 8 指定结果数组中每一行的开头应具有arr 中的元素,即与其他行的起始元素相距8 字节,并且第二个8 指定每行中的每个其他元素都应该是8 字节,除了arr。
在我的平铺图像代码中没有看到8 的原因是uint8 类型的数组的步长是1,而不是8。见:
>>> import numpy as np
>>> arr = np.array([4, 9, 3, 11], 'uint8')
>>> arr.strides
(1,)
另外,如果您想知道* 3s,由于图像有3 通道,我们需要移动3 字节才能从一个像素移到另一个像素。
进一步解释np.lib.stride_tricks.as_strided()方法的参数:
让我们看看如何确定我们需要的滑动窗口的形状和步幅。
shape = (p_h // (t_h - 1 + y_off),
p_w // (t_w - 1 + x_off), t_h, t_w, 3)
strides = ((t_h + y_off) * 3 * p_w,
(t_w + x_off) * 3, p_w * 3, 3, 1)
我们可以看到我们想要从np.lib.stride_tricks.as_strided() 方法得到的结果数组的形状应该有 4 个维度:
- 每行瓷砖一个
- 每个图块一个
- 每个图块中的每一行一个
- 每 3 通道像素一个
所以shape 关键字参数中的第一个值是我们希望平铺到图像上的平铺行数(包括 y 偏移),第二个值是每行的瓦片数(包括 x 偏移)。第三个值是每个图块的行数,第四个值是每个图块每行的像素数,最后一个值是每个像素的通道数。
请记住,uint8 数组中的每个相邻元素相隔一个字节,我们不必担心将步幅乘以数组的步幅,但请注意,使用中的值更安全img.strides 反正。
-
strides关键字参数中的第一个值是从原始数组中的第一个元素到原始数组中将成为第一个元素所需的元素数结果数组第二行的元素。
-
strides 关键字参数中的第二个值是从原始数组中的第一个元素到原始数组中将成为第一个元素的元素所需的元素数结果数组的第二个图块的元素。
-
strides关键字参数中的第三个值是从原始数组中的第一个元素到原始数组中将成为第一个元素的元素的数量结果数组的第一个图块的第二行的元素。
-
strides 关键字参数中的第四个值是从原始数组中的第一个元素到原始数组中将成为第一个元素所需的元素数结果数组的第一个图块的第一行的第二个像素的元素。
-
strides 关键字参数中的
最后一个值 是从原始数组中的第一个元素到原始数组中将成为第二个元素的元素所需的元素数结果数组的元素。
同样,如果数组的类型为float64,则每个值都必须乘以8。