【问题标题】:Using numpy to modify RGB values on entire image, instead of looping pixels使用 numpy 修改整个图像的 RGB 值,而不是循环像素
【发布时间】:2023-04-01 02:35:01
【问题描述】:

我有一个函数可以循环 opencv 图像的 RGB 像素并对每个像素进行一些更改。但是这种方法很慢,我认为如果我可以使用 numpy 一次完成所有更改,它会快得多。

如何在 Numpy 中而不是循环中制作这些过滤器?

def apply_image_filter(image):
    image = cv2.normalize(image, image, 0, 255, cv2.NORM_MINMAX)

    h = image.shape[0]
    w = image.shape[1]

    # loop over the image, pixel by pixel
    for y in range(0, h):
        for x in range(0, w):
            if np.all(image[y, x] == 0): # Ignore black pixels
                continue

            mean = np.mean(image[y, x])
            maxdex = np.argmax(image[y, x])
            maxval = image[y, x][maxdex]

            # Equal RGB go to white or black
            if np.all(image[y, x] == mean):
                image[y, x] = [255, 255, 255] if mean > 100 else [0, 0, 0]
                continue

            # High color equals go to white
            if np.all((mean * 0.85) < image[y, x]) and np.all((mean * 1.15) > image[y, x]):
                image[y, x] = [255, 255, 255] if mean >= 90 else [0, 0, 0]
                continue

            if maxval > mean * 1.5:
                image[y, x] = [0, 0, 0]
                image[y, x][maxdex] = 255
                continue

            median = np.median(image[y, x])

            if maxval > median * 1.5:
                image[y, x] = [0, 0, 0]
                image[y, x][maxdex] = 255
                continue

    return image

更具体地说,我如何将条件像素更新移到循环之外。

例如这一行:

if np.all((mean * 0.85) < image[y, x]) and np.all((mean * 1.15) > image[y, x]):
    image[y, x] = [255, 255, 255] if mean >= 90 else [0, 0, 0]

它检查像素中的 R、G 和 B 值是否都在像素平均值的 15% 以内,如果是,则根据平均值将像素设置为黑色或白色。

如何移动此条件更新,以便它在一个 Numpy 命令中一次处理所有像素,而不是基于每个像素?

从 cmets 更新:

我已将均值、argmax 和中值移出循环,因此它们首先应用于整个数组。现在执行速度比以前快了大约 60%。但是我仍在为每个像素做条件语句。

def apply_image_filter(image):
    image = cv2.normalize(image, image, 0, 255, cv2.NORM_MINMAX)

    h = image.shape[0]
    w = image.shape[1]

    # These Numpy operations are now outside the loop
    means = np.mean(image, axis=2)
    maxdexes = np.argmax(image, axis=2)
    medians = np.median(image, axis=2)

    # loop over the image, pixel by pixel
    for y in range(0, h):
        for x in range(0, w):
            if means[y, x] == 0: # Ignore black pixels
                continue
            
            # assign to local variables for readability
            mean = means[y, x]
            maxdex = maxdexes[y, x]
            maxval = image[y, x][maxdex]

            # Equal RGB go to white or black
            if np.all(image[y, x] == mean):
                image[y, x] = [255, 255, 255] if mean > 100 else [0, 0, 0]
                continue

            # High color equals go to white
            if np.all((mean * 0.85) < image[y, x]) and np.all((mean * 1.15) > image[y, x]):
                image[y, x] = [255, 255, 255] if mean >= 90 else [0, 0, 0]
                continue

            if maxval > mean * 1.5:
                image[y, x] = [0, 0, 0]
                image[y, x][maxdex] = 255
                continue

            if maxval > medians[y, x] * 1.5:
                image[y, x] = [0, 0, 0]
                image[y, x][maxdex] = 255
                continue

    return image

更新 #2(我的解决方案)

阅读 cmets 并回答后,我花了一些时间试图弄清楚如何“矢量化”我的代码,并且我相信我设法将我的每一个条件像素语句都移动到一个 numpy 向量操作中,这是我的更新后的代码,与原始代码的作用相同,但运行速度提高了大约 100 倍。

我觉得它失去了一些可读性,但为了 100 倍的加速,我只需要添加好的 cmets。

def apply_image_filter(image):
    image = cv2.normalize(image, image, 0, 255, cv2.NORM_MINMAX)

    means = np.mean(image, axis=2)
    maxdexes = np.argmax(image, axis=2)

    maxes = np.amax(image, axis=2)
    mins = np.amin(image, axis=2)

    image[np.logical_and(means == maxes, means > 100)] = 255
    image[np.logical_and(means == maxes, means <= 100)] = 0

    up_means = means * 1.15
    down_means = means * 0.85

    image[np.logical_and(means >= 90, np.logical_and(up_means > maxes, down_means < mins))] = 255
    image[np.logical_and(means < 90, np.logical_and(up_means > maxes, down_means < mins))] = 0

    up_means = means * 1.5
    new_means = np.mean(image, axis=2)

    higher_than_mean_logic = np.logical_and(maxes > up_means, new_means < 255)
    image[higher_than_mean_logic] = 0
    image[higher_than_mean_logic, maxdexes[higher_than_mean_logic]] = 255

    new_maxes = np.amax(image, axis=2)
    medians = np.median(image, axis=2)
    medians_logic = np.logical_and(new_maxes > medians * 1.5, new_maxes < 255)
    image[medians_logic] = 0
    image[medians_logic, maxdexes[medians_logic]] = 255

    return image

【问题讨论】:

  • 尝试在询问之前解决这个问题。展示你的尝试。 How to Ask。 -- 这里有一个提示:你可以让mean = np.mean(image, axis=2) 在整个图像上得到它
  • 我花了一整天的时间来解决这个问题。因此,根据您的提示,我可以将平均值移到像素循环之外并放置类似 means = np.mean(image, axis=2) 然后 mean = means[y, x] 的内容,但它不会使循环更快。
  • 循环不会运行得更快,因为它正在对其中的每个操作进行类型检查。您需要通过 NumPy 或 OpenCV 对操作进行矢量化。目前尚不清楚您要对循环做什么,并且您没有提供能够实际检查功能的测试图像,因此很难帮助您。还有np.mean对整个矩阵进行操作,所以不知道为什么会出现在循环里面。
  • 我的提示是 one 事情要做。您需要掌握这个想法并将其应用于所有其他操作(argmax 也可以采用轴参数......继续)。当您将所有内容移出循环后,循环已变为空。 -- @stateMachine np.mean 对给定的任何内容进行操作。在那个循环中,它被赋予了一个像素。它将返回三个颜色值的平均值。使用轴参数,它只对给定的轴进行操作,而将未提及的轴单独留下。

标签: python numpy opencv


【解决方案1】:

像你所做的那样的 Python 循环很慢,因为 CPython interpreter 还因为对 Numpy 的标量访问非常慢,因为 Numpy 没有为此优化(即使是这样,CPython由于解释器本身及其设计方式,防止代码变快)。一般的解决方案是使用高级 Numpy 调用而不是循环中的标量访问来矢量化您的代码。但是,虽然这在您的情况下是可能的,但它会导致创建许多效率不高的临时数组(尽管它应该比您当前的代码至少快一个数量级)。当没有可以使用的简单 Numpy 函数时,一种更快更简单的方法是简单地使用 Numba 来完成此类任务。

这是生成的(未经测试的)Numba 代码:

@nb.njit('void(uint8[:,:,::1], float64[:,::1], int64[:,::1], float64[:,::1])')
def computeLoop(image, means, maxdexes, medians):
    assert image.shape[0] == 3

    # loop over the image, pixel by pixel
    for y in range(0, h):
        for x in range(0, w):
            if means[y, x] == 0: # Ignore black pixels
                continue
            
            # assign to local variables for readability
            mean = means[y, x]
            maxdex = maxdexes[y, x]
            maxval = image[y, x, maxdex]

            # Equal RGB go to white or black
            if image[y, x, 0] == mean and image[y, x, 1] == mean and image[y, x, 2] == mean:
                value = 255 if mean > 100 else 0
                image[y, x, :] = value
                continue

            mini, maxi = mean * 0.85, mean * 1.15

            # High color equals go to white
            if mini < image[y, x, 0] < maxi and mini < image[y, x, 1] < maxi and mini < image[y, x, 2] < maxi:
                value = 255 if mean >= 90 else 0
                image[y, x, :] = value
                continue

            if maxval > mean * 1.5 or maxval > medians[y, x] * 1.5:
                image[y, x, :] = 0
                image[y, x, maxdex] = 255
                continue

def apply_image_filter(image):
    image = cv2.normalize(image, image, 0, 255, cv2.NORM_MINMAX)

    h = image.shape[0]
    w = image.shape[1]

    # These Numpy operations are now outside the loop
    means = np.mean(image, axis=2)
    maxdexes = np.argmax(image, axis=2)
    medians = np.median(image, axis=2)

    computeLoop(image, means, maxdexes, medians)

    return image

这段代码肯定比最初的代码快几个数量级。此外,请注意,可以通过添加njit 标志parallel=True 并在包含y 索引的循环中使用np.prange 而不是range 来并行化代码。请注意,您有责任检查代码是否可以并行化(否则行为未定义:结果可能错误并且程序可能崩溃)。

【讨论】:

    猜你喜欢
    • 2013-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-05
    • 2010-12-11
    • 1970-01-01
    • 2020-08-23
    • 1970-01-01
    相关资源
    最近更新 更多