HueNet--Differentiable Histogram Loss Functions for Intensity-based Image-to-Image Translation

TMAMI2023代码地址

这个工作2019年就有了:arxiv地址

解决问题

图像到图像翻译问题–>颜色迁移(非配对问题)、图像着色
将不可微分直方图构成成可微,融入神经网络进行处理
在这里插入图片描述

贡献

  1. 一种新的深度学习框架HueNet ,本质是构造了网络输出和源图像之间的联合直方图
  2. 可微直方图构造架构
  3. 新型损失函数:基于直方图的EMD损失、MI损失

网络架构

可微直方图构造

1. 颜色空间选择

  • YUV : Y 通道的范围是 [0,1],U 和 V 通道的范围是 [-0.5, 0.5]。为便于处理,我们将所有通道值映射到[-1, 1]。
  • HSV: 图像的颜色分量(色调)与其明暗度(饱和度)和黑白比例(明度)分离,色调通道是环形

2. 1D 强度直方图的可微构造

  • 核密度估计(KDE)
    用于估计图像通道 I I I的灰度密度 f I f_I fI
    f ^ I ​ ( g ) = 1 N W ​ ∑ x ∈ Ω ​ K ( I ( x ) − g W ​ ) \hat{f}_I​(g)=\frac{1}{NW}​∑_{x∈Ω}​\mathcal{K}(\frac{I(x)−g}{W}​) f^I(g)=NW1xΩK(WI(x)g)
    g ∈ [ − 1 , 1 ] g \in [-1, 1] g[1,1] K ( ⋅ ) \mathcal{K}(\cdot) K()是核函数(逻辑回归函数 σ ( z ) = 1 1 + e − z σ(z)=\frac{1}{1+e^{−z}} σ(z)=1+ez1的导数),W 是带宽, N = ∣ Ω ∣ N=|\Omega| N=∣Ω∣是图像中的像素总数, I ( x ) ∈ [ − 1 , 1 ] I(x) \in [-1, 1] I(x)[1,1]是图像像素 x ∈ Ω x \in \Omega xΩ在某个通道的强度
    K ( z ) = σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \mathcal{K}(z)=σ′(z)=σ(z)(1−σ(z)) K(z)=σ(z)=σ(z)(1σ(z))
  • 归一化直方图bin值(像素属于某个灰度区间概率)
    将区间 [-1, 1] 划分为 K 个子区间 { B k } k = 0 K − 1 \{B_k\}_{k=0}^{K-1} {Bk}k=0K1,每个子区间长度为 L = 2 K L=\frac{2}{K} L=K2,中心为 μ k = − 1 + L ( k + 1 2 ) \mu_k = -1 + L(k + \frac{1}{2}) μk=1+L(k+21)
    P I ​ ( k ) ≜ P r ( g ∈ B k ​ ) = ∫ B k ​​ f ^ ​ I ​ ( g ) d g = 1 N ∑ x ∈ Ω Π k ( I ( x ) ) P_I​(k)≜Pr(g∈B_k​)=∫_{B_k}\hat{​​f}_{​I}​(g)dg= \frac{1}{N}\sum_{x\in\Omega} \Pi_k(I(x)) PI(k)Pr(gBk)=Bk​​f^I(g)dg=N1xΩΠk(I(x)) Π k ( z ) = σ ( z − μ k + L / 2 W ) − σ ( z − μ k − L / 2 W ) \Pi_k(z) = \sigma\left(\frac{z-\mu_k+L/2}{W}\right) - \sigma\left(\frac{z-\mu_k-L/2}{W}\right) Πk(z)=σ(Wzμk+L/2)σ(WzμkL/2)

