【Python数字图像处理】基于LAB空间的图像去阴影方法

这篇具有很好参考价值的文章主要介绍了【Python数字图像处理】基于LAB空间的图像去阴影方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

整体架构流程

(1)阴影区域检测

①LAB颜色空间

②阴影检测

③代码 

(2)阴影去除

①在LAB空间上对单独目标区域去除阴影

②处理每个阴影区域

③代码

(3)阴影边缘校正

①中值滤波器的实现

②调用中值滤波器

③代码

效果展示

①环境图片

②文档上的阴影

全部代码

基于CNN的进阶方法

参考文献


概要

阴影检测和去除是许多计算机视觉应用中的一项重要的预处理任务。在图像分割过程中,阴影可能会产生错误的片段。此外,在对象检测算法中,阴影可能被错误地检测为对象。目前已有多种研究提出了各种基于像素和基于区域的方法来检测并去除图像中的阴影。

大多数的阴影检测方法都需要多幅图像来进行相机的校准,但是最好能够从单一图像中提取阴影。同时,从单一图像中区分黑暗物体和阴影也具有一定的难度。在阴影区域检测和去除的工作上,基于LAB等效图像的RGB图像的平均值选择阴影检测方法卓有成效。

整体架构流程

本文提供的图像去阴影方法分为三个流程:阴影区域检测、阴影去除、阴影边缘校正。

(1)阴影区域检测

阴影出现在光源由于某些物体的阻碍而不能直接到达的区域。一个对象也可以向自己投射一个阴影。在这项工作中,阴影检测是在LAB颜色空间中完成的。所以我们首先需要将RGB图像转换为其LAB等效图像;再根据图像在A平面和B平面上的平均值进行分类讨论将可能为影的区域检测出来。

①LAB颜色空间

LAB颜色空间的三个通道含义如下:

  • L通道:亮度通道,取值范围为[0, 100],这个值越大表示对应区域亮度越大;
  • A通道:RGB通道下的红绿比,取值范围为[-128, 128];
  • B通道:RGB通道下的黄蓝比,取值范围为[-128, 128];

python 图像阴影检测,python,图像处理     

②阴影检测

阴影检测是在LAB颜色空间中完成的。在这一部分首先将RGB图像转换为LAB图像并计算图片的每个像素在LAB通道的均值,接下来的工作就是识别阴影区域了。我们识别一个阴影区域的讨论方法如下:

  • 当A和B通道的平均值之和小于一个给定的阈值时开始对阴影的情况分类讨论:将满足L≤[mean(L)-std(L)/3] 中的值的像素分类为阴影像素,并将其他像素分类为非阴影像素;
  • 另外,将L和B平面上值较低的像素(低于某个给定的阈值)分类为阴影像素,其他像素分类为非阴影像素。

定义calculate_mask()完成阴影识别:它接受三个参数:org_image表示原始图像,ab_threshold表示A和B通道的平均值阈值,region_adjustment_kernel_size表示区域调整的核大小。

1.首先使用cv.cvtColor函数将原始图像转换为LAB颜色空间的图像,将LAB图像的数据类型转换为int16,并对L通道进行范围调整,使其取值范围变为0到100;

2.计算LAB图像中每个通道的平均值,并根据平均值计算阈值。阈值的计算方法是平均值减去每个通道的标准差的1/3;

3.判断A和B通道的平均值之和是否小于等于ab_threshold。

  • 如果是:将满足条件的像素点归类为阴影,使用cv.inRange函数根据阈值范围创建一个二值掩码图像;
  • 否则:将L和B通道低于阈值的部分归类为阴影,同样使用cv.inRange函数创建一个二值掩码图像。

4.使用cv.getStructuringElement()创建一个椭圆形的结构元素,用于形态学操作;使用cv.morphologyEx()对掩码图像进行闭运算和开运算,以去除噪声并填充阴影区域。

5.最后返回处理后的二值掩码图像:阴影区域在掩码图像中表示为白色,其他区域表示为黑色。

