用Python进行图像处理计数粒子

10

有没有好的算法可以检测背景强度变化中的粒子呢?例如,如果我有以下图片:

fluorescence

有没有一种方法可以计算小白色颗粒的数量,即使出现在左下角明显不同的背景中?

更明确地说,我想标记图像并使用算法计算这些颗粒的数量,以发现这些颗粒的重要性:

labeled_particles

我尝试过使用PILcvscipynumpy等模块,但是没有成功。我从这个非常相似的SO问题得到了一些提示,乍一看,你可以使用一个简单的阈值来实现:
im = mahotas.imread('particles.jpg')
T = mahotas.thresholding.otsu(im)

labeled, nr_objects = ndimage.label(im>T)
print nr_objects
pylab.imshow(labeled)

但是因为背景不断变化,你会看到这样的结果: bad_threshold_image

我也尝试了其他想法,比如我发现的一种测量爪子的技术,我是这样实现的:

import numpy as np
import scipy
import pylab
import pymorph
import mahotas
from scipy import ndimage
import cv


def detect_peaks(image):
    """
    Takes an image and detect the peaks usingthe local maximum filter.
    Returns a boolean mask of the peaks (i.e. 1 when
    the pixel's value is the neighborhood maximum, 0 otherwise)
    """

    # define an 8-connected neighborhood
    neighborhood = ndimage.morphology.generate_binary_structure(2,2)

    #apply the local maximum filter; all pixel of maximal value 
    #in their neighborhood are set to 1
    local_max = ndimage.filters.maximum_filter(image, footprint=neighborhood)==image
    #local_max is a mask that contains the peaks we are 
    #looking for, but also the background.
    #In order to isolate the peaks we must remove the background from the mask.

    #we create the mask of the background
    background = (image==0)

    #a little technicality: we must erode the background in order to 
    #successfully subtract it form local_max, otherwise a line will 
    #appear along the background border (artifact of the local maximum filter)
    eroded_background = ndimage.morphology.binary_erosion(background, structure=neighborhood, border_value=1)

    #we obtain the final mask, containing only peaks, 
    #by removing the background from the local_max mask
    detected_peaks = local_max - eroded_background

    return detected_peaks

im = mahotas.imread('particles.jpg')
imf = ndimage.gaussian_filter(im, 3)
#rmax = pymorph.regmax(imf)
detected_peaks = detect_peaks(imf)
pylab.imshow(pymorph.overlay(im, detected_peaks))
pylab.show()

但这也没有带来好运,显示出以下结果:

bad_result_from_detect_peaks

使用区域最大值函数,我得到的图像似乎几乎能正确地识别颗粒,但是在我进行高斯滤波时,有太多或太少的颗粒出现在错误的位置(图像具有高斯滤波器2、3和4)。

gaussian of 2 gaussian of 3 gaussian of 4

同时,它还需要适用于类似这样的图片:

fluorescence

这是与上面相同类型的图像,只是粒子密度更高。
编辑:已解决方案:我使用以下代码得到了一个不错的工作解决方案:
import cv2
import pylab
from scipy import ndimage

im = cv2.imread('particles.jpg')
pylab.figure(0)
pylab.imshow(im)

gray = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5,5), 0)
maxValue = 255
adaptiveMethod = cv2.ADAPTIVE_THRESH_GAUSSIAN_C#cv2.ADAPTIVE_THRESH_MEAN_C #cv2.ADAPTIVE_THRESH_GAUSSIAN_C
thresholdType = cv2.THRESH_BINARY#cv2.THRESH_BINARY #cv2.THRESH_BINARY_INV
blockSize = 5 #odd number like 3,5,7,9,11
C = -3 # constant to be subtracted
im_thresholded = cv2.adaptiveThreshold(gray, maxValue, adaptiveMethod, thresholdType, blockSize, C) 
labelarray, particle_count = ndimage.measurements.label(im_thresholded)
print particle_count
pylab.figure(1)
pylab.imshow(im_thresholded)
pylab.show()

这将展示图片如下: particles_given (即给定的图片)
以及

counted_particles

(这是计算的粒子数)
并将粒子计数为60。

正是我想要的!谢谢!另外,scikit-image库中还有一个自适应阈值函数可供使用。 - jadelord
2个回答

6

我使用一种称为自适应对比度的技术,通过使用经过调整的差异阈值来解决了“背景中的可变亮度”问题。它通过对灰度图像与其模糊版本进行线性组合(在这种情况下是差异),然后对其应用阈值来实现。

  1. 使用适当的统计运算符对图像进行卷积。
  2. 从卷积图像中减去原始图像,必要时校正强度比例/伽马值。
  3. 使用常数对差异图像进行阈值处理。

(原始论文)

我非常成功地使用了scipy.ndimage在浮点域中进行操作(比整数图像处理获得更好的结果),如下所示:

original_grayscale = numpy.asarray(some_PIL_image.convert('L'), dtype=float)
blurred_grayscale = scipy.ndimage.filters.gaussian_filter(original_grayscale, blur_parameter)
difference_image = original_grayscale - (multiplier * blurred_grayscale);
image_to_be_labeled = ((difference_image > threshold) * 255).astype('uint8')  # not sure if it is necessary

labelarray, particle_count = scipy.ndimage.measurements.label(image_to_be_labeled)

希望这有所帮助!

谢谢@heltonbiker的帮助!这个想法指引我去了解从图像中解决数独谜题的有趣思路,我成功地实现了类似的功能,并且效果很好。如果其他人遇到类似的问题,我会在我的问题中发布代码。 - chase

3
我无法给出确切的答案,但以下是几个指针:
1. 函数 `mahotas.morph.regmax` 可能比最大滤波更好,因为它可以去除伪峰值。或许可以将此与全局阈值结合使用,添加局部阈值(如窗口平均值)或两者都有。
2. 如果您有多个图像并且具有相同的不均匀背景,则可以计算出平均背景并以其进行归一化,或者使用空白图像作为背景估计。如果您有显微镜,并且像我见过的每个显微镜一样,照明都是不均匀的,那么这种情况就会发生。
类似于:
average = average_of_many(images)
# smooth it
average = mahotas.gaussian_filter(average,24)

现在您可以对图像进行预处理,例如:
preproc = image/average

或类似的东西。

谢谢@luispedro,我已经尝试了区域最大值,看起来几乎很有前途,但是出现了很多不应该考虑的额外粒子。此外,平均化是个好主意,这实际上是在背景去除之后进行的。 - chase
尝试将其与全局阈值结合使用。 - luispedro

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