a photo filter for nightmares

left of the uncanny valley lies the
Nope valley

Neil Gaiman's

Isolate a face

Find the eyes

Find the mouth



only a few hundred magic lines
cfilter = np.ones((3,3))
mouth = convolve(mouth, cfilter).astype(np.bool)
# Fill the mouth in if it isn't too open
mouth = morph.binary_fill_holes(mouth)
whole_face_pts = np.vstack([L[k] for k in L])
mouth_pts = np.vstack([L[k] for k in mouth_keys])
nose_pts = np.vstack([L[k] for k in ['nose_tip','nose_bridge']])
face_mask = get_mask(whole_face_pts, height, width)
mouth_to_face_ratio = np.sqrt(bounding_box_area(mouth_pts) / bounding_box_area(whole_face_pts) )
# Clip the ratio so the mouth-eyes don't get too small
mouth_to_face_ratio = np.clip(mouth_to_face_ratio, 0.5, 1.2)
left_eye = get_mask(L['left_eye'], height, width)
E0 = copy_mask(img, left_eye, mouth, mouth_to_face_ratio)
# Inpaint around the eyes one out and one in from the outer edge
d = morph.binary_dilation(E0,iterations=1) & (~E0) #& (~nose_mask)
d = morph.binary_dilation(d,iterations=1)
img = inpaint.inpaint_biharmonic(img, d, multichannel=True)
img = np.clip((img*255).astype(np.uint8), 0, 255)
really, just go here

Technical details

  • Everything is scripted
  • Faces are found with a CNN
  • Facial landmarks from dlib
  • Masks created from convex hull fill
  • Masks expanded with a simple block convolution
  • Mouths are placed on the eye center-of-mass
  • Mini-mouths sized by sqrt of eye/face ratios
  • Sizing is clipped from 0.5 to 1.2
  • After pasting, mouths are expanded and in-filled (smoothing)

Why stop with just one?