使用numpy修改整个图像上的RGB值,而不是循环像素



我有一个函数,它循环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命令中同时处理所有像素,而不是以每个像素为基础?

根据评论更新:

我已经将均值、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(我的解决方案(

在阅读了评论和答案后,我花了一些时间试图弄清楚如何";矢量化";我的代码,我相信我已经成功地将我的每一个条件像素语句移动到了一个numpy向量运算中,这是我更新的代码,它做的事情与原始代码相同,但运行速度大约快100倍。

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

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

像您所做的那样的Python循环很慢,这是因为CPython解释器,但也因为对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来并行化代码。请注意,检查代码是否可以并行化是您的责任(否则行为是未定义的:结果可能是错误的,程序可能崩溃(。

最新更新