创新实训第一周团队博客
本周主要确定了大致的前端界面框架和后端接口,初步搭建了PSGAN的网络结构,得到初步训练结果。具体实现和工作参考团队成员博客。
App功能设想:
登录注册界面;
主界面是美妆社区,有用户关注的人分享的美妆,也有一些随机的推荐美妆,需要可以分辨出是关注的人还是推荐,分享的美妆类似qq说说,有图片、文字、点赞和评论;
还要有一个界面,美妆示例榜,10(或更多)个推荐,每个条目显示的信息有比如某某明星,还有这个美妆图的缩略图;
还有一个上妆的界面,先是用户上传自己的照片,然后可以选择示例美妆,或者在相册中用自己的美妆图,还要有滑动条表示要求浓淡的程度;选择美妆图时,可以先让用户选择这张美妆图时用全部妆容,还是只是挑选其某个部分(只能选择眼影、嘴唇、面部三个部分);
最后应该要有一个用户的个人主页界面吧,可以看和修改自己的一些个人信息,也可以查看关注的人之类的。
后端框架设计
数据库:Mysql、Redis
数据库表目前主要有以下几个:
用户表:存储用户信息
动态表:存储用户动态
评论表:存储用户评论
模型图片表:存储系统提供的模型图片
收藏表:存储用户收藏的图片
美妆历史表:存储用户的美妆记录
用户之间的关注与被关注关系使用 redis 的 hashmap 存储,每个用户对应两个 hashmap,分别用来记录关注了谁和被谁关注.
动态的点赞使用 redis 的 hashmap 存储,每条动态对应一个 hashmap,记录点赞者与点赞时间.
浏览记录使用 redis 的 zset 存储,每个用户使用一个 zset,以浏览时间作为分数,上限为 1000.
为了提高用户体验和并发性,使用 RabbitMQ 消息队列进行异步通信来进行数据库操作。
PSGAN 初步搭建与训练
阅读了妆容迁移的相关论文,比如PSGAN、BeautyGAN、SPADE、StarGAN v2等,并使用PyTorch初步复现PSGAN网络结构。
网络结构部分代码如下:
# Makeup Apply Network(MANet)
class Generator(nn.Module):
"""Generator. Encoder-Decoder Architecture."""
def __init__(self, conv_dim=64, repeat_num=6):
super(Generator, self).__init__()
encoder_layers = []
encoder_layers.append(nn.Conv2d(3, conv_dim, kernel_size=7, stride=1, padding=3, bias=False))
# MANet设置没有affine
encoder_layers.append(nn.InstanceNorm2d(conv_dim, affine=False))
encoder_layers.append(nn.ReLU(inplace=True))
# Down-Sampling
curr_dim = conv_dim
for i in range(2):
encoder_layers.append(nn.Conv2d(curr_dim, curr_dim * 2, kernel_size=4, stride=2, padding=1, bias=False))
encoder_layers.append(nn.InstanceNorm2d(curr_dim * 2, affine=False))
encoder_layers.append(nn.ReLU(inplace=True))
curr_dim = curr_dim * 2
# Bottleneck
for i in range(3):
encoder_layers.append(ResidualBlock(dim_in=curr_dim, dim_out=curr_dim))
decoder_layers = []
for i in range(3):
decoder_layers.append(ResidualBlock(dim_in=curr_dim, dim_out=curr_dim))
# Up-Sampling
for i in range(2):
decoder_layers.append(
nn.ConvTranspose2d(curr_dim, curr_dim // 2, kernel_size=4, stride=2, padding=1, bias=False))
decoder_layers.append(nn.InstanceNorm2d(curr_dim // 2, affine=True))
decoder_layers.append(nn.ReLU(inplace=True))
curr_dim = curr_dim // 2
decoder_layers.append(nn.Conv2d(curr_dim, 3, kernel_size=7, stride=1, padding=3, bias=False))
decoder_layers.append(nn.Tanh())
self.encoder = nn.Sequential(*encoder_layers)
self.decoder = nn.Sequential(*decoder_layers)
self.MDNet = MDNet()
self.AMM = AMM()
def forward(self, source_image, reference_image):
fm_source = self.encoder(source_image)
fm_reference = self.MDNet(reference_image)
morphed_fm = self.AMM(fm_source, fm_reference)
result = self.decoder(morphed_fm)
return result
class MDNet(nn.Module):
"""Generator. Encoder-Decoder Architecture."""
# MDNet is similar to the encoder of StarGAN
def __init__(self, conv_dim=64, repeat_num=3):
super(MDNet, self).__init__()
layers = []
layers.append(nn.Conv2d(3, conv_dim, kernel_size=7, stride=1, padding=3, bias=False))
layers.append(nn.InstanceNorm2d(conv_dim, affine=True))
layers.append(nn.ReLU(inplace=True))
# Down-Sampling
curr_dim = conv_dim
for i in range(2):
layers.append(nn.Conv2d(curr_dim, curr_dim * 2, kernel_size=4, stride=2, padding=1, bias=False))
layers.append(nn.InstanceNorm2d(curr_dim * 2, affine=True))
layers.append(nn.ReLU(inplace=True))
curr_dim = curr_dim * 2
# Bottleneck
for i in range(repeat_num):
layers.append(ResidualBlock(dim_in=curr_dim, dim_out=curr_dim))
self.main = nn.Sequential(*layers)
def forward(self, reference_image):
fm_reference = self.main(reference_image)
return fm_reference
# AMM暂时先不用landmark detector,先试试一般的attention效果如何
# feature map也先不乘以visual_feature_weight
# 这里attention部分的计算也先存疑,因为论文中提到x和y需要是同一个区域,但结构图中是softmax
class AMM(nn.Module):
"""Attentive Makeup Morphing module"""
def __init__(self):
super(AMM, self).__init__()
self.visual_feature_weight = 0.01
self.lambda_matrix_conv = nn.Conv2d(in_channels=256, out_channels=1, kernel_size=1)
self.beta_matrix_conv = nn.Conv2d(in_channels=256, out_channels=1, kernel_size=1)
self.softmax = nn.Softmax(dim=-1)
def forward(self, fm_source, fm_reference):
batch_size, channels, width, height = fm_reference.size()
old_lambda_matrix = self.lambda_matrix_conv(fm_reference).view(batch_size, -1, width * height)
old_beta_matrix = self.beta_matrix_conv(fm_reference).view(batch_size, -1, width * height)
# reshape后fm的形状是C*(H*W)
temp_fm_reference = fm_reference.view(batch_size, -1, height * width)
# print('temp_fm_reference shape: ', temp_fm_reference.shape)
# fm_source 在reshape后需要transpose成(H*W)*C
temp_fm_source = fm_source.view(batch_size, -1, height * width).permute(0, 2, 1)
# print('temp_fm_source shape: ', temp_fm_source.shape)
# energy的形状应该是N*N,N=H*W
energy = torch.bmm(temp_fm_source, temp_fm_reference)
attention_map = self.softmax(energy)
new_lambda_matrix = torch.bmm(old_lambda_matrix, attention_map.permute(0, 2, 1))
new_beta_matrix = torch.bmm(old_beta_matrix, attention_map.permute(0, 2, 1))
new_lambda_matrix = new_lambda_matrix.view(batch_size, 1, width, height)
new_beta_matrix = new_beta_matrix.view(batch_size, 1, width, height)
# 对feature_map_source进行修改
lambda_tensor = new_lambda_matrix.expand(batch_size, 256, width, height)
beta_tensor = new_beta_matrix.expand(batch_size, 256, width, height)
morphed_fm_source = torch.mul(lambda_tensor, fm_source)
morphed_fm_source = torch.add(morphed_fm_source, beta_tensor)
return morphed_fm_source
训练尚未完成,但有前几轮迭代的妆容迁移效果,训练效果如下: