【北京化工大学】图像理解与机器视觉第三周上机

DeHaze、Seam Carving算法实现

Part one dehaze 去雾算法的实现

用到的原始图像

去雾算法总共三步:

第一步是计算暗通道图像;

        具体方法:对于图像中的每一个像素点,在邻域中的rgb三种颜色中找到最小值,把所有最小值组合起来。

def zmMinFilterGray(src, r=7):
    #滤波半径7,src是一个灰度图像
    #erode是腐蚀操作,在滤波核区域内取最小值,实现最小值滤波
    return cv2.erode(src, np.ones((2 * r + 1, 2 * r + 1)))
'''
暗通道计算
'''
#1.计算每个像素点中的最小值,这里的m是输入的图像,是一个三维数组(图像的高/宽/rgb通道),min(m,2)就是rgb通道的最小值
V1 = np.min(m, 2)
#这里是进行最小值滤波。实现去雾总共要两次滤波,这里是第一次。最小值滤波的效果是进一步捕获图像局部区域的最小通道值。
#这里的7是滤波的窗口大小,用一个7x7的框框在图像上滑动
Dark_Channel = zmMinFilterGray(V1, 7)

 最后效果

第二步,进行 导向图滤波,这是第二个滤波

导向滤波是一种边缘保留的图像滤波方法,它可以在平滑图像的同时保留图像的边缘信息
def guidedfilter(I, p, r, eps):
    m_I = cv2.boxFilter(I, -1, (r, r))
    m_p = cv2.boxFilter(p, -1, (r, r))
    m_Ip = cv2.boxFilter(I * p, -1, (r, r))
    cov_Ip = m_Ip - m_I * m_p

    m_II = cv2.boxFilter(I * I, -1, (r, r))
    var_I = m_II - m_I * m_I

    a = cov_Ip / (var_I + eps)
    b = m_p - a * m_I

    m_a = cv2.boxFilter(a, -1, (r, r))
    m_b = cv2.boxFilter(b, -1, (r, r))
    return m_a * I + m_b

 

第三步,估计大气光值 

大气光值就是去雾公式里的A。在暗通道图像中选取亮度值最高的前0.1%的像素点,然后在原始图像中找到这些像素点对应位置的像素值,取其最大值作为大气光值。大气光值代表了雾的强度

-

#这里计算图像的直方图,直方图区间数量2000
    bins = 2000
#对V1计算直方图,返回ht0每个区间的记数,ht1是区间的边界值
    ht = np.histogram(V1, bins)
#累计分布d,表示小于等于每个区间边界值的像素比例
    d = np.cumsum(ht[0]) / float(V1.size)
#从直方图最高区间开始,反向遍历d
    for lmax in range(bins - 1, 0, -1):
        if d[lmax] <= 0.999:
            break
#np.mean(m,2)是一个灰度图像,之后晒栓除V1大于阈值ht[1][lamx]的像素,选取最大值为A
    A = []
    for channel in range(m.shape[2]):
        # 修正的部分
        channel_max = m[:, :, channel][V1 >= ht[1][lmax]].max()
        A.append(channel_max)
    A = np.array(A)
    V1 = np.minimum(V1 * w, maxV1)  # 对值范围进行限制
    return V1, A

现在得到了两个图像和A,接下来要求解公式中的J=[I-A(1-t)]/t

 

def deHaze(m, r=81, eps=0.001, w=0.95, maxV1=0.80, bGamma=False):
    Y = np.zeros(m.shape)
    t, A = Defog(m, r, eps, w, maxV1)             # 得到遮罩图像和大气光照

    for k in range(3):
        # Y[:,:,k] = (m[:,:,k] - Mask_img)/(1-Mask_img/A)  # 颜色校正
        Y[:, :, k] = (m[:, :, k] - A[k] * (1 - t)) / t
#要把颜色限制到0-1,要不然图像就会变态掉

    Y = np.clip(Y, 0, 1)
    return Y

这个就是没有限制到0-1的后果 

 

import cv2
import numpy as np

def zmMinFilterGray(src, r=7):
    # 滤波半径7,src是一个灰度图像
    # erode是腐蚀操作,在滤波核区域内取最小值,实现最小值滤波
    return cv2.erode(src, np.ones((2 * r + 1, 2 * r + 1)))

def guidedfilter(I, p, r, eps):
    m_I = cv2.boxFilter(I, -1, (r, r))
    m_p = cv2.boxFilter(p, -1, (r, r))
    m_Ip = cv2.boxFilter(I * p, -1, (r, r))
    cov_Ip = m_Ip - m_I * m_p

    m_II = cv2.boxFilter(I * I, -1, (r, r))
    var_I = m_II - m_I * m_I

    a = cov_Ip / (var_I + eps)
    b = m_p - a * m_I

    m_a = cv2.boxFilter(a, -1, (r, r))
    m_b = cv2.boxFilter(b, -1, (r, r))
    return m_a * I + m_b

