Pose Estimation [Active Liveness]
You can access the PoseEstimation module from the base Amani class.
Before using this module, please make sure you've initialized the SDK correctly.
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:
| Version | Description |
|---|---|
v1 | Legacy pose estimation flow. The user is asked to turn their head to random directions such as left, right, up, or down. |
v2 | Circle 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.
| Key | Type | Description |
|---|---|---|
faceIsOk | String | Shows before capturing the selfie |
notInArea | String | Shows when the user's face is not aligned with the area |
faceTooSmall | String | Shows when the user's face is too far |
faceTooBig | String | Shows when the user's face is too close |
completed | String | Shows when the process is complete |
turnRight | String | Shows when the random pose is right |
turnLeft | String | Shows when the random pose is left |
turnUp | String | Shows when the random pose is up |
turnDown | String | Shows when the random pose is down |
lookStraight | String | Shows when the user's face is not straight |
errorMessage | String | Shows when the user has failed the pose flow |
tryAgain | String | Shows when the user is prompted to try again |
errorTitle | String | Shows when the user has failed the pose flow |
next | String | Next pose message |
holdPhoneVertically | String | Prompts the user to hold the phone at the correct angle |
wrongPose | String | Shows when the user failed a pose |
descriptionHeader | String | Shows 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 #.
| Key | Type | Description |
|---|---|---|
appBackgroundColor | String | Screen background color |
appFontColor | String | Text color |
primaryButtonBackgroundColor | String | Primary button background color |
primaryButtonTextColor | String | Primary button text color |
ovalBorderSuccessColor | String | Success border color |
ovalBorderColor | String | Default border color |
poseCount | String | Number of poses in string format |
mainGuideVisibility | String | "true" or "false" |
secondaryGuideVisibility | String | "true" or "false" |
buttonRadius | String | Button 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 type | Description |
|---|---|
| Main guide images | Shown in the center |
| Secondary guide images | Shown at the bottom |
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
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.
| Key | Type | Description |
|---|---|---|
lookStraight | String | Shows when the user needs to look straight before starting the movement |
turnLeft | String | Shows when the user needs to rotate their head to the left direction |
turnRight | String | Shows when the user needs to rotate their head to the right direction |
notInArea | String | Shows when the user's face is not aligned inside the circle |
faceTooFar | String | Shows when the user's face is too far |
faceTooBig | String | Shows when the user's face is too close |
holdPhoneVertically | String | Prompts the user to hold the phone vertically |
keepRotating | String | Shows while the user should keep rotating their head |
completed | String | Shows 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 #.
| Key | Type | Description |
|---|---|---|
appBackgroundColor | String | Screen background color |
overlayBackgroundColor | String | Background color inside the circular overlay |
progressRingColor | String | Base ring color |
progressRingTrackColor | String | Active progress ring color |
appFontColor | String | Text 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.
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])