Over a million developers have joined DZone.

CartoonEyes: Compositing Cartoon Eyes Over Face From Front Camera in Swift

More magic with images from Simon Gladman, this time concentrating on making cartoon eyes in Swift.

· Mobile Zone

Here's some festive silliness: CartoonEyes composites cartoon eyeballs over a face captured by an iOS devices's front camera and then passes that composite through a Core Image Comic Effect filter. It makes use of Core Image's face detection class, CIDetector, to find the positions of the eyes.

Capturing Video From Front Camera

I played with applying Core Image filters to a live camera feed earlier this year (see: Applying CIFilters to a Live Camera Feed with Swift). The main difference with this project is that I wanted to use the front camera rather than the default camera. To do this, I use a guard statement to filter the the devices by their position and pick the first item of that filter as my device:

guard let frontCamera = AVCaptureDevice.devicesWithMediaType(AVMediaTypeVideo)
        .filter({ ($0 as? AVCaptureDevice)?.position == AVCaptureDevicePosition.Front })
        .first as? AVCaptureDevice else
    {
        fatalError("Unable to access front camera")

    }

Compositing Cartoon Eyes

Once I have the source image from the camera, it's time to composite the cartoon eyes over it. My function, eyeImage(), takes the original camera image (to calculate  the eye positions), an image to composite the eye over and a Boolean indicating whether it's looking at the left or right eye:

    func eyeImage(cameraImage: CIImage, backgroundImage: CIImage, leftEye: Bool) -> CIImage

It's called twice:

    let leftEyeImage = eyeImage(cameraImage, backgroundImage: cameraImage, leftEye: true)
    let rightEyeImage = eyeImage(cameraImage, backgroundImage: leftEyeImage, leftEye: false)

The first call creates an image compositing the left eye over the original camera image and the second call composites the right eye over the previous left eye composite.

Before I can invoke eyeImage(), I need a CIDetector instance which will give me the positions of the facial features:

 lazy var ciContext: CIContext =
    {
        [unowned self] in

        return  CIContext(EAGLContext: self.eaglContext)
    }()

    lazy var detector: CIDetector =
    {
        [unowned self] in

        CIDetector(ofType: CIDetectorTypeFace, context: self.ciContext, options: nil)

    }()

Inside eyeImage(), I first create a composite Core Image filter and a transform filter:


    let compositingFilter = CIFilter(name: "CISourceAtopCompositing")!
    let transformFilter = CIFilter(name: "CIAffineTransform")!

I also need to calculate the midpoint of the cartoon eye image as an offset for the transform:

  let halfEyeWidth = eyeballImage.extent.width / 2
    let halfEyeHeight = eyeballImage.extent.height / 2

Now, using the detector, I need the features of the first face in the image and, depending on the leftEye argument, whether it has the eye position I need:

   if let features = detector.featuresInImage(cameraImage).first as? CIFaceFeature
        where leftEye ? features.hasLeftEyePosition : features.hasRightEyePosition
    {

        ...

If it does, I'll need to create a transform for the transform filter to position the cartoon eye image based on the "real" eye position:

 let eyePosition = CGAffineTransformMakeTranslation(
        leftEye ? features.leftEyePosition.x - halfEyeWidth : features.rightEyePosition.x - halfEyeWidth,
        leftEye ? features.leftEyePosition.y - halfEyeHeight : features.rightEyePosition.y - halfEyeHeight)

...and now I can execute the transform filter and get an image of the cartoon eye positioned over the real eye:

 transformFilter.setValue(eyeballImage, forKey: "inputImage")
    transformFilter.setValue(NSValue(CGAffineTransform: eyePosition), forKey: "inputTransform")

    let transformResult = transformFilter.valueForKey("outputImage") as! CIImage

Finally I composite the transformed cartoon eye over the supplied background image and return that result:

 compositingFilter.setValue(backgroundImage, forKey: kCIInputBackgroundImageKey)
    compositingFilter.setValue(transformResult, forKey: kCIInputImageKey)


    return  compositingFilter.valueForKey("outputImage") as! CIImage

If there were no facial features detected, I simply return the background image as it was supplied.

Displaying Output with OpenGL ES

Rather than converting the output to a UIImage and displaying it in a UIImageView, this project takes the more direct route and renders the output using a GLKView. Once the captureOutput method of myAVCaptureVideoDataOutputSampleBufferDelegate has its image, it invalidates the GLKView's display in a foreground thread:

  dispatch_async(dispatch_get_main_queue())
    {
        self.imageView.setNeedsDisplay()

    }

Since my view controller is the GLKView's GLKViewDelegate, this invokes glkView() where I call eyeImage() and pass the right eye's image into a comic effect Core Image filter and draw that output the the GLKView's context:

let leftEyeImage = eyeImage(cameraImage, backgroundImage: cameraImage, leftEye: true)
    let rightEyeImage = eyeImage(cameraImage, backgroundImage: leftEyeImage, leftEye: false)

    comicEffect.setValue(rightEyeImage, forKey: kCIInputImageKey)

    let outputImage = comicEffect.valueForKey(kCIOutputImageKey) as! CIImage

    ciContext.drawImage(outputImage,
        inRect: CGRect(x: 0, y: 0,
            width: imageView.drawableWidth,
            height: imageView.drawableHeight),
        fromRect: outputImage.extent)

Conclusion

With very little work, the combination of Core Image filters and detectors combined with AV Capture Session has allowed me to composite different images based on the positions of different facial features. 

As always, the source code for this project is available at my GitHub repository here. Enjoy!

Topics:
swift ,image ,camera

Published at DZone with permission of Simon Gladman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}