【问题标题】:Numpy Array Multiplication Differnce in Regular vs when using For loops常规与使用 For 循环时的 Numpy 数组乘法差异
【发布时间】:2020-10-26 18:09:06
【问题描述】:

我想在具有完全黄色图像的图像上应用多重混合。 图像是: Eiffel Tower Paris

黄色图像是使用以下方法创建的:

img_paris = img1 = cv2.imread("/content/drive/My Drive/Datasets/Images/paris.jpg")
yellow_image = np.ones(img_paris.shape) * 255 

yellow_image[:,:,0] *= 0

我尝试了两种技术: 首先我使用了正则乘法

imgc = img_paris.copy()

imgc = (imgc * yellow_image)

这导致: Eiffel Multiplied

然后我使用 for 循环将单个元素相乘

for x in range(yellow_image.shape[0]):
  for y in range(yellow_image.shape[1]):
    imgc[x,y] = (imgc[x,y] * yellow_image[x,y])

导致 Eiffel Multiplied 2

第二个的结果看起来图像以某种方式被反转了。

这两种技术应该会产生相似的结果。 为什么在 for 循环技术中图像会倒置? 我想使用 For 循环进行更多控制。有人能告诉我为什么会这样吗?为什么两种技术会导致不同的结果?

【问题讨论】:

  • 这不是 rgb 空间中的黄色图像。它是青色左右。
  • @MadPhysicist cv2 按 BGR 顺序存储图像。

标签: python numpy opencv cv2


【解决方案1】:

这两种技术应该会产生相似的结果。为什么是图像 在 for 循环技术中被倒置?

因为你应该这样做,转换数据类型:

imgc = np.uint64(img_paris.copy()) # <-- convert datatype

for x in range(yellow_image.shape[0]):
    for y in range(yellow_image.shape[1]):
        imgc[x,y] = (imgc[x,y] * yellow_image[x,y])

说明第 1 部分(dtype)

这是因为dtype 在重新分配整个矩阵时会发生变化,而重新分配切片时不会发生:

a = np.array([[1]], np.uint8)
b = np.array([[1]], np.float64)

a[0] = a[0] * b[0] # assigning slices dtype of a does not change
print(a.dtype) #=> uint8

a = a * b # while assigning the full matrix it does
print(a.dtype) #=> float64

如果您在看到这个的过程中打印您的dtype

yellow_image_1 = np.ones(img_paris.shape) * 255
print(yellow_image_1.dtype) #=> float64
yellow_image_1[:,:,0] *= 0
print(yellow_image_1.dtype) #=> float64

imgc_1 = img_paris.copy()
print(imgc_1.dtype) #=> uint8
imgc_1 = (imgc_1 * yellow_image_1)
print(imgc_1.dtype) #=> float64

还有这个:

yellow_image_2 = np.ones(img_paris.shape) * 255
print(yellow_image_2.dtype)  #=> float64
yellow_image_2[:,:,0] *= 0
print(yellow_image_2.dtype) #=> float64

imgc_2 = img_paris.copy()
print(imgc_2.dtype) #=> uint8
for x in range(yellow_image_2.shape[0]):
    for y in range(yellow_image_2.shape[1]):
        imgc_2[x,y] = (imgc_2[x,y] * yellow_image_2[x,y])
print(imgc_2.dtype) #=> uint8

所以你最终会得到不同的dtype 矩阵。


解释第 2 部分(OpenCV BGR)

如前所述,请记住,OpenCv 使用BGR format,每个像素值0 变为255np.uint8 表示。

因此,如果您使用例如 matplotlib,为了显示图像,您必须交换 B 和 R 通道

img_paris = cv2.imread('3ClnT.jpg')
plt.imshow(img_paris[:,:,::-1])

如果您使用cv2.imwrite()cv2imshow() 进行保存,则不需要这样做,例如:

cv2.imwrite('paris.jpg', img_paris)

也就是说,您可以使用这个单行命令生成纯黄色图像:

yellow_image = np.ones_like(img_paris) * (0, 255, 255)

并显示或保存:

plt.imshow(yellow_image[:,:,::-1])
cv2.imwrite('solid_yellow.jpg', yellow_image)

现在,乘法 paris_yellow = img_paris * yellow_image 的结果大于 255

使用 RGB 数据将输入数据剪切到 imshow 的有效范围([0..1] 用于浮点数或 [0..255] 用于整数)。

因此,当您相乘时,您最终会得到一个最大像素值,它可以是 255 * 255 = 65025

然后你需要:

  • 将乘法项转换为支持高达65025 的整数的数据类型
  • 乘法后,归一化然后转换回uint8

这是一个例子:

paris_yellow_2 = np.int64(img_paris) * np.int64(yellow_image) # <- use int64 terms
max_px_val = np.amax(paris_yellow_2) # <-- Max pixel alue
paris_yellow_2 = np.uint8((paris_yellow_2/max_px_val) * 255) # <- normalize and convert back to uint8
plt.imshow(paris_yellow_2[:,:,::-1])

结果如下:


其他选项,给出不同的结果是将GR 通道相乘以获得大于1 的系数,然后裁剪值&gt;255。在这种情况下,您需要使用浮点数dtype

paris_yellow_3 = np.float64(img_paris) * (1, 3, 3)
paris_yellow_3[paris_yellow_3 > 255] = 255 # <- crops to 255 pixels values > 255
paris_yellow_3 = paris_yellow_3.astype(np.uint8) # <- back to uint8

在这种情况下,B 乘以 1(没有变化),GR 乘以 3,得到以下结果:

【讨论】:

    【解决方案2】:

    图片不同,因为前一张图片包含浮点数据,而后一张图片的数据类型为uint8

    由于yellow_image的数据类型是浮点数,指令imgc = (imgc * yellow_image)生成了一个带有浮点数据的新数组。

    创建一个数据类型为uint8yellow_image 来解决问题:

    yellow_image = np.ones(img_paris.shape, dtype=np.uint8) * 255 
    yellow_image[:,:,0] *= 0
    imgc *= yellow_image
    

    或者使用numpy.ndarray.astyp 创建数组的副本并转换为uint8

    imgc = (imgc * yellow_image)

    imgc = (imgc * yellow_image).astype(np.uint8)
    

    或使用numpy.multiply,通过指定转换规则和类型:

    imgc = np.multiply(imgc, yellow_image, casting='unsafe', dtype=np.uint8)
    

    【讨论】:

      【解决方案3】:

      问题出在一个完全出乎意料的地方:yellow_image 的创建。 np.ones 默认创建浮点类型数组:因此 yellow_image 使用浮点类型元素创建。检查:

      >>> yellow_image.dtype
      dtype('float64')
      

      因此,当您执行以下操作时:imgc * yellow_image,生成的数组数据类型将提升为精度更高的数据类型(当然是浮点类型),因此imgc 具有以下元素:

      array([[[    0., 24225., 12750.],
              [    0., 23715., 12240.],
              [    0., 23460., 11985.],
              ...,
      

      浮点型。

      为了避免这个问题,并且不经历显式编写数据类型的麻烦,请使用:

      yellow_image = np.ones_like(img_paris) * 255
      

      np.ones_like 使用传递给它的数组的形状和完全相同的 dtype 创建一个数组 - 消除您的担忧。 现在检查:

      >>> yellow_image.dtype
      dtype('uint8')
      

      现在考虑imgc1 = imgc * yellow_imageimgc2 是循环的输出。检查:

      >>> np.allclose(imgc1, imgc2)
      True
      

      问题解决了。

      注意 - 回答您关于为什么图像倒置的问题:

      当乘法产生浮点类型时,它会将大量数字(24225.、12750. 等)保存为像素颜色值。当您使用此数组编写图像时,所有这些数字都被裁剪为最大可能的像素颜色值:255。因此,您看到的大部分图像都是黄色的,因为所有“溢出”值都被裁剪为 255,导致最亮的黄色阴影。

      另一方面,当乘法完成强制uint8 类型时,任何大于 255 的值都会“回滚”到 无符号 8 位整数的最小可能值: 0. 所以如果值为 487,dtype 限制将强制它为0 + (287 - 255) - 1 = 31。这是溢出。所以一个很大的数字最终会变得非常小——数学很简单,查一下。因此,您会得到倒置的图像(意外的暗像素)。

      【讨论】:

        猜你喜欢
        • 2020-12-19
        • 2021-02-07
        • 1970-01-01
        • 1970-01-01
        • 2016-01-16
        • 2021-07-18
        • 1970-01-01
        • 2023-03-03
        • 2010-11-13
        相关资源
        最近更新 更多