Acuant iOS SDK v11.6.1
2023 年 6 月
请参阅 https://github.com/Acuant/iOSSDKV11/releases 以查看发行说明。
许可证
本软件受 Acuant 终端用户许可协议(EULA)的约束,可在此处找到。
简介
本文件详细介绍了 Acuant iOS SDK。以下描述了 Acuant 推荐的工作流程。
注意:可接受的图像应为清晰裁剪,无反光,分辨率为至少 300 dpi(用于数据捕获)或 600 dpi(用于认证)。纵横比应该是可接受的,并且与身份证件相符。
更新到11.6.1+
请参阅提供的迁移详情,了解更多关于更新到11.6.0+的信息。
前提条件
- iOS版本11.0或更高
- Xcode 14.0-14.2
注意: Xcode v14.3在通过CocoaPods集成IPLiveness模块时存在已知问题。在寻找解决方案的同时,Acuant建议使用IPLiveness通过CocoaPods的客户继续使用(或降级至)Xcode 14.2。
模块
SDK包含以下模块
Acuant人脸捕获库(AcuantFaceCapture)
- 使用原生iOS相机捕捉人脸。
Acuant被动活性检测库(AcuantPassiveLiveness)
- 使用专有算法检测活人。
Acuant通用库(AcuanCommon)
- 包含所有共享内部模型和类。
Acuant相机库(AcuantCamera)
- 使用原生iOS相机库实现。
- 使用AcuantImagePreparation进行裁剪。
- 使用
https://github.com/SwiftyTesseract/libtesseract在设备上进行OCR。
Acuant图像预处理库(AcuantImagePreparation)
- 包含所有图像处理,如裁剪、锐度和眩光计算。
Acuant文档处理库(AcuantDocumentProcessing)
- 包含上传文档图像、处理和获取结果的所有方法。
Acuant人脸匹配库(AcuantFaceMatch)
- 包含匹配两个面部图像的方法。
Acuant电子芯片库(AcuantEchipReader)
- 包含使用Ozone进行电子护照芯片读取和身份验证的方法。
- 依赖于
https://github.com/krzyzanowskim/OpenSSL
Acuant HG活性检测库(AcuantHGLiveness)
- 使用原生iOS相机库通过专有算法捕捉面部活性。
Acuant IP活性检测库(AcuantIPLiveness)
- 使用专有算法检测活人。
注意: 现在在UI中,IP活性被称为增强活性。
手动设置
-
添加以下依赖的嵌入框架:
- AcuantFaceCapture
- AcuantPassiveLiveness
- AcuantCommon
- AcuantImagePreparation
- AcuantDocumentProcessing
- AcuantHGLiveness
- AcuantFaceMatch
- AcuantEchipReader
- OpenSSL.xcframework
- AcuantCamera
- libtesseract.xcframework(不要嵌入)
- AcuantIPLiveness
- iProov.xcframework
- SocketIO.xcframework
- Starscream.xcframework
注意:AcuantCamera 和 AcuantFaceCapture 是开源项目。您必须将源代码添加到您的解决方案中。
使用 CocoaPods
重要提示:使用 cocoa pods 对开源项目(特别是 AcuantCamera 和 AcuantFaceCapture)进行导入声明时,请在 Xcode 中使用 import AcuantiOSSDKV11
。
-
在 podfile 中添加以下内容以获取所有模块:
platform :ios, '11' use_frameworks! # important pod 'AcuantiOSSDKV11', '~> 11.6.1' #for all packages
或者,使用以下方法在 podfile 中添加独立的模块:
-
AcuantCamera
pod 'AcuantiOSSDKV11/AcuantCamera' dependency AcuantImagePreparation dependency AcuantCommon pod 'AcuantiOSSDKV11/AcuantCamera/Document' # if you want Document camera pod 'AcuantiOSSDKV11/AcuantCamera/Mrz' # if you want MRZ camera dependency TesseractOCRiOS pod 'AcuantiOSSDK11/AcuantCamera/Barcode' # if you want Barcode camera
-
AcuantFaceCapture
pod 'AcuantiOSSDKV11/AcuantFaceCapture'
-
AcuantImagePreparation
pod 'AcuantiOSSDKV11/AcuantImagePreparation' dependency AcuantCommon
-
AcuantDocumentProcessing
pod 'AcuantiOSSDKV11/AcuantDocumentProcessing' dependency AcuantCommon
-
AcuantHGLiveness
pod 'AcuantiOSSDKV11/AcuantHGLiveness' dependency AcuantCommon
-
AcuantIPLiveness
pod 'AcuantiOSSDKV11/AcuantIPLiveness' dependency AcuantCommon dependency iProov
-
AcuantPassiveLiveness
pod 'AcuantiOSSDKV11/AcuantPassiveLiveness' dependency AcuantCommon
-
AcuantFaceMatch
pod 'AcuantiOSSDKV11/AcuantFaceMatch' dependency AcuantCommon
-
AcuantEchipReader
pod 'AcuantiOSSDKV11/AcuantEchipReader' dependency AcuantCommon dependency OpenSSL
-
在构建设置中为所有 Acuant pod 框架启用 "BUILD_LIBRARY_FOR_DISTRIBUTION"。
-
使用 Cocoapods。将其添加到您的 Podfile 中。
post_install do |installer| installer.pods_project.targets.each do |target| if ['AcuantiOSSDKV11', 'Socket.IO-Client-Swift', 'Starscream'].include? target.name target.build_configurations.each do |config| config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' end end end end
-
手动添加
-
所需设置
创建一个名为 AcuantConfig 的 plist 文件,其中包含以下详细信息:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>acuant_username</key>
<string>[email protected]</string>
<key>acuant_password</key>
<string>xxxxxxxxxx</string>
<key>acuant_subscription</key>
<string>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</string>
<key>frm_endpoint</key>
<string>https://frm.acuant.net</string>
<key>passive_liveness_endpoint</key>
<string>https://us.passlive.acuant.net</string>
<key>med_endpoint</key>
<string>https://medicscan.acuant.net</string>
<key>assureid_endpoint</key>
<string>https://services.assureid.net</string>
<key>acas_endpoint</key>
<string>https://acas.acuant.net</string>
<key>ozone_endpoint</key>
<string>https://ozone.acuant.net</string>
</dict>
</plist>
The following are the default values for testing purposes:
PREVIEW
<key>frm_endpoint</key>
<string>https://preview.face.acuant.net</string>
<key>passive_liveness_endpoint</key>
<string>https://preview.passlive.acuant.net</string>
<key>med_endpoint</key>
<string>https://preview.medicscan.acuant.net</string>
<key>assureid_endpoint</key>
<string>https://preview.assureid.acuant.net</string>
<key>acas_endpoint</key>
<string>https://preview.acas.acuant.net</string>
<key>ozone_endpoint</key>
<string>https://preview.ozone.acuant.net</string>
The following are the default values based on region:
USA
<key>frm_endpoint</key>
<string>https://frm.acuant.net</string>
<key>passive_liveness_endpoint</key>
<string>https://us.passlive.acuant.net</string>
<key>med_endpoint</key>
<string>https://medicscan.acuant.net</string>
<key>assureid_endpoint</key>
<string>https://services.assureid.net</string>
<key>acas_endpoint</key>
<string>https://acas.acuant.net</string>
<key>ozone_endpoint</key>
<string>https://ozone.acuant.net</string>
EU
<key>frm_endpoint</key>
<string>https://eu.frm.acuant.net</string>
<key>passive_liveness_endpoint</key>
<string>https://eu.passlive.acuant.net</string>
<key>assureid_endpoint</key>
<string>https://eu.assureid.acuant.net</string>
<key>acas_endpoint</key>
<string>https://eu.acas.acuant.net</string>
<key>ozone_endpoint</key>
<string>https://eu.ozone.acuant.net</string>
AUS
<key>frm_endpoint</key>
<string>https://aus.frm.acuant.net</string>
<key>passive_liveness_endpoint</key>
<string>https://aus.passlive.acuant.net</string>
<key>assureid_endpoint</key>
<string>https://aus.assureid.acuant.net</string>
<key>acas_endpoint</key>
<string>https://aus.acas.acuant.net</string>
<key>ozone_endpoint</key>
<string>https://aus.ozone.acuant.net</string>
初始化
初始化 在使用SDK之前,您必须对其进行初始化,可通过使用设备上保存的凭据或使用由外部服务器提供的令牌( bearer tokens)来实现。
-
选择初始化的软件包。
let packages = [AcuantEchipPackage(), ImagePreparationPackage()]
-
初始化SDK。
let initalizer: IAcuantInitializer = AcuantInitializer() let task = initalizer.initialize(packages: packages){ [weak self] error in if let err = error{ //error } else{ //success } }
注意: 如果您不使用配置文件进行初始化,则使用以下语句(为用户名(username)、密码(password)和订阅ID(subscription ID)提供适当的凭据)。
Credential.setUsername(username: "xxx") Credential.setPassword(password: "xxxx") Credential.setSubscription(subscription: "xxxxxx") let endpoints = Endpoints() endpoints.frmEndpoint = "https://frm.acuant.net" endpoints.healthInsuranceEndpoint = "https://medicscan.acuant.net" endpoints.idEndpoint = "https://services.assureid.net" endpoints.acasEndpoint = "https://acas.acuant.net" endpoints.ozoneEndpoint = "https://ozone.acuant.net" Credential.setEndpoints(endpoints: endpoints)
令牌(bearer tokens)
-
使用Acuant服务来获取令牌。Acuant建议使用代理服务来获取令牌,以确保凭据的完整性。
-
设置令牌
if let success = Credential.setToken(token: ""){ //token was valid } else{ //invalid or expired token }
-
令牌最终会过期,具体取决于提供的设置。使用以下方法确保在重新使用令牌之前令牌仍有效。
if let token : AcuantJwtToken = Credential.getToken(){ let valid: Bool = token.isValid() }
-
注意: 如果设置了令牌,所有服务调用都将尝试使用该令牌进行授权。如果未设置令牌,则将使用传统凭据。
不带订阅ID的初始化
AcuantImagePreparation 可以通过只提供用户名和密码进行初始化。但是,如果不提供订阅ID,应用程序只能捕捉图像并获得图像。
不带订阅ID的初始化
- 只能使用AcuantCamera、AcuantImagePreparation和AcuantHGLiveness模块。
- SDK可以用来捕捉身份证明文件。
- 捕获的图像可以从SDK中导出。请参阅AcuantCamera项目中的DocumentCaptureDelegate协议。
使用AcuantCamera捕获图像
AcuantCamera在肖像模式下使用最佳。在使用相机之前锁定应用的方向。
-
要获取文档图像和条码字符串,实现
DocumentCameraViewControllerDelegate
协议。public protocol DocumentCameraViewControllerDelegate { func onCaptured(image: Image, barcodeString: String?) } extension MyViewController: DocumentCameraViewControllerDelegate { func onCaptured(image: Image, barcodeString: String?) { if let result = image.image { } else { //user has cancelled } } }
-
自定义、创建并打开相机。对于所有可配置字段,请参阅
DocumentCameraOptions
。let textForState: (DocumentCameraState) -> String = { state in switch state { case .align: return "ALIGN" case .moveCloser: return "MOVE CLOSER" case .tooClose: return "TOO CLOSE" case .steady: return "HOLD STEADY" case .hold: return "HOLD" case .capture: return "CAPTURING" @unknown default: return "" } } let options = DocumentCameraOptions(autoCapture: true, hideNavigationBar: true, textForState: textForState) let documentCameraViewController = DocumentCameraViewController(options: options) documentCameraViewController.delegate = self navigationController.pushViewController(documentCameraViewController, animated: false)
注意: 当相机启动时,会自动检查图像处理速度。
- 如果设备支持130ms以上的速度,将启用实时文档检测和自动捕获功能。
- 对于不满足处理阈值的设备,将启用点击捕获。实时文档检测和自动捕获功能将禁用,并切换到点击捕获。用户需要手动捕获文件。
使用DocumentCaptureSession自定义UI(参见DocumentCameraViewController.swift获取参考)
-
要获取图像和条码字符串,实现DocumentCaptureSessionDelegate协议。
public protocol DocumentCaptureSessionDelegate { func readyToCapture() func documentCaptured(image: UIImage, barcodeString: String?) }
-
(可选)实现AutoCaptureDelegate和FrameAnalysisDelegate协议来定义自动捕获行为并接收帧结果。
public protocol AutoCaptureDelegate { func getAutoCapture() -> Bool func setAutoCapture(autoCapture: Bool) } public protocol FrameAnalysisDelegate { func onFrameAvailable(frameResult: FrameResult, points: [CGPoint]?) } public enum FrameResult: Int { case noDocument, smallDocument, badAspectRatio, goodDocument, documentNotInFrame }
-
创建捕获会话。
let captureDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: AVMediaType.video, position: .back)! let captureSession = DocumentCaptureSession(captureDevice: captureDevice) captureSession.delegate = self captureSession.autoCaptureDelegate = self //optional captureSession.frameDelegate = self //optional
-
将捕获会话添加到AVCaptureVideoPreviewLayer,然后启动它。
let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) captureSession.start() //Starts the capture session asynchronously on a background queue and notifies //the caller once the session is started via a completion handler. //The completion handler is optional and will be called on the main queue. //write your own custom UI code
-
触发捕获。
captureSession.enableCapture()
-
停止捕获会话。
captureSession.stop() // stops the capture session asynchronously on a background queue
注意:AcuantCamera依赖于AcuantImagePreparation和AcuantCommon。
使用AcuantCamera捕获文档条码
注意:在实际捕获文档时,相机会尝试读取条码。只有当根据文档分类预计将出现条码并且在正常捕获相关侧面时读取失败时,才启动此相机模式。
-
要获取条码字符串,实现BarcodeCameraViewControllerDelegate协议。
public protocol BarcodeCameraViewControllerDelegate: AnyObject { func onCaptured(barcode: String?) }
-
自定义、创建并打开相机。对于所有可配置字段,请参阅BarcodeCameraOptions。
let options = BarcodeCameraOptions(waitTimeAfterCapturingInSeconds: 1, timeoutInSeconds: 20) let barcodeCamera = BarcodeCameraViewController(options: options) barcodeCamera.delegate = self navigationController?.pushViewController(barcodeCamera, animated: false)
使用AcuantCamera读取MRZ
-
如果您手动整合SDK,请确保链接libtesseract.xcframework并选择“不嵌入”。
-
将OCR B训练数据添加到项目的引用文件夹 tessdata 中。Acuant建议使用Sample App的Assets目录下的训练数据资源。请参阅 https://www.kodeco.com/2010498-tesseract-ocr-tutorial-for-ios#toc-anchor-005。
-
实现
MrzCameraViewControllerDelegate
协议以获取MRZ结果。public protocol MrzCameraViewControllerDelegate: AnyObject { func onCaptured(mrz: AcuantMrzResult?) }
-
自定义、创建并打开相机。有关所有可配置字段,请参阅 MrzCameraOptions 。
let textForState: (MrzCameraState) -> String = { state in switch state { case .none, .align: return "" case .moveCloser: return "Move Closer" case .tooClose: return "Too Close!" case .good: return "Reading MRZ" case .captured: return "Captured" case .reposition: return "Reposition" @unknown default: return "" } } let options = MrzCameraOptions(textForState: textForState) let mrzCameraViewController = MrzCameraViewController(options: options) mrzCameraViewController.delegate = self navigationController?.pushViewController(mrzCameraViewController, animated: false)
-
结果。
public class AcuantMrzResult { public var surName: String = "" public var givenName: String = "" public var country: String = "" public var passportNumber: String = "" public var nationality: String = "" public var dob: String = "" public var gender: String = "" public var passportExpiration: String = "" public var personalDocNumber: String = "" public var threeLineMrz: Bool = false public var checkSumResult1: Bool = false public var checkSumResult2: Bool = false public var checkSumResult3: Bool = false public var checkSumResult4: Bool = false public var checkSumResult5: Bool = false }
使用AcuantEchipReader
-
将应用程序配置为检测NFC标签。在Xcode的“签名和功能”下将“近场通信”功能添加到目标应用程序中。然后,将“NFCReaderUsageDescription”键添加到应用程序的Info.plist中。更多信息请参阅 https://developer.apple.com/documentation/corenfc/building_an_nfc_tag-reader_app。
-
在应用程序信息.plist中添加带有“ISO7816应用程序标识符(A0000002471001)”的项作为值。
-
AcuantEchipPackage必须在上一步骤中初始化。
-
此功能仅适用于iOS版本13及以上。您需要使用@available属性来使用模块中的方法。
-
创建Acuant Reader的实例。确保在读取过程中不要丢弃此对象。
private let reader: IEchipReader = EchipReader()
-
创建会话请求。
let request = AcuantEchipSessionRequest(passportNumber: "", dateOfBirth: "", expiryDate: "")
-
根据读取器的状态设置自定义消息。
public enum AcuantEchipDisplayMessage { case requestPresentPassport case authenticatingWithPassport(Int) case readingDataGroupProgress(String, Int) case error case authenticatingExtractedData case successfulRead } let customDisplayMessage : ((AcuantEchipDisplayMessage) -> String?) = { message in switch message { case .requestPresentPassport: return "Hold your iPhone near an NFC enabled passport." case .authenticatingWithPassport(let progress): let progressString = handleProgress(percentualProgress: progress) return "Authenticating with passport.....\n\n\(progressString)" case .readingDataGroupProgress(let dataGroup, let progress): let progressString = handleProgress(percentualProgress: progress) return "Reading \(dataGroup).....\n\n\(progressString)" case .error: return "Sorry, there was a problem reading the passport. Please try again" case .successfulRead: return "Passport read successfully" case .authenticatingExtractedData: return "Authenicating with Ozone" } }
-
开始读取。
self.reader.readNfcTag(request: request, customDisplayMessage: customDisplayMessage) { [weak self] model, error in if let result = model { //success } else if let error = error { //error } else { //user canceled } }
-
结果。
public class AcuantPassportModel { public var documentType: String public var documentSubType: String public var documentCode: String public var translatedDocumentType: TranslatedDocumentType public var personalNumber: String public var documentNumber: String public var issuingAuthority: String public var documentExpiryDate: String public var firstName: String public var lastName: String public var dateOfBirth: String public var gender: String public var nationality: String public var image: UIImage? public var signatureImage: UIImage? public var passportSigned: OzoneResultStatus public var passportCountrySigned: OzoneResultStatus public var PACEStatus: AuthStatus public var BACStatus: AuthStatus public var chipAuthenticationStatus: AuthStatus public var activeAuthenticationStatus: AuthStatus public var passportDataValid: Bool public var age: Int? public var isExpired: Bool? public func getRawDataGroup(dgId: AcuantDataGroupId) -> [UInt8]? { return self.dataGroups[dgId] } } public enum AuthStatus { case success case failure case skipped } public enum OzoneResultStatus: Int { case SUCCESS case FAILED case UNKNOWN } public enum AcuantDataGroupId: Int { case COM case DG1 case DG2 case DG3 case DG4 case DG5 case DG6 case DG7 case DG8 case DG9 case DG10 case DG11 case DG12 case DG13 case DG14 case DG15 case DG16 case SOD case Unknown } public enum TranslatedDocumentType: String, CaseIterable { case `default` = "Default" case nationalPassport = "National Passport" case emergencyPassport = "Emergency Passport" case diplomaticPassport = "Diplomatic Passport" case officialOrServicePassport = "Official/Service Passport" case refugeePassport = "Refugee Passport" case alienPassport = "Alien Passport" case statelessPassport = "Stateless Passport" case travelDocument = "Travel Document" case militaryPassport = "Military Passport" }
-
将国家与护照映射。
public let AcuantCountryDataPageMap: [String: String] //{ "CountryCode": "Location" }
AcuantImagePreparation
此模块包含所有图像准备功能。
裁剪、锐度和炫光
在捕获图像后,会对其执行裁剪并检查锐度和炫光。这是通过使用 AcuantImagePreparation 的 evaluateImage 来实现的。
public class func evaluateImage(data: CroppingData, callback: (AcuantImage, AcuantError) -> ())
要创建上述的 CropData,请使用以下方法,并将从相机接收到的图像传递给该方法
CroppingData.newInstance(image: Image)
回调返回 AcuantImage 和 AcuantError。可以使用 AcuantImage 验证图像的裁剪、锐度和炫光,然后在下一步上传文档(参见 AcuantDocumentProcessing)。
public class AcuantImage {
public let image: UIImage
public let data: NSData
public let sharpness: Int
public let glare: Int
public let dpi: Int
public let isPassport: Bool
}
如果锐度值大于50,则认为图像是锐利的(不是模糊的)。如果炫光值为100,则图像不含有炫光。如果炫光值为0,则图像含有炫光。
为了在认证和数据提取中获得最佳结果,图像必须既尖锐又不含有炫光。如果图像有炫光、低锐度或两者都有,则重试捕获。
注意:如果您正在使用独立的编排层,请确保供电 AcuantImage.data,而不是 AcuantImage.image。AcuantImage.image 仅提供给应用程序内的视觉使用(例如,用于将裁剪结果呈现给用户进行视觉验证)。在上传之前,不要以任何方式修改 AcuantImage.data。
AcuantDocumentProcessing
捕获文档图像并完成裁剪后,可以使用以下步骤进行处理。
注意:如果上传失败并出现错误,请使用更好的图像重试图像上传。
-
创建实例
public class func createInstance(options:IdOptions,delegate:CreateInstanceDelegate) public protocol CreateInstanceDelegate{ func instanceCreated(instanceId: String?,error:AcuantError?); }
-
准备图像。必须在 EvaluatedImageData 中传递 AcuantImage.data 和 idData.barcodeString(如有适用)。
public class EvaluatedImageData { public let imageBytes: NSData public let barcodeString: String? public init(imageBytes: NSData, barcodeString: String?) }
-
上传图像
public class func uploadImage(instancdId:String,data:EvaluatedImageData,options:IdOptions,delegate:UploadImageDelegate) public protocol UploadImageDelegate{ func imageUploaded(error: AcuantError?,classification:Classification?); }
-
获取数据
public class func getData(instanceId:String,isHealthCard:Bool,delegate:GetDataDelegate?) public protocol UploadImageDelegate{ func imageUploaded(error: AcuantError?,classification:Classification?); }
-
删除实例
public class func deleteInstance(instanceId : String,type:DeleteType, delegate:DeleteDelegate) public protocol DeleteDelegate { func instanceDeleted(success : Bool) }
Acuant Face Capture
-
(可选) 设置默认图像。在示例应用程序项目的 Assets 目录中定位 "acuan_default_face_image.png"。如果需要,将其添加到您的应用程序中。
-
将应用程序可本地化部分中的本地化字符串添加至指定位置这里。
-
配置所需的UI自定义。
class FaceCameraOptions{ public let totalCaptureTime: Int //totoal time to capture public let bracketColorDefault: CGColor //bracket color default (no face) public let bracketColorError: CGColor //bracket color error (error in face requirements) public let bracketColorGood: CGColor //bracket color good (good face requirements) public let fontColorDefault: CGColor //font color default public let fontColorError: CGColor //font color error public let fontColorGood: CGColor //font color good public let defaultImageUrl: String //default image public let showOval: Bool // show oval } //example let options = FaceCameraOptions()
-
获取控制器并将其推送到navigationController。
let controller = FaceCaptureController() controller.options = options controller.callback = { [weak self] (result: FaceCaptureResult?) in if (result == nil) { //user canceled } } self.navigationController.pushViewController(controller, animated: true)
-
使用捕获的结果调用回调。
Acuant 无动作活体检测
Acuant 建议使用 LiveAssessment 属性而不是分数来评估响应。调用 PassiveLiveness.startSelfieCapture 会返回一个重新调整大小的图像。
遵循以下建议以有效地处理用于无动作活体的图像
图像要求
- 高度:最小 480 像素;推荐 720 或 1080 像素
- 压缩:不建议对图像进行压缩(JPEG 70 级别或以上是可接受的)。为了获得最佳效果,请使用未压缩的图像。
人脸要求
- 水平面外旋转:人脸俯仰角和偏航角:从 -20 到 20 度 +/-3 度
- 水平面内旋转:人脸滚动角:从 -30 到 30 度 +/- 3 度
- 瞳距:眼睛之间的最小距离为 90 +/- 5 像素
- 人脸大小:任意维度最小 200 像素
- 每张图像的人脸数量:1
- 太阳镜:必须摘下
捕获需求
以下情况可能显著增加错误或假结果
- 使用运动模糊效果
- 纹理过滤
- 前景光线聚焦于面部和附近环境
- 光线昏暗或颜色混浊的环境
注意:此API不支持使用鱼眼镜头。
-
带数据获取被动活动性结果
//liveness request class AcuantLivenessRequest { public let jpegData: Data public init(jpegData: Data) } //liveness response class AcuantLivenessResponse { public let score: Int public let result: AcuantLivenessAssessment public enum AcuantLivenessAssessment: String { case Error case PoorQuality case Live case NotLive } } //liveness response class AcuantLivenessError { public let errorCode: AcuantLivenessErrorCode? public let description: String? public enum AcuantLivenessErrorCode: String { case Unknown case FaceTooClose case FaceNotFound case FaceTooSmall case FaceAngleTooLarge case FailedToReadImage case InvalidRequest case InvalidRequestSettings case Unauthorized case NotFound case InternalError case InvalidJson } } //example PassiveLiveness.postLiveness(request: AcuantLivenessRequest(jpegData: jpegData)) { [weak self] (result: AcuantLivenessResponse?, error: AcuantLivenessError?) in //response }
AcuantHGLiveness
该模块通过检测眨眼来判断活动性(主题是否为真人)。此模块的用户界面代码包含在示例应用程序中(FaceLivenessCameraController.swift),客户可根据特定需求对其进行修改。
示例应用程序中的Acuant UI
// Code for HG Live controller
let liveFaceViewController = FaceLivenessCameraController()
liveFaceViewController.delegate : AcuantHGLiveFaceCaptureDelegate = self
self.navigationController.pushViewController(liveFaceViewController, animated: true)
自定义UI,创建面部活动捕获会话
enum AcuantFaceType : Int {
case NONE // No face was detected
case FACE_TOO_CLOSE // Face is too close to the camera
case FACE_MOVED // Face is no longer in its original position
case FACE_TOO_FAR // Face is too far from camera
case FACE_GOOD_DISTANCE // Face is at the appropriate distance and is in frame
case FACE_NOT_IN_FRAME // Face is not in the frame
case FACE_HAS_ANGLE // Face is angled either roll or yaw
}
public protocol AcuantHGLiveFaceCaptureDelegate {
func liveFaceDetailsCaptured(liveFaceDetails: LiveFaceDetails?, faceType: HGLiveness.AcuantFaceType)
}
public class func getFaceCaptureSession(delegate:AcuantHGLiveFaceCaptureDelegate?, captureDevice: AVCaptureDevice?)-> FaceCaptureSession
let faceCaptureSession = HGLiveness.getFaceCaptureSession(delegate: self, captureDevice: captureDevice)
AcuantIPLiveness
AcuantIPLiveness模块检查主题是否为真人。
-
将应用程序可本地化部分中的本地化字符串添加至指定位置这里。
-
运行设置
IPLiveness.performLivenessSetup(delegate:LivenessSetupDelegate) public protocol LivenessSetupDelegate{ func livenessSetupSucceeded(result:LivenessSetupResult) // Called when setup succeeds func livenessSetupFailed(error:AcuantError) // Called when setup failed } public class LivenessSetupResult { public var apiKey : String public var token : String public var userId : String public var apiEndpoint : String }
-
执行活动性测试
注意:您可以使用LivenessSetupResult来根据需要自定义UI。
// Adjust various colors for the camera preview: setupResult.ui.lineColor = .white setupResult.ui.backgroundColor = .black setupResult.ui.loadingTintColor = .lightGray setupResult.ui.notReadyTintColor = .orange setupResult.ui.readyTintColor = .green setupResult.ui.title = "title" // Specify a custom title to be shown. Defaults to nil which will show an auto generated message. Set to empty string ("") to hide the message entirely. setupResult.ui.regularFont = "SomeFont" setupResult.ui.boldFont = "SomeFont-Bold" setupResult.ui.fonts = ["SomeFont", "SomeFont-Bold"] // If using custom fonts, specify them here (don't forget to add them to your Info.plist!) setupResult.ui.logoImage = UIImage(named: "foo") setupResult.ui.scanLineDisabled = false // Disables the vertical sweeping scanline while flashing setupResult.ui.autoStartDisabled = false // Disable the "auto start" countdown functionality. The user will have to tap the screen to start liveness test IPLiveness.performLivenessTest(setupResult:LivenessSetupResult, delegate:LivenessTestDelegate) public protocol LivenessTestDelegate{ func livenessTestCompleted() // This is for the test; called when Enroll is complete func livenessTestCompletedWithError(error:AcuantError?) // This is for the test; called when Enroll is complete and error occured func livenessTestProcessing(progress: Double, message: String) // This is for real-time notifications of progress of liveness test. It will be called after user captures live face. It is intended to be used for custom UI progress notification. func livenessTestConnecting() // Will be called before face capture starts. Use for custom UI while test is connecting. func livenessTestConnected() // Will be called as face capture starts. Can usually be blank or can be used to clear any custom connecting UI if needed. }
-
获取活动性测试结果
IPLiveness.getLivenessTestResult(token:String,userId:String,delegate:LivenessTestResultDelegate) public protocol LivenessTestResultDelegate{ func livenessTestResultReceived(result:LivenessResult) // Called when test result was received successfully func livenessTestResultReceiveFailed(error:AcuantError) // Called when test result was not received } public class LivenessTestResult { public var passedLivenessTest : Bool = false public var image : UIImage? = nil }
以下是依赖列表
- iProov.xcframework
- SocketIO.xcframework
- Startscream.xcframework
AcuantFaceMatch
此模块用于匹配两张面部图像
public class func processFacialMatch(facialData: FacialMatchData, delegate: FacialMatchDelegate)
public protocol FacialMatchDelegate {
func facialMatchFinished(result: FacialMatchResult?)
}
public class FacialMatchData {
public var faceOneData: Data // Facial image from ID Card (image gets compressed by 80%)
public var faceTwoData: Data // Facial image from selfie capture during liveness check (image gets compressed by 80%)
}
语言本地化
为了显示对应语言下的文本,您需要将以下可本地化的字符串添加到您的应用程序的可本地化
AcuantFaceCapture
"acuant_face_camera_initial" = "Align face to start capture";
"acuant_face_camera_face_too_close" = "Too Close! Move Away";
"acuant_face_camera_face_too_far" = "Move Closer";
"acuant_face_camera_face_has_angle" = "Face has Angle. Do not tilt";
"acuant_face_camera_face_not_in_frame" = "Move in Frame";
"acuant_face_camera_face_moved" = "Hold Steady";
"acuant_face_camera_capturing_2" = "Capturing\n2...";
"acuant_face_camera_capturing_1" = "Capturing\n1...";
"acuant_face_camera_rotate_portrait" = "Face can only be captured in portrait";
"acuant_face_camera_paused" = "Camera paused";
AcuantIPLiveness
"IProov_LanguageFile" = "en-US";
"IProov_PromptTapToBegin" = "Tap the screen to begin";
"IProov_PromptTooFar" = "Move closer";
"IProov_PromptTooBright" = "Go somewhere shadier";
"IProov_PromptLivenessScanCompleted" = "Scan completed";
"IProov_PromptGenuinePresenceAlignFace" = "Put your face in the oval";
"IProov_PromptLivenessAlignFace" = "Fill the oval with your face";
"IProov_PromptLivenessNoTarget" = "Put your face in the frame";
"IProov_ProgressStreaming" = "Streaming…";
"IProov_ProgressStreamingSlow" = "Streaming, network is slow…";
"IProov_PromptScanning" = "Scanning…";
"IProov_ProgressIdentifyingFace" = "Identifying face…";
"IProov_ProgressConfirmingIdentity" = "Confirming identity…";
"IProov_ProgressAssessingGenuinePresence" = "Assessing genuine presence…";
"IProov_ProgressAssessingLiveness" = "Assessing liveness…";
"IProov_ProgressLoading" = "Loading…";
"IProov_ProgressCreatingIdentity" = "Creating identity…";
"IProov_ProgressFindingFace" = "Finding face…";
"IProov_Authenticate" = "Authenticate";
"IProov_Enrol" = "Enrol";
"IProov_MessageFormat" = "%@ to %@";
"IProov_PromptTooClose" = "Too close";
"IProov_FailureMotionTooMuchMovement" = "Please do not move while iProoving";
"IProov_FailureLightingFlashReflectionTooLow" = "Ambient light too strong or screen brightness too low";
"IProov_FailureLightingBacklit" = "Strong light source detected behind you";
"IProov_FailureLightingTooDark" = "Your environment appears too dark";
"IProov_FailureLightingFaceTooBright" = "Too much light detected on your face";
"IProov_FailureMotionTooMuchMouthMovement" = "Please do not talk while iProoving";
"IProov_MessageFormatWithUsername" = "%@ as %@ to %@";
"IProov_PromptRollTooHigh" = "Avoid tilting your head";
"IProov_PromptRollTooLow" = "Avoid tilting your head";
"IProov_PromptYawTooLow" = "Turn slightly to your right";
"IProov_PromptYawTooHigh" = "Turn slightly to your left";
"IProov_PromptPitchTooHigh" = "Hold the device at eye level";
"IProov_PromptPitchTooLow" = "Hold the device at eye level";
"IProov_ErrorNetwork" = "Network error";
"IProov_ErrorCameraPermissionDenied" = "Camera permission denied";
"IProov_ErrorCameraPermissionDeniedMessageIos" = "Please allow camera access for this app in iOS Settings";
"IProov_ErrorServer" = "Server error";
"IProov_ErrorUnexpected" = "Unexpected error";
"IProov_ErrorCaptureAlreadyActive" = "An existing capture is already in progress";
"IProov_PromptGetReady" = "Get ready…";
"IProov_PromptGrantPermission" = "Grant Camera Access";
"IProov_PromptGrantPermissionMessage" = "Camera access must be granted to use iProov";
"IProov_FailureAmbiguousOutcome" = "Ambiguous outcome";
错误代码
public class AcuantErrorCodes{
public static let ERROR_InvalidCredentials = -1
public static let ERROR_InvalidEndpoint = -3
public static let ERROR_InitializationNotFinished = -4
public static let ERROR_Network = -5
public static let ERROR_InvalidJson = -6
public static let ERROR_CouldNotCrop = -7
public static let ERROR_CouldNotCreateConnectInstance = -13
public static let ERROR_CouldNotUploadConnectImage = -14
public static let ERROR_CouldNotUploadConnectBarcode = -15
public static let ERROR_CouldNotGetConnectData = -16
public static let ERROR_CouldNotClassifyDocument = -20
public static let ERROR_LowResolutionImage = -21
public static let ERROR_BlurryImage = -22
public static let ERROR_ImageWithGlare = -23
public static let ERROR_CouldNotGetIPLivenessToken = -24
public static let ERROR_NotALiveFace = -25
public static let ERROR_CouldNotAccessLivenessData = -26
public static let ERROR_CouldNotAccessCredential = -27
public static let ERROR_USER_CANCELED_ACTIVITY = -28
public static let ERROR_INVALID_PARAMETER = -29
//ozone
public static let ERROR_OzoneInvalidFormat = -50;
public static let ERROR_OzoneNotAuthorized = -51;
//echip
public static let ERROR_EChipReadError = -60;
public static let ERROR_InvalidNfcTag = -61;
public static let ERROR_InvalidNfcKeyFormatting = -62;
}
错误描述
public class AcuantErrorDescriptions {
public static let ERROR_DESC_InvalidCredentials = "Invalid credentials"
public static let ERROR_DESC_InvalidLicenseKey = "Invalid License Key"
public static let ERROR_DESC_InvalidEndpoint = "Invalid endpoint"
public static let ERROR_DESC_Network = "Network problem"
public static let ERROR_DESC_InitializationNotFinished = "Initialization not finished"
public static let ERROR_DESC_InvalidJson = "Invalid Json response"
public static let ERROR_DESC_CouldNotCrop = "Could not crop image"
public static let ERROR_DESC_BarcodeCaptureFailed = "Barcode capture failed"
public static let ERROR_DESC_BarcodeCaptureTimedOut = "Barcode capture timed out"
public static let ERROR_DESC_BarcodeCaptureNotAuthorized = "Barcode capture is not authorized"
public static let ERROR_DESC_LiveFaceCaptureNotAuthorized = "Live face capture is not authorized"
public static let ERROR_DESC_CouldNotCreateConnectInstance = "Could not create connect Instance"
public static let ERROR_DESC_CouldNotUploadConnectImage = "Could not upload image to connect instance"
public static let ERROR_DESC_CouldNotUploadConnectBarcode = "Could not upload barcode to connect instance"
public static let ERROR_DESC_CouldNotGetConnectData = "Could not get connect image data"
public static let ERROR_DESC_CardWidthNotSet = "Card width not set"
public static let ERROR_DESC_CouldNotGetHealthCardData = "Could not get health card data"
public static let ERROR_DESC_CouldNotClassifyDocument = "Could not classify document"
public static let ERROR_DESC_LowResolutionImage = "Low resolution image"
public static let ERROR_DESC_BlurryImage = "Blurry image"
public static let ERROR_DESC_ImageWithGlare = "Image has glare"
public static let ERROR_DESC_CouldNotGetIPLivenessToken = "Could not get face liveness token"
public static let ERROR_DESC_NotALiveFace = "Not a live face"
public static let ERROR_DESC_CouldNotAccessLivenessData = "Could not access liveness data"
public static let ERROR_DESC_ERROR_CouldNotAccessCredential = "Could not get credential"
public static let ERROR_DESC_USER_CANCELED_ACTIVITY = "User canceled activity"
public static let ERROR_DESC_INVALID_PARAMETERS = "Invalid Parameters."
public static let ERROR_DESC_OzoneInvalidFormat = "Ozone returned invalid format";
public static let ERROR_DESC_OzoneNotAuthorized = "Credentials not authorized for ozone";
public static let ERROR_DESC_EChipReadError = "Error reading eChip. Connection lost to passport or incorrect key.";
public static let ERROR_DESC_InvalidNfcTag = "Tag Tech list was null. Most likely means unsupported passport/not a passport";
public static let ERROR_DESC_InvalidNfcKeyFormatting = "Decryption key formatted incorrectly. Check DOB, DOE, and doc number.";
}
图片
public class Image {
public var image: UIImage? = nil
public var dpi: Int = 0 // dpi value of the captured image
public var error: AcuantError? = nil
public var isCorrectAspectRatio = false // If the captured image has the correct aspect ratio
public var aspectRatio: Float = 0.0 // Aspect ratio of the captured image
public var points: Array<CGPoint> = []
public var isPassport = false
public init(){}
}
文档相机选项
public class DocumentCameraOptions: CameraOptions {
public let countdownDigits: Int
public let timeInSecondsPerCountdownDigit: Double
public let textForManualCapture: String
public let textForState: (DocumentCameraState) -> String
public let colorForState: (DocumentCameraState) -> CGColor
public init(countdownDigits: Int = 2,
timeInSecondsPerCountdownDigit: Double = 0.9,
showDetectionBox: Bool = true,
autoCapture: Bool = true,
hideNavigationBar: Bool = true,
showBackButton: Bool = true,
bracketLengthInHorizontal: Int = 80,
bracketLengthInVertical: Int = 50,
defaultBracketMarginWidth: CGFloat = 0.5,
defaultBracketMarginHeight: CGFloat = 0.6,
textForManualCapture: String = "ALIGN & TAP",
textForState: @escaping (DocumentCameraState) -> String = { state in
switch state {
case .align: return "ALIGN"
case .moveCloser: return "MOVE CLOSER"
case .tooClose: return "TOO CLOSE"
case .steady: return "HOLD STEADY"
case .hold: return "HOLD"
case .capture: return "CAPTURING"
@unknown default: return ""
}
},
colorForState: @escaping (DocumentCameraState) -> CGColor = { state in
switch state {
case .align: return UIColor.black.cgColor
case .moveCloser: return UIColor.red.cgColor
case .tooClose: return UIColor.red.cgColor
case .steady: return UIColor.yellow.cgColor
case .hold: return UIColor.yellow.cgColor
case .capture: return UIColor.green.cgColor
@unknown default: return UIColor.black.cgColor
}
},
textForCameraPaused: String = "CAMERA PAUSED",
backButtonText: String = "BACK",
preventScreenshots: Bool = true)
}
条形码相机选项
public class BarcodeCameraOptions: CameraOptions {
public let waitTimeAfterCapturingInSeconds: Int
public let timeoutInSeconds: Int
public let textForState: (BarcodeCameraState) -> String
public let colorForState: (BarcodeCameraState) -> CGColor
public init(hideNavigationBar: Bool = true,
showBackButton: Bool = true,
waitTimeAfterCapturingInSeconds: Int = 1,
timeoutInSeconds: Int = 20,
textForState: @escaping (BarcodeCameraState) -> String = { state in
switch state {
case .align: return "CAPTURE BARCODE"
case .capturing: return "CAPTURING"
@unknown default: return ""
}
},
colorForState: @escaping (BarcodeCameraState) -> CGColor = { state in
switch state {
case .align: return UIColor.white.cgColor
case .capturing: return UIColor.green.cgColor
@unknown default: return UIColor.white.cgColor
}
},
textForCameraPaused: String = "CAMERA PAUSED",
backButtonText: String = "BACK",
placeholderImageName: String? = "barcode_placement_overlay",
preventScreenshots: Bool = true)
}
MRZ相机选项
public class MrzCameraOptions: CameraOptions {
public let textForState: (MrzCameraState) -> String
public let colorForState: (MrzCameraState) -> CGColor
public init(showDetectionBox: Bool = true,
bracketLengthInHorizontal: Int = 50,
bracketLengthInVertical: Int = 40,
defaultBracketMarginWidth: CGFloat = 0.58,
defaultBracketMarginHeight: CGFloat = 0.63,
hideNavigationBar: Bool = true,
showBackButton: Bool = true,
placeholderImageName: String? = "Passport_placement_Overlay",
textForState: @escaping (MrzCameraState) -> String = { state in
switch state {
case .none, .align: return ""
case .moveCloser: return "Move Closer"
case .tooClose: return "Too Close!"
case .good: return "Reading MRZ"
case .captured: return "Captured"
case .reposition: return "Reposition"
@unknown default: return ""
}
},
colorForState: @escaping (MrzCameraState) -> CGColor = { state in
switch state {
case .none, .align: return UIColor.black.cgColor
case .moveCloser: return UIColor.red.cgColor
case .tooClose: return UIColor.red.cgColor
case .good: return UIColor.yellow.cgColor
case .captured: return UIColor.green.cgColor
case .reposition: return UIColor.red.cgColor
@unknown default: return UIColor.black.cgColor
}
},
textForCameraPaused: String = "CAMERA PAUSED",
backButtonText: String = "BACK",
preventScreenshots: Bool = true)
}
身份证选项
public class IdOptions {
public var cardSide: DocumentSide = .front
public var isHealthCard: Bool = false
public var isRetrying: Bool = false
public var authenticationSensitivity: AuthenticationSensitivity = AuthenticationSensitivity.normal
public var tamperSensitivity: TamperSensitivity = TamperSensitivity.normal
public var countryCode: String?
}
AcuantPassportModel(用于eChip工作流程)
public class AcuantPassportModel {
public var documentType: String
public var documentSubType: String
public var documentCode: String
public var translatedDocumentType: TranslatedDocumentType
public var personalNumber: String
public var documentNumber: String
public var issuingAuthority: String
public var documentExpiryDate: String
public var firstName: String
public var lastName: String
public var dateOfBirth: String
public var gender: String
public var nationality: String
public var image: UIImage?
public var signatureImage: UIImage?
public var passportSigned: OzoneResultStatus
public var passportCountrySigned: OzoneResultStatus
public var PACEStatus: AuthStatus
public var BACStatus: AuthStatus
public var chipAuthenticationStatus: AuthStatus
public var activeAuthenticationStatus: AuthStatus
public var passportDataValid: Bool
public var age: Int?
public var isExpired: Bool?
public func getRawDataGroup(dgId: AcuantDataGroupId) -> [UInt8]?
}
常见问题
为什么我在使用CocoaPods时导入AcuantCamera时在Xcode中得到“找不到模块”错误
AcuantCamera和AcuantFaceCapture是开源项目,必须由用户编译。使用CocoaPods时,它们都被编译进名为AcuantiOSSDKV11的pods中,在Xcode中必须使用import AcuantiOSSDKV11
。使用import AcuantCamera
和import AcuantFaceCapture
将不会起作用。
为什么在存档示例应用时出现代码签名“AcutanCommon.framework”错误?
Acuant为所有模拟器和设备所需的CPU架构提供支持。但是,当导出或发布到Test Flight/App Store时,应从框架二进制文件中移除模拟器架构(i386和x86(64))。
- 存档应用。
- 选择存档,然后点击分发应用> App Store > 导出。
如何混淆我的iOS应用?
Acuant不提供混淆工具,但是有几个第三方工具可供选择,包括iXGuard和Arxan。
版权所有 © 2022 Acuant Inc. 保留所有权利。
本文档包含由Acuant及其相应许可方拥有的专有和保密信息以及创意作品。未经Acuant事先书面明确许可,不得以任何形式、任何方式全部或部分使用、复制、发布、分发、展示、修改或传输此类技术。除非Acuant以书面形式明确提供,否则拥有此类信息不能被认为是对Acuant知识产权的任何许可或权利,不论是因推定、暗示或其他方式。
AssureID和
所有3M商标是Gemalto Inc/Thales的商标。
Windows是微软公司的注册商标。
本文档中可能提到了Acuant以外的公司名称、产品或服务标识,仅用于识别目的。此类标识通常被认为是商标或服务标志。在Acuant知晓的任何情况下,此类标识均以大写字母或全部大写字母显示。然而,您应联系相关公司以获取关于此类标识及其注册状态更完整的信息。
如需技术支持,请访问:https://support.acuant.com
Acuant Inc. 6080 Center Drive,Suite 850,洛杉矶,加利福尼亚州 90045