Commit ba24c088 authored by Daniel Drake's avatar Daniel Drake

Rework some image handling; add public binarization API

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.
parent 72c30499
......@@ -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;
}
......
......@@ -181,7 +181,7 @@ retry:
return 0;
err:
g_free(data);
g_free(img);
fp_img_free(img);
return r;
}
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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 */
......
......@@ -23,7 +23,6 @@
#include <string.h>
#include <glib.h>
#include <magick/ImageMagick.h>
#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;
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(minutiae, bw, bh, print->data);
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;
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;
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;
}
......@@ -18,7 +18,9 @@
*/
#include <errno.h>
#include <glib.h>
#include <magick/ImageMagick.h>
#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;
......
......@@ -43,6 +43,7 @@ identified are necessarily the best available for the purpose.
#include <math.h>
#include <stdio.h>
#include <fp_internal.h>
/*************************************************************************/
/* 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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment