在python,使用cv2库;如何将一张图像平铺到第二张图像中?

In python, using the cv2 library; how can I tile one image into a second image?

import cv2
tile = cv2.imread('.\tile_pattern.png')
h,w = tile.shape[:2]
photo = cv2.imread('.\picture.png')
# ???? do something to copy tile onto photo @ x=10 y=10
# COPYIMAGE( tile, 0:0, h:w, photo, 10:10)
cv2.imshow('image', photo)
cv2.imwrite('./modified.png', photo)
cv2.waitKey(0)
cv2.destroyAllWindows()

如何获取 tile_pattern.png 图像并将其粘贴到 picture.png 并将合并的图像保存到 modified.png

假设tile_pattern.png是一张16x16的彩色图片; picture.png 是一张 128x128 彩色图像,并且 我想将 tile_pattern.png 放在 picture.png 之上,从偏移位置 X=10 开始,Y=10picture.png

你可以使用np.lib.stride_tricks.as_strided()方法,它基本上允许你通过一个数组创建一个滑动数组windows。假设我们有这两张图片:

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:方法应该 return 的数组形状和 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 数组,return 是一个形状为 (4,) 的数组,它移动 8 字节以到达每个其他元素。现在让我们试试:

>>> np.lib.stride_tricks.as_strided(arr, (4,), (4,))
array([          4, 38654705664,           9, 12884901888])

我们没有告诉方法移动 8 个字节来获取所有其他元素,而是使用了 4。如果您还记得 arr.strides,它已经表明每个元素相隔 8 个字节,因此 4 将使结果数组中的每隔 2 个元素来自连续元素在原始数组中。您在 2 个数字之间看到的数字是不属于 arr.

的随机内存片段

我们已经可以从 arr 创建一个滑动 window,像这样:

>>> np.lib.stride_tricks.as_strided(arr, (3, 2), (8, 8))
array([[ 4,  9],
       [ 9,  3],
       [ 3, 11]])

strides 关键字参数中的第一个 8 指定结果数组中每一行的开头应包含 arr 中的元素,即 8 字节远离每一行的起始元素,第二个 8 指定每行中的每个其他元素应与 arr.

相隔 8 字节

您在平铺图像代码中看不到 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()方法参数的进一步解释:

让我们看看如何确定滑动的形状和步幅 window 我们需要。

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

这是一个蛮力解决方案,它使用更直接的 np.tile() 方法和遮罩来帮助在 Python/OpenCV 中将一张图像平铺在另一张图像上。它不像 @Ann Zen 的解决方案那么优雅,但也许更容易理解。我会用她的图片和参数。

图片:

平铺:

import cv2
import numpy as np
import math

# read image
photo = cv2.imread('gray_gradient.png')
ph, pw = photo.shape[:2]

# read tile
tile = cv2.imread('red_green_tile.png')

# pad tile on right and bottom by 10 with black
top=0
bottom=10
left=0
right=10
tile_pad = cv2.copyMakeBorder(tile, top, bottom, left, right, cv2.BORDER_CONSTANT, (0,0,0))
tph, tpw = tile_pad.shape[:2]

# create white image the same size as tile
white = np.full_like(tile, (255,255,255))

# pad white image with black by 10 on right and bottom
white = cv2.copyMakeBorder(white, top, bottom, left, right, cv2.BORDER_CONSTANT, (0,0,0))

# tile the tile and white images out to be as larger or slightly larger than the input image and crop to same size
xrepeats = math.ceil(pw/tpw)
yrepeats = math.ceil(ph/tph)
print(yrepeats,xrepeats)
tiled_tile = np.tile(tile_pad, (yrepeats,xrepeats,1))[0:ph, 0:pw]
tiled_white = np.tile(white, (yrepeats,xrepeats,1))[0:ph, 0:pw]
tiled_white = tiled_white[:,:,0]

# mask the tile with the tiled_white image
masked_tile = cv2.bitwise_and(tiled_tile, tiled_tile, mask=tiled_white)

# mask the photo with the inverse tiled_white image
masked_photo = cv2.bitwise_and(photo, photo, mask=255-tiled_white)

# combine the two masked images
result = cv2.add(masked_photo, masked_tile)

# save results
cv2.imwrite("red_green_tile_tiled.png", tiled_tile)
cv2.imwrite("red_green_tile_tiled_mask.png", tiled_white)
cv2.imwrite("red_green_tile_tiled_over_gray_gradient.png", result)

# show the results
cv2.imshow("tiled_tile", tiled_tile)
cv2.imshow("tiled_white", tiled_white)
cv2.imshow("masked_tile", masked_tile)
cv2.imshow("masked_photo", masked_photo)
cv2.imshow("result", result)
cv2.waitKey(0)

黑色平铺瓷砖:

掩码:

结果: