Python - 计算图像的直方图

时间:2014-03-03 22:44:24

标签: python opencv numpy histogram

我正在努力教自己计算机化图像处理的基础知识,同时我也在教自己Python。

给定具有3个通道的尺寸为2048x1354的图像x,有效地计算像素强度的直方图。

import numpy as np, cv2 as cv

img = cv.imread("image.jpg")
bins = np.zeros(256, np.int32)

for i in range(0, img.shape[0]):
    for j in range(0, img.shape[1]):

        intensity = 0
        for k in range(0, len(img[i][j])):
            intensity += img[i][j][k]

        bins[intensity/3] += 1

print bins

我的问题是这段代码运行得非常慢,大约在30秒内。我怎样才能加快速度并且更加Pythonic?

4 个答案:

答案 0 :(得分:5)

您可以使用更新的OpenCV python接口,它本身使用numpy数组并使用matplotlib hist绘制像素强度的直方图。我的电脑只需不到秒。

import matplotlib.pyplot as plt
import cv2

im = cv2.imread('image.jpg')
# calculate mean value from RGB channels and flatten to 1D array
vals = im.mean(axis=2).flatten()
# plot histogram with 255 bins
b, bins, patches = plt.hist(vals, 255)
plt.xlim([0,255])
plt.show()

enter image description here

更新: 上面指定的箱数并不总是提供所需的结果,因为最小值和最大值是根据实际值计算的。此外,值254和255的计数在最后一个仓中求和。这是更新的代码,它总是正确绘制直方图,条形图以值0..255

为中心
import numpy as np
import matplotlib.pyplot as plt
import cv2

# read image
im = cv2.imread('image.jpg')
# calculate mean value from RGB channels and flatten to 1D array
vals = im.mean(axis=2).flatten()
# calculate histogram
counts, bins = np.histogram(vals, range(257))
# plot histogram centered on values 0..255
plt.bar(bins[:-1] - 0.5, counts, width=1, edgecolor='none')
plt.xlim([-0.5, 255.5])
plt.show()

enter image description here

答案 1 :(得分:3)

在纯python中不可能这样做(即不删除for循环)。 Python的for循环结构有太多东西要快。如果你真的想要保持for循环,唯一的解决方案是numba或cython,但这些都有自己的问题。通常,这样的循环用c / c ++编写(在我看来最直接),然后从python调用,它的主要作用是脚本语言。

话虽如此,opencv + numpy提供了足够多的有用例程,因此在90%的情况下,可以简单地使用内置函数,而无需编写自己的像素级代码。

这是numba中的解决方案,无需更改循环代码。在我的电脑上,它比纯蟒蛇快约150倍。

import numpy as np, cv2 as cv

from time import time
from numba import jit,int_,uint8 

@jit(argtypes=(uint8[:,:,:],int_[:]),
    locals=dict(intensity=int_),
    nopython=True
    )
def numba(img,bins):
    for i in range(0, img.shape[0]):
        for j in range(0, img.shape[1]):
            intensity = 0
            for k in range(0, len(img[i][j])):
                intensity += img[i][j][k]
            bins[intensity/3] += 1


def python(img,bins):
    for i in range(0, img.shape[0]):
        for j in range(0, img.shape[1]):
            intensity = 0
            for k in range(0, len(img[i][j])):
                intensity += img[i][j][k]
            bins[intensity/3] += 1

img = cv.imread("image.jpg")
bins = np.zeros(256, np.int32)

t0 = time()
numba(img,bins)
t1 = time()
#print bins
print t1 - t0

bins[...]=0
t0 = time()
python(img,bins)
t1 = time()
#print bins
print t1 - t0    

答案 2 :(得分:2)

如果您只想计算数组中每个值的出现次数,numpy可以使用numpy.bincount为您执行此操作。在你的情况下:

arr  = numpy.asarray(img)
flat = arr.reshape(numpy.prod(arr.shape[:2]),-1)
bins = numpy.bincount(np.sum(flat,1)/flat.shape[1],minsize=256)

我在这里使用numpy.asarray来确保img是一个numpy数组,所以我可以将它展平为一维数组bincount的需要。如果img已经是数组,则可以跳过该步骤。计数本身将非常快。这里的大部分时间都可能用于将cv矩阵转换为数组。

修改:根据this answer,您可能需要使用numpy.asarray(img[:,:])(或可能img[:,:,:])才能成功将图片转换为数组。另一方面,根据this,从较新版本的openCV中获得的内容已经是一个numpy数组。因此,在这种情况下,您可以完全跳过asarray

答案 3 :(得分:1)

看看at MatPlotLib。这将带您完成您想要做的所有事情,并且没有for循环。