【问题标题】:Computing a contrast map计算对比图
【发布时间】:2021-02-25 16:57:11
【问题描述】:

我正在尝试计算NxN 窗口中每个像素周围的对比度,并将结果保存在新图像中,其中新图像中的每个像素都是旧图像中它周围区域的对比度。从另一个帖子我得到了这个:

1) Convert the image to say LAB and get the L channel
2) Compute the max for an NxN neighborhood around each pixel
3) Compute the min for an NxN neighborhood around each pixel
4) Compute the contrast from the equation above at each pixel.
5) Insert the contrast as a pixel value in new image.

目前我有以下:

def cmap(roi):
    max = roi.reshape((roi.shape[0] * roi.shape[1], 3)).max(axis=0)
    min = roi.reshape((roi.shape[0] * roi.shape[1], 3)).min(axis=0)
    contrast = (max - min) / (max + min)
    return contrast


def cm(img):
    # convert to LAB color space
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

    # separate channels
    L, A, B = cv2.split(lab)

    img_shape = L.shape

    size = 5

    shape = (L.shape[0] - size + 1, L.shape[1] - size + 1, size, size)
    strides = 2 * L.strides
    patches = np.lib.stride_tricks.as_strided(L, shape=shape, strides=strides)
    patches = patches.reshape(-1, size, size)

    output_img = np.array([cmap(roi) for roi in patches])
    cv2.imwrite("labtest.png", output_img)

代码抱怨 roi 的大小。有没有更好的(pythonic)方式来做我想做的事?

【问题讨论】:

    标签: python opencv image-processing contrast


    【解决方案1】:

    您可以使用 DilationErosion 形态学运算来找到 NxN 邻域的最大值和最小值。

    • NxN 的扩张相当于 NxN 邻域的最大值
    • NxN 的侵蚀相当于 NxN 邻域的最小值

    使用形态学操作使解决方案比“手动”将图像分成小块要简单得多。

    您可以使用以下阶段:

    • 转换为 LAB 色彩空间并获得 L 通道。
    • 使用“扩张”形态学运算(扩张相当于在 NxN 邻域中找到最大像素)。
    • 使用“侵蚀”形态学运算(扩张相当于在 NxN 邻域中找到最大像素)。
    • 将图像转换为浮点型(在使用除法运算之前需要)。
    • 计算对比图(对比图范围为 [0, 1])。
    • 将对比度映射转换为 uint8 类型并进行舍入 - 转换会降低精度,因此我不推荐它(但我假设您需要转换才能将输出作为图像)。

    这是一个完整的代码示例:

    import numpy as np
    import cv2
    
    size_n = 5 # NxN neighborhood around each pixel
    
    # Read input image
    img = cv2.imread('chelsea.png')
    
    # Convert to LAB color space
    lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    
    # Get the L channel
    L = lab[:, :, 0]
    
    # Use "dilate" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood)
    img_max = cv2.morphologyEx(L, cv2.MORPH_DILATE, np.ones((size_n, size_n)))
    
    # Use "erode" morphological operation (dilate is equivalent to finding maximum pixel in NxN neighborhood)
    img_min = cv2.morphologyEx(L, cv2.MORPH_ERODE, np.ones((size_n, size_n)))
    
    # Convert to type float (required before using division operation)
    img_max = img_max.astype(float)
    img_min = img_min.astype(float)
    
    # Compute contrast map (range of img_contrast is [0, 1])
    img_contrast = (img_max - img_min) / (img_max + img_min)
    
    # Convert contrast map to type uint8 with rounding - the conversion loosed accuracy, so I can't recommend it.
    # Note: img_contrast_uint8 is scaled by 255 (scaled by 255 relative to the original formula).
    img_contrast_uint8 = np.round(img_contrast*255).astype(np.uint8)
    
    # Show img_contrast as output
    cv2.imshow('img_contrast', img_contrast_uint8)
    cv2.waitKey()
    cv2.destroyAllWindows()
    

    输入图片:

    L 图片:

    img_max:

    img_min:

    对比图img_contrast_uint8:

    【讨论】:

    • 你推荐什么而不是使用四舍五入?
    • 这取决于...我认为对比度图不是目标,而只是用于其他算法的中间阶段。如果这是一个练习,并且您想显示对比度图,则转换为 uint8 是要走的路。如果您需要使用对比图,只需将其保存为float 格式(您可以将其保存为 NumPy 数组或二进制文件)。
    • 知道了,最后一件事,这如何循环遍历所有像素?从外观上看,它需要一个全局最大值和最小值,然后使用它们来构建地图。我正在尝试使计算在该区域的每个最大值/最小值的滑动窗口中完成,然后移动到下一个像素,等等。
    • 不是全局最大值和最小值,而是每 5x5 像素的最大值(和最小值)。对于每个像素,获取 5x5 个邻居,并找到最大值。从水平通道开始:每 5 个像素单行的最大值: max([a0, a1, a2, a3, a4]) 存储在dst_row[2],移动到 max([a1, a2, a3, a4, a5]) 存储在 dst_row[3]... 完成所有行后迭代列(使用第一遍中的行作为输入)。
    • 注意:二元腐蚀和扩张可能使用“滑动窗口”实现,但最小和最大实现并不是真正的“滑动窗口”。最小值和最大值可以实现为“可分离”过滤器——迭代行(1x5 窗口)然后迭代列(5x1 窗口)。这是每像素 10 次操作(而不是 25 次)。 [我想有更好的解决方案]。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-05
    • 2013-11-14
    • 2015-05-24
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多