Add optional face blendshapes to face landmarks detector graph.
PiperOrigin-RevId: 513867488
This commit is contained in:
parent
c9c1bf21ae
commit
b7ec83efb5
|
@ -69,12 +69,14 @@ cc_library(
|
|||
name = "face_landmarks_detector_graph",
|
||||
srcs = ["face_landmarks_detector_graph.cc"],
|
||||
deps = [
|
||||
":face_blendshapes_graph",
|
||||
":tensors_to_face_landmarks_graph",
|
||||
"//mediapipe/calculators/core:begin_loop_calculator",
|
||||
"//mediapipe/calculators/core:end_loop_calculator",
|
||||
"//mediapipe/calculators/core:split_vector_calculator",
|
||||
"//mediapipe/calculators/core:split_vector_calculator_cc_proto",
|
||||
"//mediapipe/calculators/image:image_properties_calculator",
|
||||
"//mediapipe/calculators/tensor:image_to_tensor_calculator_cc_proto",
|
||||
"//mediapipe/calculators/tensor:inference_calculator",
|
||||
"//mediapipe/calculators/tensor:tensors_to_floats_calculator",
|
||||
"//mediapipe/calculators/tensor:tensors_to_floats_calculator_cc_proto",
|
||||
|
@ -89,8 +91,11 @@ cc_library(
|
|||
"//mediapipe/calculators/util:rect_transformation_calculator_cc_proto",
|
||||
"//mediapipe/calculators/util:thresholding_calculator",
|
||||
"//mediapipe/calculators/util:thresholding_calculator_cc_proto",
|
||||
"//mediapipe/framework:calculator_framework",
|
||||
"//mediapipe/framework:calculator_options_cc_proto",
|
||||
"//mediapipe/framework/api2:builder",
|
||||
"//mediapipe/framework/api2:port",
|
||||
"//mediapipe/framework/formats:classification_cc_proto",
|
||||
"//mediapipe/framework/formats:image",
|
||||
"//mediapipe/framework/formats:landmark_cc_proto",
|
||||
"//mediapipe/framework/formats:rect_cc_proto",
|
||||
|
@ -101,9 +106,11 @@ cc_library(
|
|||
"//mediapipe/tasks/cc/core:model_resources",
|
||||
"//mediapipe/tasks/cc/core:model_task_graph",
|
||||
"//mediapipe/tasks/cc/core:utils",
|
||||
"//mediapipe/tasks/cc/vision/face_landmarker/proto:face_blendshapes_graph_options_cc_proto",
|
||||
"//mediapipe/tasks/cc/vision/face_landmarker/proto:face_landmarks_detector_graph_options_cc_proto",
|
||||
"//mediapipe/tasks/cc/vision/face_landmarker/proto:tensors_to_face_landmarks_graph_options_cc_proto",
|
||||
"//mediapipe/tasks/cc/vision/utils:image_tensor_specs",
|
||||
"//mediapipe/util:graph_builder_utils",
|
||||
],
|
||||
alwayslink = 1,
|
||||
)
|
||||
|
|
|
@ -14,6 +14,7 @@ limitations under the License.
|
|||
==============================================================================*/
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -26,6 +27,9 @@ limitations under the License.
|
|||
#include "mediapipe/calculators/util/thresholding_calculator.pb.h"
|
||||
#include "mediapipe/framework/api2/builder.h"
|
||||
#include "mediapipe/framework/api2/port.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/calculator_options.pb.h"
|
||||
#include "mediapipe/framework/formats/classification.pb.h"
|
||||
#include "mediapipe/framework/formats/image.h"
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
|
@ -36,9 +40,11 @@ limitations under the License.
|
|||
#include "mediapipe/tasks/cc/core/model_resources.h"
|
||||
#include "mediapipe/tasks/cc/core/model_task_graph.h"
|
||||
#include "mediapipe/tasks/cc/core/utils.h"
|
||||
#include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_blendshapes_graph_options.pb.h"
|
||||
#include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.pb.h"
|
||||
#include "mediapipe/tasks/cc/vision/face_landmarker/proto/tensors_to_face_landmarks_graph_options.pb.h"
|
||||
#include "mediapipe/tasks/cc/vision/utils/image_tensor_specs.h"
|
||||
#include "mediapipe/util/graph_builder_utils.h"
|
||||
|
||||
namespace mediapipe {
|
||||
namespace tasks {
|
||||
|
@ -72,6 +78,7 @@ constexpr char kIterableTag[] = "ITERABLE";
|
|||
constexpr char kBatchEndTag[] = "BATCH_END";
|
||||
constexpr char kItemTag[] = "ITEM";
|
||||
constexpr char kDetectionTag[] = "DETECTION";
|
||||
constexpr char kBlendshapesTag[] = "BLENDSHAPES";
|
||||
|
||||
// a landmarks tensor and a scores tensor
|
||||
constexpr int kFaceLandmarksOutputTensorsNum = 2;
|
||||
|
@ -83,6 +90,7 @@ struct SingleFaceLandmarksOutputs {
|
|||
Stream<NormalizedRect> rect_next_frame;
|
||||
Stream<bool> presence;
|
||||
Stream<float> presence_score;
|
||||
std::optional<Stream<ClassificationList>> face_blendshapes;
|
||||
};
|
||||
|
||||
struct MultiFaceLandmarksOutputs {
|
||||
|
@ -90,6 +98,7 @@ struct MultiFaceLandmarksOutputs {
|
|||
Stream<std::vector<NormalizedRect>> rects_next_frame;
|
||||
Stream<std::vector<bool>> presences;
|
||||
Stream<std::vector<float>> presence_scores;
|
||||
std::optional<Stream<std::vector<ClassificationList>>> face_blendshapes;
|
||||
};
|
||||
|
||||
absl::Status SanityCheckOptions(
|
||||
|
@ -180,6 +189,62 @@ bool IsAttentionModel(const core::ModelResources& model_resources) {
|
|||
// Boolean value indicates whether the face is present.
|
||||
// PRESENCE_SCORE - float
|
||||
// Float value indicates the probability that the face is present.
|
||||
// BLENDSHAPES - ClassificationList @optional
|
||||
// Blendshape classification, available when face_blendshapes_graph_options
|
||||
// is set.
|
||||
// All 52 blendshape coefficients:
|
||||
// 0 - _neutral (ignore it)
|
||||
// 1 - browDownLeft
|
||||
// 2 - browDownRight
|
||||
// 3 - browInnerUp
|
||||
// 4 - browOuterUpLeft
|
||||
// 5 - browOuterUpRight
|
||||
// 6 - cheekPuff
|
||||
// 7 - cheekSquintLeft
|
||||
// 8 - cheekSquintRight
|
||||
// 9 - eyeBlinkLeft
|
||||
// 10 - eyeBlinkRight
|
||||
// 11 - eyeLookDownLeft
|
||||
// 12 - eyeLookDownRight
|
||||
// 13 - eyeLookInLeft
|
||||
// 14 - eyeLookInRight
|
||||
// 15 - eyeLookOutLeft
|
||||
// 16 - eyeLookOutRight
|
||||
// 17 - eyeLookUpLeft
|
||||
// 18 - eyeLookUpRight
|
||||
// 19 - eyeSquintLeft
|
||||
// 20 - eyeSquintRight
|
||||
// 21 - eyeWideLeft
|
||||
// 22 - eyeWideRight
|
||||
// 23 - jawForward
|
||||
// 24 - jawLeft
|
||||
// 25 - jawOpen
|
||||
// 26 - jawRight
|
||||
// 27 - mouthClose
|
||||
// 28 - mouthDimpleLeft
|
||||
// 29 - mouthDimpleRight
|
||||
// 30 - mouthFrownLeft
|
||||
// 31 - mouthFrownRight
|
||||
// 32 - mouthFunnel
|
||||
// 33 - mouthLeft
|
||||
// 34 - mouthLowerDownLeft
|
||||
// 35 - mouthLowerDownRight
|
||||
// 36 - mouthPressLeft
|
||||
// 37 - mouthPressRight
|
||||
// 38 - mouthPucker
|
||||
// 39 - mouthRight
|
||||
// 40 - mouthRollLower
|
||||
// 41 - mouthRollUpper
|
||||
// 42 - mouthShrugLower
|
||||
// 43 - mouthShrugUpper
|
||||
// 44 - mouthSmileLeft
|
||||
// 45 - mouthSmileRight
|
||||
// 46 - mouthStretchLeft
|
||||
// 47 - mouthStretchRight
|
||||
// 48 - mouthUpperUpLeft
|
||||
// 49 - mouthUpperUpRight
|
||||
// 50 - noseSneerLeft
|
||||
// 51 - noseSneerRight
|
||||
//
|
||||
// Example:
|
||||
// node {
|
||||
|
@ -191,6 +256,7 @@ bool IsAttentionModel(const core::ModelResources& model_resources) {
|
|||
// output_stream: "FACE_RECT_NEXT_FRAME:face_rect_next_frame"
|
||||
// output_stream: "PRESENCE:presence"
|
||||
// output_stream: "PRESENCE_SCORE:presence_score"
|
||||
// output_stream: "BLENDSHAPES:blendshapes"
|
||||
// options {
|
||||
// [mediapipe.tasks.vision.face_landmarker.proto.FaceLandmarksDetectorGraphOptions.ext]
|
||||
// {
|
||||
|
@ -200,6 +266,13 @@ bool IsAttentionModel(const core::ModelResources& model_resources) {
|
|||
// }
|
||||
// }
|
||||
// min_detection_confidence: 0.5
|
||||
// face_blendshapes_graph_options {
|
||||
// base_options {
|
||||
// model_asset {
|
||||
// file_name: "face_blendshape.tflite"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@ -214,7 +287,7 @@ class SingleFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
ASSIGN_OR_RETURN(
|
||||
auto outs,
|
||||
BuildSingleFaceLandmarksDetectorGraph(
|
||||
sc->Options<proto::FaceLandmarksDetectorGraphOptions>(),
|
||||
*sc->MutableOptions<proto::FaceLandmarksDetectorGraphOptions>(),
|
||||
*model_resources, graph[Input<Image>(kImageTag)],
|
||||
graph[Input<NormalizedRect>::Optional(kNormRectTag)], graph));
|
||||
outs.landmarks >>
|
||||
|
@ -223,6 +296,10 @@ class SingleFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
graph.Out(kFaceRectNextFrameTag).Cast<NormalizedRect>();
|
||||
outs.presence >> graph.Out(kPresenceTag).Cast<bool>();
|
||||
outs.presence_score >> graph.Out(kPresenceScoreTag).Cast<float>();
|
||||
if (outs.face_blendshapes) {
|
||||
outs.face_blendshapes.value() >>
|
||||
graph.Out(kBlendshapesTag).Cast<ClassificationList>();
|
||||
}
|
||||
return graph.GetConfig();
|
||||
}
|
||||
|
||||
|
@ -239,7 +316,7 @@ class SingleFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
// graph: the mediapipe graph instance to be updated.
|
||||
absl::StatusOr<SingleFaceLandmarksOutputs>
|
||||
BuildSingleFaceLandmarksDetectorGraph(
|
||||
const proto::FaceLandmarksDetectorGraphOptions& subgraph_options,
|
||||
proto::FaceLandmarksDetectorGraphOptions& subgraph_options,
|
||||
const core::ModelResources& model_resources, Stream<Image> image_in,
|
||||
Stream<NormalizedRect> face_rect, Graph& graph) {
|
||||
MP_RETURN_IF_ERROR(SanityCheckOptions(subgraph_options));
|
||||
|
@ -351,11 +428,26 @@ class SingleFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
auto face_rect_next_frame =
|
||||
AllowIf(face_rect_transformation.Out("").Cast<NormalizedRect>(),
|
||||
presence, graph);
|
||||
|
||||
std::optional<Stream<ClassificationList>> face_blendshapes;
|
||||
if (subgraph_options.has_face_blendshapes_graph_options()) {
|
||||
auto& face_blendshapes_graph = graph.AddNode(
|
||||
"mediapipe.tasks.vision.face_landmarker.FaceBlendshapesGraph");
|
||||
face_blendshapes_graph.GetOptions<proto::FaceBlendshapesGraphOptions>()
|
||||
.Swap(subgraph_options.mutable_face_blendshapes_graph_options());
|
||||
projected_landmarks >> face_blendshapes_graph.In(kLandmarksTag);
|
||||
image_size >> face_blendshapes_graph.In(kImageSizeTag);
|
||||
face_blendshapes =
|
||||
std::make_optional(face_blendshapes_graph.Out(kBlendshapesTag)
|
||||
.Cast<ClassificationList>());
|
||||
}
|
||||
|
||||
return {{
|
||||
/* landmarks= */ projected_landmarks,
|
||||
/* rect_next_frame= */ face_rect_next_frame,
|
||||
/* presence= */ presence,
|
||||
/* presence_score= */ presence_score,
|
||||
/* face_blendshapes= */ face_blendshapes,
|
||||
}};
|
||||
}
|
||||
};
|
||||
|
@ -390,6 +482,9 @@ REGISTER_MEDIAPIPE_GRAPH(
|
|||
// Vector of boolean value indicates whether the face is present.
|
||||
// PRESENCE_SCORE - std::vector<float>
|
||||
// Vector of float value indicates the probability that the face is present.
|
||||
// BLENDSHAPES - std::vector<ClassificationList> @optional
|
||||
// Vector of face blendshape classification, available when
|
||||
// face_blendshapes_graph_options is set.
|
||||
//
|
||||
// Example:
|
||||
// node {
|
||||
|
@ -401,6 +496,7 @@ REGISTER_MEDIAPIPE_GRAPH(
|
|||
// output_stream: "FACE_RECTS_NEXT_FRAME:face_rects_next_frame"
|
||||
// output_stream: "PRESENCE:presence"
|
||||
// output_stream: "PRESENCE_SCORE:presence_score"
|
||||
// output_stream: "BLENDSHAPES:blendshapes"
|
||||
// options {
|
||||
// [mediapipe.tasks.vision.face_landmarker.proto.FaceLandmarksDetectorGraphOptions.ext]
|
||||
// {
|
||||
|
@ -410,6 +506,13 @@ REGISTER_MEDIAPIPE_GRAPH(
|
|||
// }
|
||||
// }
|
||||
// min_detection_confidence: 0.5
|
||||
// face_blendshapes_graph_options {
|
||||
// base_options {
|
||||
// model_asset {
|
||||
// file_name: "face_blendshape.tflite"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@ -421,7 +524,7 @@ class MultiFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
ASSIGN_OR_RETURN(
|
||||
auto outs,
|
||||
BuildFaceLandmarksDetectorGraph(
|
||||
sc->Options<proto::FaceLandmarksDetectorGraphOptions>(),
|
||||
*sc->MutableOptions<proto::FaceLandmarksDetectorGraphOptions>(),
|
||||
graph[Input<Image>(kImageTag)],
|
||||
graph[Input<std::vector<NormalizedRect>>(kNormRectTag)], graph));
|
||||
outs.landmarks_lists >> graph.Out(kNormLandmarksTag)
|
||||
|
@ -431,13 +534,16 @@ class MultiFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
outs.presences >> graph.Out(kPresenceTag).Cast<std::vector<bool>>();
|
||||
outs.presence_scores >>
|
||||
graph.Out(kPresenceScoreTag).Cast<std::vector<float>>();
|
||||
|
||||
if (outs.face_blendshapes) {
|
||||
outs.face_blendshapes.value() >>
|
||||
graph.Out(kBlendshapesTag).Cast<std::vector<ClassificationList>>();
|
||||
}
|
||||
return graph.GetConfig();
|
||||
}
|
||||
|
||||
private:
|
||||
absl::StatusOr<MultiFaceLandmarksOutputs> BuildFaceLandmarksDetectorGraph(
|
||||
const proto::FaceLandmarksDetectorGraphOptions& subgraph_options,
|
||||
proto::FaceLandmarksDetectorGraphOptions& subgraph_options,
|
||||
Stream<Image> image_in,
|
||||
Stream<std::vector<NormalizedRect>> multi_face_rects, Graph& graph) {
|
||||
auto& face_landmark_subgraph = graph.AddNode(
|
||||
|
@ -445,7 +551,7 @@ class MultiFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
"SingleFaceLandmarksDetectorGraph");
|
||||
face_landmark_subgraph
|
||||
.GetOptions<proto::FaceLandmarksDetectorGraphOptions>()
|
||||
.CopyFrom(subgraph_options);
|
||||
.Swap(&subgraph_options);
|
||||
|
||||
auto& begin_loop_multi_face_rects =
|
||||
graph.AddNode("BeginLoopNormalizedRectCalculator");
|
||||
|
@ -490,11 +596,27 @@ class MultiFaceLandmarksDetectorGraph : public core::ModelTaskGraph {
|
|||
auto face_rects_next_frame = end_loop_rects_next_frame.Out(kIterableTag)
|
||||
.Cast<std::vector<NormalizedRect>>();
|
||||
|
||||
std::optional<Stream<std::vector<ClassificationList>>>
|
||||
face_blendshapes_vector;
|
||||
if (face_landmark_subgraph
|
||||
.GetOptions<proto::FaceLandmarksDetectorGraphOptions>()
|
||||
.has_face_blendshapes_graph_options()) {
|
||||
auto blendshapes = face_landmark_subgraph.Out(kBlendshapesTag);
|
||||
auto& end_loop_blendshapes =
|
||||
graph.AddNode("EndLoopClassificationListCalculator");
|
||||
batch_end >> end_loop_blendshapes.In(kBatchEndTag);
|
||||
blendshapes >> end_loop_blendshapes.In(kItemTag);
|
||||
face_blendshapes_vector =
|
||||
std::make_optional(end_loop_blendshapes.Out(kIterableTag)
|
||||
.Cast<std::vector<ClassificationList>>());
|
||||
}
|
||||
|
||||
return {{
|
||||
/* landmarks_lists= */ landmark_lists,
|
||||
/* face_rects_next_frame= */ face_rects_next_frame,
|
||||
/* presences= */ presences,
|
||||
/* presence_scores= */ presence_scores,
|
||||
/* face_blendshapes= */ face_blendshapes_vector,
|
||||
}};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,6 +13,8 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
==============================================================================*/
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
@ -21,6 +23,7 @@ limitations under the License.
|
|||
#include "mediapipe/framework/api2/port.h"
|
||||
#include "mediapipe/framework/calculator_framework.h"
|
||||
#include "mediapipe/framework/deps/file_path.h"
|
||||
#include "mediapipe/framework/formats/classification.pb.h"
|
||||
#include "mediapipe/framework/formats/image.h"
|
||||
#include "mediapipe/framework/formats/landmark.pb.h"
|
||||
#include "mediapipe/framework/formats/rect.pb.h"
|
||||
|
@ -33,6 +36,7 @@ limitations under the License.
|
|||
#include "mediapipe/tasks/cc/core/proto/base_options.pb.h"
|
||||
#include "mediapipe/tasks/cc/core/proto/external_file.pb.h"
|
||||
#include "mediapipe/tasks/cc/core/task_runner.h"
|
||||
#include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_blendshapes_graph_options.pb.h"
|
||||
#include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.pb.h"
|
||||
#include "mediapipe/tasks/cc/vision/utils/image_utils.h"
|
||||
|
||||
|
@ -70,6 +74,9 @@ constexpr char kPortraitExpectedFaceLandamrksName[] =
|
|||
"portrait_expected_face_landmarks.pbtxt";
|
||||
constexpr char kPortraitExpectedFaceLandamrksWithAttentionName[] =
|
||||
"portrait_expected_face_landmarks_with_attention.pbtxt";
|
||||
constexpr char kFaceBlendshapesModel[] = "face_blendshapes.tflite";
|
||||
constexpr char kPortraitExpectedBlendshapesName[] =
|
||||
"portrait_expected_blendshapes_with_attention.pbtxt";
|
||||
|
||||
constexpr char kImageTag[] = "IMAGE";
|
||||
constexpr char kImageName[] = "image";
|
||||
|
@ -86,13 +93,17 @@ constexpr char kPresenceTag[] = "PRESENCE";
|
|||
constexpr char kPresenceName[] = "presence";
|
||||
constexpr char kPresenceScoreTag[] = "PRESENCE_SCORE";
|
||||
constexpr char kPresenceScoreName[] = "presence_score";
|
||||
constexpr char kBlendshapesTag[] = "BLENDSHAPES";
|
||||
constexpr char kBlendshapesName[] = "blendshapes";
|
||||
|
||||
constexpr float kFractionDiff = 0.05; // percentage
|
||||
constexpr float kAbsMargin = 0.03;
|
||||
constexpr float kBlendshapesDiffMargin = 0.1;
|
||||
|
||||
// Helper function to create a Single Face Landmark TaskRunner.
|
||||
absl::StatusOr<std::unique_ptr<TaskRunner>> CreateSingleFaceLandmarksTaskRunner(
|
||||
absl::string_view model_name) {
|
||||
absl::string_view landmarks_model_name,
|
||||
std::optional<absl::string_view> blendshapes_model_name) {
|
||||
Graph graph;
|
||||
|
||||
auto& face_landmark_detection = graph.AddNode(
|
||||
|
@ -101,8 +112,17 @@ absl::StatusOr<std::unique_ptr<TaskRunner>> CreateSingleFaceLandmarksTaskRunner(
|
|||
|
||||
auto options = std::make_unique<proto::FaceLandmarksDetectorGraphOptions>();
|
||||
options->mutable_base_options()->mutable_model_asset()->set_file_name(
|
||||
JoinPath("./", kTestDataDirectory, model_name));
|
||||
JoinPath("./", kTestDataDirectory, landmarks_model_name));
|
||||
options->set_min_detection_confidence(0.5);
|
||||
|
||||
if (blendshapes_model_name.has_value()) {
|
||||
options->mutable_face_blendshapes_graph_options()
|
||||
->mutable_base_options()
|
||||
->mutable_model_asset()
|
||||
->set_file_name(
|
||||
JoinPath("./", kTestDataDirectory, *blendshapes_model_name));
|
||||
}
|
||||
|
||||
face_landmark_detection.GetOptions<proto::FaceLandmarksDetectorGraphOptions>()
|
||||
.Swap(options.get());
|
||||
|
||||
|
@ -120,6 +140,10 @@ absl::StatusOr<std::unique_ptr<TaskRunner>> CreateSingleFaceLandmarksTaskRunner(
|
|||
face_landmark_detection.Out(kFaceRectNextFrameTag)
|
||||
.SetName(kFaceRectNextFrameName) >>
|
||||
graph[Output<NormalizedRect>(kFaceRectNextFrameTag)];
|
||||
if (blendshapes_model_name.has_value()) {
|
||||
face_landmark_detection.Out(kBlendshapesTag).SetName(kBlendshapesName) >>
|
||||
graph[Output<ClassificationList>(kBlendshapesTag)];
|
||||
}
|
||||
|
||||
return TaskRunner::Create(
|
||||
graph.GetConfig(), absl::make_unique<core::MediaPipeBuiltinOpResolver>());
|
||||
|
@ -127,7 +151,8 @@ absl::StatusOr<std::unique_ptr<TaskRunner>> CreateSingleFaceLandmarksTaskRunner(
|
|||
|
||||
// Helper function to create a Multi Face Landmark TaskRunner.
|
||||
absl::StatusOr<std::unique_ptr<TaskRunner>> CreateMultiFaceLandmarksTaskRunner(
|
||||
absl::string_view model_name) {
|
||||
absl::string_view landmarks_model_name,
|
||||
std::optional<absl::string_view> blendshapes_model_name) {
|
||||
Graph graph;
|
||||
|
||||
auto& face_landmark_detection = graph.AddNode(
|
||||
|
@ -136,8 +161,15 @@ absl::StatusOr<std::unique_ptr<TaskRunner>> CreateMultiFaceLandmarksTaskRunner(
|
|||
|
||||
auto options = std::make_unique<proto::FaceLandmarksDetectorGraphOptions>();
|
||||
options->mutable_base_options()->mutable_model_asset()->set_file_name(
|
||||
JoinPath("./", kTestDataDirectory, model_name));
|
||||
JoinPath("./", kTestDataDirectory, landmarks_model_name));
|
||||
options->set_min_detection_confidence(0.5);
|
||||
if (blendshapes_model_name.has_value()) {
|
||||
options->mutable_face_blendshapes_graph_options()
|
||||
->mutable_base_options()
|
||||
->mutable_model_asset()
|
||||
->set_file_name(
|
||||
JoinPath("./", kTestDataDirectory, *blendshapes_model_name));
|
||||
}
|
||||
face_landmark_detection.GetOptions<proto::FaceLandmarksDetectorGraphOptions>()
|
||||
.Swap(options.get());
|
||||
|
||||
|
@ -156,6 +188,10 @@ absl::StatusOr<std::unique_ptr<TaskRunner>> CreateMultiFaceLandmarksTaskRunner(
|
|||
face_landmark_detection.Out(kFaceRectsNextFrameTag)
|
||||
.SetName(kFaceRectsNextFrameName) >>
|
||||
graph[Output<std::vector<NormalizedRect>>(kFaceRectsNextFrameTag)];
|
||||
if (blendshapes_model_name.has_value()) {
|
||||
face_landmark_detection.Out(kBlendshapesTag).SetName(kBlendshapesName) >>
|
||||
graph[Output<ClassificationList>(kBlendshapesTag)];
|
||||
}
|
||||
|
||||
return TaskRunner::Create(
|
||||
graph.GetConfig(), absl::make_unique<core::MediaPipeBuiltinOpResolver>());
|
||||
|
@ -168,6 +204,13 @@ NormalizedLandmarkList GetExpectedLandmarkList(absl::string_view filename) {
|
|||
return expected_landmark_list;
|
||||
}
|
||||
|
||||
ClassificationList GetBlendshapes(absl::string_view filename) {
|
||||
ClassificationList blendshapes;
|
||||
MP_EXPECT_OK(GetTextProto(file::JoinPath("./", kTestDataDirectory, filename),
|
||||
&blendshapes, Defaults()));
|
||||
return blendshapes;
|
||||
}
|
||||
|
||||
// Helper function to construct NormalizeRect proto.
|
||||
NormalizedRect MakeNormRect(float x_center, float y_center, float width,
|
||||
float height, float rotation) {
|
||||
|
@ -185,8 +228,10 @@ NormalizedRect MakeNormRect(float x_center, float y_center, float width,
|
|||
struct SingeFaceTestParams {
|
||||
// The name of this test, for convenience when displaying test results.
|
||||
std::string test_name;
|
||||
// The filename of the model to test.
|
||||
std::string input_model_name;
|
||||
// The filename of landmarks model name.
|
||||
std::string landmarks_model_name;
|
||||
// The filename of blendshape model name.
|
||||
std::optional<std::string> blendshape_model_name;
|
||||
// The filename of the test image.
|
||||
std::string test_image_name;
|
||||
// RoI on image to detect hands.
|
||||
|
@ -195,15 +240,22 @@ struct SingeFaceTestParams {
|
|||
bool expected_presence;
|
||||
// The expected output landmarks positions.
|
||||
NormalizedLandmarkList expected_landmarks;
|
||||
// The expected output blendshape classification;
|
||||
std::optional<ClassificationList> expected_blendshapes;
|
||||
// The max value difference between expected_positions and detected positions.
|
||||
float landmarks_diff_threshold;
|
||||
// The max value difference between expected blendshapes and actual
|
||||
// blendshapes.
|
||||
float blendshapes_diff_threshold;
|
||||
};
|
||||
|
||||
struct MultiFaceTestParams {
|
||||
// The name of this test, for convenience when displaying test results.
|
||||
std::string test_name;
|
||||
// The filename of the model to test.
|
||||
std::string input_model_name;
|
||||
// The filename of landmarks model name.
|
||||
std::string landmarks_model_name;
|
||||
// The filename of blendshape model name.
|
||||
std::optional<std::string> blendshape_model_name;
|
||||
// The filename of the test image.
|
||||
std::string test_image_name;
|
||||
// RoI on image to detect hands.
|
||||
|
@ -212,8 +264,13 @@ struct MultiFaceTestParams {
|
|||
std::vector<bool> expected_presence;
|
||||
// The expected output landmarks positions.
|
||||
std::optional<std::vector<NormalizedLandmarkList>> expected_landmarks_lists;
|
||||
// The expected output blendshape classification;
|
||||
std::optional<std::vector<ClassificationList>> expected_blendshapes;
|
||||
// The max value difference between expected_positions and detected positions.
|
||||
float landmarks_diff_threshold;
|
||||
// The max value difference between expected blendshapes and actual
|
||||
// blendshapes.
|
||||
float blendshapes_diff_threshold;
|
||||
};
|
||||
|
||||
class SingleFaceLandmarksDetectionTest
|
||||
|
@ -223,8 +280,10 @@ TEST_P(SingleFaceLandmarksDetectionTest, Succeeds) {
|
|||
MP_ASSERT_OK_AND_ASSIGN(
|
||||
Image image, DecodeImageFromFile(JoinPath("./", kTestDataDirectory,
|
||||
GetParam().test_image_name)));
|
||||
MP_ASSERT_OK_AND_ASSIGN(auto task_runner, CreateSingleFaceLandmarksTaskRunner(
|
||||
GetParam().input_model_name));
|
||||
MP_ASSERT_OK_AND_ASSIGN(
|
||||
auto task_runner,
|
||||
CreateSingleFaceLandmarksTaskRunner(GetParam().landmarks_model_name,
|
||||
GetParam().blendshape_model_name));
|
||||
|
||||
auto output_packets = task_runner->Process(
|
||||
{{kImageName, MakePacket<Image>(std::move(image))},
|
||||
|
@ -246,6 +305,15 @@ TEST_P(SingleFaceLandmarksDetectionTest, Succeeds) {
|
|||
Approximately(Partially(EqualsProto(expected_landmarks)),
|
||||
/*margin=*/kAbsMargin,
|
||||
/*fraction=*/GetParam().landmarks_diff_threshold));
|
||||
if (GetParam().expected_blendshapes) {
|
||||
const ClassificationList& actual_blendshapes =
|
||||
(*output_packets)[kBlendshapesName].Get<ClassificationList>();
|
||||
const ClassificationList& expected_blendshapes =
|
||||
*GetParam().expected_blendshapes;
|
||||
EXPECT_THAT(actual_blendshapes,
|
||||
Approximately(EqualsProto(expected_blendshapes),
|
||||
GetParam().blendshapes_diff_threshold));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,8 +324,10 @@ TEST_P(MultiFaceLandmarksDetectionTest, Succeeds) {
|
|||
MP_ASSERT_OK_AND_ASSIGN(
|
||||
Image image, DecodeImageFromFile(JoinPath("./", kTestDataDirectory,
|
||||
GetParam().test_image_name)));
|
||||
MP_ASSERT_OK_AND_ASSIGN(auto task_runner, CreateMultiFaceLandmarksTaskRunner(
|
||||
GetParam().input_model_name));
|
||||
MP_ASSERT_OK_AND_ASSIGN(
|
||||
auto task_runner,
|
||||
CreateMultiFaceLandmarksTaskRunner(GetParam().landmarks_model_name,
|
||||
GetParam().blendshape_model_name));
|
||||
|
||||
auto output_packets = task_runner->Process(
|
||||
{{kImageName, MakePacket<Image>(std::move(image))},
|
||||
|
@ -278,29 +348,63 @@ TEST_P(MultiFaceLandmarksDetectionTest, Succeeds) {
|
|||
/*fraction=*/GetParam().landmarks_diff_threshold),
|
||||
*GetParam().expected_landmarks_lists));
|
||||
}
|
||||
if (GetParam().expected_blendshapes) {
|
||||
const std::vector<ClassificationList>& actual_blendshapes =
|
||||
(*output_packets)[kBlendshapesName]
|
||||
.Get<std::vector<ClassificationList>>();
|
||||
const std::vector<ClassificationList>& expected_blendshapes =
|
||||
*GetParam().expected_blendshapes;
|
||||
EXPECT_THAT(actual_blendshapes,
|
||||
Pointwise(Approximately(EqualsProto(),
|
||||
GetParam().blendshapes_diff_threshold),
|
||||
expected_blendshapes));
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
FaceLandmarksDetectionTest, SingleFaceLandmarksDetectionTest,
|
||||
Values(SingeFaceTestParams{
|
||||
/* test_name= */ "Portrait",
|
||||
/*input_model_name= */ kFaceLandmarksDetectionModel,
|
||||
/* landmarks_model_name= */ kFaceLandmarksDetectionModel,
|
||||
/* blendshape_model_name= */ std::nullopt,
|
||||
/* test_image_name=*/kPortraitImageName,
|
||||
/* norm_rect= */ MakeNormRect(0.4987, 0.2211, 0.2877, 0.2303, 0),
|
||||
/* expected_presence= */ true,
|
||||
/* expected_landmarks= */
|
||||
GetExpectedLandmarkList(kPortraitExpectedFaceLandamrksName),
|
||||
/*landmarks_diff_threshold = */ kFractionDiff},
|
||||
/* expected_blendshapes= */ std::nullopt,
|
||||
/* landmarks_diff_threshold= */ kFractionDiff,
|
||||
/* blendshapes_diff_threshold= */ kBlendshapesDiffMargin},
|
||||
SingeFaceTestParams{
|
||||
/* test_name= */ "PortraitWithAttention",
|
||||
/*input_model_name= */ kFaceLandmarksDetectionWithAttentionModel,
|
||||
/* landmarks_model_name= */
|
||||
kFaceLandmarksDetectionWithAttentionModel,
|
||||
/* blendshape_model_name= */ std::nullopt,
|
||||
/* test_image_name= */ kPortraitImageName,
|
||||
/* norm_rect= */ MakeNormRect(0.4987, 0.2211, 0.2877, 0.2303, 0),
|
||||
/* expected_presence= */ true,
|
||||
/* expected_landmarks= */
|
||||
GetExpectedLandmarkList(
|
||||
kPortraitExpectedFaceLandamrksWithAttentionName),
|
||||
/*landmarks_diff_threshold = */ kFractionDiff}),
|
||||
/* expected_blendshapes= */ std::nullopt,
|
||||
/* landmarks_diff_threshold= */ kFractionDiff,
|
||||
/* blendshapes_diff_threshold= */ kBlendshapesDiffMargin},
|
||||
SingeFaceTestParams{
|
||||
/* test_name= */ "PortraitWithAttentionWithBlendshapes",
|
||||
/* landmarks_model_name= */
|
||||
kFaceLandmarksDetectionWithAttentionModel,
|
||||
/* blendshape_model_name= */ kFaceBlendshapesModel,
|
||||
/* test_image_name= */ kPortraitImageName,
|
||||
/* norm_rect= */ MakeNormRect(0.4987, 0.2211, 0.2877, 0.2303, 0),
|
||||
/* expected_presence= */ true,
|
||||
/* expected_landmarks= */
|
||||
GetExpectedLandmarkList(
|
||||
kPortraitExpectedFaceLandamrksWithAttentionName),
|
||||
/* expected_blendshapes= */
|
||||
GetBlendshapes(kPortraitExpectedBlendshapesName),
|
||||
/* landmarks_diff_threshold= */ kFractionDiff,
|
||||
/* blendshapes_diff_threshold= */ kBlendshapesDiffMargin}),
|
||||
|
||||
[](const TestParamInfo<SingleFaceLandmarksDetectionTest::ParamType>& info) {
|
||||
return info.param.test_name;
|
||||
});
|
||||
|
@ -310,31 +414,57 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
Values(
|
||||
MultiFaceTestParams{
|
||||
/* test_name= */ "Portrait",
|
||||
/*input_model_name= */ kFaceLandmarksDetectionModel,
|
||||
/* landmarks_model_name= */ kFaceLandmarksDetectionModel,
|
||||
/* blendshape_model_name= */ std::nullopt,
|
||||
/* test_image_name= */ kPortraitImageName,
|
||||
/* norm_rects= */ {MakeNormRect(0.4987, 0.2211, 0.2877, 0.2303, 0)},
|
||||
/* expected_presence= */ {true},
|
||||
/* expected_landmarks_list= */
|
||||
{{GetExpectedLandmarkList(kPortraitExpectedFaceLandamrksName)}},
|
||||
/*landmarks_diff_threshold = */ kFractionDiff},
|
||||
/* expected_blendshapes= */ std::nullopt,
|
||||
/* landmarks_diff_threshold= */ kFractionDiff,
|
||||
/* blendshapes_diff_threshold= */ kBlendshapesDiffMargin},
|
||||
MultiFaceTestParams{
|
||||
/* test_name= */ "PortraitWithAttention",
|
||||
/*input_model_name= */ kFaceLandmarksDetectionWithAttentionModel,
|
||||
/* landmarks_model_name= */
|
||||
kFaceLandmarksDetectionWithAttentionModel,
|
||||
/* blendshape_model_name= */ std::nullopt,
|
||||
/* test_image_name= */ kPortraitImageName,
|
||||
/* norm_rects= */ {MakeNormRect(0.4987, 0.2211, 0.2877, 0.2303, 0)},
|
||||
/* expected_presence= */ {true},
|
||||
/* expected_landmarks_list= */
|
||||
{{GetExpectedLandmarkList(
|
||||
kPortraitExpectedFaceLandamrksWithAttentionName)}},
|
||||
/*landmarks_diff_threshold = */ kFractionDiff},
|
||||
/* expected_blendshapes= */ std::nullopt,
|
||||
/* landmarks_diff_threshold= */ kFractionDiff,
|
||||
/* blendshapes_diff_threshold= */ kBlendshapesDiffMargin},
|
||||
MultiFaceTestParams{
|
||||
/* test_name= */ "PortraitWithAttentionWithBlendshapes",
|
||||
/* landmarks_model_name= */
|
||||
kFaceLandmarksDetectionWithAttentionModel,
|
||||
/* blendshape_model_name= */ kFaceBlendshapesModel,
|
||||
/* test_image_name= */ kPortraitImageName,
|
||||
/* norm_rects= */ {MakeNormRect(0.4987, 0.2211, 0.2877, 0.2303, 0)},
|
||||
/* expected_presence= */ {true},
|
||||
/* expected_landmarks_list= */
|
||||
{{GetExpectedLandmarkList(
|
||||
kPortraitExpectedFaceLandamrksWithAttentionName)}},
|
||||
/* expected_blendshapes= */
|
||||
{{GetBlendshapes(kPortraitExpectedBlendshapesName)}},
|
||||
/* landmarks_diff_threshold= */ kFractionDiff,
|
||||
/* blendshapes_diff_threshold= */ kBlendshapesDiffMargin},
|
||||
MultiFaceTestParams{
|
||||
/* test_name= */ "NoFace",
|
||||
/*input_model_name= */ kFaceLandmarksDetectionModel,
|
||||
/* landmarks_model_name= */
|
||||
kFaceLandmarksDetectionModel,
|
||||
/* blendshape_model_name= */ std::nullopt,
|
||||
/* test_image_name= */ kCatImageName,
|
||||
/* norm_rects= */ {MakeNormRect(0.5, 0.5, 1.0, 1.0, 0)},
|
||||
/* expected_presence= */ {false},
|
||||
/* expected_landmarks_list= */ std::nullopt,
|
||||
/*landmarks_diff_threshold = */ kFractionDiff}),
|
||||
/* expected_blendshapes= */ std::nullopt,
|
||||
/*landmarks_diff_threshold= */ kFractionDiff,
|
||||
/*blendshapes_diff_threshold= */ kBlendshapesDiffMargin}),
|
||||
[](const TestParamInfo<MultiFaceLandmarksDetectionTest::ParamType>& info) {
|
||||
return info.param.test_name;
|
||||
});
|
||||
|
|
|
@ -34,6 +34,7 @@ mediapipe_proto_library(
|
|||
name = "face_landmarks_detector_graph_options_proto",
|
||||
srcs = ["face_landmarks_detector_graph_options.proto"],
|
||||
deps = [
|
||||
":face_blendshapes_graph_options_proto",
|
||||
"//mediapipe/framework:calculator_options_proto",
|
||||
"//mediapipe/framework:calculator_proto",
|
||||
"//mediapipe/tasks/cc/core/proto:base_options_proto",
|
||||
|
|
|
@ -20,6 +20,7 @@ package mediapipe.tasks.vision.face_landmarker.proto;
|
|||
import "mediapipe/framework/calculator.proto";
|
||||
import "mediapipe/framework/calculator_options.proto";
|
||||
import "mediapipe/tasks/cc/core/proto/base_options.proto";
|
||||
import "mediapipe/tasks/cc/vision/face_landmarker/proto/face_blendshapes_graph_options.proto";
|
||||
|
||||
option java_package = "com.google.mediapipe.tasks.vision.facelandmarker.proto";
|
||||
option java_outer_classname = "FaceLandmarksDetectorGraphOptionsProto";
|
||||
|
@ -35,4 +36,8 @@ message FaceLandmarksDetectorGraphOptions {
|
|||
// Minimum confidence value ([0.0, 1.0]) for confidence score to be considered
|
||||
// successfully detecting a face in the image.
|
||||
optional float min_detection_confidence = 2 [default = 0.5];
|
||||
|
||||
// Optional options for FaceBlendshapeGraph. If this options is set, the
|
||||
// FaceLandmarksDetectorGraph would output the face blendshapes.
|
||||
optional FaceBlendshapesGraphOptions face_blendshapes_graph_options = 3;
|
||||
}
|
||||
|
|
262
mediapipe/tasks/testdata/vision/portrait_expected_blendshapes_with_attention.pbtxt
vendored
Normal file
262
mediapipe/tasks/testdata/vision/portrait_expected_blendshapes_with_attention.pbtxt
vendored
Normal file
|
@ -0,0 +1,262 @@
|
|||
# proto-file: mediapipe/framework/formats/classification.proto
|
||||
# proto-message: ClassificationList
|
||||
classification {
|
||||
index: 0
|
||||
score: 4.9559007e-06
|
||||
label: "_neutral"
|
||||
}
|
||||
classification {
|
||||
index: 1
|
||||
score: 0.22943014
|
||||
label: "browDownLeft"
|
||||
}
|
||||
classification {
|
||||
index: 2
|
||||
score: 0.22297752
|
||||
label: "browDownRight"
|
||||
}
|
||||
classification {
|
||||
index: 3
|
||||
score: 0.015948873
|
||||
label: "browInnerUp"
|
||||
}
|
||||
classification {
|
||||
index: 4
|
||||
score: 0.006946607
|
||||
label: "browOuterUpLeft"
|
||||
}
|
||||
classification {
|
||||
index: 5
|
||||
score: 0.0070318673
|
||||
label: "browOuterUpRight"
|
||||
}
|
||||
classification {
|
||||
index: 6
|
||||
score: 0.0013679645
|
||||
label: "cheekPuff"
|
||||
}
|
||||
classification {
|
||||
index: 7
|
||||
score: 7.1003383e-06
|
||||
label: "cheekSquintLeft"
|
||||
}
|
||||
classification {
|
||||
index: 8
|
||||
score: 5.78299e-06
|
||||
label: "cheekSquintRight"
|
||||
}
|
||||
classification {
|
||||
index: 9
|
||||
score: 0.20132238
|
||||
label: "eyeBlinkLeft"
|
||||
}
|
||||
classification {
|
||||
index: 10
|
||||
score: 0.16521452
|
||||
label: "eyeBlinkRight"
|
||||
}
|
||||
classification {
|
||||
index: 11
|
||||
score: 0.03764786
|
||||
label: "eyeLookDownLeft"
|
||||
}
|
||||
classification {
|
||||
index: 12
|
||||
score: 0.04828824
|
||||
label: "eyeLookDownRight"
|
||||
}
|
||||
classification {
|
||||
index: 13
|
||||
score: 0.016539993
|
||||
label: "eyeLookInLeft"
|
||||
}
|
||||
classification {
|
||||
index: 14
|
||||
score: 0.20026363
|
||||
label: "eyeLookInRight"
|
||||
}
|
||||
classification {
|
||||
index: 15
|
||||
score: 0.21363346
|
||||
label: "eyeLookOutLeft"
|
||||
}
|
||||
classification {
|
||||
index: 16
|
||||
score: 0.024430025
|
||||
label: "eyeLookOutRight"
|
||||
}
|
||||
classification {
|
||||
index: 17
|
||||
score: 0.30147508
|
||||
label: "eyeLookUpLeft"
|
||||
}
|
||||
classification {
|
||||
index: 18
|
||||
score: 0.28701693
|
||||
label: "eyeLookUpRight"
|
||||
}
|
||||
classification {
|
||||
index: 19
|
||||
score: 0.67143106
|
||||
label: "eyeSquintLeft"
|
||||
}
|
||||
classification {
|
||||
index: 20
|
||||
score: 0.5306328
|
||||
label: "eyeSquintRight"
|
||||
}
|
||||
classification {
|
||||
index: 21
|
||||
score: 0.0041342233
|
||||
label: "eyeWideLeft"
|
||||
}
|
||||
classification {
|
||||
index: 22
|
||||
score: 0.005231879
|
||||
label: "eyeWideRight"
|
||||
}
|
||||
classification {
|
||||
index: 23
|
||||
score: 0.009427094
|
||||
label: "jawForward"
|
||||
}
|
||||
classification {
|
||||
index: 24
|
||||
score: 0.0015789346
|
||||
label: "jawLeft"
|
||||
}
|
||||
classification {
|
||||
index: 25
|
||||
score: 0.073719256
|
||||
label: "jawOpen"
|
||||
}
|
||||
classification {
|
||||
index: 26
|
||||
score: 0.00046979196
|
||||
label: "jawRight"
|
||||
}
|
||||
classification {
|
||||
index: 27
|
||||
score: 0.0011400756
|
||||
label: "mouthClose"
|
||||
}
|
||||
classification {
|
||||
index: 28
|
||||
score: 0.0060502808
|
||||
label: "mouthDimpleLeft"
|
||||
}
|
||||
classification {
|
||||
index: 29
|
||||
score: 0.013351685
|
||||
label: "mouthDimpleRight"
|
||||
}
|
||||
classification {
|
||||
index: 30
|
||||
score: 0.09859665
|
||||
label: "mouthFrownLeft"
|
||||
}
|
||||
classification {
|
||||
index: 31
|
||||
score: 0.08897466
|
||||
label: "mouthFrownRight"
|
||||
}
|
||||
classification {
|
||||
index: 32
|
||||
score: 0.0020718675
|
||||
label: "mouthFunnel"
|
||||
}
|
||||
classification {
|
||||
index: 33
|
||||
score: 6.42887e-06
|
||||
label: "mouthLeft"
|
||||
}
|
||||
classification {
|
||||
index: 34
|
||||
score: 0.68950605
|
||||
label: "mouthLowerDownLeft"
|
||||
}
|
||||
classification {
|
||||
index: 35
|
||||
score: 0.7864029
|
||||
label: "mouthLowerDownRight"
|
||||
}
|
||||
classification {
|
||||
index: 36
|
||||
score: 0.056456964
|
||||
label: "mouthPressLeft"
|
||||
}
|
||||
classification {
|
||||
index: 37
|
||||
score: 0.037348792
|
||||
label: "mouthPressRight"
|
||||
}
|
||||
classification {
|
||||
index: 38
|
||||
score: 0.00067001814
|
||||
label: "mouthPucker"
|
||||
}
|
||||
classification {
|
||||
index: 39
|
||||
score: 0.005189785
|
||||
label: "mouthRight"
|
||||
}
|
||||
classification {
|
||||
index: 40
|
||||
score: 0.018723497
|
||||
label: "mouthRollLower"
|
||||
}
|
||||
classification {
|
||||
index: 41
|
||||
score: 0.052819636
|
||||
label: "mouthRollUpper"
|
||||
}
|
||||
classification {
|
||||
index: 42
|
||||
score: 0.0033772716
|
||||
label: "mouthShrugLower"
|
||||
}
|
||||
classification {
|
||||
index: 43
|
||||
score: 0.0031609535
|
||||
label: "mouthShrugUpper"
|
||||
}
|
||||
classification {
|
||||
index: 44
|
||||
score: 0.49639142
|
||||
label: "mouthSmileLeft"
|
||||
}
|
||||
classification {
|
||||
index: 45
|
||||
score: 0.4014515
|
||||
label: "mouthSmileRight"
|
||||
}
|
||||
classification {
|
||||
index: 46
|
||||
score: 0.5825701
|
||||
label: "mouthStretchLeft"
|
||||
}
|
||||
classification {
|
||||
index: 47
|
||||
score: 0.73058575
|
||||
label: "mouthStretchRight"
|
||||
}
|
||||
classification {
|
||||
index: 48
|
||||
score: 0.13561466
|
||||
label: "mouthUpperUpLeft"
|
||||
}
|
||||
classification {
|
||||
index: 49
|
||||
score: 0.20078722
|
||||
label: "mouthUpperUpRight"
|
||||
}
|
||||
classification {
|
||||
index: 50
|
||||
score: 3.3396598e-06
|
||||
label: "noseSneerLeft"
|
||||
}
|
||||
classification {
|
||||
index: 51
|
||||
score: 1.3096546e-05
|
||||
label: "noseSneerRight"
|
||||
}
|
Loading…
Reference in New Issue
Block a user