diff --git a/mediapipe/tasks/cc/vision/face_landmarker/BUILD b/mediapipe/tasks/cc/vision/face_landmarker/BUILD new file mode 100644 index 000000000..50d16751b --- /dev/null +++ b/mediapipe/tasks/cc/vision/face_landmarker/BUILD @@ -0,0 +1,60 @@ +# Copyright 2023 The MediaPipe Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +package(default_visibility = [ + "//mediapipe/tasks:internal", +]) + +licenses(["notice"]) + +cc_library( + name = "face_landmarks_detector_graph", + srcs = ["face_landmarks_detector_graph.cc"], + deps = [ + "//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:inference_calculator", + "//mediapipe/calculators/tensor:tensors_to_floats_calculator", + "//mediapipe/calculators/tensor:tensors_to_floats_calculator_cc_proto", + "//mediapipe/calculators/tensor:tensors_to_landmarks_calculator", + "//mediapipe/calculators/tensor:tensors_to_landmarks_calculator_cc_proto", + "//mediapipe/calculators/util:detections_to_rects_calculator", + "//mediapipe/calculators/util:detections_to_rects_calculator_cc_proto", + "//mediapipe/calculators/util:landmark_letterbox_removal_calculator", + "//mediapipe/calculators/util:landmark_projection_calculator", + "//mediapipe/calculators/util:landmarks_to_detection_calculator", + "//mediapipe/calculators/util:rect_transformation_calculator", + "//mediapipe/calculators/util:rect_transformation_calculator_cc_proto", + "//mediapipe/calculators/util:thresholding_calculator", + "//mediapipe/calculators/util:thresholding_calculator_cc_proto", + "//mediapipe/framework/api2:builder", + "//mediapipe/framework/api2:port", + "//mediapipe/framework/formats:image", + "//mediapipe/framework/formats:landmark_cc_proto", + "//mediapipe/framework/formats:rect_cc_proto", + "//mediapipe/framework/formats:tensor", + "//mediapipe/tasks/cc:common", + "//mediapipe/tasks/cc/components/processors:image_preprocessing_graph", + "//mediapipe/tasks/cc/components/utils:gate", + "//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_landmarks_detector_graph_options_cc_proto", + "//mediapipe/tasks/cc/vision/utils:image_tensor_specs", + ], + alwayslink = 1, +) diff --git a/mediapipe/tasks/cc/vision/face_landmarker/face_landmarks_detector_graph.cc b/mediapipe/tasks/cc/vision/face_landmarker/face_landmarks_detector_graph.cc new file mode 100644 index 000000000..7a2549b92 --- /dev/null +++ b/mediapipe/tasks/cc/vision/face_landmarker/face_landmarks_detector_graph.cc @@ -0,0 +1,489 @@ +/* Copyright 2023 The MediaPipe Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include +#include +#include +#include + +#include "mediapipe/calculators/core/split_vector_calculator.pb.h" +#include "mediapipe/calculators/tensor/tensors_to_floats_calculator.pb.h" +#include "mediapipe/calculators/tensor/tensors_to_landmarks_calculator.pb.h" +#include "mediapipe/calculators/util/detections_to_rects_calculator.pb.h" +#include "mediapipe/calculators/util/rect_transformation_calculator.pb.h" +#include "mediapipe/calculators/util/thresholding_calculator.pb.h" +#include "mediapipe/framework/api2/builder.h" +#include "mediapipe/framework/api2/port.h" +#include "mediapipe/framework/formats/image.h" +#include "mediapipe/framework/formats/landmark.pb.h" +#include "mediapipe/framework/formats/rect.pb.h" +#include "mediapipe/framework/formats/tensor.h" +#include "mediapipe/tasks/cc/common.h" +#include "mediapipe/tasks/cc/components/processors/image_preprocessing_graph.h" +#include "mediapipe/tasks/cc/components/utils/gate.h" +#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_landmarks_detector_graph_options.pb.h" +#include "mediapipe/tasks/cc/vision/utils/image_tensor_specs.h" + +namespace mediapipe { +namespace tasks { +namespace vision { +namespace face_landmarker { + +namespace { + +using ::mediapipe::NormalizedRect; +using ::mediapipe::api2::Input; +using ::mediapipe::api2::Output; +using ::mediapipe::api2::builder::Graph; +using ::mediapipe::api2::builder::Stream; +using ::mediapipe::tasks::components::utils::AllowIf; + +constexpr char kImageTag[] = "IMAGE"; +constexpr char kNormRectTag[] = "NORM_RECT"; +constexpr char kFaceRectNextFrameTag[] = "FACE_RECT_NEXT_FRAME"; +constexpr char kFaceRectsNextFrameTag[] = "FACE_RECTS_NEXT_FRAME"; +constexpr char kPresenceTag[] = "PRESENCE"; +constexpr char kPresenceScoreTag[] = "PRESENCE_SCORE"; +constexpr char kImageSizeTag[] = "IMAGE_SIZE"; +constexpr char kTensorsTag[] = "TENSORS"; +constexpr char kLandmarksTag[] = "LANDMARKS"; +constexpr char kNormLandmarksTag[] = "NORM_LANDMARKS"; +constexpr char kFloatTag[] = "FLOAT"; +constexpr char kFlagTag[] = "FLAG"; +constexpr char kLetterboxPaddingTag[] = "LETTERBOX_PADDING"; +constexpr char kCloneTag[] = "CLONE"; +constexpr char kIterableTag[] = "ITERABLE"; +constexpr char kBatchEndTag[] = "BATCH_END"; +constexpr char kItemTag[] = "ITEM"; +constexpr char kDetectionTag[] = "DETECTION"; + +constexpr int kLandmarksNum = 468; +constexpr int kModelOutputTensorSplitNum = 2; + +struct SingleFaceLandmarksOutputs { + Stream landmarks; + Stream rect_next_frame; + Stream presence; + Stream presence_score; +}; + +struct MultiFaceLandmarksOutputs { + Stream> landmarks_lists; + Stream> rects_next_frame; + Stream> presences; + Stream> presence_scores; +}; + +absl::Status SanityCheckOptions( + const proto::FaceLandmarksDetectorGraphOptions& options) { + if (options.min_detection_confidence() < 0 || + options.min_detection_confidence() > 1) { + return CreateStatusWithPayload(absl::StatusCode::kInvalidArgument, + "Invalid `min_detection_confidence` option: " + "value must be in the range [0.0, 1.0]", + MediaPipeTasksStatus::kInvalidArgumentError); + } + return absl::OkStatus(); +} + +// Split face landmark detection model output tensor into two parts, +// representing landmarks and face presence scores. +void ConfigureSplitTensorVectorCalculator( + mediapipe::SplitVectorCalculatorOptions* options) { + for (int i = 0; i < kModelOutputTensorSplitNum; ++i) { + auto* range = options->add_ranges(); + range->set_begin(i); + range->set_end(i + 1); + } +} + +void ConfigureTensorsToLandmarksCalculator( + const ImageTensorSpecs& input_image_tensor_spec, + mediapipe::TensorsToLandmarksCalculatorOptions* options) { + options->set_num_landmarks(kLandmarksNum); + options->set_input_image_height(input_image_tensor_spec.image_height); + options->set_input_image_width(input_image_tensor_spec.image_width); +} + +void ConfigureFaceDetectionsToRectsCalculator( + mediapipe::DetectionsToRectsCalculatorOptions* options) { + // Left side of left eye. + options->set_rotation_vector_start_keypoint_index(33); + // Right side of right eye. + options->set_rotation_vector_end_keypoint_index(263); + options->set_rotation_vector_target_angle_degrees(0); +} + +void ConfigureFaceRectTransformationCalculator( + mediapipe::RectTransformationCalculatorOptions* options) { + // TODO: make rect transformation configurable, e.g. from + // Metadata or configuration options. + options->set_scale_x(1.5f); + options->set_scale_y(1.5f); + options->set_square_long(true); +} + +} // namespace + +// A "mediapipe.tasks.vision.face_landmarker.SingleFaceLandmarksDetectorGraph" +// performs face landmarks detection. +// +// Inputs: +// IMAGE - Image +// Image to perform detection on. +// NORM_RECT - NormalizedRect @Optional +// Rect enclosing the RoI to perform detection on. If not set, the detection +// RoI is the whole image. +// +// +// Outputs: +// NORM_LANDMARKS: - NormalizedLandmarkList +// Detected face landmarks. +// FACE_RECT_NEXT_FRAME - NormalizedRect +// The predicted Rect enclosing the face RoI for landmark detection on the +// next frame. +// PRESENCE - bool +// Boolean value indicates whether the face is present. +// PRESENCE_SCORE - float +// Float value indicates the probability that the face is present. +// +// Example: +// node { +// calculator: +// "mediapipe.tasks.vision.face_landmarker.SingleFaceLandmarksDetectorGraph" +// input_stream: "IMAGE:input_image" +// input_stream: "FACE_RECT:face_rect" +// output_stream: "LANDMARKS:face_landmarks" +// output_stream: "FACE_RECT_NEXT_FRAME:face_rect_next_frame" +// output_stream: "PRESENCE:presence" +// output_stream: "PRESENCE_SCORE:presence_score" +// options { +// [mediapipe.tasks.vision.face_landmarker.proto.FaceLandmarksDetectorGraphOptions.ext] +// { +// base_options { +// model_asset { +// file_name: "face_landmark_lite.tflite" +// } +// } +// min_detection_confidence: 0.5 +// } +// } +// } +class SingleFaceLandmarksDetectorGraph : public core::ModelTaskGraph { + public: + absl::StatusOr GetConfig( + SubgraphContext* sc) override { + ASSIGN_OR_RETURN( + const auto* model_resources, + CreateModelResources(sc)); + Graph graph; + ASSIGN_OR_RETURN( + auto outs, + BuildSingleFaceLandmarksDetectorGraph( + sc->Options(), + *model_resources, graph[Input(kImageTag)], + graph[Input::Optional(kNormRectTag)], graph)); + outs.landmarks >> + graph.Out(kNormLandmarksTag).Cast(); + outs.rect_next_frame >> + graph.Out(kFaceRectNextFrameTag).Cast(); + outs.presence >> graph.Out(kPresenceTag).Cast(); + outs.presence_score >> graph.Out(kPresenceScoreTag).Cast(); + return graph.GetConfig(); + } + + private: + // Adds a mediapipe face landmark detection graph into the provided + // builder::Graph instance. + // + // subgraph_options: the mediapipe tasks module + // FaceLandmarksDetectorGraphOptions. + // model_resources: the ModelSources object initialized from a face landmark + // detection model file with model metadata. + // image_in: (mediapipe::Image) stream to run face landmark detection on. + // face_rect: (NormalizedRect) stream to run on the RoI of image. + // graph: the mediapipe graph instance to be updated. + absl::StatusOr + BuildSingleFaceLandmarksDetectorGraph( + const proto::FaceLandmarksDetectorGraphOptions& subgraph_options, + const core::ModelResources& model_resources, Stream image_in, + Stream face_rect, Graph& graph) { + MP_RETURN_IF_ERROR(SanityCheckOptions(subgraph_options)); + + auto& preprocessing = graph.AddNode( + "mediapipe.tasks.components.processors.ImagePreprocessingGraph"); + bool use_gpu = + components::processors::DetermineImagePreprocessingGpuBackend( + subgraph_options.base_options().acceleration()); + MP_RETURN_IF_ERROR(components::processors::ConfigureImagePreprocessingGraph( + model_resources, use_gpu, + &preprocessing.GetOptions())); + image_in >> preprocessing.In(kImageTag); + face_rect >> preprocessing.In(kNormRectTag); + auto image_size = preprocessing.Out(kImageSizeTag); + auto letterbox_padding = preprocessing.Out(kLetterboxPaddingTag); + auto input_tensors = preprocessing.Out(kTensorsTag); + + auto& inference = AddInference( + model_resources, subgraph_options.base_options().acceleration(), graph); + input_tensors >> inference.In(kTensorsTag); + auto output_tensors = inference.Out(kTensorsTag); + + // Split model output tensors to multiple streams. + auto& split_tensors_vector = graph.AddNode("SplitTensorVectorCalculator"); + ConfigureSplitTensorVectorCalculator( + &split_tensors_vector + .GetOptions()); + output_tensors >> split_tensors_vector.In(""); + auto landmark_tensors = split_tensors_vector.Out(0); + auto presence_flag_tensors = split_tensors_vector.Out(1); + + // Decodes the landmark tensors into a list of landmarks, where the landmark + // coordinates are normalized by the size of the input image to the model. + auto& tensors_to_landmarks = graph.AddNode("TensorsToLandmarksCalculator"); + ASSIGN_OR_RETURN(auto image_tensor_specs, + vision::BuildInputImageTensorSpecs(model_resources)); + ConfigureTensorsToLandmarksCalculator( + image_tensor_specs, + &tensors_to_landmarks + .GetOptions()); + landmark_tensors >> tensors_to_landmarks.In(kTensorsTag); + auto landmarks = tensors_to_landmarks.Out(kNormLandmarksTag); + + // Converts the presence flag tensor into a float that represents the + // confidence score of face presence. + auto& tensors_to_presence = graph.AddNode("TensorsToFloatsCalculator"); + tensors_to_presence + .GetOptions() + .set_activation(mediapipe::TensorsToFloatsCalculatorOptions::SIGMOID); + presence_flag_tensors >> tensors_to_presence.In(kTensorsTag); + auto presence_score = tensors_to_presence.Out(kFloatTag).Cast(); + + // Applies a threshold to the confidence score to determine whether a + // face is present. + auto& presence_thresholding = graph.AddNode("ThresholdingCalculator"); + presence_thresholding.GetOptions() + .set_threshold(subgraph_options.min_detection_confidence()); + presence_score >> presence_thresholding.In(kFloatTag); + auto presence = presence_thresholding.Out(kFlagTag).Cast(); + + // Adjusts landmarks (already normalized to [0.f, 1.f]) on the letterboxed + // face image (after image transformation with the FIT scale mode) to the + // corresponding locations on the same image with the letterbox removed + // (face image before image transformation). + auto& landmark_letterbox_removal = + graph.AddNode("LandmarkLetterboxRemovalCalculator"); + letterbox_padding >> landmark_letterbox_removal.In(kLetterboxPaddingTag); + landmarks >> landmark_letterbox_removal.In(kLandmarksTag); + auto landmarks_letterbox_removed = + landmark_letterbox_removal.Out(kLandmarksTag); + + // Projects the landmarks from the cropped face image to the corresponding + // locations on the full image before cropping (input to the graph). + auto& landmark_projection = graph.AddNode("LandmarkProjectionCalculator"); + landmarks_letterbox_removed >> landmark_projection.In(kNormLandmarksTag); + face_rect >> landmark_projection.In(kNormRectTag); + auto projected_landmarks = AllowIf( + landmark_projection[Output(kNormLandmarksTag)], + presence, graph); + + // Converts the face landmarks into a rectangle (normalized by image size) + // that encloses the face. + auto& landmarks_to_detection = + graph.AddNode("LandmarksToDetectionCalculator"); + projected_landmarks >> landmarks_to_detection.In(kNormLandmarksTag); + auto face_landmarks_detection = landmarks_to_detection.Out(kDetectionTag); + auto& detection_to_rect = graph.AddNode("DetectionsToRectsCalculator"); + ConfigureFaceDetectionsToRectsCalculator( + &detection_to_rect + .GetOptions()); + face_landmarks_detection >> detection_to_rect.In(kDetectionTag); + image_size >> detection_to_rect.In(kImageSizeTag); + auto face_landmarks_rect = detection_to_rect.Out(kNormRectTag); + + // Expands the face rectangle so that in the next video frame it's likely to + // still contain the face even with some motion. + auto& face_rect_transformation = + graph.AddNode("RectTransformationCalculator"); + ConfigureFaceRectTransformationCalculator( + &face_rect_transformation + .GetOptions()); + image_size >> face_rect_transformation.In(kImageSizeTag); + face_landmarks_rect >> face_rect_transformation.In(kNormRectTag); + auto face_rect_next_frame = + AllowIf(face_rect_transformation.Out("").Cast(), + presence, graph); + return {{ + /* landmarks= */ projected_landmarks, + /* rect_next_frame= */ face_rect_next_frame, + /* presence= */ presence, + /* presence_score= */ presence_score, + }}; + } +}; + +// clang-format off +REGISTER_MEDIAPIPE_GRAPH( + ::mediapipe::tasks::vision::face_landmarker::SingleFaceLandmarksDetectorGraph); // NOLINT +// clang-format on + +// A "mediapipe.tasks.vision.face_landmarker.MultiFaceLandmarksDetectorGraph" +// performs multi face landmark detection. +// - Accepts an input image and a vector of face rect RoIs to detect the +// multiple face landmarks enclosed by the RoIs. Output vectors of +// face landmarks related results, where each element in the vectors +// corrresponds to the result of the same face. +// +// Inputs: +// IMAGE - Image +// Image to perform detection on. +// NORM_RECT - std::vector +// A vector of multiple norm rects enclosing the face RoI to perform +// landmarks detection on. +// +// +// Outputs: +// LANDMARKS: - std::vector +// Vector of detected face landmarks. +// FACE_RECTS_NEXT_FRAME - std::vector +// Vector of the predicted rects enclosing the same face RoI for landmark +// detection on the next frame. +// PRESENCE - std::vector +// Vector of boolean value indicates whether the face is present. +// PRESENCE_SCORE - std::vector +// Vector of float value indicates the probability that the face is present. +// +// Example: +// node { +// calculator: +// "mediapipe.tasks.vision.face_landmarker.MultiFaceLandmarksDetectorGraph" +// input_stream: "IMAGE:input_image" +// input_stream: "NORM_RECT:norm_rect" +// output_stream: "LANDMARKS:landmarks" +// output_stream: "FACE_RECTS_NEXT_FRAME:face_rects_next_frame" +// output_stream: "PRESENCE:presence" +// output_stream: "PRESENCE_SCORE:presence_score" +// options { +// [mediapipe.tasks.vision.face_landmarker.proto.FaceLandmarksDetectorGraphOptions.ext] +// { +// base_options { +// model_asset { +// file_name: "face_landmark_lite.tflite" +// } +// } +// min_detection_confidence: 0.5 +// } +// } +// } +class MultiFaceLandmarksDetectorGraph : public core::ModelTaskGraph { + public: + absl::StatusOr GetConfig( + SubgraphContext* sc) override { + Graph graph; + ASSIGN_OR_RETURN( + auto outs, + BuildFaceLandmarksDetectorGraph( + sc->Options(), + graph[Input(kImageTag)], + graph[Input>(kNormRectTag)], graph)); + outs.landmarks_lists >> graph.Out(kNormLandmarksTag) + .Cast>(); + outs.rects_next_frame >> + graph.Out(kFaceRectsNextFrameTag).Cast>(); + outs.presences >> graph.Out(kPresenceTag).Cast>(); + outs.presence_scores >> + graph.Out(kPresenceScoreTag).Cast>(); + + return graph.GetConfig(); + } + + private: + absl::StatusOr BuildFaceLandmarksDetectorGraph( + const proto::FaceLandmarksDetectorGraphOptions& subgraph_options, + Stream image_in, + Stream> multi_face_rects, Graph& graph) { + auto& face_landmark_subgraph = graph.AddNode( + "mediapipe.tasks.vision.face_landmarker." + "SingleFaceLandmarksDetectorGraph"); + face_landmark_subgraph + .GetOptions() + .CopyFrom(subgraph_options); + + auto& begin_loop_multi_face_rects = + graph.AddNode("BeginLoopNormalizedRectCalculator"); + + image_in >> begin_loop_multi_face_rects.In(kCloneTag); + multi_face_rects >> begin_loop_multi_face_rects.In(kIterableTag); + auto batch_end = begin_loop_multi_face_rects.Out(kBatchEndTag); + auto image = begin_loop_multi_face_rects.Out(kCloneTag); + auto face_rect = begin_loop_multi_face_rects.Out(kItemTag); + + image >> face_landmark_subgraph.In(kImageTag); + face_rect >> face_landmark_subgraph.In(kNormRectTag); + auto presence = face_landmark_subgraph.Out(kPresenceTag); + auto presence_score = face_landmark_subgraph.Out(kPresenceScoreTag); + auto face_rect_next_frame = + face_landmark_subgraph.Out(kFaceRectNextFrameTag); + auto landmarks = face_landmark_subgraph.Out(kNormLandmarksTag); + + auto& end_loop_presence = graph.AddNode("EndLoopBooleanCalculator"); + batch_end >> end_loop_presence.In(kBatchEndTag); + presence >> end_loop_presence.In(kItemTag); + auto presences = + end_loop_presence.Out(kIterableTag).Cast>(); + + auto& end_loop_presence_score = graph.AddNode("EndLoopFloatCalculator"); + batch_end >> end_loop_presence_score.In(kBatchEndTag); + presence_score >> end_loop_presence_score.In(kItemTag); + auto presence_scores = + end_loop_presence_score.Out(kIterableTag).Cast>(); + + auto& end_loop_landmarks = + graph.AddNode("EndLoopNormalizedLandmarkListVectorCalculator"); + batch_end >> end_loop_landmarks.In(kBatchEndTag); + landmarks >> end_loop_landmarks.In(kItemTag); + auto landmark_lists = end_loop_landmarks.Out(kIterableTag) + .Cast>(); + + auto& end_loop_rects_next_frame = + graph.AddNode("EndLoopNormalizedRectCalculator"); + batch_end >> end_loop_rects_next_frame.In(kBatchEndTag); + face_rect_next_frame >> end_loop_rects_next_frame.In(kItemTag); + auto face_rects_next_frame = end_loop_rects_next_frame.Out(kIterableTag) + .Cast>(); + + return {{ + /* landmarks_lists= */ landmark_lists, + /* face_rects_next_frame= */ face_rects_next_frame, + /* presences= */ presences, + /* presence_scores= */ presence_scores, + }}; + } +}; + +// clang-format off +REGISTER_MEDIAPIPE_GRAPH( + ::mediapipe::tasks::vision::face_landmarker::MultiFaceLandmarksDetectorGraph); + // NOLINT +// clang-format on + +} // namespace face_landmarker +} // namespace vision +} // namespace tasks +} // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/face_landmarker/face_landmarks_detector_graph_test.cc b/mediapipe/tasks/cc/vision/face_landmarker/face_landmarks_detector_graph_test.cc new file mode 100644 index 000000000..fb3c50c5b --- /dev/null +++ b/mediapipe/tasks/cc/vision/face_landmarker/face_landmarks_detector_graph_test.cc @@ -0,0 +1,324 @@ +/* Copyright 2023 The MediaPipe Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "absl/flags/flag.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "mediapipe/framework/api2/builder.h" +#include "mediapipe/framework/api2/port.h" +#include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/deps/file_path.h" +#include "mediapipe/framework/formats/image.h" +#include "mediapipe/framework/formats/landmark.pb.h" +#include "mediapipe/framework/formats/rect.pb.h" +#include "mediapipe/framework/formats/tensor.h" +#include "mediapipe/framework/packet.h" +#include "mediapipe/framework/port/file_helpers.h" +#include "mediapipe/framework/port/gmock.h" +#include "mediapipe/framework/port/gtest.h" +#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_landmarks_detector_graph_options.pb.h" +#include "mediapipe/tasks/cc/vision/utils/image_utils.h" + +namespace mediapipe { +namespace tasks { +namespace vision { +namespace face_landmarker { +namespace { + +using ::file::Defaults; +using ::file::GetTextProto; +using ::mediapipe::NormalizedRect; +using ::mediapipe::api2::Input; +using ::mediapipe::api2::Output; +using ::mediapipe::api2::builder::Graph; +using ::mediapipe::file::JoinPath; +using ::mediapipe::tasks::core::TaskRunner; +using ::mediapipe::tasks::vision::DecodeImageFromFile; +using ::testing::ElementsAreArray; +using ::testing::EqualsProto; +using ::testing::Pointwise; +using ::testing::TestParamInfo; +using ::testing::TestWithParam; +using ::testing::Values; +using ::testing::proto::Approximately; +using ::testing::proto::Partially; + +constexpr char kTestDataDirectory[] = "/mediapipe/tasks/testdata/vision/"; +constexpr char kFaceLandmarksDetectionModel[] = "face_landmark.tflite"; +constexpr char kPortraitImageName[] = "portrait.jpg"; +constexpr char kCatImageName[] = "cat.jpg"; +constexpr char kPortraitExpectedFaceLandamrksName[] = + "portrait_expected_face_landmarks.pbtxt"; + +constexpr char kImageTag[] = "IMAGE"; +constexpr char kImageName[] = "image"; +constexpr char kNormRectTag[] = "NORM_RECT"; +constexpr char kNormRectName[] = "norm_rect"; + +constexpr char kNormLandmarksTag[] = "NORM_LANDMARKS"; +constexpr char kNormLandmarksName[] = "norm_landmarks"; +constexpr char kFaceRectNextFrameTag[] = "FACE_RECT_NEXT_FRAME"; +constexpr char kFaceRectNextFrameName[] = "face_rect_next_frame"; +constexpr char kFaceRectsNextFrameTag[] = "FACE_RECTS_NEXT_FRAME"; +constexpr char kFaceRectsNextFrameName[] = "face_rects_next_frame"; +constexpr char kPresenceTag[] = "PRESENCE"; +constexpr char kPresenceName[] = "presence"; +constexpr char kPresenceScoreTag[] = "PRESENCE_SCORE"; +constexpr char kPresenceScoreName[] = "presence_score"; + +constexpr float kFractionDiff = 0.05; // percentage +constexpr float kAbsMargin = 0.03; + +// Helper function to create a Single Face Landmark TaskRunner. +absl::StatusOr> CreateSingleFaceLandmarksTaskRunner( + absl::string_view model_name) { + Graph graph; + + auto& face_landmark_detection = graph.AddNode( + "mediapipe.tasks.vision.face_landmarker." + "SingleFaceLandmarksDetectorGraph"); + + auto options = std::make_unique(); + options->mutable_base_options()->mutable_model_asset()->set_file_name( + JoinPath("./", kTestDataDirectory, model_name)); + options->set_min_detection_confidence(0.5); + face_landmark_detection.GetOptions() + .Swap(options.get()); + + graph[Input(kImageTag)].SetName(kImageName) >> + face_landmark_detection.In(kImageTag); + graph[Input(kNormRectTag)].SetName(kNormRectName) >> + face_landmark_detection.In(kNormRectTag); + + face_landmark_detection.Out(kNormLandmarksTag).SetName(kNormLandmarksName) >> + graph[Output(kNormLandmarksTag)]; + face_landmark_detection.Out(kPresenceTag).SetName(kPresenceName) >> + graph[Output(kPresenceTag)]; + face_landmark_detection.Out(kPresenceScoreTag).SetName(kPresenceScoreName) >> + graph[Output(kPresenceScoreTag)]; + face_landmark_detection.Out(kFaceRectNextFrameTag) + .SetName(kFaceRectNextFrameName) >> + graph[Output(kFaceRectNextFrameTag)]; + + return TaskRunner::Create( + graph.GetConfig(), + absl::make_unique()); +} + +// Helper function to create a Multi Face Landmark TaskRunner. +absl::StatusOr> CreateMultiFaceLandmarksTaskRunner( + absl::string_view model_name) { + Graph graph; + + auto& face_landmark_detection = graph.AddNode( + "mediapipe.tasks.vision.face_landmarker." + "MultiFaceLandmarksDetectorGraph"); + + auto options = std::make_unique(); + options->mutable_base_options()->mutable_model_asset()->set_file_name( + JoinPath("./", kTestDataDirectory, model_name)); + options->set_min_detection_confidence(0.5); + face_landmark_detection.GetOptions() + .Swap(options.get()); + + graph[Input(kImageTag)].SetName(kImageName) >> + face_landmark_detection.In(kImageTag); + graph[Input>(kNormRectTag)].SetName( + kNormRectName) >> + face_landmark_detection.In(kNormRectTag); + + face_landmark_detection.Out(kNormLandmarksTag).SetName(kNormLandmarksName) >> + graph[Output>(kNormLandmarksTag)]; + face_landmark_detection.Out(kPresenceTag).SetName(kPresenceName) >> + graph[Output>(kPresenceTag)]; + face_landmark_detection.Out(kPresenceScoreTag).SetName(kPresenceScoreName) >> + graph[Output>(kPresenceScoreTag)]; + face_landmark_detection.Out(kFaceRectsNextFrameTag) + .SetName(kFaceRectsNextFrameName) >> + graph[Output>(kFaceRectsNextFrameTag)]; + + return TaskRunner::Create( + graph.GetConfig(), + absl::make_unique()); +} + +NormalizedLandmarkList GetExpectedLandmarkList(absl::string_view filename) { + NormalizedLandmarkList expected_landmark_list; + MP_EXPECT_OK(GetTextProto(file::JoinPath("./", kTestDataDirectory, filename), + &expected_landmark_list, Defaults())); + return expected_landmark_list; +} + +// Helper function to construct NormalizeRect proto. +NormalizedRect MakeNormRect(float x_center, float y_center, float width, + float height, float rotation) { + NormalizedRect hand_rect; + hand_rect.set_x_center(x_center); + hand_rect.set_y_center(y_center); + hand_rect.set_width(width); + hand_rect.set_height(height); + hand_rect.set_rotation(rotation); + return hand_rect; +} + +// Struct holding the parameters for parameterized FaceLandmarksDetectionTest +// class. +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 the test image. + std::string test_image_name; + // RoI on image to detect hands. + NormalizedRect norm_rect; + // Expected hand presence value. + bool expected_presence; + // The expected output landmarks positions. + NormalizedLandmarkList expected_landmarks; + // The max value difference between expected_positions and detected positions. + float landmarks_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 the test image. + std::string test_image_name; + // RoI on image to detect hands. + std::vector norm_rects; + // Expected hand presence value. + std::vector expected_presence; + // The expected output landmarks positions. + std::optional> expected_landmarks_lists; + // The max value difference between expected_positions and detected positions. + float landmarks_diff_threshold; +}; + +class SingleFaceLandmarksDetectionTest + : public testing::TestWithParam {}; + +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)); + + auto output_packets = task_runner->Process( + {{kImageName, MakePacket(std::move(image))}, + {kNormRectName, + MakePacket(std::move(GetParam().norm_rect))}}); + MP_ASSERT_OK(output_packets); + + const bool presence = (*output_packets)[kPresenceName].Get(); + ASSERT_EQ(presence, GetParam().expected_presence); + + if (presence) { + const NormalizedLandmarkList landmarks = + (*output_packets)[kNormLandmarksName].Get(); + const NormalizedLandmarkList& expected_landmarks = + GetParam().expected_landmarks; + + EXPECT_THAT( + landmarks, + Approximately(Partially(EqualsProto(expected_landmarks)), + /*margin=*/kAbsMargin, + /*fraction=*/GetParam().landmarks_diff_threshold)); + } +} + +class MultiFaceLandmarksDetectionTest + : public testing::TestWithParam {}; + +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)); + + auto output_packets = task_runner->Process( + {{kImageName, MakePacket(std::move(image))}, + {kNormRectName, MakePacket>( + std::move(GetParam().norm_rects))}}); + MP_ASSERT_OK(output_packets); + + const std::vector& presences = + (*output_packets)[kPresenceName].Get>(); + EXPECT_THAT(presences, ElementsAreArray(GetParam().expected_presence)); + if (GetParam().expected_landmarks_lists) { + const std::vector& landmarks_lists = + (*output_packets)[kNormLandmarksName] + .Get>(); + EXPECT_THAT(landmarks_lists, + Pointwise(Approximately( + Partially(EqualsProto()), /*margin=*/kAbsMargin, + /*fraction=*/GetParam().landmarks_diff_threshold), + *GetParam().expected_landmarks_lists)); + } +} + +INSTANTIATE_TEST_SUITE_P( + FaceLandmarksDetectionTest, SingleFaceLandmarksDetectionTest, + Values(SingeFaceTestParams{ + /* test_name= */ "Portrait", + /*input_model_name= */ kFaceLandmarksDetectionModel, + /*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}), + [](const TestParamInfo& info) { + return info.param.test_name; + }); + +INSTANTIATE_TEST_SUITE_P( + FaceLandmarksDetectionTest, MultiFaceLandmarksDetectionTest, + Values( + MultiFaceTestParams{ + /* test_name= */ "Portrait", + /*input_model_name= */ kFaceLandmarksDetectionModel, + /*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}, + MultiFaceTestParams{ + /* test_name= */ "NoFace", + /*input_model_name= */ kFaceLandmarksDetectionModel, + /*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}), + [](const TestParamInfo& info) { + return info.param.test_name; + }); + +} // namespace + +} // namespace face_landmarker +} // namespace vision +} // namespace tasks +} // namespace mediapipe diff --git a/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD b/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD new file mode 100644 index 000000000..7d5b57e43 --- /dev/null +++ b/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD @@ -0,0 +1,31 @@ +# Copyright 2023 The MediaPipe Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("//mediapipe/framework/port:build_config.bzl", "mediapipe_proto_library") + +package(default_visibility = [ + "//mediapipe/tasks:internal", +]) + +licenses(["notice"]) + +mediapipe_proto_library( + name = "face_landmarks_detector_graph_options_proto", + srcs = ["face_landmarks_detector_graph_options.proto"], + deps = [ + "//mediapipe/framework:calculator_options_proto", + "//mediapipe/framework:calculator_proto", + "//mediapipe/tasks/cc/core/proto:base_options_proto", + ], +) diff --git a/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.proto b/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.proto new file mode 100644 index 000000000..90bfd0087 --- /dev/null +++ b/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.proto @@ -0,0 +1,38 @@ +/* Copyright 2023 The MediaPipe Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +syntax = "proto2"; + +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"; + +option java_package = "com.google.mediapipe.tasks.vision.facelandmarker.proto"; +option java_outer_classname = "FaceLandmarksDetectorGraphOptionsProto"; + +message FaceLandmarksDetectorGraphOptions { + extend mediapipe.CalculatorOptions { + optional FaceLandmarksDetectorGraphOptions ext = 508968149; + } + // Base options for configuring Task library, such as specifying the TfLite + // model file with metadata, accelerator options, etc. + optional core.proto.BaseOptions base_options = 1; + + // 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]; +} diff --git a/mediapipe/tasks/testdata/vision/BUILD b/mediapipe/tasks/testdata/vision/BUILD index 09f830aba..a3aaeab0e 100644 --- a/mediapipe/tasks/testdata/vision/BUILD +++ b/mediapipe/tasks/testdata/vision/BUILD @@ -39,6 +39,7 @@ mediapipe_files(srcs = [ "deeplabv3.tflite", "face_detection_full_range.tflite", "face_detection_full_range_sparse.tflite", + "face_landmark.tflite", "fist.jpg", "fist.png", "hand_landmark_full.tflite", @@ -136,6 +137,7 @@ filegroup( "deeplabv3.tflite", "face_detection_full_range.tflite", "face_detection_full_range_sparse.tflite", + "face_landmark.tflite", "hand_landmark_full.tflite", "hand_landmark_lite.tflite", "hand_landmarker.task", @@ -148,6 +150,7 @@ filegroup( "mobilenet_v2_1.0_224.tflite", "mobilenet_v3_small_100_224_embedder.tflite", "palm_detection_full.tflite", + "portrait_expected_face_landmarks.pbtxt", "selfie_segm_128_128_3.tflite", "selfie_segm_144_256_3.tflite", ], @@ -169,6 +172,7 @@ filegroup( "pointing_up_landmarks.pbtxt", "pointing_up_rotated_landmarks.pbtxt", "portrait_expected_detection.pbtxt", + "portrait_expected_face_landmarks.pbtxt", "thumb_up_landmarks.pbtxt", "thumb_up_rotated_landmarks.pbtxt", "victory_landmarks.pbtxt", diff --git a/mediapipe/tasks/testdata/vision/portrait_expected_face_landmarks.pbtxt b/mediapipe/tasks/testdata/vision/portrait_expected_face_landmarks.pbtxt new file mode 100644 index 000000000..f8eca5b6d --- /dev/null +++ b/mediapipe/tasks/testdata/vision/portrait_expected_face_landmarks.pbtxt @@ -0,0 +1,1874 @@ +# proto-file: mediapipe/framework/formats/landmark.proto +# proto-message: NormalizedLandmarkList +landmark { + x: 0.4980545938014984 + y: 0.24903230369091034 +} +landmark { + x: 0.49932512640953064 + y: 0.2245415896177292 +} +landmark { + x: 0.49835407733917236 + y: 0.23289766907691956 +} +landmark { + x: 0.48964300751686096 + y: 0.19509243965148926 +} +landmark { + x: 0.4992780089378357 + y: 0.21520577371120453 +} +landmark { + x: 0.49882733821868896 + y: 0.20322635769844055 +} +landmark { + x: 0.4974270761013031 + y: 0.17528100311756134 +} +landmark { + x: 0.4252423644065857 + y: 0.1752239167690277 +} +landmark { + x: 0.4969234764575958 + y: 0.16028350591659546 +} +landmark { + x: 0.4968925714492798 + y: 0.1495988965034485 +} +landmark { + x: 0.4961165189743042 + y: 0.10130326449871063 +} +landmark { + x: 0.4980241656303406 + y: 0.2529524564743042 +} +landmark { + x: 0.4979502260684967 + y: 0.25524431467056274 +} +landmark { + x: 0.49776896834373474 + y: 0.2552323341369629 +} +landmark { + x: 0.4978826642036438 + y: 0.27776089310646057 +} +landmark { + x: 0.49799230694770813 + y: 0.2809632420539856 +} +landmark { + x: 0.4979100227355957 + y: 0.2848512530326843 +} +landmark { + x: 0.49800440669059753 + y: 0.28988373279571533 +} +landmark { + x: 0.4979381561279297 + y: 0.29874739050865173 +} +landmark { + x: 0.4990279972553253 + y: 0.22949126362800598 +} +landmark { + x: 0.48731645941734314 + y: 0.228602334856987 +} +landmark { + x: 0.38321125507354736 + y: 0.14629608392715454 +} +landmark { + x: 0.45509326457977295 + y: 0.1804829090833664 +} +landmark { + x: 0.445110023021698 + y: 0.18121978640556335 +} +landmark { + x: 0.4348788261413574 + y: 0.1813269555568695 +} +landmark { + x: 0.4198905825614929 + y: 0.17857316136360168 +} +landmark { + x: 0.4631618857383728 + y: 0.17870596051216125 +} +landmark { + x: 0.44001317024230957 + y: 0.16361379623413086 +} +landmark { + x: 0.4507884979248047 + y: 0.16328848898410797 +} +landmark { + x: 0.4298645257949829 + y: 0.16532668471336365 +} +landmark { + x: 0.42292359471321106 + y: 0.16806119680404663 +} +landmark { + x: 0.4108901619911194 + y: 0.1839759796857834 +} +landmark { + x: 0.4559507369995117 + y: 0.3100507855415344 +} +landmark { + x: 0.42083221673965454 + y: 0.1743769347667694 +} +landmark { + x: 0.3772680461406708 + y: 0.18411599099636078 +} +landmark { + x: 0.39838600158691406 + y: 0.18110841512680054 +} +landmark { + x: 0.4412991404533386 + y: 0.21456821262836456 +} +landmark { + x: 0.4794607162475586 + y: 0.24747860431671143 +} +landmark { + x: 0.48048245906829834 + y: 0.2548246681690216 +} +landmark { + x: 0.46039167046546936 + y: 0.2487250715494156 +} +landmark { + x: 0.4483419954776764 + y: 0.25085747241973877 +} +landmark { + x: 0.4663160741329193 + y: 0.2544938921928406 +} +landmark { + x: 0.455595999956131 + y: 0.2548428177833557 +} +landmark { + x: 0.4378071427345276 + y: 0.2695227563381195 +} +landmark { + x: 0.4899967610836029 + y: 0.22446328401565552 +} +landmark { + x: 0.4890080690383911 + y: 0.2152511477470398 +} +landmark { + x: 0.40738368034362793 + y: 0.16371771693229675 +} +landmark { + x: 0.46559059619903564 + y: 0.19408845901489258 +} +landmark { + x: 0.46049150824546814 + y: 0.21944357454776764 +} +landmark { + x: 0.4600639343261719 + y: 0.2145257592201233 +} +landmark { + x: 0.4089595675468445 + y: 0.2129758894443512 +} +landmark { + x: 0.4894029200077057 + y: 0.2041754424571991 +} +landmark { + x: 0.43470585346221924 + y: 0.1551753580570221 +} +landmark { + x: 0.4189683794975281 + y: 0.15813124179840088 +} +landmark { + x: 0.394975483417511 + y: 0.1302863210439682 +} +landmark { + x: 0.47864243388175964 + y: 0.1597566157579422 +} +landmark { + x: 0.4602668881416321 + y: 0.16489382088184357 +} +landmark { + x: 0.4282156825065613 + y: 0.257098525762558 +} +landmark { + x: 0.38497745990753174 + y: 0.27065035700798035 +} +landmark { + x: 0.46812984347343445 + y: 0.22463132441043854 +} +landmark { + x: 0.4778384268283844 + y: 0.22797265648841858 +} +landmark { + x: 0.43393614888191223 + y: 0.2568528354167938 +} +landmark { + x: 0.4395427405834198 + y: 0.2576085329055786 +} +landmark { + x: 0.4129953682422638 + y: 0.1529330313205719 +} +landmark { + x: 0.4586719870567322 + y: 0.2223067581653595 +} +landmark { + x: 0.4548490345478058 + y: 0.1551821231842041 +} +landmark { + x: 0.4527568817138672 + y: 0.14797326922416687 +} +landmark { + x: 0.43789538741111755 + y: 0.10732019692659378 +} +landmark { + x: 0.40471237897872925 + y: 0.14079004526138306 +} +landmark { + x: 0.44655588269233704 + y: 0.12573182582855225 +} +landmark { + x: 0.4005853235721588 + y: 0.16001185774803162 +} +landmark { + x: 0.3920975625514984 + y: 0.15330302715301514 +} +landmark { + x: 0.479657381772995 + y: 0.25213903188705444 +} +landmark { + x: 0.46366050839424133 + y: 0.25241443514823914 +} +landmark { + x: 0.4519921839237213 + y: 0.2528719902038574 +} +landmark { + x: 0.4703972041606903 + y: 0.22607524693012238 +} +landmark { + x: 0.4363592267036438 + y: 0.25706034898757935 +} +landmark { + x: 0.4440081715583801 + y: 0.26552918553352356 +} +landmark { + x: 0.4417681097984314 + y: 0.2580646276473999 +} +landmark { + x: 0.4761415719985962 + y: 0.22374515235424042 +} +landmark { + x: 0.45743221044540405 + y: 0.25383251905441284 +} +landmark { + x: 0.4683329463005066 + y: 0.25396355986595154 +} +landmark { + x: 0.4815440773963928 + y: 0.25474318861961365 +} +landmark { + x: 0.47932085394859314 + y: 0.2976193428039551 +} +landmark { + x: 0.47975021600723267 + y: 0.28835925459861755 +} +landmark { + x: 0.48039740324020386 + y: 0.28342658281326294 +} +landmark { + x: 0.4811536967754364 + y: 0.2794558107852936 +} +landmark { + x: 0.48218032717704773 + y: 0.27668148279190063 +} +landmark { + x: 0.4579659700393677 + y: 0.26989150047302246 +} +landmark { + x: 0.45591992139816284 + y: 0.2706441879272461 +} +landmark { + x: 0.4531732201576233 + y: 0.27245426177978516 +} +landmark { + x: 0.45113396644592285 + y: 0.2749839127063751 +} +landmark { + x: 0.4426419138908386 + y: 0.24372598528862 +} +landmark { + x: 0.37437647581100464 + y: 0.22582200169563293 +} +landmark { + x: 0.49874579906463623 + y: 0.23130004107952118 +} +landmark { + x: 0.45071229338645935 + y: 0.26583003997802734 +} +landmark { + x: 0.4470844864845276 + y: 0.26525816321372986 +} +landmark { + x: 0.4817662835121155 + y: 0.23206906020641327 +} +landmark { + x: 0.4612492620944977 + y: 0.22707121074199677 +} +landmark { + x: 0.4798736870288849 + y: 0.23013688623905182 +} +landmark { + x: 0.4540415406227112 + y: 0.1982722133398056 +} +landmark { + x: 0.43609002232551575 + y: 0.2030995935201645 +} +landmark { + x: 0.45713263750076294 + y: 0.21781134605407715 +} +landmark { + x: 0.41249439120292664 + y: 0.11695081740617752 +} +landmark { + x: 0.42242154479026794 + y: 0.1313907653093338 +} +landmark { + x: 0.4310758113861084 + y: 0.14831000566482544 +} +landmark { + x: 0.44858187437057495 + y: 0.2805789113044739 +} +landmark { + x: 0.4752667546272278 + y: 0.14848341047763824 +} +landmark { + x: 0.47004297375679016 + y: 0.12331744283437729 +} +landmark { + x: 0.4646402597427368 + y: 0.1024433895945549 +} +landmark { + x: 0.4255844056606293 + y: 0.1805812418460846 +} +landmark { + x: 0.39879530668258667 + y: 0.1897958517074585 +} +landmark { + x: 0.46812960505485535 + y: 0.17677296698093414 +} +landmark { + x: 0.4110848903656006 + y: 0.17171640694141388 +} +landmark { + x: 0.47348183393478394 + y: 0.18901485204696655 +} +landmark { + x: 0.46935269236564636 + y: 0.21757104992866516 +} +landmark { + x: 0.38642019033432007 + y: 0.19684071838855743 +} +landmark { + x: 0.4065028429031372 + y: 0.1935700625181198 +} +landmark { + x: 0.42013058066368103 + y: 0.19527363777160645 +} +landmark { + x: 0.4402572810649872 + y: 0.19438785314559937 +} +landmark { + x: 0.4552159011363983 + y: 0.19161175191402435 +} +landmark { + x: 0.466064989566803 + y: 0.18805746734142303 +} +landmark { + x: 0.4877389967441559 + y: 0.17775586247444153 +} +landmark { + x: 0.3850080370903015 + y: 0.21342645585536957 +} +landmark { + x: 0.402156800031662 + y: 0.17169930040836334 +} +landmark { + x: 0.49324363470077515 + y: 0.22913014888763428 +} +landmark { + x: 0.4649255573749542 + y: 0.20216087996959686 +} +landmark { + x: 0.37319791316986084 + y: 0.1850086748600006 +} +landmark { + x: 0.47394266724586487 + y: 0.1837497055530548 +} +landmark { + x: 0.4555046260356903 + y: 0.21799549460411072 +} +landmark { + x: 0.4165469706058502 + y: 0.17533442378044128 +} +landmark { + x: 0.46884915232658386 + y: 0.2114345282316208 +} +landmark { + x: 0.3772091567516327 + y: 0.24764499068260193 +} +landmark { + x: 0.4673137962818146 + y: 0.17410144209861755 +} +landmark { + x: 0.47945156693458557 + y: 0.2071564644575119 +} +landmark { + x: 0.40883955359458923 + y: 0.2874322533607483 +} +landmark { + x: 0.4108079671859741 + y: 0.30158883333206177 +} +landmark { + x: 0.37442252039909363 + y: 0.2208070307970047 +} +landmark { + x: 0.39394834637641907 + y: 0.2761845290660858 +} +landmark { + x: 0.3842906951904297 + y: 0.16709479689598083 +} +landmark { + x: 0.4550851285457611 + y: 0.32118359208106995 +} +landmark { + x: 0.49397364258766174 + y: 0.23093459010124207 +} +landmark { + x: 0.45444783568382263 + y: 0.2077380120754242 +} +landmark { + x: 0.38875845074653625 + y: 0.18339571356773376 +} +landmark { + x: 0.43656331300735474 + y: 0.17613092064857483 +} +landmark { + x: 0.4456058740615845 + y: 0.1762271374464035 +} +landmark { + x: 0.44135409593582153 + y: 0.26622864603996277 +} +landmark { + x: 0.3838962912559509 + y: 0.23220419883728027 +} +landmark { + x: 0.4754045009613037 + y: 0.335421085357666 +} +landmark { + x: 0.44223690032958984 + y: 0.3225649893283844 +} +landmark { + x: 0.42790481448173523 + y: 0.3136621117591858 +} +landmark { + x: 0.4964919984340668 + y: 0.12334776669740677 +} +landmark { + x: 0.49819302558898926 + y: 0.33688169717788696 +} +landmark { + x: 0.4536263346672058 + y: 0.17571088671684265 +} +landmark { + x: 0.46115797758102417 + y: 0.1747676432132721 +} +landmark { + x: 0.4655093550682068 + y: 0.1744125783443451 +} +landmark { + x: 0.393926203250885 + y: 0.17026498913764954 +} +landmark { + x: 0.458525151014328 + y: 0.16972589492797852 +} +landmark { + x: 0.44999489188194275 + y: 0.16840852797031403 +} +landmark { + x: 0.44186726212501526 + y: 0.1684008091688156 +} +landmark { + x: 0.4332900941371918 + y: 0.16989856958389282 +} +landmark { + x: 0.42744091153144836 + y: 0.17192566394805908 +} +landmark { + x: 0.37643229961395264 + y: 0.16329239308834076 +} +landmark { + x: 0.4300766587257385 + y: 0.17572201788425446 +} +landmark { + x: 0.49822694063186646 + y: 0.2383425235748291 +} +landmark { + x: 0.45442208647727966 + y: 0.23917418718338013 +} +landmark { + x: 0.46854329109191895 + y: 0.22389239072799683 +} +landmark { + x: 0.47962474822998047 + y: 0.23819245398044586 +} +landmark { + x: 0.49700650572776794 + y: 0.16724109649658203 +} +landmark { + x: 0.42490097880363464 + y: 0.30003395676612854 +} +landmark { + x: 0.4395797848701477 + y: 0.3106118142604828 +} +landmark { + x: 0.4749631881713867 + y: 0.3278457820415497 +} +landmark { + x: 0.39698177576065063 + y: 0.2887762784957886 +} +landmark { + x: 0.46450716257095337 + y: 0.17226554453372955 +} +landmark { + x: 0.48090100288391113 + y: 0.19143134355545044 +} +landmark { + x: 0.4984689950942993 + y: 0.3292686939239502 +} +landmark { + x: 0.45732998847961426 + y: 0.33026236295700073 +} +landmark { + x: 0.37620770931243896 + y: 0.24124185740947723 +} +landmark { + x: 0.46904605627059937 + y: 0.2738858163356781 +} +landmark { + x: 0.46702563762664795 + y: 0.2757854759693146 +} +landmark { + x: 0.4652661085128784 + y: 0.27886587381362915 +} +landmark { + x: 0.463967889547348 + y: 0.2832222580909729 +} +landmark { + x: 0.46147748827934265 + y: 0.290606290102005 +} +landmark { + x: 0.4453439712524414 + y: 0.2553885579109192 +} +landmark { + x: 0.44186925888061523 + y: 0.25434958934783936 +} +landmark { + x: 0.4388599991798401 + y: 0.25289586186408997 +} +landmark { + x: 0.43243885040283203 + y: 0.24915771186351776 +} +landmark { + x: 0.39733195304870605 + y: 0.23440396785736084 +} +landmark { + x: 0.4810236096382141 + y: 0.18301787972450256 +} +landmark { + x: 0.475889652967453 + y: 0.16885876655578613 +} +landmark { + x: 0.46913397312164307 + y: 0.16969595849514008 +} +landmark { + x: 0.4482960104942322 + y: 0.2544528543949127 +} +landmark { + x: 0.3960878551006317 + y: 0.25897282361984253 +} +landmark { + x: 0.48444268107414246 + y: 0.16877110302448273 +} +landmark { + x: 0.45883187651634216 + y: 0.2991879880428314 +} +landmark { + x: 0.49830764532089233 + y: 0.19364532828330994 +} +landmark { + x: 0.48863235116004944 + y: 0.18682867288589478 +} +landmark { + x: 0.4978567063808441 + y: 0.18477898836135864 +} +landmark { + x: 0.4722864031791687 + y: 0.20431742072105408 +} +landmark { + x: 0.4984210133552551 + y: 0.3189559280872345 +} +landmark { + x: 0.4981224536895752 + y: 0.3075484037399292 +} +landmark { + x: 0.47765108942985535 + y: 0.3065096139907837 +} +landmark { + x: 0.43310534954071045 + y: 0.27504512667655945 +} +landmark { + x: 0.4470079839229584 + y: 0.22515136003494263 +} +landmark { + x: 0.44496527314186096 + y: 0.28839245438575745 +} +landmark { + x: 0.4224763810634613 + y: 0.22504357993602753 +} +landmark { + x: 0.43616050481796265 + y: 0.2342083901166916 +} +landmark { + x: 0.4120646119117737 + y: 0.23961234092712402 +} +landmark { + x: 0.475606232881546 + y: 0.3177781403064728 +} +landmark { + x: 0.4643869698047638 + y: 0.208414688706398 +} +landmark { + x: 0.4268232583999634 + y: 0.285778284072876 +} +landmark { + x: 0.44140246510505676 + y: 0.2983972728252411 +} +landmark { + x: 0.4230496883392334 + y: 0.26123762130737305 +} +landmark { + x: 0.3859580159187317 + y: 0.2489413619041443 +} +landmark { + x: 0.41095247864723206 + y: 0.2668672502040863 +} +landmark { + x: 0.3822171688079834 + y: 0.26013708114624023 +} +landmark { + x: 0.42548611760139465 + y: 0.24387983977794647 +} +landmark { + x: 0.4732677638530731 + y: 0.19690032303333282 +} +landmark { + x: 0.4727466106414795 + y: 0.22204428911209106 +} +landmark { + x: 0.4637482762336731 + y: 0.2227250039577484 +} +landmark { + x: 0.4789898693561554 + y: 0.21637049317359924 +} +landmark { + x: 0.4690355956554413 + y: 0.16393627226352692 +} +landmark { + x: 0.452879935503006 + y: 0.1608419120311737 +} +landmark { + x: 0.4382350742816925 + y: 0.1606438159942627 +} +landmark { + x: 0.4255324602127075 + y: 0.1623798906803131 +} +landmark { + x: 0.4161430895328522 + y: 0.16602107882499695 +} +landmark { + x: 0.4089227616786957 + y: 0.17826607823371887 +} +landmark { + x: 0.3748510777950287 + y: 0.20213350653648376 +} +landmark { + x: 0.41730061173439026 + y: 0.1863732486963272 +} +landmark { + x: 0.4285638630390167 + y: 0.18778443336486816 +} +landmark { + x: 0.4425407648086548 + y: 0.18756519258022308 +} +landmark { + x: 0.4555114805698395 + y: 0.18586406111717224 +} +landmark { + x: 0.46523046493530273 + y: 0.18328996002674103 +} +landmark { + x: 0.47178375720977783 + y: 0.18066181242465973 +} +landmark { + x: 0.3736625909805298 + y: 0.20530056953430176 +} +landmark { + x: 0.46318209171295166 + y: 0.22422729432582855 +} +landmark { + x: 0.4809984564781189 + y: 0.1990271806716919 +} +landmark { + x: 0.48176494240760803 + y: 0.22318817675113678 +} +landmark { + x: 0.4872453808784485 + y: 0.2276468127965927 +} +landmark { + x: 0.48110896348953247 + y: 0.22458484768867493 +} +landmark { + x: 0.4653593599796295 + y: 0.2264489233493805 +} +landmark { + x: 0.4895707070827484 + y: 0.2285487949848175 +} +landmark { + x: 0.4901042878627777 + y: 0.23013441264629364 +} +landmark { + x: 0.47118645906448364 + y: 0.17443525791168213 +} +landmark { + x: 0.47672608494758606 + y: 0.17624422907829285 +} +landmark { + x: 0.4798724353313446 + y: 0.17757169902324677 +} +landmark { + x: 0.42379844188690186 + y: 0.1733894646167755 +} +landmark { + x: 0.4173426032066345 + y: 0.1713654100894928 +} +landmark { + x: 0.50678950548172 + y: 0.19487784802913666 +} +landmark { + x: 0.5664036870002747 + y: 0.17354270815849304 +} +landmark { + x: 0.5098778605461121 + y: 0.22844582796096802 +} +landmark { + x: 0.6026901006698608 + y: 0.1446288377046585 +} +landmark { + x: 0.5374819040298462 + y: 0.1796381175518036 +} +landmark { + x: 0.5473308563232422 + y: 0.18004438281059265 +} +landmark { + x: 0.5573220252990723 + y: 0.17993563413619995 +} +landmark { + x: 0.5713184475898743 + y: 0.17684750258922577 +} +landmark { + x: 0.5294344425201416 + y: 0.17807874083518982 +} +landmark { + x: 0.551594614982605 + y: 0.16238141059875488 +} +landmark { + x: 0.5410234928131104 + y: 0.16237744688987732 +} +landmark { + x: 0.5616905689239502 + y: 0.16386936604976654 +} +landmark { + x: 0.5684432983398438 + y: 0.16646406054496765 +} +landmark { + x: 0.5801206827163696 + y: 0.18219953775405884 +} +landmark { + x: 0.5396831631660461 + y: 0.3086676597595215 +} +landmark { + x: 0.5704851150512695 + y: 0.1726381778717041 +} +landmark { + x: 0.6091540455818176 + y: 0.1819297969341278 +} +landmark { + x: 0.5911511778831482 + y: 0.17913101613521576 +} +landmark { + x: 0.5526814460754395 + y: 0.21349221467971802 +} +landmark { + x: 0.5163634419441223 + y: 0.24709725379943848 +} +landmark { + x: 0.5148695707321167 + y: 0.2545338273048401 +} +landmark { + x: 0.5347130298614502 + y: 0.2479257881641388 +} +landmark { + x: 0.5457966923713684 + y: 0.2498200535774231 +} +landmark { + x: 0.5283692479133606 + y: 0.25389036536216736 +} +landmark { + x: 0.5383691787719727 + y: 0.25401008129119873 +} +landmark { + x: 0.5557026863098145 + y: 0.268137663602829 +} +landmark { + x: 0.5084323883056641 + y: 0.22426101565361023 +} +landmark { + x: 0.5091908574104309 + y: 0.21496908366680145 +} +landmark { + x: 0.583114504814148 + y: 0.1616487205028534 +} +landmark { + x: 0.528192937374115 + y: 0.1935209333896637 +} +landmark { + x: 0.5355716943740845 + y: 0.2187693566083908 +} +landmark { + x: 0.5354732275009155 + y: 0.21379713714122772 +} +landmark { + x: 0.5836221575737 + y: 0.21118909120559692 +} +landmark { + x: 0.508026123046875 + y: 0.2039874643087387 +} +landmark { + x: 0.5573052167892456 + y: 0.15374767780303955 +} +landmark { + x: 0.5723438858985901 + y: 0.15636125206947327 +} +landmark { + x: 0.592279314994812 + y: 0.12885108590126038 +} +landmark { + x: 0.5147846341133118 + y: 0.159391850233078 +} +landmark { + x: 0.5318648815155029 + y: 0.16428986191749573 +} +landmark { + x: 0.5648412108421326 + y: 0.255575954914093 +} +landmark { + x: 0.6016966700553894 + y: 0.2676719129085541 +} +landmark { + x: 0.5281904935836792 + y: 0.22413136065006256 +} +landmark { + x: 0.5187554359436035 + y: 0.22772032022476196 +} +landmark { + x: 0.5590209364891052 + y: 0.25563740730285645 +} +landmark { + x: 0.5534543395042419 + y: 0.2565377950668335 +} +landmark { + x: 0.5777165293693542 + y: 0.15111610293388367 +} +landmark { + x: 0.5371914505958557 + y: 0.2216498702764511 +} +landmark { + x: 0.537873387336731 + y: 0.1542656123638153 +} +landmark { + x: 0.5399894714355469 + y: 0.14695629477500916 +} +landmark { + x: 0.5525091886520386 + y: 0.10661839693784714 +} +landmark { + x: 0.5843519568443298 + y: 0.13914364576339722 +} +landmark { + x: 0.5451668500900269 + y: 0.12492523342370987 +} +landmark { + x: 0.5889578461647034 + y: 0.15803614258766174 +} +landmark { + x: 0.5957990288734436 + y: 0.15151908993721008 +} +landmark { + x: 0.5159457921981812 + y: 0.2518090605735779 +} +landmark { + x: 0.5312853455543518 + y: 0.25169894099235535 +} +landmark { + x: 0.542163610458374 + y: 0.251932829618454 +} +landmark { + x: 0.5259739756584167 + y: 0.2256430983543396 +} +landmark { + x: 0.5565677881240845 + y: 0.25596001744270325 +} +landmark { + x: 0.549358606338501 + y: 0.2644067406654358 +} +landmark { + x: 0.5510720610618591 + y: 0.25702980160713196 +} +landmark { + x: 0.5209062695503235 + y: 0.22332420945167542 +} +landmark { + x: 0.5362206697463989 + y: 0.25305813550949097 +} +landmark { + x: 0.5261251926422119 + y: 0.2534092962741852 +} +landmark { + x: 0.5135098695755005 + y: 0.25447532534599304 +} +landmark { + x: 0.5163015127182007 + y: 0.29726582765579224 +} +landmark { + x: 0.5158073306083679 + y: 0.28806692361831665 +} +landmark { + x: 0.515143871307373 + y: 0.28313547372817993 +} +landmark { + x: 0.5142782330513 + y: 0.27918556332588196 +} +landmark { + x: 0.5130523443222046 + y: 0.27645161747932434 +} +landmark { + x: 0.5359764695167542 + y: 0.269015908241272 +} +landmark { + x: 0.5380879640579224 + y: 0.2697351574897766 +} +landmark { + x: 0.5408694744110107 + y: 0.2715359628200531 +} +landmark { + x: 0.5429098606109619 + y: 0.2739952504634857 +} +landmark { + x: 0.551747739315033 + y: 0.2425888031721115 +} +landmark { + x: 0.6104724407196045 + y: 0.22327962517738342 +} +landmark { + x: 0.542661726474762 + y: 0.264887273311615 +} +landmark { + x: 0.5463249087333679 + y: 0.26419177651405334 +} +landmark { + x: 0.5149042010307312 + y: 0.23182332515716553 +} +landmark { + x: 0.5344391465187073 + y: 0.2265375852584839 +} +landmark { + x: 0.5167554020881653 + y: 0.22990605235099792 +} +landmark { + x: 0.5396073460578918 + y: 0.19743278622627258 +} +landmark { + x: 0.5572812557220459 + y: 0.20191730558872223 +} +landmark { + x: 0.5381895303726196 + y: 0.21704182028770447 +} +landmark { + x: 0.5762028098106384 + y: 0.11580926924943924 +} +landmark { + x: 0.5680140852928162 + y: 0.13006141781806946 +} +landmark { + x: 0.5606018304824829 + y: 0.14677119255065918 +} +landmark { + x: 0.5457060933113098 + y: 0.27935856580734253 +} +landmark { + x: 0.5182281136512756 + y: 0.14809072017669678 +} +landmark { + x: 0.5226633548736572 + y: 0.12293121218681335 +} +landmark { + x: 0.5269424915313721 + y: 0.10210835933685303 +} +landmark { + x: 0.566146969795227 + y: 0.17894425988197327 +} +landmark { + x: 0.5916416645050049 + y: 0.1878890097141266 +} +landmark { + x: 0.5245495438575745 + y: 0.1763075739145279 +} +landmark { + x: 0.5795127153396606 + y: 0.169851154088974 +} +landmark { + x: 0.5203115940093994 + y: 0.18858660757541656 +} +landmark { + x: 0.5273734331130981 + y: 0.21701613068580627 +} +landmark { + x: 0.6029866337776184 + y: 0.194704070687294 +} +landmark { + x: 0.5848985910415649 + y: 0.19179144501686096 +} +landmark { + x: 0.5722278356552124 + y: 0.1937248259782791 +} +landmark { + x: 0.5527111887931824 + y: 0.19322136044502258 +} +landmark { + x: 0.5378798842430115 + y: 0.19076111912727356 +} +landmark { + x: 0.5270953178405762 + y: 0.18745900690555573 +} +landmark { + x: 0.5068857669830322 + y: 0.1776244342327118 +} +landmark { + x: 0.6050539016723633 + y: 0.21112805604934692 +} +landmark { + x: 0.5876967310905457 + y: 0.16968470811843872 +} +landmark { + x: 0.5046964883804321 + y: 0.22904616594314575 +} +landmark { + x: 0.5294264554977417 + y: 0.2015819251537323 +} +landmark { + x: 0.6109298467636108 + y: 0.18285338580608368 +} +landmark { + x: 0.5193479061126709 + y: 0.1833764761686325 +} +landmark { + x: 0.5392886400222778 + y: 0.21720528602600098 +} +landmark { + x: 0.5743647813796997 + y: 0.17350730299949646 +} +landmark { + x: 0.5272390842437744 + y: 0.210930734872818 +} +landmark { + x: 0.6083701252937317 + y: 0.2448720633983612 +} +landmark { + x: 0.5251532793045044 + y: 0.17359605431556702 +} +landmark { + x: 0.5172861814498901 + y: 0.206781804561615 +} +landmark { + x: 0.5826858878135681 + y: 0.2846713066101074 +} +landmark { + x: 0.5798115730285645 + y: 0.29861539602279663 +} +landmark { + x: 0.6129974722862244 + y: 0.2183024138212204 +} +landmark { + x: 0.5959714651107788 + y: 0.27324289083480835 +} +landmark { + x: 0.6027761697769165 + y: 0.16515110433101654 +} +landmark { + x: 0.5405923128128052 + y: 0.31956949830055237 +} +landmark { + x: 0.5035058856010437 + y: 0.23085595667362213 +} +landmark { + x: 0.539728045463562 + y: 0.2068905085325241 +} +landmark { + x: 0.5997242331504822 + y: 0.18125012516975403 +} +landmark { + x: 0.5557644963264465 + y: 0.17472141981124878 +} +landmark { + x: 0.5469692945480347 + y: 0.17501085996627808 +} +landmark { + x: 0.5519768595695496 + y: 0.2650691866874695 +} +landmark { + x: 0.6063312888145447 + y: 0.2296946942806244 +} +landmark { + x: 0.5207729339599609 + y: 0.3345435559749603 +} +landmark { + x: 0.5519165992736816 + y: 0.32048481702804565 +} +landmark { + x: 0.5647541880607605 + y: 0.31104913353919983 +} +landmark { + x: 0.5389063358306885 + y: 0.1747787445783615 +} +landmark { + x: 0.5314264893531799 + y: 0.17405255138874054 +} +landmark { + x: 0.5270696878433228 + y: 0.1738554984331131 +} +landmark { + x: 0.5948655009269714 + y: 0.16821417212486267 +} +landmark { + x: 0.5335545539855957 + y: 0.1689416468143463 +} +landmark { + x: 0.5418227910995483 + y: 0.16735565662384033 +} +landmark { + x: 0.5498901605606079 + y: 0.1670738011598587 +} +landmark { + x: 0.5583690404891968 + y: 0.16833548247814178 +} +landmark { + x: 0.5640782117843628 + y: 0.17028631269931793 +} +landmark { + x: 0.6084479689598083 + y: 0.1614430546760559 +} +landmark { + x: 0.5618270039558411 + y: 0.1741718202829361 +} +landmark { + x: 0.5407617092132568 + y: 0.23834827542304993 +} +landmark { + x: 0.5280399322509766 + y: 0.22334744036197662 +} +landmark { + x: 0.5165486931800842 + y: 0.2378668338060379 +} +landmark { + x: 0.5681453347206116 + y: 0.2975575923919678 +} +landmark { + x: 0.5546253323554993 + y: 0.30852413177490234 +} +landmark { + x: 0.5215611457824707 + y: 0.3270350992679596 +} +landmark { + x: 0.5914955139160156 + y: 0.2856939435005188 +} +landmark { + x: 0.5278031826019287 + y: 0.17169266939163208 +} +landmark { + x: 0.514165461063385 + y: 0.19115599989891052 +} +landmark { + x: 0.5380026698112488 + y: 0.3287186622619629 +} +landmark { + x: 0.6117194890975952 + y: 0.23853100836277008 +} +landmark { + x: 0.5255835056304932 + y: 0.2732813060283661 +} +landmark { + x: 0.5276772379875183 + y: 0.2751905024051666 +} +landmark { + x: 0.5295414328575134 + y: 0.2782367765903473 +} +landmark { + x: 0.5309793949127197 + y: 0.28254830837249756 +} +landmark { + x: 0.5334357619285583 + y: 0.2897222638130188 +} +landmark { + x: 0.5479921698570251 + y: 0.25442880392074585 +} +landmark { + x: 0.5514671802520752 + y: 0.2532214820384979 +} +landmark { + x: 0.5545699000358582 + y: 0.25174254179000854 +} +landmark { + x: 0.561098039150238 + y: 0.24773401021957397 +} +landmark { + x: 0.5944876670837402 + y: 0.23217952251434326 +} +landmark { + x: 0.5131715536117554 + y: 0.18276511132717133 +} +landmark { + x: 0.5169176459312439 + y: 0.1685507744550705 +} +landmark { + x: 0.5233891010284424 + y: 0.16924551129341125 +} +landmark { + x: 0.5447842478752136 + y: 0.2536035180091858 +} +landmark { + x: 0.5950148105621338 + y: 0.25640690326690674 +} +landmark { + x: 0.5090537667274475 + y: 0.16862088441848755 +} +landmark { + x: 0.536418616771698 + y: 0.29798656702041626 +} +landmark { + x: 0.5068923234939575 + y: 0.1866418570280075 +} +landmark { + x: 0.5229700207710266 + y: 0.20384481549263 +} +landmark { + x: 0.5183667540550232 + y: 0.3059324622154236 +} +landmark { + x: 0.5605188012123108 + y: 0.27336931228637695 +} +landmark { + x: 0.5474860668182373 + y: 0.22416174411773682 +} +landmark { + x: 0.5493848919868469 + y: 0.28694725036621094 +} +landmark { + x: 0.5709414482116699 + y: 0.22346587479114532 +} +landmark { + x: 0.5579255223274231 + y: 0.232930988073349 +} +landmark { + x: 0.5807584524154663 + y: 0.23765110969543457 +} +landmark { + x: 0.5209594964981079 + y: 0.316994309425354 +} +landmark { + x: 0.530515730381012 + y: 0.2078046351671219 +} +landmark { + x: 0.5666337609291077 + y: 0.28360864520072937 +} +landmark { + x: 0.553034245967865 + y: 0.29658618569374084 +} +landmark { + x: 0.5700827240943909 + y: 0.2593379318714142 +} +landmark { + x: 0.6041326522827148 + y: 0.24623559415340424 +} +landmark { + x: 0.5814452767372131 + y: 0.26452142000198364 +} +landmark { + x: 0.6062427163124084 + y: 0.25723811984062195 +} +landmark { + x: 0.5678514838218689 + y: 0.24233733117580414 +} +landmark { + x: 0.5213474631309509 + y: 0.19642430543899536 +} +landmark { + x: 0.5243054628372192 + y: 0.22156046330928802 +} +landmark { + x: 0.5326778888702393 + y: 0.22209812700748444 +} +landmark { + x: 0.5186276435852051 + y: 0.21597601473331451 +} +landmark { + x: 0.5235650539398193 + y: 0.1633981466293335 +} +landmark { + x: 0.5392965078353882 + y: 0.1598573923110962 +} +landmark { + x: 0.5537004470825195 + y: 0.1593489944934845 +} +landmark { + x: 0.5661267042160034 + y: 0.16085092723369598 +} +landmark { + x: 0.574979305267334 + y: 0.16426688432693481 +} +landmark { + x: 0.581565260887146 + y: 0.17640192806720734 +} +landmark { + x: 0.6120520830154419 + y: 0.19976450502872467 +} +landmark { + x: 0.5742601156234741 + y: 0.18469390273094177 +} +landmark { + x: 0.5635954737663269 + y: 0.186308354139328 +} +landmark { + x: 0.5499920845031738 + y: 0.18640676140785217 +} +landmark { + x: 0.5372087359428406 + y: 0.1849617063999176 +} +landmark { + x: 0.5276203155517578 + y: 0.18271957337856293 +} +landmark { + x: 0.521136462688446 + y: 0.18020761013031006 +} +landmark { + x: 0.61063152551651 + y: 0.2030305564403534 +} +landmark { + x: 0.5329957604408264 + y: 0.22360016405582428 +} +landmark { + x: 0.5149561166763306 + y: 0.19873511791229248 +} +landmark { + x: 0.516097366809845 + y: 0.22282160818576813 +} +landmark { + x: 0.5102393627166748 + y: 0.22739730775356293 +} +landmark { + x: 0.5163232088088989 + y: 0.22425635159015656 +} +landmark { + x: 0.5307353138923645 + y: 0.22599026560783386 +} +landmark { + x: 0.5081872940063477 + y: 0.22832725942134857 +} +landmark { + x: 0.5072928071022034 + y: 0.22999370098114014 +} +landmark { + x: 0.5214151740074158 + y: 0.17402707040309906 +} +landmark { + x: 0.5161834955215454 + y: 0.176010400056839 +} +landmark { + x: 0.5135918259620667 + y: 0.17730969190597534 +} +landmark { + x: 0.5676515102386475 + y: 0.17169912159442902 +} +landmark { + x: 0.5736849904060364 + y: 0.1696227788925171 +} diff --git a/third_party/external_files.bzl b/third_party/external_files.bzl index 1d9239c83..8326d0414 100644 --- a/third_party/external_files.bzl +++ b/third_party/external_files.bzl @@ -258,8 +258,8 @@ def external_files(): http_file( name = "com_google_mediapipe_face_landmark_tflite", - sha256 = "c603fa6149219a3e9487dc9abd7a0c24474c77263273d24868378cdf40aa26d1", - urls = ["https://storage.googleapis.com/mediapipe-assets/face_landmark.tflite?generation=1662063817995673"], + sha256 = "1055cb9d4a9ca8b8c688902a3a5194311138ba256bcc94e336d8373a5f30c814", + urls = ["https://storage.googleapis.com/mediapipe-assets/face_landmark.tflite?generation=1676316347980492"], ) http_file( @@ -718,6 +718,12 @@ def external_files(): urls = ["https://storage.googleapis.com/mediapipe-assets/portrait_expected_detection.pbtxt?generation=1674261627835475"], ) + http_file( + name = "com_google_mediapipe_portrait_expected_face_landmarks_pbtxt", + sha256 = "4ac8587379bd072c36cda0d7345f5e592fae51b30522475e0b49c18aab108ce7", + urls = ["https://storage.googleapis.com/mediapipe-assets/portrait_expected_face_landmarks.pbtxt?generation=1676316357333369"], + ) + http_file( name = "com_google_mediapipe_portrait_jpg", sha256 = "a6f11efaa834706db23f275b6115058fa87fc7f14362681e6abe14e82749de3e",