用OpenCV图像处理技巧之巧用直方图

1. 引言

欢迎回到我的Python图像处理系列!在这一节中,我们将更深入地研究图像分析领域中图像直方图的应用,事实上通过对直方图进行相应操作,我们可以来调整图像的对比度和亮度,这可以极大地改善图像的视觉效果。

闲话少说,我们直接开始吧!

2. 前置条件

一般来说,通过合理利用一些直方图的技巧,可以用于提高低光照拍摄图像中物体的可见性,改善图像的细节,以及校正曝光过度或曝光不足的图像。

在介绍了枯燥的理论知识后,我们来看个具体的例子。首先还是导入我们所需要的基础库,代码如下:

import numpy as np
import matplotlib.pyplot as plt
from skimage.color import rgb2gray
from skimage.exposure import histogram, cumulative_distribution
from skimage import filters
from skimage.color import rgb2hsv, rgb2gray, rgb2yuv
from skimage import color, exposure, transform
from skimage.exposure import histogram, cumulative_distribution

接着使用以下代码加载我们的测试用例,如下:

# Load the image & remove the alpha or opacity channel (transparency) 
dark_image = imread('plasma_ball.png')[:,:,:3]

# Visualize the image
plt.figure(figsize=(10, 10))
plt.title('Original Image: Plasma Ball')
plt.imshow(dark_image)
plt.show()

显示结果如下:
在这里插入图片描述
可以看到,上述图像是在光照不足下进行拍摄的,接着我们就来通过控制直方图,来改善我们的视觉效果。

3. 统计数据分析

接着我们使用以下代码,进行相应的统计数据分析,主要是计算三个颜色通道下不同百分位下的亮度均值和方差,代码如下:

def calc_color_overcast(image):
    # Calculate color overcast for each channel
    red_channel = image[:, :, 0]
    green_channel = image[:, :, 1]
    blue_channel = image[:, :, 2]

    # Create a dataframe to store the results
    channel_stats = pd.DataFrame(columns=['Mean', 'Std', 'Min', 'Median', 
                                          'P_80', 'P_90', 'P_99', 'Max'])

    # Compute and store the statistics for each color channel
    for channel, name in zip([red_channel, green_channel, blue_channel], 
                             ['Red', 'Green', 'Blue']):
        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

调用上述函数后,得到结果如下:
在这里插入图片描述

进而我们可以使用以下代码,来生成上述图像的直方图分布:

# Histogram plot
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('intensity value')
plt.ylabel('fraction of pixels');

得到直方图可视化效果如下:
在这里插入图片描述
通过直方图可以看到,像素的平均强度似乎极低,这证实了图像的黑暗和曝光不足。直方图显示大多数像素具有低强度值,这是有道理的,因为低像素强度值意味着图像中的大多数像素非常暗或呈现黑色。

4. 直方图均衡化原理

我们可以将各种直方图处理技术应用于图像以提高其对比度。首先我们来介绍直方图均衡化,直方图均衡化是一种重新调整图像中像素强度分布以使直方图更加均匀的技术。不均匀的像素强度分布可能导致图像的对比度和细节较低,从而难以区分图像中的对象或特征。通过使像素强度分布更加均匀,提高了图像的对比度,使其更容易感知细节和特征。

实现直方图均衡化最为常用的一种方法是使图像的累积分布函数(CDF)线性化。这是因为线性CDF意味着每个像素强度值在图像中出现的可能性相等。另一方面,非线性CDF意味着某些像素强度值比其他像素强度值更频繁地出现,导致像素强度分布不均匀。通过使CDF线性化,可以使像素强度分布更加均匀,提高图像对比度。

通过以下代码实现CDF线性化:

def plot_cdf(image):
    """
    Plot the cumulative distribution function of an image.
    
    Parameters:
    image (ndarray): Input image.
    """
    
    # Convert the image to grayscale if needed
    if len(image.shape) == 3:
        image = rgb2gray(image[:,:,:3])
    
    # Compute the cumulative distribution function
    intensity = np.round(image * 255).astype(np.uint8)
    freq, bins = cumulative_distribution(intensity)
    
    # Plot the actual and target CDFs
    target_bins = np.arange(256)
    target_freq = np.linspace(0, 1, len(target_bins))
    plt.step(bins, freq, c='b', label='Actual CDF')
    plt.plot(target_bins, target_freq, c='r', label='Target CDF')
    
    # Plot an example lookup
    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 lookup ({
    
    example_intensity} -> {
    
    example_target:.0f})')
    
    # Customize the plot
    plt.legend()
    plt.xlim(0, 255)
    plt.ylim(0, 1)
    plt.xlabel('Intensity Values')
    plt.ylabel('Cumulative Fraction of Pixels')
    plt.title('Cumulative Distribution Function')
    
    return freq, bins, target_freq, target_bins

调用上述函数,代码如下:

dark_image = imread('plasma_ball.png')
freq, bins, target_freq, target_bins = plot_cdf(dark_image);

得到结果如下:
在这里插入图片描述

5. 直方图均衡化实现

上述代码计算了dark_image的累积分布函数(CDF),然后基于线性分布定义目标CDF。然后,它用蓝色绘制dark_image的实际CDF,用红色绘制目标CDF(线性)。还绘制了强度值的示例查找,显示示例中实际CDF为50,我们希望其目标为230。

在获得目标CDF之后,下一步是计算要用于替换原始像素强度的强度值。这是通过使用插值来创建查找表来完成的,代码如下

# Display the result after replacing all actual values to target values
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');

得到结果如下:
在这里插入图片描述

可以看到,使用直方图均衡化后,原来偏暗的灰度图,整体对比度和亮度都得到了改善。

6. 扩展

上面展示了最基本的直方图操作类型,接着让我们尝试不同类型的CDF技术,看看哪种技术适合给定的图像,代码如下:

# Linear
target_bins = np.arange(256)


# Sigmoid
def sigmoid_cdf(x, a=1):
    return (1 + np.tanh(a * x)) / 2


# Exponential
def exponential_cdf(x, alpha=1):
    return 1 - np.exp(-alpha * x)


# Power
def power_law_cdf(x, alpha=1):
    return x ** alpha


# Other techniques:
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 clahe


def gamma_correction(image, gamma=1.0):
    corrected_image = exposure.adjust_gamma(image, gamma)
    return corrected_image


def 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_image


def 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_image


def equalize_hist_rgb(image):
    equalized_image = exposure.equalize_hist(image)
    return equalized_image


def 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_adjusted


def 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

调用代码如下:

# Save each technique to a variable
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)

最后得到的不同展示效果如下:
在这里插入图片描述
有很多方法/技术可以用于改善RGB图像的可视化效果,但其中大多数都需要手动调整参数。上述输出显示了使用各种直方图操作生成的亮度校正后的效果。通过观察,HSV调整、指数、对比度拉伸和unsharp masking的效果都令人满意。

请注意,不同方法的结果会因使用的原始图像而异。针对具体的图像,大家可以尝试使用不同的参数值来改善相应图像的质量。直方图处理技术可以极大地增强图像的对比度和整体外观。然而,谨慎使用它们是很重要的,因为如果过度使用,它们也会引入伪影,并导致不自然的外观,我们观察自适应直方图均衡化的输出可以明显看出该方法由于过度强调边缘而显得有些不自然。

7. 其他例子

与上面使用的亮度偏暗的图像相反,我们尝试在亮度良好的图像上使用相同的参数值执行代码。让我们观察一下相应的效果:
在这里插入图片描述
正如大家可能已经注意到的,大多数对亮度偏暗的图像有效的技术对偏亮的图像无效。这可能是由于这些技术可能会增强或放大图像中现有的亮度,导致过度曝光或添加伪影或噪声。

8. 总结

在这一节中,我们深入探讨了图像处理的世界,探索了各种图像增强技术。重点介绍了利用直方图技术来实现各种图像增强的效果,并给出了相应的代码示例。

您学废了嘛?

参考链接

猜你喜欢

转载自blog.csdn.net/sgzqc/article/details/131869013