img.c 11 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*
 * 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>
Daniel Drake's avatar
Daniel Drake committed
26
#include <magick/ImageMagick.h>
27 28

#include "fp_internal.h"
29 30
#include "nbis/include/bozorth.h"
#include "nbis/include/lfs.h"
31

Daniel Drake's avatar
Daniel Drake committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
/** @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.
 */

49 50 51 52 53 54 55 56 57
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;
}

58
struct fp_img *fpi_img_new_for_imgdev(struct fp_img_dev *imgdev)
59
{
60 61 62
	struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(imgdev->dev->drv);
	int width = imgdrv->img_width;
	int height = imgdrv->img_height;
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
	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);
}

Daniel Drake's avatar
Daniel Drake committed
87 88
/** \ingroup img
 * Frees an image. Must be called when you are finished working with an image.
89
 * \param img the image to destroy. If NULL, function simply returns.
Daniel Drake's avatar
Daniel Drake committed
90
 */
91 92 93 94 95
API_EXPORTED void fp_img_free(struct fp_img *img)
{
	g_free(img);
}

Daniel Drake's avatar
Daniel Drake committed
96 97 98 99 100
/** \ingroup img
 * Gets the pixel height of an image.
 * \param img an image
 * \returns the height of the image
 */
101 102 103 104 105
API_EXPORTED int fp_img_get_height(struct fp_img *img)
{
	return img->height;
}

Daniel Drake's avatar
Daniel Drake committed
106 107 108 109 110
/** \ingroup img
 * Gets the pixel width of an image.
 * \param img an image
 * \returns the width of the image
 */
111 112 113 114 115
API_EXPORTED int fp_img_get_width(struct fp_img *img)
{
	return img->width;
}

Daniel Drake's avatar
Daniel Drake committed
116 117 118 119 120 121
/** \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
 */
122 123 124 125 126
API_EXPORTED unsigned char *fp_img_get_data(struct fp_img *img)
{
	return img->data;
}

Daniel Drake's avatar
Daniel Drake committed
127 128 129 130 131 132 133
/** \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.
 */
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
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;
}

Daniel Drake's avatar
Daniel Drake committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
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];
}

Daniel Drake's avatar
Daniel Drake committed
207 208 209 210 211 212 213
/** \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
 */
Daniel Drake's avatar
Daniel Drake committed
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
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;
	}
}
229

Daniel Drake's avatar
Daniel Drake committed
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
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;
}

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
/* 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;
}

Daniel Drake's avatar
Daniel Drake committed
309
int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *_img,
310 311 312 313 314 315 316 317 318 319
	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;
Daniel Drake's avatar
Daniel Drake committed
320 321 322
	struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(imgdev->dev->drv);
	struct fp_img *img = _img;
	int free_img = 0;
323 324
	GTimer *timer;

Daniel Drake's avatar
Daniel Drake committed
325 326 327 328 329 330 331 332 333
	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;
	}

334 335 336 337 338 339 340 341 342 343
	/* 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);
Daniel Drake's avatar
Daniel Drake committed
344 345
	if (free_img)
		g_free(img);
346 347 348 349 350 351 352 353 354 355
	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));
356 357
	print->type = PRINT_DATA_NBIS_MINUTIAE;
	minutiae_to_xyt(minutiae, bw, bh, print->data);
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
	/* 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;
}
373 374 375 376

int fpi_img_compare_print_data(struct fp_print_data *enrolled_print,
	struct fp_print_data *new_print)
{
377 378
	struct xyt_struct *gstruct = (struct xyt_struct *) enrolled_print->data;
	struct xyt_struct *pstruct = (struct xyt_struct *) new_print->data;
379 380 381
	GTimer *timer;
	int r;

382 383 384 385 386 387
	if (enrolled_print->type != PRINT_DATA_NBIS_MINUTIAE ||
			new_print->type != PRINT_DATA_NBIS_MINUTIAE) {
		fp_err("invalid print format");
		return -EINVAL;
	}

388 389 390 391 392 393 394 395 396
	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;
}