APIs for interacting with the ObjectBox database to manage persons and their associated face records.
PersonDB
Database access layer for managing person records.
@Single
class PersonDB {
private val personBox = ObjectBoxStore.store.boxFor(PersonRecord::class.java)
fun addPerson(person: PersonRecord): Long
fun removePerson(personID: Long)
fun getCount(): Long
fun getAll(): Flow<MutableList<PersonRecord>>
}
Methods
addPerson
Add a new person to the database.
Returns: Long - The auto-generated person ID
val person = PersonRecord(
personName = "John Doe",
numImages = 0,
addTime = System.currentTimeMillis()
)
val personID = personDB.addPerson(person)
removePerson
Remove a person and all associated face records from the database.
The ID of the person to remove
personDB.removePerson(personID = 123)
This operation only removes the PersonRecord. You must also call ImagesVectorDB.removeFaceRecordsWithPersonID() to delete the associated face embeddings.
getCount
Get the total number of persons in the database.
Returns: Long - The count of person records
val totalPersons = personDB.getCount()
println("Database contains $totalPersons persons")
getAll
Get a Flow of all person records, automatically updated when the database changes.
Returns: Flow<MutableList<PersonRecord>> - Reactive flow of person records
personDB.getAll().collect { persons ->
persons.forEach { person ->
println("${person.personName}: ${person.numImages} images")
}
}
The Flow uses Dispatchers.IO internally, so database queries happen on a background thread.
Usage example
Complete workflow for managing persons:
// Inject PersonDB
@Single
class PersonUseCase(
private val personDB: PersonDB
) {
// Add a new person
fun addPerson(name: String, numImages: Long): Long {
return personDB.addPerson(
PersonRecord(
personName = name,
numImages = numImages,
addTime = System.currentTimeMillis()
)
)
}
// Remove a person
fun removePerson(id: Long) {
personDB.removePerson(id)
}
// Get all persons
fun getAll(): Flow<List<PersonRecord>> = personDB.getAll()
}
PersonUseCase
Use case layer for person management operations, providing a clean API for ViewModels.
@Single
class PersonUseCase(
private val personDB: PersonDB
) {
fun addPerson(name: String, numImages: Long): Long
fun removePerson(id: Long)
fun getAll(): Flow<List<PersonRecord>>
fun getCount(): Long
}
Methods
addPerson
Add a new person with the current timestamp.
Initial number of face images (typically 0)
Returns: Long - The auto-generated person ID
val personID = personUseCase.addPerson(
name = "Alice Johnson",
numImages = 0
)
removePerson
Remove a person from the database.
personUseCase.removePerson(id = personID)
getAll
Get a reactive Flow of all persons.
Returns: Flow<List<PersonRecord>> - Flow emitting updated person lists
val personsFlow = personUseCase.getAll()
getCount
Get the total count of persons.
Returns: Long - Number of persons in database
val count = personUseCase.getCount()
ImageVectorUseCase
Use case for face recognition operations, coordinating face detection, embedding generation, and vector search.
@Single
class ImageVectorUseCase(
private val faceDetector: BaseFaceDetector,
private val faceSpoofDetector: FaceSpoofDetector,
private val imagesVectorDB: ImagesVectorDB,
private val faceNet: FaceNet
) {
data class FaceRecognitionResult(
val personName: String,
val boundingBox: Rect,
val spoofResult: FaceSpoofDetector.FaceSpoofResult? = null
)
suspend fun addImage(personID: Long, personName: String, imageUri: Uri): Result<Boolean>
suspend fun getNearestPersonName(frameBitmap: Bitmap, flatSearch: Boolean): Pair<RecognitionMetrics?, List<FaceRecognitionResult>>
fun removeAllImagesForPerson(personID: Long)
}
Data classes
FaceRecognitionResult
Recognition result for a detected face.
The recognized person’s name, or “NOT RECOGNIZED” if no match found
Face bounding box coordinates in the frame
spoofResult
FaceSpoofDetector.FaceSpoofResult?
default:"null"
Spoof detection result if enabled, null otherwise
Methods
addImage
Add a face image for enrollment.
URI of the image to process
Returns: Result<Boolean> - Success/failure with error details
val result = imageVectorUseCase.addImage(
personID = 123,
personName = "John Doe",
imageUri = selectedImageUri
)
result.onSuccess {
println("Face enrolled successfully")
}.onFailure { error ->
println("Enrollment failed: ${error.message}")
}
The method performs:
- Face detection on the image
- FaceNet embedding generation
- Storage in vector database with person metadata
getNearestPersonName
Recognize faces in a camera frame.
The camera frame to analyze
Whether to use precise flat search (true) or HNSW approximate search (false)
Returns: Pair<RecognitionMetrics?, List<FaceRecognitionResult>> - Metrics and recognition results for all detected faces
val (metrics, results) = imageVectorUseCase.getNearestPersonName(
frameBitmap = cameraFrame,
flatSearch = false
)
results.forEach { result ->
if (result.personName != "NOT RECOGNIZED") {
println("Recognized: ${result.personName}")
println("Bounding box: ${result.boundingBox}")
result.spoofResult?.let { spoof ->
if (spoof.isSpoof) {
println("WARNING: Spoof detected!")
}
}
}
}
The pipeline:
- Detect all faces in frame
- Generate embeddings for each face
- Search vector database for matches
- Run spoof detection
- Return results with metrics
Use flatSearch = false for real-time recognition (faster). Use flatSearch = true when accuracy is critical and you have time for linear search.
removeAllImagesForPerson
Remove all face embeddings for a person.
The person ID whose face records should be deleted
imageVectorUseCase.removeAllImagesForPerson(personID = 123)
This only removes FaceImageRecord entries. To fully remove a person, also call PersonUseCase.removePerson().
Complete enrollment workflow
// 1. Create person record
val personID = personUseCase.addPerson(
name = "Alice",
numImages = 0
)
// 2. Enroll multiple face images
imageUris.forEach { uri ->
imageVectorUseCase.addImage(
personID = personID,
personName = "Alice",
imageUri = uri
).onFailure { error ->
println("Failed to enroll image: ${error.message}")
}
}
// 3. Update person record with image count
val updatedPerson = PersonRecord(
personID = personID,
personName = "Alice",
numImages = imageUris.size.toLong(),
addTime = System.currentTimeMillis()
)
Complete recognition workflow
// Get camera frame
val bitmap = cameraPreview.getBitmap()
// Perform recognition
val (metrics, results) = imageVectorUseCase.getNearestPersonName(
frameBitmap = bitmap,
flatSearch = false
)
// Process results
results.forEach { result ->
val isRecognized = result.personName != "NOT RECOGNIZED"
val isReal = result.spoofResult?.isSpoof == false
when {
!isRecognized -> drawBox(result.boundingBox, Color.RED, "Unknown")
!isReal -> drawBox(result.boundingBox, Color.YELLOW, "Spoof")
else -> drawBox(result.boundingBox, Color.GREEN, result.personName)
}
}
// Display metrics
metrics?.let {
println("Total pipeline time: ${
it.timeFaceDetection +
it.timeFaceEmbedding +
it.timeVectorSearch +
it.timeFaceSpoofDetection
}ms")
}
Source: domain/PersonUseCase.kt, domain/ImageVectorUseCase.kt, data/PersonDB.kt