关于styleclip的踩坑(1)global direction中的fs3.npy的生成

随心情填坑。

0.背景

        因为公司项目有个需求需要用到styleclip,所以就去了解了一下。这项技术就是可以通过clip输入文字,对生成的图片产生一定影响,从而生成符合描述的图片,或者,描述为图像编辑,将一张苦瓜脸变为笑脸。我这边的项目需要将该项目改为对其他目标的编辑,不是脸,但是这里就不仅仅是更改数据集这么简单了。

这边用到的stylegan项目地址:GitHub - dvschultz/stylegan2-ada-pytorch: StyleGAN2-ADA - Official PyTorch implementationStyleGAN2-ADA - Official PyTorch implementation. Contribute to dvschultz/stylegan2-ada-pytorch development by creating an account on GitHub.https://github.com/dvschultz/stylegan2-ada-pytorch

 用到的styleclip项目:

GitHub - orpatashnik/StyleCLIP: Official Implementation for "StyleCLIP: Text-Driven Manipulation of StyleGAN Imagery" (ICCV 2021 Oral)Official Implementation for "StyleCLIP: Text-Driven Manipulation of StyleGAN Imagery" (ICCV 2021 Oral) - GitHub - orpatashnik/StyleCLIP: Official Implementation for "StyleCLIP: Text-Driven Manipulation of StyleGAN Imagery" (ICCV 2021 Oral)https://github.com/orpatashnik/StyleCLIP还会用到一个项目hyperstyle:

GitHub - yuval-alaluf/hyperstyle: Official Implementation for "HyperStyle: StyleGAN Inversion with HyperNetworks for Real Image Editing" https://arxiv.org/abs/2111.15666Official Implementation for "HyperStyle: StyleGAN Inversion with HyperNetworks for Real Image Editing" https://arxiv.org/abs/2111.15666 - GitHub - yuval-alaluf/hyperstyle: Official Implementation for "HyperStyle: StyleGAN Inversion with HyperNetworks for Real Image Editing" https://arxiv.org/abs/2111.15666https://github.com/yuval-alaluf/hyperstyle

1.stylegan

感觉stylegan应该不用我介绍,网上一搜一大堆,这里推荐看以下几篇

StyleGAN 和 StyleGAN2 的深度理解 - 知乎StyleGAN 论文:A Style-Based Generator Architecture for Generative Adversarial Networks 源码:https://github.com/NVlabs/stylegan 效果:人脸生成效果 生成的假车效果: 生成的假卧室效果: 效果视频(建议…https://zhuanlan.zhihu.com/p/263554045

StyleCLIP: Text-Driven Manipulation of StyleGAN Imagery 阅读笔记 - 知乎论文:StyleCLIP: Text-Driven Manipulation of StyleGAN Imagery code: https://github.com/orpatashnik/StyleCLIP 效果: 由于StyleGAN、StyleGAN2在生成图像上的优秀表现,越来越多的paper基于styleGAN来对图像…https://zhuanlan.zhihu.com/p/368134937

         这两篇是同一位大神写的,感觉讲解的还是非常透彻的,为方便下面的内容的理解,这里简单说一下网络结构,就不附图了。

        首先是GAN网络,必然是分为两个大部分,分别是生成网络和判别网络,在stylegan的生成网络中,又分为两个部分,分别成为mapping和synthesis,前者是输入一段噪声生成latent,成为Z空间到W空间,后者是接收latent以及噪声,再生成图片。这里说的非常简略啊,感兴趣需要去详细了解一下,其中synthesis这个,内部有好几个block,每个block又有好几个layer,每个layer都需要接收这个latent,用意应该就是对生成图片进行干预,来生成希望的图片,而噪声就是为了对图片进行细调,比如有几根头发啊,几条皱纹啥的,增加一些波动性。个人理解。

2.styleclip

        这个项目,上面第二篇已经有过介绍了,但是对于具体的方法不是特别详细,只讲了一些原理,这里增加一篇实战:

【实战】文本驱动的StyleGAN2图像处理(三):全局指向(Global Direction)_闪闪·Style的博客-CSDN博客StyleCLIP项目由以色列的耶路撒冷希伯来大学、特拉维夫大学和Adobe研究所共同完成,它用对比语言—图像预训练(CLIP)模型的力量,为StyleGAN2 图像处理开发一个基于文本的输入界面,利用StyleGAN2的潜在空间来操纵生成的StyleGAN2图像,而不需要人工去操作或修饰图像。简单地说,就是写一段文字,指导StyleGAN2生成具备指定特征的图像。论文地址:https://arxiv.org/abs/2103.17249Github项目:https://github.com/orphttps://blog.csdn.net/weixin_41943311/article/details/118673470

这位博主这个系列都还不错,建议都看一下,我主要是看了二和三。

        对于styleclip,这篇论文一共提出了三种方法,我这边试过第一个和第三个,该项目都有提供代码,有的可能需要自己debug一下,比如第一种,optimization的方法,他默认是不使用id_loss的,但是默认又需要加载这个权重,这样的话如果没有该权重就会报错,就直接注释掉就可以了,因为默认这个loss没有使用到,代码搜索id_loss就能看到。

        对于 第三种方法,是我领导最为感兴趣的方法,他觉得就是训练好stylegan之后,通过结合clip,不需要再训练啊标注啥的就能达到比较好的效果。但是这里有个问题是,项目的源码提供的是针对tf的权重来进行的,而我现有的权重是pytorch的,要怎么玩呢。

        于是出现另外一个项目hyperstyle,他也提供了两种编辑latent的方法,其中一个就是global direction。说明一下,该项目默认支持的不是我发的那个stylegan的项目,但是有提供代码来转换权重,转换代码没有仔细看,应该就是对各个网络层的名字转换了一下而已,我用的那个是ada,而默认的是原版,大概就是这样。这里其实我已经在坑里了,自己没有想明白而已。

        hyperstyle的global direction说的很明确,需要几个npy,简单说一下,需要待转换图片的latent和style,这两个npy文件,可以通过hyperstyle得到,跑一边inference就可以了,这里他也需要一些输入,但是比较好弄,就不多说了,他的主要目的就是反演图像为latent code,据说会有点耗时,这部分以及上一级的stylegan都是由我同事负责的;然后他还需要通过stylegan来生成两个文件,分别是s_mean_std和fs3.npy,其中后者的生成过程就需要前者,所以两个可以说一起生成的。

3.生成文件

        生成文件碰到的难题是什么,hyperstyle没有提供如何生成的代码,而styleclip的项目里有,可惜是针对tf的权重的,不软那个项目在这篇文里就没有任何意义了。我这边采用的是,将tf的代码平替为pytorch的,就是tf用了啥功能,在pytorch中找替代就可以了。这里主要用到GetCode.py这个文件,修改的是他里面的函数,流程和参数主要参考上面提及的那个实战的博文。

         根据操作步骤,先要生成W.npy,生成就是通过mapping部分,输入随机生成的tensor,得到W,代码如下,注释的是源代码部分,也有我改过又注释了的。

@torch.no_grad()
def GetCode(Gs,random_state,num_img,num_once,dataset_name):
    rnd = np.random.RandomState(random_state)  #5
    
    truncation_psi=0.5
    truncation_cutoff=8
    
    # dlatent_avg=Gs.mapping.w_avg
    
    dlatents=[]#np.zeros((num_img,512),dtype='float32')
    for i in tqdm(range(int(num_img/num_once))):
        src_latents =  torch.randn([num_once, Gs.z_dim]).cuda()
        src_dlatents = Gs.mapping(src_latents, None, truncation_psi=truncation_psi, truncation_cutoff=truncation_cutoff) # [seed, layer, component]
        
        # # Apply truncation trick.
        # if truncation_psi is not None and truncation_cutoff is not None:
        #     layer_idx = np.arange(src_dlatents.shape[1])[np.newaxis, :, np.newaxis]
        #     ones = np.ones(layer_idx.shape, dtype=np.float32)
        #     coefs = np.where(layer_idx < truncation_cutoff, truncation_psi * ones, ones)
        #     coefs = torch.from_numpy(coefs).cuda()
        #     src_dlatents_np=lerp(dlatent_avg, src_dlatents, coefs)
        #     src_dlatents=src_dlatents_np[:,0,:]
        dlatents.append(src_dlatents[:,0,:])
    dlatents = torch.cat(dlatents, dim=0)
    dlatents = dlatents.cpu().numpy().astype('float32')
    print('get all z and w')
    
    tmp='./npy/'+dataset_name+'/W'
    np.save(tmp,dlatents)

        在tf的代码中,得到结果后还做了一个lerp这个函数的操作,不太明确是要干啥,因为看到pytorch这边好像本来就是做了这个处理,所以直接注释了。如果想要完全按照原逻辑来的话,可以打开注释,再微调一下,这里dlatent_avg这个参数,我在pytorch这边找到一个可能对应的东西,反正函数不会报错。他的结果的shape,原本应该是b*14*512(我这边只有14哈,原始脸的应该是18),然后我发现他只取了第一个,挺奇怪的,输出看了下,发现他的值,前7个和后7内容是一样的,不知道是不是做了下平均,我没有深究,即使按照他的套路来做,也是前一半和后一半是不一样的,可能后半段不需要吧。

        保存完毕后,后面会需要生成一个文件S,我的代码如下

@torch.no_grad()
def GetS(dataset_name,num_img):
    print('Generate S')
    tmp='./npy/'+dataset_name+'/W.npy'
    dlatents=np.load(tmp)[:num_img]
        
    Gs=LoadModel(dataset_name)
    # Gs.print_layers()  #for ada
    model=G_synthesisNetwork(Gs.synthesis)  
    dlatents=dlatents[:,None,:]
    dlatents=np.tile(dlatents,(1,Gs.synthesis.num_ws,1))
    
    all_s = {}
    once = 20
    for i in tqdm(range(num_img//once)):
        src_dlatents = torch.from_numpy(dlatents[i*once:(i+1)*once]).cuda()
        s = model(src_dlatents, noise_mode='const', force_fp32=True)
        for k ,v in s.items():
            if k not in all_s:
                all_s[k] = []
            all_s[k].append(v)

    layer_names=[layer for layer in all_s]
    all_s = [torch.cat(all_s[l], dim=0).cpu().numpy() for l in layer_names]
    save_tmp=[layer_names,all_s]
    return save_tmp

         这里可以看到,是先要加载上面的W.npy的,然后很自然想要他是想要使用synthesis部分,这里的W.npy,他加载之后又扩展到需要的大小,那为啥之前要只取其中一个呢,不知道,可能为了节省空间吧。这里我的显卡显存有限,所以他原本是一次性得到全部的内容,我这边是用了一个循环。这边有个核心问题是,懒得贴原始代码了,简单描述就是他用tf权重,然后筛选出需要得到结果的网络层的名字,然后run一下就行了,惨的是,pytorch好像没有这个功能,踩完坑了,说一下,他需要的是每一次卷积时的style,变量名就叫style,他应该是每个layer中,会需要将输入的w经过一个仿射变换(FullyConnectedLayer)得到style,然后在处理。因为我用的那个stylegan的加载方式是下面这样的,这也是该项目自己使用的方法。

def LoadModel(model_name):
    with open(model_name, 'rb') as f:
        Gs = pickle.load(f)['G_ema'].cuda()
    return Gs

         由于懒得去改代码,找到去怎么搭建网络,再load state啥的,就沿着这个方法来往下走,但是原来的网络结构中,没有留出空间去返回我想要的值,上面代码的可以看到,我是自定义了一个类,然后输入这个Gs,类的的定义如下,这个定义主要就是复制了原始的网络的定义,然后在forward里,将self改为self.model,这样的话,原来代码里需要用到的变量会通过self.model调用到,包括网络,这么做的主要目的就是微调代码,来增加原来没有的输出。

class G_synthesisNetwork(nn.Module):
    def __init__(self, model):
        super(G_synthesisNetwork, self).__init__()

        self.model = model

    @torch.no_grad()
    def forward(self, ws, **block_kwargs):
        block_ws = []
        with torch.autograd.profiler.record_function('split_ws'):
            misc.assert_shape(ws, [None, self.model.num_ws, self.model.w_dim])
            ws = ws.to(torch.float32)
            w_idx = 0
            for res in self.model.block_resolutions:
                block = getattr(self.model, f'b{res}')
                block_ws.append(ws.narrow(1, w_idx, block.num_conv + block.num_torgb))
                w_idx += block.num_conv

        x = img = None
        outs = {}
        for res, cur_ws in zip(self.model.block_resolutions, block_ws):
            block = getattr(self.model, f'b{res}')
            block = G_synthesisBlock(block)
            x, img, style_conv0, style_conv1, style_torgb = block(x, img, cur_ws, **block_kwargs)
            
            
            if style_conv0 is not None:
                outs[f"synthesis.b{res}.conv0"] = style_conv0
            outs[f"synthesis.b{res}.conv1"] = style_conv1
            outs[f"synthesis.b{res}.torgb"] = style_torgb
        return outs

        原始的网络结构链接是network,上面是synthesisnetwork的类,还有block和layer都是这么操作的,就不贴了,只能说我知道这么写很low,但是这是我一下子就想到的,最省脑细胞的方法了,就是复制一下就完了。

        这样的话,生成S就能够执行了,需要注意的是,这个S需要的是网络层的名字和输出,且是一一对应的,后面会用到,网络层的名字是有顺序的,最好按照conv0,conv1,torgb这样的顺序来,其中第一个block没有conv0。

         生成s_mean_std文件,这个方法其实就是先生成S然后对S中的数据分别去mean和std再保存就好了,为啥不在上一步获得S的时候一起做呢,在原始代码中就是分两步,而且传递的数量也不同。简单的提一下,上面的获取S的代码中,有个变量once,是控制每个循环每次做多少个的,这里发现,在我这边,不是越大越好,设置20时的总耗时比设置为50的耗时少了好几倍。

        到此,已经得到W.npy,S,s_mean_std三个文件了,按照流程,接下来需要参考SingleChannel.py这个来生成最终的fs3.npy文件了。根据上面的流程,那这个的操作就非常简单,非常类似,就不细说了,只提几个要点

        首先是,代码中对于torgb层的判断是用的大写,注意修改,他的目的是区分哪些是torgb层;

        然后是,tf的代码中,是说有的层需要加入噪声的,这次要改为随机造成,主要看代码的话,可以看到上面的生成用的不是随机噪声哦,在pytorch这边,因为原本就有参数控制噪声类别,所以就省略了;

        最后,有个生成图片的函数中,需要传递编辑过的latent,所谓编辑就是将这个值分别乘了-5和5,具体逻辑可以看看代码,反正就是放回去的时候,主要要按照网络名来放,所以这里我又是复制了大段的网络定义,这次又修改为增加了一个传递参数,layer里面的style通过传参得到,仿射变换不用做了,大概就是下面这样

for res, cur_ws in zip(self.model.block_resolutions, block_ws):
            block = getattr(self.model, f'b{res}')
            block = G_synthesisBlock(block)

            if f"synthesis.b{res}.conv0" in names:
                style_conv0 = datas[names.index(f"synthesis.b{res}.conv0")]
            else:
                style_conv0 = None
            style_conv1 = datas[names.index(f"synthesis.b{res}.conv1")]
            style_torgb = datas[names.index(f"synthesis.b{res}.torgb")]

            x, img = block(x, img, cur_ws, style_conv0, style_conv1, style_torgb, **block_kwargs)
            
        return img

        这个操作是非常耗时的,因为他是需要将latent的每个维度都要变化一下,这里摘抄一下上面提到的实战的博文的原文

论文中给出的方法是:取100个图像,对StyleGAN2输出的feature maps中的具体某一个channel,先求出其在100个图像上的平均值,保持其他channel不变,在该channel上分别加上正/负五倍均方差大小的扰动,对应生成100x2=200个图像,分别计算其在CLIP模型中的feature,算出100对图像feature的差值,并进行规格化,得到StyleGAN2输出的feature maps中的每一个channel变化时与图像CLIP语义的关联系数

这样,当输入一段有变化的CLIP文本时,首先生成图像CLIP语义的变化量,然后用这个变化量与上文提到的关联系数 Δic 相乘,得到StyleGAN2 feature maps中的每一个channel上的变化值,将该变化值与原始图片的feature maps相加,最后用预训练模型生成风格编辑后的新图像。

        以上,就能够生成四个文件了,得到这四个之后,就可以在hyperstyle中使用global direction了,当然前提要先跑一下他的inference哈,得到输入图片的latent。

        但是这个方法啊,我试了下脸的,用的就是官方提供的文件,然后,对于官方提供的图片,效果还是非常不错的,但是对于自己找的图片,效果比较一般,当然这个可能也是这个hyperstyle的反演功能我没有弄好,跑得次数不够或是啥吧,这个是其他同事负责的,然后用我这边自己跑的stylegan权重,不能说完全没效果,但是还是比较微妙,这个也可能是我的gan没有训练非常收敛,或者还是hyperstyle没有跑收敛啥的,又或者gan嘛,效果就那样,好的非常好,差的非常差,这也是我一直以来的观点,GAN的效果非常不稳定,也就是写写论文,真要拿来做项目,比较难以控制,最后贴几张做的脸部的global direction,第一张是官方的,后面是找的外部的图片,需要严正声明的是,原图是正常的图片,但是网络生成的像是被打后的状态,我也不明白为啥,真是难为他了,不过对于smile,至少还是做到了。。。好担心这么放出会被打,求轻喷

最后

提一下,为啥上面说自己在坑底,因为感觉这篇文的精华,就是说怎么把tf的代码改为pytorch,而且非常繁琐,后来发现,hyperstyle不是要转权重嘛,转了之后,他也有提供一个网络结构的文件model.py感觉就是考虑非常全面,对于需要传参和取值这方面都有预留啊,导致上面的篇幅大部分是没有用的,还想出各种骚操作,我只需要转一下权重,然后按照新的stylegan的流程来做就可以了。

所以这篇文章,连记录的意义都没有,完全就是对自己做的无用功的一次不服,,,

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
好的,我会为您演示三组不同采样率下的语音信号的采样、时域波形图和频谱图。请您稍等片刻。 首先,我们需要录制一分钟的语音信号。由于不方便录制现场音频,我将使用MATLAB自带的 `gong` 音频作为演示,您可以将其替换为您自己的音频文件。 以下是MATLAB代码: ```matlab % 读取音频文件 [x, Fs] = audioread('gong.wav'); % 选取时长为1分钟的语音信号 x = x(1:60*Fs, :); ``` 接下来,我们将对三个不同采样率的语音信号进行采样,并绘制出时域波形图和频谱图。 以下是MATLAB代码: ```matlab % 采样率1:5kHz Fs1 = 5000; Ts1 = 1/Fs1; t1 = 0:Ts1:(length(x)-1)*Ts1; y1 = x(1:Fs/Fs1:end, :); % 绘制时域波形图 figure plot(t1, y1); title('时域波形图(采样率1:5kHz)'); xlabel('时间 (s)'); ylabel('振幅'); % 绘制频谱图 figure N1 = length(y1); f1 = (0:N1-1)/N1*Fs1; Y1 = fft(y1); Y1 = abs(Y1(1:N1/2)); plot(f1, Y1); title('频谱图(采样率1:5kHz)'); xlabel('频率 (Hz)'); ylabel('振幅'); % 采样率2:10kHz Fs2 = 10000; Ts2 = 1/Fs2; t2 = 0:Ts2:(length(x)-1)*Ts2; y2 = x(1:Fs/Fs2:end, :); % 绘制时域波形图 figure plot(t2, y2); title('时域波形图(采样率2:10kHz)'); xlabel('时间 (s)'); ylabel('振幅'); % 绘制频谱图 figure N2 = length(y2); f2 = (0:N2-1)/N2*Fs2; Y2 = fft(y2); Y2 = abs(Y2(1:N2/2)); plot(f2, Y2); title('频谱图(采样率2:10kHz)'); xlabel('频率 (Hz)'); ylabel('振幅'); % 采样率3:20kHz Fs3 = 20000; Ts3 = 1/Fs3; t3 = 0:Ts3:(length(x)-1)*Ts3; y3 = x(1:Fs/Fs3:end, :); % 绘制时域波形图 figure plot(t3, y3); title('时域波形图(采样率3:20kHz)'); xlabel('时间 (s)'); ylabel('振幅'); % 绘制频谱图 figure N3 = length(y3); f3 = (0:N3-1)/N3*Fs3; Y3 = fft(y3); Y3 = abs(Y3(1:N3/2)); plot(f3, Y3); title('频谱图(采样率3:20kHz)'); xlabel('频率 (Hz)'); ylabel('振幅'); ``` 以上就是三组不同采样率下的语音信号的采样、时域波形图和频谱图。希望能对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值