def Defog(m, r, eps, w, maxV1):                 # 输入rgb图像,值范围[0,1]
    '''计算大气遮罩图像V1和光照值A, V1 = 1-t/A'''
    V1 = np.min(m, 2)
    # 得到暗通道图像
    Dark_Channel = zmMinFilterGray(V1, 7)
    cv2.imshow('V1', V1)
    cv2.imshow('Dark',Dark_Channel)    # 查看暗通
    cv2.waitKey(0)
    # cv2.destroyAllWindows()

    V1 = guidedfilter(V1, Dark_Channel, r, eps)  # 使用导向图滤波优化
    bins = 2000
    ht = np.histogram(V1, bins)
    d = np.cumsum(ht[0]) / float(V1.size)
    for lmax in range(bins - 1, 0, -1):
        if d[lmax] <= 0.999:
            break
    A = []
    for channel in range(m.shape[2]):
        # 修正的部分
        channel_max = m[:, :, channel][V1 >= ht[1][lmax]].max()
        A.append(channel_max)
    A = np.array(A)
    V1 = np.minimum(V1 * w, maxV1)  # 对值范围进行限制
    return V1, A

def deHaze(m, r=81, eps=0.001, w=0.95, maxV1=0.80, bGamma=False):
    Y = np.zeros(m.shape)
    t, A = Defog(m, r, eps, w, maxV1)             # 得到遮罩图像和大气光照

    for k in range(3):
        # Y[:,:,k] = (m[:,:,k] - Mask_img)/(1-Mask_img/A)  # 颜色校正
        Y[:, :, k] = (m[:, :, k] - A[k] * (1 - t)) / t
    Y = np.clip(Y, 0, 1)
    return Y

if __name__ == '__main__':
    m = (deHaze(cv2.imread(r'E:\Strudy\Data\pythonProject2\fog.jpg') / 255.0) * 255).astype(np.uint8)
    cv2.imshow("final",m)
    cv2.waitKey(0)
    # cv2.imwrite('20190708_02.png', m)

最终效果: 

如果板板正正理解了看到这里那你可就真棒!

part two Seam Carving算法实现

用到的图像

第一步 计算能量图

使用的soebl方法

def energy_function(image):
    if len(image.shape) == 3:
#图像如果三维的话就转化为灰度图像
        image = color.rgb2gray(image)
    # 计算 x 方向的梯度
    sobel_x = filters.sobel_v(image)
    # 计算 y 方向的梯度
    sobel_y = filters.sobel_h(image)
    # 计算梯度的幅值作为能量
    energy = np.sqrt(sobel_x**2 + sobel_y**2)
    return energy

第二步,计算累计能量和路径

方法:对于2这个格子,选取上面一行5,8,12中的最小值。2这个格子就变成了2+5=7

可以自己算一下

注意,这段代码实现的是上下裁剪。

def compute_cost(image, energy_map):
    H, W = energy_map.shape
    cost = np.zeros((H, W))
    path = np.zeros((H, W), dtype=int)

    # 初始化第一列的累积能量成本为能量图的第一列
    cost[:, 0] = energy_map[:, 0]

    for j in range(1, W):
        for i in range(H):
            if i == 0:
                # 处理第一行
                min_index = np.argmin(cost[i:i + 2, j - 1])
                cost[i, j] = energy_map[i, j] + cost[i + min_index, j - 1]
                path[i, j] = min_index
            elif i == H - 1:
                # 处理最后一行
                min_index = np.argmin(cost[i - 1:i + 1, j - 1])
                cost[i, j] = energy_map[i, j] + cost[i - 1 + min_index, j - 1]
                path[i, j] = min_index - 1
            else:
                # 处理中间行
                min_index = np.argmin(cost[i - 1:i + 2, j - 1])
                cost[i, j] = energy_map[i, j] + cost[i - 1 + min_index, j - 1]
                path[i, j] = min_index - 1

    return path, cost

示例 

 

若要实现左右裁剪,就是在循环那里修改先遍历H就好了

def compute_cost(image, energy_map):
    H, W = energy_map.shape
    cost = np.zeros((H, W))
    path = np.zeros((H, W), dtype=int)

    # 初始化第一行的累积能量成本为能量图的第一行
    cost[0] = energy_map[0]

    for i in range(1, H):
        for j in range(W):
            if j == 0:
                # 处理第一列
                min_index = np.argmin(cost[i - 1, j:j + 2])
                cost[i, j] = energy_map[i, j] + cost[i - 1, j + min_index]
                path[i, j] = min_index
            elif j == W - 1:
                # 处理最后一列
                min_index = np.argmin(cost[i - 1, j - 1:j + 1])
                cost[i, j] = energy_map[i, j] + cost[i - 1, j - 1 + min_index]
                path[i, j] = min_index - 1
            else:
                # 处理中间列
                min_index = np.argmin(cost[i - 1, j - 1:j + 2])
                cost[i, j] = energy_map[i, j] + cost[i - 1, j - 1 + min_index]
                path[i, j] = min_index - 1

    return path, cost

示例

第三步,回溯找到接缝

