关于python:如何转换灰度图像的直方图以强制特定比例的高光/中间调/阴影?

How can I transform the histograms of grayscale images to enforce a particular ratio of highlights/midtones/shadows?

我收集了大量700万像素的灰度图像,我希望批量处理它们以调整对比度和亮度,使每个图像包含大约:

  • 50%高光(发光值为200-255的像素)

  • 30%中音(发光值为55-199的像素)

  • 20%阴影(发光值为0-54的像素)

  • 小精灵

    它需要合理的效率,因为我只有1.8GHz和许多图像。我知道,有了numpy,你可以让pil/枕头处理图像比没有它更有效,但我从来没有用过它。


    不久前,我写了一些麻木的代码来解决这个确切的问题。

    有许多可能的方法来转换输入图像的柱状图,以便正确的像素值数量落在每个bin中。也许最简单的方法是找出对应于每个百分位的当前像素值与所需值之间的差异,然后沿纸槽边缘线性内插,以找出每个像素值的加/减量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import numpy as np

    def hist_norm(x, bin_edges, quantiles, inplace=False):
       """
        Linearly transforms the histogram of an image such that the pixel values
        specified in `bin_edges` are mapped to the corresponding set of `quantiles`

        Arguments:
        -----------
            x: np.ndarray
                Input image; the histogram is computed over the flattened array
            bin_edges: array-like
                Pixel values; must be monotonically increasing
            quantiles: array-like
                Corresponding quantiles between 0 and 1. Must have same length as
                bin_edges, and must be monotonically increasing
            inplace: bool
                If True, x is modified in place (faster/more memory-efficient)

        Returns:
        -----------
            x_normed: np.ndarray
                The normalized array
       """


        bin_edges = np.atleast_1d(bin_edges)
        quantiles = np.atleast_1d(quantiles)

        if bin_edges.shape[0] != quantiles.shape[0]:
            raise ValueError('# bin edges does not match number of quantiles')

        if not inplace:
            x = x.copy()
        oldshape = x.shape
        pix = x.ravel()

        # get the set of unique pixel values, the corresponding indices for each
        # unique value, and the counts for each unique value
        pix_vals, bin_idx, counts = np.unique(pix, return_inverse=True,
                                              return_counts=True)

        # take the cumsum of the counts and normalize by the number of pixels to
        # get the empirical cumulative distribution function (which maps pixel
        # values to quantiles)
        ecdf = np.cumsum(counts).astype(np.float64)
        ecdf /= ecdf[-1]

        # get the current pixel value corresponding to each quantile
        curr_edges = pix_vals[ecdf.searchsorted(quantiles)]

        # how much do we need to add/subtract to map the current values to the
        # desired values for each quantile?
        diff = bin_edges - curr_edges

        # interpolate linearly across the bin edges to get the delta for each pixel
        # value within each bin
        pix_delta = np.interp(pix_vals, curr_edges, diff)

        # add these deltas to the corresponding pixel values
        pix += pix_delta[bin_idx]

        return pix.reshape(oldshape)

    例如:

    1
    2
    3
    4
    5
    6
    from scipy.misc import lena

    bin_edges = 0, 55, 200, 255
    quantiles = 0, 0.2, 0.5, 1.0
    img = lena()
    normed = hist_norm(img, bin_edges, quantiles)

    绘图:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    from matplotlib import pyplot as plt

    def ecdf(x):
        vals, counts = np.unique(x, return_counts=True)
        ecdf = np.cumsum(counts).astype(np.float64)
        ecdf /= ecdf[-1]
        return vals, ecdf

    x1, y1 = ecdf(img.ravel())
    x2, y2 = ecdf(normed.ravel())

    fig = plt.figure()
    gs = plt.GridSpec(2, 2)
    ax1 = fig.add_subplot(gs[0, 0])
    ax2 = fig.add_subplot(gs[0, 1], sharex=ax1, sharey=ax1)
    ax3 = fig.add_subplot(gs[1, :])
    for aa in (ax1, ax2):
        aa.set_axis_off()

    ax1.imshow(img, cmap=plt.cm.gray)
    ax1.set_title('Original')
    ax2.imshow(normed, cmap=plt.cm.gray)
    ax2.set_title('Normalised')

    ax3.plot(x1, y1 * 100, lw=2, label='Original')
    ax3.plot(x2, y2 * 100, lw=2, label='Normalised')
    for xx in bin_edges:
        ax3.axvline(xx, ls='--', c='k')
    for yy in quantiles:
        ax3.axhline(yy * 100., ls='--', c='k')
    ax3.set_xlim(bin_edges[0], bin_edges[-1])
    ax3.set_xlabel('Pixel value')
    ax3.set_ylabel('Cumulative %')
    ax3.legend(loc=2)

    enter image description here