③代码 
# 识别阴影区域
def calculate_mask(org_image: np.ndarray,
                   ab_threshold: int,
                   region_adjustment_kernel_size: int) -> np.ndarray:
    # 将原图转换为LAB空间图片
    lab_img = cv.cvtColor(org_image, cv.COLOR_BGR2LAB)

    # L以及A、B的取值范围
    l_range = (0, 100)
    ab_range = (-128, 127)

    # 调整参数
    lab_img = lab_img.astype('int16')
    lab_img[:, :, 0] = lab_img[:, :, 0] * l_range[1] / 255
    lab_img[:, :, 1] += ab_range[0]
    lab_img[:, :, 2] += ab_range[0]

    # 计算所有像素的LAB平均值及其阈值
    means = [np.mean(lab_img[:, :, i]) for i in range(3)]
    thresholds = [means[i] - (np.std(lab_img[:, :, i]) / 3) for i in range(3)]

    # mean(A) + mean(B)<threshold, 归类为阴影
    if sum(means[1:]) <= ab_threshold:
        mask = cv.inRange(lab_img, (l_range[0], ab_range[0], ab_range[0]),
                                   (thresholds[0], ab_range[1], ab_range[1]))
    # 否则将L、B低于threshold的部分归类为阴影
    else:  
        mask = cv.inRange(lab_img, (l_range[0], ab_range[0], ab_range[0]),
                                   (thresholds[0], ab_range[1], thresholds[2]))

    kernel_size = (region_adjustment_kernel_size, region_adjustment_kernel_size)
    kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, kernel_size)
    cv.morphologyEx(mask, cv.MORPH_CLOSE, kernel, mask)
    cv.morphologyEx(mask, cv.MORPH_OPEN, kernel, mask)

    return mask

(2)阴影去除

阴影去除是通过使用适当的常数与阴影像素的R、G和B通道相乘来完成的。这里采用单独考虑每个阴影区域的做法,将近非阴影区域的每个通道的平均值与阴影区域的平均值之比作为每个通道的常数。虽然利用近非阴影区域的平均亮度近似阴影区域的亮度可以使阴影区域实现的照明几乎与非阴影区域相同,但是这种做法可能导致阴影区域的边缘出现过度照明的问题。而这个问题就要靠阴影边缘矫正来“兜底”了。

阴影去除的实现是通过计算阴影/非阴影区域的颜色比率,并利用该比率对阴影区域的色彩进行放缩实现的,定义correct_region_lab()以及process_regions()实现阴影去除:

①在LAB空间上对单独目标区域去除阴影

correct_region_lab()接受四个参数:org_image表示原始图像,shadow_clear_img表示去除阴影后的图像,shadow_indices表示阴影区域的索引,non_shadow_indices表示非阴影区域的索引。该函数用于对LAB版本的图像进行颜色修正。

1.首先计算阴影区域和非阴影区域的平均LAB值并计算颜色修正的比率,即非阴影区域的LAB平均值除以阴影区域的LAB平均值;

2.使用cv.cvtColor()将去除阴影后的图像转换为LAB颜色空间;

3.根据阴影区域的索引将阴影区域的LAB值乘以颜色修正的比率;

4.使用cv.cvtColor()将图像转换回BGR颜色空间并返回修正后的图像。

同时也可以定义correct_region_bgr(),它与correct_region_lab()的功能类似,区别在于它对RGB版本的图像进行颜色修正。但是总体来说仍然是LAB版本的阴影去除方法表现得更加优秀。

②处理每个阴影区域

process_regions()是阴影去除的主函数,接受多个参数:org_image表示原始图像,mask表示阴影掩码图像,lab_adjustment表示是否使用LAB版本进行颜色修正。shadow_dilation_kernel_size表示阴影膨胀的核大小,shadow_dilation_iteration表示阴影膨胀的迭代次数,shadow_size_threshold表示阴影区域的最小像素数阈值,verbose表示是否显示详细信息。

1.使用cv.cvtColor()将原始图像转换为LAB颜色空间的图像,并创建一个与原始图像相同的图像副本shadow_clear_img、measure.label函数对阴影掩码图像进行标记,得到每个阴影区域的标签;

2.循环处理每个阴影区域:判断阴影区域是否足够大,如果不满足阈值要求,则不进行处理;

3.通过cv.dilate()对阴影区域进行膨胀操作,得到非阴影区域;

4.按位异或对非阴影区域和阴影区域进行操作,得到阴影区域的边缘;

5.cv.findContours()提取阴影区域的轮廓,根据lab_adjustment参数的值,调用相应的颜色修正函数进行颜色修正;

6.edge_median_filter()对阴影区域的边缘进行中值滤波;

