Only show high quality faces in file info
This commit is contained in:
parent
974b7c7329
commit
39f16ff517
11 changed files with 43 additions and 13 deletions
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
const kLaplacianThreshold = 15;
|
||||
const kLapacianDefault = 10000.0;
|
|
@ -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');
|
|
@ -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;
|
|
@ -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');
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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!!!!
|
||||
|
|
Loading…
Add table
Reference in a new issue