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)





