TMAMI2023代码地址
这个工作2019年就有了:arxiv地址
解决问题
图像到图像翻译问题–>颜色迁移(非配对问题)、图像着色
将不可微分直方图构成成可微,融入神经网络进行处理

贡献
- 一种新的深度学习框架HueNet ,本质是构造了网络输出和源图像之间的联合直方图
- 可微直方图构造架构
- 新型损失函数:基于直方图的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+e−z1的导数),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=0K−1,每个子区间长度为 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(g∈Bk)=∫Bkf^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−μk−L/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)=N1∣W∣−1/2x∈Ω∑K(W−1/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]T,g=[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=0∑K−1(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,…,K−1minEMD2(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(I1∣I2)通过最小化条件熵
H
(
I
1
∣
I
2
)
H(I_1|I_2)
H(I1∣I2)实现:
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(I1∣I2)=−k1,k2∑PI1,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(1−D(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 对比。
评价指标
- 定量指标
- 归一化互信息(NMI):衡量输出与源图像的结构相似性,通过联合直方图计算,验证 MI 损失对结构保留的贡献。
- 均方误差(MSE):评估边缘→照片任务中生成图像与源图像的像素级差异,数值越小表示颜色和结构越接近。
- 弗雷歇初始距离(FID):度量生成图像与真实图像的统计相似性,值越小表示图像真实性越高。
- 定性指标
- 问卷
- 主观视觉对比
- 损失函数相关指标
- 推土机距离(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

1277

被折叠的 条评论
为什么被折叠?



