Python中针对图像中每个像素选择7*7邻域像素的最快方法

5
需要将图像读取为数组,对于每个像素选择7*7邻域像素,然后将其重塑并放置在训练集的第一行:
  import numpy as np
  from scipy import misc
  face1=misc.imread('face1.jpg') 

face1 的尺寸为 (288, 352, 3),需要为每个像素找到 7*7 邻居像素,因此有 49*3 种颜色,然后将其重塑为一个 (1,147) 数组,并将其叠加成所有像素的数组。我采取了以下方法:

X_training=np.zeros([1,147] ,dtype=np.uint8)
for i in range(3, face1.shape[0]-3):
    for j in range(3, face1.shape[1]-3):
        block=face1[i-3:i+4,j-3:j+4]
        pxl=np.reshape(block,(1,147))
        X_training=np.vstack((pxl,X_training))

导致 X_training 的形状为 (97572, 147)

由于最后一行包含全部为零的数据,因此:

a = len(X_training)-1
X_training = X_training[:a]

上述代码适用于一张图片,但如果有2000张图片,则需要花费大量时间进行处理(Wall time: 5min 19s)。我正在寻找更快的方法来迭代每个像素并执行上述任务。
编辑:enter image description here 这就是我所说的相邻像素,对于每个像素face1[i-3 : i+4 ,j-3:j+4]

1
你能把这张图片分成块,并使用线程或类似的方式处理不同的部分吗? - Jacobr365
@Jacobr365 没有必要的技能来完成那个任务。 - chessosapiens
1
认为应该这样写:X_training=np.zeros([0,147], dtype=np.uint8) - Divakar
@jotasi编辑了帖子。 - chessosapiens
标题是“为每个像素选择7 * 7邻域像素..”。由于有全零行,可能会让人感到困惑。如果我的语气听起来太挑剔了,对不起,我只是想看到一个好问题。 - Divakar
显示剩余9条评论
3个回答

6

一种高效的方法是使用 stride_tricks 创建一个二维滚动窗口,然后将其展平:

import numpy as np

face1 = np.arange(288*352*3).reshape(288, 352, 3)  # toy data

n = 7  # neighborhood size

h, w, d = face1.shape
s = face1.strides

tmp = np.lib.stride_tricks.as_strided(face1, strides=s[:2] + s,
                                      shape=(h - n + 1, w - n + 1, n, n, d))
X_training = tmp.reshape(-1, n**2 * d)
X_training = X_training[::-1]  # to get the rows into same order as in the question

tmp 是图像的一个5D视图,其中 tmp[x, y, :, :, c] 等价于颜色通道 c 中的邻域 face1[x:x+n, y:y+n, c]


不错!为了复制 OP 的输出,我们需要沿着行进行翻转。 - Divakar
@好的,谢谢。我将其作为显式步骤添加,因为我不太喜欢负步长 :) - MB-F
我以前从没听说过strides,但是通过一些阅读,我发现我错过了一些强大的东西...谢谢! - 2cynykyl
很高兴听到这个消息.. 我也在这个网站上学习了步幅。 - MB-F
@kazemakase 你有什么想法可以在Pyspark中完成完全相同的任务吗?https://stackoverflow.com/questions/45400235/fastest-way-to-select-77-neighbor-pixels-for-every-pixel-in-an-image-in-pyspark - chessosapiens
@sanaz 不好意思,我不知道关于pyspark的任何信息。 - MB-F

3
以下内容在我的笔记本电脑上不到1秒钟:
import scipy as sp
im = sp.rand(300, 300, 3)

size = 3
ij = sp.meshgrid(range(size, im.shape[0]-size), range(size, im.shape[1]-size))
i = ij[0].T.flatten()
j = ij[1].T.flatten()

N = len(i)
L = (2*size + 1)**2
X_training = sp.empty(shape=[N, 3*L])

for pixel in range(N):
    si = (slice(i[pixel]-size, i[pixel]+size+1))
    sj = (slice(j[pixel]-size, j[pixel]+size+1))
    X_training[pixel, :] = im[si, sj, :].flatten()

X_training = X_training[-1::-1, :]

当我无法想出一行向量化版本时,总是有点难过,但至少这对你来说更快。


你能否将自己的答案与最佳答案进行比较?它们返回的结果不同。某个地方一定有错误,我的方法和最佳答案返回的结果完全相同。 - chessosapiens
我已经更新了我的答案,现在它产生的输出与@kazemakase相同。行的顺序不同,所以我转置了“ij”数组,并且还反转了最终答案中的行的顺序。 - 2cynykyl

3

使用scikit-image:

import numpy as np
from skimage import util

image = np.random.random((288, 352, 3))
windows = util.view_as_windows(image, (7, 7, 3))

out = windows.reshape(-1, 7 * 7 * 3)

+1 用skimage不错...但我刚刚阅读了这个“view_as_window”函数的文档,它有可怕的性能问题!有人应该真正重新实现它,使用被接受的答案中的“stride_tricks”。 - 2cynykyl
我不确定您的意思;它确实使用步幅:https://github.com/scikit-image/scikit-image/blob/master/skimage/util/shape.py#L107 - Stefan van der Walt
非常抱歉没有检查源代码...但是文档字符串确实让人感觉它正在创建数组的新副本,而且内存可能会变得异常庞大。我的理解是,步幅提供了对基础数据单个副本的新“视图”,因此不需要复制,速度更快。我想知道你的答案在速度方面与@kazemakase相比如何? - 2cynykyl
如果您能进行比较,那将是非常棒的!我们非常乐意接受PR来改进文档或实现。 - Stefan van der Walt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接