Skip to content

Instantly share code, notes, and snippets.

@alexruperez
Last active April 23, 2018 11:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexruperez/65efaf4a158418f28edf1bfc4037efbe to your computer and use it in GitHub Desktop.
Save alexruperez/65efaf4a158418f28edf1bfc4037efbe to your computer and use it in GitHub Desktop.
Create your own Animoji in Swift | Lil ‘Bits | https://www.youtube.com/watch?v=6Khyi2xKow4
import UIKit
import Vision
import AVFoundation
class BearmojiViewController: UIViewController {
private lazy var drawLayer = CALayer()
private lazy var captureSession: AVCaptureSession = {
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front),
let input = try? AVCaptureDeviceInput(device: captureDevice) else {
return captureSession
}
captureSession.addInput(input)
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "SampleBuffer"))
captureSession.addOutput(output)
return captureSession
}()
let sequenceRequestHandler = VNSequenceRequestHandler()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .lightGray
view.layer.addSublayer(drawLayer)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
drawLayer.frame = view.frame
checkCameraAccess()
}
func checkCameraAccess() {
switch AVCaptureDevice.authorizationStatus(for: .video) {
case .authorized: captureSession.startRunning()
default: requestCameraAccess()
}
}
func requestCameraAccess() {
AVCaptureDevice.requestAccess(for: .video) { _ in
self.checkCameraAccess()
}
}
}
extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return
}
let request = VNDetectFaceLandmarksRequest(completionHandler: handleDetectFaceLandmarksRequest)
try? sequenceRequestHandler.perform([request], on: pixelBuffer, orientation: .right)
}
private func handleDetectFaceLandmarksRequest(_ request: VNRequest, error: Error?) {
DispatchQueue.main.async {
self.drawLayer.sublayers?.forEach { $0.removeFromSuperlayer() }
guard let faceObservations = request.results as? [VNFaceObservation] else {
return
}
let landmarks = faceObservations.compactMap { $0.landmarks }
let rect = self.drawLayer.frame
for landmark in landmarks {
if let faceContourPoints = landmark.faceContour?.pointsInImage(imageSize: rect.size),
let leftEyebrowPoints = landmark.leftEyebrow?.pointsInImage(imageSize: rect.size),
let rightEyebrowPoints = landmark.rightEyebrow?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, leftEyebrowPoints + faceContourPoints + rightEyebrowPoints), .orange)
self.fill(self.pointsIn(rect: rect, leftEyebrowPoints), .brown)
self.fill(self.pointsIn(rect: rect, rightEyebrowPoints), .brown)
}
if let points = landmark.leftEye?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .white)
}
if let points = landmark.rightEye?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .white)
}
if let points = landmark.leftPupil?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .black)
}
if let points = landmark.rightPupil?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .black)
}
if let points = landmark.outerLips?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .red)
}
if let points = landmark.innerLips?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .magenta)
}
if let points = landmark.nose?.pointsInImage(imageSize: rect.size) {
self.fill(self.pointsIn(rect: rect, points), .brown)
}
}
}
}
private func pointsIn(rect: CGRect, _ points: [CGPoint]) -> [CGPoint] {
return points.map {
var point = $0
point.x = rect.width - point.x
point.y = rect.height - point.y
return point
}
}
private func fill(_ points: [CGPoint], _ fillColor: UIColor, _ strokeColor: UIColor = .black) {
let layer = CAShapeLayer()
if points.count > 1 {
let bezierPath = UIBezierPath()
var linePoints = points
let first = linePoints.removeFirst()
bezierPath.move(to: first)
for linePoint in linePoints {
bezierPath.addLine(to: linePoint)
}
bezierPath.addLine(to: first)
layer.path = bezierPath.cgPath
} else if let point = points.first {
let radius: CGFloat = 5
layer.path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 2.0 * radius, height: 2.0 * radius), cornerRadius: radius).cgPath
layer.position = CGPoint(x: point.x - radius, y: point.y - radius)
}
layer.fillColor = fillColor.cgColor
layer.strokeColor = strokeColor.cgColor
drawLayer.addSublayer(layer)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment