【发布时间】:2020-02-16 02:11:38
【问题描述】:
我想在图像中拟合多个点,以在局部背景校正失败的拥挤区域中找到它们的真实强度。
我的方法只是选择图像的一个局部区域并进行拟合。问题是,拟合不会产生任何有用的结果,只是默认为初始参数。添加边界来帮助它使拟合根本不收敛。
我做错了什么?
代码:
import scipy.optimize as opt
import numpy as np
import matplotlib.pyplot as plt
import skimage.feature
from collections import namedtuple
import skimage.io
def gaussian_2d(
xy_array, amplitude, pos_x, pos_y, sigma_x, sigma_y, rotation, offset
):
"""
Expression for a 2D gaussian function with variance in both x and y
"""
x, y = xy_array
a = (np.cos(rotation) ** 2) / (2 * sigma_x ** 2) + (
np.sin(rotation) ** 2
) / (2 * sigma_y ** 2)
b = -(np.sin(2 * rotation)) / (4 * sigma_x ** 2) + (
np.sin(2 * rotation)
) / (4 * sigma_y ** 2)
c = (np.sin(rotation) ** 2) / (2 * sigma_x ** 2) + (
np.cos(rotation) ** 2
) / (2 * sigma_y ** 2)
g = amplitude * np.exp(
-(
a * ((x - pos_x) ** 2)
+ 2 * b * (x - pos_x) * (y - pos_y)
+ c * ((y - pos_y) ** 2)
)
)
g += offset
return g.ravel()
def fit_gaussian_spots(x_guess, y_guess, array):
Params = namedtuple(
"Parameters", "amp, x, y, sigma_x, sigma_y, rotation, offset"
)
eps = 1e-8
initial_guess = Params(
amp=1, x=x_guess, y=y_guess, sigma_x=1, sigma_y=1, rotation=0, offset=0
)
# Bounds makes it even harder to converge
min_bounds = Params(
amp=eps,
x=x_guess * 0.5,
y=y_guess * 0.5,
sigma_x=eps,
sigma_y=eps,
rotation=-np.inf,
offset=eps,
)
max_bounds = Params(
amp=np.max(array),
x=x_guess * 1.5,
y=y_guess * 1.5,
sigma_x=np.inf,
sigma_y=np.inf,
rotation=2 * np.pi,
offset=np.max(array),
)
try:
X, Y = create_grid(*array.shape)
popt, pcov = opt.curve_fit(
f=gaussian_2d,
xdata=(X, Y),
ydata=array.ravel(),
p0=initial_guess,
# bounds=(min_bounds, max_bounds),
)
popt = Params(*np.round(popt))
except ValueError:
print("fit didn't converge!")
popt, pcov = None, None
return popt, pcov
def create_grid(h, w):
"""
Creates a grid of x and y points to fit and evaluate over
"""
x = np.arange(0, w, 1)
y = np.arange(0, h, 1)
x, y = np.meshgrid(x, y)
return x, y
def evaluate_gaussian(x, y, popt):
"""
Evaluates gaussian in coordinate positions.
NOTE: this is not necessary for extracting intensity,
as the pure signal is fitted as the amplitude.
"""
z = gaussian_2d((x, y), *popt)
return z
if __name__ == "__main__":
# Create x and y indices
np.random.seed(4)
h, w = 200, 200
x, y = create_grid(h=h, w=w)
# create data
img = []
for _ in range(10):
randx = np.random.randint(10, w - 10)
randy = np.random.randint(10, h - 10)
amp = 100
d = gaussian_2d(
xy_array=(x, y),
amplitude=amp,
pos_x=randx,
pos_y=randy,
sigma_x=9,
sigma_y=3,
rotation=3,
offset=0,
)
# d = d + np.random.normal(0, 5, d.shape) # add noise
# d += 100 # add offset
img.append(d)
img = np.sum(img, axis=0)
img = img.reshape(h, w)
print("max intensity: {:.2f}".format(img.max()))
# Detect soem possible spots first
spots = skimage.feature.peak_local_max(img, num_peaks=20, min_distance=10)
fig, ax = plt.subplots(ncols=2)
h, w = img.shape
local_area = 20
fit = []
# skimage returns rows, columns (y,x) while matplotlib operates in (x,y)
for idx, (pre_y, pre_x) in enumerate(spots):
# Fit gaussian in local area
popt, pcov = fit_gaussian_spots(
x_guess=pre_x,
y_guess=pre_y,
# Avoid falling off the edge of the image
array=img[
max(pre_y - local_area, 0) : pre_y + local_area,
max(pre_x - local_area, 0) : pre_x + local_area,
],
)
if popt is None:
continue
print(popt)
ax[0].add_patch(
plt.Circle(
(pre_x, pre_y), 5, linewidth=0.5, fill=False, color="red"
)
)
ax[1].add_patch(
plt.Rectangle(
(pre_x - local_area, pre_y - local_area),
width=local_area * 2,
height=local_area * 2,
fill=False,
color="yellow",
)
)
fit.append(evaluate_gaussian(x, y, popt))
fit = np.sum(fit, axis=0)
ax[0].set_title("true")
ax[0].imshow(
img, origin="bottom", extent=(x.min(), x.max(), y.min(), y.max())
)
ax[1].set_title("predicted")
ax[1].imshow(
fit.reshape(img.shape),
origin="bottom",
extent=(x.min(), x.max(), y.min(), y.max()),
)
plt.show()
【问题讨论】:
-
当curve_fit() 不能对初始参数估计做出任何改进时,它将返回它们,因为在拟合算法达到其内部停止标准时,算法无法进一步改进。在这种情况下,添加边界将无济于事,因为 curve_fit 没有找到需要限制的参数改进。我的建议是首先使用一个高斯,一个你已经知道拟合参数的地方。使用接近 - 但不等于 - 这些已知值的初始参数估计应该有效。
标签: image-processing scipy curve-fitting gaussian