SourceAFIS for Java

SourceAFIS for Java is a pure Java port of SourceAFIS, an algorithm for recognition of human fingerprints. It can compare two fingerprints 1:1 or search large database 1:N for matching fingerprint. It takes fingerprint images on input and produces similarity score on output. Similarity score is then compared to customizable match threshold.

Download

Get SourceAFIS for Java from Maven Central.

<dependency>
    <groupId>com.machinezoo.sourceafis</groupId>
    <artifactId>sourceafis</artifactId>
    <version>3.4.1</version>
</dependency>

Or clone the source code. Don't forget to configure your build for Java 8+. Sources and binaries are distributed under Apache License 2.0. Running SourceAFIS on Android involves applying some hacks to the source code and building new binaries. SourceAFIS homepage has some links to fingerprint image databases in case you don't have any sample fingerprints at hand.

Matching two fingerprints 1:1

We start by creating FingerprintTemplate objects for both fingerprints. Fingerprint template is a biometric description of human fingerprint. It contains features relevant for matching. In case of SourceAFIS, these features are fingerprint minutiae, essentially ridge endings and bifurcations.

Template construction is a separate step, because it is computationally expensive, taking significant fraction of a second to compute. Application developers need to think where to put this step in their code.

byte[] probeImage = Files.readAllBytes(Paths.get("probe.png"));
byte[] candidateImage = Files.readAllBytes(Paths.get("candidate.png"));

FingerprintTemplate probe = new FingerprintTemplate()
    .dpi(500)
    .create(probeImage);

FingerprintTemplate candidate = new FingerprintTemplate()
    .dpi(500)
    .create(candidateImage);

FingerprintTemplate's create method accepts common image formats. We have to call dpi method, because SourceAFIS is not scale-invariant and every image must have associated DPI.

SourceAFIS is designed to process fingerprint images coming directly from fingerprint reader. Some fingerprint reader vendors might try to put you through the pain of handling proprietary fingerprint templates. Email them for image acquisition SDK or shop around for better sensor. Trust me, coding against image API is way more enjoyable. If you are stuck, try to pass the proprietary template to FingerprintTemplate's convert method. If you are lucky, the template conforms to ISO 19794-2:2005 and SourceAFIS will accept it.

We can now create FingerprintMatcher, which is just another representation of the fingerprint template passed to its index method that will become important later. We can then pass the other template to its match method.

double score = new FingerprintMatcher()
    .index(probe)
    .match(candidate);

Variable score now holds similarity score of the two fingerprints. Similarity score is an approximate measure of similarity of two fingerprints. The higher it is, the more likely is the match.

Similarity score is necessarily probabilistic. You can never be sure of a match no matter how high the score is. It is however possible to come up with a reasonable threshold beyond which match is extremely likely.

double threshold = 40;
boolean matches = score >= threshold;

Certainty in fingerprint recognition is measured by FMR: false match rate. FMR is the frequency with which the system incorrectly recognizes non-matching fingerprints as matching. Threshold 40 corresponds to FMR 0.01%. Applications can increase the threshold to get exponentially lower FMR at the cost of slightly higher FNMR: false non-match rate. In the end, choice of threshold is application-dependent, but 40 is a good starting point.

Searching fingerprint databases (1:N matching)

Let's say we want to compare one probe fingerprint, which was just read from fingerprint reader, against a number of candidate fingerprints, which are already stored in a database. Databases are usually on-disk, but fingerprint databases must be in-memory, because probe fingerprint must be compared to each one of the candidate fingerprints.

With 1:N matching, it is no longer sufficient to return matching score. We need to identify the user whose fingerprint was matched, perhaps through a class describing the user.

class UserDetails {
    int id;
    String name;
    FingerprintTemplate template;
}

We can now define a method that takes the probe fingerprint and a list of candidate fingerprints and returns the best match or null if there is no match.

UserDetails find(FingerprintTemplate probe, Iterable<UserDetails> candidates) {
    FingerprintMatcher matcher = new FingerprintMatcher()
        .index(probe);
    UserDetails match = null;
    double high = 0;
    for (UserDetails candidate : candidates) {
        double score = matcher.match(candidate.template);
        if (score > high) {
            high = score;
            match = candidate;
        }
    }
    double threshold = 40;
    return high >= threshold ? match : null;
}

It is important to call index method only once, because it is an expensive operation. Indexing builds in-memory data structures that speed up matching. Individual calls to match method are relatively fast.

You might be wondering why doesn't SourceAFIS provide such search method in the API. Aside from making it easier to associate templates with application-defined user identities, keeping the search loop on application side allows applications to customize the loop. Examples of such customizations include:

Caching fingerprint templates

It would be unreasonably expensive to recreate all fingerprint templates in the database from fingerprint images every time the application restarts. For this reason, SourceAFIS provides a way to cache fingerprint templates.

byte[] image = Files.readAllBytes(Paths.get("fingerprint.jpeg"));
FingerprintTemplate template = new FingerprintTemplate()
    .dpi(500)
    .create(image);
String json = template.serialize();

JSON representation of the template produced by serialize method can be stored in database to speed up application restarts. When application starts, it deserializes JSON templates using deserialize method instead of recreating them from fingerprint images.

FingerprintTemplate template = new FingerprintTemplate()
    .deserialize(json);

JSON templates are not a substitute for fingerprint images. They are tied to specific SourceAFIS version. In order to allow SourceAFIS upgrades, applications should always store original fingerprint images and treat JSON templates as a temporary cache.

Next steps