libfprint/libfprint/img.c
Daniel Drake e3451158e9 Custom image resizing
mindtct appears to completely ignore the pixels-per-mm input parameter
(ippmm). When processing AES4000 images, the binarized image is
completely mangled and a lot of ridge information is lost.

Resizing the AES4000's small images results in a huge imaging performance
gain.

We use imagemagick for the resizing, as it's resizing code resamples the
image too (smoothing it out), which further improves performance.
2007-11-12 23:28:46 +00:00

397 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 <magick/ImageMagick.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
*/
API_EXPORTED void fp_img_free(struct fp_img *img)
{
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;
}
}
static struct fp_img *im_resize(struct fp_img *img, unsigned int factor)
{
Image *mimg;
Image *resized;
ExceptionInfo *exception;
MagickBooleanType ret;
int new_width = img->width * factor;
int new_height = img->height * factor;
struct fp_img *newimg;
/* It is possible to implement resizing using a simple algorithm, however
* we use ImageMagick because it applies some kind of smoothing to the
* result, which improves matching performances in my experiments. */
if (!IsMagickInstantiated())
MagickCoreGenesis(NULL, MagickFalse);
exception = AcquireExceptionInfo();
mimg = ConstituteImage(img->width, img->height, "I", CharPixel, img->data, exception);
ClearMagickException(exception);
resized = ResizeImage(mimg, new_width, new_height, 0, 1.0, exception);
newimg = fpi_img_new(new_width * new_height);
newimg->width = new_width;
newimg->height = new_height;
newimg->flags = img->flags;
ClearMagickException(exception);
ret = ExportImagePixels(resized, 0, 0, new_width, new_height, "I",
CharPixel, newimg->data, exception);
if (ret != MagickTrue) {
fp_err("export failed");
return NULL;
}
DestroyImage(mimg);
DestroyImage(resized);
DestroyExceptionInfo(exception);
return newimg;
}
/* Based on write_minutiae_XYTQ and bz_load */
static void minutiae_to_xyt(MINUTIAE *minutiae, int bwidth,
int bheight, unsigned char *buf)
{
int i;
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_dev *imgdev, struct fp_img *_img,
struct fp_print_data **ret)
{
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;
struct fp_print_data *print;
struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(imgdev->dev->drv);
struct fp_img *img = _img;
int free_img = 0;
GTimer *timer;
if (imgdrv->enlarge_factor) {
/* FIXME: enlarge_factor should not exist! instead, MINDTCT should
* actually look at the value of the pixels-per-mm parameter and
* figure out itself when the image needs to be treated as if it
* were bigger. */
img = im_resize(_img, imgdrv->enlarge_factor);
free_img = 1;
}
/* 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 (free_img)
g_free(img);
if (r) {
fp_err("get minutiae failed, code %d", r);
return r;
}
fp_dbg("detected %d minutiae", minutiae->num);
r = minutiae->num;
/* 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(minutiae, bw, bh, 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;
free_minutiae(minutiae);
free(quality_map);
free(direction_map);
free(low_contrast_map);
free(low_flow_map);
free(high_curve_map);
free(bdata);
return r;
}
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;
}