Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Dart side of face detector #649

Merged
merged 18 commits into from
Jul 16, 2018
Merged

Conversation

bparrishMines
Copy link
Contributor

No description provided.

Copy link
Contributor

@mravn-google mravn-google left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

/// The options for the face detector.
final FaceDetectorOptions options;

/// Closes the face detector and release its model resources.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

release => releases

'enableLandmarks': options.enableLandmarks,
'enableTracking': options.enableTracking,
'minFaceSize': options.minFaceSize,
'mode': options.mode == FaceDetectorMode.fast ? 'fast' : 'accurate',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code silently does the wrong thing if you add a new FaceDetectorMode in the future. We could add something like

String modeToString(FaceDetectorMode mode) {
  final String full = mode.toString();
  return full.substring(full.indexOf('.') + 1);
}

This method could alternatively be implemented using a switch.

}
}

class FaceDetectorOptions {}
/// Options for [FaceDetector].
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't give any new information :-)
You might want to add that instances are immutable.

class FaceDetectorOptions {}
/// Options for [FaceDetector].
class FaceDetectorOptions {
FaceDetectorOptions({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dartdoc missing on constructor. The preconditions enforced by your asserts should be documented.

faces.add(new Face._(data));
});

return faces;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using forEach might be considered clunkier than using List.map

return reply.map<Face>((dynamic data) => Face._(data)).toList();

and is likely slower than using a plain for loop:

final List<Face> faces = <Face>[];
for (dynamic data in reply) {
  faces.add(Face._(data));
}
return faces;

/// The axis-aligned bounding rectangle of the detected face.
final Rectangle<int> boundingBox;

/// The rotation of the face about the vertical axis of the image.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In degrees or radians? Is the rotation normalized to some interval like 0..360?

Same below.


final Map<FaceLandmarkType, FaceLandmark> _landmarks;

/// The axis-aligned bounding rectangle of the detected face.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the coordinate system here? Image pixels with (0, 0) the upper left corner?

const Point<num>(19, 20),
expect(block.cornerPoints, <Point<int>>[
const Point<int>(17, 18),
const Point<int>(19, 20),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] You could move the const out to the list literal to avoid repeating it:

const <Point<int>>[
  Point<int>(17, 18),
  Point<int>(19, 20),
]

expect(
face.landmark(FaceLandmarkType.rightMouth).type,
FaceLandmarkType.rightMouth,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Half of the above code could be reduced to

for (FaceLandmarkType type in FaceLandmarkType.values) {
  expect(face.landmark(type).type, type)
}


data.forEach((dynamic key, dynamic point) {
FaceLandmarkType landmarkType;
switch (key) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This switch could be replaced by a lookup into a Map<String, FaceLandmarkType>:

enum FaceLandmarkType { ... }

Map<String, FaceLandmarkType> _stringToTypeMap;

String _typeToString(FaceLandmarkType type) {
  final String full = type.toString();
  return full.substring(full.indexOf('.') + 1);
}

FaceLandmarkType _stringToType(String string) {
  _stringToTypeMap ??= Map<String, FaceLandmarkType>.fromIterables(
    FaceLandmarkType.values.map<String>(_typeToString),
    FaceLandmarkType.values,
  );
  FaceLandmarkType type = _stringToTypeMap[string];
  if (type == null) {
    throw ArgumentError.value(string, 'Landmark name', 'Not valid landmark.');
  }
  return type;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how important readability is, but i also got this to work. haha

_landmarks = Map<FaceLandmarkType, FaceLandmark>.fromIterables(
          FaceLandmarkType.values,
          List<FaceLandmark>.generate(
            FaceLandmarkType.values.length,
            (index) {
              final FaceLandmarkType type = FaceLandmarkType.values[index];
              return FaceLandmark._(
                type,
                Point<int>(
                  data['landmarks'][_enumToString(type)][0],
                  data['landmarks'][_enumToString(type)][1],
                ),
              );
            },
          ),
        );

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would fail if missing one though. I'll probably have to add a check.

),
);
},
),
Copy link
Contributor

@mravn-google mravn-google Jul 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have a list of type List<T> and an expression expr(t) that has type U when t has type T, then you can replace

List<U>.generate(list.length, (int index) => expr(list[index]))

by the simpler

list.map<U>((T t) => expr(t)).toList()

provided expr does not use index. Since you only need an Iterable<U>, you can dispense with the .toList() and simplify your code to

_landmarks = Map<FaceLandmarkType, FaceLandmark>.fromIterables(
  FaceLandmarkType.values,
  FaceLandmarkType.values.map<FaceLandmark>((FaceLandmarkType type) {
    final dynamic landmarkMap = data['landmarks'];
    final String typeString = _enumToString(type);
    if (landmarkMap[typeString] == null) return null;
    return FaceLandmark._(
      type,
      Point<int>(
        landmarkMap[typeString][0],
        landmarkMap[typeString][1],
      ),
    );
  }
);

Next, you can avoid repeated lookup of data['landmarks'] and landmarkMap[typeString] by a bit of hoisting:

final Map<dynamic, dynamic> landmarkMap = data['landmarks'];
_landmarks = Map<FaceLandmarkType, FaceLandmark>.fromIterables(
  FaceLandmarkType.values,
  FaceLandmarkType.values.map<FaceLandmark>((FaceLandmarkType type) {
    final List<dynamic> position = landmarkMap[_enumToString(type)];
    return (position == null) ? null : FaceLandmark._(type, Point<int>(position[0], position[1]));
  }
);

@bparrishMines bparrishMines merged commit f82464c into flutter:master Jul 16, 2018
@bparrishMines bparrishMines deleted the detector_face branch July 16, 2018 19:45
kmorkos pushed a commit to kmorkos/plugins that referenced this pull request Jul 19, 2018
julianscheel pushed a commit to jusst-engineering/plugins that referenced this pull request Mar 11, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
4 participants