2015-09-18 27 views
9

Próbuję dopasować histogramy dwóch obrazów (w MATLAB można to zrobić przy użyciu imhistmatch). Czy istnieje odpowiednia funkcja dostępna ze standardowej biblioteki Pythona? Spojrzałem na OpenCV, scipy i numpy, ale nie widzę podobnych funkcji.Dopasowanie histogramu dwóch obrazów w Pythonie 2.x?

+0

czy próbowałeś PIL? – nln

+0

Tak. Szkoda, przetwarzanie obrazu w pythonie nie jest w dobrej formie. – nln

+1

PIL nie umiera - nowszy pakiet 'pillow' jest zaktualizowaną implementacją. – holdenweb

Odpowiedz

29

Wcześniej napisałem odpowiedź: here wyjaśniając, jak wykonać liniową interpolację liniową na histogramie obrazu, aby wymusić określone proporcje świateł/półcieni/cieni.

Te same podstawowe zasady leżą u podstaw histogram matching między dwoma obrazami. Zasadniczo obliczyć łączne histogramy dla obrazów źródłowych i szablonów, a następnie interpolować liniowo, aby znaleźć unikalne wartości pikseli w matrycy obrazu, które w największym stopniu odpowiadają quantiles unikalnej wartości pikseli w obrazie źródłowym:

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) 

Dla Przykład:

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

dla pary obrazów RGB można zastosować tę funkcję oddzielnie dla każdego kanału koloru.

+0

Trudno mi się upewnić, że nie mam obrazów wejściowych, ale to, co opisujesz, brzmi jak oczekiwany rezultat, gdy w obrazie źródłowym występuje znacznie mniejsza zmienność tonalna w porównaniu z celem. To, co prawdopodobnie dzieje się w "stałych" obszarach obrazu źródłowego, polega na tym, że niewielka liczba przypadkowych zmian jest wzmacniana w celu "rozciągnięcia" histogramu, aby dopasować go do szablonu. Mógłbym wymyślić kilka rzeczy, które mogłyby pomóc, ale generalnie im większa różnica między histogramem źródłowym a szablonowym, tym trudniej uzyskać "ładny" wynik. –

+0

Dla wszystkich, którzy czytali te stroje, odpowiedź Alego działała dobrze dla mnie. – ConfusinglyCuriousTheThird

+0

@ali_m: Próbowałem tego podejścia na obrazie i cieniowanej wersji tego obrazu, ale wydaje się, że dostaję dziwne wyniki. Jakąkolwiek wskazówkę, dlaczego tak się stało? – Megha