【问题标题】:How do I calculate the average value of a pixel's surrounding pixels?如何计算像素周围像素的平均值?
【发布时间】:2018-02-27 05:03:47
【问题描述】:

我正在尝试创建一个 5x5 均值过滤器以从图像中去除一些椒盐噪声。我将图像读入一个 numpy 数组。并尝试进行一些更改来计算像素邻居的平均值。我得到的结果很糟糕,我似乎无法弄清楚为什么我的图像结果中有差距。

from PIL import Image
import numpy

image1 = 'noisy.jpg'
save1 = 'filtered.jpg'

def average(path, name):
    temp=Image.open(path)
    image_array = numpy.array(temp)
    new_image = []
    for i in range(0, len(image_array)):
        new_image.append([])
    n = 0
    average_sum = 0
    for i in range(0, len(image_array)):
        for j in range(0, len(image_array[i])):
            for k in range(-2, 3):
                for l in range(-2, 3):
                    if (len(image_array) > (i + k) >= 0) and (len(image_array[i]) > (j + l) >= 0):
                        average_sum += image_array[i+k][j+l]
                        n += 1

            new_image[i].append(int(round(average_sum/n)))
            average_sum = 0
            n = 0

    x = Image.fromarray(numpy.array(new_image), 'L')
    x.save(name)
    print("done")

average(image1, save1)

---------------------输入图像-----------------

---------------------输出图像-----------------

【问题讨论】:

    标签: python-3.x numpy image-processing noise-reduction


    【解决方案1】:

    我只想警告任何发现此页面的人。基本上,没有人应该通过somenumpyarray[y,x] 直接逐个访问像素值。每次输入类似的内容时,Numpy 都必须创建 4 个新的 Python 对象(包含 RGB 值的 tuple 对象,以及每个 R/G/B 值的三个单独的 int 对象)。这是因为在 Python 中,everything 是一个对象(偶数是对象),这意味着数据不能“直接从 Numpy 读取”。必须创建实际的 Python 对象,并且每次您尝试将 Numpy 数据(例如数字)复制到这些对象中,将 Numpy 中的内容读入 Python。

    这是您尝试从数组中读取的每个像素创建 4 个 Python 对象。对于 1080p 图像,如果您只读取每个像素一次,即 8 294 400 个对象。但是上面的代码检查每个像素周围的 5x5(25 像素)矩阵,所以这是 207 360 000 个对象创建!疯了!

    这种对象创建称为装箱(获取本机 Numpy 数据并将其打包/装箱到 Python 对象数据结构中)。以及拆箱(获取 Python 数据,提取其中包含的实际值(例如数字)并将其打包到本机 Numpy 数组中)。从 Python 读取/写入 Numpy 数组中的值总是涉及装箱和拆箱,这就是它非常慢的原因,您应该始终使用本机 Numpy 方法来操作数据. Numpy 不是一个通用的“数组”,我们不能像对待任何随机访问的 Python 列表一样对待。 Numpy 用于使用自己的内置函数进行向量/矩阵运算!事实上,你甚至不应该使用for X in some_ndarray,因为迭代会调用相同的慢速装箱过程(这样的循环中的每个 X 项目都是从 Numpy 中提取并装箱的)。

    无论如何...您尝试实现的是 5x5“框模糊”,即 5x5 正方形半径内所有附近像素的平均值。

    因此,您应该使用本机 C++ 库,该库在纯净、干净的 RAM 中执行所有操作,而完全不涉及 Python。一个这样的库是 OpenCV,它接受一个 ndarray(你的图像像素),并在内部直接从 ndarray 拥有的 RAM 中读取,并直接对每个像素进行原生操作。

    代码如下:

    import cv2
    
    path = "noisy.jpg"
    img = cv2.imread(path)
    img = cv2.blur(img, (5,5)) # This is now your box-blurred image.
    

    1920x1080 图像中的基准:

    • 您的原始代码(滥用 Numpy 数组并创建了超过 2 亿个新 Python 对象):87.80000 秒(这甚至还没有计算所有的 RAM 滥用创建超过 2 亿个对象)
    • 改用 OpenCV:0.02992 秒(快 2935 倍)

    永远不要直接访问 Numpy 数组元素。不是为了那个。

    祝所有未来的读者好运。


    编辑:顺便说一句,要回答原始问题...要去除椒盐噪声,您应该使用 median 过滤器而不是框模糊。

    输入:

    5x5 框模糊(又名平均/平均模糊):

    img = cv2.blur(img, (5,5))

    3x3 中值模糊:

    img = cv2.medianBlur(img, 3)

    【讨论】:

      【解决方案2】:

      无需为新图像创建列表,只需复制原始图像(或创建与原始图像大小相同的数组)并使用新的平均像素值更改其值。像这样

      def average(path, name):
          temp=Image.open(path)
          image_array = numpy.array(temp)
          new_image = image_array.copy()
          n = 0
          average_sum = 0
          for i in range(0, len(image_array)):
              for j in range(0, len(image_array[i])):
                  for k in range(-2, 3):
                      for l in range(-2, 3):
                          if (len(image_array) > (i + k) >= 0) and (len(image_array[i]) > (j + l) >= 0):
                              average_sum += image_array[i+k][j+l]
                              n += 1
      
                  new_image[i][j] = (int(round(average_sum/n)))
                  average_sum = 0
                  n = 0
      
          x = Image.fromarray(numpy.array(new_image), 'L')
          x.save(name)
          print("done")
      

      这给了我以下输出:

      【讨论】:

      • numpy 数据的装箱和拆箱速度极慢(与 python 变量和本机内存之间的转换),所有这些按像素读取的循环在 numpy 中都是死亡和滥用麻木的。您的代码非常慢。 Numpy 并不意味着像这样的随机手动数学的存储容器。 Numpy 用于使用其本机 C++ 函数进行内部数学运算。至于上面的例子:只需使用 OpenCV 和 cv.blur() 即可在 1 行代码中获得超高性能。而且 OpenCV 也有很多其他的过滤器,比如高斯模糊和中值模糊以及自定义内核等。
      • 问题不在于如何更快地实现算法或可以使用哪些其他库。代码有错误,我修复了它(请阅读问题)。如果您是初学者,并且正在尝试实现算法,则可以先提出一个蛮力算法,然后再提高效率。
      猜你喜欢
      • 1970-01-01
      • 2019-05-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-26
      • 1970-01-01
      相关资源
      最近更新 更多