
On-device face and Indonesian e-KTP detection with YuNet face detector, passive liveness, quality checks, KTP rectification, and selfie-to-KTP matching via swappable inference backend.
On-device face and Indonesian e-KTP detection for Kotlin Multiplatform.
Faktel is an open-source Kotlin Multiplatform (KMP) library that runs computer-vision pipelines on the device - no cloud round trip, no vendor CV SDK. One shared Kotlin API for Android, iOS and desktop JVM.
Real output of the sample apps (SwiftUI on an iPhone simulator, Jetpack Compose on an Android emulator), running the bundled models on-device. Photo: Duman Photography / Unsplash; the card is a fictional, synthetic sample.
Status: pre-1.0 (
0.x). The API can still change between minor versions; every change is recorded in the CHANGELOG. See Limitations before using it for anything security-sensitive.
| Capability | API | Backed by | Needs a model? |
|---|---|---|---|
| Face detection + 5 landmarks | YuNetFaceDetector |
YuNet (MIT) | yes - bundled in repo |
| Face quality gate (size, blur, exposure, pose, cut-off) | FaceQualityAssessor |
pure Kotlin | no |
| Passive liveness (print/replay attacks) | MiniFasNetLivenessDetector |
MiniFASNetV2 (Apache-2.0) | yes - bundled in repo |
| Face embedding + selfie-to-KTP matching |
ArcFaceEmbedder, FaceMatcher
|
any ArcFace-style ONNX | yes - bring your own (why) |
| KTP card localisation |
ClassicalKtpDetector (or your own KtpDetector) |
pure Kotlin | no |
| KTP rectification + validation (aspect ratio 1.586, size, blur, glare, portrait placement) | KtpScanner |
pure Kotlin + face detector | optional |
| Camera-frame conversion (NV21 / YUV_420_888 / BGRA / RGBA, rotate, flip) | RgbImage.from* |
pure Kotlin | no |
All heavy math runs through one small seam, InferenceEngine, so the ML runtime is swappable.
The samples/ folder has two complete, modern demo apps - Jetpack Compose for Android and
SwiftUI for iOS - that run the real pipelines on-device:
| Selfie check | KTP scan |
|---|---|
![]() |
![]() |
![]() |
![]() |
All integration code is one file per platform (Kotlin, Swift). Run them yourself: samples/README.md.
| Artifact | Contents | Targets |
|---|---|---|
faktel |
Umbrella: depends on everything below; also the iOS Faktel.xcframework
|
Android, iOS, JVM |
faktel-core |
Image type, geometry, preprocessing, quality metrics, InferenceEngine interface |
Android, iOS, JVM |
faktel-face |
Detector, quality, liveness, embedding, matching | Android, iOS, JVM |
faktel-ktp |
KTP detection, rectification, validation | Android, iOS, JVM |
faktel-ort |
ONNX Runtime backend (Android + JVM; iOS via Swift package) | Android, JVM (+ Swift) |
Group ID: io.github.cybersafetyid.faktel. Details: architecture.
// build.gradle.kts of your shared module
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.cybersafetyid.faktel:faktel:<version>")
}
}
}Then wire the platform pieces (one-time): Android - iOS - Desktop JVM.
Shared code depends only on the InferenceEngine interface:
class SelfieChecker(engine: InferenceEngine, yunet: ByteArray, fas: ByteArray) {
private val analyzer = FaceAnalyzer(
detector = YuNetFaceDetector(engine, yunet),
liveness = MiniFasNetLivenessDetector(engine, fas),
)
fun check(frame: RgbImage): FaceAnalysis = analyzer.analyze(frame) // detect -> quality -> liveness
}val scanner = KtpScanner(faceDetector = YuNetFaceDetector(engine, yunet))
val scan = scanner.scan(photo)
if (scan.isAcceptable) {
val card = scan.card!! // upright 1011x638 image
val portrait = scan.portrait!! // face box in card coordinates
} else {
showHint(scan.issues) // e.g. [BLURRY, GLARE] -> "hold steady, avoid reflections"
}Selfie-to-KTP match: docs/guides/selfie-ktp-matching.md.
Start at docs/README.md. Highlights: Sample apps - Getting started - Architecture - Face guide - KTP guide - Models & licences - Tuning thresholds - Privacy & security - Troubleshooting - API reference.
KtpDetector interface is where it plugs in.Unit tests run on JVM and the iOS simulator in CI. The real models (YuNet, MiniFASNetV2, an ArcFace MobileFaceNet) were exercised end-to-end through ONNX Runtime on desktop JVM, on an iPhone simulator and on an Android emulator (the sample apps - screenshots above). Not yet measured: latency on real mid-range phones, and emulator runs are not automated in CI; see docs/development/testing.md for exactly what is and is not covered automatically.
Contributions are welcome - bug reports, docs, thresholds from real devices, new backends, a learned KTP detector. Read CONTRIBUTING.md and the Code of Conduct. Versioning follows SemVer. Security issues: SECURITY.md.
Apache License 2.0. Bundled model weights keep their own licences - see models/MODELS.md and NOTICE.
On-device face and Indonesian e-KTP detection for Kotlin Multiplatform.
Faktel is an open-source Kotlin Multiplatform (KMP) library that runs computer-vision pipelines on the device - no cloud round trip, no vendor CV SDK. One shared Kotlin API for Android, iOS and desktop JVM.
Real output of the sample apps (SwiftUI on an iPhone simulator, Jetpack Compose on an Android emulator), running the bundled models on-device. Photo: Duman Photography / Unsplash; the card is a fictional, synthetic sample.
Status: pre-1.0 (
0.x). The API can still change between minor versions; every change is recorded in the CHANGELOG. See Limitations before using it for anything security-sensitive.
| Capability | API | Backed by | Needs a model? |
|---|---|---|---|
| Face detection + 5 landmarks | YuNetFaceDetector |
YuNet (MIT) | yes - bundled in repo |
| Face quality gate (size, blur, exposure, pose, cut-off) | FaceQualityAssessor |
pure Kotlin | no |
| Passive liveness (print/replay attacks) | MiniFasNetLivenessDetector |
MiniFASNetV2 (Apache-2.0) | yes - bundled in repo |
| Face embedding + selfie-to-KTP matching |
ArcFaceEmbedder, FaceMatcher
|
any ArcFace-style ONNX | yes - bring your own (why) |
| KTP card localisation |
ClassicalKtpDetector (or your own KtpDetector) |
pure Kotlin | no |
| KTP rectification + validation (aspect ratio 1.586, size, blur, glare, portrait placement) | KtpScanner |
pure Kotlin + face detector | optional |
| Camera-frame conversion (NV21 / YUV_420_888 / BGRA / RGBA, rotate, flip) | RgbImage.from* |
pure Kotlin | no |
All heavy math runs through one small seam, InferenceEngine, so the ML runtime is swappable.
The samples/ folder has two complete, modern demo apps - Jetpack Compose for Android and
SwiftUI for iOS - that run the real pipelines on-device:
| Selfie check | KTP scan |
|---|---|
![]() |
![]() |
![]() |
![]() |
All integration code is one file per platform (Kotlin, Swift). Run them yourself: samples/README.md.
| Artifact | Contents | Targets |
|---|---|---|
faktel |
Umbrella: depends on everything below; also the iOS Faktel.xcframework
|
Android, iOS, JVM |
faktel-core |
Image type, geometry, preprocessing, quality metrics, InferenceEngine interface |
Android, iOS, JVM |
faktel-face |
Detector, quality, liveness, embedding, matching | Android, iOS, JVM |
faktel-ktp |
KTP detection, rectification, validation | Android, iOS, JVM |
faktel-ort |
ONNX Runtime backend (Android + JVM; iOS via Swift package) | Android, JVM (+ Swift) |
Group ID: io.github.cybersafetyid.faktel. Details: architecture.
// build.gradle.kts of your shared module
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.cybersafetyid.faktel:faktel:<version>")
}
}
}Then wire the platform pieces (one-time): Android - iOS - Desktop JVM.
Shared code depends only on the InferenceEngine interface:
class SelfieChecker(engine: InferenceEngine, yunet: ByteArray, fas: ByteArray) {
private val analyzer = FaceAnalyzer(
detector = YuNetFaceDetector(engine, yunet),
liveness = MiniFasNetLivenessDetector(engine, fas),
)
fun check(frame: RgbImage): FaceAnalysis = analyzer.analyze(frame) // detect -> quality -> liveness
}val scanner = KtpScanner(faceDetector = YuNetFaceDetector(engine, yunet))
val scan = scanner.scan(photo)
if (scan.isAcceptable) {
val card = scan.card!! // upright 1011x638 image
val portrait = scan.portrait!! // face box in card coordinates
} else {
showHint(scan.issues) // e.g. [BLURRY, GLARE] -> "hold steady, avoid reflections"
}Selfie-to-KTP match: docs/guides/selfie-ktp-matching.md.
Start at docs/README.md. Highlights: Sample apps - Getting started - Architecture - Face guide - KTP guide - Models & licences - Tuning thresholds - Privacy & security - Troubleshooting - API reference.
KtpDetector interface is where it plugs in.Unit tests run on JVM and the iOS simulator in CI. The real models (YuNet, MiniFASNetV2, an ArcFace MobileFaceNet) were exercised end-to-end through ONNX Runtime on desktop JVM, on an iPhone simulator and on an Android emulator (the sample apps - screenshots above). Not yet measured: latency on real mid-range phones, and emulator runs are not automated in CI; see docs/development/testing.md for exactly what is and is not covered automatically.
Contributions are welcome - bug reports, docs, thresholds from real devices, new backends, a learned KTP detector. Read CONTRIBUTING.md and the Code of Conduct. Versioning follows SemVer. Security issues: SECURITY.md.
Apache License 2.0. Bundled model weights keep their own licences - see models/MODELS.md and NOTICE.