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?
Odpowiedz
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)
dla pary obrazów RGB można zastosować tę funkcję oddzielnie dla każdego kanału koloru.
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. –
Dla wszystkich, którzy czytali te stroje, odpowiedź Alego działała dobrze dla mnie. – ConfusinglyCuriousTheThird
@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
czy próbowałeś PIL? – nln
Tak. Szkoda, przetwarzanie obrazu w pythonie nie jest w dobrej formie. – nln
PIL nie umiera - nowszy pakiet 'pillow' jest zaktualizowaną implementacją. – holdenweb