[mob] Use more conservative cluster threshold for sideways faces

This commit is contained in:
laurenspriem 2024-04-19 14:58:52 +05:30
parent 2b88daa15f
commit ecc1bc9980
4 changed files with 44 additions and 5 deletions

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import "dart:convert" show json;
import "dart:io" show Directory; import "dart:io" show Directory;
import "dart:math"; import "dart:math";
@ -10,6 +11,7 @@ import 'package:path_provider/path_provider.dart';
import "package:photos/extensions/stop_watch.dart"; import "package:photos/extensions/stop_watch.dart";
import 'package:photos/face/db_fields.dart'; import 'package:photos/face/db_fields.dart';
import "package:photos/face/db_model_mappers.dart"; import "package:photos/face/db_model_mappers.dart";
import "package:photos/face/model/detection.dart";
import "package:photos/face/model/face.dart"; import "package:photos/face/model/face.dart";
import "package:photos/models/file/file.dart"; import "package:photos/models/file/file.dart";
import "package:photos/services/machine_learning/face_ml/face_clustering/face_info_for_clustering.dart"; import "package:photos/services/machine_learning/face_ml/face_clustering/face_info_for_clustering.dart";
@ -455,6 +457,31 @@ class FaceMLDataDB {
); );
} }
// TODO: remove this method and replace by correct logic during indexing
Future<Map<String, bool>> getFaceIdsToIsSidewaysFaceTEMP() async {
final db = await instance.sqliteAsyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT $faceIDColumn, $faceDetectionColumn FROM $facesTable',
);
final time = DateTime.now();
final Map<String, bool> result = {};
for (final map in maps) {
final faceID = map[faceIDColumn] as String;
final detection =
Detection.fromJson(json.decode(map[faceDetectionColumn] as String));
result[faceID] = detection.faceIsSideways();
}
_logger.info(
'decoding detections and calculating face sideways bools took ${DateTime.now().difference(time).inMilliseconds} ms',
);
final double sidewaysRatio =
result.values.where((e) => e).length / result.length;
_logger.info('sideways face ratio: $sidewaysRatio');
return result;
}
Future<Set<FaceInfoForClustering>> getFaceInfoForClustering({ Future<Set<FaceInfoForClustering>> getFaceInfoForClustering({
double minScore = kMinHighQualityFaceScore, double minScore = kMinHighQualityFaceScore,
int minClarity = kLaplacianHardThreshold, int minClarity = kLaplacianHardThreshold,
@ -468,6 +495,9 @@ class FaceMLDataDB {
); );
final db = await instance.sqliteAsyncDB; final db = await instance.sqliteAsyncDB;
final Map<String, bool> faceIdsToIsSideways =
await getFaceIdsToIsSidewaysFaceTEMP();
final Set<FaceInfoForClustering> result = {}; final Set<FaceInfoForClustering> result = {};
while (true) { while (true) {
// Query a batch of rows // Query a batch of rows
@ -494,6 +524,7 @@ class FaceMLDataDB {
embeddingBytes: map[faceEmbeddingBlob] as Uint8List, embeddingBytes: map[faceEmbeddingBlob] as Uint8List,
faceScore: map[faceScore] as double, faceScore: map[faceScore] as double,
blurValue: map[faceBlur] as double, blurValue: map[faceBlur] as double,
isSideways: faceIdsToIsSideways[faceID] ?? false,
); );
result.add(faceInfo); result.add(faceInfo);
} }

View file

@ -22,7 +22,7 @@ class Detection {
bool get isEmpty => box.width == 0 && box.height == 0 && landmarks.isEmpty; bool get isEmpty => box.width == 0 && box.height == 0 && landmarks.isEmpty;
// emoty box // empty box
Detection.empty() Detection.empty()
: box = FaceBox( : box = FaceBox(
xMin: 0, xMin: 0,
@ -94,6 +94,9 @@ class Detection {
} }
FaceDirection getFaceDirection() { FaceDirection getFaceDirection() {
if (isEmpty) {
return FaceDirection.straight;
}
final leftEye = [landmarks[0].x, landmarks[0].y]; final leftEye = [landmarks[0].x, landmarks[0].y];
final rightEye = [landmarks[1].x, landmarks[1].y]; final rightEye = [landmarks[1].x, landmarks[1].y];
final nose = [landmarks[2].x, landmarks[2].y]; final nose = [landmarks[2].x, landmarks[2].y];
@ -131,6 +134,9 @@ class Detection {
} }
bool faceIsSideways() { bool faceIsSideways() {
if (isEmpty) {
return false;
}
final leftEye = [landmarks[0].x, landmarks[0].y]; final leftEye = [landmarks[0].x, landmarks[0].y];
final rightEye = [landmarks[1].x, landmarks[1].y]; final rightEye = [landmarks[1].x, landmarks[1].y];
final nose = [landmarks[2].x, landmarks[2].y]; final nose = [landmarks[2].x, landmarks[2].y];

View file

@ -452,7 +452,8 @@ class FaceClusteringService {
faceScore: face.faceScore, faceScore: face.faceScore,
blurValue: face.blurValue, blurValue: face.blurValue,
badFace: face.faceScore < kMinHighQualityFaceScore || badFace: face.faceScore < kMinHighQualityFaceScore ||
face.blurValue < kLaplacianSoftThreshold, face.blurValue < kLaplacianSoftThreshold ||
face.isSideways,
vEmbedding: Vector.fromList( vEmbedding: Vector.fromList(
EVector.fromBuffer(face.embeddingBytes).values, EVector.fromBuffer(face.embeddingBytes).values,
dtype: DType.float32, dtype: DType.float32,
@ -606,7 +607,7 @@ class FaceClusteringService {
); );
if (useDynamicThreshold) { if (useDynamicThreshold) {
log( log(
"[ClusterIsolate] ${DateTime.now()} Dynamic thresholding: $dynamicThresholdCount faces had a low face score or high blur value", "[ClusterIsolate] ${DateTime.now()} Dynamic thresholding: $dynamicThresholdCount faces had a low face score or low blur clarity",
); );
} }

View file

@ -1,4 +1,3 @@
import "dart:typed_data" show Uint8List; import "dart:typed_data" show Uint8List;
class FaceInfoForClustering { class FaceInfoForClustering {
@ -7,6 +6,7 @@ class FaceInfoForClustering {
final Uint8List embeddingBytes; final Uint8List embeddingBytes;
final double faceScore; final double faceScore;
final double blurValue; final double blurValue;
final bool isSideways;
FaceInfoForClustering({ FaceInfoForClustering({
required this.faceID, required this.faceID,
@ -14,5 +14,6 @@ class FaceInfoForClustering {
required this.embeddingBytes, required this.embeddingBytes,
required this.faceScore, required this.faceScore,
required this.blurValue, required this.blurValue,
this.isSideways = false,
}); });
} }