从numpy.ndarray中计数项出现次数的最快方法

2 投票
2 回答
62 浏览
提问于 2025-04-13 20:04

我有一张图片的直方图,简单来说,直方图就是一个图表,显示了在这张图片中,像素值(从0到255)出现的次数。Y轴表示出现的次数,X轴表示像素值。

enter image description here

我需要的是像素值在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秒,我觉得即使是慢电脑,这个时间也太长了。

这里有一张示例图片:

enter image description here

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)

撰写回答