OpenCV实现基于交叉双边滤波的红外可见光融合算法

1 算法原理

CBF是*Cross Bilateral Filter(交叉双边滤波)*的缩写,论文《IMAGE FUSION BASED ON PIXEL SIGNIFICANCE USING CROSS BILATERAL FILTER》。

论文中,作者使用交叉双边滤波算法对原始图像 A A A, B B B 进行处理得到细节(detail)层,通过细节层得到权重系数,最后经过权重系数对原始图像 A A A, B B B进行融合,得到最终的输出图像 F F F 。

2 算法实现

2.1 交叉双边滤波算法

双边波算法是局部的,非线性、非迭代的方法,使用高斯滤波器和颜色滤波器来对图像进行处理, 兼具平滑和保持边界的功能。这在我之前的专栏中有提到过,我在这就不在细说双边滤波,接下来说一下文中提到的交叉双边滤波。

交叉双边滤波算法考虑了 A A A图像的灰度层的相似性和几何层的闭合性来对 B B B 图像进行过滤,或者使用 B B B图像来对 A A A 图像进行过滤,输入为 A A A, B B B 两幅输入图像,假设使用 A A A 图像对 B B B图像进行过滤,论文中 σ s = 1.8 , σ r = 25 \sigma_s=1.8,\sigma_r=25 σs=1.8,σr=25 ,CBFlv滤波窗口大小为 11 ∗ 11 11*11 11∗11 那么:

B C B F ( p ) = 1 W ∑ q ∈ S ( e − ∣ p − q ∣ 2 2 σ s 2 ∗ e − ∣ A ( p ) − A ( q ) ∣ 2 2 σ r 2 ∗ B ( q ) ) B_{CBF}(p)=\frac{1}{W}\sum_{q\in S}(e^{-\frac{|p-q|^2}{2\sigma_s^2}}*e^{-\frac{|A(p)-A(q)|^2}{2\sigma_r^2}}*B(q)) BCBF(p)=W1q∈S∑(e−2σs2∣p−q∣2∗e−2σr2∣A(p)−A(q)∣2∗B(q))
W = ∑ q ∈ S e − ∣ p − q ∣ 2 2 σ s 2 ∗ e − ∣ A ( p ) − A ( q ) ∣ 2 2 σ r 2 W=\sum_{q\in S}e^{-\frac{|p-q|^2}{2\sigma_s^2}}*e^{-\frac{|A(p)-A(q)|^2}{2\sigma_r^2}} W=q∈S∑e−2σs2∣p−q∣2∗e−2σr2∣A(p)−A(q)∣2

同理,对于 A C B F ( p ) A_{CBF}(p) ACBF(p) 使用的是 B B B图像来对 A A A 图像进行过滤,由 $B_{CBF}、 A_{CBF} $以及原始图像 A A A 、 B B B ,在原始图像的基础上减去交叉双边滤波得到的图像就是细节层图像,
A D = A − A C B F , B D = B − B C B F A_D=A-A_{CBF},B_D=B-B_{CBF} AD=A−ACBF,BD=B−BCBF
文中作者给出交叉双边滤波有用的解释是:在多聚焦图像中, A A A 图像未聚焦的区域在图像 B B B 中是聚焦的区域,对图像 B B B 进行交叉双边滤波会使得对图像 B B B 的聚焦区域平滑的比图像 B B B 的非聚焦区域更强,原因是:图像 A A A 中的非聚焦区域由相近的颜色会使得过滤器更像是高斯过滤器。

最上面两幅为原始图像,中间两幅为CBF滤波后的图像,最下面两幅就是由原始图像-CBF图像得到的图像

2.2基于像素的融合策略

接下来的一步是使用细节图像计算融合权重。

选定窗口大小为 w ∗ w w∗w w∗w(论文中使用 5 ∗ 5 5*5 5∗5),来方便计算权重,这个窗口划过的区域看做矩阵 $C_h^{i,j}C_h^{i,j} $的无偏估计:

c o v a r i a n c e ( X ) = E [ ( X − E [ X ] ) ( X − E ( X ) ) T ] covariance(X)=E[(X-E[X])(X-E(X))^T] covariance(X)=E[(X−E[X])(X−E(X))T]

C h i , j = ∑ k = 1 w ( x k − x ˉ ) ( x k − x ˉ ) T w − 1 C_h^{i,j}=\frac{\sum_{k=1}^w(x_k-\bar{x})(x_k-\bar{x})^T}{w-1} Chi,j=w−1∑k=1w(xk−xˉ)(xk−xˉ)T

