在Python中将模型2D拟合到图像

时间:2016-04-09 22:06:48

标签: python scipy least-squares imaging

我希望在Python中使用图像来拟合模型(这里是2D高斯,但可能是其他东西)。

尝试使用scipy.optimize.curve_fit我有一些问题。见下文。

让我们从一些功能开始:

import numpy as np
from scipy.optimize import curve_fit
from scipy.signal import argrelmax

import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.patches import Circle

from tifffile import TiffFile

# 2D Gaussian model
def func(xy, x0, y0, sigma, H):

    x, y = xy

    A = 1 / (2 * sigma**2)
    I = H * np.exp(-A * ( (x - x0)**2 + (y - y0)**2))
    return I

# Generate 2D gaussian
def generate(x0, y0, sigma, H):

    x = np.arange(0, max(x0, y0) * 2 + sigma, 1)
    y = np.arange(0, max(x0, y0) * 2 + sigma, 1)
    xx, yy = np.meshgrid(x, y)

    I = func((xx, yy), x0=x0, y0=y0, sigma=sigma, H=H)

    return xx, yy, I

def fit(image, with_bounds):

    # Prepare fitting
    x = np.arange(0, image.shape[1], 1)
    y = np.arange(0, image.shape[0], 1)
    xx, yy = np.meshgrid(x, y)

    # Guess intial parameters
    x0 = int(image.shape[0]) # Middle of the image
    y0 = int(image.shape[1]) # Middle of the image
    sigma = max(*image.shape) * 0.1 # 10% of the image
    H = np.max(image) # Maximum value of the image
    initial_guess = [x0, y0, sigma, H]

    # Constraints of the parameters
    if with_bounds:
        lower = [0, 0, 0, 0]
        upper = [image.shape[0], image.shape[1], max(*image.shape), image.max() * 2]
        bounds = [lower, upper]
    else:
        bounds = [-np.inf, np.inf]

    pred_params, uncert_cov = curve_fit(func, (xx.ravel(), yy.ravel()), image.ravel(),
                                        p0=initial_guess, bounds=bounds)

    # Get residual
    predictions = func((xx, yy), *pred_params)
    rms = np.sqrt(np.mean((image.ravel() - predictions.ravel())**2))

    print("True params : ", true_parameters)
    print("Predicted params : ", pred_params)
    print("Residual : ", rms)

    return pred_params

def plot(image, params):

    fig, ax = plt.subplots()
    ax.imshow(image, cmap=plt.cm.BrBG, interpolation='nearest', origin='lower')

    ax.scatter(params[0], params[1], s=100, c="red", marker="x")

    circle = Circle((params[0], params[1]), params[2], facecolor='none',
            edgecolor="red", linewidth=1, alpha=0.8)
    ax.add_patch(circle)
# Simulate and fit model
true_parameters = [50, 60, 10, 500]
xx, yy, image = generate(*true_parameters)

# The fit performs well without bounds
params = fit(image, with_bounds=False)
plot(image, params)

输出:

True params :  [50, 60, 10, 500]
Predicted params :  [  50.   60.   10.  500.]
Residual :  0.0

enter image description here

现在,如果我们对边界(或约束)做同样的拟合。

# The fit is really bad with bounds
params = fit(image, with_bounds=True)
plot(image, params)

输出:

True params :  [50, 60, 10, 500]
Predicted params :  [ 130.          130.            0.72018729    1.44948159]
Residual :  68.1713019773

enter image description here

为什么在添加边界时,拟合效果不佳?

现在另一件事我不明白。当它应用于真实数据时,为什么这种拟合不稳定?见下文。

# Load some real data
image = TiffFile("../data/spot.tif").asarray()
plt.imshow(image, aspect='equal', origin='lower', interpolation="none", cmap=plt.cm.BrBG)
plt.colorbar()

enter image description here

# Fit is not possible without bounds
params = fit(image, with_bounds=False)
plot(image, params)

输出:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-14-3187b53d622d> in <module>()
      1 # Fit is not possible without bounds
----> 2 params = fit(image, with_bounds=False)
      3 plot(image, params)

<ipython-input-11-f14c9dec72f2> in fit(image, with_bounds)
     54 
     55     pred_params, uncert_cov = curve_fit(func, (xx.ravel(), yy.ravel()), image.ravel(),
---> 56                                         p0=initial_guess, bounds=bounds)
     57 
     58     # Get residual

/home/hadim/local/conda/envs/ws/lib/python3.5/site-packages/scipy/optimize/minpack.py in curve_fit(f, xdata, ydata, p0, sigma, absolute_sigma, check_finite, bounds, method, **kwargs)
    653         cost = np.sum(infodict['fvec'] ** 2)
    654         if ier not in [1, 2, 3, 4]:
--> 655             raise RuntimeError("Optimal parameters not found: " + errmsg)
    656     else:
    657         res = least_squares(func, p0, args=args, bounds=bounds, method=method,

RuntimeError: Optimal parameters not found: Number of calls to function has reached maxfev = 1000.

# Fit works but is not accurate at all with bounds
params = fit(image, with_bounds=True)
plot(image, params)

输出:

True params :  [50, 60, 10, 500]
Predicted params :  [   19.31770886    10.52153346    37.          1296.22524248]
Residual :  83.1944464761

enter image description here

2 个答案:

答案 0 :(得分:1)

首先,您的初始参数x0y0是错误的,它们不在图像的中间,但在边界处,它们应该是

x0 = int(image.shape[0])/2 # Middle of the image
y0 = int(image.shape[1])/2 # Middle of the image

将它们放在图像的边界可能会在受约束的情况下产生一些问题,因为它没有给它在某些方向上移动的空间。这是我的推测,取决于拟合方法。

同样谈论方法,curve_fit可以使用scipy least_squares documentation中的三个中的任何一个:lmtrfdogbox

  
      
  • 'trf':信任区域反射算法,特别适用于带边界的大型稀疏问题。通常很健全的方法。
  •   
  • 'dogbox':具有矩形信任区域的狗腿洞算法,典型用例是边界的小问题。不建议用于排名不足的雅可比人的问题。
  •   
  • 'lm':在MINPACK中实现的Levenberg-Marquardt算法。不处理边界和稀疏的雅可比人。通常是解决小型无约束问题的最有效方法。
  •   

curve_fit will use different methods for bounded and unbounded cases

  

对于无约束问题,默认为'lm',如果提供了边界,则为'trf'

所以我建议定义一个使用的方法,在更正初始参数后,我的示例使用trfdogbox获得了良好的结果,但是您应该检查哪种方法更适合您的实际数据

答案 1 :(得分:1)

我写了lightweight class来做到这一点。边界没有很好地实现,但可以根据您的需要进行更改。

你在这里遇到三个主要问题:

  1. 您没有在数据中建模偏移量。通过查看您的图像,背景大约为1300-1400。
  2. 初始猜测参数不是很好的估计(正如@Noel所指出的)
  3. 您需要的数据远远超过必要的数据,我建议选择峰值周围的区域。如果您对初始参数做出更好的猜测,则可以将您的拟合限制在以猜测x0y0为中心的窗口中。
  4. 有两种解决问题1的方法:

    • 估算背景并从数据中减去背景(数据的medianmode是一个不错的选择,my class使用RANSAC提供的blob detection algorithms算法1}}以更复杂的方式估计这一点)
    • 或者您可以直接为背景建模。

    对于问题2.您可以使用sklearn的{​​{3}}。我还写了另一个class来包装skimage的DOG算法,以使这更容易。一旦解决问题2,问题3也会得到解决。