【问题标题】:Fastest way to compute image dataset channel wise mean and standard deviation in Python在 Python 中计算图像数据集通道均值和标准差的最快方法
【发布时间】:2024-04-22 09:10:01
【问题描述】:

我有一个不适合内存的巨大图像数据集。我想计算meanstandard deviation,从磁盘加载图像。

我目前正在尝试使用在 wikipedia 上找到的算法。

# for a new value newValue, compute the new count, new mean, the new M2.
# mean accumulates the mean of the entire dataset
# M2 aggregates the squared distance from the mean
# count aggregates the amount of samples seen so far
def update(existingAggregate, newValue):
    (count, mean, M2) = existingAggregate
    count = count + 1 
    delta = newValue - mean
    mean = mean + delta / count
    delta2 = newValue - mean
    M2 = M2 + delta * delta2

    return existingAggregate

# retrieve the mean and variance from an aggregate
def finalize(existingAggregate):
    (count, mean, M2) = existingAggregate
    (mean, variance) = (mean, M2/(count - 1)) 
    if count < 2:
        return float('nan')
    else:
        return (mean, variance)

这是我当前的实现(只为红色通道计算):

count = 0
mean = 0
delta = 0
delta2 = 0
M2 = 0
for i, file in enumerate(tqdm(first)):
    image = cv2.imread(file)
    for i in range(224):
        for j in range(224):
            r, g, b = image[i, j, :]
            newValue = r
            count = count + 1
            delta = newValue - mean
            mean = mean + delta / count
            delta2 = newValue - mean
            M2 = M2 + delta * delta2

print('first mean', mean)
print('first std', np.sqrt(M2 / (count - 1)))

这个实现在我尝试过的数据集的一个子集上运行得足够接近。

问题是它非常慢,因此无法生存。

  • 有标准的方法吗?

  • 如何调整它以获得更快的结果或计算所有数据集的 RGB 均值和标准差,而无需同时以合理的速度将其全部加载到内存中?

【问题讨论】:

  • Olla Bruno...听起来多处理是您的解决方案。最后,您需要将每个图像加载到内存中......最终......但是通过多进程,您可以拆分工作负载并将文件列表拆分为可以更轻松地处理的块,因此不会完全耗尽您的内存.使用 thread1 设置进程,然后在 4-7 个内核上每单位时间分出(取决于您的内核)4-7 个线程进程。如果处理速度很快,您可以将每个核心发送的线程数加倍。想想线程中的python示例...ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer.

标签: python opencv computer-vision


【解决方案1】:

由于这是一项数值繁重的任务(围绕矩阵或张量进行大量迭代),我总是建议使用擅长此方面的库:numpy.

正确安装的 numpy 应该能够利用底层 BLAS(基本线性代数子例程)例程,这些例程经过优化,可以从内存层次结构的角度操作浮点数组。

imread 应该已经为您提供了 numpy 数组。您可以通过

获得红色通道图像的重构一维数组
import numpy as np
val = np.reshape(image[:,:,0], -1)

这样的平均值

np.mean(val)

和标准差

np.std(val)

这样就可以摆脱两层python循环了:

count = 0
mean = 0
delta = 0
delta2 = 0
M2 = 0
for i, file in enumerate(tqdm(first)):
    image = cv2.imread(file)
        val = np.reshape(image[:,:,0], -1)
        img_mean = np.mean(val)
        img_std = np.std(val)
        ...

增量更新的其余部分应该很简单。

一旦你这样做了,瓶颈将成为图像加载速度,它受到磁盘读取操作性能的限制。在这方面,根据我之前的经验,我怀疑使用其他人建议的多线程会有很大帮助。

【讨论】:

  • 行话“baed” = ?
  • @ZF007:已修复。谢谢!
  • > 竖起大拇指
  • 但是使用这种方法,我怎样才能得到数据集的整体std?我需要单独的频道值。
  • @Bruno Klein 您上面引用的方法是一种数值稳定的方法,非常棒。但是,如果它不会溢出,您也可以选择更简单的方法:每个图像均值的数组之和的均值是整体均值。标准是一个棘手的问题。如果您不关注世界级的实现,则可能存储每个图像的平方和 (sos) 并逐步求和。完成后,计算sqrt(sum_sos/N - mean**2)(你会想知道N 是什么)。请注意,如果 sum_sosmean**2 太接近,您可能需要注意数字问题。
【解决方案2】:

你也可以使用opencv的方法meanstddev

cv2.meanStdDev(src[, mean[, stddev[, mask]]]) → mean, stddev

【讨论】:

    【解决方案3】:

    如果您不想将内容放入包含整个数据集的数组的内存中,则可以迭代地计算它

    # can be whatever I just made this number up
    global_mean = 134.0
    # just get a per-pixel array with the vals for (x_i - mu) ** 2 / |x|
    sums = ((images[0] - global_mean) ** 2) / len(images)
    
    for img in images[1:]:
        sums = sums + ((img - global_mean) ** 2) / len(images)
    
    # Get mean of all per-pixel variances, and then take sqrt to get std
    dataset_std = np.sqrt(np.mean(sums))
    

    【讨论】:

      最近更新 更多