3. 可微联合直方图公式化

  • 多元 KDE :
    f ^ ​ I 1 ​ , I 2 ​​ ( g 1 ​ , g 2 ​ ) = 1 N ​ ∣ W ∣ − 1 / 2 ∑ x ∈ Ω ​ K ( W − 1 / 2 ( I ( x ) − g ) ) \hat{f}_{​I_1​,I_2}​​(g_1​,g_2​)=\frac{1}{N}​∣W∣^{−1/2}∑_{x∈Ω}\mathcal{​K}(W^{−1/2}(I(x)−g)) f^I1,I2​​(g1,g2)=N1W1/2xΩK(W1/2(I(x)g))
    I ( x ) = [ I 1 , I 2 ] T , g = [ g 1 , g 2 ] T I(x) = [I_1, I_2]^T,g = [g_1, g_2]^T I(x)=[I1,I2]Tg=[g1,g2]T,W 是带宽(平滑)2×2 矩阵, K ( ⋅ , ⋅ ) \mathcal{K}(\cdot, \cdot) K(,)是对称二维核函数。
    对两个图像 I 1 , I 2 I_1, I_2 I1,I2,使用二维核函数,构造联合灰度密度。
    K ( z 1 , z 2 ) = σ ′ ( z 1 ) σ ′ ( z 2 ) W = [ W 0 0 W ] \begin{gathered} \mathcal{K}(z_1, z_2) = \sigma'(z_1)\sigma'(z_2) \\ W = \begin{bmatrix} W & 0 \\ 0 & W \end{bmatrix} \end{gathered} K(z1,z2)=σ(z1)σ(z2)W=[W00W]
  • 联合直方图 bin 的值为: P I 1 , I 2 ( k 1 , k 2 ) = 1 N ∑ x ∈ Ω Π k 1 ( I 1 ( x ) ) Π k 2 ( I 2 ( x ) ) P_{I_1,I_2}(k_1,k_2) = \frac{1}{N}\sum_{x\in\Omega} \Pi_{k_1}(I_1(x))\Pi_{k_2}(I_2(x)) PI1,I2(k1,k2)=N1xΩΠk1(I1(x))Πk2(I2(x))
    通过矩阵乘法高效计算:将激活图展平为 K × N K \times N K×N 矩阵 P 1 , P 2 P_1, P_2 P1,P2,联合直方图 J = 1 N P 1 P 2 T J = \frac{1}{N}P_1P_2^T J=N1P1P2T

4. 直方图层设计

  • 1D 直方图层
    颜色通道 l 的激活图 k 通过将第 k 个激活函数应用于输出图像通道 l 得到
    请添加图片描述

  • 联合直方图层
    每个输出通道生成 K 个激活图后,通过将 H × W H \times W H×W的激活图重塑为 N × 1 N \times 1 N×1向量,构造三个 K × N K \times N K×N矩阵,HueNet联合直方图层执行此构建过程。
    在这里插入图片描述

损失函数

1. 推土机距离损失( L EMD L_{\text{EMD}} LEMD

针对两个强度直方图(输出、参考图像)–>使输出直方图趋近于参考图像直方图

  • 对1D直方图,使用平方EMD,等价于累积分布函数(CDF)的欧氏距离: EMD 2 ( h 1 , h 2 ) = ∑ i = 0 K − 1 ( CDF i ( h 1 ) − CDF i ( h 2 ) ) 2 \text{EMD}^2(h_1, h_2) = \sum_{i=0}^{K-1} \left(\text{CDF}_i(h_1) - \text{CDF}_i(h_2)\right)^2 EMD2(h1,h2)=i=0K1(CDFi(h1)CDFi(h2))2
  • 对环形通道(如HSV的色调通道),引入循环EMD,通过最小化所有循环排列后的EMD距离: EMD Cyclic 2 ( h 1 , h 2 ) = min ⁡ l = 0 , … , K − 1 EMD 2 ( T ( h 1 , l ) , T ( h 2 , l ) ) \text{EMD}_{\text{Cyclic}}^2(h_1, h_2) = \min_{l=0,\ldots,K-1} \text{EMD}^2(T(h_1, l), T(h_2, l)) EMDCyclic2(h1,h2)=l=0,,K1minEMD2(T(h1,l),T(h2,l)) 其中 T ( h , l ) T(h, l) T(h,l)为直方图循环移位操作。
    def emd_loss(self, maps, maps_hat):
        ecdf_p = self.ecdf(maps)  # shape=(batch_size, bin_bum)
        ecdf_p_hat = self.ecdf(maps_hat)  # shape=(batch_size, bin_bum)
        emd = tf.reduce_mean(tf.pow(tf.abs(ecdf_p - ecdf_p_hat), 2), axis=-1)  # shape=(batch_size,1)
        emd = tf.pow(emd, 1 / 2)
        return tf.reduce_mean(emd)  # shape=0

2. 互信息损失( L MI L_{\text{MI}} LMI

约束输出图像与源图像的结构相似性,基于二者的联合直方图计算互信息(MI)
互信息 I ( I 1 , I 2 ) = H ( I 1 ) − H ( I 1 ∣ I 2 ) \mathcal{I}(I_1, I_2) = H(I_1) - H(I_1|I_2) I(I1,I2)=H(I1)H(I1I2)通过最小化条件熵 H ( I 1 ∣ I 2 ) H(I_1|I_2) H(I1I2)实现: H ( I 1 ∣ I 2 ) = − ∑ k 1 , k 2 P I 1 , I 2 ( k 1 , k 2 ) log ⁡ P I 1 , I 2 ( k 1 , k 2 ) P I 2 ( k 2 ) H(I_1|I_2) = -\sum_{k_1,k_2} P_{I_1,I_2}(k_1,k_2) \log \frac{P_{I_1,I_2}(k_1,k_2)}{P_{I_2}(k_2)} H(I1I2)=k1,k2PI1,I2(k1,k2)logPI2(k2)PI1,I2(k1,k2)其中 P I 1 , I 2 P_{I_1,I_2} PI1,I2为联合直方图, P I 2 P_{I_2} PI2为输出图像直方图。

    def calc_cond_entropy_loss(self, maps_x, maps_y):
        '''
        计算MI损失,和公式差不多
        '''
        pxy = tf.matmul(maps_x, maps_y, transpose_a=True) / self.n_pixels
        py = tf.reduce_sum(pxy, 1)
        # calc conditional entropy: H(X|Y)=-sum_(x,y) p(x,y)log(p(x,y)/p(y))
        hy = tf.reduce_sum(tf.math.xlogy(py, py), 1)
        hxy = tf.reduce_sum(tf.math.xlogy(pxy, pxy), [1, 2])
        cond_entropy = hy - hxy
        mean_cond_entropy = tf.reduce_mean(cond_entropy)
        return mean_cond_entropy

3. 对抗损失( L ADV L_{\text{ADV}} LADV

增强生成图像的真实性和自然性,确保颜色分配符合现实语义(如草绿、天蓝)。
配对任务(如边缘→照片)使用条件GAN损失,非配对任务(如颜色迁移)使用无条件GAN损失: a r g min ⁡ G max ⁡ D E x , y [ log ⁡ D ( x , y ) ] + E x , z [ log ⁡ ( 1 − D ( x , G ( x , z ) ) ) ] arg\min_G \max_D \mathbb{E}_{x,y}[\log D(x,y)] + \mathbb{E}_{x,z}[\log(1-D(x,G(x,z)))] argGminDmaxEx,y[logD(x,y)]+Ex,z[log(1D(x,G(x,z)))]

bc_loss_object = tf.keras.losses.BinaryCrossentropy(from_logits=True)

#GAN损失-生成器
gan_loss = bc_loss_object(tf.ones_like(disc_generated_output), disc_generated_output)

#GAN损失-鉴别器
def discriminator_loss(disc_real_output, disc_generated_output):
    # 计算真实图像的损失
    real_loss = bc_loss_object(tf.ones_like(disc_real_output), disc_real_output)

    # 计算生成图像的损失
    generated_loss = bc_loss_object(tf.zeros_like(disc_generated_output), disc_generated_output)

    # 计算判别器的总损失
    total_disc_loss = real_loss + generated_loss

    return total_disc_loss

判别器采用“PatchGAN”结构,关注图像局部补丁的真实性。
L = α L EMD + β L MI + γ L ADV \mathcal{L} = \alpha \mathcal{L}_{\text{EMD}} + \beta \mathcal{L}_{\text{MI}} + \gamma \mathcal{L}_{\text{ADV}} L=αLEMD+βLMI+γLADV α , β , γ \alpha, \beta, \gamma α,β,γ为超参数,通过验证集调优:

  • 颜色迁移和边缘→照片: α = 100 , β = 1 , γ = 1 \alpha=100, \beta=1, \gamma=1 α=100,β=1,γ=1
  • 图像着色: α = 20 , β = 1 , γ = 1 \alpha=20, \beta=1, \gamma=1 α=20,β=1,γ=1

网络结构

在这里插入图片描述

实验结果

颜色迁移任务

  • Oxford flowers102数据集:包含 8189 张花卉图像,训练模型
  • 室内外场景数据集:包含 120 张图像,用于测试跨场景颜色迁移
  • 夏季 - 冬季 Yosemite 数据集:用于跨数据集颜色迁移测试,验证模型对不同颜色分布的适配能力

图像着色

  • 夏季 / 冬季 Yosemite 数据集:输入为灰度图像,输出为夏季或冬季着色效果

边缘->颜色

  • 鞋子数据集包包数据集:用于边缘图到真实图像的翻译,评估生成图像的颜色和结构准确性,与 Pix2Pix 对比。

评价指标

  1. 定量指标
    • 归一化互信息(NMI):衡量输出与源图像的结构相似性,通过联合直方图计算,验证 MI 损失对结构保留的贡献。
    • 均方误差(MSE):评估边缘→照片任务中生成图像与源图像的像素级差异,数值越小表示颜色和结构越接近。
    • 弗雷歇初始距离(FID):度量生成图像与真实图像的统计相似性,值越小表示图像真实性越高。
  2. 定性指标
    • 问卷
    • 主观视觉对比
  3. 损失函数相关指标
    • 推土机距离(EMD):作为颜色相似性损失,约束输出与参考图像的强度直方图距离,确保颜色分布匹配。
    • 互信息(MI):作为结构相似性损失,通过联合直方图最大化输出与源图像的结构依赖,避免颜色变形破坏内容。
    • 对抗损失(Adversarial Loss):通过判别器评估生成图像的真实性,确保颜色分配符合自然语义(如绿色叶子、蓝色天空)。

代码细节

  • 获取直方图
def load_hist(real_image, args):
    '''
    计算三个通道的颜色直方图
    范围由min_val max_val定义,bins数量又bin_num指定
    '''
    hist1 = tf.histogram_fixed_width(real_image[..., 0], [args.min_val, args.max_val], nbins=args.bin_num)
    hist2 = tf.histogram_fixed_width(real_image[..., 1], [args.min_val, args.max_val], nbins=args.bin_num)
    hist3 = tf.histogram_fixed_width(real_image[..., 2], [args.min_val, args.max_val], nbins=args.bin_num)

    return hist1, hist2, hist3
  • 计算损失
def generator_loss(disc_generated_output, gen_output, target, args):
    gan_loss = bc_loss_object(tf.ones_like(disc_generated_output), disc_generated_output)

    # histograms instances
    hist_1 = HistogramLayers(out=gen_output[..., 0], tar=target[..., 0], args=args)
    hist_2 = HistogramLayers(out=gen_output[..., 1], tar=target[..., 1], args=args)
    hist_3 = HistogramLayers(out=gen_output[..., 2], tar=target[..., 2], args=args)

    # mi loss
    mi_loss_1 = hist_1.calc_cond_entropy_loss_tar_out()
    mi_loss_2 = hist_2.calc_cond_entropy_loss_tar_out()
    mi_loss_3 = hist_3.calc_cond_entropy_loss_tar_out()

    mi_loss = (mi_loss_1 + mi_loss_2 + mi_loss_3) / 3

    # hist loss
    hist_loss_1 = hist_1.calc_hist_loss_tar_out()
    hist_loss_2 = hist_2.calc_hist_loss_tar_out()
    hist_loss_3 = hist_3.calc_hist_loss_tar_out()

    hist_loss = (hist_loss_1 + hist_loss_2 + hist_loss_3) / 3

    total_gen_loss = (args.gan_loss_weight * gan_loss) + (args.mi_loss_weight * mi_loss) + (
                args.hist_loss_weight * hist_loss)

    return total_gen_loss, gan_loss, mi_loss, hist_loss
  • 计算直方图的类
#只写了一部分
class HistogramLayers(object):
    """Network augmentation for 1D and 2D (Joint) histograms construction,
    Calculate Earth Mover's Distance, Mutual Information loss
    between output and target
    """

    def __init__(self, out, tar, args):
        """
        初始化类的构造函数。

        参数:
        - out: 模型的输出,用于计算激活图。
        - tar: 目标输出,用于计算激活图。
        - args: 包含多个参数的命名空间,包括bin_num, min_val, max_val和kernel_width_ratio。

        该构造函数初始化了多个属性,包括每个区间的长度、核宽度、激活图等。
        """
        # 初始化区间数量、最小值和最大值
        self.bin_num = args.bin_num
        self.min_val = args.min_val
        self.max_val = args.max_val

        # 计算每个区间长度
        self.interval_length = (self.max_val - self.min_val) / self.bin_num

        # 根据区间长度和核宽度比计算核宽度
        self.kernel_width = self.interval_length / args.kernel_width_ratio

        # 计算输出和目标的激活图
        self.maps_out = self.calc_activation_maps(out)
        self.maps_tar = self.calc_activation_maps(tar)

        # 获取图像中像素的数量(H*W)
        self.n_pixels = self.maps_out.get_shape().as_list()[1]

        # 获取batch size
        self.bs = self.maps_out.get_shape().as_list()[0]

    def calc_activation_maps(self, img):
        '''
        计算激活图
        '''
        # apply approximated shifted rect (bin_num) functions on img
        bins_min_max = np.linspace(self.min_val, self.max_val, self.bin_num + 1) #生成bin_num+1个等间距的值,[min_val,max_val]
        bins_av = (bins_min_max[0:-1] + bins_min_max[1:]) / 2
        bins_av = tf.constant(bins_av, dtype=tf.float32)  # shape = (,bin_num) float64转换为float32
        bins_av = tf.expand_dims(bins_av, axis=0)  # shape = (1,bin_num)
        bins_av = tf.expand_dims(bins_av, axis=0)  # shape = (1,1,bin_num)
        img_flat = tf.expand_dims(tf.keras.layers.Flatten()(img), axis=-1) #img展平之后末尾添加一个维度
        maps = self.activation_func(img_flat, bins_av)  # shape = (batch_size,H*W,bin_num)
        return maps

    def activation_func(self, img_flat, bins_av):
        """
        自定义激活函数,用于处理输入的图像数据并将其转换为特定的特征表示。

        参数:
        img_flat: 输入的图像数据,经过展平处理,形状为(batch_size, H*W, bin_num)。
        bins_av: 用于计算的平均值向量,形状与img_flat相匹配。

        返回:
        maps: 处理后的特征表示,用于后续的神经网络层。
        """
        # 计算图像数据与平均值的差值
        img_minus_bins_av = tf.subtract(img_flat, bins_av)  # shape=  (batch_size,H*W,bin_num)
        # 计算图像数据与平均值的和
        img_plus_bins_av = tf.add(img_flat, bins_av)  # shape = (batch_size,H*W,bin_num)

        # 计算最终的特征表示
        maps = tf.math.sigmoid((img_minus_bins_av + self.interval_length / 2) / self.kernel_width) \
               - tf.math.sigmoid((img_minus_bins_av - self.interval_length / 2) / self.kernel_width) \
               + tf.math.sigmoid((img_plus_bins_av - 2 * self.min_val + self.interval_length / 2) / self.kernel_width) \
               - tf.math.sigmoid((img_plus_bins_av - 2 * self.min_val - self.interval_length / 2) / self.kernel_width) \
               + tf.math.sigmoid((img_plus_bins_av - 2 * self.max_val + self.interval_length / 2) / self.kernel_width) \
               - tf.math.sigmoid((img_plus_bins_av - 2 * self.max_val - self.interval_length / 2) / self.kernel_width)
        return maps
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值