[mob] Better blur detection handling background noise
This commit is contained in:
parent
624a06c3f8
commit
e3b8d8975f
3 changed files with 107 additions and 22 deletions
|
@ -1,7 +1,24 @@
|
|||
import 'dart:math' show sqrt, pow;
|
||||
import 'dart:math' show max, min, pow, sqrt;
|
||||
|
||||
import "package:photos/face/model/dimension.dart";
|
||||
|
||||
enum FaceDirection { left, right, straight }
|
||||
|
||||
extension FaceDirectionExtension on FaceDirection {
|
||||
String toDirectionString() {
|
||||
switch (this) {
|
||||
case FaceDirection.left:
|
||||
return 'L';
|
||||
case FaceDirection.right:
|
||||
return 'R';
|
||||
case FaceDirection.straight:
|
||||
return 'S';
|
||||
default:
|
||||
throw Exception('Unknown FaceDirection');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Detection {
|
||||
final double score;
|
||||
|
||||
|
@ -426,6 +443,37 @@ class FaceDetectionAbsolute extends Detection {
|
|||
|
||||
/// The height of the bounding box of the face detection, in number of pixels, range [0, imageHeight].
|
||||
double get height => yMaxBox - yMinBox;
|
||||
|
||||
FaceDirection getFaceDirection() {
|
||||
final double eyeDistanceX = (rightEye[0] - leftEye[0]).abs();
|
||||
final double eyeDistanceY = (rightEye[1] - leftEye[1]).abs();
|
||||
final double mouthDistanceY = (rightMouth[1] - leftMouth[1]).abs();
|
||||
|
||||
final bool faceIsUpright =
|
||||
(max(leftEye[1], rightEye[1]) + 0.5 * eyeDistanceY < nose[1]) &&
|
||||
(nose[1] + 0.5 * mouthDistanceY < min(leftMouth[1], rightMouth[1]));
|
||||
|
||||
final bool noseStickingOutLeft = (nose[0] < min(leftEye[0], rightEye[0])) &&
|
||||
(nose[0] < min(leftMouth[0], rightMouth[0]));
|
||||
final bool noseStickingOutRight =
|
||||
(nose[0] > max(leftEye[0], rightEye[0])) &&
|
||||
(nose[0] > max(leftMouth[0], rightMouth[0]));
|
||||
|
||||
final bool noseCloseToLeftEye =
|
||||
(nose[0] - leftEye[0]).abs() < 0.2 * eyeDistanceX;
|
||||
final bool noseCloseToRightEye =
|
||||
(nose[0] - rightEye[0]).abs() < 0.2 * eyeDistanceX;
|
||||
|
||||
// if (faceIsUpright && (noseStickingOutLeft || noseCloseToLeftEye)) {
|
||||
if (noseStickingOutLeft || (faceIsUpright && noseCloseToLeftEye)) {
|
||||
return FaceDirection.left;
|
||||
// } else if (faceIsUpright && (noseStickingOutRight || noseCloseToRightEye)) {
|
||||
} else if (noseStickingOutRight || (faceIsUpright && noseCloseToRightEye)) {
|
||||
return FaceDirection.right;
|
||||
}
|
||||
|
||||
return FaceDirection.straight;
|
||||
}
|
||||
}
|
||||
|
||||
List<FaceDetectionAbsolute> relativeToAbsoluteDetections({
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:logging/logging.dart';
|
||||
import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart";
|
||||
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
|
||||
|
||||
class BlurDetectionService {
|
||||
|
@ -12,8 +13,10 @@ class BlurDetectionService {
|
|||
Future<(bool, double)> predictIsBlurGrayLaplacian(
|
||||
List<List<int>> grayImage, {
|
||||
int threshold = kLaplacianThreshold,
|
||||
FaceDirection faceDirection = FaceDirection.straight,
|
||||
}) async {
|
||||
final List<List<int>> laplacian = _applyLaplacian(grayImage);
|
||||
final List<List<int>> laplacian =
|
||||
_applyLaplacian(grayImage, faceDirection: faceDirection);
|
||||
final double variance = _calculateVariance(laplacian);
|
||||
_logger.info('Variance: $variance');
|
||||
return (variance < threshold, variance);
|
||||
|
@ -46,43 +49,80 @@ class BlurDetectionService {
|
|||
return variance;
|
||||
}
|
||||
|
||||
List<List<int>> _padImage(List<List<int>> image) {
|
||||
List<List<int>> _padImage(
|
||||
List<List<int>> image, {
|
||||
int removeSideColumns = 56,
|
||||
FaceDirection faceDirection = FaceDirection.straight,
|
||||
}) {
|
||||
// Exception is removeSideColumns is not even
|
||||
if (removeSideColumns % 2 != 0) {
|
||||
throw Exception('removeSideColumns must be even');
|
||||
}
|
||||
|
||||
final int numRows = image.length;
|
||||
final int numCols = image[0].length;
|
||||
final int paddedNumCols = numCols + 2 - removeSideColumns;
|
||||
final int paddedNumRows = numRows + 2;
|
||||
|
||||
// Create a new matrix with extra padding
|
||||
final List<List<int>> paddedImage = List.generate(
|
||||
numRows + 2,
|
||||
(i) => List.generate(numCols + 2, (j) => 0, growable: false),
|
||||
paddedNumRows,
|
||||
(i) => List.generate(
|
||||
paddedNumCols,
|
||||
(j) => 0,
|
||||
growable: false,
|
||||
),
|
||||
growable: false,
|
||||
);
|
||||
|
||||
// Copy original image into the center of the padded image
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
for (int j = 0; j < numCols; j++) {
|
||||
paddedImage[i + 1][j + 1] = image[i][j];
|
||||
// Copy original image into the center of the padded image, taking into account the face direction
|
||||
if (faceDirection == FaceDirection.straight) {
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
for (int j = 0; j < (paddedNumCols - 2); j++) {
|
||||
paddedImage[i + 1][j + 1] =
|
||||
image[i][j + (removeSideColumns / 2).round()];
|
||||
}
|
||||
}
|
||||
// If the face is facing left, we only take the right side of the face image
|
||||
} else if (faceDirection == FaceDirection.left) {
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
for (int j = 0; j < (paddedNumCols - 2); j++) {
|
||||
paddedImage[i + 1][j + 1] = image[i][j + removeSideColumns];
|
||||
}
|
||||
}
|
||||
// If the face is facing right, we only take the left side of the face image
|
||||
} else if (faceDirection == FaceDirection.right) {
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
for (int j = 0; j < (paddedNumCols - 2); j++) {
|
||||
paddedImage[i + 1][j + 1] = image[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reflect padding
|
||||
// Top and bottom rows
|
||||
for (int j = 1; j <= numCols; j++) {
|
||||
for (int j = 1; j <= (paddedNumCols - 2); j++) {
|
||||
paddedImage[0][j] = paddedImage[2][j]; // Top row
|
||||
paddedImage[numRows + 1][j] = paddedImage[numRows - 1][j]; // Bottom row
|
||||
}
|
||||
// Left and right columns
|
||||
for (int i = 0; i < numRows + 2; i++) {
|
||||
paddedImage[i][0] = paddedImage[i][2]; // Left column
|
||||
paddedImage[i][numCols + 1] = paddedImage[i][numCols - 1]; // Right column
|
||||
paddedImage[i][paddedNumCols - 1] =
|
||||
paddedImage[i][paddedNumCols - 3]; // Right column
|
||||
}
|
||||
|
||||
return paddedImage;
|
||||
}
|
||||
|
||||
List<List<int>> _applyLaplacian(List<List<int>> image) {
|
||||
final List<List<int>> paddedImage = _padImage(image);
|
||||
final int numRows = image.length;
|
||||
final int numCols = image[0].length;
|
||||
List<List<int>> _applyLaplacian(
|
||||
List<List<int>> image, {
|
||||
FaceDirection faceDirection = FaceDirection.straight,
|
||||
}) {
|
||||
final List<List<int>> paddedImage =
|
||||
_padImage(image, faceDirection: faceDirection);
|
||||
final int numRows = paddedImage.length - 2;
|
||||
final int numCols = paddedImage[0].length - 2;
|
||||
final List<List<int>> outputImage = List.generate(
|
||||
numRows,
|
||||
(i) => List.generate(numCols, (j) => 0, growable: false),
|
||||
|
|
|
@ -1099,19 +1099,16 @@ Future<(Float32List, List<AlignmentResult>, List<bool>, List<double>, Size)>
|
|||
imageHeight: image.height,
|
||||
);
|
||||
|
||||
final List<List<List<double>>> faceLandmarks =
|
||||
absoluteFaces.map((face) => face.allKeypoints).toList();
|
||||
|
||||
final alignedImagesFloat32List =
|
||||
Float32List(3 * width * height * faceLandmarks.length);
|
||||
Float32List(3 * width * height * absoluteFaces.length);
|
||||
final alignmentResults = <AlignmentResult>[];
|
||||
final isBlurs = <bool>[];
|
||||
final blurValues = <double>[];
|
||||
|
||||
int alignedImageIndex = 0;
|
||||
for (final faceLandmark in faceLandmarks) {
|
||||
for (final face in absoluteFaces) {
|
||||
final (alignmentResult, correctlyEstimated) =
|
||||
SimilarityTransform.instance.estimate(faceLandmark);
|
||||
SimilarityTransform.instance.estimate(face.allKeypoints);
|
||||
if (!correctlyEstimated) {
|
||||
alignedImageIndex += 3 * width * height;
|
||||
alignmentResults.add(AlignmentResult.empty());
|
||||
|
@ -1137,7 +1134,7 @@ Future<(Float32List, List<AlignmentResult>, List<bool>, List<double>, Size)>
|
|||
final grayscalems = blurDetectionStopwatch.elapsedMilliseconds;
|
||||
log('creating grayscale matrix took $grayscalems ms');
|
||||
final (isBlur, blurValue) = await BlurDetectionService.instance
|
||||
.predictIsBlurGrayLaplacian(faceGrayMatrix);
|
||||
.predictIsBlurGrayLaplacian(faceGrayMatrix, faceDirection: face.getFaceDirection());
|
||||
final blurms = blurDetectionStopwatch.elapsedMilliseconds - grayscalems;
|
||||
log('blur detection took $blurms ms');
|
||||
log(
|
||||
|
|
Loading…
Add table
Reference in a new issue