7.返回去除阴影后的图像。

③代码
# 阴影去除的具体流程,分为LAB版本和RGB版本
def correct_region_lab(org_img: np.ndarray,
                       shadow_clear_img: np.ndarray,
                       shadow_indices: np.ndarray,
                       non_shadow_indices: np.ndarray) -> np.ndarray:
    # 计算阴影区域及非阴影区域的平均RGB值
    shadow_average_lab = np.mean(org_img[shadow_indices[0], shadow_indices[1], :], axis=0)

    # 边缘区域的RGB均值
    border_average_lab = np.mean(org_img[non_shadow_indices[0], non_shadow_indices[1], :],
                                 axis=0)

    # 使用常数比率修正色彩
    lab_ratio = border_average_lab / shadow_average_lab

    shadow_clear_img = cv.cvtColor(shadow_clear_img, cv.COLOR_BGR2LAB)
    shadow_clear_img[shadow_indices[0], shadow_indices[1]] = np.uint8(
        shadow_clear_img[shadow_indices[0],
                         shadow_indices[1]] * lab_ratio)
    shadow_clear_img = cv.cvtColor(shadow_clear_img, cv.COLOR_LAB2BGR)

    return shadow_clear_img


def correct_region_bgr(org_img: np.ndarray,
                       shadow_clear_img: np.ndarray,
                       shadow_indices: np.ndarray,
                       non_shadow_indices: np.ndarray) -> np.ndarray:
    # 计算阴影区域及非阴影区域的平均RGB值
    shadow_average_bgr = np.mean(org_img[shadow_indices[0], shadow_indices[1], :], axis=0)

    # 边缘区域的RGB均值
    border_average_bgr = np.mean(org_img[non_shadow_indices[0], non_shadow_indices[1], :], axis=0)
    bgr_ratio = border_average_bgr / shadow_average_bgr

    # 使用常数比率修正色彩
    shadow_clear_img[shadow_indices[0], shadow_indices[1]] = np.uint8(
        shadow_clear_img[shadow_indices[0],
                         shadow_indices[1]] * bgr_ratio)

    return shadow_clear_img

