Python2.x中两张图片的直方图匹配?
Histogram matching of two images in Python 2.x?
我正在尝试匹配两个图像的直方图(在 MATLAB 中这可以使用 imhistmatch
完成)。标准 Python 库中是否有可用的等效函数?我看过 OpenCV、scipy 和 numpy,但没有看到任何类似的功能。
我之前写了一个答案 解释了如何对图像直方图进行分段线性插值以强制执行 highlights/midtones/shadows 的特定比率。
两张图片之间 histogram matching 的基本原理相同。本质上,您计算源图像和模板图像的累积直方图,然后进行线性插值以找到模板图像中与源图像中唯一像素值的分位数最匹配的唯一像素值:
import numpy as np
def hist_match(source, template):
"""
Adjust the pixel values of a grayscale image such that its histogram
matches that of a target image
Arguments:
-----------
source: np.ndarray
Image to transform; the histogram is computed over the flattened
array
template: np.ndarray
Template image; can have different dimensions to source
Returns:
-----------
matched: np.ndarray
The transformed output image
"""
oldshape = source.shape
source = source.ravel()
template = template.ravel()
# get the set of unique pixel values and their corresponding indices and
# counts
s_values, bin_idx, s_counts = np.unique(source, return_inverse=True,
return_counts=True)
t_values, t_counts = np.unique(template, return_counts=True)
# take the cumsum of the counts and normalize by the number of pixels to
# get the empirical cumulative distribution functions for the source and
# template images (maps pixel value --> quantile)
s_quantiles = np.cumsum(s_counts).astype(np.float64)
s_quantiles /= s_quantiles[-1]
t_quantiles = np.cumsum(t_counts).astype(np.float64)
t_quantiles /= t_quantiles[-1]
# interpolate linearly to find the pixel values in the template image
# that correspond most closely to the quantiles in the source image
interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)
return interp_t_values[bin_idx].reshape(oldshape)
例如:
from matplotlib import pyplot as plt
from scipy.misc import lena, ascent
source = lena()
template = ascent()
matched = hist_match(source, template)
def ecdf(x):
"""convenience function for computing the empirical CDF"""
vals, counts = np.unique(x, return_counts=True)
ecdf = np.cumsum(counts).astype(np.float64)
ecdf /= ecdf[-1]
return vals, ecdf
x1, y1 = ecdf(source.ravel())
x2, y2 = ecdf(template.ravel())
x3, y3 = ecdf(matched.ravel())
fig = plt.figure()
gs = plt.GridSpec(2, 3)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1], sharex=ax1, sharey=ax1)
ax3 = fig.add_subplot(gs[0, 2], sharex=ax1, sharey=ax1)
ax4 = fig.add_subplot(gs[1, :])
for aa in (ax1, ax2, ax3):
aa.set_axis_off()
ax1.imshow(source, cmap=plt.cm.gray)
ax1.set_title('Source')
ax2.imshow(template, cmap=plt.cm.gray)
ax2.set_title('template')
ax3.imshow(matched, cmap=plt.cm.gray)
ax3.set_title('Matched')
ax4.plot(x1, y1 * 100, '-r', lw=3, label='Source')
ax4.plot(x2, y2 * 100, '-k', lw=3, label='Template')
ax4.plot(x3, y3 * 100, '--r', lw=3, label='Matched')
ax4.set_xlim(x1[0], x1[-1])
ax4.set_xlabel('Pixel value')
ax4.set_ylabel('Cumulative %')
ax4.legend(loc=5)
对于一对 RGB 图像,您可以将此函数分别应用于每个通道。根据您要达到的效果,您可能需要先将图像转换为不同的颜色 space。例如,如果您想要匹配亮度而不是色调或饱和度,您可以转换为 HSV space 然后仅在 V 通道上进行匹配。
这是另一个基于 this 和 scikit-image exposure
的 cumulative_distribution
函数的实现,它使用与 ali_m 的实现类似的 np.interp
。假定输入和模板图像为灰度图像,像素值为 [0,255] 中的整数。
from skimage.exposure import cumulative_distribution
import matplotlib.pylab as plt
import numpy as np
def cdf(im):
'''
computes the CDF of an image im as 2D numpy ndarray
'''
c, b = cumulative_distribution(im)
# pad the beginning and ending pixels and their CDF values
c = np.insert(c, 0, [0]*b[0])
c = np.append(c, [1]*(255-b[-1]))
return c
def hist_matching(c, c_t, im):
'''
c: CDF of input image computed with the function cdf()
c_t: CDF of template image computed with the function cdf()
im: input image as 2D numpy ndarray
returns the modified pixel values
'''
pixels = np.arange(256)
# find closest pixel-matches corresponding to the CDF of the input image, given the value of the CDF H of
# the template image at the corresponding pixels, s.t. c_t = H(pixels) <=> pixels = H-1(c_t)
new_pixels = np.interp(c, c_t, pixels)
im = (np.reshape(new_pixels[im.ravel()], im.shape)).astype(np.uint8)
return im
输出如下图:
我想对上面写的两个解决方案添加一个小补充。如果有人计划将其作为全局函数(例如灰度图像),将最终匹配的数组转换为相应的格式 (numpy.uint8) 是个好主意。这可能有助于将来的图像转换而不会产生冲突。
def hist_norm(source, template):
olddtype = source.dtype
oldshape = source.shape
source = source.ravel()
template = template.ravel()
s_values, bin_idx, s_counts = np.unique(source, return_inverse=True,
return_counts=True)
t_values, t_counts = np.unique(template, return_counts=True)
s_quantiles = np.cumsum(s_counts).astype(np.float64)
s_quantiles /= s_quantiles[-1]
t_quantiles = np.cumsum(t_counts).astype(np.float64)
t_quantiles /= t_quantiles[-1]
interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)
interp_t_values = interp_t_values.astype(olddtype)
return interp_t_values[bin_idx].reshape(oldshape)
我正在尝试匹配两个图像的直方图(在 MATLAB 中这可以使用 imhistmatch
完成)。标准 Python 库中是否有可用的等效函数?我看过 OpenCV、scipy 和 numpy,但没有看到任何类似的功能。
我之前写了一个答案
两张图片之间 histogram matching 的基本原理相同。本质上,您计算源图像和模板图像的累积直方图,然后进行线性插值以找到模板图像中与源图像中唯一像素值的分位数最匹配的唯一像素值:
import numpy as np
def hist_match(source, template):
"""
Adjust the pixel values of a grayscale image such that its histogram
matches that of a target image
Arguments:
-----------
source: np.ndarray
Image to transform; the histogram is computed over the flattened
array
template: np.ndarray
Template image; can have different dimensions to source
Returns:
-----------
matched: np.ndarray
The transformed output image
"""
oldshape = source.shape
source = source.ravel()
template = template.ravel()
# get the set of unique pixel values and their corresponding indices and
# counts
s_values, bin_idx, s_counts = np.unique(source, return_inverse=True,
return_counts=True)
t_values, t_counts = np.unique(template, return_counts=True)
# take the cumsum of the counts and normalize by the number of pixels to
# get the empirical cumulative distribution functions for the source and
# template images (maps pixel value --> quantile)
s_quantiles = np.cumsum(s_counts).astype(np.float64)
s_quantiles /= s_quantiles[-1]
t_quantiles = np.cumsum(t_counts).astype(np.float64)
t_quantiles /= t_quantiles[-1]
# interpolate linearly to find the pixel values in the template image
# that correspond most closely to the quantiles in the source image
interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)
return interp_t_values[bin_idx].reshape(oldshape)
例如:
from matplotlib import pyplot as plt
from scipy.misc import lena, ascent
source = lena()
template = ascent()
matched = hist_match(source, template)
def ecdf(x):
"""convenience function for computing the empirical CDF"""
vals, counts = np.unique(x, return_counts=True)
ecdf = np.cumsum(counts).astype(np.float64)
ecdf /= ecdf[-1]
return vals, ecdf
x1, y1 = ecdf(source.ravel())
x2, y2 = ecdf(template.ravel())
x3, y3 = ecdf(matched.ravel())
fig = plt.figure()
gs = plt.GridSpec(2, 3)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1], sharex=ax1, sharey=ax1)
ax3 = fig.add_subplot(gs[0, 2], sharex=ax1, sharey=ax1)
ax4 = fig.add_subplot(gs[1, :])
for aa in (ax1, ax2, ax3):
aa.set_axis_off()
ax1.imshow(source, cmap=plt.cm.gray)
ax1.set_title('Source')
ax2.imshow(template, cmap=plt.cm.gray)
ax2.set_title('template')
ax3.imshow(matched, cmap=plt.cm.gray)
ax3.set_title('Matched')
ax4.plot(x1, y1 * 100, '-r', lw=3, label='Source')
ax4.plot(x2, y2 * 100, '-k', lw=3, label='Template')
ax4.plot(x3, y3 * 100, '--r', lw=3, label='Matched')
ax4.set_xlim(x1[0], x1[-1])
ax4.set_xlabel('Pixel value')
ax4.set_ylabel('Cumulative %')
ax4.legend(loc=5)
对于一对 RGB 图像,您可以将此函数分别应用于每个通道。根据您要达到的效果,您可能需要先将图像转换为不同的颜色 space。例如,如果您想要匹配亮度而不是色调或饱和度,您可以转换为 HSV space 然后仅在 V 通道上进行匹配。
这是另一个基于 this 和 scikit-image exposure
的 cumulative_distribution
函数的实现,它使用与 ali_m 的实现类似的 np.interp
。假定输入和模板图像为灰度图像,像素值为 [0,255] 中的整数。
from skimage.exposure import cumulative_distribution
import matplotlib.pylab as plt
import numpy as np
def cdf(im):
'''
computes the CDF of an image im as 2D numpy ndarray
'''
c, b = cumulative_distribution(im)
# pad the beginning and ending pixels and their CDF values
c = np.insert(c, 0, [0]*b[0])
c = np.append(c, [1]*(255-b[-1]))
return c
def hist_matching(c, c_t, im):
'''
c: CDF of input image computed with the function cdf()
c_t: CDF of template image computed with the function cdf()
im: input image as 2D numpy ndarray
returns the modified pixel values
'''
pixels = np.arange(256)
# find closest pixel-matches corresponding to the CDF of the input image, given the value of the CDF H of
# the template image at the corresponding pixels, s.t. c_t = H(pixels) <=> pixels = H-1(c_t)
new_pixels = np.interp(c, c_t, pixels)
im = (np.reshape(new_pixels[im.ravel()], im.shape)).astype(np.uint8)
return im
输出如下图:
我想对上面写的两个解决方案添加一个小补充。如果有人计划将其作为全局函数(例如灰度图像),将最终匹配的数组转换为相应的格式 (numpy.uint8) 是个好主意。这可能有助于将来的图像转换而不会产生冲突。
def hist_norm(source, template):
olddtype = source.dtype
oldshape = source.shape
source = source.ravel()
template = template.ravel()
s_values, bin_idx, s_counts = np.unique(source, return_inverse=True,
return_counts=True)
t_values, t_counts = np.unique(template, return_counts=True)
s_quantiles = np.cumsum(s_counts).astype(np.float64)
s_quantiles /= s_quantiles[-1]
t_quantiles = np.cumsum(t_counts).astype(np.float64)
t_quantiles /= t_quantiles[-1]
interp_t_values = np.interp(s_quantiles, t_quantiles, t_values)
interp_t_values = interp_t_values.astype(olddtype)
return interp_t_values[bin_idx].reshape(oldshape)