mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-03-28 07:40:41 +01:00
Add SIGFM (SIFT-based) matching algorithm for small-area sensors
Add SIGFM, a SIFT-based fingerprint matching algorithm designed for small-area sensors where NBIS minutiae detection is unreliable. SIGFM uses OpenCV4 SIFT keypoint matching with geometric consistency verification to compare fingerprint images directly, making it suitable for sensors with capture areas as small as 112x88 pixels. The algorithm is integrated as an optional dependency: when OpenCV4 is available, SIGFM support and SIGFM-based drivers are built; otherwise, the build proceeds without them. This follows the same pattern used for PIXMAN and UDEV optional dependencies. Based on work by Natalie Klestrup Röijezon (MR !418) and Tooniis (MR !530).
This commit is contained in:
parent
2c7842c905
commit
910fcad2cf
36 changed files with 6694 additions and 102 deletions
|
|
@ -850,7 +850,7 @@ fpi_device_aes1610_class_init (FpiDeviceAes1610Class *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->bz3_threshold = 20;
|
||||
img_class->score_threshold = 20;
|
||||
|
||||
img_class->img_width = IMAGE_WIDTH;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ fpi_device_aes1660_class_init (FpiDeviceAes1660Class *klass)
|
|||
dev_class->id_table = id_table;
|
||||
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
|
||||
|
||||
img_class->bz3_threshold = 20;
|
||||
img_class->score_threshold = 20;
|
||||
|
||||
img_class->img_width = FRAME_WIDTH + FRAME_WIDTH / 2;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ fpi_device_aes2660_class_init (FpiDeviceAes2660Class *klass)
|
|||
dev_class->id_table = id_table;
|
||||
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
|
||||
|
||||
img_class->bz3_threshold = 20;
|
||||
img_class->score_threshold = 20;
|
||||
|
||||
img_class->img_width = FRAME_WIDTH + FRAME_WIDTH / 2;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ fpi_device_aes3k_class_init (FpiDeviceAes3kClass *klass)
|
|||
img_class->deactivate = aes3k_dev_deactivate;
|
||||
|
||||
/* Extremely low due to low image quality. */
|
||||
img_class->bz3_threshold = 9;
|
||||
img_class->score_threshold = 9;
|
||||
|
||||
/* Everything else is set by the subclasses. */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -440,5 +440,5 @@ fpi_device_egis0570_class_init (FpDeviceEgis0570Class *klass)
|
|||
img_class->img_width = EGIS0570_IMGWIDTH;
|
||||
img_class->img_height = -1;
|
||||
|
||||
img_class->bz3_threshold = EGIS0570_BZ3_THRESHOLD; /* security issue */
|
||||
img_class->score_threshold = EGIS0570_BZ3_THRESHOLD; /* security issue */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ static unsigned char repeat_pkts[][EGIS0570_PKTSIZE] =
|
|||
};
|
||||
|
||||
/*
|
||||
* This sensor is small so I decided to reduce bz3_threshold from
|
||||
* This sensor is small so I decided to reduce score_threshold from
|
||||
* 40 to 10 to have more success to fail ratio
|
||||
* Bozorth3 Algorithm seems not fine at the end
|
||||
* foreget about security :))
|
||||
|
|
|
|||
|
|
@ -1006,5 +1006,5 @@ fpi_device_elan_class_init (FpiDeviceElanClass *klass)
|
|||
img_class->deactivate = dev_deactivate;
|
||||
img_class->change_state = dev_change_state;
|
||||
|
||||
img_class->bz3_threshold = 24;
|
||||
img_class->score_threshold = 24;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1701,7 +1701,7 @@ fpi_device_elanspi_class_init (FpiDeviceElanSpiClass *klass)
|
|||
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
|
||||
dev_class->nr_enroll_stages = 7; /* these sensors are very hit or miss, may as well record a few extras */
|
||||
|
||||
img_class->bz3_threshold = 24;
|
||||
img_class->score_threshold = 24;
|
||||
img_class->img_open = elanspi_open;
|
||||
img_class->activate = elanspi_activate;
|
||||
img_class->deactivate = elanspi_deactivate;
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ fpi_device_nb1010_class_init (FpiDeviceNb1010Class *klass)
|
|||
img_class->img_height = FRAME_HEIGHT;
|
||||
img_class->img_width = FRAME_WIDTH;
|
||||
|
||||
img_class->bz3_threshold = 24;
|
||||
img_class->score_threshold = 24;
|
||||
|
||||
img_class->img_open = nb1010_dev_init;
|
||||
img_class->img_close = nb1010_dev_deinit;
|
||||
|
|
|
|||
|
|
@ -1540,7 +1540,7 @@ dev_init (FpImageDevice *dev)
|
|||
self->assembling_ctx.line_width = IMG_WIDTH_1001;
|
||||
|
||||
/* The sensor resolution is too low for the normal threshold. */
|
||||
fpi_image_device_set_bz3_threshold (dev, 25);
|
||||
fpi_image_device_set_score_threshold (dev, 25);
|
||||
break;
|
||||
|
||||
case UPEKSONLY_2016:
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ fpi_device_upektc_class_init (FpiDeviceUpektcClass *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->bz3_threshold = 30;
|
||||
img_class->score_threshold = 30;
|
||||
|
||||
img_class->img_width = IMAGE_WIDTH;
|
||||
img_class->img_height = IMAGE_HEIGHT;
|
||||
|
|
|
|||
|
|
@ -777,7 +777,7 @@ fpi_device_upektc_img_class_init (FpiDeviceUpektcImgClass *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->bz3_threshold = 20;
|
||||
img_class->score_threshold = 20;
|
||||
|
||||
img_class->img_width = -1;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -766,7 +766,7 @@ fpi_device_vfs0050_class_init (FpDeviceVfs0050Class *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->bz3_threshold = 24;
|
||||
img_class->score_threshold = 24;
|
||||
|
||||
img_class->img_width = VFS_IMAGE_WIDTH;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -1363,7 +1363,7 @@ fpi_device_vfs101_class_init (FpDeviceVfs101Class *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->bz3_threshold = 24;
|
||||
img_class->score_threshold = 24;
|
||||
|
||||
img_class->img_width = VFS_IMG_WIDTH;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ fpi_device_vfs301_class_init (FpDeviceVfs301Class *klass)
|
|||
img_class->deactivate = dev_deactivate;
|
||||
img_class->change_state = dev_change_state;
|
||||
|
||||
img_class->bz3_threshold = 24;
|
||||
img_class->score_threshold = 24;
|
||||
|
||||
img_class->img_width = VFS301_FP_WIDTH;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -894,7 +894,7 @@ fpi_device_vfs5011_class_init (FpDeviceVfs5011Class *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->deactivate = dev_deactivate;
|
||||
|
||||
img_class->bz3_threshold = 20;
|
||||
img_class->score_threshold = 20;
|
||||
|
||||
img_class->img_width = VFS5011_IMAGE_WIDTH;
|
||||
img_class->img_height = -1;
|
||||
|
|
|
|||
|
|
@ -1065,7 +1065,7 @@ fpi_device_vfs7552_class_init (FpDeviceVfs7552Class *klass)
|
|||
img_class->activate = dev_activate;
|
||||
img_class->img_open = dev_open;
|
||||
|
||||
img_class->bz3_threshold = 20;
|
||||
img_class->score_threshold = 20;
|
||||
|
||||
img_class->img_width = VFS7552_IMAGE_WIDTH;
|
||||
img_class->img_height = VFS7552_IMAGE_HEIGHT;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ typedef struct
|
|||
GError *action_error;
|
||||
FpImage *capture_image;
|
||||
|
||||
gint bz3_threshold;
|
||||
gint score_threshold;
|
||||
FpiPrintType algorithm;
|
||||
} FpImageDevicePrivate;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "fpi-print.h"
|
||||
#define FP_COMPONENT "image_device"
|
||||
#include "fpi-log.h"
|
||||
|
||||
|
|
@ -101,7 +102,6 @@ fp_image_device_start_capture_action (FpDevice *device)
|
|||
FpImageDevice *self = FP_IMAGE_DEVICE (device);
|
||||
FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
|
||||
FpiDeviceAction action;
|
||||
FpiPrintType print_type;
|
||||
|
||||
/* There is just one action that we cannot support out
|
||||
* of the box, which is a capture without first waiting
|
||||
|
|
@ -125,9 +125,10 @@ fp_image_device_start_capture_action (FpDevice *device)
|
|||
FpPrint *enroll_print = NULL;
|
||||
|
||||
fpi_device_get_enroll_data (device, &enroll_print);
|
||||
FpiPrintType print_type;
|
||||
g_object_get (enroll_print, "fpi-type", &print_type, NULL);
|
||||
if (print_type != FPI_PRINT_NBIS)
|
||||
fpi_print_set_type (enroll_print, FPI_PRINT_NBIS);
|
||||
if (print_type != priv->algorithm)
|
||||
fpi_print_set_type (enroll_print, priv->algorithm);
|
||||
}
|
||||
|
||||
priv->enroll_stage = 0;
|
||||
|
|
@ -194,9 +195,12 @@ fp_image_device_constructed (GObject *obj)
|
|||
FpImageDeviceClass *cls = FP_IMAGE_DEVICE_GET_CLASS (self);
|
||||
|
||||
/* Set default threshold. */
|
||||
priv->bz3_threshold = BOZORTH3_DEFAULT_THRESHOLD;
|
||||
if (cls->bz3_threshold > 0)
|
||||
priv->bz3_threshold = cls->bz3_threshold;
|
||||
priv->score_threshold = BOZORTH3_DEFAULT_THRESHOLD;
|
||||
if (cls->score_threshold > 0)
|
||||
priv->score_threshold = cls->score_threshold;
|
||||
priv->algorithm = FPI_PRINT_NBIS;
|
||||
if (cls->algorithm > 0)
|
||||
priv->algorithm = (FpiPrintType) cls->algorithm;
|
||||
|
||||
G_OBJECT_CLASS (fp_image_device_parent_class)->constructed (obj);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,10 +165,21 @@ typedef struct
|
|||
FpiImageFlags flags;
|
||||
unsigned char *image;
|
||||
gboolean image_changed;
|
||||
} DetectMinutiaeNbisData;
|
||||
} DetectMinutiaeData;
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
typedef struct
|
||||
{
|
||||
SigfmImgInfo * sigfm_info;
|
||||
guchar * image;
|
||||
gint width;
|
||||
gint height;
|
||||
GAsyncReadyCallback user_cb;
|
||||
} ExtractSigfmData;
|
||||
#endif
|
||||
|
||||
static void
|
||||
fp_image_detect_minutiae_free (DetectMinutiaeNbisData *data)
|
||||
fp_image_detect_minutiae_free (DetectMinutiaeData *data)
|
||||
{
|
||||
g_clear_pointer (&data->minutiae, free_minutiae);
|
||||
g_clear_pointer (&data->binarized, g_free);
|
||||
|
|
@ -179,15 +190,45 @@ fp_image_detect_minutiae_free (DetectMinutiaeNbisData *data)
|
|||
g_free (data);
|
||||
}
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DetectMinutiaeNbisData, fp_image_detect_minutiae_free)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DetectMinutiaeData, fp_image_detect_minutiae_free)
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
static void
|
||||
fp_image_sigfm_extract_free (ExtractSigfmData * data)
|
||||
{
|
||||
g_clear_pointer (&data->image, g_free);
|
||||
g_clear_pointer (&data->sigfm_info, sigfm_free_info);
|
||||
g_free (data);
|
||||
}
|
||||
|
||||
static void
|
||||
fp_image_sigfm_extract_cb (GObject * source_object, GAsyncResult * res,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask * task = G_TASK (res);
|
||||
FpImage * image;
|
||||
ExtractSigfmData * data = g_task_get_task_data (task);
|
||||
|
||||
if (!g_task_had_error (task))
|
||||
{
|
||||
image = FP_IMAGE (source_object);
|
||||
|
||||
g_clear_pointer (&image->data, g_free);
|
||||
image->data = g_steal_pointer (&data->image);
|
||||
image->sigfm_info = g_steal_pointer (&data->sigfm_info);
|
||||
}
|
||||
|
||||
if (data->user_cb)
|
||||
data->user_cb (source_object, res, user_data);
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
fp_image_detect_minutiae_nbis_finish (FpImage *self,
|
||||
GTask *task,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(DetectMinutiaeNbisData) data = NULL;
|
||||
g_autoptr(DetectMinutiaeData) data = NULL;
|
||||
|
||||
data = g_task_propagate_pointer (task, error);
|
||||
|
||||
|
|
@ -270,14 +311,48 @@ invert_colors (guint8 *data, gint width, gint height)
|
|||
data[i] = 0xff - data[i];
|
||||
}
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
static void
|
||||
fp_image_detect_minutiae_nbis_thread_func (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
fp_image_sigfm_extract_thread_func (GTask * task, void * src_obj,
|
||||
void * task_data,
|
||||
GCancellable * cancellable)
|
||||
{
|
||||
ExtractSigfmData * data = task_data;
|
||||
GTimer * timer = g_timer_new ();
|
||||
|
||||
data->sigfm_info = sigfm_extract (data->image, data->width, data->height);
|
||||
g_timer_stop (timer);
|
||||
fp_dbg ("sigfm extract completed in %f secs", g_timer_elapsed (timer, NULL));
|
||||
g_timer_destroy (timer);
|
||||
|
||||
if (!data->sigfm_info)
|
||||
{
|
||||
fp_err ("extract sigfm info failed");
|
||||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "SIGFM scan failed");
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sigfm_keypoints_count (data->sigfm_info) < 25)
|
||||
{
|
||||
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"No enough keypoints found");
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
fp_image_detect_minutiae_thread_func (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
g_autoptr(GTimer) timer = NULL;
|
||||
g_autoptr(DetectMinutiaeNbisData) ret_data = NULL;
|
||||
g_autoptr(DetectMinutiaeData) ret_data = NULL;
|
||||
g_autoptr(GTask) thread_task = g_steal_pointer (&task);
|
||||
g_autofree gint *direction_map = NULL;
|
||||
g_autofree gint *low_contrast_map = NULL;
|
||||
|
|
@ -300,7 +375,7 @@ fp_image_detect_minutiae_nbis_thread_func (GTask *task,
|
|||
if (minutiae_flags != FPI_IMAGE_NONE)
|
||||
image = g_memdup2 (self->data, self->width * self->height);
|
||||
|
||||
ret_data = g_new0 (DetectMinutiaeNbisData, 1);
|
||||
ret_data = g_new0 (DetectMinutiaeData, 1);
|
||||
ret_data->flags = minutiae_flags;
|
||||
ret_data->image = image;
|
||||
ret_data->image_changed = image != self->data;
|
||||
|
|
@ -448,6 +523,51 @@ fp_image_get_minutiae (FpImage *self)
|
|||
return self->minutiae;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
/**
|
||||
* fp_image_get_sigfm_info:
|
||||
* @self: A #FpImage
|
||||
*
|
||||
* Gets the SIGFM keypoints and descriptors for an image. This data must
|
||||
* not be modified or freed. You need to first extract keypoints and
|
||||
* descriptors using fp_image_extract_sigfm_info().
|
||||
*
|
||||
* Returns: (skip): SIGFM keypoints and descriptors
|
||||
*/
|
||||
SigfmImgInfo *
|
||||
fp_image_get_sigfm_info (FpImage * self)
|
||||
{
|
||||
return self->sigfm_info;
|
||||
}
|
||||
|
||||
/*
|
||||
* fp_image_extract_sigfm_info:
|
||||
*
|
||||
* Extracts SIFT keypoints and descriptors from an image.
|
||||
* Completion is handled via fp_image_detect_minutiae_finish().
|
||||
*/
|
||||
void
|
||||
fp_image_extract_sigfm_info (FpImage * self, GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback, gpointer user_data)
|
||||
{
|
||||
GTask * task;
|
||||
ExtractSigfmData * data = g_new0 (ExtractSigfmData, 1);
|
||||
|
||||
task = g_task_new (self, cancellable, fp_image_sigfm_extract_cb, user_data);
|
||||
g_task_set_source_tag (task, fp_image_extract_sigfm_info);
|
||||
|
||||
data->image = g_malloc (self->width * self->height);
|
||||
memcpy (data->image, self->data, self->width * self->height);
|
||||
data->width = self->width;
|
||||
data->height = self->height;
|
||||
data->user_cb = callback;
|
||||
|
||||
g_task_set_task_data (task, data,
|
||||
(GDestroyNotify) fp_image_sigfm_extract_free);
|
||||
g_task_run_in_thread (task, fp_image_sigfm_extract_thread_func);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* fp_image_detect_minutiae:
|
||||
* @self: A #FpImage
|
||||
|
|
@ -481,7 +601,7 @@ fp_image_detect_minutiae (FpImage *self,
|
|||
}
|
||||
|
||||
g_task_run_in_thread (g_steal_pointer (&task),
|
||||
fp_image_detect_minutiae_nbis_thread_func);
|
||||
fp_image_detect_minutiae_thread_func);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -504,6 +624,12 @@ fp_image_detect_minutiae_finish (FpImage *self,
|
|||
|
||||
g_return_val_if_fail (FP_IS_IMAGE (self), FALSE);
|
||||
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
if (g_task_get_source_tag (G_TASK (result)) == fp_image_extract_sigfm_info)
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
#endif
|
||||
|
||||
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
|
||||
fp_image_detect_minutiae, FALSE);
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ void fp_image_detect_minutiae (FpImage *self,
|
|||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
gboolean fp_image_detect_minutiae_finish (FpImage *self,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#ifdef HAVE_SIGFM
|
||||
#include "sigfm/sigfm.h"
|
||||
#endif
|
||||
#define FP_COMPONENT "print"
|
||||
|
||||
#include "fp-print-private.h"
|
||||
|
|
@ -680,6 +684,8 @@ fp_print_serialize (FpPrint *print,
|
|||
g_variant_builder_open (&builder, G_VARIANT_TYPE_VARDICT);
|
||||
g_variant_builder_close (&builder);
|
||||
|
||||
GPtrArray * to_free = NULL;
|
||||
|
||||
/* Insert NBIS print data for type NBIS, otherwise the GVariant directly */
|
||||
if (print->type == FPI_PRINT_NBIS)
|
||||
{
|
||||
|
|
@ -714,6 +720,30 @@ fp_print_serialize (FpPrint *print,
|
|||
g_variant_builder_close (&nested);
|
||||
g_variant_builder_add (&builder, "v", g_variant_builder_end (&nested));
|
||||
}
|
||||
#ifdef HAVE_SIGFM
|
||||
else if (print->type == FPI_PRINT_SIGFM)
|
||||
{
|
||||
to_free = g_ptr_array_new ();
|
||||
g_ptr_array_set_free_func (to_free, free);
|
||||
GVariantBuilder nested =
|
||||
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("(a(ay))"));
|
||||
g_variant_builder_open (&nested, G_VARIANT_TYPE ("a(ay)"));
|
||||
for (int i = 0; i != print->prints->len; ++i)
|
||||
{
|
||||
g_variant_builder_open (&nested, G_VARIANT_TYPE ("(ay)"));
|
||||
SigfmImgInfo * info = g_ptr_array_index (print->prints, i);
|
||||
int slen;
|
||||
unsigned char * serialized = sigfm_serialize_binary (info, &slen);
|
||||
g_variant_builder_add_value (
|
||||
&nested, g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
||||
serialized, slen, 1));
|
||||
g_ptr_array_add (to_free, serialized);
|
||||
g_variant_builder_close (&nested);
|
||||
}
|
||||
g_variant_builder_close (&nested);
|
||||
g_variant_builder_add (&builder, "v", g_variant_builder_end (&nested));
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
g_variant_builder_add (&builder, "v", g_variant_new_variant (print->data));
|
||||
|
|
@ -741,6 +771,8 @@ fp_print_serialize (FpPrint *print,
|
|||
|
||||
g_variant_get_data (result);
|
||||
g_variant_store (result, (*data) + 3);
|
||||
if (to_free != NULL)
|
||||
g_ptr_array_free (to_free, TRUE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -870,6 +902,37 @@ fp_print_deserialize (const guchar *data,
|
|||
g_ptr_array_add (result->prints, g_steal_pointer (&xyt));
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_SIGFM
|
||||
else if (type == FPI_PRINT_SIGFM)
|
||||
{
|
||||
g_autoptr(GVariant) prints = g_variant_get_child_value (print_data, 0);
|
||||
guint i;
|
||||
|
||||
result = g_object_new (FP_TYPE_PRINT, "driver", driver, "device-id",
|
||||
device_id, "device-stored", device_stored, NULL);
|
||||
g_object_ref_sink (result);
|
||||
fpi_print_set_type (result, FPI_PRINT_SIGFM);
|
||||
|
||||
for (i = 0; i < g_variant_n_children (prints); i++)
|
||||
{
|
||||
g_autoptr(GVariant) sigfm_data = NULL;
|
||||
|
||||
sigfm_data = g_variant_get_child_value (prints, i);
|
||||
|
||||
GVariant * child = g_variant_get_child_value (sigfm_data, 0);
|
||||
gsize slen;
|
||||
const unsigned char * serialized =
|
||||
g_variant_get_fixed_array (child, &slen, sizeof (unsigned char));
|
||||
g_variant_unref (child);
|
||||
|
||||
SigfmImgInfo * sigfm_info = sigfm_deserialize_binary (serialized, slen);
|
||||
if (!sigfm_info)
|
||||
goto invalid_format;
|
||||
|
||||
g_ptr_array_add (result->prints, g_steal_pointer (&sigfm_info));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (type == FPI_PRINT_RAW)
|
||||
{
|
||||
g_autoptr(GVariant) fp_data = g_variant_get_child_value (print_data, 0);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
#include "fpi-print.h"
|
||||
#include "fpi-image.h"
|
||||
#define FP_COMPONENT "image_device"
|
||||
#include "fpi-log.h"
|
||||
|
||||
|
|
@ -276,7 +279,7 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||
if (!error)
|
||||
{
|
||||
print = fp_print_new (device);
|
||||
fpi_print_set_type (print, FPI_PRINT_NBIS);
|
||||
fpi_print_set_type (print, priv->algorithm);
|
||||
if (!fpi_print_add_from_image (print, image, &error))
|
||||
{
|
||||
g_clear_object (&print);
|
||||
|
|
@ -319,13 +322,24 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||
else if (action == FPI_DEVICE_ACTION_VERIFY)
|
||||
{
|
||||
FpPrint *template;
|
||||
FpiMatchResult result;
|
||||
FpiMatchResult result = FPI_MATCH_ERROR;
|
||||
|
||||
fpi_device_get_verify_data (device, &template);
|
||||
if (print)
|
||||
result = fpi_print_bz3_match (template, print, priv->bz3_threshold, &error);
|
||||
{
|
||||
if (priv->algorithm == FPI_PRINT_NBIS)
|
||||
result = fpi_print_bz3_match (template, print, priv->score_threshold,
|
||||
&error);
|
||||
#ifdef HAVE_SIGFM
|
||||
else if (priv->algorithm == FPI_PRINT_SIGFM)
|
||||
result = fpi_print_sigfm_match (template, print, priv->score_threshold,
|
||||
&error);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
result = FPI_MATCH_ERROR;
|
||||
{
|
||||
result = FPI_MATCH_ERROR;
|
||||
}
|
||||
|
||||
if (!error || error->domain == FP_DEVICE_RETRY)
|
||||
fpi_device_verify_report (device, result, g_steal_pointer (&print), g_steal_pointer (&error));
|
||||
|
|
@ -343,7 +357,17 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||
{
|
||||
FpPrint *template = g_ptr_array_index (templates, i);
|
||||
|
||||
if (fpi_print_bz3_match (template, print, priv->bz3_threshold, &error) == FPI_MATCH_SUCCESS)
|
||||
int match_result = FPI_MATCH_ERROR;
|
||||
if (priv->algorithm == FPI_PRINT_NBIS)
|
||||
match_result = fpi_print_bz3_match (template, print,
|
||||
priv->score_threshold, &error);
|
||||
#ifdef HAVE_SIGFM
|
||||
else if (priv->algorithm == FPI_PRINT_SIGFM)
|
||||
match_result = fpi_print_sigfm_match (template, print,
|
||||
priv->score_threshold, &error);
|
||||
#endif
|
||||
|
||||
if (match_result == FPI_MATCH_SUCCESS)
|
||||
{
|
||||
result = template;
|
||||
break;
|
||||
|
|
@ -371,9 +395,9 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||
/* Private API */
|
||||
|
||||
/**
|
||||
* fpi_image_device_set_bz3_threshold:
|
||||
* fpi_image_device_set_score_threshold:
|
||||
* @self: a #FpImageDevice imaging fingerprint device
|
||||
* @bz3_threshold: BZ3 threshold to use
|
||||
* @score_threshold: BZ3 threshold to use
|
||||
*
|
||||
* Dynamically adjust the bz3 threshold. This is only needed for drivers
|
||||
* that support devices with different properties. It should generally be
|
||||
|
|
@ -381,15 +405,15 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||
* callback.
|
||||
*/
|
||||
void
|
||||
fpi_image_device_set_bz3_threshold (FpImageDevice *self,
|
||||
gint bz3_threshold)
|
||||
fpi_image_device_set_score_threshold (FpImageDevice *self,
|
||||
gint score_threshold)
|
||||
{
|
||||
FpImageDevicePrivate *priv = fp_image_device_get_instance_private (self);
|
||||
|
||||
g_return_if_fail (FP_IS_IMAGE_DEVICE (self));
|
||||
g_return_if_fail (bz3_threshold > 0);
|
||||
g_return_if_fail (score_threshold > 0);
|
||||
|
||||
priv->bz3_threshold = bz3_threshold;
|
||||
priv->score_threshold = score_threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -494,12 +518,22 @@ fpi_image_device_image_captured (FpImageDevice *self, FpImage *image)
|
|||
|
||||
priv->minutiae_scan_active = TRUE;
|
||||
|
||||
/* XXX: We also detect minutiae in capture mode, we solely do this
|
||||
* to normalize the image which will happen as a by-product. */
|
||||
fp_image_detect_minutiae (image,
|
||||
fpi_device_get_cancellable (FP_DEVICE (self)),
|
||||
fpi_image_device_minutiae_detected,
|
||||
self);
|
||||
#ifdef HAVE_SIGFM
|
||||
if (priv->algorithm == FPI_PRINT_SIGFM)
|
||||
{
|
||||
fp_image_extract_sigfm_info (image,
|
||||
fpi_device_get_cancellable (FP_DEVICE (self)),
|
||||
fpi_image_device_minutiae_detected, self);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
/* XXX: We also detect minutiae in capture mode, we solely do this
|
||||
* to normalize the image which will happen as a by-product. */
|
||||
fp_image_detect_minutiae (image,
|
||||
fpi_device_get_cancellable (FP_DEVICE (self)),
|
||||
fpi_image_device_minutiae_detected, self);
|
||||
}
|
||||
|
||||
/* XXX: This is wrong if we add support for raw capture mode. */
|
||||
fp_image_device_change_state (self, FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF);
|
||||
|
|
|
|||
|
|
@ -19,8 +19,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "fpi-device.h"
|
||||
#include "fp-image-device.h"
|
||||
#include "fpi-device.h"
|
||||
#include "fpi-print.h"
|
||||
|
||||
/**
|
||||
* FpiImageDeviceState:
|
||||
|
|
@ -70,9 +71,14 @@ typedef enum {
|
|||
FPI_IMAGE_DEVICE_STATE_AWAIT_FINGER_OFF,
|
||||
} FpiImageDeviceState;
|
||||
|
||||
typedef enum {
|
||||
FPI_DEVICE_ALGO_NBIS = FPI_PRINT_NBIS,
|
||||
FPI_DEVICE_ALGO_SIGFM = FPI_PRINT_SIGFM,
|
||||
} FpiImageDeviceAlgorithm;
|
||||
|
||||
/**
|
||||
* FpImageDeviceClass:
|
||||
* @bz3_threshold: Threshold to consider bozorth3 score a match, default: 40
|
||||
* @score_threshold: Threshold to consider bozorth3 score a match, default: 40
|
||||
* @img_width: Width of the image, only provide if constant
|
||||
* @img_height: Height of the image, only provide if constant
|
||||
* @img_open: Open the device and do basic initialization
|
||||
|
|
@ -102,22 +108,23 @@ typedef enum {
|
|||
*/
|
||||
struct _FpImageDeviceClass
|
||||
{
|
||||
FpDeviceClass parent_class;
|
||||
FpDeviceClass parent_class;
|
||||
|
||||
gint bz3_threshold;
|
||||
gint img_width;
|
||||
gint img_height;
|
||||
gint score_threshold;
|
||||
gint img_width;
|
||||
gint img_height;
|
||||
FpiImageDeviceAlgorithm algorithm;
|
||||
|
||||
void (*img_open) (FpImageDevice *dev);
|
||||
void (*img_close) (FpImageDevice *dev);
|
||||
void (*activate) (FpImageDevice *dev);
|
||||
void (*change_state) (FpImageDevice *dev,
|
||||
FpiImageDeviceState state);
|
||||
void (*deactivate) (FpImageDevice *dev);
|
||||
void (*img_open) (FpImageDevice *dev);
|
||||
void (*img_close) (FpImageDevice *dev);
|
||||
void (*activate) (FpImageDevice *dev);
|
||||
void (*change_state) (FpImageDevice *dev,
|
||||
FpiImageDeviceState state);
|
||||
void (*deactivate) (FpImageDevice *dev);
|
||||
};
|
||||
|
||||
void fpi_image_device_set_bz3_threshold (FpImageDevice *self,
|
||||
gint bz3_threshold);
|
||||
void fpi_image_device_set_score_threshold (FpImageDevice *self,
|
||||
gint score_threshold);
|
||||
|
||||
void fpi_image_device_session_error (FpImageDevice *self,
|
||||
GError *error);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "fp-image.h"
|
||||
#include <config.h>
|
||||
#ifdef HAVE_SIGFM
|
||||
#include "sigfm/sigfm.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* FpiImageFlags:
|
||||
|
|
@ -64,12 +68,15 @@ struct _FpImage
|
|||
FpiImageFlags flags;
|
||||
|
||||
/*< private >*/
|
||||
guint8 *data;
|
||||
guint8 *binarized;
|
||||
guint8 *data;
|
||||
guint8 *binarized;
|
||||
|
||||
GPtrArray *minutiae;
|
||||
GPtrArray *minutiae;
|
||||
#ifdef HAVE_SIGFM
|
||||
SigfmImgInfo *sigfm_info;
|
||||
#endif
|
||||
|
||||
gboolean detection_in_progress;
|
||||
gboolean detection_in_progress;
|
||||
};
|
||||
|
||||
gint fpi_std_sq_dev (const guint8 *buf,
|
||||
|
|
@ -81,3 +88,11 @@ gint fpi_mean_sq_diff_norm (const guint8 *buf1,
|
|||
FpImage *fpi_image_resize (FpImage *orig,
|
||||
guint w_factor,
|
||||
guint h_factor);
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
SigfmImgInfo * fp_image_get_sigfm_info (FpImage *self);
|
||||
void fp_image_extract_sigfm_info (FpImage *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "fpi-print.h"
|
||||
#ifdef HAVE_SIGFM
|
||||
#include "sigfm/sigfm.h"
|
||||
#endif
|
||||
#define FP_COMPONENT "print"
|
||||
#include "fpi-log.h"
|
||||
|
||||
|
|
@ -39,18 +43,36 @@
|
|||
* @print: A #FpPrint
|
||||
* @add: Print to append to @print
|
||||
*
|
||||
* Appends the single #FPI_PRINT_NBIS print from @add to the collection of
|
||||
* prints in @print. Both print objects need to be of type #FPI_PRINT_NBIS
|
||||
* for this to work.
|
||||
* Appends the single #FPI_PRINT_NBIS or #FPI_PRINT_SIGFM print from @add
|
||||
* to the collection of prints in @print. Both print objects need to be of
|
||||
* the same type for this to work.
|
||||
*/
|
||||
void
|
||||
fpi_print_add_print (FpPrint *print, FpPrint *add)
|
||||
{
|
||||
g_return_if_fail (print->type == FPI_PRINT_NBIS);
|
||||
g_return_if_fail (add->type == FPI_PRINT_NBIS);
|
||||
g_return_if_fail (print->type == FPI_PRINT_NBIS
|
||||
#ifdef HAVE_SIGFM
|
||||
|| print->type == FPI_PRINT_SIGFM
|
||||
#endif
|
||||
);
|
||||
g_return_if_fail (add->type == FPI_PRINT_NBIS
|
||||
#ifdef HAVE_SIGFM
|
||||
|| add->type == FPI_PRINT_SIGFM
|
||||
#endif
|
||||
);
|
||||
g_return_if_fail (add->type == print->type);
|
||||
g_return_if_fail (add->prints->len > 0);
|
||||
|
||||
g_assert (add->prints->len == 1);
|
||||
g_ptr_array_add (print->prints, g_memdup2 (add->prints->pdata[0], sizeof (struct xyt_struct)));
|
||||
#ifdef HAVE_SIGFM
|
||||
void * to_add =
|
||||
print->type == FPI_PRINT_NBIS ?
|
||||
g_memdup2 (add->prints->pdata[0], sizeof (struct xyt_struct)) :
|
||||
(void *) sigfm_copy_info (add->prints->pdata[0]);
|
||||
#else
|
||||
void * to_add = g_memdup2 (add->prints->pdata[0], sizeof (struct xyt_struct));
|
||||
#endif
|
||||
g_ptr_array_add (print->prints, to_add);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,10 +93,20 @@ fpi_print_set_type (FpPrint *print,
|
|||
g_return_if_fail (print->type == FPI_PRINT_UNDEFINED);
|
||||
|
||||
print->type = type;
|
||||
if (print->type == FPI_PRINT_NBIS)
|
||||
if (print->type == FPI_PRINT_NBIS
|
||||
#ifdef HAVE_SIGFM
|
||||
|| print->type == FPI_PRINT_SIGFM
|
||||
#endif
|
||||
)
|
||||
{
|
||||
g_assert_null (print->prints);
|
||||
#ifdef HAVE_SIGFM
|
||||
print->prints = g_ptr_array_new_with_free_func (
|
||||
print->type == FPI_PRINT_NBIS ? g_free :
|
||||
(void (*)(void *))(sigfm_free_info));
|
||||
#else
|
||||
print->prints = g_ptr_array_new_with_free_func (g_free);
|
||||
#endif
|
||||
}
|
||||
g_object_notify (G_OBJECT (print), "fpi-type");
|
||||
}
|
||||
|
|
@ -144,7 +176,7 @@ minutiae_to_xyt (struct fp_minutiae *minutiae,
|
|||
* @error: Return location for error
|
||||
*
|
||||
* Extracts the minutiae from the given image and adds it to @print of
|
||||
* type #FPI_PRINT_NBIS.
|
||||
* type #FPI_PRINT_NBIS or #FPI_PRINT_SIGFM.
|
||||
*
|
||||
* The @image will be kept so that API users can get retrieve it e.g.
|
||||
* for debugging purposes.
|
||||
|
|
@ -160,7 +192,11 @@ fpi_print_add_from_image (FpPrint *print,
|
|||
struct fp_minutiae _minutiae;
|
||||
struct xyt_struct *xyt;
|
||||
|
||||
if (print->type != FPI_PRINT_NBIS || !image)
|
||||
if ((print->type != FPI_PRINT_NBIS
|
||||
#ifdef HAVE_SIGFM
|
||||
&& print->type != FPI_PRINT_SIGFM
|
||||
#endif
|
||||
) || !image)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
|
|
@ -169,23 +205,31 @@ fpi_print_add_from_image (FpPrint *print,
|
|||
return FALSE;
|
||||
}
|
||||
|
||||
minutiae = fp_image_get_minutiae (image);
|
||||
if (!minutiae || minutiae->len == 0)
|
||||
if (print->type == FPI_PRINT_NBIS)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_INVALID_DATA,
|
||||
"No minutiae found in image or not yet detected!");
|
||||
return FALSE;
|
||||
minutiae = fp_image_get_minutiae (image);
|
||||
if (!minutiae || minutiae->len == 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
|
||||
"No minutiae found in image or not yet detected!");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
_minutiae.num = minutiae->len;
|
||||
_minutiae.list = (struct fp_minutia **) minutiae->pdata;
|
||||
_minutiae.alloc = minutiae->len;
|
||||
|
||||
xyt = g_new0 (struct xyt_struct, 1);
|
||||
minutiae_to_xyt (&_minutiae, image->width, image->height, xyt);
|
||||
g_ptr_array_add (print->prints, xyt);
|
||||
}
|
||||
|
||||
_minutiae.num = minutiae->len;
|
||||
_minutiae.list = (struct fp_minutia **) minutiae->pdata;
|
||||
_minutiae.alloc = minutiae->len;
|
||||
|
||||
xyt = g_new0 (struct xyt_struct, 1);
|
||||
minutiae_to_xyt (&_minutiae, image->width, image->height, xyt);
|
||||
g_ptr_array_add (print->prints, xyt);
|
||||
#ifdef HAVE_SIGFM
|
||||
else if (print->type == FPI_PRINT_SIGFM)
|
||||
{
|
||||
SigfmImgInfo *info = fp_image_get_sigfm_info (image);
|
||||
g_ptr_array_add (print->prints, info);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_clear_object (&print->image);
|
||||
print->image = g_object_ref (image);
|
||||
|
|
@ -198,7 +242,7 @@ fpi_print_add_from_image (FpPrint *print,
|
|||
* fpi_print_bz3_match:
|
||||
* @template: A #FpPrint containing one or more prints
|
||||
* @print: A newly scanned #FpPrint to test
|
||||
* @bz3_threshold: The BZ3 match threshold
|
||||
* @score_threshold: The BZ3 match threshold
|
||||
* @error: Return location for error
|
||||
*
|
||||
* Match the newly scanned @print (containing exactly one print) against the
|
||||
|
|
@ -210,14 +254,14 @@ fpi_print_add_from_image (FpPrint *print,
|
|||
* Returns: Whether the prints match, @error will be set if #FPI_MATCH_ERROR is returned
|
||||
*/
|
||||
FpiMatchResult
|
||||
fpi_print_bz3_match (FpPrint *template, FpPrint *print, gint bz3_threshold, GError **error)
|
||||
fpi_print_bz3_match (FpPrint *template, FpPrint *print, gint score_threshold, GError **error)
|
||||
{
|
||||
struct xyt_struct *pstruct;
|
||||
gint probe_len;
|
||||
gint i;
|
||||
|
||||
/* XXX: Use a different error type? */
|
||||
if (template->type != FPI_PRINT_NBIS || print->type != FPI_PRINT_NBIS)
|
||||
if (template->type != FPI_PRINT_NBIS)
|
||||
{
|
||||
*error = fpi_device_error_new_msg (FP_DEVICE_ERROR_NOT_SUPPORTED,
|
||||
"It is only possible to match NBIS type print data");
|
||||
|
|
@ -240,15 +284,62 @@ fpi_print_bz3_match (FpPrint *template, FpPrint *print, gint bz3_threshold, GErr
|
|||
gint score;
|
||||
gstruct = g_ptr_array_index (template->prints, i);
|
||||
score = bozorth_to_gallery (probe_len, pstruct, gstruct);
|
||||
fp_dbg ("score %d/%d", score, bz3_threshold);
|
||||
fp_dbg ("score %d/%d", score, score_threshold);
|
||||
|
||||
if (score >= bz3_threshold)
|
||||
if (score >= score_threshold)
|
||||
return FPI_MATCH_SUCCESS;
|
||||
}
|
||||
|
||||
return FPI_MATCH_FAIL;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
/**
|
||||
* fpi_print_sigfm_match:
|
||||
* @template: A #FpPrint containing one or more prints
|
||||
* @print: A newly scanned #FpPrint to test
|
||||
* @score_threshold: The BZ3 match threshold
|
||||
* @error: Return location for error
|
||||
*
|
||||
* Match the newly scanned @print (containing exactly one print) against the
|
||||
* prints contained in @template which will have been stored during enrollment.
|
||||
*
|
||||
* Both @template and @print need to be of type #FPI_PRINT_SIGFM for this to
|
||||
* work.
|
||||
*
|
||||
* Returns: Whether the prints match, @error will be set if #FPI_MATCH_ERROR is returned
|
||||
*/
|
||||
FpiMatchResult
|
||||
fpi_print_sigfm_match (FpPrint * template, FpPrint * print,
|
||||
gint score_threshold, GError ** error)
|
||||
{
|
||||
if (template->type != FPI_PRINT_SIGFM)
|
||||
{
|
||||
*error = fpi_device_error_new_msg (
|
||||
FP_DEVICE_ERROR_NOT_SUPPORTED,
|
||||
"Cannot call sigfm match with non-sigfm print data, type was %d",
|
||||
template->type);
|
||||
return FPI_MATCH_ERROR;
|
||||
}
|
||||
SigfmImgInfo * against = g_ptr_array_index (print->prints, 0);
|
||||
for (int i = 0; i != template->prints->len; ++i)
|
||||
{
|
||||
SigfmImgInfo * pinfo = g_ptr_array_index (template->prints, i);
|
||||
int score = sigfm_match_score (pinfo, against);
|
||||
if (score < 0)
|
||||
{
|
||||
*error = fpi_device_error_new_msg (FP_DEVICE_ERROR_DATA_INVALID,
|
||||
"error in sigfm_match_score");
|
||||
return FPI_MATCH_ERROR;
|
||||
}
|
||||
fp_dbg ("sigfm score %d/%d", score, score_threshold);
|
||||
if (score >= score_threshold)
|
||||
return FPI_MATCH_SUCCESS;
|
||||
}
|
||||
return FPI_MATCH_FAIL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* fpi_print_generate_user_id:
|
||||
* @print: #FpPrint to generate the ID for
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "fpi-enums.h"
|
||||
#include "fp-device.h"
|
||||
#include "fp-print.h"
|
||||
#include <config.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
|
|
@ -11,11 +12,13 @@ G_BEGIN_DECLS
|
|||
* @FPI_PRINT_UNDEFINED: Undefined type, this happens prior to enrollment
|
||||
* @FPI_PRINT_RAW: A raw print where the data is directly compared
|
||||
* @FPI_PRINT_NBIS: NBIS minutiae comparison
|
||||
* @FPI_PRINT_SIGFM: SIGFM minutiae comparison
|
||||
*/
|
||||
typedef enum {
|
||||
FPI_PRINT_UNDEFINED = 0,
|
||||
FPI_PRINT_RAW,
|
||||
FPI_PRINT_NBIS,
|
||||
FPI_PRINT_SIGFM,
|
||||
} FpiPrintType;
|
||||
|
||||
/**
|
||||
|
|
@ -44,11 +47,16 @@ gboolean fpi_print_add_from_image (FpPrint *print,
|
|||
|
||||
FpiMatchResult fpi_print_bz3_match (FpPrint *temp,
|
||||
FpPrint *print,
|
||||
gint bz3_threshold,
|
||||
gint score_threshold,
|
||||
GError **error);
|
||||
|
||||
#ifdef HAVE_SIGFM
|
||||
FpiMatchResult fpi_print_sigfm_match (FpPrint * template, FpPrint * print,
|
||||
gint score_threshold, GError * *error);
|
||||
#endif
|
||||
|
||||
/* Helpers to encode metadata into user ID strings. */
|
||||
gchar * fpi_print_generate_user_id (FpPrint *print);
|
||||
gchar * fpi_print_generate_user_id (FpPrint * print);
|
||||
gboolean fpi_print_fill_from_user_id (FpPrint *print,
|
||||
const char *user_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ driver_sources = {
|
|||
[ 'drivers/goodixmoc/goodix.c', 'drivers/goodixmoc/goodix_proto.c' ],
|
||||
'fpcmoc' :
|
||||
[ 'drivers/fpcmoc/fpc.c' ],
|
||||
'fpcmoh' :
|
||||
[ 'drivers/fpcmoh/fpcmoh.c' ],
|
||||
'realtek' :
|
||||
[ 'drivers/realtek/realtek.c' ],
|
||||
'focaltech_moc' :
|
||||
|
|
@ -236,6 +238,13 @@ deps = [
|
|||
mathlib_dep,
|
||||
] + optional_deps
|
||||
|
||||
subdir('sigfm')
|
||||
|
||||
sigfm_link = []
|
||||
if have_sigfm
|
||||
sigfm_link = [libsigfm]
|
||||
endif
|
||||
|
||||
# These are empty and only exist so that the include directories are created
|
||||
# in the build tree. This silences a build time warning.
|
||||
subdir('nbis/include')
|
||||
|
|
@ -265,7 +274,7 @@ libfprint_private = static_library('fprint-private',
|
|||
libfprint_private_sources,
|
||||
],
|
||||
dependencies: deps,
|
||||
link_with: libnbis,
|
||||
link_with: [libnbis] + sigfm_link,
|
||||
install: false)
|
||||
|
||||
libfprint_drivers = static_library('fprint-drivers',
|
||||
|
|
@ -309,6 +318,11 @@ libfprint_dep = declare_dependency(link_with: libfprint,
|
|||
install_headers(['fprint.h'] + libfprint_public_headers,
|
||||
subdir: versioned_libname
|
||||
)
|
||||
if have_sigfm
|
||||
install_headers(['sigfm/sigfm.h'],
|
||||
subdir: versioned_libname + '/sigfm'
|
||||
)
|
||||
endif
|
||||
|
||||
libfprint_private_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
|
|
|
|||
229
libfprint/sigfm/binary.hpp
Normal file
229
libfprint/sigfm/binary.hpp
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
// SIGFM algorithm for libfprint
|
||||
|
||||
// Copyright (C) 2022 Matthieu CHARETTE <matthieu.charette@gmail.com>
|
||||
// Copyright (c) 2022 Natasha England-Elbro <natasha@natashaee.me>
|
||||
// Copyright (c) 2022 Timur Mangliev <tigrmango@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "opencv2/core/mat.hpp"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace bin {
|
||||
using byte = unsigned char;
|
||||
|
||||
class stream;
|
||||
|
||||
template<typename T, typename EnableIf = void>
|
||||
struct serializer : public std::false_type {
|
||||
void serialize(const T& m, stream& out);
|
||||
};
|
||||
|
||||
template<typename T, typename EnableIf = void>
|
||||
struct deserializer : public std::false_type {
|
||||
T deserialize(stream& in);
|
||||
};
|
||||
class stream {
|
||||
public:
|
||||
stream() = default;
|
||||
|
||||
template<
|
||||
typename Iter,
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<
|
||||
std::decay_t<Iter>>::value_type,
|
||||
byte>,
|
||||
bool> = true>
|
||||
stream(Iter begin, Iter end) : store_{begin, end}
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T, std::enable_if_t<serializer<T>::value, bool> = true>
|
||||
constexpr stream& operator<<(T v)
|
||||
{
|
||||
serializer<T>::serialize(v, *this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, std::enable_if_t<deserializer<T>::value, bool> = true>
|
||||
constexpr stream& operator>>(T& v)
|
||||
{
|
||||
v = deserializer<T>::deserialize(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<
|
||||
typename Iter,
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<
|
||||
std::decay_t<Iter>>::value_type,
|
||||
byte>,
|
||||
bool> = true>
|
||||
constexpr stream& write(Iter&& begin, Iter&& end)
|
||||
{
|
||||
std::copy(std::forward<Iter>(begin), std::forward<Iter>(end),
|
||||
std::back_inserter(store_));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, std::enable_if_t<serializer<T>::value, bool> = true>
|
||||
stream& serialize(const T& m, stream& out)
|
||||
{
|
||||
serializer<T>::serialize(m, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
template<
|
||||
typename Iter,
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<
|
||||
std::decay_t<Iter>>::value_type,
|
||||
byte>,
|
||||
bool> = true>
|
||||
constexpr stream& read(Iter&& begin, Iter&& end)
|
||||
{
|
||||
const auto dist = std::distance(begin, end);
|
||||
return stream::read(begin, dist);
|
||||
}
|
||||
|
||||
template<
|
||||
typename Iter,
|
||||
std::enable_if_t<std::is_same_v<typename std::iterator_traits<
|
||||
std::decay_t<Iter>>::value_type,
|
||||
byte>,
|
||||
bool> = true>
|
||||
constexpr stream& read(Iter&& begin, std::size_t dist)
|
||||
{
|
||||
if (dist > store_.size()) {
|
||||
throw std::runtime_error{"trying to read too much from a stream. wanted: " + std::to_string(dist) + " available: " + std::to_string(store_.size())};
|
||||
}
|
||||
std::copy(store_.begin(), store_.begin() + dist, begin);
|
||||
store_.erase(store_.begin(), store_.begin() + dist);
|
||||
return *this;
|
||||
}
|
||||
byte* copy_buffer() const
|
||||
{
|
||||
byte* raw = static_cast<byte*>(malloc(store_.size()));
|
||||
std::copy(store_.begin(), store_.end(), raw);
|
||||
return raw;
|
||||
}
|
||||
std::size_t size() const { return store_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<byte> store_;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct serializer<T, std::enable_if_t<std::is_trivial_v<T>>> : public std::true_type {
|
||||
static void serialize(T v, stream& out) {
|
||||
using seg_store = std::array<byte, sizeof(T)>;
|
||||
alignas(T) seg_store s = {};
|
||||
std::memcpy(s.data(), &v, sizeof(T));
|
||||
out.write(s.begin(), s.end());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct deserializer<T, std::enable_if_t<std::is_trivial_v<T>>> : public std::true_type {
|
||||
static T deserialize(stream& in) {
|
||||
alignas(T) std::array<byte, sizeof(T)> s = {};
|
||||
in.read(s.begin(), s.size());
|
||||
T v;
|
||||
std::memcpy(&v, s.data(), s.size());
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<>
|
||||
struct serializer<cv::Mat> : public std::true_type {
|
||||
static void serialize(const cv::Mat& m, stream& out)
|
||||
{
|
||||
out << m.type() << m.rows << m.cols;
|
||||
out.write(m.datastart, m.dataend);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct deserializer<cv::Mat> : public std::true_type {
|
||||
static cv::Mat deserialize(stream& in)
|
||||
{
|
||||
int rows, cols, type;
|
||||
in >> type >> rows >> cols;
|
||||
cv::Mat m;
|
||||
m.create(rows, cols, type);
|
||||
in.read(m.data, std::distance(m.datastart, m.dataend));
|
||||
return m;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct deserializer<cv::Point_<T>> : public std::true_type {
|
||||
static cv::Point2f deserialize(stream& in)
|
||||
{
|
||||
cv::Point_<T> p;
|
||||
in >> p.x >> p.y;
|
||||
return p;
|
||||
}
|
||||
};
|
||||
template<typename T>
|
||||
struct serializer<cv::Point_<T>> : public std::true_type {
|
||||
static void serialize(const cv::Point_<T>& pt, stream& out)
|
||||
{
|
||||
out << pt.x << pt.y;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct serializer<cv::KeyPoint> : public std::true_type {
|
||||
static void serialize(const cv::KeyPoint& pt, stream& out)
|
||||
{
|
||||
out << pt.class_id << pt.angle << pt.octave << pt.response << pt.size
|
||||
<< pt.pt;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct deserializer<cv::KeyPoint> : public std::true_type {
|
||||
static cv::KeyPoint deserialize(stream& in)
|
||||
{
|
||||
cv::KeyPoint pt;
|
||||
in >> pt.class_id >> pt.angle >> pt.octave >> pt.response >> pt.size >>
|
||||
pt.pt;
|
||||
return pt;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct serializer<std::vector<T>, std::enable_if_t<serializer<T>::value>> : public std::true_type {
|
||||
static void serialize(const std::vector<T>& vs, stream& out)
|
||||
{
|
||||
out << static_cast<std::size_t>(vs.size());
|
||||
std::for_each(vs.begin(), vs.end(),
|
||||
[&out](const auto& el) { out << el; });
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct deserializer<std::vector<T>, std::enable_if_t<deserializer<T>::value>> : public std::true_type {
|
||||
static std::vector<T> deserialize(stream& in)
|
||||
{
|
||||
std::size_t size;
|
||||
in >> size;
|
||||
std::vector<T> vs;
|
||||
vs.reserve(size);
|
||||
for (std::size_t n = 0; n != size; ++n) {
|
||||
T v;
|
||||
in >> v;
|
||||
vs.emplace_back(std::move(v));
|
||||
}
|
||||
return vs;
|
||||
}
|
||||
};
|
||||
} // namespace bin
|
||||
18
libfprint/sigfm/img-info.hpp
Normal file
18
libfprint/sigfm/img-info.hpp
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// SIGFM algorithm for libfprint
|
||||
|
||||
// Copyright (C) 2022 Matthieu CHARETTE <matthieu.charette@gmail.com>
|
||||
// Copyright (c) 2022 Natasha England-Elbro <natasha@natashaee.me>
|
||||
// Copyright (c) 2022 Timur Mangliev <tigrmango@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <opencv2/core.hpp>
|
||||
#include <vector>
|
||||
|
||||
struct SigfmImgInfo {
|
||||
std::vector<cv::KeyPoint> keypoints;
|
||||
cv::Mat descriptors;
|
||||
};
|
||||
20
libfprint/sigfm/meson.build
Normal file
20
libfprint/sigfm/meson.build
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
sigfm_sources = ['sigfm.cpp']
|
||||
|
||||
opencv = dependency('opencv4', required: false)
|
||||
have_sigfm = opencv.found()
|
||||
|
||||
if have_sigfm
|
||||
libsigfm = static_library('sigfm',
|
||||
sigfm_sources,
|
||||
dependencies: [opencv],
|
||||
cpp_args: cpp.get_supported_arguments([
|
||||
'-Wno-suggest-attribute=format',
|
||||
]),
|
||||
)
|
||||
|
||||
doctest = dependency('doctest', required: false)
|
||||
if doctest.found()
|
||||
sigfm_tests = executable('sigfm-tests', ['./tests.cpp'], dependencies: [doctest, opencv], link_with: [libsigfm])
|
||||
endif
|
||||
endif
|
||||
208
libfprint/sigfm/sigfm.cpp
Normal file
208
libfprint/sigfm/sigfm.cpp
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// SIGFM algorithm for libfprint
|
||||
|
||||
// Copyright (C) 2022 Matthieu CHARETTE <matthieu.charette@gmail.com>
|
||||
// Copyright (c) 2022 Natasha England-Elbro <natasha@natashaee.me>
|
||||
// Copyright (c) 2022 Timur Mangliev <tigrmango@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
//
|
||||
|
||||
#include "sigfm.h"
|
||||
#include "binary.hpp"
|
||||
#include "img-info.hpp"
|
||||
|
||||
#include "opencv2/core/persistence.hpp"
|
||||
#include "opencv2/core/types.hpp"
|
||||
#include "opencv2/features2d.hpp"
|
||||
#include "opencv2/imgcodecs.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace bin {
|
||||
|
||||
template<>
|
||||
struct serializer<SigfmImgInfo> : public std::true_type {
|
||||
static void serialize(const SigfmImgInfo& info, stream& out)
|
||||
{
|
||||
out << info.keypoints << info.descriptors;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct deserializer<SigfmImgInfo> : public std::true_type {
|
||||
static SigfmImgInfo deserialize(stream& in)
|
||||
{
|
||||
SigfmImgInfo info;
|
||||
in >> info.keypoints >> info.descriptors;
|
||||
return info;
|
||||
}
|
||||
};
|
||||
} // namespace bin
|
||||
|
||||
namespace {
|
||||
constexpr auto distance_match = 0.75;
|
||||
constexpr auto length_match = 0.05;
|
||||
constexpr auto angle_match = 0.05;
|
||||
constexpr auto min_match = 5;
|
||||
struct match {
|
||||
cv::Point2i p1;
|
||||
cv::Point2i p2;
|
||||
match(cv::Point2i ip1, cv::Point2i ip2) : p1{ip1}, p2{ip2} {}
|
||||
match() : p1{cv::Point2i(0, 0)}, p2{cv::Point2i(0, 0)} {}
|
||||
bool operator==(const match& right) const
|
||||
{
|
||||
return std::tie(this->p1, this->p2) == std::tie(right.p1, right.p2);
|
||||
}
|
||||
bool operator<(const match& right) const
|
||||
{
|
||||
return (this->p1.y < right.p1.y) ||
|
||||
((this->p1.y < right.p1.y) && this->p1.x < right.p1.x);
|
||||
}
|
||||
};
|
||||
struct angle {
|
||||
double cos;
|
||||
double sin;
|
||||
match corr_matches[2];
|
||||
angle(double cos_, double sin_, match m1, match m2)
|
||||
: cos{cos_}, sin{sin_}, corr_matches{m1, m2}
|
||||
{
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
SigfmImgInfo* sigfm_copy_info(SigfmImgInfo* info) { return new SigfmImgInfo{*info}; }
|
||||
|
||||
int sigfm_keypoints_count(SigfmImgInfo* info) { return info->keypoints.size(); }
|
||||
unsigned char* sigfm_serialize_binary(SigfmImgInfo* info, int* outlen)
|
||||
{
|
||||
bin::stream s;
|
||||
s << *info;
|
||||
*outlen = s.size();
|
||||
return s.copy_buffer();
|
||||
}
|
||||
|
||||
SigfmImgInfo* sigfm_deserialize_binary(const unsigned char* bytes, int len)
|
||||
{
|
||||
try {
|
||||
bin::stream s{bytes, bytes + len};
|
||||
auto info = std::make_unique<SigfmImgInfo>();
|
||||
s >> *info;
|
||||
return info.release();
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
SigfmImgInfo* sigfm_extract(const SigfmPix* pix, int width, int height)
|
||||
{
|
||||
try {
|
||||
cv::Mat img;
|
||||
img.create(height, width, CV_8UC1);
|
||||
std::memcpy(img.data, pix, width * height);
|
||||
const auto roi = cv::Mat::ones(cv::Size{img.size[1], img.size[0]}, CV_8UC1);
|
||||
std::vector<cv::KeyPoint> pts;
|
||||
|
||||
cv::Mat descs;
|
||||
cv::SIFT::create()->detectAndCompute(img, roi, pts, descs);
|
||||
|
||||
auto* info = new SigfmImgInfo{pts, descs};
|
||||
return info;
|
||||
} catch(...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int sigfm_match_score(SigfmImgInfo* frame, SigfmImgInfo* enrolled)
|
||||
{
|
||||
try {
|
||||
std::vector<std::vector<cv::DMatch>> points;
|
||||
auto bfm = cv::BFMatcher::create();
|
||||
bfm->knnMatch(frame->descriptors, enrolled->descriptors, points, 2);
|
||||
std::set<match> matches_unique;
|
||||
int nb_matched = 0;
|
||||
for (const auto& pts : points) {
|
||||
if (pts.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
const cv::DMatch& match_1 = pts.at(0);
|
||||
if (match_1.distance < distance_match * pts.at(1).distance) {
|
||||
matches_unique.emplace(
|
||||
match{frame->keypoints.at(match_1.queryIdx).pt,
|
||||
enrolled->keypoints.at(match_1.trainIdx).pt});
|
||||
nb_matched++;
|
||||
}
|
||||
}
|
||||
if (nb_matched < min_match) {
|
||||
return 0;
|
||||
}
|
||||
std::vector<match> matches{matches_unique.begin(),
|
||||
matches_unique.end()};
|
||||
|
||||
std::vector<angle> angles;
|
||||
for (std::size_t j = 0; j < matches.size(); j++) {
|
||||
match match_1 = matches[j];
|
||||
for (std::size_t k = j + 1; k < matches.size(); k++) {
|
||||
match match_2 = matches[k];
|
||||
|
||||
int vec_1[2] = {match_1.p1.x - match_2.p1.x,
|
||||
match_1.p1.y - match_2.p1.y};
|
||||
int vec_2[2] = {match_1.p2.x - match_2.p2.x,
|
||||
match_1.p2.y - match_2.p2.y};
|
||||
|
||||
double length_1 = sqrt(pow(vec_1[0], 2) + pow(vec_1[1], 2));
|
||||
double length_2 = sqrt(pow(vec_2[0], 2) + pow(vec_2[1], 2));
|
||||
|
||||
if (1 - std::min(length_1, length_2) /
|
||||
std::max(length_1, length_2) <=
|
||||
length_match) {
|
||||
|
||||
double product = length_1 * length_2;
|
||||
angles.emplace_back(angle(
|
||||
M_PI / 2 +
|
||||
asin((vec_1[0] * vec_2[0] + vec_1[1] * vec_2[1]) /
|
||||
product),
|
||||
acos((vec_1[0] * vec_2[1] - vec_1[1] * vec_2[0]) /
|
||||
product),
|
||||
match_1, match_2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (angles.size() < min_match) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (std::size_t j = 0; j < angles.size(); j++) {
|
||||
angle angle_1 = angles[j];
|
||||
for (std::size_t k = j + 1; k < angles.size(); k++) {
|
||||
angle angle_2 = angles[k];
|
||||
|
||||
if (1 - std::min(angle_1.sin, angle_2.sin) /
|
||||
std::max(angle_1.sin, angle_2.sin) <=
|
||||
angle_match &&
|
||||
1 - std::min(angle_1.cos, angle_2.cos) /
|
||||
std::max(angle_1.cos, angle_2.cos) <=
|
||||
angle_match) {
|
||||
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void sigfm_free_info(SigfmImgInfo* info) { delete info; }
|
||||
91
libfprint/sigfm/sigfm.h
Normal file
91
libfprint/sigfm/sigfm.h
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// SIGFM algorithm for libfprint
|
||||
|
||||
// Copyright (C) 2022 Matthieu CHARETTE <matthieu.charette@gmail.com>
|
||||
// Copyright (c) 2022 Natasha England-Elbro <natasha@natashaee.me>
|
||||
// Copyright (c) 2022 Timur Mangliev <tigrmango@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
typedef unsigned char SigfmPix;
|
||||
/**
|
||||
* @brief Contains information used by the sigfm algorithm for matching
|
||||
* @details Get one from sigfm_extract() and make sure to clean it up with sigfm_free_info()
|
||||
* @struct SigfmImgInfo
|
||||
*/
|
||||
typedef struct SigfmImgInfo SigfmImgInfo;
|
||||
|
||||
/**
|
||||
* @brief Extracts information from an image for later use sigfm_match_score
|
||||
*
|
||||
* @param pix Pixels of the image must be width * height in length
|
||||
* @param width Width of the image
|
||||
* @param height Height of the image
|
||||
* @return SigfmImgInfo* Info that can be used with the API
|
||||
*/
|
||||
SigfmImgInfo * sigfm_extract (const SigfmPix * pix,
|
||||
int width,
|
||||
int height);
|
||||
|
||||
/**
|
||||
* @brief Destroy an SigfmImgInfo
|
||||
* @warning Call this instead of free() or you will get UB!
|
||||
* @param info SigfmImgInfo to destroy
|
||||
*/
|
||||
void sigfm_free_info (SigfmImgInfo * info);
|
||||
|
||||
/**
|
||||
* @brief Score how closely a frame matches another
|
||||
*
|
||||
* @param frame Print to be checked
|
||||
* @param enrolled Canonical print to verify against
|
||||
* @return int Score of how closely they match, values <0 indicate error, 0 means always reject
|
||||
*/
|
||||
int sigfm_match_score (SigfmImgInfo * frame,
|
||||
SigfmImgInfo * enrolled);
|
||||
|
||||
/**
|
||||
* @brief Serialize an image info for storage
|
||||
*
|
||||
* @param info SigfmImgInfo to store
|
||||
* @param outlen output: Length of the returned byte array
|
||||
* @return unsigned* char byte array for storage, should be free'd by the callee
|
||||
*/
|
||||
unsigned char * sigfm_serialize_binary (SigfmImgInfo * info,
|
||||
int * outlen);
|
||||
/**
|
||||
* @brief Deserialize an SigfmImgInfo from storage
|
||||
*
|
||||
* @param bytes Byte array to deserialize from
|
||||
* @param len Length of the byte array
|
||||
* @return SigfmImgInfo* Deserialized info, or NULL if deserialization failed
|
||||
*/
|
||||
SigfmImgInfo * sigfm_deserialize_binary (const unsigned char * bytes,
|
||||
int len);
|
||||
|
||||
/**
|
||||
* @brief Keypoints for an image. Low keypoints generally means the image is
|
||||
* low quality for matching
|
||||
*
|
||||
* @param info
|
||||
* @return int
|
||||
*/
|
||||
|
||||
int sigfm_keypoints_count (SigfmImgInfo * info);
|
||||
|
||||
/**
|
||||
* @brief Copy an SigfmImgInfo
|
||||
*
|
||||
* @param info Source of copy
|
||||
* @return SigfmImgInfo* Newly allocated and copied version of info
|
||||
*/
|
||||
SigfmImgInfo * sigfm_copy_info (SigfmImgInfo * info);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5479
libfprint/sigfm/tests-embedded.hpp
Normal file
5479
libfprint/sigfm/tests-embedded.hpp
Normal file
File diff suppressed because it is too large
Load diff
160
libfprint/sigfm/tests.cpp
Normal file
160
libfprint/sigfm/tests.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// SIGFM algorithm for libfprint
|
||||
|
||||
// Copyright (C) 2022 Matthieu CHARETTE <matthieu.charette@gmail.com>
|
||||
// Copyright (c) 2022 Natasha England-Elbro <natasha@natashaee.me>
|
||||
// Copyright (c) 2022 Timur Mangliev <tigrmango@gmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
//
|
||||
#include "opencv2/core.hpp"
|
||||
#include "opencv2/core/types.hpp"
|
||||
#include "sigfm.h"
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "binary.hpp"
|
||||
#include "tests-embedded.hpp"
|
||||
|
||||
#include "img-info.hpp"
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#include<vector>
|
||||
|
||||
namespace cv {
|
||||
bool operator==(const cv::KeyPoint& lhs, const cv::KeyPoint& rhs)
|
||||
{
|
||||
return lhs.angle == rhs.angle && lhs.class_id == rhs.class_id &&
|
||||
lhs.octave == rhs.octave && lhs.size == rhs.size &&
|
||||
lhs.response == rhs.response && lhs.pt == rhs.pt;
|
||||
}
|
||||
|
||||
} // namespace cv
|
||||
|
||||
namespace {
|
||||
bool comp_mats(const cv::Mat& lhs, const cv::Mat& rhs)
|
||||
{
|
||||
return std::equal(lhs.datastart, lhs.dataend, rhs.datastart, rhs.dataend);
|
||||
}
|
||||
|
||||
std::string to_str(const cv::KeyPoint& k)
|
||||
{
|
||||
std::stringstream s;
|
||||
s << "angle: " << k.angle << ", class_id: " << k.class_id
|
||||
<< ", octave: " << k.octave << ", size: " << k.size
|
||||
<< ", reponse: " << k.response << ", ptx: " << k.pt.x
|
||||
<< ", pty: " << k.pt.y;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<typename T>
|
||||
void check_vec(const std::vector<T>& vs)
|
||||
{
|
||||
for (auto i : vs) {
|
||||
bin::stream s;
|
||||
s << i;
|
||||
T iv;
|
||||
s >> iv;
|
||||
CHECK(i == iv);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("binary")
|
||||
{
|
||||
|
||||
TEST_CASE("float can be stored and restored")
|
||||
{
|
||||
check_vec<float>({3, 2.4, 6.7});
|
||||
}
|
||||
|
||||
TEST_CASE("size_t can be stored and restored")
|
||||
{
|
||||
check_vec<std::size_t>({2, 5, 803, 900});
|
||||
}
|
||||
TEST_CASE("number can be stored and restored")
|
||||
{
|
||||
check_vec<int>({5, 3, 10, 16, 24, 900});
|
||||
}
|
||||
TEST_CASE("image can be stored and restored")
|
||||
{
|
||||
cv::Mat input;
|
||||
input.create(256, 256, CV_8UC1);
|
||||
std::memcpy(input.data, embedded::capture_aes3500, 256 * 256);
|
||||
bin::stream s;
|
||||
s << input;
|
||||
|
||||
cv::Mat output;
|
||||
s >> output;
|
||||
CHECK(std::equal(input.datastart, input.dataend, output.datastart,
|
||||
output.dataend));
|
||||
}
|
||||
TEST_CASE("taking more than giving to a stream will cause an exception") {
|
||||
bin::stream s;
|
||||
s << 5;
|
||||
int v1;
|
||||
s >> v1;
|
||||
CHECK_THROWS(s >> v1);
|
||||
}
|
||||
|
||||
TEST_CASE("vector of values can be stored and restored")
|
||||
{
|
||||
std::vector inputs = {3, 5, 1, 7};
|
||||
bin::stream s;
|
||||
s << inputs;
|
||||
|
||||
std::vector<int> outputs;
|
||||
s >> outputs;
|
||||
CHECK(outputs == inputs);
|
||||
}
|
||||
|
||||
TEST_CASE("keypoints can be stored and restored")
|
||||
{
|
||||
cv::KeyPoint pt;
|
||||
pt.angle = 20;
|
||||
pt.octave = 3;
|
||||
pt.response = 3;
|
||||
pt.size = 40;
|
||||
pt.pt = cv::Point2f{3, 1};
|
||||
|
||||
bin::stream s;
|
||||
s << pt;
|
||||
|
||||
cv::KeyPoint ptout;
|
||||
s >> ptout;
|
||||
CHECK(to_str(pt) == to_str(ptout));
|
||||
}
|
||||
TEST_CASE("sigfm img info can be stored and restored")
|
||||
{
|
||||
constexpr auto img_w = 256;
|
||||
constexpr auto img_h = 256;
|
||||
constexpr auto img = embedded::capture_aes3500;
|
||||
SigfmImgInfo* info = sigfm_extract(img, img_w, img_h);
|
||||
REQUIRE(info != nullptr);
|
||||
const auto inf1desc = info->descriptors;
|
||||
cv::Mat descout;
|
||||
bin::stream s;
|
||||
s << inf1desc;
|
||||
s >> descout;
|
||||
CHECK(comp_mats(inf1desc, descout));
|
||||
|
||||
int slen;
|
||||
const auto bin_data = sigfm_serialize_binary(info, &slen);
|
||||
int slen2;
|
||||
SigfmImgInfo* info2 = sigfm_deserialize_binary(bin_data, slen);
|
||||
REQUIRE(info2);
|
||||
const auto bin_data2 = sigfm_serialize_binary(info2, &slen2);
|
||||
CHECK(slen == slen2);
|
||||
CHECK(std::equal(bin_data, bin_data + slen, bin_data2,
|
||||
bin_data2 + slen2));
|
||||
|
||||
REQUIRE(info->keypoints == info2->keypoints);
|
||||
REQUIRE(std::equal(
|
||||
info->descriptors.datastart, info->descriptors.dataend,
|
||||
info2->descriptors.datastart, info2->descriptors.dataend));
|
||||
sigfm_free_info(info);
|
||||
sigfm_free_info(info2);
|
||||
free(bin_data);
|
||||
free(bin_data2);
|
||||
}
|
||||
}
|
||||
23
meson.build
23
meson.build
|
|
@ -154,6 +154,17 @@ if have_spi
|
|||
default_drivers += spi_drivers
|
||||
endif
|
||||
|
||||
# SIGFM-based drivers require OpenCV4 for SIFT keypoint matching
|
||||
sigfm_drivers = [
|
||||
'fpcmoh',
|
||||
]
|
||||
|
||||
have_opencv = dependency('opencv4', required: false).found()
|
||||
if have_opencv
|
||||
default_drivers += sigfm_drivers
|
||||
libfprint_conf.set10('HAVE_SIGFM', true)
|
||||
endif
|
||||
|
||||
# FIXME: All the drivers should be fixed by adjusting the byte order.
|
||||
# See https://gitlab.freedesktop.org/libfprint/libfprint/-/issues/236
|
||||
endian_independent_drivers = virtual_drivers + [
|
||||
|
|
@ -201,6 +212,17 @@ if enabled_spi_drivers.length() > 0 and not have_spi
|
|||
error('SPI drivers @0@ are not supported'.format(enabled_spi_drivers))
|
||||
endif
|
||||
|
||||
enabled_sigfm_drivers = []
|
||||
foreach driver : sigfm_drivers
|
||||
if driver in drivers
|
||||
enabled_sigfm_drivers += driver
|
||||
endif
|
||||
endforeach
|
||||
|
||||
if enabled_sigfm_drivers.length() > 0 and not have_opencv
|
||||
error('SIGFM drivers @0@ require opencv4'.format(enabled_sigfm_drivers))
|
||||
endif
|
||||
|
||||
driver_helper_mapping = {
|
||||
'aes1610' : [ 'aeslib' ],
|
||||
'aes1660' : [ 'aeslib', 'aesx660' ],
|
||||
|
|
@ -210,6 +232,7 @@ driver_helper_mapping = {
|
|||
'aes3500' : [ 'aeslib', 'aes3k' ],
|
||||
'aes4000' : [ 'aeslib', 'aes3k' ],
|
||||
'uru4000' : [ 'openssl' ],
|
||||
'fpcmoh' : [ 'openssl' ],
|
||||
'elanspi' : [ 'udev' ],
|
||||
'virtual_image' : [ 'virtual' ],
|
||||
'virtual_device' : [ 'virtual' ],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue