Jelajahi Sumber

Only show high quality faces in file info

laurenspriem 1 tahun lalu
induk
melakukan
39f16ff517

+ 6 - 3
mobile/lib/face/db.dart

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

+ 1 - 1
mobile/lib/face/db_fields.dart

@@ -1,5 +1,5 @@
 // Faces Table Fields & Schema Queries
 // 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 facesTable = 'faces';
 const fileIDColumn = 'file_id';
 const fileIDColumn = 'file_id';

+ 5 - 1
mobile/lib/face/model/face.dart

@@ -1,5 +1,5 @@
 import "package:photos/face/model/detection.dart";
 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 {
 class Face {
   final int fileID;
   final int fileID;
@@ -11,6 +11,10 @@ class Face {
 
 
   bool get isBlurry => blur < kLaplacianThreshold;
   bool get isBlurry => blur < kLaplacianThreshold;
 
 
+  bool get hasHighScore => score > kMinFaceScore;
+
+  bool get isHighQuality => (!isBlurry) && hasHighScore;
+
   Face(
   Face(
     this.faceID,
     this.faceID,
     this.fileID,
     this.fileID,

+ 0 - 2
mobile/lib/services/face_ml/blur_detection/blur_constants.dart

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

+ 1 - 1
mobile/lib/services/face_ml/blur_detection/blur_detection_service.dart → mobile/lib/services/face_ml/face_filtering/blur_detection_service.dart

@@ -1,5 +1,5 @@
 import 'package:logging/logging.dart';
 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 {
 class BlurDetectionService {
   final _logger = Logger('BlurDetectionService');
   final _logger = Logger('BlurDetectionService');

+ 8 - 0
mobile/lib/services/face_ml/face_filtering/face_filtering_constants.dart

@@ -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;

+ 1 - 1
mobile/lib/services/face_ml/face_ml_result.dart

@@ -6,11 +6,11 @@ import "package:photos/db/ml_data_db.dart";
 import "package:photos/models/file/file.dart";
 import "package:photos/models/file/file.dart";
 import 'package:photos/models/ml/ml_typedefs.dart';
 import 'package:photos/models/ml/ml_typedefs.dart';
 import "package:photos/models/ml/ml_versions.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_alignment/alignment_result.dart";
 import "package:photos/services/face_ml/face_clustering/cosine_distance.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_detection/detection.dart";
 import "package:photos/services/face_ml/face_feedback.dart/cluster_feedback.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";
 import "package:photos/services/face_ml/face_ml_methods.dart";
 
 
 final _logger = Logger('ClusterResult_FaceMlResult');
 final _logger = Logger('ClusterResult_FaceMlResult');

+ 2 - 1
mobile/lib/services/face_ml/face_ml_service.dart

@@ -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_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/face_embedding_exceptions.dart";
 import 'package:photos/services/face_ml/face_embedding/onnx_face_embedding.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_exceptions.dart";
 import "package:photos/services/face_ml/face_ml_result.dart";
 import "package:photos/services/face_ml/face_ml_result.dart";
 import 'package:photos/services/machine_learning/file_ml/file_ml.dart';
 import 'package:photos/services/machine_learning/file_ml/file_ml.dart';
@@ -361,7 +362,7 @@ class FaceMlService {
     await clusterAllImages();
     await clusterAllImages();
   }
   }
 
 
-  Future<void> clusterAllImages({double minFaceScore = 0.75}) async {
+  Future<void> clusterAllImages({double minFaceScore = kMinFaceScore}) async {
     _logger.info("`clusterAllImages()` called");
     _logger.info("`clusterAllImages()` called");
 
 
     try {
     try {

+ 4 - 0
mobile/lib/ui/viewer/file_details/face_widget.dart

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

+ 14 - 2
mobile/lib/ui/viewer/file_details/faces_item_widget.dart

@@ -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!);
           .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 [
         return [
           const ChipButtonWidget(
           const ChipButtonWidget(
             "No faces found",
             "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.
       // 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));
       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
       // TODO: add deduplication of faces of same person
       final faceIdsToClusterIds = await FaceMLDataDB.instance
       final faceIdsToClusterIds = await FaceMLDataDB.instance
           .getFaceIdsToClusterIds(faces.map((face) => face.faceID));
           .getFaceIdsToClusterIds(faces.map((face) => face.faceID));

+ 1 - 1
mobile/lib/utils/image_ml_util.dart

@@ -18,10 +18,10 @@ import 'package:flutter/painting.dart' as paint show decodeImageFromList;
 import 'package:ml_linalg/linalg.dart';
 import 'package:ml_linalg/linalg.dart';
 import "package:photos/face/model/box.dart";
 import "package:photos/face/model/box.dart";
 import 'package:photos/models/ml/ml_typedefs.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/alignment_result.dart";
 import "package:photos/services/face_ml/face_alignment/similarity_transform.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_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.
 /// 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!!!!
 /// Don't use them outside of the isolate, unless you are okay with UI jank!!!!