mirror of
https://gitlab.freedesktop.org/libfprint/libfprint.git
synced 2026-05-05 03:08:14 +02:00
Merge branch 'sigfm' into 'master'
add sigfm algorithm implementation (rebased and enabled Elan sensors) See merge request libfprint/libfprint!530
This commit is contained in:
commit
920ab58e97
38 changed files with 6606 additions and 123 deletions
|
|
@ -1,2 +1,2 @@
|
|||
variables:
|
||||
LIBFPRINT_IMAGE_TAG: v6
|
||||
LIBFPRINT_IMAGE_TAG: v7
|
||||
|
|
|
|||
|
|
@ -44,4 +44,6 @@
|
|||
libgusb \
|
||||
libusb \
|
||||
openssl \
|
||||
pixman
|
||||
pixman \
|
||||
opencv-devel \
|
||||
doctest-devel
|
||||
|
|
|
|||
|
|
@ -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 :))
|
||||
|
|
|
|||
|
|
@ -19,23 +19,6 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* The algorithm which libfprint uses to match fingerprints doesn't like small
|
||||
* images like the ones these drivers produce. There's just not enough minutiae
|
||||
* (recognizable print-specific points) on them for a reliable match. This means
|
||||
* that unless another matching algo is found/implemented, these readers will
|
||||
* not work as good with libfprint as they do with vendor drivers.
|
||||
*
|
||||
* To get bigger images the driver expects you to swipe the finger over the
|
||||
* reader. This works quite well for readers with a rectangular 144x64 sensor.
|
||||
* Worse than real swipe readers but good enough for day-to-day use. It needs
|
||||
* a steady and relatively slow swipe. There are also square 96x96 sensors and
|
||||
* I don't know whether they are in fact usable or not because I don't have one.
|
||||
* I imagine they'd be less reliable because the resulting image is even
|
||||
* smaller. If they can't be made usable with libfprint, I might end up dropping
|
||||
* them because it's better than saying they work when they don't.
|
||||
*/
|
||||
|
||||
#define FP_COMPONENT "elan"
|
||||
|
||||
#include "drivers_api.h"
|
||||
|
|
@ -998,7 +981,8 @@ fpi_device_elan_class_init (FpiDeviceElanClass *klass)
|
|||
dev_class->full_name = "ElanTech Fingerprint Sensor";
|
||||
dev_class->type = FP_DEVICE_TYPE_USB;
|
||||
dev_class->id_table = elan_id_table;
|
||||
dev_class->scan_type = FP_SCAN_TYPE_SWIPE;
|
||||
dev_class->scan_type = FP_SCAN_TYPE_PRESS;
|
||||
dev_class->nr_enroll_stages = 15;
|
||||
|
||||
img_class->img_open = dev_init;
|
||||
img_class->img_close = dev_deinit;
|
||||
|
|
@ -1006,5 +990,6 @@ 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->algorithm = FPI_DEVICE_ALGO_SIGFM;
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "sigfm/sigfm.h"
|
||||
#define FP_COMPONENT "image"
|
||||
|
||||
#include "fpi-compat.h"
|
||||
|
|
@ -165,10 +166,19 @@ typedef struct
|
|||
FpiImageFlags flags;
|
||||
unsigned char *image;
|
||||
gboolean image_changed;
|
||||
} DetectMinutiaeNbisData;
|
||||
} DetectMinutiaeData;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SigfmImgInfo * sigfm_info;
|
||||
guchar * image;
|
||||
gint width;
|
||||
gint height;
|
||||
GAsyncReadyCallback user_cb;
|
||||
} ExtractSigfmData;
|
||||
|
||||
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 +189,43 @@ 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)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -271,13 +309,45 @@ invert_colors (guint8 *data, gint width, gint height)
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 +370,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 +518,52 @@ fp_image_get_minutiae (FpImage *self)
|
|||
return self->minutiae;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: (transfer none) (element-type SigfmImgInfo): The detected minutiae
|
||||
*/
|
||||
SigfmImgInfo *
|
||||
fp_image_get_sigfm_info (FpImage * self)
|
||||
{
|
||||
return self->sigfm_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* fp_image_extract_sigfm_info:
|
||||
* @self: A #FpImage
|
||||
* @cancellable: a #GCancellable, or %NULL
|
||||
* @callback: the function to call on completion
|
||||
* @user_data: the data to pass to @callback
|
||||
*
|
||||
* Extracts keypoints and descriptors found in an image.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* fp_image_detect_minutiae:
|
||||
* @self: A #FpImage
|
||||
|
|
@ -481,7 +597,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 +620,10 @@ 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);
|
||||
|
||||
if (g_task_get_source_tag (G_TASK (result)) == fp_image_extract_sigfm_info)
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
|
||||
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
|
||||
fp_image_detect_minutiae, FALSE);
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "sigfm/sigfm.h"
|
||||
#include <gio/gio.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
|
@ -43,6 +44,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);
|
||||
|
|
@ -56,4 +58,9 @@ void fp_minutia_get_coords (FpMinutia *min,
|
|||
gint *x,
|
||||
gint *y);
|
||||
|
||||
SigfmImgInfo * fp_image_get_sigfm_info (FpImage * self);
|
||||
void fp_image_extract_sigfm_info (FpImage * self,
|
||||
GCancellable * cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
G_END_DECLS
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "sigfm/sigfm.h"
|
||||
#define FP_COMPONENT "print"
|
||||
|
||||
#include "fp-print-private.h"
|
||||
|
|
@ -680,6 +681,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 +717,28 @@ fp_print_serialize (FpPrint *print,
|
|||
g_variant_builder_close (&nested);
|
||||
g_variant_builder_add (&builder, "v", g_variant_builder_end (&nested));
|
||||
}
|
||||
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));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_variant_builder_add (&builder, "v", g_variant_new_variant (print->data));
|
||||
|
|
@ -741,6 +766,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 +897,35 @@ fp_print_deserialize (const guchar *data,
|
|||
g_ptr_array_add (result->prints, g_steal_pointer (&xyt));
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
else if (type == FPI_PRINT_RAW)
|
||||
{
|
||||
g_autoptr(GVariant) fp_data = g_variant_get_child_value (print_data, 0);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
@ -276,7 +277,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);
|
||||
|
|
@ -323,9 +324,18 @@ fpi_image_device_minutiae_detected (GObject *source_object, GAsyncResult *res, g
|
|||
|
||||
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);
|
||||
else if (priv->algorithm == FPI_PRINT_SIGFM)
|
||||
result = fpi_print_sigfm_match (template, print, priv->score_threshold,
|
||||
&error);
|
||||
}
|
||||
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 +353,15 @@ 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);
|
||||
else if (priv->algorithm == FPI_PRINT_SIGFM)
|
||||
match_result = fpi_print_sigfm_match (template, print,
|
||||
priv->score_threshold, &error);
|
||||
|
||||
if (match_result == FPI_MATCH_SUCCESS)
|
||||
{
|
||||
result = template;
|
||||
break;
|
||||
|
|
@ -371,9 +389,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 +399,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 +512,20 @@ 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);
|
||||
if (priv->algorithm != FPI_PRINT_SIGFM)
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
fp_image_extract_sigfm_info (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,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "fp-image.h"
|
||||
#include "sigfm/sigfm.h"
|
||||
#include <config.h>
|
||||
|
||||
/**
|
||||
* FpiImageFlags:
|
||||
|
|
@ -64,12 +66,13 @@ struct _FpImage
|
|||
FpiImageFlags flags;
|
||||
|
||||
/*< private >*/
|
||||
guint8 *data;
|
||||
guint8 *binarized;
|
||||
guint8 *data;
|
||||
guint8 *binarized;
|
||||
|
||||
GPtrArray *minutiae;
|
||||
GPtrArray *minutiae;
|
||||
SigfmImgInfo *sigfm_info;
|
||||
|
||||
gboolean detection_in_progress;
|
||||
gboolean detection_in_progress;
|
||||
};
|
||||
|
||||
gint fpi_std_sq_dev (const guint8 *buf,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "fpi-print.h"
|
||||
#include "sigfm/sigfm.h"
|
||||
#define FP_COMPONENT "print"
|
||||
#include "fpi-log.h"
|
||||
|
||||
|
|
@ -39,18 +41,26 @@
|
|||
* @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 ||
|
||||
print->type == FPI_PRINT_SIGFM);
|
||||
g_return_if_fail (add->type == FPI_PRINT_NBIS ||
|
||||
add->type == FPI_PRINT_SIGFM);
|
||||
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)));
|
||||
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]);
|
||||
g_ptr_array_add (print->prints, to_add);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -71,10 +81,12 @@ 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 || print->type == FPI_PRINT_SIGFM)
|
||||
{
|
||||
g_assert_null (print->prints);
|
||||
print->prints = g_ptr_array_new_with_free_func (g_free);
|
||||
print->prints = g_ptr_array_new_with_free_func (
|
||||
print->type == FPI_PRINT_NBIS ? g_free :
|
||||
(void (*)(void *))(sigfm_free_info));
|
||||
}
|
||||
g_object_notify (G_OBJECT (print), "fpi-type");
|
||||
}
|
||||
|
|
@ -144,7 +156,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 +172,8 @@ 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 && print->type != FPI_PRINT_SIGFM) ||
|
||||
!image)
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
|
|
@ -168,24 +181,29 @@ fpi_print_add_from_image (FpPrint *print,
|
|||
"Cannot add print data from image!");
|
||||
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);
|
||||
}
|
||||
else if (print->type == FPI_PRINT_SIGFM)
|
||||
{
|
||||
SigfmImgInfo * info = fp_image_get_sigfm_info (image);
|
||||
g_ptr_array_add (print->prints, info);
|
||||
}
|
||||
|
||||
_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);
|
||||
|
||||
g_clear_object (&print->image);
|
||||
print->image = g_object_ref (image);
|
||||
|
|
@ -198,7 +216,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 +228,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 +258,60 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* fpi_print_generate_user_id:
|
||||
* @print: #FpPrint to generate the ID for
|
||||
|
|
|
|||
|
|
@ -11,11 +11,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 +46,14 @@ 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);
|
||||
|
||||
FpiMatchResult fpi_print_sigfm_match (FpPrint * template, FpPrint * print,
|
||||
gint score_threshold, GError * *error);
|
||||
|
||||
/* 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);
|
||||
|
||||
|
|
|
|||
|
|
@ -236,6 +236,8 @@ deps = [
|
|||
mathlib_dep,
|
||||
] + optional_deps
|
||||
|
||||
subdir('sigfm')
|
||||
|
||||
# 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')
|
||||
|
|
@ -258,13 +260,15 @@ libnbis = static_library('nbis',
|
|||
]),
|
||||
install: false)
|
||||
|
||||
priv_deps = deps + libsigfm
|
||||
|
||||
libfprint_private = static_library('fprint-private',
|
||||
sources: [
|
||||
fpi_enums,
|
||||
libfprint_private_sources,
|
||||
],
|
||||
dependencies: deps,
|
||||
link_with: libnbis,
|
||||
link_with: [libnbis, libsigfm],
|
||||
install: false)
|
||||
|
||||
libfprint_drivers = static_library('fprint-drivers',
|
||||
|
|
@ -308,6 +312,9 @@ libfprint_dep = declare_dependency(link_with: libfprint,
|
|||
install_headers(['fprint.h'] + libfprint_public_headers,
|
||||
subdir: versioned_libname
|
||||
)
|
||||
install_headers(['sigfm/sigfm.h'],
|
||||
subdir: versioned_libname + '/sigfm'
|
||||
)
|
||||
|
||||
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;
|
||||
};
|
||||
11
libfprint/sigfm/meson.build
Normal file
11
libfprint/sigfm/meson.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
sigfm_sources = ['sigfm.cpp']
|
||||
|
||||
opencv = dependency('opencv4', required: true)
|
||||
doctest = dependency('doctest', required: true)
|
||||
|
||||
libsigfm = static_library('sigfm',
|
||||
sigfm_sources,
|
||||
dependencies: [opencv],
|
||||
)
|
||||
sigfm_tests = executable('sigfm-tests', ['./tests.cpp'], dependencies: [doctest, opencv], link_with: [libsigfm])
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ project('libfprint', [ 'c', 'cpp' ],
|
|||
'buildtype=debugoptimized',
|
||||
'warning_level=1',
|
||||
'c_std=gnu99',
|
||||
'cpp_std=c++17',
|
||||
],
|
||||
meson_version: '>= 0.59.0')
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue