This one feels like the never-ending project. Okay, time to move on to generating series of images that morph from one state to another. I will start with the simple glasses to no glasses state. I likely won’t bother with the reverse.

Morph From Glasses to No-Glasses

For each image I will use the same noise tensor. I will only change the condition tensor. As with the previous project the one-hot column for the glasses condition will go from \(1\) to \(0\) in some number of equal steps. The column for the no-glasses state will be the reverse. For each row the two columns will sum to \(1\).

When generating the one-hot tensor I tried to take the two end values and mutliply them by the tensor that contains the range of decimal values. Similar to what I did in the previous project. But I could not get that work. Always ended up with an error similar to RuntimeError: Tensors must have same number of dimensions: got 4 and 3. It did vary depending on what I tried, but it was always related to incompatible tensor shapes. So, I gave up and used a for loop.

    if do_lbl:
      m_sz = n_steps + 2
      for t_lbl in ['m1']:
        if t_lbl == 'm1':
          noise = torch.randn(1, nz, 1, 1)
          m_nz = noise.repeat(m_sz, 1, 1, 1)
          pcnt_2nd_lbl = torch.linspace(0, 1, m_sz)[:, None]
          labels = torch.ones(m_sz, 2, 1, 1)
          for i in range(m_sz):
            labels[i, 0, :, :] = labels[i, 0, :, :] * (1-pcnt_2nd_lbl[i])
            labels[i, 1, :, :] = labels[i, 1, :, :] * pcnt_2nd_lbl[i]
          nz_lbls = torch.cat([m_nz, labels], dim=1).to(device)
          fakes = genr(nz_lbls)
          img = fakes[:, :3, :, :] / 2 + 0.5
          i_lbls = labels[:, 1].flatten().detach().int()
          img_grid_lbl(img, i_lbls, (2, 5), i_show=True, epoch=f"{t_lbl}_{c_tm}")

And a couple samples of the output generated by the above. Though I must say, it occasionally failed to remove the glass by the end of the series. And, the faces do not stay exactly the same all the way through the series. Insufficient training?

I am increasing the size of the images slightly when generating the figures below. Which is making them a touch fuzzy.

You should hear how the fans crank up when the GAN generator goes to work.

Generator Images Showing Morphing Behaviour

first of two figures showing images morphing from face with glasses to that face without glasses
second of two figures showing images morphing from face with glasses to that face without glasses

Now to try something a little different.

Morph Male to Female

The noise tensor seems to determine whether the face will be female or male in appearance. So, I added code to save a few. I am now going to see if I can morph a mail face with glasses into a female face with glasses. Will be a lot of similarity with the above code, but we will be generating a series of noise tensors that are percentages of those at either end instead of the one-hot encoded label values.

I load two of those saved noise tensors from their respective files. I then use them to generate the interpolated noise tensors to use for morphing from an apparently male face to an apparently female face.

Once again I couldn’t seem to sort things out using matrix arithmetic. Need to do some serious research.

        if t_lbl == 'm2' or t_lbl == 'm3':
          g_lbls = torch.zeros(m_sz, 2, 1, 1).to(device)
          g_lbls[:,0,:,:] = 1
          ng_lbls = torch.zeros(m_sz, 2, 1, 1).to(device)
          ng_lbls[:,1,:,:] = 1
          fz_pth = Path("./nz") / "f_nz_20240614_232359.pt"
          mz_pth = Path("./nz") / "m_nz_20240614_232807.pt"
          pcnt_2nd_lbl = torch.linspace(0, 1, m_sz)[:, None].to(device)

          z1 = torch.load(fz_pth).to(device)
          z2 = torch.load(mz_pth).to(device)
          m_nz = z2
          m_nz = m_nz.repeat(m_sz, 1, 1, 1)
          m_nz[-1] = z1
          for i in range(1, m_sz-2):
            m_nz[i] = z2 * (1-pcnt_2nd_lbl[i]) + z1 * pcnt_2nd_lbl[i]
          if t_lbl == "m2":
            lbls = g_lbls
          else:
            lbls = ng_lbls
          nz_lbls = torch.cat([m_nz, lbls], dim=1).to(device)
          fakes = genr(nz_lbls)
          img = fakes[:, :3, :, :] / 2 + 0.5
          i_lbls = g_lbls[:, 1].flatten().detach().int()
          c_tm = strftime("%Y%m%d_%H%M%S", localtime())
          img_grid_lbl(img, i_lbls, (2, 5), i_show=True, epoch=f"{t_lbl}_{c_tm}")

Generator Images Showing Morphing Faces

first of two figures showing images morphing from male face with glasses to female face both with glasses
second of two figures showing images morphing from male face with glasses to female face both without glasses

Let’s Morph Both

Well seems to me to be a reasonable thing to have a look at morphing faces and glasses at the same time.

As I am doing this on the fly with little forethought, I am duplicating a great deal of the code in each if block. May get around to moving a lot of that into functions and rework the block to simplify things. If I do I will update the post.

And, though I am not showing it, I have also been refactoring img_grid_lbl() slightly to accound for title changes and the like.

        if t_lbl == 'm4':
          fz_pth = Path("./nz") / "f_nz_20240614_232359.pt"
          mz_pth = Path("./nz") / "m_nz_20240614_232807.pt"
          pcnt_2nd_lbl = torch.linspace(0, 1, m_sz)[:, None].to(device)

          labels = torch.ones(m_sz, 2, 1, 1).to(device)
          for i in range(m_sz):
            labels[i, 0, :, :] = labels[i, 0, :, :] * (1-pcnt_2nd_lbl[i])
            labels[i, 1, :, :] = labels[i, 1, :, :] * pcnt_2nd_lbl[i]

          z1 = torch.load(fz_pth).to(device)
          z2 = torch.load(mz_pth).to(device)
          # m_nz = z2 * (1-pcnt_2nd_lbl) + z1 * pcnt_2nd_lbl
          m_nz = z2
          m_nz = m_nz.repeat(m_sz, 1, 1, 1)
          m_nz[-1] = z1
          for i in range(1, m_sz-2):
            m_nz[i] = z2 * (1-pcnt_2nd_lbl[i]) + z1 * pcnt_2nd_lbl[i]
          nz_lbls = torch.cat([m_nz, labels], dim=1).to(device)
          fakes = genr(nz_lbls)
          img = fakes[:, :3, :, :] / 2 + 0.5
          i_lbls = labels[:, 1].flatten().detach().int()
          c_tm = strftime("%Y%m%d_%H%M%S", localtime())
          img_grid_lbl(img, i_lbls, (2, 5), i_show=True, epoch=f"{t_lbl}_{c_tm}")

Generator Images Showing Morphing of Faces and Glasses

figure showing images morphing from male face with glasses to female face without glasses

Done

Now that was fun. If a little frustrating not being able to use matrix arithmetic rather than loops. Another day perhaps.

Until next time, may your time morphing be equally entertaining.