得到 C h i , j C_h^{i,j} Chi,j 大小为 w ∗ w w*w w∗w,然后计算矩阵 C h i , j C_h^{i,j} Chi,j 的特征值,并且将这些特征值相加就得到了水平方向区域细节强度,并记为 H d e t a i l S t r e n g t h HdetailStrength HdetailStrength :
H d e t a i l S t r e n g t h ( i , j ) = ∑ k = 1 w e i g e n k HdetailStrength(i,j)=\sum_{k=1}^weigen_k HdetailStrength(i,j)=k=1∑weigenk

同理将 X X X的每一列看做观察量,将 X X X的行看做是变量,计算协方差矩阵 C v i , j C_v^{i,j} Cvi,j,并计算垂直方向区域细节强度 V d e t a i l S t r e n g t h VdetailStrength VdetailStrength :

V d e t a i l S t r e n g t h ( i , j ) = ∑ k = 1 w e i g e n k VdetailStrength(i,j)=\sum_{k=1}^weigen_k VdetailStrength(i,j)=∑k=1weigenk$

最终的权重为,水平和垂直方向的累加和:
w t ( i , j ) = H d e t a i l S t r e n g t ( i , j ) + V d e t a i l S t r e n g t ( i , j ) wt(i,j)=HdetailStrengt(i,j)+VdetailStrengt(i,j) wt(i,j)=HdetailStrengt(i,j)+VdetailStrengt(i,j)

得到 A A A 、 B B B 图像的权重系数后,就可以按照权重系数就行融合:

F ( i , j ) = A ( i , j ) w t a ( i , j ) + B ( i , j ) w t b ( i , j ) w t a ( i , j ) + w t b ( i , j ) F(i,j)=\frac{A(i,j)wt_a(i,j)+B(i,j)wt_b(i,j)}{wt_a(i,j)+wt_b(i,j)} F(i,j)=wta(i,j)+wtb(i,j)A(i,j)wta(i,j)+B(i,j)wtb(i,j)

3 opencv实现代码

python 复制代码
import numpy as np
import cv2
import argparse
import math

cov_wsize = 5
sigmas = 1.8
sigmar = 25
ksize = 11

def gaussian_kernel_2d_opencv(kernel_size = 11,sigma = 1.8):
    kx = cv2.getGaussianKernel(kernel_size,sigma)
    ky = cv2.getGaussianKernel(kernel_size,sigma)
    return np.multiply(kx,np.transpose(ky)) 

def bilateralFilterEx(img_r, img_v):
    #edge solved
    win_size = ksize//2
    img_r_copy = None
    img_v_copy = None
    img_r_copy = cv2.copyTo(img_r, None)
    img_v_copy = cv2.copyTo(img_v, None)
    img_r_cbf = np.ones_like(img_r, dtype=np.float32)
    img_v_cbf = np.ones_like(img_r, dtype=np.float32)
    img_r_copy = np.pad(img_r_copy, (win_size, win_size), 'reflect')
    img_v_copy = np.pad(img_v_copy, (win_size, win_size), 'reflect')
    gk = gaussian_kernel_2d_opencv()
    for i in range(win_size, win_size+img_r.shape[0]):
        for j in range(win_size, win_size+img_r.shape[1]):
            sumr1 = 0.
            sumr2 = 0.
            sumv1 = 0.
            sumv2 = 0.
            img_r_cdis = img_r_copy[i-win_size:i+win_size+1, j-win_size:j+win_size+1] *1.0- img_r_copy[i,j]*1.0
            img_v_cdis = img_v_copy[i-win_size:i+win_size+1, j-win_size:j+win_size+1] *1.0- img_v_copy[i,j]*1.0
            sumr1 = np.sum(np.exp(-img_v_cdis*img_v_cdis) *gk/ (2*sigmar*sigmar) )
            sumv1 = np.sum(np.exp(-img_r_cdis*img_r_cdis) *gk/ (2*sigmar*sigmar) )
            sumr2 = np.sum(np.exp(-img_v_cdis*img_v_cdis) *gk*img_r_copy[i-win_size:i+win_size+1, j-win_size:j+win_size+1] *1.0/ (2*sigmar*sigmar) )
            sumv2 = np.sum(np.exp(-img_r_cdis*img_r_cdis) *gk*img_v_copy[i-win_size:i+win_size+1, j-win_size:j+win_size+1] *1.0/ (2*sigmar*sigmar) )
            img_r_cbf[i-win_size,j-win_size] = sumr2 / sumr1
            img_v_cbf[i-win_size,j-win_size] = sumv2 / sumv1
    return (img_r*1. - img_r_cbf, img_v*1. - img_v_cbf)

def CBF_WEIGHTS(img_r_d, img_v_d):
    win_size = cov_wsize // 2
    img_r_weights = np.ones_like(img_r_d, dtype=np.float32)
    img_v_weights= np.ones_like(img_v_d, dtype=np.float32)
    img_r_d_pad = np.pad(img_r_d, (win_size, win_size), 'reflect')
    img_v_d_pad = np.pad(img_v_d, (win_size, win_size), 'reflect')
    for i in range(win_size, win_size+img_r_d.shape[0]):
        for j in range(win_size, win_size+img_r_d.shape[1]):
            npt_r = img_r_d_pad[i-win_size:i+win_size+1, j-win_size:j+win_size+1]
            npt_v = img_v_d_pad[i-win_size:i+win_size+1, j-win_size:j+win_size+1]
            npt_r_V = npt_r - np.mean(npt_r, axis=0)
            npt_r_V = npt_r_V*npt_r_V.transpose()
            npt_r_H = npt_r.transpose() - np.mean(npt_r, axis=1)
            npt_r_H = npt_r_H*npt_r_H.transpose()
            npt_v_V = npt_v - np.mean(npt_v, axis=0)
            npt_v_V = npt_v_V*npt_v_V.transpose()
            npt_v_H = npt_v.transpose() - np.mean(npt_v, axis=1)
            npt_v_H = npt_v_H*npt_v_H.transpose()
            img_r_weights[i-win_size,j-win_size] = np.trace(npt_r_H) + np.trace(npt_r_V) 
            img_v_weights[i-win_size,j-win_size] = np.trace(npt_v_H) + np.trace(npt_v_V) 
    return img_r_weights, img_v_weights

def CBF_GRAY(img_r, img_v):
    img_r_d, img_v_d = bilateralFilterEx(img_r, img_v)
    img_r_weights, img_v_weights = CBF_WEIGHTS(img_r_d, img_v_d)
    img_fused =(img_r*1. * img_r_weights + img_v*1.*img_v_weights) /(img_r_weights+img_v_weights)
    img_fused = cv2.convertScaleAbs(img_fused)
    return img_fused

def CBF_RGB(img_r, img_v):
    img_r_gray = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY)
    img_v_gray = cv2.cvtColor(img_v, cv2.COLOR_BGR2GRAY)
    return CBF_GRAY(img_r_gray, img_v_gray)

def CBF(_rpath, _vpath):
    img_r = cv2.imread(_rpath)
    img_v = cv2.imread(_vpath)
    if not isinstance(img_r, np.ndarray) :
        print('img_r is null')
        return
    if not isinstance(img_v, np.ndarray) :
        print('img_v is null')
        return
    if img_r.shape[0] != img_v.shape[0]  or img_r.shape[1] != img_v.shape[1]:
        print('size is not equal')
        return
    fused_img = None
    if len(img_r.shape)  < 3 or img_r.shape[2] ==1:
        if len(img_v.shape)  < 3 or img_v.shape[-1] ==1:
            fused_img = CBF_GRAY(img_r, img_v)
        else:
            img_v_gray = cv2.cvtColor(img_v, cv2.COLOR_BGR2GRAY)
            fused_img = CBF_GRAY(img_r, img_v)
    else:
        if len(img_v.shape)  < 3 or img_v.shape[-1] ==1:
            img_r_gray = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY)
            fused_img = CBF_GRAY(img_r_gray, img_v)
        else:
            fused_img = CBF_RGB(img_r, img_v)
    cv2.imshow('fused image', fused_img)

    cv2.imwrite("fused_image_2.jpg", fused_img)
    cv2.waitKey(0)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-r', type=str, default='ir2.png' ,help='input IR image path', required=False)
    parser.add_argument('-v', type=str, default= 'vr2.png',help='input Visible image path', required=False)
    args = parser.parse_args()
    CBF(args.r, args.v)



相关推荐
AI技术控1 小时前
计算机视觉算法实战——吸烟人员检测
计算机视觉
Ckyeka1 小时前
Leetcode刷题笔记—栈与队列
数据结构·python·算法·leetcode
大丈夫立于天地间1 小时前
OSPF - 特殊报文与ospf的机制
网络·网络协议·学习·算法·智能路由器·信息与通信
夏末秋也凉2 小时前
力扣-数组-219 存在重复元素Ⅱ
算法·leetcode
Wang's Blog2 小时前
数据结构与算法之二叉树: LeetCode 543. 二叉树的直径 (Ts版)
算法·leetcode
graceyun2 小时前
C语言初阶习题【23】输出数组的前5项之和
c语言·开发语言·算法
Wang's Blog2 小时前
数据结构与算法之二叉树: LeetCode 701. 二叉搜索树中的插入操作 (Ts版)
算法·leetcode
夏末秋也凉2 小时前
力扣-数组-169 多数元素
数据结构·算法·leetcode
戊子仲秋2 小时前
【LeetCode】每日一题 2024_1_10 统计重新排列后包含另一个字符串的子字符串数目 II(滑动窗口)
算法·leetcode·职场和发展
KeyPan2 小时前
【Ubuntu与Linux操作系统:九、Shell编程】
linux·运维·服务器·算法·ubuntu