Last active
June 19, 2024 16:25
-
-
Save tayden/dcc83424ce55bfb970f60db3d4ddad18 to your computer and use it in GitHub Desktop.
Scikit-Image histogram matching doesn't work on numpy masked arrays. This gist is a workaround for this issue. It shows how to do histogram matching when you have an image and need to exclude certain values from being considered as part of the image histogram.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from skimage import io, exposure | |
import numpy as np | |
# Histogram matching with masked image | |
def match_histograms(image, reference, image_mask, fill_value=0): | |
masked_image = np.ma.array(image, mask=image_mask) | |
matched = np.ma.array(np.empty(image.shape, dtype=image.dtype), | |
mask=image_mask, fill_value=fill_value) | |
for channel in range(masked_image.shape[-1]): | |
matched_channel = exposure.match_histograms(masked_image[...,channel].compressed(), | |
reference[...,channel].ravel()) | |
# Re-insert masked background | |
mask_ch = image_mask[...,channel] | |
matched[..., channel][~mask_ch] = matched_channel.ravel() | |
return matched.filled() | |
# Load images | |
reference_img = io.imread("/path/to/reference_image.jpg") / 255 | |
image = io.imread("/path/to/image.jpg") / 255 | |
# Get a mask that matches image.shape, with mask being where pixel val across channels is 0 | |
mask = np.repeat(np.expand_dims(np.all(img == 0, axis=2), axis=2), repeats=3, axis=2) | |
# Do the masked histogram matching | |
output = match_histograms(image, reference_img, mask, fill_value=0) |
This is very cool and just what I was looking for! Just one thing I noticed. The fill value passed to the function is not relayed to the masked array, but set to 0.
Ah good catch! I'll update it
Thanks so much for sharing this!
I slightly modified the code, in case both images contain black background elements / masks:
# Histogram matching with masked image
def match_histograms(image, reference, image_mask, reference_mask, fill_value=0):
# adapted from https://gist.github.com/tayden/dcc83424ce55bfb970f60db3d4ddad18
# to include masks for the input and reference image
masked_source_image = np.ma.array(image, mask=image_mask)
masked_reference_image = np.ma.array(reference, mask=reference_mask)
matched = np.ma.array(np.empty(image.shape, dtype=image.dtype),
mask=image_mask, fill_value=fill_value)
for channel in range(masked_source_image.shape[-1]):
matched_channel = exposure.match_histograms(masked_source_image[...,channel].compressed(),
masked_reference_image[...,channel].compressed())
# Re-insert masked background
mask_ch = image_mask[...,channel]
matched[..., channel][~mask_ch] = matched_channel.ravel()
return matched.filled()
Cool, why this is not implemented in the main exposure.match_histograms
?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Tayden this is awesome! I actually need to go from a masked reference image to a masked target image. I'm going to use your gist here as a starting point to getting something working.