diff --git a/libfprint/drivers/elanspi.c b/libfprint/drivers/elanspi.c index 9338013d..4e23087b 100644 --- a/libfprint/drivers/elanspi.c +++ b/libfprint/drivers/elanspi.c @@ -1329,11 +1329,52 @@ elanspi_fp_assembling_get_pixel (struct fpi_frame_asmbl_ctx *ctx, struct fpi_fra return frame->data[y * ctx->frame_width + x]; } +/* Separable gaussian smoothing pass (sigma ~1.2, radius 3). Suppresses + * sensor-noise speckle that otherwise produces spurious, non-repeatable + * image features on the eFSA80SC. */ +static void +elanspi_smooth_image (FpImage *img) +{ + /* gaussian kernel, sigma=1.2 */ + static const guint kern[7] = { 34, 111, 218, 298, 218, 111, 34 }; + guint w = img->width, h = img->height; + g_autofree guint8 *tmp = g_malloc (w * h); + guint8 *data = (guint8 *) img->data; + + for (guint y = 0; y < h; y++) + for (guint x = 0; x < w; x++) + { + guint acc = 0, wsum = 0; + for (gint k = -3; k <= 3; k++) + { + gint xx = (gint) x + k; + if (xx < 0 || xx >= (gint) w) + continue; + acc += kern[k + 3] * data[y * w + xx]; + wsum += kern[k + 3]; + } + tmp[y * w + x] = acc / wsum; + } + for (guint y = 0; y < h; y++) + for (guint x = 0; x < w; x++) + { + guint acc = 0, wsum = 0; + for (gint k = -3; k <= 3; k++) + { + gint yy = (gint) y + k; + if (yy < 0 || yy >= (gint) h) + continue; + acc += kern[k + 3] * tmp[yy * w + x]; + wsum += kern[k + 3]; + } + data[y * w + x] = acc / wsum; + } +} + static void elanspi_fp_frame_stitch_and_submit (FpiDeviceElanSpi *self) { g_autoptr(FpImage) img = NULL; - g_autoptr(FpImage) scaled = NULL; struct fpi_frame_asmbl_ctx assembling_ctx = { .image_width = (self->frame_width * 3) / 2, @@ -1348,12 +1389,19 @@ elanspi_fp_frame_stitch_and_submit (FpiDeviceElanSpi *self) fpi_do_movement_estimation (&assembling_ctx, frame_start); img = fpi_assemble_frames (&assembling_ctx, frame_start); - scaled = fpi_image_resize (img, 2, 2); - scaled->flags |= FPI_IMAGE_PARTIAL | FPI_IMAGE_COLORS_INVERTED; + /* The assembled image is used at native scale: its ~10px ridge period is + * already in the matcher's expected band, and a 2x upscale only starves + * feature extraction. FPI_IMAGE_PARTIAL is not set since on these narrow + * strips it would cull most minutiae as perimeter points. Light smoothing + * suppresses sensor noise that otherwise creates spurious features. */ + elanspi_smooth_image (img); + img->flags |= FPI_IMAGE_COLORS_INVERTED; + + fp_dbg ("stitched image %dx%d submitted to SIGFM", img->width, img->height); /* submit image */ - fpi_image_device_image_captured (FP_IMAGE_DEVICE (self), g_steal_pointer (&scaled)); + fpi_image_device_image_captured (FP_IMAGE_DEVICE (self), g_steal_pointer (&img)); /* clean out frame data */ g_slist_free_full (g_steal_pointer (&self->fp_frame_list), g_free); @@ -1396,7 +1444,7 @@ elanspi_fp_frame_handler (FpiSsm *ssm, FpiDeviceElanSpi *self) case ELANSPI_GUESS_EMPTY: self->fp_empty_counter += 1; fp_dbg (" got empty"); - if (self->fp_empty_counter > 1) + if (self->fp_empty_counter > 3) { fp_dbg (" have enough debounce"); if (g_slist_length (self->fp_frame_list) >= ELANSPI_MIN_FRAMES_SWIPE) @@ -1416,10 +1464,10 @@ elanspi_fp_frame_handler (FpiSsm *ssm, FpiDeviceElanSpi *self) case ELANSPI_GUESS_FINGERPRINT: if (self->fp_empty_counter && self->fp_frame_list) { - if (self->fp_empty_counter < 1) + if (self->fp_empty_counter <= 2) { - fp_dbg (" possible bounced fp"); - break; + fp_dbg (" momentary gap during swipe, keeping list"); + self->fp_empty_counter = 0; } else { @@ -1445,7 +1493,19 @@ elanspi_fp_frame_handler (FpiSsm *ssm, FpiDeviceElanSpi *self) { gint difference = elanspi_get_frame_diff_stddev_sq (self, self->last_image, self->prev_frame_image); fp_dbg (" diff = %d", difference); - if (difference < ELANSPI_MIN_FRAME_TO_FRAME_DIFF) + gboolean is_enroll = + fpi_device_get_current_action (FP_DEVICE (self)) == + FPI_DEVICE_ACTION_ENROLL; + gint min_frame_diff = + is_enroll + ? ELANSPI_MIN_FRAME_TO_FRAME_DIFF_ENROLL + : ELANSPI_MIN_FRAME_TO_FRAME_DIFF_VERIFY; + + fp_dbg (" threshold = %d, profile = %s", + min_frame_diff, + is_enroll ? "enroll" : "verify"); + + if (difference < min_frame_diff) { fp_dbg (" ignoring b.c. difference is too small"); break; @@ -1641,7 +1701,12 @@ elanspi_fp_capture_finish (FpiSsm *ssm, FpDevice *dev, GError *error) /* if there was an error, report it */ if (error) - fpi_image_device_session_error (idev, error); + { + if (fpi_device_get_current_action (dev) != FPI_DEVICE_ACTION_NONE) + fpi_image_device_session_error (idev, error); + else + g_error_free (error); + } } static void @@ -1701,7 +1766,13 @@ 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; + /* SIGFM (SIFT-based) matching: NBIS/bozorth3 cannot discriminate fingers + * on the narrow stitched strips these sensors produce (measured genuine + * and impostor score distributions fully overlap). SIGFM separates them + * by ~3 orders of magnitude. Threshold chosen from a measured corpus: + * impostor pairs scored <= 26, genuine pairs typically 10^3..10^7. */ + img_class->algorithm = FPI_DEVICE_ALGO_SIGFM; + img_class->bz3_threshold = 30; img_class->img_open = elanspi_open; img_class->activate = elanspi_activate; img_class->deactivate = elanspi_deactivate; diff --git a/libfprint/drivers/elanspi.h b/libfprint/drivers/elanspi.h index a8d73194..8e8d07a0 100644 --- a/libfprint/drivers/elanspi.h +++ b/libfprint/drivers/elanspi.h @@ -340,6 +340,8 @@ static const struct elanspi_regtable elanspi_calibration_table_new_page1 = { // using checkargs ACPI:HIDPID static const FpIdEntry elanspi_id_table[] = { + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3128}, .driver_data = ELANSPI_180_ROTATE}, + {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN70A1", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x322d}, .driver_data = ELANSPI_90LEFT_ROTATE}, {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3057}, .driver_data = ELANSPI_180_ROTATE}, {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x3087}, .driver_data = ELANSPI_180_ROTATE}, {.udev_types = ELANSPI_UDEV_TYPES, .spi_acpi_id = "ELAN7001", .hid_id = {.vid = ELANSPI_TP_VID, .pid = 0x30c6}, .driver_data = ELANSPI_180_ROTATE}, @@ -362,17 +364,18 @@ static const FpIdEntry elanspi_id_table[] = { #define ELANSPI_MIN_EMPTY_INVALID_PERCENT 6 #define ELANSPI_MAX_REAL_INVALID_PERCENT 3 -#define ELANSPI_MIN_REAL_STDDEV (592 * 592) -#define ELANSPI_MAX_EMPTY_STDDEV (350 * 350) +#define ELANSPI_MIN_REAL_STDDEV (250 * 250) +#define ELANSPI_MAX_EMPTY_STDDEV (180 * 180) #define ELANSPI_MIN_FRAMES_DEBOUNCE 2 #define ELANSPI_SWIPE_FRAMES_DISCARD 1 -#define ELANSPI_MIN_FRAMES_SWIPE (7 + ELANSPI_SWIPE_FRAMES_DISCARD) -#define ELANSPI_MAX_FRAMES_SWIPE (20 + ELANSPI_SWIPE_FRAMES_DISCARD) +#define ELANSPI_MIN_FRAMES_SWIPE (2 + ELANSPI_SWIPE_FRAMES_DISCARD) +#define ELANSPI_MAX_FRAMES_SWIPE (12 + ELANSPI_SWIPE_FRAMES_DISCARD) #define ELANSPI_MAX_FRAME_HEIGHT 43 -#define ELANSPI_MIN_FRAME_TO_FRAME_DIFF (250 * 250) +#define ELANSPI_MIN_FRAME_TO_FRAME_DIFF_VERIFY (100 * 100) +#define ELANSPI_MIN_FRAME_TO_FRAME_DIFF_ENROLL (100 * 100) #define ELANSPI_HV_SENSOR_FRAME_DELAY 23 diff --git a/libfprint/fp-image.c b/libfprint/fp-image.c index 2303f6af..f95bd04a 100644 --- a/libfprint/fp-image.c +++ b/libfprint/fp-image.c @@ -316,7 +316,7 @@ fp_image_sigfm_extract_thread_func (GTask * task, void * src_obj, g_timer_stop (timer); fp_dbg ("sigfm extract completed in %f secs", g_timer_elapsed (timer, NULL)); g_timer_destroy (timer); - if (sigfm_keypoints_count (data->sigfm_info) < 25) + if (sigfm_keypoints_count (data->sigfm_info) < 5) { g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "No enough keypoints found"); diff --git a/libfprint/fpi-print.c b/libfprint/fpi-print.c index 4368b82e..33d02f1b 100644 --- a/libfprint/fpi-print.c +++ b/libfprint/fpi-print.c @@ -305,7 +305,8 @@ fpi_print_sigfm_match (FpPrint * template, FpPrint * print, "error in sigfm_match_score"); return FPI_MATCH_ERROR; } - fp_dbg ("sigfm score %d/%d", score, bz3_threshold); + fp_dbg ("sigfm template %d/%d score %d/%d", + i + 1, template->prints->len, score, bz3_threshold); if (score >= bz3_threshold) return FPI_MATCH_SUCCESS; }