【问题标题】:In python, using the cv2 library; how can I tile one image into a second image?在python中,使用cv2库;如何将一张图像平铺成第二张图像?
【发布时间】:2021-08-10 04:23:21
【问题描述】:
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 的顶部,从picture.png 内的偏移位置X=10Y=10 开始

【问题讨论】:

    标签: python image opencv cv2


    【解决方案1】:

    可以使用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

    【讨论】:

    • @Ann Zen 非常聪明。但请解释含义以及您如何指定形状和步幅的参数。关于此的 Numpy 文档没有提供任何信息。
    • @fmw42 我添加了解释。
    • @Ann Zen 感谢您提供的简单示例。他们信息量很大。但是,您能否进一步解释一下您对该问题的实际答案。例如,为什么形状和步幅每个元素都是 5 个元素,而不是例如 3 个元素。另外,为什么 (th+yoff) 的跳过值乘以输入的宽度。所以基本上,如果你不介意,请在这个问题中解释你的体型和步幅的实际值。
    • @fmw42 不客气。当然,我添加了。
    • @Ann Zen 再次感谢您对形状参数的详细解释以及您用于回答的值。
    【解决方案2】:

    这是一个蛮力解决方案,它使用更直接的 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)
    

    在黑色上平铺瓷砖:

    面具:

    结果:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-04-22
      • 2015-07-12
      • 2022-07-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-18
      • 2013-09-04
      • 1970-01-01
      相关资源
      最近更新 更多