Only show high quality faces in file info

This commit is contained in:
laurenspriem 2024-03-20 14:34:12 +05:30
parent 974b7c7329
commit 39f16ff517
11 changed files with 43 additions and 13 deletions

View file

@ -11,7 +11,7 @@ import "package:photos/face/db_model_mappers.dart";
import "package:photos/face/model/face.dart";
import "package:photos/face/model/person.dart";
import "package:photos/models/file/file.dart";
import "package:photos/services/face_ml/blur_detection/blur_constants.dart";
import 'package:photos/services/face_ml/face_filtering/face_filtering_constants.dart';
import 'package:sqflite/sqflite.dart';
/// Stores all data for the ML-related features. The database can be accessed by `MlDataDB.instance.database`.
@ -226,7 +226,7 @@ class FaceMLDataDB {
return null;
}
Future<List<Face>> getFacesForGivenFileID(int fileUploadID) async {
Future<List<Face>?> getFacesForGivenFileID(int fileUploadID) async {
final db = await instance.database;
final List<Map<String, dynamic>> maps = await db.query(
facesTable,
@ -246,6 +246,9 @@ class FaceMLDataDB {
where: '$fileIDColumn = ?',
whereArgs: [fileUploadID],
);
if (maps.isEmpty) {
return null;
}
return maps.map((e) => mapRowToFace(e)).toList();
}
@ -338,7 +341,7 @@ class FaceMLDataDB {
///
/// Only selects faces with score greater than [minScore] and blur score greater than [minClarity]
Future<Map<String, (int?, Uint8List)>> getFaceEmbeddingMap({
double minScore = 0.78,
double minScore = kMinFaceScore,
int minClarity = kLaplacianThreshold,
int maxRows = 20000,
}) async {

View file

@ -1,5 +1,5 @@
// Faces Table Fields & Schema Queries
import "package:photos/services/face_ml/blur_detection/blur_constants.dart";
import 'package:photos/services/face_ml/face_filtering/face_filtering_constants.dart';
const facesTable = 'faces';
const fileIDColumn = 'file_id';

View file

@ -1,5 +1,5 @@
import "package:photos/face/model/detection.dart";
import "package:photos/services/face_ml/blur_detection/blur_constants.dart";
import 'package:photos/services/face_ml/face_filtering/face_filtering_constants.dart';
class Face {
final int fileID;
@ -11,6 +11,10 @@ class Face {
bool get isBlurry => blur < kLaplacianThreshold;
bool get hasHighScore => score > kMinFaceScore;
bool get isHighQuality => (!isBlurry) && hasHighScore;
Face(
this.faceID,
this.fileID,

View file

@ -1,2 +0,0 @@
const kLaplacianThreshold = 15;
const kLapacianDefault = 10000.0;

View file

@ -1,5 +1,5 @@
import 'package:logging/logging.dart';
import "package:photos/services/face_ml/blur_detection/blur_constants.dart";
import 'package:photos/services/face_ml/face_filtering/face_filtering_constants.dart';
class BlurDetectionService {
final _logger = Logger('BlurDetectionService');

View file

@ -0,0 +1,8 @@
/// Blur detection threshold
const kLaplacianThreshold = 15;
/// Default blur value
const kLapacianDefault = 10000.0;
/// The minimum score for a face to be considered a face
const kMinFaceScore = 0.78;

View file

@ -6,11 +6,11 @@ import "package:photos/db/ml_data_db.dart";
import "package:photos/models/file/file.dart";
import 'package:photos/models/ml/ml_typedefs.dart';
import "package:photos/models/ml/ml_versions.dart";
import "package:photos/services/face_ml/blur_detection/blur_constants.dart";
import "package:photos/services/face_ml/face_alignment/alignment_result.dart";
import "package:photos/services/face_ml/face_clustering/cosine_distance.dart";
import "package:photos/services/face_ml/face_detection/detection.dart";
import "package:photos/services/face_ml/face_feedback.dart/cluster_feedback.dart";
import 'package:photos/services/face_ml/face_filtering/face_filtering_constants.dart';
import "package:photos/services/face_ml/face_ml_methods.dart";
final _logger = Logger('ClusterResult_FaceMlResult');

View file

@ -31,6 +31,7 @@ import 'package:photos/services/face_ml/face_detection/yolov5face/onnx_face_dete
import "package:photos/services/face_ml/face_detection/yolov5face/yolo_face_detection_exceptions.dart";
import "package:photos/services/face_ml/face_embedding/face_embedding_exceptions.dart";
import 'package:photos/services/face_ml/face_embedding/onnx_face_embedding.dart';
import "package:photos/services/face_ml/face_filtering/face_filtering_constants.dart";
import "package:photos/services/face_ml/face_ml_exceptions.dart";
import "package:photos/services/face_ml/face_ml_result.dart";
import 'package:photos/services/machine_learning/file_ml/file_ml.dart';
@ -361,7 +362,7 @@ class FaceMlService {
await clusterAllImages();
}
Future<void> clusterAllImages({double minFaceScore = 0.75}) async {
Future<void> clusterAllImages({double minFaceScore = kMinFaceScore}) async {
_logger.info("`clusterAllImages()` called");
try {

View file

@ -88,6 +88,10 @@ class FaceWidget extends StatelessWidget {
),
),
const SizedBox(height: 8),
Text(
(face.score).toStringAsFixed(2),
style: Theme.of(context).textTheme.bodySmall,
),
if (person != null)
Text(
person!.attr.name.trim(),

View file

@ -36,9 +36,18 @@ class FacesItemWidget extends StatelessWidget {
];
}
final List<Face> faces = await FaceMLDataDB.instance
final List<Face>? faces = await FaceMLDataDB.instance
.getFacesForGivenFileID(file.uploadedFileID!);
if (faces.isEmpty || faces.every((face) => face.score < 0.5)) {
if (faces == null) {
return [
const ChipButtonWidget(
"Image not analyzed",
noChips: true,
),
];
}
if (faces.isEmpty ||
faces.every((face) => face.score < 0.75 || face.isBlurry)) {
return [
const ChipButtonWidget(
"No faces found",
@ -50,6 +59,9 @@ class FacesItemWidget extends StatelessWidget {
// Sort the faces by score in descending order, so that the highest scoring face is first.
faces.sort((Face a, Face b) => b.score.compareTo(a.score));
// Remove faces with low scores and blurry faces
faces.removeWhere((face) => face.isHighQuality == false);
// TODO: add deduplication of faces of same person
final faceIdsToClusterIds = await FaceMLDataDB.instance
.getFaceIdsToClusterIds(faces.map((face) => face.faceID));

View file

@ -18,10 +18,10 @@ import 'package:flutter/painting.dart' as paint show decodeImageFromList;
import 'package:ml_linalg/linalg.dart';
import "package:photos/face/model/box.dart";
import 'package:photos/models/ml/ml_typedefs.dart';
import "package:photos/services/face_ml/blur_detection/blur_detection_service.dart";
import "package:photos/services/face_ml/face_alignment/alignment_result.dart";
import "package:photos/services/face_ml/face_alignment/similarity_transform.dart";
import "package:photos/services/face_ml/face_detection/detection.dart";
import 'package:photos/services/face_ml/face_filtering/blur_detection_service.dart';
/// All of the functions in this file are helper functions for the [ImageMlIsolate] isolate.
/// Don't use them outside of the isolate, unless you are okay with UI jank!!!!