Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,22 @@ ktlint_standard_annotation = disabled
# If enabled, this rules forces any multiline assignment to the new line regardless of length
ktlint_standard_multiline-expression-wrapping = disabled

# If enabled, this rules forces braces to all branches if any single one has them
ktlint_standard_when-entry-bracing = disabled

# If enabled, this rule forces blank line between when branches if there is a single multiline branch
ktlint_standard_blank-line-between-when-conditions = disabled
ij_kotlin_line_break_after_multiline_when_entry = false

# Default allows the expression body to be a single call to a wrapper
# function (e.g. `runTest{}`) without a line break
ktlint_function_signature_body_expression_wrapping = default

# In tests star imports are fine since there could be lots helper functions
ij_kotlin_packages_to_use_import_on_demand=androidx.test.**,io.mockk.**,com.google.common.truth.**

# Backticked function names are only used in tests and those names can be very long
ktlint_ignore_back_ticked_identifier=true

# Unused import deletion is disabled by default due to a issue with
ktlint_standard_no-unused-imports = enabled
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
java-version: '17'

- name: Build with Gradle
run: ./gradlew build
run: ./gradlew simface:build
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ jobs:
run: chmod +x gradlew

- name: Build libraries
run: ./gradlew build
run: ./gradlew simface:build

- name: Publish to GitHub Packages
env:
USERNAME: ${{ github.actor }}
TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew publish
run: ./gradlew simface:publish
108 changes: 12 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# SimFace
# Simprints Face Biometrics SDK

## SimFace Library

An Android library for face recognition and quality assessment on edge devices.
It provides face detection, embedding creation, and biometric matching capabilities.
Expand All @@ -9,15 +11,17 @@ detection and [EdgeFace](https://github.com/otroshi/edgeface) for embedding (tem
2. Embedding Creation: This module is used to generate a 512-float vector representation of face images.
3. Matching and Identification: Used for verification and identification purposes.

**📚 [View Full Documentation](simface/README.md)** for installation and usage examples.

## SimQ Library

**SimQ** is a standalone Android library for comprehensive face quality assessment. It can be used independently or as part of SimFace for enhanced quality evaluation. SimQ provides a quality score from 0.0 to 1.0, with customizable weights for each metric and configurable thresholds.
**SimQ** is a standalone Android library for comprehensive face quality assessment. It can be used independently or as part of SimFace for
enhanced quality evaluation. SimQ provides a quality score from 0.0 to 1.0, with customizable weights for each metric and configurable
thresholds.

**📚 [View Full SimQ Documentation](simq/README.md)** for installation, usage examples, and advanced configuration options.

## Include the library into the project.

### Option 1 (Recommended)
### Installation

1. Add the repository to your `settings.gradle.kts` under `dependencyResolutionManagement` under `respositories`:

Expand All @@ -31,99 +35,11 @@ maven {
}
```

2. Import the dependencies in `build.gradle.kts`:
Import the dependencies in `build.gradle.kts`:

```kotlin
implementation("com.simprints.biometrics:simface:2026.1.0")
```

## Implement the functionality.

### Coroutines (Recommended)

```kotlin
// Initialize library configuration
val simFace = SimFace()
val simFaceConfig = SimFaceConfig(context)
simFace.initialize(simFaceConfig)

// Load a bitmap image for processing
val faceImage: Bitmap =
BitmapFactory.decodeResource(context.resources, R.drawable.royalty_free_good_face)

lifecycleScope.launch {
val faces = simFace.detectFaceBlocking(faceImage)
val face = faces[0]
if (faces.size != 1 || face.quality < 0.6) throw Exception("Quality not sufficient")

// Align and crop the image of the face
val alignedFace = face.alignedFaceImage(bitmap)

// Generate an embedding from the image
val probe = simFace.getFaceDetectionProcessor().getEmbedding(alignedFace)

// Verify the embedding against itself
val score = simFace.verificationScore(probe, probe)
}
```

### Callbacks

```kotlin
// Initialize library configuration
val simFace = SimFace()
val simFaceConfig = SimFaceConfig(context)
simFace.initialize(simFaceConfig)

// Load a bitmap image for processing
val faceImage: Bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.royalty_free_good_face)

// Note that this can be better handled with callbacks or coroutines
simFace.getFaceDetectionProcessor().detectFace(faceImage, onSuccess = { faces ->
val face = faces[0]
if (faces.size != 1 || face.quality < 0.6) throw Exception("Quality not sufficient")

// Align and crop the image of the face
val alignedFace = face.alignedFaceImage(bitmap)

// Generate an embedding from the image
val probe = simFace.getEmbedding(alignedFace)

// Verify the embedding against itself
val score = simFace.verificationScore(probe, probe)
})
// Or if only quality assessment is needed
implementation("com.simprints.biometrics:simq:2026.1.0")
```

## Workflow

### Face Detection and Embedding

We first initialize the library. Then we can use the
`simFace.detectFace` method to detect faces in images and evaluate their quality.
We can repeat this process multiple times until a sufficiently good face image is selected.

Afterwards, we can use the `simFace.getEmbedding` method to obtain a vector template
from the selected image. The embedding is represented by a 512 float array.

### Verification and Identification

The same steps are taken to initialize the library.

Then, the matching of two templates is carried out using the `simFace.verificationScore` method,
which returns a score in the [0, 1] range, being 1 a perfect match.

Identification can be carried using the `simFace.identificationScore` method which returns a mapping of the
referenceVectors to the the score with respect to the probe.

Both methods use the cosine similarity between vectors as a measure of the score.

## System Requirements

The library works with a minimum version of Android 6.0 (API Level 23). It has been tested and runs
smoothly on _Samsung Galaxy A03 Core_ which has the following specifications:

- Android 11
- 1.6GHz Octa-core
- 2GB RAM
- 8MP f/2.0 Camera
- 32GB Storage
91 changes: 3 additions & 88 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,91 +1,6 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.jetbrains.kotlin.android)
`maven-publish`
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.kotlin.compose) apply false
}

val projectGroupId = "com.simprints.biometrics"
val projectArtifactId = "simface"
val projectVersion = "2026.1.0"

group = projectGroupId
version = projectVersion

android {

namespace = "$projectGroupId.$projectArtifactId"
compileSdk = 36

defaultConfig {
minSdk = 23

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

dependencies {
api(project(":simq"))

// Tensorflow versions that works with Edgeface
api(libs.tensorflow.lite.support)
api(libs.tensorflow.lite.metadata)
api(libs.tensorflow.lite)

// Face Detection and quality
api(libs.face.detection)

// For face alignment
api(libs.ejml.simple)

androidTestImplementation(libs.truth)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.kotlinx.coroutines.test)
}

publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/simprints/Biometrics-SimFace")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("USERNAME")
password = project.findProperty("gpr.key") as String? ?: System.getenv("TOKEN")
}
}
}
publications {
create<MavenPublication>("ReleaseAar") {
groupId = projectGroupId
artifactId = projectArtifactId
version = projectVersion
afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }

pom.withXml {
val dependenciesNode = asNode().appendNode("dependencies")

configurations.getByName("api").dependencies.map { dependency ->
dependenciesNode.appendNode("dependency").also {
it.appendNode("groupId", dependency.group)
it.appendNode("artifactId", dependency.name)
it.appendNode("version", dependency.version)
}
}
}
}
}
}
6 changes: 5 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false
46 changes: 36 additions & 10 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
[versions]
agp = "8.13.2"
agp = "9.2.1"
kotlin = "2.2.21"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
tensorflowLite = "2.17.0"
tensorflowLiteMetadata = "0.5.0"
tensorflowLiteSupport = "0.5.0"
faceDetection = "16.1.7"
ejmlSimple = "0.44.0"
coreKtx = "1.17.0"
appcompat = "1.7.1"
material = "1.13.0"
composeBom = "2024.09.00"
activityCompose = "1.10.1"
lifecycleRuntimeKtx = "2.9.3"
camera = "1.3.1"
faceDetection = "16.1.7"
ejmlSimple = "0.44.0"
opencv = "4.13.0"
tensorflowLite = "2.17.0"
tensorflowLiteMetadata = "0.5.0"
tensorflowLiteSupport = "0.5.0"
truth = "1.4.5"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
coroutines = "1.10.2"

[libraries]
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
tensorflow-lite = { module = "org.tensorflow:tensorflow-lite", version.ref = "tensorflowLite" }
tensorflow-lite-metadata = { module = "org.tensorflow:tensorflow-lite-metadata", version.ref = "tensorflowLiteMetadata" }
tensorflow-lite-support = { module = "org.tensorflow:tensorflow-lite-support", version.ref = "tensorflowLiteSupport" }
Expand All @@ -28,8 +34,28 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
opencv = { module = "org.opencv:opencv", version.ref = "opencv" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }
junit = { group = "junit", name = "junit", version.ref = "junit" }

androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }

camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camera" }
camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camera" }
camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camera" }
camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camera" }

[plugins]
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Thu Oct 02 14:49:35 EEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading