引言
最近在专研红外弱小目标检测算法,QDCT发表于2019年,算法也比较经典。由于作者只发布了编译后的MATLAB代码,于是决定动手复现一下。话不多说,直接上干货。
1. 算法框架
2. 特征选择
先从4个独立分量的特征开始。涉及到算法总共有3个,分别是: steerable filter, Kurtosis, Motion
2.1 steerable filter
若多元函数
f
f
f在点
P
0
(
x
1
0
,
x
2
0
,
.
.
.
,
x
n
0
,
)
P_{0}(x_1^0,x_2^0,...,x_n^0,)
P0(x10,x20,...,xn0,) 存在对所有自变量的偏导数,则称向量
g
r
a
d
f
gradf
gradf为函数
f
f
f在点 的梯度,记向量的长度(或模)为
g
r
a
d
f
=
(
f
x
1
(
P
0
)
,
f
x
2
(
P
0
)
,
.
.
.
,
f
x
n
(
P
0
)
)
gradf = (f_{x_1}(P_0),f_{x_2}(P_0),...,f_{x_n}(P_0))
gradf=(fx1(P0),fx2(P0),...,fxn(P0))
∣
g
r
a
d
f
∣
=
[
f
x
1
(
P
0
)
]
2
+
[
f
x
2
(
P
0
)
]
2
+
.
.
.
+
[
f
x
n
(
P
0
)
]
2
|gradf|=\sqrt{[f_{x_1}(P_0)]^2+[f_{x_2}(P_0)]^2+...+[f_{x_n}(P_0)]^2}
∣gradf∣=[fx1(P0)]2+[fx2(P0)]2+...+[fxn(P0)]2
对于函数来说,变化最剧烈的方向即函数在该点的梯度方向,对于一幅灰度图
I
m
(
x
,
y
)
Im(x,y)
Im(x,y)来说,我们可以将其看成是一个二元函数(
p
p
p为像素值,
I
m
Im
Im为原图)
p
=
f
(
r
o
w
s
,
c
o
l
s
)
=
I
m
(
r
o
w
s
,
c
o
l
s
)
p=f(rows,cols)=Im(rows,cols)
p=f(rows,cols)=Im(rows,cols)
边缘是像素值变化相对剧烈的点,且该点的梯度方向是像素值变化最剧烈的方向,我们将与该点梯度方向垂直的方向称为该点的边缘方向。求取图像边缘的过程其实就可以等价于求导,然后筛选导数绝对值较大的位置。图像作为一个二元函数,求导准确来说是求方向导数,沿着不同方向求导,得到的值也不一样。根据多元函数的知识我们可以知道,方向导数其实就是该点梯度与该方向方向余弦的点积(投影)。
v
=
g
r
a
d
f
∗
α
v= gradf * \alpha
v=gradf∗α
所以当求导方向与该点梯度平行时,最能突出该点的变化,如果垂直,则完全忽视了该点的变化。这样的性质在边缘提取中可以用来特意的突出某一方向的边缘,是设计steerable filter 的基础.
假设输入图像为
I
m
(
x
,
y
)
Im(x,y)
Im(x,y),关注的方向角度为
θ
\theta
θ,则
α
=
P
I
/
2
+
θ
\alpha = PI/2+\theta
α=PI/2+θ, steerable filter的方向余弦为:
(
c
o
s
(
α
)
,
s
i
n
(
α
)
)
(cos(\alpha),sin(\alpha))
(cos(α),sin(α)).
d
I
m
d
x
=
c
o
n
v
2
(
I
m
,
g
x
)
\frac{dIm}{dx}=conv2(Im,g_x)
dxdIm=conv2(Im,gx)
d
I
m
d
y
=
c
o
n
v
2
(
I
m
,
g
y
)
\frac{dIm}{dy}=conv2(Im,g_y)
dydIm=conv2(Im,gy)
其中
g
x
,
g
y
g_x,g_y
gx,gy都是沿
x
x
x方向和
y
y
y方向的卷积核,上式给出的是利用卷积求图像偏导的公式,
g
x
,
g
y
g_x,g_y
gx,gy可以是一阶的高斯导数离散化之后的模板,也可以是诸如sobel沿x和y方向的两个卷积核。因此steerable filter的输出为:
g
α
=
c
o
s
(
α
)
∗
g
x
+
s
i
n
(
α
)
∗
g
y
g_{\alpha}=cos(\alpha)*g_x+sin(\alpha)*g_y
gα=cos(α)∗gx+sin(α)∗gy
文章中,作者选了
0
o
+
9
0
o
0^o+90^o
0o+90o和
4
5
o
+
13
5
o
45^o+135^o
45o+135o两个特征。
参考代码如下:
def creat_gauss_kernel(kernel_size=3, sigma=1, k=1):
if sigma == 0:
sigma = ((kernel_size - 1) * 0.5 - 1) * 0.3 + 0.8
X = np.linspace(-k, k, kernel_size)
Y = np.linspace(-k, k, kernel_size)
x, y = np.meshgrid(X, Y)
x0 = 0
y0 = 0
gauss = 1 / (2 * np.pi * sigma ** 2) * np.exp(- ((x - x0) ** 2 + (y - y0) ** 2) / (2 * sigma ** 2))
return gauss
def get_steerablefilter(Im_in, angle, sigma,debug= 0):
# 利用高斯一节差分,求图像的可调滤波器结果
# 输入参数:
# Im_in : input image, gray
# angle : unit/rad range: 0~ 2*pi
# output paramenters:
# Im_out image after steerablefilte
if(Im_in.dtype== 'uint8'):
Im_in = Im_in.astype('float')
gauss = creat_gauss_kernel(3, sigma, 1)
nn = np.zeros_like(gauss)
nn[:, 0] = [1 / sigma **2, 1 / sigma ** 2, 1 / sigma**2]
nn[:, 2] = [-1 / sigma **2, -1 / sigma **2, -1 / sigma ** 2]
gx = np.multiply(nn, gauss) # 矩阵点乘
gy = np.multiply(nn.T, gauss)
# 任意线性滤波器 把图像和卷积核进行卷积 参数2为卷积深度 -1和原图像一样
dst_x = cv2.filter2D(Im_in, -1, gx)
dst_y = cv2.filter2D(Im_in, -1, gy)
Im_out = dst_x * np.cos(angle) + dst_y * np.sin(angle)
if(debug):
if(np.max(dst_x)-np.min(dst_x))>1e-3:
dst_x_save = (dst_x- np.min(dst_x))/(np.max(dst_x)-np.min(dst_x))*255
dst_x_save = dst_x_save.astype('uint8')
else:
dst_x_save = np.zeros_like(Im_in,dtype='uint8')
if (np.max(dst_y) - np.min(dst_y)) > 1e-3:
dst_y_save = (dst_y - np.min(dst_y)) / (np.max(dst_y) - np.min(dst_y)) * 255
dst_y_save = dst_y_save.astype('uint8')
else:
dst_y_save = np.zeros_like(Im_in, dtype='uint8')
if (np.max(Im_out) - np.min(Im_out)) > 1e-3:
Im_out_save = (Im_out - np.min(Im_out)) / (np.max(Im_out) - np.min(Im_out)) * 255
Im_out_save = Im_out_save.astype('uint8')
else:
Im_out_save = np.zeros_like(Im_in, dtype='uint8')
cv2.imwrite('sf_dx'+str(int(angle/math.pi*180))+'.png',dst_x_save,[cv2.IMWRITE_PNG_COMPRESSION, 0])
cv2.imwrite('sf_dy'+str(int(angle/math.pi*180))+'.png', dst_y_save, [cv2.IMWRITE_PNG_COMPRESSION, 0])
cv2.imwrite('sf_out'+str(int(angle/math.pi*180))+'.png', Im_out_save, [cv2.IMWRITE_PNG_COMPRESSION, 0])
return Im_out
2.2 Kurtosis feature map
Kurtosis 实际上计算的目标与周围背景的差异性特征,计算比较简单。其计算公式如下:
k
u
r
(
I
m
(
x
,
y
,
t
)
)
=
K
−
C
=
E
(
X
−
μ
)
4
δ
4
−
C
=
1
m
n
∗
∑
i
=
1
m
n
[
I
m
(
x
,
y
,
t
)
−
μ
]
4
δ
4
−
C
kur(Im(x,y,t))=K-C=\frac{E(X-\mu)^4}{\delta^4}-C\\ \\ =\frac{1}{mn}*\frac{\sum_{i=1}^{mn}[Im(x,y,t)-\mu]^4}{\delta^4}-C
kur(Im(x,y,t))=K−C=δ4E(X−μ)4−C=mn1∗δ4∑i=1mn[Im(x,y,t)−μ]4−C
作者在文中,选取
m
=
n
=
5
m=n=5
m=n=5,
μ
\mu
μ和
δ
\delta
δ分别为每个图像区域的均值和方差,
C
C
C作者取3.
参考代码如下:
def get_kurtosis(Im_in, kernel_size=(5, 5)):
# get the kurtosis feature map of input image Im_in
pad_width = ((kernel_size[0] // 2, kernel_size[0] // 2), (kernel_size[1] // 2, kernel_size[1] // 2))
# 计算 Kurtosis Feature Map
kfm = np.zeros_like(Im_in)
for i in range(pad_width[0][0], Im_in.shape[0] - pad_width[0][1]):
for j in range(pad_width[1][0], Im_in.shape[1] - pad_width[1][1]):
patch = Im_in[i - pad_width[0][0]:i + pad_width[0][1] + 1, j - pad_width[1][0]:j + pad_width[1][1] + 1]
kfm[i, j] = kurtosis(patch.ravel())
return kfm
2.3 motion feature map
在运动特征的计算方面,作者采用了motion charge (MC)的方法,方法计算过程如下:
1、计算相邻帧之间的帧间差分
M
o
v
(
x
,
y
,
t
)
=
{
0
,
i
f
I
m
(
x
,
y
,
t
)
=
I
m
(
x
,
y
,
t
−
1
)
1
,
i
f
I
m
(
x
,
y
,
t
)
≠
I
m
(
x
,
y
,
t
−
1
)
Mov(x,y,t)=\left \{ \begin{aligned} 0,&&if Im(x,y,t)=Im(x,y,t-1)\\ 1, &&if Im(x,y,t) \neq Im(x,y,t-1) \end{aligned} \right.
Mov(x,y,t)={0,1,ifIm(x,y,t)=Im(x,y,t−1)ifIm(x,y,t)=Im(x,y,t−1)
2、计算运动累计变量:
C
h
m
o
v
(
x
,
y
,
t
)
=
{
C
h
m
i
n
,
i
f
M
o
v
(
x
,
y
,
t
)
=
1
C
h
,
i
f
M
o
v
(
x
,
y
,
t
)
=
0
Ch_{mov}(x,y,t)=\left \{ \begin{aligned} Ch_{min},&&if Mov(x,y,t)=1\\ Ch, &&if Mov(x,y,t) =0 \end{aligned} \right.
Chmov(x,y,t)={Chmin,Ch,ifMov(x,y,t)=1ifMov(x,y,t)=0
其中
C
h
=
m
i
n
(
C
h
m
o
v
(
x
,
y
,
t
−
1
)
+
A
,
C
h
m
a
x
)
Ch=min(Ch_{mov}(x,y,t-1)+A, Ch_{max})
Ch=min(Chmov(x,y,t−1)+A,Chmax),
C
h
m
o
v
(
x
,
y
,
0
)
=
C
h
m
a
x
=
255
Ch_{mov}(x,y,0)=Ch_{max}=255
Chmov(x,y,0)=Chmax=255,
C
h
m
i
n
=
0
Ch_{min}=0
Chmin=0
3、对motion charge的结果取反,以突出运动特征。
m
(
x
,
y
,
t
)
=
255
−
C
h
m
o
v
(
x
,
y
,
t
)
m(x,y,t)=255-Ch_{mov}(x,y,t)
m(x,y,t)=255−Chmov(x,y,t)
参考代码如下:
def get_motion_charge(Im_in, Im_in_pre, thr=3, debug =1):
# get the motion charge feature of image
# input parameter
# Im_in ,the image of now
# Im_in_pre, the pre image
# thr : the threshold of two image
image_d= np.abs(Im_in - Im_in_pre)>thr
motion = image_d*255
if debug:
save_scale_img('motion.png',motion)
return motion
3. 四象特征向量
四象的特征向量示意图如下:
q ( x , y , t ) = m ( x , y , t ) + f 1 ( x , y , t ) i + f 2 ( x , y , t ) j + f 3 ( x , y , t ) k q(x,y,t)=m(x,y,t)+f_1(x,y,t)i+f_2(x,y,t)j+f_3(x,y,t)k q(x,y,t)=m(x,y,t)+f1(x,y,t)i+f2(x,y,t)j+f3(x,y,t)k
def constract_quard_feature(Im_in,Im_in_pre,thr,debug= 1):
sigma = 1
f1 = get_steerablefilter(Im_in,0,sigma)+get_steerablefilter(Im_in,math.pi/2,sigma)
f2 = get_steerablefilter(Im_in, math.pi/4, sigma) + get_steerablefilter(Im_in, math.pi*3 / 4, sigma)
f3 = get_kurtosis(Im_in)
f0 = get_motion_charge(Im_in,Im_in_pre,thr)
feature = np.zeros((Im_in.shape[0],Im_in.shape[1],4),dtype='float32')
feature[:,:,0] = f0
feature[:, :, 1] = f1
feature[:, :, 2] = f2
feature[:, :, 3] = f3
if(debug):
save_scale_img('f1.png',f1)
save_scale_img('f2.png',f2)
save_scale_img('f3.png', f3)
save_scale_img('f0.png', f0)
return feature
4. QDCT和IQDCT变换
1、将QDCT应用于q (x、y、t),得到频率特征图Q (u、v、t)。给定四元数q(x,y,t)的分辨率为M×N,其中M,N分别表示图像的长度和宽度。q(x,y,t)的QDCT由表示为:
Q
D
C
T
(
q
(
x
,
y
,
t
)
)
=
Q
(
u
,
v
,
t
)
=
α
u
M
α
v
N
∑
m
=
0
M
−
1
∑
n
=
0
N
−
1
μ
Q
q
(
x
,
y
,
t
)
N
(
u
,
v
,
m
,
n
)
\begin{aligned} QDCT(q(x,y,t))&=Q(u,v,t)\\ &\\ &=\alpha _u^M\alpha _v^N\sum_{m=0}^{M-1} \sum_{n=0}^{N-1}\mu _Qq(x,y,t)N(u,v,m,n) \\ \end{aligned}
QDCT(q(x,y,t))=Q(u,v,t)=αuMαvNm=0∑M−1n=0∑N−1μQq(x,y,t)N(u,v,m,n)
其中:
N
(
u
,
v
,
m
,
n
)
=
c
o
s
[
π
M
(
m
+
1
2
)
μ
]
c
o
s
[
π
N
(
n
+
1
2
)
v
]
N(u,v,m,n)= cos[\frac{π}{M}(m+\frac{1}{2})\mu]cos[\frac{π}{N}(n+\frac{1}{2})v]
N(u,v,m,n)=cos[Mπ(m+21)μ]cos[Nπ(n+21)v]
μ Q = − 1 3 i − 1 3 j − 1 3 k \mu _Q=-\sqrt{\frac{1}{3} }i-\sqrt{\frac{1}{3} }j-\sqrt{\frac{1}{3} }k μQ=−31i−31j−31k
α μ M = { 2 M , μ ≠ 0 1 M , μ = 0 \alpha_\mu^M = \begin{cases} \sqrt{\frac{2}{M}}, & {\mu \neq 0}\\ \sqrt{\frac{1}{M}}, & {\mu = 0} \end{cases} αμM=⎩ ⎨ ⎧M2,M1,μ=0μ=0
α v N = { 2 N , v ≠ 0 1 N , v = 0 \alpha_v^N = \begin{cases} \sqrt{\frac{2}{N}}, & {v \neq 0}\\ \sqrt{\frac{1}{N}}, & {v = 0} \end{cases} αvN=⎩ ⎨ ⎧N2,N1,v=0v=0
def get_multi_quad(a,b):
out = np.zeros_like(a)
out[0] = a[0]*b[0]-a[1]*b[1]-a[2]*b[2]-a[3]*b[3]
out[1] = a[0]*b[1]+a[1]*b[0]
out[2] = a[0]*b[2]+a[2]*b[0]
out[3] = a[0]*b[3]+a[3]*b[0]
return out
def get_qdct(feature,debug = 1):
M = feature.shape[0]
N = feature.shape[1]
q_out = np.zeros_like(feature)
pi= math.pi
b = [0, -np.sqrt(1 / 3), -np.sqrt(1 / 3),-np.sqrt(1 / 3)]
for u in range(M):
if u== 0:
alpha_u = np.sqrt(1/M)
else:
alpha_u = np.sqrt(2/M)
for v in range(N):
if v == 0:
alpha_v = np.sqrt(1 / N)
else:
alpha_v = np.sqrt(2 / N)
for m in range(M):
for n in range(N):
if debug:
print('get_qdct')
print([u,v,m,n])
a = feature[m,n,:]
q_out[u,v,:] = q_out[u,v,:]+alpha_u*alpha_v*np.cos(pi/M*(m+1/2)*u)*np.cos(pi/N*(n+1/2)*v)*get_multi_quad(a,b)
return q_out
2、使用符号函数提取变换区域的显著区域自四元数的符号函数对应于视觉反应,用于删除杂乱背景。
Q
′
=
s
g
n
(
Q
)
=
{
x
0
∣
Q
∣
+
x
1
∣
Q
∣
i
+
x
2
∣
Q
∣
j
+
x
3
∣
Q
∣
k
,
∣
Q
∣
≠
0
0
,
|Q| =0
Q^\prime =sgn(Q)= \begin{cases} \frac{x_0}{|Q|}+\frac{x_1}{|Q|}i+\frac{x_2}{|Q|}j+\frac{x_3}{|Q|}k, & {|Q| \neq 0}\\ 0,& \text{|Q| =0} \end{cases}
Q′=sgn(Q)={∣Q∣x0+∣Q∣x1i+∣Q∣x2j+∣Q∣x3k,0,∣Q∣=0|Q| =0
其中,
x
0
、
x
1
、
x
2
、
x
3
x0、x1、x2、x3
x0、x1、x2、x3分别是Q的四个分量。
∣
Q
∣
是四元数的大小,
∣
Q
∣
=
Q
⋅
Q
‾
。
Q
‾
是
Q
|Q|是四元数的大小,|Q| = \sqrt {Q·\overline Q}。\overline Q是Q
∣Q∣是四元数的大小,∣Q∣=Q⋅Q。Q是Q的复共轭。
def get_normalize_quad(q_in,debug = 1):
M = q_in.shape[0]
N = q_in.shape[1]
q_mod = np.zeros_like(q_in)
for m in range(M):
for n in range(N):
if debug:
print('get_normalize_quad')
print([m,n])
q_m = np.sqrt(np.sum(np.multiply(q_in[m,n,:],q_in[m,n,:])))
if(q_m<1e-5):
q_mod[m,n,:] = [0,0,0,0]
else:
q_mod[m, n, :] =q_in[m,n,:]/q_m
return q_mod
3、应用
I
Q
D
C
T
将
Q
′
IQDCT将Q^\prime
IQDCT将Q′转换回空间域。
q
′
(
x
,
y
,
t
)
=
I
Q
D
C
T
(
s
g
n
(
Q
D
C
T
(
q
(
x
,
y
,
t
)
)
)
)
=
I
Q
D
C
T
(
Q
′
)
=
∑
u
=
0
M
−
1
∑
v
=
0
N
−
1
α
u
M
α
v
N
μ
q
Q
′
(
u
,
v
,
t
)
N
(
u
,
v
,
m
,
n
)
\begin{aligned} q^\prime(x,y,t)&=IQDCT(sgn(QDCT(q(x,y,t))))=IQDCT(Q^\prime)\\ &\\ &=\sum_{u=0}^{M-1} \sum_{v=0}^{N-1}\alpha _u^M\alpha _v^N\mu _qQ^\prime(u,v,t)N(u,v,m,n) \\ \end{aligned}
q′(x,y,t)=IQDCT(sgn(QDCT(q(x,y,t))))=IQDCT(Q′)=u=0∑M−1v=0∑N−1αuMαvNμqQ′(u,v,t)N(u,v,m,n)
def get_iqdct(feature,debug = 1):
# 计算输入的四元数的反四元数变换
M = feature.shape[0]
N = feature.shape[1]
q_out = np.zeros_like(feature)
pi = math.pi
b = [0, -np.sqrt(1 / 3), -np.sqrt(1 / 3), -np.sqrt(1 / 3)]
for m in range(M):
for n in range(N):
for u in range(M):
if u == 0:
alpha_u = np.sqrt(1 / M)
else:
alpha_u = np.sqrt(2 / M)
for v in range(N):
if v == 0:
alpha_v = np.sqrt(1 / N)
else:
alpha_v = np.sqrt(2 / N)
if debug:
print('get_idct')
print([m,n,u,v])
a = feature[u, v, :]
q_out[m, n, :] = q_out[m, n, :] + alpha_u * alpha_v * np.cos(pi / M * (m + 1 / 2) * u) * np.cos(
pi / N * (n + 1 / 2) * v) * get_multi_quad(a, b)
return q_out
4、为了得到更平滑的结果,对
I
Q
D
C
T
IQDCT
IQDCT的结果进行高斯滤波,使用
S
(
x
,
y
,
t
)
S(x,y,t)
S(x,y,t)得到最终重建的目标检测结果。
S
(
x
,
y
,
t
)
=
g
(
x
,
y
)
∗
[
q
′
(
x
,
y
,
t
)
⊙
q
′
‾
(
x
,
y
,
t
)
]
S(x,y,t)=g(x,y)*[q^\prime(x,y,t)\odot\overline{q^\prime}(x,y,t) ]
S(x,y,t)=g(x,y)∗[q′(x,y,t)⊙q′(x,y,t)]
其中
g
(
x
,
y
)
g(x,y)
g(x,y)是一个使用
σ
=
1.5
σ = 1.5
σ=1.5的高斯平滑滤波器。
⊙
\odot
⊙表示元素级乘积。
参考代码如下:
def get_qf_norm(q_in,debug =1):
#计算输入的四元数的模
M = q_in.shape[0]
N = q_in.shape[1]
q_norm = np.zeros((M,N),dtype='float32')
for m in range(M):
for n in range(N):
if debug:
print('get_qf_norm')
print([m,n])
q_norm[m,n]= np.sqrt(np.sum(np.multiply(q_in[m, n, :], q_in[m, n, :])))
return q_norm
整个算法的参考代码如下:
def target_detector_qdct(Im_in,Im_in_pre,thr):
feature = constract_quard_feature(Im_in, Im_in_pre, thr)
q_in = get_qdct(feature)
q_mod = get_normalize_quad(q_in)
q_out = get_iqdct(q_mod)
q_norm = get_qf_norm(q_out)
gauss = creat_gauss_kernel(5, 1.5, 2)
s_out = cv2.filter2D(q_norm, -1, gauss)
save_scale_img('s_out.png',s_out)
return s_out
5、实验结果对比
为了简化实验,实验过程中只选取了两张连续图片。如下所示:
将f0, f1,f2,f3均归一化后,计算过程的结果如下:(PS:qdct算法的计算量相当大,复现的代码的计算效率很低,因此本次实验为将10241024的图像缩放到256256处理后得到的结果。)
实验效果与原作者给出的效果,差异很大。可能是一些细节的处理与作者的不一致,还需要继续优化。也希望跟大家继续探讨。