【问题标题】:Histogram matching of two images in Python 2.x?Python 2.x 中两个图像的直方图匹配?
【发布时间】:2015-12-15 19:40:06
【问题描述】:

我正在尝试匹配两个图像的直方图(在 MATLAB 中,这可以使用 imhistmatch 完成)。标准 Python 库是否提供等效函数?我看过 OpenCV、scipy 和 numpy,但没有看到任何类似的功能。

【问题讨论】:

  • 是的。很遗憾,python 中的图像处理并不是很好。
  • PIL 不会死 - 较新的 pillow 包是更新的实现
  • Pillow 也确实没有做到这一点...转到他们的文档(托管在 readthedocs.org 上),他们有 zero 个图像来展示任何东西的作用...无论如何,看起来相当有限。
  • “标准库”是修复要求吗?否则我会说 OpenCV 提供你所需要的一切:pyimagesearch.com/2014/07/14/…
  • 您的代码示例执行 histogram equalisation 而不是直方图匹配 - 本质上它使单个图像中像素值的直方图变平,这有时有助于增强对比度。

标签: python numpy image-processing histogram


【解决方案1】:

我之前写了一个答案here,解释了如何对图像直方图进行分段线性插值,以强制执行特定的高光/中间调/阴影比率。

两个图像之间的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 图像,您可以将此函数分别应用于每个通道。根据您要达到的效果,您可能希望首先将图像转换为不同的色彩空间。例如,你可以转换成HSV space,然后如果你想匹配亮度而不是色调或饱和度,则只在V通道上进行匹配。

【讨论】:

  • 如果没有输入图像,我很难确定,但是当与目标图像相比,源图像的色调变化要小得多时,您所描述的听起来像是预期的结果。在源图像的“实心”区域内可能发生的情况是,为了“拉伸”直方图以匹配模板的直方图,正在放大少量随机变化。我可以想到一些可能会有所帮助的事情,但总的来说,源直方图和模板直方图之间的差异越大,就越难获得“漂亮”的结果。
  • 对于其他阅读这些评论的人来说,阿里的回答对我来说效果很好。
  • @ali_m:我在一个图像和该图像的阴影版本上尝试了这种方法,但它似乎得到了奇怪的结果。任何线索为什么会发生这种情况?
  • @Megha 没有看到图片很难说,但我怀疑这与我在上面评论中提到的原因相同。如果源图像的直方图看起来与模板的直方图非常不同,那么拉伸和压缩它以适应模板可能会产生奇怪的结果。
  • ali_m : 我有相同的图像,唯一的区别是源图像是模板的阴影版本(来自树的阴影)
【解决方案2】:

我想对上面写的两个解决方案添加一个小的补充。如果有人打算将其作为全局函数(例如灰度图像),最好将最终匹配的数组转换为其相应的格式(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)

【讨论】:

  • 那么,这似乎也适用于 rgb 图像,而无需之前分离通道?结果看起来至少令人信服……
【解决方案3】:

这是另一个基于thisscikit-image exposurecumulative_distribution 函数的实现,它使用np.interp,类似于ali_m 的实现。假设输入和模板图像是灰度的,像素值是 [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

输出如下图:

【讨论】:

    猜你喜欢
    • 2012-10-15
    • 2022-10-06
    • 2015-01-02
    • 2011-09-15
    • 1970-01-01
    • 2023-04-05
    • 2011-04-28
    • 2011-02-24
    相关资源
    最近更新 更多