从numpy.ndarray中计数项出现次数的最快方法
我有一张图片的直方图,简单来说,直方图就是一个图表,显示了在这张图片中,像素值(从0到255)出现的次数。Y轴表示出现的次数,X轴表示像素值。
我需要的是像素值在75到125之间的总数。
image= cv2.imread('grade_0.jpg')
listOfNumbers = image.ravel() #generates the long list of 0-255 values from the image) type numpy.ndarray
现在我的代码是通过把numpy.ndarray转换成列表,然后一个一个地数值来实现这个功能。
start = time.time()
numberlist = list(list0fNumbers)
sum = 0
for x in range(75,125):
sum = sum + numberlist.count(x)
end = time.time()
print('Sum: ' + str(sum))
print('Execution time_ms: ' + str((end-start) * 10**3))
结果:
Sum: 57111
Execution time_ms: 13492.571830749512
我需要对成千上万张图片做这样的处理,而仅仅处理这一张图片就花了13秒。这实在是太低效了。有没有什么建议可以让我把这个速度提高到10毫秒以内?我不仅仅是要计算75到125的总和,还会计算其他范围,比如0-80、75-125、120-220、210-255。如果这些范围也需要13秒来处理一张256x256的图片,那处理一张这样的图片大约需要60秒,我觉得即使是慢电脑,这个时间也太长了。
这里有一张示例图片:
2 个回答
3
你可以使用 np.bincount
这个函数:
y = np.bincount(arr)
print(y[75:125].sum())
输出结果是:
57032
完整代码如下:
import numpy as np
from PIL import Image
# Open your image file:
image_path = "image.png"
image = Image.open(image_path)
arr = np.array(image).ravel()
y = np.bincount(arr)
print(y[75:125].sum())
2
你可以使用简单的布尔运算符:
import cv2
image = cv2.imread('grade_0.jpg')
out = ((image>=75)&(image<125)).sum()
# 57032
或者,正如@jared所建议的:
out = np.count_nonzero((image>=75)&(image<125))
计数的时间:
# sum
170 µs ± 2.81 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
# count_non_zero
47.6 µs ± 2.94 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
补充说明:我意识到你想处理多个区间,这可以通过以下方式实现:
bins = [(0,80),(75,125),(120,220),(210,255)]
out = {f'{a}-{b}': np.count_nonzero((image>=a)&(image<b)) for a, b in bins}
# {'0-80': 26274, '75-125': 57032, '120-220': 86283, '210-255': 40967}
但是这样会对每个区间重新读取图像的数据。
在这种情况下,正如@Andrej所建议的,bincount
确实更合适,因为它只需计算一次像素:
bins = [(0,80),(75,125),(120,220),(210,255)]
counts = np.bincount(image.ravel())
out = {'-'.join(map(str, t)): counts[slice(*t)].sum() for t in bins}
# {'0-80': 26274, '75-125': 57032, '120-220': 86283, '210-255': 40967}
计时会根据图像的大小和区间的数量而有所不同。对于小图像,重新计数可能更有效,而对于大图像,bincount
可能会更好(但令人惊讶的是,并不总是如此)。
256 x 256
# count_nonzero in loop
198 µs ± 8.75 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
# bincount
440 µs ± 6.82 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
512 x 512:
# count_nonzero in loop
918 µs ± 31 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
# bincount
1.76 ms ± 26.1 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
1024 x 1024:
# count_nonzero in loop
11 ms ± 210 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# bincount
8.15 ms ± 437 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2048 x 2048:
# count_nonzero in loop
47.1 ms ± 3.01 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
# bincount
48.8 ms ± 3.02 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)