第二集:图像增强,第三部分:直方图操作

欢迎回到我们图像处理系列的第二集第三部分!在系列的前几部分中,我们讨论了傅里叶变换和白平衡技术,现在我们将探索另一种令人兴奋的技术,称为直方图操作。
如果你和我一样,你可能会想知道如何操作直方图。我是说,直方图不就是显示图像中像素值分布的图表吗?事实证明,通过操作直方图,我们可以调整图像的对比度和亮度,从而大大改善其视觉效果。
所以,让我们深入研究直方图操作的世界,发现如何使用各种直方图操作技术增强图像的对比度和亮度。这些技术可用于改善低光图像中物体的可见性,改善图像的细节,并纠正过曝或曝光不足的图像。
让我们首先导入相关的Python库:
第一步:导入库,然后加载和显示图像
import numpy as npimport matplotlib.pyplot as pltfrom skimage.color import rgb2grayfrom skimage.exposure import histogram, cumulative_distributionfrom skimage import filtersfrom skimage.color import rgb2hsv, rgb2gray, rgb2yuvfrom skimage import color, exposure, transformfrom skimage.exposure import histogram, cumulative_distribution
# 加载图像并去除透明度通道(不透明度)dark_image = imread('plasma_ball.png')[:,:,:3]# 可视化图像plt.figure(figsize=(10, 10))plt.title('原始图像:等离子球')plt.imshow(dark_image)plt.show()

第二步:检查通道统计信息并绘制图像的直方图
def calc_color_overcast(image): # 计算每个通道的颜色过度 red_channel = image[:, :, 0] green_channel = image[:, :, 1] blue_channel = image[:, :, 2] # 创建一个存储结果的数据框 channel_stats = pd.DataFrame(columns=['Mean', 'Std', 'Min', 'Median', 'P_80', 'P_90', 'P_99', 'Max']) # 计算并存储每个颜色通道的统计信息 for channel, name in zip([red_channel, green_channel, blue_channel], ['红色', '绿色', '蓝色']): mean = np.mean(channel) std = np.std(channel) minimum = np.min(channel) median = np.median(channel) p_80 = np.percentile(channel, 80) p_90 = np.percentile(channel, 90) p_99 = np.percentile(channel, 99) maximum = np.max(channel) channel_stats.loc[name] = [mean, std, minimum, median, p_80, p_90, p_99, maximum] return channel_stats
# 这是前一集第二部分中相同的函数(看看吧!)calc_color_overcast(dark_image)

# 绘制直方图dark_image_intensity = img_as_ubyte(rgb2gray(dark_image))freq, bins = histogram(dark_image_intensity)plt.step(bins, freq*1.0/freq.sum())plt.xlabel('强度值')plt.ylabel('像素的比例');

看起来没有明显的阴天,但像素的平均强度似乎极低,确认了图像的暗和过曝的可视化。直方图显示大多数像素具有低强度值,这是有道理的,因为低像素强度值意味着图像中大多数像素非常暗或黑。我们可以对图像应用各种直方图处理技术来改善其对比度。
步骤3:探索各种直方图处理技术
在深入研究直方图处理的几种技术之前,让我们了解一下常用的直方图处理技术,称为直方图均衡化。
直方图均衡化是一种将图像中的像素强度重新分布以使直方图更均匀的技术。非均匀的像素强度分布可能导致对比度和细节较低的图像,使得难以区分图像中的对象或特征。通过使像素强度分布更均匀,可以改善图像的对比度,使细节和特征更容易被感知。
<p实现像素强度分布均匀的一种方法是使图像的累积分布函数(CDF)是线性的。这是因为线性的CDF意味着图像中每个像素强度值出现的可能性是相等的。另一方面,非线性的CDF意味着某些像素强度值比其他像素强度值更频繁地出现,导致非均匀的像素强度分布。通过使CDF线性,可以使像素强度分布更均匀,从而改善图像的对比度。
def plot_cdf(image): """ 绘制图像的累积分布函数。 参数: image(ndarray):输入图像。 """ # 如果需要,将图像转换为灰度图像 if len(image.shape) == 3: image = rgb2gray(image[:,:,:3]) # 计算累积分布函数 intensity = np.round(image * 255).astype(np.uint8) freq, bins = cumulative_distribution(intensity) # 绘制实际和目标CDF target_bins = np.arange(256) target_freq = np.linspace(0, 1, len(target_bins)) plt.step(bins, freq, c='b', label='实际CDF') plt.plot(target_bins, target_freq, c='r', label='目标CDF') # 绘制示例查找 example_intensity = 50 example_target = np.interp(freq[example_intensity], target_freq, target_bins) plt.plot([example_intensity, example_intensity, target_bins[-11], target_bins[-11]], [0, freq[example_intensity], freq[example_intensity], 0], 'k--', label=f'示例查找({example_intensity} -> {example_target:.0f})') # 自定义绘图 plt.legend() plt.xlim(0, 255) plt.ylim(0, 1) plt.xlabel('强度值') plt.ylabel('像素累积分数') plt.title('累积分布函数') return freq, bins, target_freq, target_bins
dark_image = imread('plasma_ball.png')freq, bins, target_freq, target_bins = plot_cdf(dark_image);

<p该代码计算了暗图像的累积分布函数(CDF),然后根据线性分布定义了一个目标CDF。然后以蓝色绘制了暗图像的实际CDF,以红色绘制了目标CDF(线性)。还绘制了一个强度值的示例查找,显示示例中实际CDF为50,我们希望将其目标化为230。
# 将强度值从实际值 50 转换为目标值 230 的示例转换dark_image_230 = dark_image_intensity.copy()dark_image_230[dark_image_230==50] = 230plt.figure(figsize=(10,10))plt.imshow(dark_image_230,cmap='gray');

获得目标CDF之后,下一步是计算用于替换原始像素强度的强度值。这是使用插值来创建查找表来完成的。
# 在替换所有实际值为目标值后显示结果new_vals = np.interp(freq,target_freq,target_bins)dark_image_eq = img_as_ubyte(new_vals[img_as_ubyte(rgb2gray(dark_image[:,:,:3]))].astype(int))plt.figure(figsize=(10,10))plt.imshow(dark_image_eq, cmap='gray');

np.interp()函数通过在实际和目标CDF之间进行插值来计算用于替换原始像素强度的强度值。然后,使用NumPy索引来替换原始像素强度。最后,使用imshow()以cmap=gray的方式显示结果均衡化的图像。
现在,我已经展示了最基本的直方图操作类型,让我们尝试不同类型的CDF和技术,并亲自看看哪种技术适用于给定的图像:
def custom_rgb_adjustment(image, target_freq): target_bins = np.arange(256) freq_bins = [cumulative_distribution(image[:, :, i]) for i in range(3)] adjusted_channels = [] # 使用最小频率填充频率 padded_freqs = [] for i in range(len(freq_bins)): if len(freq_bins[i][0]) < 256: frequencies = list(freq_bins[i][0]) min_pad = [min(frequencies)] * (256 - len(frequencies)) frequencies = min_pad + frequencies else: frequencies = freq_bins[i][0] padded_freqs.append(np.array(frequencies)) for n in range(3): interpolation = np.interp(padded_freqs[n], target_freq, target_bins) adjusted_channel = img_as_ubyte(interpolation[image[:, :, n]].astype(int)) adjusted_channels.append([adjusted_channel]) adjusted_image = np.dstack((adjusted_channels[0][0], adjusted_channels[1][0], adjusted_channels[2][0])) return adjusted_image
# 线性目标_bins = np.arange(256)# Sigmoiddef sigmoid_cdf(x, a=1): return (1 + np.tanh(a * x)) / 2# 指数def exponential_cdf(x, alpha=1): return 1 - np.exp(-alpha * x)# 幂律def power_law_cdf(x, alpha=1): return x ** alpha# 其他技术:def adaptive_histogram_equalization(image, clip_limit=0.03, tile_size=(8, 8)): clahe = exposure.equalize_adapthist( image, clip_limit=clip_limit, nbins=256, kernel_size=(tile_size[0], tile_size[1])) return clahedef gamma_correction(image, gamma=1.0): corrected_image = exposure.adjust_gamma(image, gamma) return corrected_imagedef contrast_stretching_percentile(image, lower_percentile=5, upper_percentile=95): in_range = tuple(np.percentile(image, (lower_percentile, upper_percentile))) stretched_image = exposure.rescale_intensity(image, in_range) return stretched_imagedef unsharp_masking(image, radius=5, amount=1.0): blurred_image = filters.gaussian(image, sigma=radius, multichannel=True) sharpened_image = (image + (image - blurred_image) * amount).clip(0, 1) return sharpened_imagedef equalize_hist_rgb(image): equalized_image = exposure.equalize_hist(image) return equalized_imagedef equalize_hist_hsv(image): hsv_image = color.rgb2hsv(image[:,:,:3]) hsv_image[:, :, 2] = exposure.equalize_hist(hsv_image[:, :, 2]) hsv_adjusted = color.hsv2rgb(hsv_image) return hsv_adjusteddef equalize_hist_yuv(image): yuv_image = color.rgb2yuv(image[:,:,:3]) yuv_image[:, :, 0] = exposure.equalize_hist(yuv_image[:, :, 0]) yuv_adjusted = color.yuv2rgb(yuv_image) return yuv_adjusted
# 将每种技术保存到变量中linear = custom_rgb_adjustment(dark_image, np.linspace(0, 1, len(target_bins)))sigmoid = custom_rgb_adjustment(dark_image, sigmoid_cdf((target_bins - 128) / 64, a=1))exponential = custom_rgb_adjustment(dark_image, exponential_cdf(target_bins / 255, alpha=3))power = custom_rgb_adjustment(dark_image, power_law_cdf(target_bins / 255, alpha=2))clahe_image = adaptive_histogram_equalization( dark_image, clip_limit=0.09, tile_size=(50, 50))gamma_corrected_image = gamma_correction(dark_image, gamma=0.4)sharpened_image = unsharp_masking(dark_image, radius=10, amount=-0.98)cs_image = contrast_stretching_percentile(dark_image, 0, 70)equalized_rgb = equalize_hist_rgb(dark_image)equalized_hsv = equalize_hist_hsv(dark_image)equalized_yuv = equalize_hist_yuv(dark_image)
# 绘图fig, axes = plt.subplots(3, 4, figsize=(20, 20))# 原始图像axes[0, 0].imshow(dark_image)axes[0, 0].set_title('原始图像', fontsize=20)# 直方图均衡化:RGB调整axes[0, 1].imshow(equalized_rgb)axes[0, 1].set_title('RGB调整', fontsize=20)# HSV调整axes[0, 2].imshow(equalized_hsv)axes[0, 2].set_title('HSV调整', fontsize=20)# YUV调整axes[0, 3].imshow(equalized_yuv)axes[0, 3].set_title('YUV调整', fontsize=20)# 线性CDFaxes[1, 0].imshow(linear)axes[1, 0].set_title('线性', fontsize=20)# Sigmoid CDFaxes[1, 1].imshow(sigmoid)axes[1, 1].set_title('Sigmoid', fontsize=20)# 指数CDFaxes[1, 2].imshow(exponential)axes[1, 2].set_title('指数', fontsize=20)# 幂律CDFaxes[1, 3].imshow(power)axes[1, 3].set_title('幂律', fontsize=20)# 对比度拉伸axes[2, 0].imshow(cs_image)axes[2, 0].set_title('对比度拉伸', fontsize=20)# 自适应直方图均衡化(CLAHE)axes[2, 1].imshow(clahe_image)axes[2, 1].set_title('自适应直方图均衡化', fontsize=20)# 伽马校正axes[2, 2].imshow(gamma_corrected_image)axes[2, 2].set_title('伽马校正', fontsize=20)# 锐化掩蔽axes[2, 3].imshow(sharpened_image)axes[2, 3].set_title('锐化掩蔽', fontsize=20)# 移除坐标轴刻度和标签for ax in axes.flatten(): ax.set_xticks([]) ax.set_yticks([])plt.tight_layout()plt.show()

校正RGB图像有很多方法/技术,但大多数方法都需要手动调整参数。输出 #7显示了使用各种直方图操作技术生成的校正图像的绘图。
HSV调整、指数、对比度拉伸和锐化掩蔽都似乎令人满意。请注意,结果会根据使用的原始图像而有所不同。根据具体的图像,您可以尝试不同的参数值来实现所需的图像质量。
直方图操作技术可以大大增强图像的对比度和整体外观。然而,重要的是要小心使用它们,因为如果过度使用,它们也可能引入伪影并导致不自然的外观,正如输出 #7中使用的一些技术所示(例如,带有颗粒背景和过度强调边缘的自适应直方图均衡化)。
与上面使用的暗图像相比,我还尝试以相同的参数值在一个亮图像上执行我的代码。让我们观察一下这里发生了什么:

您可能已经注意到,对于暗图像效果良好的大多数技术在亮图像上效果并不好。例如,HSV调整、指数和锐化掩蔽等技术表现更差,会给图像添加伪影或噪点。这可能是因为这些技术可能增强或放大图像中现有的亮度,导致过曝光或添加伪影或噪点。
然而,值得注意的是,对比度拉伸,尽管如预期地使某些部分比原始图像更亮,因为对比度拉伸字面上扩展图像中的像素值范围以增加整体对比度,提供了一种更灵活的解决方案,可用于亮图像和暗图像。
结论
在这一节中,我们深入探讨了图像处理的世界,探索了各种图像增强技术。我们涵盖了傅里叶变换(第1部分)、白平衡算法(第2部分)和直方图操作技术(第3部分,本部分),以及使用skimage库的相关Python代码。
最终,选择最合适的图像增强技术取决于具体的图像和您的输出质量要求。作为最佳实践,尝试多种图像增强技术,并调整不同的参数值以实现所需的图像质量。希望这次探索能帮助您更好地理解各种图像增强技术的影响。
随着我们继续这个令人兴奋的图像处理之旅,还有很多东西可以学习和探索。敬请期待我Python图像处理系列的下一个部分,其中将讨论更高级的技术和应用!