Skip to main content

Pose Estimation [Active Liveness]

You can access the PoseEstimation module from the base Amani class.

caution

Before using this module, please make sure you've initialized the SDK correctly.

warning

If you are already using the old (v1) Pose Estimation module, all you need to do is add ".v1" to the end of the "PoseEstimation()" definition.

Pose Estimation has two different flows:

VersionDescription
v1Legacy pose estimation flow. The user is asked to turn their head to random directions such as left, right, up, or down.
v2Circle pose estimation flow. The user is asked to rotate their head in a circular movement.
let poseEstimation = Amani.sharedInstance.poseEstimation()

let poseV1 = poseEstimation.v1
let poseV2 = poseEstimation.v2

Pose Estimation V1

Pose Estimation V1 is the existing active liveness flow. It asks the user to complete a number of random head poses.

Use V1 when you want the user to look left, right, up, or down according to the instructions shown on screen.

let poseV1 = Amani.sharedInstance.poseEstimation().v1

Configuring V1 messages

V1 uses a dictionary with [poseState: String] type.

KeyTypeDescription
faceIsOkStringShows before capturing the selfie
notInAreaStringShows when the user's face is not aligned with the area
faceTooSmallStringShows when the user's face is too far
faceTooBigStringShows when the user's face is too close
completedStringShows when the process is complete
turnRightStringShows when the random pose is right
turnLeftStringShows when the random pose is left
turnUpStringShows when the random pose is up
turnDownStringShows when the random pose is down
lookStraightStringShows when the user's face is not straight
errorMessageStringShows when the user has failed the pose flow
tryAgainStringShows when the user is prompted to try again
errorTitleStringShows when the user has failed the pose flow
nextStringNext pose message
holdPhoneVerticallyStringPrompts the user to hold the phone at the correct angle
wrongPoseStringShows when the user failed a pose
descriptionHeaderStringShows when the screen has started
let infoMessages: [poseState: String] = [
.faceIsOk: "Please hold stable",
.notInArea: "Please align your face with the area",
.faceTooSmall: "Your face is too far",
.faceTooBig: "Your face is too close",
.completed: "All done!",
.turnRight: "→",
.turnLeft: "←",
.turnUp: "↑",
.turnDown: "↓",
.lookStraight: "Look straight",
.errorMessage: "Please complete the steps without leaving your face outside the area",
.tryAgain: "Try again",
.errorTitle: "Failed",
.next: "Next",
.holdPhoneVertically: "Please hold your phone at the correct angle",
.wrongPose: "Your face should match the pose",
.descriptionHeader: "Please align your face with the area and follow the poses shown on screen"
]

poseV1.setInfoMessages(infoMessages: infoMessages)

Configuring V1 screen

V1 uses a dictionary with [poseConfigState: String] type.

Hex color values should be provided without the hash sign #.

KeyTypeDescription
appBackgroundColorStringScreen background color
appFontColorStringText color
primaryButtonBackgroundColorStringPrimary button background color
primaryButtonTextColorStringPrimary button text color
ovalBorderSuccessColorStringSuccess border color
ovalBorderColorStringDefault border color
poseCountStringNumber of poses in string format
mainGuideVisibilityString"true" or "false"
secondaryGuideVisibilityString"true" or "false"
buttonRadiusStringButton radius in string format
let screenConfig: [poseConfigState: String] = [
.appBackgroundColor: "000000",
.appFontColor: "FFFFFF",
.primaryButtonBackgroundColor: "FFFFFF",
.primaryButtonTextColor: "000000",
.ovalBorderSuccessColor: "00FF00",
.ovalBorderColor: "FFFFFF",
.poseCount: "2",
.mainGuideVisibility: "true",
.secondaryGuideVisibility: "true",
.buttonRadius: "10"
]

poseV1.setScreenConfig(screenConfig: screenConfig)

Starting Pose Estimation V1

The start method returns an optional UIView.

let poseEstimation = Amani.sharedInstance.poseEstimation()
let poseV1 = poseEstimation.v1

poseV1
.setInfoMessages(infoMessages: infoMessages)
.setScreenConfig(screenConfig: screenConfig)
.setVideoRecording(enabled: true)

do {
let poseEstimationView = try poseV1.start { [weak self] previewImage in
DispatchQueue.main.async {
self?.poseEstimationView?.removeFromSuperview()
}

// TODO: Upload or show a confirmation screen
}

if let poseEstimationView {
self.poseEstimationView = poseEstimationView

DispatchQueue.main.async {
self.view.addSubview(poseEstimationView)
poseEstimationView.frame = self.view.bounds
poseEstimationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
} catch {
// TODO: Handle start error
}

Changing V1 guide images

V1 supports two different guide image groups.

Guide image typeDescription
Main guide imagesShown in the center
Secondary guide imagesShown at the bottom
note

To change the visibility of the guide images, configure mainGuideVisibility and secondaryGuideVisibility in screenConfig.

Setting V1 main guide images

poseV1.setMainGuideImages(guideImages: [
.mainGuideUp: UIImage(named: "face_up"),
.mainGuideDown: UIImage(named: "face_down"),
.mainGuideLeft: UIImage(named: "face_left"),
.mainGuideRight: UIImage(named: "face_right"),
.mainGuideStraight: UIImage(named: "face_straight")
])

Setting V1 secondary guide images

poseV1.setSecondaryGuideImages(guideImages: [
.secondaryGuideUp: UIImage(named: "arrow_up"),
.secondaryGuideDown: UIImage(named: "arrow_down"),
.secondaryGuideRight: UIImage(named: "arrow_right"),
.secondaryGuideLeft: UIImage(named: "arrow_left")
])

Pose Estimation V2

Pose Estimation V2 is the circle active liveness flow. Instead of asking the user to complete multiple random poses, it asks the user to rotate their head in a circular movement.

Use V2 when you want a circle-based active liveness check.

let poseV2 = Amani.sharedInstance.poseEstimation().v2
info

V1 and V2 use different liveness logic. V1 is based on random head poses. V2 is based on circular head rotation.

Configuring V2 messages

V2 uses a dictionary with [PoseEstimationV2TextKey: String] type.

KeyTypeDescription
lookStraightStringShows when the user needs to look straight before starting the movement
turnLeftStringShows when the user needs to rotate their head to the left direction
turnRightStringShows when the user needs to rotate their head to the right direction
notInAreaStringShows when the user's face is not aligned inside the circle
faceTooFarStringShows when the user's face is too far
faceTooBigStringShows when the user's face is too close
holdPhoneVerticallyStringPrompts the user to hold the phone vertically
keepRotatingStringShows while the user should keep rotating their head
completedStringShows when the process is complete
let infoMessages: [PoseEstimationV2TextKey: String] = [
.lookStraight: "Look straight",
.turnLeft: "Turn your head left",
.turnRight: "Turn your head right",
.notInArea: "Please align your face inside the circle",
.faceTooFar: "Your face is too far",
.faceTooBig: "Your face is too close",
.holdPhoneVertically: "Please hold your phone vertically",
.keepRotating: "Keep rotating your head",
.completed: "Completed"
]

poseV2.setInfoMessages(infoMessages: infoMessages)

Configuring V2 screen

V2 uses a dictionary with [PoseEstimationV2ColorKey: String] type.

Hex color values should be provided without the hash sign #.

KeyTypeDescription
appBackgroundColorStringScreen background color
overlayBackgroundColorStringBackground color inside the circular overlay
progressRingColorStringBase ring color
progressRingTrackColorStringActive progress ring color
appFontColorStringText color
let screenConfig: [PoseEstimationV2ColorKey: String] = [
.appBackgroundColor: "000000",
.overlayBackgroundColor: "000000",
.progressRingColor: "FFFFFF",
.progressRingTrackColor: "00FF00",
.appFontColor: "FFFFFF"
]

poseV2.setScreenConfig(screenConfig: screenConfig)

Starting Pose Estimation V2

The start method returns an optional UIView.

let poseEstimation = Amani.sharedInstance.poseEstimation()
let poseV2 = poseEstimation.v2

let infoMessages: [PoseEstimationV2TextKey: String] = [
.lookStraight: "Look straight",
.turnLeft: "Turn your head left",
.turnRight: "Turn your head right",
.notInArea: "Please align your face inside the circle",
.faceTooFar: "Your face is too far",
.faceTooBig: "Your face is too close",
.holdPhoneVertically: "Please hold your phone vertically",
.keepRotating: "Keep rotating your head",
.completed: "Completed"
]

let screenConfig: [PoseEstimationV2ColorKey: String] = [
.appBackgroundColor: "000000",
.overlayBackgroundColor: "000000",
.progressRingColor: "FFFFFF",
.progressRingTrackColor: "00FF00",
.appFontColor: "FFFFFF"
]

poseV2
.setType(type: "XXX_SE_0")
.setInfoMessages(infoMessages: infoMessages)
.setScreenConfig(screenConfig: screenConfig)
.setVideoRecording(enabled: true)

do {
let poseEstimationView = try poseV2.start { [weak self] previewImage in
DispatchQueue.main.async {
self?.poseEstimationView?.removeFromSuperview()
}

// TODO: Upload or show a confirmation screen
}

if let poseEstimationView {
self.poseEstimationView = poseEstimationView

DispatchQueue.main.async {
self.view.addSubview(poseEstimationView)
poseEstimationView.frame = self.view.bounds
poseEstimationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
} catch {
// TODO: Handle start error
}

Using a preparation screen with V2

Pose Estimation V2 can optionally show a preparation screen before starting the camera flow. This is useful when you want to show a short instruction video before asking the user to rotate their head.

note

The preparation screen is only available for Pose Estimation V2.

let poseV2 = Amani.sharedInstance.poseEstimation().v2

poseV2
.setType(type: "XXX_SE_0")
.setInfoMessages(infoMessages: infoMessages)
.setScreenConfig(screenConfig: screenConfig)
.setVideoRecording(enabled: true)
.showPreparationScreen(
context: self,
video: Bundle.main.url(forResource: "pose_video", withExtension: "mp4"),
message: "Watch and follow the head movement",
buttonText: "Start",
buttonTextColor: "FFFFFF",
buttonBackgroundColor: "34C759",
buttonRadiusDp: 28,
overlayColor: "000000"
)

do {
let poseEstimationView = try poseV2.start { [weak self] previewImage in
DispatchQueue.main.async {
self?.poseEstimationView?.removeFromSuperview()
}

// TODO: Upload or show a confirmation screen
}

if let poseEstimationView {
self.poseEstimationView = poseEstimationView

DispatchQueue.main.async {
self.view.addSubview(poseEstimationView)
poseEstimationView.frame = self.view.bounds
poseEstimationView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
} catch {
// TODO: Handle start error
}

Upload

After completing the capture process, call upload from the main PoseEstimation module.

let poseEstimation = Amani.sharedInstance.poseEstimation()

poseEstimation.upload(location: nil) { uploadSuccess in
if let uploadSuccess {
// uploadSuccess is a Boolean value
// TODO: Handle the upload state
}
}

Summary

Use v1 for the existing random-pose active liveness flow.

let poseV1 = Amani.sharedInstance.poseEstimation().v1

Use v2 for the circle-based active liveness flow.

let poseV2 = Amani.sharedInstance.poseEstimation().v2

V1 and V2 use the same function names for configuration, but their key types are different.

// V1
poseV1.setInfoMessages(infoMessages: [poseState: String])
poseV1.setScreenConfig(screenConfig: [poseConfigState: String])

// V2
poseV2.setInfoMessages(infoMessages: [PoseEstimationV2TextKey: String])
poseV2.setScreenConfig(screenConfig: [PoseEstimationV2ColorKey: String])