我正在尝试在python中开发一种快速算法,以查找图像中的峰值,然后找到这些峰值的质心。我已经使用scipy.ndimage.label和ndimage.find_objects编写了以下代码来查找对象。这似乎是代码中的瓶颈,在500x500图像中定位20个对象大约需要7毫秒。我想将此图像放大到更大(2000x2000)的图像,但是时间增加到将近100毫秒。因此,我想知道是否有更快的选择。
这是我到目前为止的代码,可以运行,但是很慢。首先,我使用一些高斯峰来模拟我的数据。这部分很慢,但是实际上我将使用真实数据,因此我不太在意加快该部分的速度。我希望能够很快找到山峰。
import time import numpy as np import matplotlib.pyplot as plt import scipy.ndimage import matplotlib.patches plt.figure(figsize=(10,10)) ax1 = plt.subplot(221) ax2 = plt.subplot(222) ax3 = plt.subplot(223) ax4 = plt.subplot(224) size = 500 #width and height of image in pixels peak_height = 100 # define the height of the peaks num_peaks = 20 noise_level = 50 threshold = 60 np.random.seed(3) #set up a simple, blank image (Z) x = np.linspace(0,size,size) y = np.linspace(0,size,size) X,Y = np.meshgrid(x,y) Z = X*0 #now add some peaks def gaussian(X,Y,xo,yo,amp=100,sigmax=4,sigmay=4): return amp*np.exp(-(X-xo)**2/(2*sigmax**2) - (Y-yo)**2/(2*sigmay**2)) for xo,yo in size*np.random.rand(num_peaks,2): widthx = 5 + np.random.randn(1) widthy = 5 + np.random.randn(1) Z += gaussian(X,Y,xo,yo,amp=peak_height,sigmax=widthx,sigmay=widthy) #of course, add some noise: Z = Z + scipy.ndimage.gaussian_filter(0.5*noise_level*np.random.rand(size,size),sigma=5) Z = Z + scipy.ndimage.gaussian_filter(0.5*noise_level*np.random.rand(size,size),sigma=1) t = time.time() #Start timing the peak-finding algorithm #Set everything below the threshold to zero: Z_thresh = np.copy(Z) Z_thresh[Z_thresh<threshold] = 0 print 'Time after thresholding: %.5f seconds'%(time.time()-t) #now find the objects labeled_image, number_of_objects = scipy.ndimage.label(Z_thresh) print 'Time after labeling: %.5f seconds'%(time.time()-t) peak_slices = scipy.ndimage.find_objects(labeled_image) print 'Time after finding objects: %.5f seconds'%(time.time()-t) def centroid(data): h,w = np.shape(data) x = np.arange(0,w) y = np.arange(0,h) X,Y = np.meshgrid(x,y) cx = np.sum(X*data)/np.sum(data) cy = np.sum(Y*data)/np.sum(data) return cx,cy centroids = [] for peak_slice in peak_slices: dy,dx = peak_slice x,y = dx.start, dy.start cx,cy = centroid(Z_thresh[peak_slice]) centroids.append((x+cx,y+cy)) print 'Total time: %.5f seconds\n'%(time.time()-t) ########################################### #Now make the plots: for ax in (ax1,ax2,ax3,ax4): ax.clear() ax1.set_title('Original image') ax1.imshow(Z,origin='lower') ax2.set_title('Thresholded image') ax2.imshow(Z_thresh,origin='lower') ax3.set_title('Labeled image') ax3.imshow(labeled_image,origin='lower') #display the color-coded regions for peak_slice in peak_slices: #Draw some rectangles around the objects dy,dx = peak_slice xy = (dx.start, dy.start) width = (dx.stop - dx.start + 1) height = (dy.stop - dy.start + 1) rect = matplotlib.patches.Rectangle(xy,width,height,fc='none',ec='red') ax3.add_patch(rect,) ax4.set_title('Centroids on original image') ax4.imshow(Z,origin='lower') for x,y in centroids: ax4.plot(x,y,'kx',ms=10) ax4.set_xlim(0,size) ax4.set_ylim(0,size) plt.tight_layout plt.show()
size = 500的结果:
编辑:如果峰的数量很大(〜100),并且图像的大小很小,则瓶颈实际上是重心部分。因此,也许这部分的速度也需要优化。
查找峰值的方法(简单的阈值)当然对阈值的选择非常敏感:将阈值设置得太低,您将“检测”出不是峰值的事物。设置得太高,您将错过有效的峰值。
有更健壮的替代方案,它将检测图像强度中的所有局部最大值,而不管其强度值如何。我的首选方法是应用具有较小(5x5或7x7)结构元素的膨胀,然后找到原始图像及其膨胀版本具有相同值的像素。之所以起作用,是因为根据定义,dilation(x,y,E,img)= {E内以像素(x,y)为中心的img的最大值}},因此dilation(x,y,E,img)= img(x ,y)每当(x,y)是E尺度上的局部最大值的位置。
随着形态运算符的快速实现(例如,OpenCV中的一个),该算法在空间和时间上图像的大小都是线性的(一个额外的图像大小的缓冲区用于膨胀图像,一个在两者上传递)。紧要关头,它也可以在线实现,而无需额外的缓冲区和一点点的复杂性,并且它仍然是线性时间。
要在存在盐和胡椒粉或类似噪声(可能会引入许多错误的最大值)的情况下进一步增强其稳定性,可以对结构大小不同的元素(例如5x5和7x7)应用两次该方法,然后仅保留稳定的元素。最大值,可以通过不变最大值的位置或不超过一个像素的位置变化来定义稳定性,等等。此外,当您有理由认为它们是由噪声引起的时,您可能希望抑制附近的较低最大值。一种有效的方法是首先检测上述所有局部最大值,按高度降序对它们进行排序,然后在排序后的列表中查找并保留它们,如果它们在图像中的值未更改,并且将其保留,则将其设置为将(2d + 1)x(2d + 1)邻域中的所有像素归零,其中d是您愿意容忍的附近最大值之间的最小距离。