mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2025-11-15 07:38:12 +00:00
I want to offer the ability for an application to view a binarized
version of a scanned print. This lead onto a few changes:
1. Store minutiae and binarized data inside fp_img
2. Move resize code to the capture path, it previously happened much
later.
3. Add fp_img_binarize() to return a new image in binarized form.
4. Add a BINARIZED_FORM flag to prevent an image being binarized again.
In future, it would be nice to be able to binarize without detecting
minutiae, but this involves some work on the NBIS interaction.
413 lines
11 KiB
C
413 lines
11 KiB
C
/*
|
|
* Image management functions for libfprint
|
|
* Copyright (C) 2007 Daniel Drake <dsd@gentoo.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "fp_internal.h"
|
|
#include "nbis/include/bozorth.h"
|
|
#include "nbis/include/lfs.h"
|
|
|
|
/** @defgroup img Image operations
|
|
* libfprint offers several ways of retrieving images from imaging devices,
|
|
* one example being the fp_dev_img_capture() function. The functions
|
|
* documented below allow you to work with such images.
|
|
*
|
|
* \section img_fmt Image format
|
|
* All images are represented as 8-bit greyscale data.
|
|
*
|
|
* \section img_std Image standardization
|
|
* In some contexts, images you are provided through libfprint are raw images
|
|
* from the hardware. The orientation of these varies from device-to-device,
|
|
* as does the color scheme (black-on-white or white-on-black?). libfprint
|
|
* provides the fp_img_standardize function to convert images into standard
|
|
* form, which is defined to be: finger flesh as black on white surroundings,
|
|
* natural upright orientation.
|
|
*/
|
|
|
|
struct fp_img *fpi_img_new(size_t length)
|
|
{
|
|
struct fp_img *img = g_malloc(sizeof(*img) + length);
|
|
memset(img, 0, sizeof(*img));
|
|
fp_dbg("length=%zd", length);
|
|
img->length = length;
|
|
return img;
|
|
}
|
|
|
|
struct fp_img *fpi_img_new_for_imgdev(struct fp_img_dev *imgdev)
|
|
{
|
|
struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(imgdev->dev->drv);
|
|
int width = imgdrv->img_width;
|
|
int height = imgdrv->img_height;
|
|
struct fp_img *img = fpi_img_new(width * height);
|
|
img->width = width;
|
|
img->height = height;
|
|
return img;
|
|
}
|
|
|
|
gboolean fpi_img_is_sane(struct fp_img *img)
|
|
{
|
|
/* basic checks */
|
|
if (!img->length || !img->width || !img->height)
|
|
return FALSE;
|
|
|
|
/* buffer is big enough? */
|
|
if ((img->length * img->height) < img->length)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
struct fp_img *fpi_img_resize(struct fp_img *img, size_t newsize)
|
|
{
|
|
return g_realloc(img, sizeof(*img) + newsize);
|
|
}
|
|
|
|
/** \ingroup img
|
|
* Frees an image. Must be called when you are finished working with an image.
|
|
* \param img the image to destroy. If NULL, function simply returns.
|
|
*/
|
|
API_EXPORTED void fp_img_free(struct fp_img *img)
|
|
{
|
|
if (!img)
|
|
return;
|
|
|
|
if (img->minutiae)
|
|
free_minutiae(img->minutiae);
|
|
if (img->binarized)
|
|
free(img->binarized);
|
|
g_free(img);
|
|
}
|
|
|
|
/** \ingroup img
|
|
* Gets the pixel height of an image.
|
|
* \param img an image
|
|
* \returns the height of the image
|
|
*/
|
|
API_EXPORTED int fp_img_get_height(struct fp_img *img)
|
|
{
|
|
return img->height;
|
|
}
|
|
|
|
/** \ingroup img
|
|
* Gets the pixel width of an image.
|
|
* \param img an image
|
|
* \returns the width of the image
|
|
*/
|
|
API_EXPORTED int fp_img_get_width(struct fp_img *img)
|
|
{
|
|
return img->width;
|
|
}
|
|
|
|
/** \ingroup img
|
|
* Gets the greyscale data for an image. This data must not be modified or
|
|
* freed, and must not be used after fp_img_free() has been called.
|
|
* \param img an image
|
|
* \returns a pointer to libfprint's internal data for the image
|
|
*/
|
|
API_EXPORTED unsigned char *fp_img_get_data(struct fp_img *img)
|
|
{
|
|
return img->data;
|
|
}
|
|
|
|
/** \ingroup img
|
|
* A quick convenience function to save an image to a file in
|
|
* <a href="http://netpbm.sourceforge.net/doc/pgm.html">PGM format</a>.
|
|
* \param img the image to save
|
|
* \param path the path to save the image. Existing files will be overwritten.
|
|
* \returns 0 on success, non-zero on error.
|
|
*/
|
|
API_EXPORTED int fp_img_save_to_file(struct fp_img *img, char *path)
|
|
{
|
|
FILE *fd = fopen(path, "w");
|
|
size_t write_size = img->width * img->height;
|
|
int r;
|
|
|
|
if (!fd) {
|
|
fp_dbg("could not open '%s' for writing: %d", path, errno);
|
|
return -errno;
|
|
}
|
|
|
|
r = fprintf(fd, "P5 %d %d 255\n", img->width, img->height);
|
|
if (r < 0) {
|
|
fp_err("pgm header write failed, error %d", r);
|
|
return r;
|
|
}
|
|
|
|
r = fwrite(img->data, 1, write_size, fd);
|
|
if (r < write_size) {
|
|
fp_err("short write (%d)", r);
|
|
return -EIO;
|
|
}
|
|
|
|
fclose(fd);
|
|
fp_dbg("written to '%s'", path);
|
|
return 0;
|
|
}
|
|
|
|
static void vflip(struct fp_img *img)
|
|
{
|
|
int width = img->width;
|
|
int data_len = img->width * img->height;
|
|
unsigned char rowbuf[width];
|
|
int i;
|
|
|
|
for (i = 0; i < img->height / 2; i++) {
|
|
int offset = i * width;
|
|
int swap_offset = data_len - (width * (i + 1));
|
|
|
|
/* copy top row into buffer */
|
|
memcpy(rowbuf, img->data + offset, width);
|
|
|
|
/* copy lower row over upper row */
|
|
memcpy(img->data + offset, img->data + swap_offset, width);
|
|
|
|
/* copy buffer over lower row */
|
|
memcpy(img->data + swap_offset, rowbuf, width);
|
|
}
|
|
}
|
|
|
|
static void hflip(struct fp_img *img)
|
|
{
|
|
int width = img->width;
|
|
unsigned char rowbuf[width];
|
|
int i, j;
|
|
|
|
for (i = 0; i < img->height; i++) {
|
|
int offset = i * width;
|
|
|
|
memcpy(rowbuf, img->data + offset, width);
|
|
for (j = 0; j < width; j++)
|
|
img->data[offset + j] = rowbuf[width - j - 1];
|
|
}
|
|
}
|
|
|
|
static void invert_colors(struct fp_img *img)
|
|
{
|
|
int data_len = img->width * img->height;
|
|
int i;
|
|
for (i = 0; i < data_len; i++)
|
|
img->data[i] = 0xff - img->data[i];
|
|
}
|
|
|
|
/** \ingroup img
|
|
* \ref img_std "Standardizes" an image by normalizing its orientation, colors,
|
|
* etc. It is safe to call this multiple times on an image, libfprint keeps
|
|
* track of the work it needs to do to make an image standard and will not
|
|
* perform these operations more than once for a given image.
|
|
* \param img the image to standardize
|
|
*/
|
|
API_EXPORTED void fp_img_standardize(struct fp_img *img)
|
|
{
|
|
if (img->flags & FP_IMG_V_FLIPPED) {
|
|
vflip(img);
|
|
img->flags &= ~FP_IMG_V_FLIPPED;
|
|
}
|
|
if (img->flags & FP_IMG_H_FLIPPED) {
|
|
hflip(img);
|
|
img->flags &= ~FP_IMG_H_FLIPPED;
|
|
}
|
|
if (img->flags & FP_IMG_COLORS_INVERTED) {
|
|
invert_colors(img);
|
|
img->flags &= ~FP_IMG_COLORS_INVERTED;
|
|
}
|
|
}
|
|
|
|
/* Based on write_minutiae_XYTQ and bz_load */
|
|
static void minutiae_to_xyt(struct fp_minutiae *minutiae, int bwidth,
|
|
int bheight, unsigned char *buf)
|
|
{
|
|
int i;
|
|
struct fp_minutia *minutia;
|
|
struct minutiae_struct c[MAX_FILE_MINUTIAE];
|
|
struct xyt_struct *xyt = (struct xyt_struct *) buf;
|
|
|
|
/* FIXME: only considers first 150 minutiae (MAX_FILE_MINUTIAE) */
|
|
/* nist does weird stuff with 150 vs 1000 limits */
|
|
int nmin = min(minutiae->num, MAX_FILE_MINUTIAE);
|
|
|
|
for (i = 0; i < nmin; i++){
|
|
minutia = minutiae->list[i];
|
|
|
|
lfs2nist_minutia_XYT(&c[i].col[0], &c[i].col[1], &c[i].col[2],
|
|
minutia, bwidth, bheight);
|
|
c[i].col[3] = sround(minutia->reliability * 100.0);
|
|
|
|
if (c[i].col[2] > 180)
|
|
c[i].col[2] -= 360;
|
|
}
|
|
|
|
qsort((void *) &c, (size_t) nmin, sizeof(struct minutiae_struct),
|
|
sort_x_y);
|
|
|
|
for (i = 0; i < nmin; i++) {
|
|
xyt->xcol[i] = c[i].col[0];
|
|
xyt->ycol[i] = c[i].col[1];
|
|
xyt->thetacol[i] = c[i].col[2];
|
|
}
|
|
xyt->nrows = nmin;
|
|
}
|
|
|
|
int fpi_img_detect_minutiae(struct fp_img *img)
|
|
{
|
|
struct fp_minutiae *minutiae;
|
|
int r;
|
|
int *direction_map, *low_contrast_map, *low_flow_map;
|
|
int *high_curve_map, *quality_map;
|
|
int map_w, map_h;
|
|
unsigned char *bdata;
|
|
int bw, bh, bd;
|
|
GTimer *timer;
|
|
|
|
if (img->flags & FP_IMG_STANDARDIZATION_FLAGS) {
|
|
fp_err("cant detect minutiae for non-standardized image");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 25.4 mm per inch */
|
|
timer = g_timer_new();
|
|
r = get_minutiae(&minutiae, &quality_map, &direction_map,
|
|
&low_contrast_map, &low_flow_map, &high_curve_map,
|
|
&map_w, &map_h, &bdata, &bw, &bh, &bd,
|
|
img->data, img->width, img->height, 8,
|
|
DEFAULT_PPI / (double)25.4, &lfsparms_V2);
|
|
g_timer_stop(timer);
|
|
fp_dbg("minutiae scan completed in %f secs", g_timer_elapsed(timer, NULL));
|
|
g_timer_destroy(timer);
|
|
if (r) {
|
|
fp_err("get minutiae failed, code %d", r);
|
|
return r;
|
|
}
|
|
fp_dbg("detected %d minutiae", minutiae->num);
|
|
img->minutiae = minutiae;
|
|
img->binarized = bdata;
|
|
|
|
free(quality_map);
|
|
free(direction_map);
|
|
free(low_contrast_map);
|
|
free(low_flow_map);
|
|
free(high_curve_map);
|
|
return minutiae->num;
|
|
}
|
|
|
|
int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img,
|
|
struct fp_print_data **ret)
|
|
{
|
|
struct fp_print_data *print;
|
|
int r;
|
|
|
|
if (!img->minutiae) {
|
|
r = fpi_img_detect_minutiae(img);
|
|
if (r < 0)
|
|
return r;
|
|
if (!img->minutiae) {
|
|
fp_err("no minutiae after successful detection?");
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
/* FIXME: space is wasted if we dont hit the max minutiae count. would
|
|
* be good to make this dynamic. */
|
|
print = fpi_print_data_new(imgdev->dev, sizeof(struct xyt_struct));
|
|
print->type = PRINT_DATA_NBIS_MINUTIAE;
|
|
minutiae_to_xyt(img->minutiae, img->width, img->height, print->data);
|
|
|
|
/* FIXME: the print buffer at this point is endian-specific, and will
|
|
* only work when loaded onto machines with identical endianness. not good!
|
|
* data format should be platform-independant. */
|
|
*ret = print;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fpi_img_compare_print_data(struct fp_print_data *enrolled_print,
|
|
struct fp_print_data *new_print)
|
|
{
|
|
struct xyt_struct *gstruct = (struct xyt_struct *) enrolled_print->data;
|
|
struct xyt_struct *pstruct = (struct xyt_struct *) new_print->data;
|
|
GTimer *timer;
|
|
int r;
|
|
|
|
if (enrolled_print->type != PRINT_DATA_NBIS_MINUTIAE ||
|
|
new_print->type != PRINT_DATA_NBIS_MINUTIAE) {
|
|
fp_err("invalid print format");
|
|
return -EINVAL;
|
|
}
|
|
|
|
timer = g_timer_new();
|
|
r = bozorth_main(pstruct, gstruct);
|
|
g_timer_stop(timer);
|
|
fp_dbg("bozorth processing took %f seconds, score=%d",
|
|
g_timer_elapsed(timer, NULL), r);
|
|
g_timer_destroy(timer);
|
|
|
|
return r;
|
|
}
|
|
|
|
/** \ingroup img
|
|
* Get a binarized form of a standardized scanned image. This is where the
|
|
* fingerprint image has been "enhanced" and is a set of pure black ridges
|
|
* on a pure white background. Internally, image processing happens on top
|
|
* of the binarized image.
|
|
*
|
|
* The image must have been \ref img_std "standardized" otherwise this function
|
|
* will fail.
|
|
*
|
|
* It is safe to binarize an image and free the original while continuing
|
|
* to use the binarized version.
|
|
*
|
|
* You cannot binarize an image twice.
|
|
*
|
|
* \param img a standardized image
|
|
* \returns a new image representing the binarized form of the original, or
|
|
* NULL on error. Must be freed with fp_img_free() after use.
|
|
*/
|
|
API_EXPORTED struct fp_img *fp_img_binarize(struct fp_img *img)
|
|
{
|
|
struct fp_img *ret;
|
|
int height = img->height;
|
|
int width = img->width;
|
|
int imgsize = height * width;
|
|
|
|
if (img->flags & FP_IMG_BINARIZED_FORM) {
|
|
fp_err("image already binarized");
|
|
return NULL;
|
|
}
|
|
|
|
if (!img->binarized) {
|
|
int r = fpi_img_detect_minutiae(img);
|
|
if (r < 0)
|
|
return NULL;
|
|
if (!img->binarized)
|
|
fp_err("no minutiae after successful detection?");
|
|
}
|
|
|
|
ret = fpi_img_new(imgsize);
|
|
ret->flags |= FP_IMG_BINARIZED_FORM;
|
|
ret->width = width;
|
|
ret->height = height;
|
|
memcpy(ret->data, img->binarized, imgsize);
|
|
return ret;
|
|
}
|
|
|