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) |
。