从最后一列的最小累计能量成本位置开始,根据路径矩阵回溯,找到接缝(最小能量路径)

def backtrack_seam(path, end_index):
    H, W = path.shape
    seam = -np.ones(W, dtype=int)
    seam[-1] = end_index

    for j in range(W - 2, -1, -1):
        end_index = end_index + path[end_index, j + 1]
        seam[j] = end_index

    return seam

第四步,移除接缝

def reduce(image):
    # 计算能量图
    energy_map = energy_function(image)
    # 计算累积能量成本和路径
    path, cost = compute_cost(image, energy_map)
    # 找到最后一列中累积能量成本最小的位置
    end_index = np.argmin(cost[:, -1])
    # 回溯找到接缝
    seam = backtrack_seam(path, end_index)

    H, W = image.shape[:2]
    if len(image.shape) == 3:  # 彩色图像
        new_image = np.zeros((H - 1, W, 3), dtype=image.dtype)
        for j in range(W):
            row = seam[j]
            new_image[:row, j] = image[:row, j]
            new_image[row:, j] = image[row + 1:, j]
    else:  # 灰度图像
        new_image = np.zeros((H - 1, W), dtype=image.dtype)
        for j in range(W):
            row = seam[j]
            new_image[:row, j] = image[:row, j]
            new_image[row:, j] = image[row + 1:, j]

    return new_image

注意这里移除只会移除一个像素点,所以要进行循环

'''
main
'''
if __name__ == "__main__":
    image = io.imread('castle.jpg')
    # 定义要移除的接缝数量
    num = 100  # 可以根据需要调整这个值
    current_image = image
    for _ in range(num):
        current_image = reduce(current_image)
    # 保存处理后的图像
    io.imsave('resized_image.jpg', current_image)

同志们可以生成横着和竖着的裁剪

这个是上下裁剪,发现天空和草地少了很多

这个是竖着裁剪,会发现人和建筑物近了很多

完整代码:横竖裁剪替换中间reduce函数就可以

from skimage import color, io, filters
import numpy as np

'''
用sobel算子计算能量图
'''
def energy_function(image):
    if len(image.shape) == 3:
        image = color.rgb2gray(image)
    # 计算 x 方向的梯度
    sobel_x = filters.sobel_v(image)
    # 计算 y 方向的梯度
    sobel_y = filters.sobel_h(image)
    # 计算梯度的幅值作为能量
    energy = np.sqrt(sobel_x**2 + sobel_y**2)
    return energy

def compute_cost(image, energy_map):
    H, W = energy_map.shape
    cost = np.zeros((H, W))
    path = np.zeros((H, W), dtype=int)

    # 初始化第一列的累积能量成本为能量图的第一列
    cost[:, 0] = energy_map[:, 0]

    for j in range(1, W):
        for i in range(H):
            if i == 0:
                # 处理第一行
                min_index = np.argmin(cost[i:i + 2, j - 1])
                cost[i, j] = energy_map[i, j] + cost[i + min_index, j - 1]
                path[i, j] = min_index
            elif i == H - 1:
                # 处理最后一行
                min_index = np.argmin(cost[i - 1:i + 1, j - 1])
                cost[i, j] = energy_map[i, j] + cost[i - 1 + min_index, j - 1]
                path[i, j] = min_index - 1
            else:
                # 处理中间行
                min_index = np.argmin(cost[i - 1:i + 2, j - 1])
                cost[i, j] = energy_map[i, j] + cost[i - 1 + min_index, j - 1]
                path[i, j] = min_index - 1

    return path, cost

def backtrack_seam(path, end_index):
    H, W = path.shape
    seam = -np.ones(W, dtype=int)
    seam[-1] = end_index

    for j in range(W - 2, -1, -1):
        end_index = end_index + path[end_index, j + 1]
        seam[j] = end_index

    return seam

def reduce(image):
    # 计算能量图
    energy_map = energy_function(image)
    # 计算累积能量成本和路径
    path, cost = compute_cost(image, energy_map)
    # 找到最后一列中累积能量成本最小的位置
    end_index = np.argmin(cost[:, -1])
    # 回溯找到接缝
    seam = backtrack_seam(path, end_index)

    H, W = image.shape[:2]
    if len(image.shape) == 3:  # 彩色图像
        new_image = np.zeros((H - 1, W, 3), dtype=image.dtype)
        for j in range(W):
            row = seam[j]
            new_image[:row, j] = image[:row, j]
            new_image[row:, j] = image[row + 1:, j]
    else:  # 灰度图像
        new_image = np.zeros((H - 1, W), dtype=image.dtype)
        for j in range(W):
            row = seam[j]
            new_image[:row, j] = image[:row, j]
            new_image[row:, j] = image[row + 1:, j]

    return new_image

if __name__ == "__main__":
    image = io.imread('castle.jpg')
    # 定义要移除的接缝数量
    num = 100  # 可以根据需要调整这个值
    current_image = image
    for _ in range(num):
        current_image = reduce(current_image)
    # 保存处理后的图像
    io.imsave('resized_image.jpg', current_image)

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值