この記事は、Dr. He Yuming の Single Image Haze Removal Using Dark Channel Prior と Guided Image Filtering の 2 つの論文に基づく、かすみ除去アルゴリズムの Python コード実装に基づいています。
1 基本的な定義
1.1 霧画像撮像モデル
I(x) は元のイメージ、J(x) は霧のないイメージ、A は定数である大気光成分です。t(x) は光の透過率です。
その意味は、画像 I(x) は、霧が減衰した後に物体から反射された光と、霧によって反射された大気光の合成によって形成される画像であるということです。
1.2 ダークチャネルの定義
1.3 投影マップ t(x) の計算
これは、多数の写真からの著者の結論です。つまり、霧のない写真のダーク チャネルは 0 に近いです。
実際、晴れた日でも、大気中に粒子が完全にないわけではありません。そのため、遠くの物体を見ると、霧がまだそこにあります。したがって、紙の値が 0.95 の定数パラメーター w (0<w<1) を導入することで、遠くのオブジェクトの少量の霧を保持できます。
このように、t(x)が以前よりも大きくなり、大気光の重みが小さくなり、画像全体が暗くなるのでは?
ダーク チャネル プライアは、空の領域には適切なプライアではありません。幸いなことに、かすんでいる画像 I では、通常、空の色は大気光 A の色と非常に似ています。つまり、次の式が 1 に近づきます。したがって、t(x) はこれらの位置で 0 になる傾向があります。空は無限に遠いため、その透過率は実際にはゼロに近いです。したがって、空の領域を事前に分離する必要はありません。
1.4 ガイド付きフィルタリング
詳細については、別の記事Guided filtering and opencv python implementationを参照してください。
2コード
2.1 ダークチャンネルを取得する
def get_min_channel(img):
return np.min(img,axis=2)
実際、最小フィルターは腐食と同じです。
def min_filter(img,r):
kernel = np.ones((2*r-1,2*r-1))
return cv2.erode(img,kernel)#最小值滤波器,可用腐蚀替代
2.2 大気照度 A の計算
通常、霧が最も濃い場所の色が大気光の推定値として使用されます. 暗いチャンネルを使用して、霧が最も濃い場所を特定できます, つまり、暗いチャンネルの最も明るい領域が場所です.この時点では、大気光が唯一の光源です。
まず、ダーク チャネルで最も明るい 0.1% のピクセルを選択します。これらのピクセルは通常、最も不透明です。これらのピクセルのうち、入力画像 I で最も強度の高いピクセルが大気光として選択されます。
def get_A(img_haze,dark_channel,bins_l):
hist,bins = np.histogram(dark_channel,bins=bins_l)#得到直方图
d = np.cumsum(hist)/float(dark_channel.size)#累加
# print(bins)
threshold=0
for i in range(bins_l-1,0,-1):
if d[i]<=0.999:
threshold=i
break
A = img_haze[dark_channel>=bins[threshold]].max()
#候选区域可视化
show = np.copy(img_haze)
show[dark_channel>=bins[threshold]] = 0,0,255
cv2.imwrite('./most_haze_opaque_region.jpg',show*255)
return A
ダーク チャネルの最も明るい領域は、下の図の赤い領域です。
2.3 t(x)の計算
def get_t(img_haze,A,t0=0.1,w=0.95):
out = get_min_channel(img_haze)
out = min_filter(out,r=7)
t = 1-w*out/A #需要乘上一系数w,为远处的物体保留少量的雾
t = np.clip(t,t0,1)#论文4.4所提到t(x)趋于0容易产生噪声,所以设置一最小值0.1
return t
このとき得られた結果は下図のようになります。
ガイド付きフィルタリングによって伝送が洗練されていないため、効果は少し悪いです。
2.4 ガイド付きフィルタリング
最小値でフィルタリングされていないダーク チャネル画像 (ダーク チャネル画像を使用する効果は非常に低くなります) をガイド付き画像として使用して、t(x) でガイド付きフィルタリングを実行し、透過率を調整します。
入力画像は処理前に正規化する必要があります。そうしないと、フィルタリング後に 255 を超える値が存在します。
def guided_filter(I,p,win_size,eps):
mean_I = cv2.blur(I,(win_size,win_size))
mean_p = cv2.blur(p,(win_size,win_size))
corr_I = cv2.blur(I*I,(win_size,win_size))
corr_Ip = cv2.blur(I*p,(win_size,win_size))
var_I = corr_I-mean_I*mean_I
cov_Ip = corr_Ip - mean_I*mean_p
a = cov_Ip/(var_I+eps)
b = mean_p-a*mean_I
mean_a = cv2.blur(a,(win_size,win_size))
mean_b = cv2.blur(b,(win_size,win_size))
q = mean_a*I + mean_b
return q
得られた結果を次の図に示します。
元の画像と比較すると、
曇り止め効果はまだ良好であることがわかります。
2.5 評価
PSNR
def PSNR(target,ref):
#必须归一化
target=target/255.0
ref=ref/255.0
MSE = np.mean((target-ref)**2)
if MSE<1e-10:
return 100
MAXI=1
PSNR = 20*math.log10(MAXI/math.sqrt(MSE))
return PSNR
SSIM
from skimage.metrics import structural_similarity as sk_cpt_ssim
ssim = sk_cpt_ssim(J,I*255, win_size=11, data_range=255, multichannel=True)
2.6 完全なコード
# -*- coding: utf-8 -*-
# @Time : 2022/10/1 23:08
# @Author : shuoshuo
# @File : main.py
# @Project : 去雾
import cv2
import numpy as np
import matplotlib.pyplot as plt
import math
from skimage.metrics import structural_similarity as sk_cpt_ssim
def guided_filter(I,p,win_size,eps):
mean_I = cv2.blur(I,(win_size,win_size))
mean_p = cv2.blur(p,(win_size,win_size))
corr_I = cv2.blur(I*I,(win_size,win_size))
corr_Ip = cv2.blur(I*p,(win_size,win_size))
var_I = corr_I-mean_I*mean_I
cov_Ip = corr_Ip - mean_I*mean_p
a = cov_Ip/(var_I+eps)
b = mean_p-a*mean_I
mean_a = cv2.blur(a,(win_size,win_size))
mean_b = cv2.blur(b,(win_size,win_size))
q = mean_a*I + mean_b
return q
def get_min_channel(img):
return np.min(img,axis=2)
def min_filter(img,r):
kernel = np.ones((2*r-1,2*r-1))
return cv2.erode(img,kernel)#最小值滤波器,可用腐蚀替代
def get_A(img_haze,dark_channel,bins_l):
hist,bins = np.histogram(dark_channel,bins=bins_l)#得到直方图
d = np.cumsum(hist)/float(dark_channel.size)#累加
# print(bins)
threshold=0
for i in range(bins_l-1,0,-1):
if d[i]<=0.999:
threshold=i
break
A = img_haze[dark_channel>=bins[threshold]].max()
#候选区域可视化
show = np.copy(img_haze)
show[dark_channel>=bins[threshold]] = 0,0,255
cv2.imwrite('./most_haze_opaque_region.jpg',show*255)
return A
def get_t(img_haze,A,t0=0.1,w=0.95):
out = get_min_channel(img_haze)
out = min_filter(out,r=7)
t = 1-w*out/A #需要乘上一系数w,为远处的物体保留少量的雾
t = np.clip(t,t0,1)#论文4.4所提到t(x)趋于0容易产生噪声,所以设置一最小值0.1
return t
def PSNR(target,ref):
#必须归一化
target=target/255.0
ref=ref/255.0
MSE = np.mean((target-ref)**2)
if MSE<1e-10:
return 100
MAXI=1
PSNR = 20*math.log10(MAXI/math.sqrt(MSE))
return PSNR
if __name__ == '__main__':
I = cv2.imread('test.jpg')/255.0
dark_channel = get_min_channel(I)
dark_channel_1 = min_filter(dark_channel,r=7)
# cv2.imwrite("./dark_channel.jpg", dark_channel_1*255)
A = get_A(I,dark_channel_1,bins_l=2000)
t = get_t(I,A)
t = guided_filter(dark_channel,t,81,0.001)
t = t[:,:,np.newaxis].repeat(3,axis=2)#升维至(r,w,3)
J = (I-A)/t +A
J = np.clip(J,0,1)
J = J*255
J =np.uint8(J)
cv2.imwrite("./result.jpg",J)
#评估
PSNR = PSNR(J,I*255)
print(f"PSNR:{
PSNR}")
ssim = sk_cpt_ssim(J,I*255, win_size=11, data_range=255, multichannel=True)
print(f"ssim:{
ssim}")
3 参考文献
He Yuming による 2 つの論文:
Single Image Haze Removal Using Dark Channel Prior
Guided Image Filtering
Paper link
Dehazing for Image and Video Using Guided Filter