将类型为float64的np.array转换为类型为uint8,同时缩放数值

58

我有一个特定的np.array data,它代表了一张特定的灰度图像。我需要使用SimpleBlobDetector(),但是它只能接受8位图像,所以我需要转换这个图像,显然会有质量损失。

我已经尝试过:

import numpy as np
import cv2
[...]
data = data / data.max() #normalizes data in range 0 - 255
data = 255 * data
img = data.astype(np.uint8)
cv2.imshow("Window", img)

但是cv2.imshow显示的图像不如预期,存在奇怪的变形...

最终,我只需要将np.float64转换为np.uint8,缩放所有值并截断剩余部分,例如,65535变为255,65534变为254,以此类推... 有什么帮助吗?

谢谢。


2
发生了什么奇怪的扭曲?对我来说,规范化代码似乎很好。data 的类型是 np.float64 吗?此外,将 65535 转换为 255 对我来说似乎意味着您期望的输入类型是 np.uint16,而不是 np.float64 - rayryeng
图像似乎具有某种粒度,例如黑色背景变为灰色且不稳定。 你有其他的建议吗? - decadenza
我建议将图像除以该类型经历的最大值,而不是图像中的最大值。我已经写了一个答案。如果可以,请告诉我它是否有效。 - rayryeng
我还将数据除以data.max(),以便将值归一化到0-1的范围内。 - decadenza
3个回答

78
更好的归一化图像的方法是将每个值除以数据类型经历的最大值。这样可以确保在图像中动态范围较小的像素保持较小,不会无意中被归一化为灰色。例如,如果您的图像具有动态范围[0-2],则现在的代码会将其缩放为强度[0, 128, 255]。在转换为np.uint8后,您希望这些值保持较小。
因此,将每个值除以图像类型可能的最大值,而不是实际图像本身的最大值。然后将其缩放到255以生成归一化的结果。使用numpy.iinfo并提供图像的数据类型(dtype),您将获得该类型的信息结构。然后从此结构中访问max字段以确定最大值。
因此,在您的代码中进行以下修改:
import numpy as np
import cv2
[...]
info = np.iinfo(data.dtype) # Get the information of the incoming image type
data = data.astype(np.float64) / info.max # normalize the data to 0 - 1
data = 255 * data # Now scale by 255
img = data.astype(np.uint8)
cv2.imshow("Window", img)

请注意,我还将图像转换为 np.float64 (如果传入的数据类型不是这样),以在进行除法运算时保持浮点精度。


1
啊,抱歉。我现在明白了。我想@decadenza和我都在对图像本身调用.max()函数,这是一个函数。值得注意的是,还有一个cv2函数cv2.normalize。 - Schneems
3
在我看到astype被广泛使用时,有一个小注释:“您不应该在图像上使用astype,因为它违反了有关dtype范围的假设。”来自http://scikit-image.org/docs/dev/user_guide/data_types.html - hAcKnRoCk
4
对我来说,np.iinfo(data.dtype)会出现这个错误:ValueError: Invalid integer data type.data只是一个float64类型的numpy数组,对吗? - holastello
1
@holastello 在转换之前,data 需要是整数类型。如果它已经是浮点数数组,则此方法将无法工作。 - rayryeng
1
@eric 是的,我认为那确实是个问题。当有人编辑了我的回答并导致其事实不正确,而其他人可能会使用这个错误的答案时,我会非常烦恼。同时,两年前发生的一件事情让我非常恼火,也反映在了我的回复中。我承认我的言辞可能有些过激,但我认为如果有人编辑了我的回答,并在评论中说明了所做的修改,这可以避免很多问题。这是基本的礼貌和尊重。 - rayryeng
显示剩余10条评论

26

考虑到您正在使用OpenCV,转换数据类型的最佳方法是使用normalize函数。

img_n = cv2.normalize(src=img, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

但是,如果您不想使用OpenCV,您可以在numpy中完成此操作。

def convert(img, target_type_min, target_type_max, target_type):
    imin = img.min()
    imax = img.max()

    a = (target_type_max - target_type_min) / (imax - imin)
    b = target_type_max - a * imax
    new_img = (a * img + b).astype(target_type)
    return new_img

然后像这样使用它

imgu8 = convert(img16u, 0, 255, np.uint8)

这是基于我在crossvalidated论坛上找到的答案,在这个解决方案下的评论中 https://stats.stackexchange.com/a/70808/277040


我可以确认,这个在使用Sobel/Canny/Laplacian生成的OpenCV图像数据上非常有效,这些数据包含float64值,因此与QImage不兼容,而我正在使用它来显示结果(它只接受整数值)。 - rbaleksandar

12

你可以使用skimage.img_as_ubyte(yourdata),它会使你的numpy数组范围在0-255之间。

from skimage import img_as_ubyte

img = img_as_ubyte(data)
cv2.imshow("Window", img)

4
生成的浮点图像必须在-1和1之间。 - whiletrue
在哪一行? - Ali Farouk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接