# 阴影去除
def process_regions(org_image: np.ndarray,
                    mask: np.ndarray,
                    lab_adjustment: bool,
                    shadow_dilation_kernel_size: int,
                    shadow_dilation_iteration: int,
                    shadow_size_threshold: int,
                    verbose: bool) -> np.ndarray:
    
    # 转LAB空间
    lab_img = cv.cvtColor(org_image, cv.COLOR_BGR2LAB)
    shadow_clear_img = np.copy(org_image)

    # 用标签表示每个阴影区域
    labels = measure.label(mask)

    non_shadow_kernel_size = (shadow_dilation_kernel_size, shadow_dilation_kernel_size)
    non_shadow_kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, non_shadow_kernel_size)

    CHANNEL_MAX = 255

    # 单独处理每个阴影区域
    for label in np.unique(labels):
        if not label == 0:
            temp_filter = np.zeros(mask.shape, dtype="uint8")
            temp_filter[labels == label] = CHANNEL_MAX

            # 只有够大的阴影区域才会被处理
            if cv.countNonZero(temp_filter) >= shadow_size_threshold:
                shadow_indices = np.where(temp_filter == CHANNEL_MAX)

                # 各点像素值被替换为对应邻域上的最大值
                non_shadow_temp_filter = cv.dilate(temp_filter, non_shadow_kernel,
                                                   iterations=shadow_dilation_iteration)

                # 与掩膜进行按位异或运算
                non_shadow_temp_filter = cv.bitwise_xor(non_shadow_temp_filter, temp_filter)
                non_shadow_indices = np.where(non_shadow_temp_filter == CHANNEL_MAX)

                # 提取阴影区域的轮廓
                contours, hierarchy = cv.findContours(temp_filter, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

                # 是否要利用LAB空间进行阴影去除
                if lab_adjustment:
                    shadow_clear_img = correct_region_lab(lab_img, shadow_clear_img,
                                                          shadow_indices, non_shadow_indices)
                else:
                    shadow_clear_img = correct_region_bgr(org_image, shadow_clear_img,
                                                          shadow_indices, non_shadow_indices)

                # 阴影区域边缘校正
                shadow_clear_img = edge_median_filter(cv.cvtColor(shadow_clear_img, cv.COLOR_BGR2HSV),
                                                      contours)
                if verbose:
                    display_region(org_image, shadow_clear_img, label, temp_filter, contours)

    return shadow_clear_img

(3)阴影边缘校正

由于阴影区域不均匀照明,整个阴影区域的相同常数可能在阴影边缘附近创建过度照明的区域。这可以通过在过亮区域应用中值滤波器来克服。中值滤波器是一种常用的图像处理算法,用于去除图像中的噪声,它的原理是将每个像素点周围的像素值排序,然后取中间值作为该像素点的值。这样就得到了一个边缘无过亮或过亮现象有所缓解的无阴影图像。

①中值滤波器的实现

median_filter()接受三个参数:img表示输入图像,point表示需要进行中值滤波的像素点的坐标,filter_size表示滤波器的大小;

1.根据滤波器的大小和中心像素点的坐标,计算出需要参与计算中值的像素点的坐标。这里使用了两个嵌套的循环,分别遍历了滤波器范围内的所有像素点的坐标;

2.filter()过滤掉超出图像范围的像素点的坐标。然后,初始化一个长度为3的列表pixel_values,用于存储每个颜色通道的像素值;

3.循环遍历每个颜色通道,将每个像素点的颜色通道值存入pixel_values列表中;

4.np.median()计算pixel_values列表中每个颜色通道的中值,得到一个长度为3的列表pixel_values;

5.最后,返回pixel_values列表作为中值滤波后的像素值。

②调用中值滤波器

edge_median_filter()接受三个参数:img表示输入图像,contours_list表示图像中的轮廓列表,filter_size表示滤波器的大小,默认为7。

1.np.copy()创建一个与输入图像相同的临时图像temp_img;

2.循环遍历轮廓列表中的每个轮廓,并遍历每个轮廓中的每个点;对于每个点,将其坐标传递给median_filter()进行中值滤波,并将滤波后的像素值赋给temp_img对应位置的像素点;

3.最后通过cv.cvtColor()将滤波后的图像从HSV颜色空间转换为BGR颜色空间,并将结果返回。

③代码
# 中值滤波器
def median_filter(img: np.ndarray,
                  point: np.ndarray,
                  filter_size: int) -> List:
    indices = [[x, y]
               for x in range(point[1] - filter_size // 2, point[1] + filter_size // 2 + 1)
               for y in range(point[0] - filter_size // 2, point[0] + filter_size // 2 + 1)]

    indices = list(filter(lambda x: not (x[0] < 0 or x[1] < 0 or
                                         x[0] >= img.shape[0] or
                                         x[1] >= img.shape[1]), indices))

    pixel_values = [0, 0, 0]

    # 计算像素点对应中值
    for channel in range(3):
        pixel_values[channel] = list(img[index[0], index[1], channel] for index in indices)
    pixel_values = list(np.median(pixel_values, axis=1))

    return pixel_values

def edge_median_filter(img: np.ndarray,
                       contours_list: tuple,
                       filter_size: int = 7) -> np.ndarray:
    temp_img = np.copy(img)

    for partition in contours_list:
        for point in partition:
            temp_img[point[0][1]][point[0][0]] = median_filter(img,
                                                               point[0],
                                                               filter_size)

    return cv.cvtColor(temp_img, cv.COLOR_HSV2BGR)

效果展示

①环境图片

输入原始图片后,程序将给出原始图片Original Image、识别的阴影区域Shadow Regions、去除阴影后的图片Correct Image,三者用同一张图对比结果如下:

python 图像阴影检测,python,图像处理

原始图片如下,该图有较多的阴影区域:

python 图像阴影检测,python,图像处理

输出的最终效果如图,许多原本较暗的区域的亮度已经变得接近非阴影邻域的亮度,虽然在边缘的处理还是有瑕疵,但是总体来说效果还是不错的。如果要实现更加完美的去除阴影应该要使用更加高级的如神经网络方法了:

python 图像阴影检测,python,图像处理

②文档上的阴影

为了与后续的基于神经网络的去阴影方法进行对比,这里特别地针对文档上的具有阴影的图片进行测试,测试结果如下:

首先是原图、阴影区域以及修复后结果的对比:

python 图像阴影检测,python,图像处理

原图如下:

python 图像阴影检测,python,图像处理

修复后的图像的阴影区域整体会过亮,但边缘和阴影区域内部的亮度确实没有太大区别。这可能可以通过调整去除阴影所使用的系数做得更好,但不能保证在别的图片上就有更好的效果了。所以要真正做得更好还要通过更高级的方法:

python 图像阴影检测,python,图像处理

全部代码

import cv2 as cv
import numpy as np
from skimage import measure
from matplotlib import pyplot as plt
from typing import Tuple, List

# 中值滤波器
def median_filter(img: np.ndarray,
                  point: np.ndarray,
                  filter_size: int) -> List:
    indices = [[x, y]
               for x in range(point[1] - filter_size // 2, point[1] + filter_size // 2 + 1)
               for y in range(point[0] - filter_size // 2, point[0] + filter_size // 2 + 1)]

    indices = list(filter(lambda x: not (x[0] < 0 or x[1] < 0 or
                                         x[0] >= img.shape[0] or
                                         x[1] >= img.shape[1]), indices))

    pixel_values = [0, 0, 0]

    # 计算像素点对应中值
    for channel in range(3):
        pixel_values[channel] = list(img[index[0], index[1], channel] for index in indices)
    pixel_values = list(np.median(pixel_values, axis=1))

    return pixel_values

def edge_median_filter(img: np.ndarray,
                       contours_list: tuple,
                       filter_size: int = 7) -> np.ndarray:
    temp_img = np.copy(img)

    for partition in contours_list:
        for point in partition:
            temp_img[point[0][1]][point[0][0]] = median_filter(img,
                                                               point[0],
                                                               filter_size)

    return cv.cvtColor(temp_img, cv.COLOR_HSV2BGR)

def display_region(org_image: np.ndarray,
                   shadow_clear_image: np.ndarray,
                   label: int,
                   label_region: np.ndarray,
                   contours: tuple) -> None:
    # For debugging, cut the current shadow region from the image
    reverse_mask = cv.cvtColor(cv.bitwise_not(label_region), cv.COLOR_GRAY2BGR)
    img_w_hole = org_image & reverse_mask

    temp_filter = cv.cvtColor(label_region, cv.COLOR_GRAY2BGR)
    cv.drawContours(temp_filter, contours, -1, (255, 0, 0), 3)

    fig, axes = plt.subplots(2, 2)

    ax = axes.ravel()

    plt.title(f"Shadow Region {label}")

    ax[0].imshow(cv.cvtColor(org_image, cv.COLOR_BGR2RGB))
    ax[0].set_title("Original Image")

    ax[1].imshow(cv.cvtColor(temp_filter, cv.COLOR_BGR2RGB))
    ax[1].set_title("Shadow Region")

    ax[2].imshow(cv.cvtColor(img_w_hole, cv.COLOR_BGR2RGB))
    ax[2].set_title("Shadow Region Cut")

    ax[3].imshow(cv.cvtColor(shadow_clear_image, cv.COLOR_BGR2RGB))
    ax[3].set_title("Corrected Image")

    plt.tight_layout()
    plt.show()

# 阴影去除的具体流程,分为LAB版本和RGB版本
def correct_region_lab(org_img: np.ndarray,
                       shadow_clear_img: np.ndarray,
                       shadow_indices: np.ndarray,
                       non_shadow_indices: np.ndarray) -> np.ndarray:
    # 计算阴影区域及非阴影区域的平均RGB值
    shadow_average_lab = np.mean(org_img[shadow_indices[0], shadow_indices[1], :], axis=0)

    # 边缘区域的RGB均值
    border_average_lab = np.mean(org_img[non_shadow_indices[0], non_shadow_indices[1], :],
                                 axis=0)

    # 使用常数比率修正色彩
    lab_ratio = border_average_lab / shadow_average_lab

    shadow_clear_img = cv.cvtColor(shadow_clear_img, cv.COLOR_BGR2LAB)
    shadow_clear_img[shadow_indices[0], shadow_indices[1]] = np.uint8(
        shadow_clear_img[shadow_indices[0],
                         shadow_indices[1]] * lab_ratio)
    shadow_clear_img = cv.cvtColor(shadow_clear_img, cv.COLOR_LAB2BGR)

    return shadow_clear_img


def correct_region_bgr(org_img: np.ndarray,
                       shadow_clear_img: np.ndarray,
                       shadow_indices: np.ndarray,
                       non_shadow_indices: np.ndarray) -> np.ndarray:
    # 计算阴影区域及非阴影区域的平均RGB值
    shadow_average_bgr = np.mean(org_img[shadow_indices[0], shadow_indices[1], :], axis=0)

    # 边缘区域的RGB均值
    border_average_bgr = np.mean(org_img[non_shadow_indices[0], non_shadow_indices[1], :], axis=0)
    bgr_ratio = border_average_bgr / shadow_average_bgr

    # 使用常数比率修正色彩
    shadow_clear_img[shadow_indices[0], shadow_indices[1]] = np.uint8(
        shadow_clear_img[shadow_indices[0],
                         shadow_indices[1]] * bgr_ratio)

    return shadow_clear_img

# 阴影去除
def process_regions(org_image: np.ndarray,
                    mask: np.ndarray,
                    lab_adjustment: bool,
                    shadow_dilation_kernel_size: int,
                    shadow_dilation_iteration: int,
                    shadow_size_threshold: int,
                    verbose: bool) -> np.ndarray:
    
    # 转LAB空间
    lab_img = cv.cvtColor(org_image, cv.COLOR_BGR2LAB)
    shadow_clear_img = np.copy(org_image)

    # 用标签表示每个阴影区域
    labels = measure.label(mask)

    non_shadow_kernel_size = (shadow_dilation_kernel_size, shadow_dilation_kernel_size)
    non_shadow_kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, non_shadow_kernel_size)

    CHANNEL_MAX = 255

    # 单独处理每个阴影区域
    for label in np.unique(labels):
        if not label == 0:
            temp_filter = np.zeros(mask.shape, dtype="uint8")
            temp_filter[labels == label] = CHANNEL_MAX

            # 只有够大的阴影区域才会被处理
            if cv.countNonZero(temp_filter) >= shadow_size_threshold:
                shadow_indices = np.where(temp_filter == CHANNEL_MAX)

                # 各点像素值被替换为对应邻域上的最大值
                non_shadow_temp_filter = cv.dilate(temp_filter, non_shadow_kernel,
                                                   iterations=shadow_dilation_iteration)

                # 与掩膜进行按位异或运算
                non_shadow_temp_filter = cv.bitwise_xor(non_shadow_temp_filter, temp_filter)
                non_shadow_indices = np.where(non_shadow_temp_filter == CHANNEL_MAX)

                # 提取阴影区域的轮廓
                contours, hierarchy = cv.findContours(temp_filter, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)

                # 是否要利用LAB空间进行阴影去除
                if lab_adjustment:
                    shadow_clear_img = correct_region_lab(lab_img, shadow_clear_img,
                                                          shadow_indices, non_shadow_indices)
                else:
                    shadow_clear_img = correct_region_bgr(org_image, shadow_clear_img,
                                                          shadow_indices, non_shadow_indices)

                # 阴影区域边缘校正
                shadow_clear_img = edge_median_filter(cv.cvtColor(shadow_clear_img, cv.COLOR_BGR2HSV),
                                                      contours)
                if verbose:
                    display_region(org_image, shadow_clear_img, label, temp_filter, contours)

    return shadow_clear_img

# 识别阴影区域
def calculate_mask(org_image: np.ndarray,
                   ab_threshold: int,
                   region_adjustment_kernel_size: int) -> np.ndarray:
    # 将原图转换为LAB空间图片
    lab_img = cv.cvtColor(org_image, cv.COLOR_BGR2LAB)

    # L以及A、B的取值范围
    l_range = (0, 100)
    ab_range = (-128, 127)

    # 调整参数
    lab_img = lab_img.astype('int16')
    lab_img[:, :, 0] = lab_img[:, :, 0] * l_range[1] / 255
    lab_img[:, :, 1] += ab_range[0]
    lab_img[:, :, 2] += ab_range[0]

    # 计算所有像素的LAB平均值及其阈值
    means = [np.mean(lab_img[:, :, i]) for i in range(3)]
    thresholds = [means[i] - (np.std(lab_img[:, :, i]) / 3) for i in range(3)]

    # mean(A) + mean(B)<threshold, 归类为阴影
    if sum(means[1:]) <= ab_threshold:
        mask = cv.inRange(lab_img, (l_range[0], ab_range[0], ab_range[0]),
                                   (thresholds[0], ab_range[1], ab_range[1]))
    # 否则将L、B低于threshold的部分归类为阴影
    else:  
        mask = cv.inRange(lab_img, (l_range[0], ab_range[0], ab_range[0]),
                                   (thresholds[0], ab_range[1], thresholds[2]))

    kernel_size = (region_adjustment_kernel_size, region_adjustment_kernel_size)
    kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, kernel_size)
    cv.morphologyEx(mask, cv.MORPH_CLOSE, kernel, mask)
    cv.morphologyEx(mask, cv.MORPH_OPEN, kernel, mask)

    return mask


def remove_shadows(org_image: np.ndarray,
                   ab_threshold: int,
                   lab_adjustment: bool,
                   region_adjustment_kernel_size: int,
                   shadow_dilation_iteration: int,
                   shadow_dilation_kernel_size: int,
                   shadow_size_threshold: int,
                   verbose: bool) -> Tuple[np.ndarray, np.ndarray]:
    
    mask = calculate_mask(org_image,
                          ab_threshold,
                          region_adjustment_kernel_size)

    shadow_clear_img = process_regions(org_image,
                                       mask,
                                       lab_adjustment,
                                       shadow_dilation_kernel_size,
                                       shadow_dilation_iteration,
                                       shadow_size_threshold,
                                       verbose)

    mask = cv.cvtColor(mask, cv.COLOR_GRAY2RGB)

    return shadow_clear_img, mask

# 主要处理过程, 返回RGB色彩空间的图片
def process_image_file(img_name: str,
                       save: bool = False,
                       ab_threshold: int = 256,
                       lab_adjustment: bool = True,
                       region_adjustment_kernel_size: int = 10,
                       shadow_dilation_kernel_size: int = 5,
                       shadow_dilation_iteration: int = 3,
                       shadow_size_threshold: int = 2500,
                       verbose: bool = True) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    org_image = cv.imread(img_name)
    print("读取图片 {}".format(img_name))

    # 阴影去除过程
    shadow_clear, mask = remove_shadows(org_image,
                                        ab_threshold,
                                        lab_adjustment,
                                        region_adjustment_kernel_size,
                                        shadow_dilation_iteration,
                                        shadow_dilation_kernel_size,
                                        shadow_size_threshold,
                                        verbose=verbose)

    # 以下是展示图片的代码
    _, axes = plt.subplots(1, 3)
    ax = axes.ravel()

    plt.title("最终效果")

    ax[0].imshow(cv.cvtColor(org_image, cv.COLOR_BGR2RGB))
    ax[0].set_title("Original Image")

    ax[1].imshow(cv.cvtColor(mask, cv.COLOR_BGR2RGB))
    ax[1].set_title("Shadow Regions")

    ax[2].imshow(cv.cvtColor(shadow_clear, cv.COLOR_BGR2RGB))
    ax[2].set_title("Corrected Image")

    plt.tight_layout()
    plt.show()

    if save:
        f_name = img_name[:img_name.index(".")] + "_shadowClear" + img_name[img_name.index("."):]
        cv.imwrite(f_name, shadow_clear)
        print("Saved result as " + f_name)

    return org_image, mask, shadow_clear

image_path = "images/test.jpg"

org_image, mask, image_clear = process_image_file(image_path, ab_threshold = 4, save = True, verbose = True)

基于CNN的进阶方法

论文笔记【Document Image Shadow Removal Guided by Color-Aware Background】+代码复现

参考文献

[1] Murali, Saritha, and V. K. Govindan."Shadow detection and removal from a single image using LAB color space."Cybernetics and information technologies 13.1 (2013): 95-103.

[2] Murali, Saritha, and V. K. Govindan."Removal of shadows from a single image."the Proceedings of First International Conference on Futuristic Trends in Computer Science and Engineering. Vol. 4.文章来源地址https://www.toymoban.com/news/detail-829510.html

到了这里,关于【Python数字图像处理】基于LAB空间的图像去阴影方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数字图像处理【4】图像空间滤波-锐化

    对于初学者来说,可能没能搞清楚哪些是图像的高频信息,低频信息指代的是什么。 低频指的就是灰度变化比较小的像素区域 高频指的就是灰度变化比较大的像素区域 所谓灰度变化比较小的图像就是,内容;所谓灰度变化比较大的图像就是,边缘和纹理; 边缘:灰度变化较

    2024年02月10日
    浏览(41)
  • 【数字图像处理】空间滤波

    图像空间滤波是一种常用的图像处理技术,用于改变图像的亮度、对比度、锐度、噪声等特性。它是一种直接在图像空间进行像素操作的处理方法,与频域滤波不同,频域滤波是通过对图像进行傅里叶变换,然后在频域进行滤波处理。本文仅对常用的低通和高通空间滤波进行

    2024年02月04日
    浏览(37)
  • (数字图像处理MATLAB+Python)第五章图像增强-第四、五节:基于模糊技术和基于伪彩色处理的图像增强

    图像的模糊特征平面 :假设原始图像为 I ( x , y ) I(x,y) I ( x , y ) ,其中 x x x 和 y y y 分别表示图像的水平和垂直方向的坐标。模糊特征平面可以表示为 B ( x , y , θ ) B(x,y,theta) B ( x , y , θ ) ,其中 θ theta θ 是一个旋转角度参数,表示模糊核函数的旋转角度。 B ( x , y , θ ) B(x,

    2023年04月20日
    浏览(96)
  • 【遥感数字图像处理(朱文泉)】第三章 空间域处理方法

     一、空间域与变换域  二、常见数字图像空间域处理方法   - 空间域处理方法是根据图像像元数据的空间表示f(x, y)进行处理;   - 变换域处理方法是对图像像元数据的空间表示f(x, y)先进行某种变换,然后针对变换数据进行处理,最后再把处理的结果反变换到空间域。 注

    2024年01月21日
    浏览(53)
  • (数字图像处理MATLAB+Python)第五章图像增强-第二节:基于直方图修正的图像增强

    基于直方图修正的图像增强 :是一种常见的图像处理方法。该方法通过对图像的像素值分布进行调整,以改善图像的对比度和亮度等视觉效果。具体地,直方图校正方法将图像的像素值转换为一个新的值域范围,使得像素值的分布更加均匀,从而增强图像的细节和对比度。这

    2023年04月19日
    浏览(51)
  • 数字图像处理之matlab实验(三):空间滤波器

    空间滤波,就是在原图像上,用一个固定尺寸的模板去做卷积运算,得到的新图像就是滤波结果。滤波,就是过滤某种信号的意思。过滤哪种信号取决于模板设计,如果是锐化模板,处理后就保留高频信号,如果是平滑模板,处理后就保留低频信号。 (1)模板运算 图像处理

    2024年04月28日
    浏览(46)
  • 基于matlab的数字图像处理之彩色图像处理

    一、实验目的 (1)了解如何利用RGB分量生成简单的图像。 (2)熟练掌握RGB彩色模型转换到HIS彩色模型的过程。 (3)熟练掌握RGB图像的彩色分割。 (4)熟练掌握彩色图像如何在向量空间中进行边缘检测。 二、实验仪器(软件平台)     计算机、MATLAB软件 三、实验原理

    2024年02月06日
    浏览(43)
  • OpenCV数字图像处理基于C++:图像分割

    图像阈值化分割是一种常用的、传统的图像分割技术,因其 实现简单、计算量小、性能比较稳定 而成为图像分割中基本和应用广泛的分割技术。特别 适合于目标和背景占据不同灰度级范围的图像 。不仅 可以极大地压缩数据量 ,而且大大 简化了分析和处理的步骤 ,是进行

    2024年02月11日
    浏览(62)
  • 数字图像处理——实验五 基于图像分割的车牌定位识别

    (1)掌握车牌阈值分割; (2)掌握基于形态学计算的图像分割; (3)掌握图像的二值化; (4)掌握基于像素投影的字符分割; (5)掌握字符识别原理。 (1)计算机; (2)Python 3.x及PyCharm软件; (3)需进行车牌识别的图片。 注: opencv-python 使用的是3.x 版本 (1) 图像灰

    2024年02月08日
    浏览(66)
  • 彩色图像处理之彩色图像直方图处理的python实现——数字图像处理

    彩色图像的直方图处理是一种重要的图像处理技术,用于改善图像的视觉效果,增强图像的对比度,或为后续的图像处理任务(如图像分割、特征提取)做准备。彩色图像通常由红色(R)、绿色(G)、蓝色(B)三个颜色通道组成,因此彩色图像的直方图处理相比单色图像更

    2024年01月23日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包