From 7463e48fd42ebd33aa4838e6b7ca4d7eaabb1103 Mon Sep 17 00:00:00 2001 From: kinaryml Date: Thu, 9 Mar 2023 02:39:21 -0800 Subject: [PATCH 1/7] Added some files necessary for the Face Stylizer implementation --- mediapipe/python/BUILD | 1 + mediapipe/tasks/python/test/vision/BUILD | 17 ++ .../python/test/vision/face_stylizer_test.py | 118 ++++++++ mediapipe/tasks/python/vision/BUILD | 19 ++ .../tasks/python/vision/face_stylizer.py | 254 ++++++++++++++++++ 5 files changed, 409 insertions(+) create mode 100644 mediapipe/tasks/python/test/vision/face_stylizer_test.py create mode 100644 mediapipe/tasks/python/vision/face_stylizer.py diff --git a/mediapipe/python/BUILD b/mediapipe/python/BUILD index f56e5b3d..9d5ea26a 100644 --- a/mediapipe/python/BUILD +++ b/mediapipe/python/BUILD @@ -94,6 +94,7 @@ cc_library( "//mediapipe/tasks/cc/vision/image_embedder:image_embedder_graph", "//mediapipe/tasks/cc/vision/image_segmenter:image_segmenter_graph", "//mediapipe/tasks/cc/vision/object_detector:object_detector_graph", + "//mediapipe/tasks/cc/vision/face_stylizer:face_stylizer_graph", ] + select({ # TODO: Build text_classifier_graph and text_embedder_graph on Windows. "//mediapipe:windows": [], diff --git a/mediapipe/tasks/python/test/vision/BUILD b/mediapipe/tasks/python/test/vision/BUILD index 48ecc30b..19d59289 100644 --- a/mediapipe/tasks/python/test/vision/BUILD +++ b/mediapipe/tasks/python/test/vision/BUILD @@ -114,3 +114,20 @@ py_test( "@com_google_protobuf//:protobuf_python", ], ) + +py_test( + name = "face_stylizer_test", + srcs = ["face_stylizer_test.py"], + data = [ + "//mediapipe/tasks/testdata/vision:test_images", + "//mediapipe/tasks/testdata/vision:test_models", + ], + deps = [ + "//mediapipe/python:_framework_bindings", + "//mediapipe/tasks/python/core:base_options", + "//mediapipe/tasks/python/test:test_utils", + "//mediapipe/tasks/python/vision:face_stylizer", + "//mediapipe/tasks/python/vision/core:image_processing_options", + "//mediapipe/tasks/python/vision/core:vision_task_running_mode", + ], +) diff --git a/mediapipe/tasks/python/test/vision/face_stylizer_test.py b/mediapipe/tasks/python/test/vision/face_stylizer_test.py new file mode 100644 index 00000000..3c39851d --- /dev/null +++ b/mediapipe/tasks/python/test/vision/face_stylizer_test.py @@ -0,0 +1,118 @@ +# Copyright 2022 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. +"""Tests for face stylizer.""" + +import enum +import os +from unittest import mock + +import numpy as np +from absl.testing import absltest +from absl.testing import parameterized + +from mediapipe.python._framework_bindings import image as image_module +from mediapipe.tasks.python.core import base_options as base_options_module +from mediapipe.tasks.python.test import test_utils +from mediapipe.tasks.python.vision import face_stylizer +from mediapipe.tasks.python.vision.core import vision_task_running_mode as running_mode_module +from mediapipe.tasks.python.vision.core import image_processing_options as image_processing_options_module + + +_BaseOptions = base_options_module.BaseOptions +_Image = image_module.Image +_FaceStylizer = face_stylizer.FaceStylizer +_FaceStylizerOptions = face_stylizer.FaceStylizerOptions +_RUNNING_MODE = running_mode_module.VisionTaskRunningMode +_ImageProcessingOptions = image_processing_options_module.ImageProcessingOptions + +_MODEL = 'face_stylizer_model_placeholder.tflite' +_IMAGE = 'cats_and_dogs.jpg' +_STYLIZED_IMAGE = 'stylized_image_placeholder.jpg' +_TEST_DATA_DIR = 'mediapipe/tasks/testdata/vision' + + +class ModelFileType(enum.Enum): + FILE_CONTENT = 1 + FILE_NAME = 2 + + +class FaceStylizerTest(parameterized.TestCase): + + def setUp(self): + super().setUp() + self.test_image = _Image.create_from_file( + test_utils.get_test_data_path( + os.path.join(_TEST_DATA_DIR, _IMAGE))) + self.model_path = test_utils.get_test_data_path( + os.path.join(_TEST_DATA_DIR, _MODEL)) + + def test_create_from_file_succeeds_with_valid_model_path(self): + # Creates with default option and valid model file successfully. + with _FaceStylizer.create_from_model_path(self.model_path) as stylizer: + self.assertIsInstance(stylizer, _FaceStylizer) + + def test_create_from_options_succeeds_with_valid_model_path(self): + # Creates with options containing model file successfully. + base_options = _BaseOptions(model_asset_path=self.model_path) + options = _FaceStylizerOptions(base_options=base_options) + with _FaceStylizer.create_from_options(options) as stylizer: + self.assertIsInstance(stylizer, _FaceStylizer) + + def test_create_from_options_fails_with_invalid_model_path(self): + with self.assertRaisesRegex( + RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): + base_options = _BaseOptions( + model_asset_path='/path/to/invalid/model.tflite') + options = _FaceStylizerOptions(base_options=base_options) + _FaceStylizer.create_from_options(options) + + def test_create_from_options_succeeds_with_valid_model_content(self): + # Creates with options containing model content successfully. + with open(self.model_path, 'rb') as f: + base_options = _BaseOptions(model_asset_buffer=f.read()) + options = _FaceStylizerOptions(base_options=base_options) + stylizer = _FaceStylizer.create_from_options(options) + self.assertIsInstance(stylizer, _FaceStylizer) + + @parameterized.parameters( + (ModelFileType.FILE_NAME, _STYLIZED_IMAGE), + (ModelFileType.FILE_CONTENT, _STYLIZED_IMAGE)) + def test_stylize(self, model_file_type, expected_detection_result_file): + # Creates stylizer. + if model_file_type is ModelFileType.FILE_NAME: + base_options = _BaseOptions(model_asset_path=self.model_path) + elif model_file_type is ModelFileType.FILE_CONTENT: + with open(self.model_path, 'rb') as f: + model_content = f.read() + base_options = _BaseOptions(model_asset_buffer=model_content) + else: + # Should never happen + raise ValueError('model_file_type is invalid.') + + options = _FaceStylizerOptions(base_options=base_options) + stylizer = _FaceStylizer.create_from_options(options) + + # Performs face stylization on the input. + stylized_image = stylizer.detect(self.test_image) + # Comparing results. + self.assertTrue( + np.array_equal(stylized_image.numpy_view(), + self.test_image.numpy_view())) + # Closes the stylizer explicitly when the stylizer is not used in + # a context. + stylizer.close() + + +if __name__ == '__main__': + absltest.main() diff --git a/mediapipe/tasks/python/vision/BUILD b/mediapipe/tasks/python/vision/BUILD index eda8e290..8ce0ef96 100644 --- a/mediapipe/tasks/python/vision/BUILD +++ b/mediapipe/tasks/python/vision/BUILD @@ -152,3 +152,22 @@ py_library( "//mediapipe/tasks/python/vision/core:vision_task_running_mode", ], ) + +py_library( + name = "face_stylizer", + srcs = [ + "face_stylizer.py", + ], + deps = [ + "//mediapipe/python:_framework_bindings", + "//mediapipe/python:packet_creator", + "//mediapipe/python:packet_getter", + "//mediapipe/tasks/cc/vision/image_segmenter/proto:face_stylizer_graph_options_py_pb2", + "//mediapipe/tasks/python/core:base_options", + "//mediapipe/tasks/python/core:optional_dependencies", + "//mediapipe/tasks/python/core:task_info", + "//mediapipe/tasks/python/vision/core:base_vision_task_api", + "//mediapipe/tasks/python/vision/core:image_processing_options", + "//mediapipe/tasks/python/vision/core:vision_task_running_mode", + ], +) diff --git a/mediapipe/tasks/python/vision/face_stylizer.py b/mediapipe/tasks/python/vision/face_stylizer.py new file mode 100644 index 00000000..cd840fe8 --- /dev/null +++ b/mediapipe/tasks/python/vision/face_stylizer.py @@ -0,0 +1,254 @@ +# Copyright 2022 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. +"""MediaPipe face stylizer task.""" + +import dataclasses +from typing import Callable, Mapping, Optional + +from mediapipe.python import packet_creator +from mediapipe.python import packet_getter +from mediapipe.python._framework_bindings import image as image_module +from mediapipe.python._framework_bindings import packet as packet_module +from mediapipe.tasks.cc.vision.face_stylizer.proto import face_stylizer_graph_options_pb2 +from mediapipe.tasks.python.core import base_options as base_options_module +from mediapipe.tasks.python.core import task_info as task_info_module +from mediapipe.tasks.python.core.optional_dependencies import doc_controls +from mediapipe.tasks.python.vision.core import base_vision_task_api +from mediapipe.tasks.python.vision.core import image_processing_options as image_processing_options_module +from mediapipe.tasks.python.vision.core import vision_task_running_mode as running_mode_module + +_BaseOptions = base_options_module.BaseOptions +_FaceStylizerGraphOptionsProto = face_stylizer_graph_options_pb2.FaceStylizerGraphOptions +_RunningMode = running_mode_module.VisionTaskRunningMode +_ImageProcessingOptions = image_processing_options_module.ImageProcessingOptions +_TaskInfo = task_info_module.TaskInfo + +_STYLIZED_IMAGE_NAME = 'stylized_image' +_STYLIZED_IMAGE_TAG = 'STYLIZED_IMAGE' +_NORM_RECT_STREAM_NAME = 'norm_rect_in' +_NORM_RECT_TAG = 'NORM_RECT' +_IMAGE_IN_STREAM_NAME = 'image_in' +_IMAGE_OUT_STREAM_NAME = 'image_out' +_IMAGE_TAG = 'IMAGE' +_TASK_GRAPH_NAME = 'mediapipe.tasks.vision.face_stylizer.FaceStylizerGraph' +_MICRO_SECONDS_PER_MILLISECOND = 1000 + + +@dataclasses.dataclass +class FaceStylizerOptions: + """Options for the face stylizer task. + + Attributes: + base_options: Base options for the face stylizer task. + running_mode: The running mode of the task. Default to the image mode. + Face stylizer task has three running modes: + 1) The image mode for stylizing faces on single image inputs. + 2) The video mode for stylizing faces on the decoded frames of a video. + 3) The live stream mode for stylizing faces on a live stream of input + data, such as from camera. + result_callback: The user-defined result callback for processing live stream + data. The result callback should only be specified when the running mode + is set to the live stream mode. + """ + base_options: _BaseOptions + running_mode: _RunningMode = _RunningMode.IMAGE + result_callback: Optional[ + Callable[[image_module.Image, image_module.Image, int], + None]] = None + + @doc_controls.do_not_generate_docs + def to_pb2(self) -> _FaceStylizerGraphOptionsProto: + """Generates an FaceStylizerOptions protobuf object.""" + base_options_proto = self.base_options.to_pb2() + base_options_proto.use_stream_mode = False if self.running_mode == _RunningMode.IMAGE else True + return _FaceStylizerGraphOptionsProto(base_options=base_options_proto) + + +class FaceStylizer(base_vision_task_api.BaseVisionTaskApi): + """Class that performs face stylization on images.""" + + @classmethod + def create_from_model_path(cls, model_path: str) -> 'FaceStylizer': + """Creates an `FaceStylizer` object from a TensorFlow Lite model and the default `FaceStylizerOptions`. + + Note that the created `FaceDetector` instance is in image mode, for + stylizing faces on single image inputs. + + Args: + model_path: Path to the model. + + Returns: + `FaceStylizer` object that's created from the model file and the default + `FaceStylizerOptions`. + + Raises: + ValueError: If failed to create `FaceStylizer` object from the provided + file such as invalid file path. + RuntimeError: If other types of error occurred. + """ + base_options = _BaseOptions(model_asset_path=model_path) + options = FaceStylizerOptions( + base_options=base_options, running_mode=_RunningMode.IMAGE) + return cls.create_from_options(options) + + @classmethod + def create_from_options(cls, + options: FaceStylizerOptions) -> 'FaceStylizer': + """Creates the `FaceStylizer` object from face stylizer options. + + Args: + options: Options for the face stylizer task. + + Returns: + `FaceStylizer` object that's created from `options`. + + Raises: + ValueError: If failed to create `FaceStylizer` object from + `FaceStylizerOptions` such as missing the model. + RuntimeError: If other types of error occurred. + """ + + def packets_callback(output_packets: Mapping[str, packet_module.Packet]): + if output_packets[_IMAGE_OUT_STREAM_NAME].is_empty(): + return + image = packet_getter.get_image(output_packets[_IMAGE_OUT_STREAM_NAME]) + stylized_image_packet = output_packets[_STYLIZED_IMAGE_NAME] + options.result_callback( + stylized_image_packet, image, + stylized_image_packet.timestamp.value // _MICRO_SECONDS_PER_MILLISECOND) + + task_info = _TaskInfo( + task_graph=_TASK_GRAPH_NAME, + input_streams=[ + ':'.join([_IMAGE_TAG, _IMAGE_IN_STREAM_NAME]), + ':'.join([_NORM_RECT_TAG, _NORM_RECT_STREAM_NAME]), + ], + output_streams=[ + ':'.join([_STYLIZED_IMAGE_TAG, _STYLIZED_IMAGE_NAME]), + ':'.join([_IMAGE_TAG, _IMAGE_OUT_STREAM_NAME]) + ], + task_options=options) + return cls( + task_info.generate_graph_config( + enable_flow_limiting=options.running_mode == + _RunningMode.LIVE_STREAM), options.running_mode, + packets_callback if options.result_callback else None) + + def stylize( + self, + image: image_module.Image, + image_processing_options: Optional[_ImageProcessingOptions] = None + ) -> image_module.Image: + """Performs face stylization on the provided MediaPipe Image. + + Only use this method when the FaceStylizer is created with the image + running mode. + + Args: + image: MediaPipe Image. + image_processing_options: Options for image processing. + + Returns: + The stylized image. + + Raises: + ValueError: If any of the input arguments is invalid. + RuntimeError: If face stylization failed to run. + """ + normalized_rect = self.convert_to_normalized_rect(image_processing_options) + output_packets = self._process_image_data({ + _IMAGE_IN_STREAM_NAME: + packet_creator.create_image(image), + _NORM_RECT_STREAM_NAME: + packet_creator.create_proto(normalized_rect.to_pb2()) + }) + return output_packets[_STYLIZED_IMAGE_NAME] + + def stylize_for_video( + self, + image: image_module.Image, + timestamp_ms: int, + image_processing_options: Optional[_ImageProcessingOptions] = None + ) -> image_module.Image: + """Performs face stylization on the provided video frames. + + Only use this method when the FaceStylizer is created with the video + running mode. It's required to provide the video frame's timestamp (in + milliseconds) along with the video frame. The input timestamps should be + monotonically increasing for adjacent calls of this method. + + Args: + image: MediaPipe Image. + timestamp_ms: The timestamp of the input video frame in milliseconds. + image_processing_options: Options for image processing. + + Returns: + The stylized image. + + Raises: + ValueError: If any of the input arguments is invalid. + RuntimeError: If face stylization failed to run. + """ + normalized_rect = self.convert_to_normalized_rect(image_processing_options) + output_packets = self._process_video_data({ + _IMAGE_IN_STREAM_NAME: + packet_creator.create_image(image).at( + timestamp_ms * _MICRO_SECONDS_PER_MILLISECOND), + _NORM_RECT_STREAM_NAME: + packet_creator.create_proto(normalized_rect.to_pb2()).at( + timestamp_ms * _MICRO_SECONDS_PER_MILLISECOND) + }) + return output_packets[_STYLIZED_IMAGE_NAME] + + def stylize_async( + self, + image: image_module.Image, + timestamp_ms: int, + image_processing_options: Optional[_ImageProcessingOptions] = None + ) -> None: + """Sends live image data (an Image with a unique timestamp) to perform face stylization. + + Only use this method when the FaceStylizer is created with the live stream + running mode. The input timestamps should be monotonically increasing for + adjacent calls of this method. This method will return immediately after the + input image is accepted. The results will be available via the + `result_callback` provided in the `FaceStylizerOptions`. The + `stylize_async` method is designed to process live stream data such as camera + input. To lower the overall latency, face stylizer may drop the input + images if needed. In other words, it's not guaranteed to have output per + input image. + + The `result_callback` provides: + - The stylized image. + - The input image that the face stylizer runs on. + - The input timestamp in milliseconds. + + Args: + image: MediaPipe Image. + timestamp_ms: The timestamp of the input image in milliseconds. + image_processing_options: Options for image processing. + + Raises: + ValueError: If the current input timestamp is smaller than what the face + stylizer has already processed. + """ + normalized_rect = self.convert_to_normalized_rect(image_processing_options) + self._send_live_stream_data({ + _IMAGE_IN_STREAM_NAME: + packet_creator.create_image(image).at( + timestamp_ms * _MICRO_SECONDS_PER_MILLISECOND), + _NORM_RECT_STREAM_NAME: + packet_creator.create_proto(normalized_rect.to_pb2()).at( + timestamp_ms * _MICRO_SECONDS_PER_MILLISECOND) + }) From da70497f3514b03c16b986ef157addea280077aa Mon Sep 17 00:00:00 2001 From: kinaryml Date: Wed, 22 Mar 2023 21:15:04 -0700 Subject: [PATCH 2/7] Updated Face Stylizer implementation and tests --- .../python/test/vision/face_stylizer_test.py | 8 +++----- mediapipe/tasks/python/vision/BUILD | 2 +- mediapipe/tasks/python/vision/face_stylizer.py | 8 +++++--- mediapipe/tasks/testdata/vision/BUILD | 1 + .../vision/face_stylization_dummy.tflite | Bin 0 -> 21430 bytes 5 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 mediapipe/tasks/testdata/vision/face_stylization_dummy.tflite diff --git a/mediapipe/tasks/python/test/vision/face_stylizer_test.py b/mediapipe/tasks/python/test/vision/face_stylizer_test.py index 3c39851d..32643821 100644 --- a/mediapipe/tasks/python/test/vision/face_stylizer_test.py +++ b/mediapipe/tasks/python/test/vision/face_stylizer_test.py @@ -36,7 +36,7 @@ _FaceStylizerOptions = face_stylizer.FaceStylizerOptions _RUNNING_MODE = running_mode_module.VisionTaskRunningMode _ImageProcessingOptions = image_processing_options_module.ImageProcessingOptions -_MODEL = 'face_stylizer_model_placeholder.tflite' +_MODEL = 'face_stylization_dummy.tflite' _IMAGE = 'cats_and_dogs.jpg' _STYLIZED_IMAGE = 'stylized_image_placeholder.jpg' _TEST_DATA_DIR = 'mediapipe/tasks/testdata/vision' @@ -104,11 +104,9 @@ class FaceStylizerTest(parameterized.TestCase): stylizer = _FaceStylizer.create_from_options(options) # Performs face stylization on the input. - stylized_image = stylizer.detect(self.test_image) + stylized_image = stylizer.stylize(self.test_image) # Comparing results. - self.assertTrue( - np.array_equal(stylized_image.numpy_view(), - self.test_image.numpy_view())) + # TODO: # Closes the stylizer explicitly when the stylizer is not used in # a context. stylizer.close() diff --git a/mediapipe/tasks/python/vision/BUILD b/mediapipe/tasks/python/vision/BUILD index e21171fc..f89ef04d 100644 --- a/mediapipe/tasks/python/vision/BUILD +++ b/mediapipe/tasks/python/vision/BUILD @@ -162,7 +162,7 @@ py_library( "//mediapipe/python:_framework_bindings", "//mediapipe/python:packet_creator", "//mediapipe/python:packet_getter", - "//mediapipe/tasks/cc/vision/image_segmenter/proto:face_stylizer_graph_options_py_pb2", + "//mediapipe/tasks/cc/vision/face_stylizer/proto:face_stylizer_graph_options_py_pb2", "//mediapipe/tasks/python/core:base_options", "//mediapipe/tasks/python/core:optional_dependencies", "//mediapipe/tasks/python/core:task_info", diff --git a/mediapipe/tasks/python/vision/face_stylizer.py b/mediapipe/tasks/python/vision/face_stylizer.py index cd840fe8..1393982d 100644 --- a/mediapipe/tasks/python/vision/face_stylizer.py +++ b/mediapipe/tasks/python/vision/face_stylizer.py @@ -124,8 +124,10 @@ class FaceStylizer(base_vision_task_api.BaseVisionTaskApi): return image = packet_getter.get_image(output_packets[_IMAGE_OUT_STREAM_NAME]) stylized_image_packet = output_packets[_STYLIZED_IMAGE_NAME] + stylized_image = packet_getter.get_image(stylized_image_packet) + options.result_callback( - stylized_image_packet, image, + stylized_image, image, stylized_image_packet.timestamp.value // _MICRO_SECONDS_PER_MILLISECOND) task_info = _TaskInfo( @@ -173,7 +175,7 @@ class FaceStylizer(base_vision_task_api.BaseVisionTaskApi): _NORM_RECT_STREAM_NAME: packet_creator.create_proto(normalized_rect.to_pb2()) }) - return output_packets[_STYLIZED_IMAGE_NAME] + return packet_getter.get_image(output_packets[_STYLIZED_IMAGE_NAME]) def stylize_for_video( self, @@ -209,7 +211,7 @@ class FaceStylizer(base_vision_task_api.BaseVisionTaskApi): packet_creator.create_proto(normalized_rect.to_pb2()).at( timestamp_ms * _MICRO_SECONDS_PER_MILLISECOND) }) - return output_packets[_STYLIZED_IMAGE_NAME] + return packet_getter.get_image(output_packets[_STYLIZED_IMAGE_NAME]) def stylize_async( self, diff --git a/mediapipe/tasks/testdata/vision/BUILD b/mediapipe/tasks/testdata/vision/BUILD index 097acad4..f77e1780 100644 --- a/mediapipe/tasks/testdata/vision/BUILD +++ b/mediapipe/tasks/testdata/vision/BUILD @@ -170,6 +170,7 @@ filegroup( "face_detection_short_range.tflite", "face_landmark.tflite", "face_landmark_with_attention.tflite", + "face_stylization_dummy.tflite", "face_landmarker.task", "hair_segmentation.tflite", "hand_landmark_full.tflite", diff --git a/mediapipe/tasks/testdata/vision/face_stylization_dummy.tflite b/mediapipe/tasks/testdata/vision/face_stylization_dummy.tflite new file mode 100644 index 0000000000000000000000000000000000000000..2523a3a5d16fe29058a1beae2433d3a2e3fa4ac6 GIT binary patch literal 21430 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 X=g0Pt>F+X6o`?2-QUPOc%E Date: Wed, 22 Mar 2023 21:47:38 -0700 Subject: [PATCH 3/7] Removed unit tests --- .../python/test/vision/face_stylizer_test.py | 116 ------------------ 1 file changed, 116 deletions(-) delete mode 100644 mediapipe/tasks/python/test/vision/face_stylizer_test.py diff --git a/mediapipe/tasks/python/test/vision/face_stylizer_test.py b/mediapipe/tasks/python/test/vision/face_stylizer_test.py deleted file mode 100644 index 32643821..00000000 --- a/mediapipe/tasks/python/test/vision/face_stylizer_test.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2022 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. -"""Tests for face stylizer.""" - -import enum -import os -from unittest import mock - -import numpy as np -from absl.testing import absltest -from absl.testing import parameterized - -from mediapipe.python._framework_bindings import image as image_module -from mediapipe.tasks.python.core import base_options as base_options_module -from mediapipe.tasks.python.test import test_utils -from mediapipe.tasks.python.vision import face_stylizer -from mediapipe.tasks.python.vision.core import vision_task_running_mode as running_mode_module -from mediapipe.tasks.python.vision.core import image_processing_options as image_processing_options_module - - -_BaseOptions = base_options_module.BaseOptions -_Image = image_module.Image -_FaceStylizer = face_stylizer.FaceStylizer -_FaceStylizerOptions = face_stylizer.FaceStylizerOptions -_RUNNING_MODE = running_mode_module.VisionTaskRunningMode -_ImageProcessingOptions = image_processing_options_module.ImageProcessingOptions - -_MODEL = 'face_stylization_dummy.tflite' -_IMAGE = 'cats_and_dogs.jpg' -_STYLIZED_IMAGE = 'stylized_image_placeholder.jpg' -_TEST_DATA_DIR = 'mediapipe/tasks/testdata/vision' - - -class ModelFileType(enum.Enum): - FILE_CONTENT = 1 - FILE_NAME = 2 - - -class FaceStylizerTest(parameterized.TestCase): - - def setUp(self): - super().setUp() - self.test_image = _Image.create_from_file( - test_utils.get_test_data_path( - os.path.join(_TEST_DATA_DIR, _IMAGE))) - self.model_path = test_utils.get_test_data_path( - os.path.join(_TEST_DATA_DIR, _MODEL)) - - def test_create_from_file_succeeds_with_valid_model_path(self): - # Creates with default option and valid model file successfully. - with _FaceStylizer.create_from_model_path(self.model_path) as stylizer: - self.assertIsInstance(stylizer, _FaceStylizer) - - def test_create_from_options_succeeds_with_valid_model_path(self): - # Creates with options containing model file successfully. - base_options = _BaseOptions(model_asset_path=self.model_path) - options = _FaceStylizerOptions(base_options=base_options) - with _FaceStylizer.create_from_options(options) as stylizer: - self.assertIsInstance(stylizer, _FaceStylizer) - - def test_create_from_options_fails_with_invalid_model_path(self): - with self.assertRaisesRegex( - RuntimeError, 'Unable to open file at /path/to/invalid/model.tflite'): - base_options = _BaseOptions( - model_asset_path='/path/to/invalid/model.tflite') - options = _FaceStylizerOptions(base_options=base_options) - _FaceStylizer.create_from_options(options) - - def test_create_from_options_succeeds_with_valid_model_content(self): - # Creates with options containing model content successfully. - with open(self.model_path, 'rb') as f: - base_options = _BaseOptions(model_asset_buffer=f.read()) - options = _FaceStylizerOptions(base_options=base_options) - stylizer = _FaceStylizer.create_from_options(options) - self.assertIsInstance(stylizer, _FaceStylizer) - - @parameterized.parameters( - (ModelFileType.FILE_NAME, _STYLIZED_IMAGE), - (ModelFileType.FILE_CONTENT, _STYLIZED_IMAGE)) - def test_stylize(self, model_file_type, expected_detection_result_file): - # Creates stylizer. - if model_file_type is ModelFileType.FILE_NAME: - base_options = _BaseOptions(model_asset_path=self.model_path) - elif model_file_type is ModelFileType.FILE_CONTENT: - with open(self.model_path, 'rb') as f: - model_content = f.read() - base_options = _BaseOptions(model_asset_buffer=model_content) - else: - # Should never happen - raise ValueError('model_file_type is invalid.') - - options = _FaceStylizerOptions(base_options=base_options) - stylizer = _FaceStylizer.create_from_options(options) - - # Performs face stylization on the input. - stylized_image = stylizer.stylize(self.test_image) - # Comparing results. - # TODO: - # Closes the stylizer explicitly when the stylizer is not used in - # a context. - stylizer.close() - - -if __name__ == '__main__': - absltest.main() From 613bcf99f48072154c7043f2de8f8c11f0fc7094 Mon Sep 17 00:00:00 2001 From: kinaryml Date: Wed, 22 Mar 2023 21:49:11 -0700 Subject: [PATCH 4/7] Removed model --- .../vision/face_stylization_dummy.tflite | Bin 21430 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 mediapipe/tasks/testdata/vision/face_stylization_dummy.tflite diff --git a/mediapipe/tasks/testdata/vision/face_stylization_dummy.tflite b/mediapipe/tasks/testdata/vision/face_stylization_dummy.tflite deleted file mode 100644 index 2523a3a5d16fe29058a1beae2433d3a2e3fa4ac6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21430 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 X=g0Pt>F+X6o`?2-QUPOc%E Date: Wed, 22 Mar 2023 21:50:07 -0700 Subject: [PATCH 5/7] Updated BUILD --- mediapipe/tasks/python/test/vision/BUILD | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/mediapipe/tasks/python/test/vision/BUILD b/mediapipe/tasks/python/test/vision/BUILD index db0bd66b..a3285a6d 100644 --- a/mediapipe/tasks/python/test/vision/BUILD +++ b/mediapipe/tasks/python/test/vision/BUILD @@ -161,20 +161,3 @@ py_test( "@com_google_protobuf//:protobuf_python", ], ) - -py_test( - name = "face_stylizer_test", - srcs = ["face_stylizer_test.py"], - data = [ - "//mediapipe/tasks/testdata/vision:test_images", - "//mediapipe/tasks/testdata/vision:test_models", - ], - deps = [ - "//mediapipe/python:_framework_bindings", - "//mediapipe/tasks/python/core:base_options", - "//mediapipe/tasks/python/test:test_utils", - "//mediapipe/tasks/python/vision:face_stylizer", - "//mediapipe/tasks/python/vision/core:image_processing_options", - "//mediapipe/tasks/python/vision/core:vision_task_running_mode", - ], -) From ca18b95510e243faaa962eeaf8784d37f6b3b484 Mon Sep 17 00:00:00 2001 From: kinaryml Date: Wed, 22 Mar 2023 21:50:40 -0700 Subject: [PATCH 6/7] Updated BUILD --- mediapipe/tasks/testdata/vision/BUILD | 1 - 1 file changed, 1 deletion(-) diff --git a/mediapipe/tasks/testdata/vision/BUILD b/mediapipe/tasks/testdata/vision/BUILD index f77e1780..097acad4 100644 --- a/mediapipe/tasks/testdata/vision/BUILD +++ b/mediapipe/tasks/testdata/vision/BUILD @@ -170,7 +170,6 @@ filegroup( "face_detection_short_range.tflite", "face_landmark.tflite", "face_landmark_with_attention.tflite", - "face_stylization_dummy.tflite", "face_landmarker.task", "hair_segmentation.tflite", "hand_landmark_full.tflite", From 29a40413534a9b1a4653ebbc5e77813a602455aa Mon Sep 17 00:00:00 2001 From: kinaryml Date: Wed, 22 Mar 2023 21:51:45 -0700 Subject: [PATCH 7/7] Fixed a typo in docstring --- mediapipe/tasks/python/vision/face_stylizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediapipe/tasks/python/vision/face_stylizer.py b/mediapipe/tasks/python/vision/face_stylizer.py index 1393982d..83c46ae6 100644 --- a/mediapipe/tasks/python/vision/face_stylizer.py +++ b/mediapipe/tasks/python/vision/face_stylizer.py @@ -82,7 +82,7 @@ class FaceStylizer(base_vision_task_api.BaseVisionTaskApi): def create_from_model_path(cls, model_path: str) -> 'FaceStylizer': """Creates an `FaceStylizer` object from a TensorFlow Lite model and the default `FaceStylizerOptions`. - Note that the created `FaceDetector` instance is in image mode, for + Note that the created `FaceStylizer` instance is in image mode, for stylizing faces on single image inputs. Args: