diff --git a/libfprint/drivers/aes2501.c b/libfprint/drivers/aes2501.c index 1c60aca..e475787 100644 --- a/libfprint/drivers/aes2501.c +++ b/libfprint/drivers/aes2501.c @@ -630,7 +630,7 @@ static int capture(struct fp_img_dev *dev, gboolean unconditional, *ret = img; return 0; err: - g_free(img); + fp_img_free(img); return r; } diff --git a/libfprint/drivers/aes4000.c b/libfprint/drivers/aes4000.c index 8198132..4b43346 100644 --- a/libfprint/drivers/aes4000.c +++ b/libfprint/drivers/aes4000.c @@ -181,7 +181,7 @@ retry: return 0; err: g_free(data); - g_free(img); + fp_img_free(img); return r; } diff --git a/libfprint/drivers/uru4000.c b/libfprint/drivers/uru4000.c index 56ac398..51b57c3 100644 --- a/libfprint/drivers/uru4000.c +++ b/libfprint/drivers/uru4000.c @@ -360,7 +360,7 @@ static int capture(struct fp_img_dev *dev, gboolean unconditional, *ret = img; return 0; err: - g_free(img); + fp_img_free(img); return r; } diff --git a/libfprint/fp_internal.h b/libfprint/fp_internal.h index 1e6b684..2fa7dad 100644 --- a/libfprint/fp_internal.h +++ b/libfprint/fp_internal.h @@ -191,16 +191,43 @@ gboolean fpi_print_data_compatible(uint16_t driver_id1, uint32_t devtype1, enum fp_print_data_type type1, uint16_t driver_id2, uint32_t devtype2, enum fp_print_data_type type2); +struct fp_minutia { + int x; + int y; + int ex; + int ey; + int direction; + double reliability; + int type; + int appearing; + int feature_id; + int *nbrs; + int *ridge_counts; + int num_nbrs; +}; + +struct fp_minutiae { + int alloc; + int num; + struct fp_minutia **list; +}; + /* bit values for fp_img.flags */ #define FP_IMG_V_FLIPPED (1<<0) #define FP_IMG_H_FLIPPED (1<<1) #define FP_IMG_COLORS_INVERTED (1<<2) +#define FP_IMG_BINARIZED_FORM (1<<3) + +#define FP_IMG_STANDARDIZATION_FLAGS (FP_IMG_V_FLIPPED | FP_IMG_H_FLIPPED \ + | FP_IMG_COLORS_INVERTED) struct fp_img { int width; int height; size_t length; uint16_t flags; + struct fp_minutiae *minutiae; + unsigned char *binarized; unsigned char data[0]; }; @@ -208,7 +235,8 @@ struct fp_img *fpi_img_new(size_t length); struct fp_img *fpi_img_new_for_imgdev(struct fp_img_dev *dev); struct fp_img *fpi_img_resize(struct fp_img *img, size_t newsize); gboolean fpi_img_is_sane(struct fp_img *img); -int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *img, +int fpi_img_detect_minutiae(struct fp_img *img); +int fpi_img_to_print_data(struct fp_img_dev *imgdev, struct fp_img *img, struct fp_print_data **ret); int fpi_img_compare_print_data(struct fp_print_data *enrolled_print, struct fp_print_data *new_print); diff --git a/libfprint/fprint.h b/libfprint/fprint.h index 334fe99..8eccd03 100644 --- a/libfprint/fprint.h +++ b/libfprint/fprint.h @@ -212,6 +212,7 @@ int fp_img_get_width(struct fp_img *img); unsigned char *fp_img_get_data(struct fp_img *img); int fp_img_save_to_file(struct fp_img *img, char *path); void fp_img_standardize(struct fp_img *img); +struct fp_img *fp_img_binarize(struct fp_img *img); void fp_img_free(struct fp_img *img); /* Library */ diff --git a/libfprint/img.c b/libfprint/img.c index 3db9248..d579a63 100644 --- a/libfprint/img.c +++ b/libfprint/img.c @@ -23,7 +23,6 @@ #include #include -#include #include "fp_internal.h" #include "nbis/include/bozorth.h" @@ -90,6 +89,13 @@ struct fp_img *fpi_img_resize(struct fp_img *img, size_t newsize) */ 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); } @@ -227,55 +233,12 @@ API_EXPORTED void fp_img_standardize(struct fp_img *img) } } -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()) - InitializeMagick(NULL); - - GetExceptionInfo(&exception); - mimg = ConstituteImage(img->width, img->height, "I", CharPixel, img->data, - &exception); - - GetExceptionInfo(&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; - - GetExceptionInfo(&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); - - return newimg; -} - /* Based on write_minutiae_XYTQ and bz_load */ -static void minutiae_to_xyt(MINUTIAE *minutiae, int bwidth, +static void minutiae_to_xyt(struct fp_minutiae *minutiae, int bwidth, int bheight, unsigned char *buf) { int i; - MINUTIA *minutia; + struct fp_minutia *minutia; struct minutiae_struct c[MAX_FILE_MINUTIAE]; struct xyt_struct *xyt = (struct xyt_struct *) buf; @@ -305,29 +268,20 @@ static void minutiae_to_xyt(MINUTIAE *minutiae, int bwidth, xyt->nrows = nmin; } -int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *_img, - struct fp_print_data **ret) +int fpi_img_detect_minutiae(struct fp_img *img) { - MINUTIAE *minutiae; + 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; - 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; + if (img->flags & FP_IMG_STANDARDIZATION_FLAGS) { + fp_err("cant detect minutiae for non-standardized image"); + return -EINVAL; } /* 25.4 mm per inch */ @@ -340,34 +294,50 @@ int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *_img, 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; + img->minutiae = minutiae; + img->binarized = bdata; - /* 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 minutiae->num; +} - return r; +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, @@ -393,3 +363,50 @@ int fpi_img_compare_print_data(struct fp_print_data *enrolled_print, 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; +} + diff --git a/libfprint/imgdev.c b/libfprint/imgdev.c index 1e99998..f072959 100644 --- a/libfprint/imgdev.c +++ b/libfprint/imgdev.c @@ -18,7 +18,9 @@ */ #include + #include +#include #include "fp_internal.h" @@ -62,14 +64,65 @@ int fpi_imgdev_get_img_width(struct fp_img_dev *imgdev) { struct fp_driver *drv = imgdev->dev->drv; struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(drv); - return imgdrv->img_width; + int width = imgdrv->img_width; + + if (width > 0 && imgdrv->enlarge_factor > 1) + width *= imgdrv->enlarge_factor; + return width; } int fpi_imgdev_get_img_height(struct fp_img_dev *imgdev) { struct fp_driver *drv = imgdev->dev->drv; struct fp_img_driver *imgdrv = fpi_driver_to_img_driver(drv); - return imgdrv->img_height; + int height = imgdrv->img_height; + + if (height > 0 && imgdrv->enlarge_factor > 1) + height *= imgdrv->enlarge_factor; + return height; +} + +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()) + InitializeMagick(NULL); + + GetExceptionInfo(&exception); + mimg = ConstituteImage(img->width, img->height, "I", CharPixel, img->data, + &exception); + + GetExceptionInfo(&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; + + GetExceptionInfo(&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); + + return newimg; } int fpi_imgdev_capture(struct fp_img_dev *imgdev, int unconditional, @@ -147,6 +200,16 @@ int fpi_imgdev_capture(struct fp_img_dev *imgdev, int unconditional, goto err; } + if (imgdrv->enlarge_factor > 1) { + /* 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. */ + struct fp_img *tmp = im_resize(img, imgdrv->enlarge_factor); + fp_img_free(img); + img = tmp; + } + *_img = img; return 0; err: @@ -178,10 +241,10 @@ int img_dev_enroll(struct fp_dev *dev, gboolean initial, int stage, if (r) return r; - r = fpi_img_detect_minutiae(imgdev, img, &print); + r = fpi_img_to_print_data(imgdev, img, &print); if (r < 0) return r; - if (r < MIN_ACCEPTABLE_MINUTIAE) { + if (img->minutiae->num < MIN_ACCEPTABLE_MINUTIAE) { fp_dbg("not enough minutiae, %d/%d", r, MIN_ACCEPTABLE_MINUTIAE); fp_print_data_free(print); return FP_ENROLL_RETRY; @@ -214,10 +277,10 @@ static int img_dev_verify(struct fp_dev *dev, if (r) return r; - r = fpi_img_detect_minutiae(imgdev, img, &print); + r = fpi_img_to_print_data(imgdev, img, &print); if (r < 0) return r; - if (r < MIN_ACCEPTABLE_MINUTIAE) { + if (img->minutiae->num < MIN_ACCEPTABLE_MINUTIAE) { fp_dbg("not enough minutiae, %d/%d", r, MIN_ACCEPTABLE_MINUTIAE); fp_print_data_free(print); return FP_VERIFY_RETRY; diff --git a/libfprint/nbis/include/lfs.h b/libfprint/nbis/include/lfs.h index c184425..2da5d25 100644 --- a/libfprint/nbis/include/lfs.h +++ b/libfprint/nbis/include/lfs.h @@ -43,6 +43,7 @@ identified are necessarily the best available for the purpose. #include #include +#include /*************************************************************************/ /* OUTPUT FILE EXTENSIONS */ @@ -130,26 +131,8 @@ typedef struct rotgrids{ #define DISAPPEARING 0 #define APPEARING 1 -typedef struct minutia{ - int x; - int y; - int ex; - int ey; - int direction; - double reliability; - int type; - int appearing; - int feature_id; - int *nbrs; - int *ridge_counts; - int num_nbrs; -} MINUTIA; - -typedef struct minutiae{ - int alloc; - int num; - MINUTIA **list; -} MINUTIAE; +typedef struct fp_minutia MINUTIA; +typedef struct fp_minutiae MINUTIAE; typedef struct feature_pattern{ int type;