2015-09-18 37 views
9

Sto provando ad abbinare gli istogrammi di due immagini (in MATLAB ciò potrebbe essere fatto usando imhistmatch). Esiste una funzione equivalente disponibile da una libreria Python standard? Ho esaminato OpenCV, scipy e numpy ma non vedo alcuna funzionalità simile.corrispondenza dell'istogramma di due immagini in Python 2.x?

+0

hai provato PIL? – nln

+0

Sì. È un peccato, l'elaborazione delle immagini in Python non è davvero in buona forma. – nln

+1

PIL non sta morendo - il nuovo pacchetto 'pillow' è un'implementazione aggiornata – holdenweb

risposta

29

Ho scritto in precedenza una risposta here che spiega come eseguire interpolazione lineare a tratti su un istogramma di immagine per imporre particolari rapporti di luci/mezzitoni/ombre.

Gli stessi principi di base sono alla base dello histogram matching tra due immagini. In sostanza si calcola gli istogrammi cumulativi per l'origine e modello di immagini, quindi interpolare linearmente per trovare i valori dei pixel unici l'immagine modello in che corrispondono più da vicino i quantili dei valori dei pixel unici nell'immagine sorgente:

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) 

Per esempio:

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) 

enter image description here

per un paio di immagini RGB si potrebbe applicare questa funzione separatamente per ciascun canale di colore.

+0

È difficile per me essere sicuro di non avere le immagini di input, ma quello che stai descrivendo sembra un risultato atteso quando c'è meno variazione tonale nell'immagine sorgente rispetto all'obiettivo. Quello che probabilmente sta accadendo all'interno delle aree "solide" nella tua immagine sorgente è che viene amplificata una piccola quantità di variazione casuale per "allungare" l'istogramma in modo che corrisponda a quello del modello. Potrei pensare a un paio di cose che potrebbero essere d'aiuto, ma in generale maggiore è la differenza tra l'istogramma e gli istogrammi del modello, più difficile è ottenere un risultato "bello". –

+0

Per gli altri che leggono questi messaggi, la risposta di Ali ha funzionato bene per me. – ConfusinglyCuriousTheThird

+0

@ali_m: Ho provato questo approccio su un'immagine e una versione ombreggiata di quell'immagine, ma sembra ottenere risultati strani. Qualche idea sul perché questo potrebbe accadere? – Megha