Implement face stylizer graph and its C++ API.
PiperOrigin-RevId: 515139282
This commit is contained in:
		
							parent
							
								
									0a60c67667
								
							
						
					
					
						commit
						253a5b477e
					
				
							
								
								
									
										73
									
								
								mediapipe/tasks/cc/vision/face_stylizer/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								mediapipe/tasks/cc/vision/face_stylizer/BUILD
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | |||
| # 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_stylizer_graph", | ||||
|     srcs = ["face_stylizer_graph.cc"], | ||||
|     deps = [ | ||||
|         "//mediapipe/calculators/image:image_cropping_calculator", | ||||
|         "//mediapipe/calculators/image:image_cropping_calculator_cc_proto", | ||||
|         "//mediapipe/calculators/image:warp_affine_calculator", | ||||
|         "//mediapipe/calculators/image:warp_affine_calculator_cc_proto", | ||||
|         "//mediapipe/calculators/tensor:image_to_tensor_calculator_cc_proto", | ||||
|         "//mediapipe/calculators/tensor:inference_calculator", | ||||
|         "//mediapipe/calculators/util:from_image_calculator", | ||||
|         "//mediapipe/calculators/util:inverse_matrix_calculator", | ||||
|         "//mediapipe/calculators/util:to_image_calculator", | ||||
|         "//mediapipe/framework/api2:builder", | ||||
|         "//mediapipe/framework/api2:port", | ||||
|         "//mediapipe/framework/formats:image", | ||||
|         "//mediapipe/framework/formats:rect_cc_proto", | ||||
|         "//mediapipe/framework/formats:tensor", | ||||
|         "//mediapipe/framework/port:status", | ||||
|         "//mediapipe/gpu:gpu_origin_cc_proto", | ||||
|         "//mediapipe/tasks/cc:common", | ||||
|         "//mediapipe/tasks/cc/components/processors:image_preprocessing_graph", | ||||
|         "//mediapipe/tasks/cc/core:model_task_graph", | ||||
|         "//mediapipe/tasks/cc/vision/face_stylizer/calculators:strip_rotation_calculator", | ||||
|         "//mediapipe/tasks/cc/vision/face_stylizer/calculators:tensors_to_image_calculator", | ||||
|         "//mediapipe/tasks/cc/vision/face_stylizer/calculators:tensors_to_image_calculator_cc_proto", | ||||
|         "//mediapipe/tasks/cc/vision/face_stylizer/proto:face_stylizer_graph_options_cc_proto", | ||||
|         "@com_google_absl//absl/status:statusor", | ||||
|     ], | ||||
| ) | ||||
| 
 | ||||
| cc_library( | ||||
|     name = "face_stylizer", | ||||
|     srcs = ["face_stylizer.cc"], | ||||
|     hdrs = ["face_stylizer.h"], | ||||
|     deps = [ | ||||
|         ":face_stylizer_graph",  # buildcleaner:keep | ||||
|         "//mediapipe/framework/api2:builder", | ||||
|         "//mediapipe/framework/formats:image", | ||||
|         "//mediapipe/tasks/cc/core:base_options", | ||||
|         "//mediapipe/tasks/cc/core:utils", | ||||
|         "//mediapipe/tasks/cc/vision/core:base_vision_task_api", | ||||
|         "//mediapipe/tasks/cc/vision/core:image_processing_options", | ||||
|         "//mediapipe/tasks/cc/vision/core:running_mode", | ||||
|         "//mediapipe/tasks/cc/vision/core:vision_task_api_factory", | ||||
|         "//mediapipe/tasks/cc/vision/face_stylizer/proto:face_stylizer_graph_options_cc_proto", | ||||
|         "@com_google_absl//absl/status", | ||||
|         "@com_google_absl//absl/status:statusor", | ||||
|         "@com_google_absl//absl/strings", | ||||
|         "@org_tensorflow//tensorflow/lite/core/api:op_resolver", | ||||
|         "@org_tensorflow//tensorflow/lite/kernels:builtin_ops", | ||||
|     ], | ||||
| ) | ||||
|  | @ -50,6 +50,7 @@ cc_library( | |||
|         ":tensors_to_image_calculator_cc_proto", | ||||
|         "@com_google_absl//absl/status", | ||||
|         "@com_google_absl//absl/strings", | ||||
|         "//mediapipe/calculators/tensor:image_to_tensor_utils", | ||||
|         "//mediapipe/framework:calculator_framework", | ||||
|         "//mediapipe/framework:calculator_options_cc_proto", | ||||
|         "//mediapipe/framework/api2:builder", | ||||
|  | @ -57,8 +58,11 @@ cc_library( | |||
|         "//mediapipe/framework/api2:packet", | ||||
|         "//mediapipe/framework/api2:port", | ||||
|         "//mediapipe/framework/formats:image", | ||||
|         "//mediapipe/framework/formats:image_frame_opencv", | ||||
|         "//mediapipe/framework/formats:tensor", | ||||
|         "//mediapipe/framework/port:logging", | ||||
|         "//mediapipe/framework/port:opencv_core", | ||||
|         "//mediapipe/framework/port:opencv_imgproc", | ||||
|         "//mediapipe/framework/port:ret_check", | ||||
|         "//mediapipe/framework/port:status", | ||||
|         "//mediapipe/framework/port:vector", | ||||
|  | @ -106,3 +110,16 @@ cc_library( | |||
|         ], | ||||
|     }), | ||||
| ) | ||||
| 
 | ||||
| cc_library( | ||||
|     name = "strip_rotation_calculator", | ||||
|     srcs = ["strip_rotation_calculator.cc"], | ||||
|     deps = [ | ||||
|         "//mediapipe/framework:calculator_framework", | ||||
|         "//mediapipe/framework/api2:node", | ||||
|         "//mediapipe/framework/api2:packet", | ||||
|         "//mediapipe/framework/api2:port", | ||||
|         "//mediapipe/framework/formats:rect_cc_proto", | ||||
|     ], | ||||
|     alwayslink = 1, | ||||
| ) | ||||
|  |  | |||
|  | @ -0,0 +1,50 @@ | |||
| /* 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 "mediapipe/framework/api2/node.h" | ||||
| #include "mediapipe/framework/api2/packet.h" | ||||
| #include "mediapipe/framework/api2/port.h" | ||||
| #include "mediapipe/framework/calculator_framework.h" | ||||
| #include "mediapipe/framework/formats/rect.pb.h" | ||||
| 
 | ||||
| namespace mediapipe { | ||||
| namespace tasks { | ||||
| namespace { | ||||
| using ::mediapipe::api2::Input; | ||||
| using ::mediapipe::api2::Node; | ||||
| using ::mediapipe::api2::Output; | ||||
| }  // namespace
 | ||||
| 
 | ||||
| // A calculator to strip the rotation information from the NormalizedRect.
 | ||||
| class StripRotationCalculator : public Node { | ||||
|  public: | ||||
|   static constexpr Input<NormalizedRect> kInNormRect{"NORM_RECT"}; | ||||
|   static constexpr Output<NormalizedRect> kOutNormRect{"NORM_RECT"}; | ||||
|   MEDIAPIPE_NODE_CONTRACT(kInNormRect, kOutNormRect); | ||||
| 
 | ||||
|   absl::Status Process(CalculatorContext* cc) { | ||||
|     if (!kInNormRect(cc).IsEmpty()) { | ||||
|       NormalizedRect rect = kInNormRect(cc).Get(); | ||||
|       rect.clear_rotation(); | ||||
|       kOutNormRect(cc).Send(rect); | ||||
|     } | ||||
|     return absl::OkStatus(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| MEDIAPIPE_REGISTER_NODE(::mediapipe::tasks::StripRotationCalculator); | ||||
| 
 | ||||
| }  // namespace tasks
 | ||||
| }  // namespace mediapipe
 | ||||
|  | @ -18,14 +18,18 @@ | |||
| 
 | ||||
| #include "absl/status/status.h" | ||||
| #include "absl/strings/str_cat.h" | ||||
| #include "mediapipe/calculators/tensor/image_to_tensor_utils.h" | ||||
| #include "mediapipe/framework/api2/node.h" | ||||
| #include "mediapipe/framework/api2/packet.h" | ||||
| #include "mediapipe/framework/api2/port.h" | ||||
| #include "mediapipe/framework/calculator_framework.h" | ||||
| #include "mediapipe/framework/calculator_options.pb.h" | ||||
| #include "mediapipe/framework/formats/image.h" | ||||
| #include "mediapipe/framework/formats/image_frame_opencv.h" | ||||
| #include "mediapipe/framework/formats/tensor.h" | ||||
| #include "mediapipe/framework/port/logging.h" | ||||
| #include "mediapipe/framework/port/opencv_core_inc.h" | ||||
| #include "mediapipe/framework/port/opencv_imgproc_inc.h" | ||||
| #include "mediapipe/framework/port/status.h" | ||||
| #include "mediapipe/gpu/gpu_origin.pb.h" | ||||
| #include "mediapipe/tasks/cc/vision/face_stylizer/calculators/tensors_to_image_calculator.pb.h" | ||||
|  | @ -74,6 +78,15 @@ static int NumGroups(const int size, const int group_size) {  // NOLINT | |||
|   return (size + group_size - 1) / group_size; | ||||
| } | ||||
| 
 | ||||
| bool CanUseGpu() { | ||||
| #if !MEDIAPIPE_DISABLE_GPU || MEDIAPIPE_METAL_ENABLED | ||||
|   constexpr bool kAllowGpuProcessing = true; | ||||
|   return kAllowGpuProcessing; | ||||
| #else | ||||
|   return false; | ||||
| #endif  // !MEDIAPIPE_DISABLE_GPU || MEDIAPIPE_METAL_ENABLED
 | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| // Converts a MediaPipe tensor to a MediaPipe Image.
 | ||||
|  | @ -83,8 +96,6 @@ static int NumGroups(const int size, const int group_size) {  // NOLINT | |||
| //
 | ||||
| // Output streams:
 | ||||
| //   OUTPUT - mediapipe::Image.
 | ||||
| //
 | ||||
| // TODO: Enable TensorsToImageCalculator to run on CPU.
 | ||||
| class TensorsToImageCalculator : public Node { | ||||
|  public: | ||||
|   static constexpr Input<std::vector<Tensor>> kInputTensors{"TENSORS"}; | ||||
|  | @ -98,6 +109,9 @@ class TensorsToImageCalculator : public Node { | |||
|   absl::Status Close(CalculatorContext* cc); | ||||
| 
 | ||||
|  private: | ||||
|   TensorsToImageCalculatorOptions options_; | ||||
|   absl::Status CpuProcess(CalculatorContext* cc); | ||||
| 
 | ||||
| #if !MEDIAPIPE_DISABLE_GPU | ||||
| #if MEDIAPIPE_METAL_ENABLED | ||||
|   bool metal_initialized_ = false; | ||||
|  | @ -108,7 +122,7 @@ class TensorsToImageCalculator : public Node { | |||
|   absl::Status MetalProcess(CalculatorContext* cc); | ||||
| #else | ||||
|   absl::Status GlSetup(CalculatorContext* cc); | ||||
| 
 | ||||
|   absl::Status GlProcess(CalculatorContext* cc); | ||||
|   GlCalculatorHelper gl_helper_; | ||||
| 
 | ||||
|   bool gl_initialized_ = false; | ||||
|  | @ -136,112 +150,37 @@ absl::Status TensorsToImageCalculator::UpdateContract(CalculatorContract* cc) { | |||
| } | ||||
| 
 | ||||
| absl::Status TensorsToImageCalculator::Open(CalculatorContext* cc) { | ||||
|   options_ = cc->Options<TensorsToImageCalculatorOptions>(); | ||||
|   if (CanUseGpu()) { | ||||
| #if !MEDIAPIPE_DISABLE_GPU | ||||
| #if MEDIAPIPE_METAL_ENABLED | ||||
|   gpu_helper_ = [[MPPMetalHelper alloc] initWithCalculatorContext:cc]; | ||||
|   RET_CHECK(gpu_helper_); | ||||
|     gpu_helper_ = [[MPPMetalHelper alloc] initWithCalculatorContext:cc]; | ||||
|     RET_CHECK(gpu_helper_); | ||||
| #else | ||||
|   MP_RETURN_IF_ERROR(gl_helper_.Open(cc)); | ||||
|     MP_RETURN_IF_ERROR(gl_helper_.Open(cc)); | ||||
| #endif  // MEDIAPIPE_METAL_ENABLED
 | ||||
| #endif  // !MEDIAPIPE_DISABLE_GPU
 | ||||
|   } else { | ||||
|     CHECK(options_.has_input_tensor_float_range() ^ | ||||
|           options_.has_input_tensor_uint_range()) | ||||
|         << "Must specify either `input_tensor_float_range` or " | ||||
|            "`input_tensor_uint_range` in the calculator options"; | ||||
|   } | ||||
| 
 | ||||
|   return absl::OkStatus(); | ||||
| } | ||||
| 
 | ||||
| absl::Status TensorsToImageCalculator::Process(CalculatorContext* cc) { | ||||
|   if (CanUseGpu()) { | ||||
| #if !MEDIAPIPE_DISABLE_GPU | ||||
| #if MEDIAPIPE_METAL_ENABLED | ||||
| 
 | ||||
|   return MetalProcess(cc); | ||||
| 
 | ||||
|     return MetalProcess(cc); | ||||
| #else | ||||
| 
 | ||||
|   return gl_helper_.RunInGlContext([this, cc]() -> absl::Status { | ||||
|     if (!gl_initialized_) { | ||||
|       MP_RETURN_IF_ERROR(GlSetup(cc)); | ||||
|       gl_initialized_ = true; | ||||
|     } | ||||
| 
 | ||||
|     if (kInputTensors(cc).IsEmpty()) { | ||||
|       return absl::OkStatus(); | ||||
|     } | ||||
|     const auto& input_tensors = kInputTensors(cc).Get(); | ||||
|     RET_CHECK_EQ(input_tensors.size(), 1) | ||||
|         << "Expect 1 input tensor, but have " << input_tensors.size(); | ||||
|     const int tensor_width = input_tensors[0].shape().dims[2]; | ||||
|     const int tensor_height = input_tensors[0].shape().dims[1]; | ||||
| 
 | ||||
| #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_31 | ||||
| 
 | ||||
|     auto out_texture = std::make_unique<tflite::gpu::gl::GlTexture>(); | ||||
|     MP_RETURN_IF_ERROR(CreateReadWriteRgbaImageTexture( | ||||
|         tflite::gpu::DataType::UINT8,  // GL_RGBA8
 | ||||
|         {tensor_width, tensor_height}, out_texture.get())); | ||||
| 
 | ||||
|     const int output_index = 0; | ||||
|     glBindImageTexture(output_index, out_texture->id(), 0, GL_FALSE, 0, | ||||
|                        GL_WRITE_ONLY, GL_RGBA8); | ||||
| 
 | ||||
|     auto read_view = input_tensors[0].GetOpenGlBufferReadView(); | ||||
|     glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, read_view.name()); | ||||
| 
 | ||||
|     const tflite::gpu::uint3 workload = {tensor_width, tensor_height, 1}; | ||||
|     const tflite::gpu::uint3 workgroups = | ||||
|         tflite::gpu::DivideRoundUp(workload, workgroup_size_); | ||||
| 
 | ||||
|     glUseProgram(gl_compute_program_->id()); | ||||
|     glUniform2i(glGetUniformLocation(gl_compute_program_->id(), "out_size"), | ||||
|                 tensor_width, tensor_height); | ||||
| 
 | ||||
|     MP_RETURN_IF_ERROR(gl_compute_program_->Dispatch(workgroups)); | ||||
| 
 | ||||
|     auto texture_buffer = mediapipe::GlTextureBuffer::Wrap( | ||||
|         out_texture->target(), out_texture->id(), tensor_width, tensor_height, | ||||
|         mediapipe::GpuBufferFormat::kBGRA32, | ||||
|         [ptr = out_texture.release()]( | ||||
|             std::shared_ptr<mediapipe::GlSyncPoint> sync_token) mutable { | ||||
|           delete ptr; | ||||
|         }); | ||||
| 
 | ||||
|     auto output = | ||||
|         std::make_unique<mediapipe::GpuBuffer>(std::move(texture_buffer)); | ||||
|     kOutputImage(cc).Send(Image(*output)); | ||||
|     ; | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
|     if (!input_tensors[0].ready_as_opengl_texture_2d()) { | ||||
|       (void)input_tensors[0].GetCpuReadView(); | ||||
|     } | ||||
| 
 | ||||
|     auto output_texture = | ||||
|         gl_helper_.CreateDestinationTexture(tensor_width, tensor_height); | ||||
|     gl_helper_.BindFramebuffer(output_texture);  // GL_TEXTURE0
 | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     glBindTexture(GL_TEXTURE_2D, | ||||
|                   input_tensors[0].GetOpenGlTexture2dReadView().name()); | ||||
| 
 | ||||
|     MP_RETURN_IF_ERROR(gl_renderer_->GlRender( | ||||
|         tensor_width, tensor_height, output_texture.width(), | ||||
|         output_texture.height(), mediapipe::FrameScaleMode::kStretch, | ||||
|         mediapipe::FrameRotation::kNone, | ||||
|         /*flip_horizontal=*/false, /*flip_vertical=*/false, | ||||
|         /*flip_texture=*/false)); | ||||
| 
 | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     glBindTexture(GL_TEXTURE_2D, 0); | ||||
| 
 | ||||
|     auto output = output_texture.GetFrame<GpuBuffer>(); | ||||
|     kOutputImage(cc).Send(Image(*output)); | ||||
| 
 | ||||
| #endif  // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_31
 | ||||
| 
 | ||||
|     return mediapipe::OkStatus(); | ||||
|   }); | ||||
| 
 | ||||
|     return GlProcess(cc); | ||||
| #endif  // MEDIAPIPE_METAL_ENABLED
 | ||||
| #endif  // !MEDIAPIPE_DISABLE_GPU
 | ||||
|   return absl::OkStatus(); | ||||
|   } | ||||
|   return CpuProcess(cc); | ||||
| } | ||||
| 
 | ||||
| absl::Status TensorsToImageCalculator::Close(CalculatorContext* cc) { | ||||
|  | @ -258,6 +197,61 @@ absl::Status TensorsToImageCalculator::Close(CalculatorContext* cc) { | |||
|   return absl::OkStatus(); | ||||
| } | ||||
| 
 | ||||
| absl::Status TensorsToImageCalculator::CpuProcess(CalculatorContext* cc) { | ||||
|   if (kInputTensors(cc).IsEmpty()) { | ||||
|     return absl::OkStatus(); | ||||
|   } | ||||
|   const auto& input_tensors = kInputTensors(cc).Get(); | ||||
|   RET_CHECK_EQ(input_tensors.size(), 1) | ||||
|       << "Expect 1 input tensor, but have " << input_tensors.size(); | ||||
| 
 | ||||
|   const auto& input_tensor = input_tensors[0]; | ||||
|   const int tensor_in_height = input_tensor.shape().dims[1]; | ||||
|   const int tensor_in_width = input_tensor.shape().dims[2]; | ||||
|   const int tensor_in_channels = input_tensor.shape().dims[3]; | ||||
|   RET_CHECK_EQ(tensor_in_channels, 3); | ||||
| 
 | ||||
|   auto output_frame = std::make_shared<ImageFrame>( | ||||
|       mediapipe::ImageFormat::SRGB, tensor_in_width, tensor_in_height); | ||||
|   cv::Mat output_matview = mediapipe::formats::MatView(output_frame.get()); | ||||
| 
 | ||||
|   constexpr float kOutputImageRangeMin = 0.0f; | ||||
|   constexpr float kOutputImageRangeMax = 255.0f; | ||||
|   if (input_tensor.element_type() == Tensor::ElementType::kFloat32) { | ||||
|     cv::Mat tensor_matview( | ||||
|         cv::Size(tensor_in_width, tensor_in_height), | ||||
|         CV_MAKETYPE(CV_32F, tensor_in_channels), | ||||
|         const_cast<float*>(input_tensor.GetCpuReadView().buffer<float>())); | ||||
|     auto input_range = options_.input_tensor_float_range(); | ||||
|     ASSIGN_OR_RETURN(auto transform, | ||||
|                      GetValueRangeTransformation( | ||||
|                          input_range.min(), input_range.max(), | ||||
|                          kOutputImageRangeMin, kOutputImageRangeMax)); | ||||
|     tensor_matview.convertTo(output_matview, CV_8UC3, transform.scale, | ||||
|                              transform.offset); | ||||
|   } else if (input_tensor.element_type() == Tensor::ElementType::kUInt8) { | ||||
|     cv::Mat tensor_matview( | ||||
|         cv::Size(tensor_in_width, tensor_in_height), | ||||
|         CV_MAKETYPE(CV_8U, tensor_in_channels), | ||||
|         const_cast<uint8_t*>(input_tensor.GetCpuReadView().buffer<uint8_t>())); | ||||
|     auto input_range = options_.input_tensor_uint_range(); | ||||
|     ASSIGN_OR_RETURN(auto transform, | ||||
|                      GetValueRangeTransformation( | ||||
|                          input_range.min(), input_range.max(), | ||||
|                          kOutputImageRangeMin, kOutputImageRangeMax)); | ||||
|     tensor_matview.convertTo(output_matview, CV_8UC3, transform.scale, | ||||
|                              transform.offset); | ||||
|   } else { | ||||
|     return absl::InvalidArgumentError( | ||||
|         absl::Substitute("Type of tensor must be kFloat32 or kUInt8, got: $0", | ||||
|                          input_tensor.element_type())); | ||||
|   } | ||||
| 
 | ||||
|   kOutputImage(cc).Send(Image(output_frame)); | ||||
| 
 | ||||
|   return absl::OkStatus(); | ||||
| } | ||||
| 
 | ||||
| #if MEDIAPIPE_METAL_ENABLED | ||||
| 
 | ||||
| absl::Status TensorsToImageCalculator::MetalProcess(CalculatorContext* cc) { | ||||
|  | @ -433,6 +427,90 @@ absl::Status TensorsToImageCalculator::GlSetup(CalculatorContext* cc) { | |||
|   return mediapipe::OkStatus(); | ||||
| } | ||||
| 
 | ||||
| absl::Status TensorsToImageCalculator::GlProcess(CalculatorContext* cc) { | ||||
|   return gl_helper_.RunInGlContext([this, cc]() -> absl::Status { | ||||
|     if (!gl_initialized_) { | ||||
|       MP_RETURN_IF_ERROR(GlSetup(cc)); | ||||
|       gl_initialized_ = true; | ||||
|     } | ||||
| 
 | ||||
|     if (kInputTensors(cc).IsEmpty()) { | ||||
|       return absl::OkStatus(); | ||||
|     } | ||||
|     const auto& input_tensors = kInputTensors(cc).Get(); | ||||
|     RET_CHECK_EQ(input_tensors.size(), 1) | ||||
|         << "Expect 1 input tensor, but have " << input_tensors.size(); | ||||
|     const int tensor_width = input_tensors[0].shape().dims[2]; | ||||
|     const int tensor_height = input_tensors[0].shape().dims[1]; | ||||
| 
 | ||||
| #if MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_31 | ||||
| 
 | ||||
|     auto out_texture = std::make_unique<tflite::gpu::gl::GlTexture>(); | ||||
|     MP_RETURN_IF_ERROR(CreateReadWriteRgbaImageTexture( | ||||
|         tflite::gpu::DataType::UINT8,  // GL_RGBA8
 | ||||
|         {tensor_width, tensor_height}, out_texture.get())); | ||||
| 
 | ||||
|     const int output_index = 0; | ||||
|     glBindImageTexture(output_index, out_texture->id(), 0, GL_FALSE, 0, | ||||
|                        GL_WRITE_ONLY, GL_RGBA8); | ||||
| 
 | ||||
|     auto read_view = input_tensors[0].GetOpenGlBufferReadView(); | ||||
|     glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, read_view.name()); | ||||
| 
 | ||||
|     const tflite::gpu::uint3 workload = {tensor_width, tensor_height, 1}; | ||||
|     const tflite::gpu::uint3 workgroups = | ||||
|         tflite::gpu::DivideRoundUp(workload, workgroup_size_); | ||||
| 
 | ||||
|     glUseProgram(gl_compute_program_->id()); | ||||
|     glUniform2i(glGetUniformLocation(gl_compute_program_->id(), "out_size"), | ||||
|                 tensor_width, tensor_height); | ||||
| 
 | ||||
|     MP_RETURN_IF_ERROR(gl_compute_program_->Dispatch(workgroups)); | ||||
| 
 | ||||
|     auto texture_buffer = mediapipe::GlTextureBuffer::Wrap( | ||||
|         out_texture->target(), out_texture->id(), tensor_width, tensor_height, | ||||
|         mediapipe::GpuBufferFormat::kBGRA32, | ||||
|         [ptr = out_texture.release()]( | ||||
|             std::shared_ptr<mediapipe::GlSyncPoint> sync_token) mutable { | ||||
|           delete ptr; | ||||
|         }); | ||||
| 
 | ||||
|     auto output = | ||||
|         std::make_unique<mediapipe::GpuBuffer>(std::move(texture_buffer)); | ||||
|     kOutputImage(cc).Send(Image(*output)); | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
|     if (!input_tensors[0].ready_as_opengl_texture_2d()) { | ||||
|       (void)input_tensors[0].GetCpuReadView(); | ||||
|     } | ||||
| 
 | ||||
|     auto output_texture = | ||||
|         gl_helper_.CreateDestinationTexture(tensor_width, tensor_height); | ||||
|     gl_helper_.BindFramebuffer(output_texture);  // GL_TEXTURE0
 | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     glBindTexture(GL_TEXTURE_2D, | ||||
|                   input_tensors[0].GetOpenGlTexture2dReadView().name()); | ||||
| 
 | ||||
|     MP_RETURN_IF_ERROR(gl_renderer_->GlRender( | ||||
|         tensor_width, tensor_height, output_texture.width(), | ||||
|         output_texture.height(), mediapipe::FrameScaleMode::kStretch, | ||||
|         mediapipe::FrameRotation::kNone, | ||||
|         /*flip_horizontal=*/false, /*flip_vertical=*/false, | ||||
|         /*flip_texture=*/false)); | ||||
| 
 | ||||
|     glActiveTexture(GL_TEXTURE1); | ||||
|     glBindTexture(GL_TEXTURE_2D, 0); | ||||
| 
 | ||||
|     auto output = output_texture.GetFrame<GpuBuffer>(); | ||||
|     kOutputImage(cc).Send(Image(*output)); | ||||
| 
 | ||||
| #endif  // MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_31
 | ||||
| 
 | ||||
|     return mediapipe::OkStatus(); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| #endif  // !MEDIAPIPE_DISABLE_GPU && !MEDIAPIPE_METAL_ENABLED
 | ||||
| 
 | ||||
| }  // namespace tasks
 | ||||
|  |  | |||
|  | @ -28,4 +28,24 @@ message TensorsToImageCalculatorOptions { | |||
|   // to be flipped vertically as tensors are expected to start at top. | ||||
|   // (DEFAULT or unset interpreted as CONVENTIONAL.) | ||||
|   optional mediapipe.GpuOrigin.Mode gpu_origin = 1; | ||||
| 
 | ||||
|   // Range of float values [min, max]. | ||||
|   // min, must be strictly less than max. | ||||
|   message FloatRange { | ||||
|     optional float min = 1; | ||||
|     optional float max = 2; | ||||
|   } | ||||
| 
 | ||||
|   // Range of uint values [min, max]. | ||||
|   // min, must be strictly less than max. | ||||
|   message UIntRange { | ||||
|     optional uint64 min = 1; | ||||
|     optional uint64 max = 2; | ||||
|   } | ||||
| 
 | ||||
|   // The input tensor element range/type image pixels are converted from. | ||||
|   oneof range { | ||||
|     FloatRange input_tensor_float_range = 2; | ||||
|     UIntRange input_tensor_uint_range = 3; | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										196
									
								
								mediapipe/tasks/cc/vision/face_stylizer/face_stylizer.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								mediapipe/tasks/cc/vision/face_stylizer/face_stylizer.cc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,196 @@ | |||
| /* 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 "mediapipe/tasks/cc/vision/face_stylizer/face_stylizer.h" | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <map> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <string> | ||||
| #include <utility> | ||||
| 
 | ||||
| #include "absl/status/status.h" | ||||
| #include "absl/status/statusor.h" | ||||
| #include "absl/strings/str_cat.h" | ||||
| #include "mediapipe/framework/api2/builder.h" | ||||
| #include "mediapipe/framework/formats/image.h" | ||||
| #include "mediapipe/tasks/cc/core/utils.h" | ||||
| #include "mediapipe/tasks/cc/vision/core/running_mode.h" | ||||
| #include "mediapipe/tasks/cc/vision/core/vision_task_api_factory.h" | ||||
| #include "mediapipe/tasks/cc/vision/face_stylizer/proto/face_stylizer_graph_options.pb.h" | ||||
| #include "tensorflow/lite/core/api/op_resolver.h" | ||||
| 
 | ||||
| namespace mediapipe { | ||||
| namespace tasks { | ||||
| namespace vision { | ||||
| namespace face_stylizer { | ||||
| namespace { | ||||
| 
 | ||||
| constexpr char kImageInStreamName[] = "image_in"; | ||||
| constexpr char kImageOutStreamName[] = "image_out"; | ||||
| constexpr char kImageTag[] = "IMAGE"; | ||||
| constexpr char kNormRectName[] = "norm_rect_in"; | ||||
| constexpr char kNormRectTag[] = "NORM_RECT"; | ||||
| constexpr char kSubgraphTypeName[] = | ||||
|     "mediapipe.tasks.vision.face_stylizer.FaceStylizerGraph"; | ||||
| constexpr char kStylizedImageTag[] = "STYLIZED_IMAGE"; | ||||
| constexpr char kStylizedImageName[] = "stylized_image"; | ||||
| constexpr int kMicroSecondsPerMilliSecond = 1000; | ||||
| 
 | ||||
| using FaceStylizerGraphOptionsProto = | ||||
|     ::mediapipe::tasks::vision::face_stylizer::proto::FaceStylizerGraphOptions; | ||||
| 
 | ||||
| // Creates a MediaPipe graph config that only contains a single subgraph node of
 | ||||
| // "mediapipe.tasks.vision.face_stylizer.FaceStylizerGraph".
 | ||||
| CalculatorGraphConfig CreateGraphConfig( | ||||
|     std::unique_ptr<FaceStylizerGraphOptionsProto> options, | ||||
|     bool enable_flow_limiting) { | ||||
|   api2::builder::Graph graph; | ||||
|   auto& task_subgraph = graph.AddNode(kSubgraphTypeName); | ||||
|   task_subgraph.GetOptions<FaceStylizerGraphOptionsProto>().Swap(options.get()); | ||||
|   graph.In(kImageTag).SetName(kImageInStreamName); | ||||
|   graph.In(kNormRectTag).SetName(kNormRectName); | ||||
|   task_subgraph.Out(kImageTag).SetName(kImageOutStreamName) >> | ||||
|       graph.Out(kImageTag); | ||||
|   task_subgraph.Out(kStylizedImageTag).SetName(kStylizedImageName) >> | ||||
|       graph.Out(kStylizedImageTag); | ||||
|   if (enable_flow_limiting) { | ||||
|     return tasks::core::AddFlowLimiterCalculator( | ||||
|         graph, task_subgraph, {kImageTag, kNormRectTag}, kStylizedImageTag); | ||||
|   } | ||||
|   graph.In(kImageTag) >> task_subgraph.In(kImageTag); | ||||
|   graph.In(kNormRectTag) >> task_subgraph.In(kNormRectTag); | ||||
|   return graph.GetConfig(); | ||||
| } | ||||
| 
 | ||||
| // Converts the user-facing FaceStylizerOptions struct to the internal
 | ||||
| // FaceStylizerGraphOptions proto.
 | ||||
| std::unique_ptr<FaceStylizerGraphOptionsProto> | ||||
| ConvertFaceStylizerOptionsToProto(FaceStylizerOptions* options) { | ||||
|   auto options_proto = std::make_unique<FaceStylizerGraphOptionsProto>(); | ||||
|   auto base_options_proto = std::make_unique<tasks::core::proto::BaseOptions>( | ||||
|       tasks::core::ConvertBaseOptionsToProto(&(options->base_options))); | ||||
|   options_proto->mutable_base_options()->Swap(base_options_proto.get()); | ||||
|   options_proto->mutable_base_options()->set_use_stream_mode( | ||||
|       options->running_mode != core::RunningMode::IMAGE); | ||||
|   return options_proto; | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| absl::StatusOr<std::unique_ptr<FaceStylizer>> FaceStylizer::Create( | ||||
|     std::unique_ptr<FaceStylizerOptions> options) { | ||||
|   auto options_proto = ConvertFaceStylizerOptionsToProto(options.get()); | ||||
|   tasks::core::PacketsCallback packets_callback = nullptr; | ||||
|   if (options->result_callback) { | ||||
|     auto result_callback = options->result_callback; | ||||
|     packets_callback = | ||||
|         [=](absl::StatusOr<tasks::core::PacketMap> status_or_packets) { | ||||
|           if (!status_or_packets.ok()) { | ||||
|             Image image; | ||||
|             result_callback(status_or_packets.status(), image, | ||||
|                             Timestamp::Unset().Value()); | ||||
|             return; | ||||
|           } | ||||
|           if (status_or_packets.value()[kImageOutStreamName].IsEmpty()) { | ||||
|             return; | ||||
|           } | ||||
|           Packet stylized_image_packet = | ||||
|               status_or_packets.value()[kStylizedImageName]; | ||||
|           Packet image_packet = status_or_packets.value()[kImageOutStreamName]; | ||||
|           result_callback(stylized_image_packet.Get<Image>(), | ||||
|                           image_packet.Get<Image>(), | ||||
|                           stylized_image_packet.Timestamp().Value() / | ||||
|                               kMicroSecondsPerMilliSecond); | ||||
|         }; | ||||
|   } | ||||
|   return core::VisionTaskApiFactory::Create<FaceStylizer, | ||||
|                                             FaceStylizerGraphOptionsProto>( | ||||
|       CreateGraphConfig( | ||||
|           std::move(options_proto), | ||||
|           options->running_mode == core::RunningMode::LIVE_STREAM), | ||||
|       std::move(options->base_options.op_resolver), options->running_mode, | ||||
|       std::move(packets_callback)); | ||||
| } | ||||
| 
 | ||||
| absl::StatusOr<Image> FaceStylizer::Stylize( | ||||
|     mediapipe::Image image, | ||||
|     std::optional<core::ImageProcessingOptions> image_processing_options) { | ||||
|   if (image.UsesGpu()) { | ||||
|     return CreateStatusWithPayload( | ||||
|         absl::StatusCode::kInvalidArgument, | ||||
|         absl::StrCat("GPU input images are currently not supported."), | ||||
|         MediaPipeTasksStatus::kRunnerUnexpectedInputError); | ||||
|   } | ||||
|   ASSIGN_OR_RETURN(NormalizedRect norm_rect, | ||||
|                    ConvertToNormalizedRect(image_processing_options)); | ||||
|   ASSIGN_OR_RETURN( | ||||
|       auto output_packets, | ||||
|       ProcessImageData( | ||||
|           {{kImageInStreamName, MakePacket<Image>(std::move(image))}, | ||||
|            {kNormRectName, MakePacket<NormalizedRect>(std::move(norm_rect))}})); | ||||
|   return output_packets[kStylizedImageName].Get<Image>(); | ||||
| } | ||||
| 
 | ||||
| absl::StatusOr<Image> FaceStylizer::StylizeForVideo( | ||||
|     mediapipe::Image image, int64_t timestamp_ms, | ||||
|     std::optional<core::ImageProcessingOptions> image_processing_options) { | ||||
|   if (image.UsesGpu()) { | ||||
|     return CreateStatusWithPayload( | ||||
|         absl::StatusCode::kInvalidArgument, | ||||
|         absl::StrCat("GPU input images are currently not supported."), | ||||
|         MediaPipeTasksStatus::kRunnerUnexpectedInputError); | ||||
|   } | ||||
|   ASSIGN_OR_RETURN(NormalizedRect norm_rect, | ||||
|                    ConvertToNormalizedRect(image_processing_options)); | ||||
|   ASSIGN_OR_RETURN( | ||||
|       auto output_packets, | ||||
|       ProcessVideoData( | ||||
|           {{kImageInStreamName, | ||||
|             MakePacket<Image>(std::move(image)) | ||||
|                 .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}, | ||||
|            {kNormRectName, | ||||
|             MakePacket<NormalizedRect>(std::move(norm_rect)) | ||||
|                 .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}})); | ||||
|   return output_packets[kStylizedImageName].Get<Image>(); | ||||
| } | ||||
| 
 | ||||
| absl::Status FaceStylizer::StylizeAsync( | ||||
|     Image image, int64_t timestamp_ms, | ||||
|     std::optional<core::ImageProcessingOptions> image_processing_options) { | ||||
|   if (image.UsesGpu()) { | ||||
|     return CreateStatusWithPayload( | ||||
|         absl::StatusCode::kInvalidArgument, | ||||
|         absl::StrCat("GPU input images are currently not supported."), | ||||
|         MediaPipeTasksStatus::kRunnerUnexpectedInputError); | ||||
|   } | ||||
|   ASSIGN_OR_RETURN(NormalizedRect norm_rect, | ||||
|                    ConvertToNormalizedRect(image_processing_options)); | ||||
|   return SendLiveStreamData( | ||||
|       {{kImageInStreamName, | ||||
|         MakePacket<Image>(std::move(image)) | ||||
|             .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}, | ||||
|        {kNormRectName, | ||||
|         MakePacket<NormalizedRect>(std::move(norm_rect)) | ||||
|             .At(Timestamp(timestamp_ms * kMicroSecondsPerMilliSecond))}}); | ||||
| } | ||||
| 
 | ||||
| }  // namespace face_stylizer
 | ||||
| }  // namespace vision
 | ||||
| }  // namespace tasks
 | ||||
| }  // namespace mediapipe
 | ||||
							
								
								
									
										156
									
								
								mediapipe/tasks/cc/vision/face_stylizer/face_stylizer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								mediapipe/tasks/cc/vision/face_stylizer/face_stylizer.h
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,156 @@ | |||
| /* 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. | ||||
| ==============================================================================*/ | ||||
| 
 | ||||
| #ifndef MEDIAPIPE_TASKS_CC_VISION_FACE_STYLIZER_FACE_STYLIZER_H_ | ||||
| #define MEDIAPIPE_TASKS_CC_VISION_FACE_STYLIZER_FACE_STYLIZER_H_ | ||||
| 
 | ||||
| #include <functional> | ||||
| #include <memory> | ||||
| #include <optional> | ||||
| #include <utility> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "absl/status/status.h" | ||||
| #include "absl/status/statusor.h" | ||||
| #include "mediapipe/framework/formats/image.h" | ||||
| #include "mediapipe/tasks/cc/core/base_options.h" | ||||
| #include "mediapipe/tasks/cc/vision/core/base_vision_task_api.h" | ||||
| #include "mediapipe/tasks/cc/vision/core/image_processing_options.h" | ||||
| #include "tensorflow/lite/kernels/register.h" | ||||
| 
 | ||||
| namespace mediapipe { | ||||
| namespace tasks { | ||||
| namespace vision { | ||||
| namespace face_stylizer { | ||||
| 
 | ||||
| // The options for configuring a mediapipe face stylizer task.
 | ||||
| struct FaceStylizerOptions { | ||||
|   // Base options for configuring MediaPipe Tasks, such as specifying the model
 | ||||
|   // file with metadata, accelerator options, op resolver, etc.
 | ||||
|   tasks::core::BaseOptions base_options; | ||||
| 
 | ||||
|   // The running mode of the task. Default to the image mode.
 | ||||
|   // Face stylizer 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 the live stream of input
 | ||||
|   // data, such as from camera. In this mode, the "result_callback" below must
 | ||||
|   // be specified to receive the stylization results asynchronously.
 | ||||
|   core::RunningMode running_mode = core::RunningMode::IMAGE; | ||||
| 
 | ||||
|   // The user-defined result callback for processing live stream data.
 | ||||
|   // The result callback should only be specified when the running mode is set
 | ||||
|   // to RunningMode::LIVE_STREAM.
 | ||||
|   std::function<void(absl::StatusOr<mediapipe::Image>, const Image&, int64_t)> | ||||
|       result_callback = nullptr; | ||||
| }; | ||||
| 
 | ||||
| // Performs face stylization on images.
 | ||||
| class FaceStylizer : tasks::vision::core::BaseVisionTaskApi { | ||||
|  public: | ||||
|   using BaseVisionTaskApi::BaseVisionTaskApi; | ||||
| 
 | ||||
|   // Creates a FaceStylizer from the provided options.
 | ||||
|   static absl::StatusOr<std::unique_ptr<FaceStylizer>> Create( | ||||
|       std::unique_ptr<FaceStylizerOptions> options); | ||||
| 
 | ||||
|   // Performs face stylization on the provided single image.
 | ||||
|   //
 | ||||
|   // The optional 'image_processing_options' parameter can be used to specify:
 | ||||
|   //   - the rotation to apply to the image before performing stylization, by
 | ||||
|   //     setting its 'rotation_degrees' field.
 | ||||
|   //   and/or
 | ||||
|   //   - the region-of-interest on which to perform stylization, by setting its
 | ||||
|   //   'region_of_interest' field. If not specified, the full image is used.
 | ||||
|   // If both are specified, the crop around the region-of-interest is extracted
 | ||||
|   // first, then the specified rotation is applied to the crop.
 | ||||
|   //
 | ||||
|   // Only use this method when the FaceStylizer is created with the image
 | ||||
|   // running mode.
 | ||||
|   //
 | ||||
|   // The input image can be of any size with format RGB or RGBA.
 | ||||
|   // To ensure that the output image has reasonable quailty, the stylized output
 | ||||
|   // image size is the smaller of the model output size and the size of the
 | ||||
|   // 'region_of_interest' specified in 'image_processing_options'.
 | ||||
|   absl::StatusOr<mediapipe::Image> Stylize( | ||||
|       mediapipe::Image image, | ||||
|       std::optional<core::ImageProcessingOptions> image_processing_options = | ||||
|           std::nullopt); | ||||
| 
 | ||||
|   // Performs face stylization on the provided video frame.
 | ||||
|   //
 | ||||
|   // The optional 'image_processing_options' parameter can be used to specify:
 | ||||
|   //   - the rotation to apply to the image before performing stylization, by
 | ||||
|   //     setting its 'rotation_degrees' field.
 | ||||
|   //   and/or
 | ||||
|   //   - the region-of-interest on which to perform stylization, by setting its
 | ||||
|   //   'region_of_interest' field. If not specified, the full image is used.
 | ||||
|   // If both are specified, the crop around the region-of-interest is extracted
 | ||||
|   // first, then the specified rotation is applied to the crop.
 | ||||
|   //
 | ||||
|   // Only use this method when the FaceStylizer is created with the video
 | ||||
|   // running mode.
 | ||||
|   //
 | ||||
|   // The image can be of any size with format RGB or RGBA. It's required to
 | ||||
|   // provide the video frame's timestamp (in milliseconds). The input timestamps
 | ||||
|   // must be monotonically increasing.
 | ||||
|   // To ensure that the output image has reasonable quailty, the stylized output
 | ||||
|   // image size is the smaller of the model output size and the size of the
 | ||||
|   // 'region_of_interest' specified in 'image_processing_options'.
 | ||||
|   absl::StatusOr<mediapipe::Image> StylizeForVideo( | ||||
|       mediapipe::Image image, int64_t timestamp_ms, | ||||
|       std::optional<core::ImageProcessingOptions> image_processing_options = | ||||
|           std::nullopt); | ||||
| 
 | ||||
|   // Sends live image data to perform face stylization, and the results will
 | ||||
|   // be available via the "result_callback" provided in the
 | ||||
|   // FaceStylizerOptions.
 | ||||
|   //
 | ||||
|   // The optional 'image_processing_options' parameter can be used to specify:
 | ||||
|   //   - the rotation to apply to the image before performing stylization, by
 | ||||
|   //     setting its 'rotation_degrees' field.
 | ||||
|   //   and/or
 | ||||
|   //   - the region-of-interest on which to perform stylization, by setting its
 | ||||
|   //   'region_of_interest' field. If not specified, the full image is used.
 | ||||
|   // If both are specified, the crop around the region-of-interest is extracted
 | ||||
|   // first, then the specified rotation is applied to the crop.
 | ||||
|   //
 | ||||
|   // Only use this method when the FaceStylizer is created with the live stream
 | ||||
|   // running mode.
 | ||||
|   //
 | ||||
|   // The image can be of any size with format RGB or RGBA. It's required to
 | ||||
|   // provide a timestamp (in milliseconds) to indicate when the input image is
 | ||||
|   // sent to the face stylizer. The input timestamps must be monotonically
 | ||||
|   // increasing.
 | ||||
|   //
 | ||||
|   // The "result_callback" provides:
 | ||||
|   //   - The stylized image which size is the smaller of the model output size
 | ||||
|   //     and the size of the 'region_of_interest' specified in
 | ||||
|   //     'image_processing_options'.
 | ||||
|   //   - The input timestamp in milliseconds.
 | ||||
|   absl::Status StylizeAsync(mediapipe::Image image, int64_t timestamp_ms, | ||||
|                             std::optional<core::ImageProcessingOptions> | ||||
|                                 image_processing_options = std::nullopt); | ||||
| 
 | ||||
|   // Shuts down the FaceStylizer when all works are done.
 | ||||
|   absl::Status Close() { return runner_->Close(); } | ||||
| }; | ||||
| 
 | ||||
| }  // namespace face_stylizer
 | ||||
| }  // namespace vision
 | ||||
| }  // namespace tasks
 | ||||
| }  // namespace mediapipe
 | ||||
| 
 | ||||
| #endif  // MEDIAPIPE_TASKS_CC_VISION_FACE_STYLIZER_FACE_STYLIZER_H_
 | ||||
							
								
								
									
										246
									
								
								mediapipe/tasks/cc/vision/face_stylizer/face_stylizer_graph.cc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								mediapipe/tasks/cc/vision/face_stylizer/face_stylizer_graph.cc
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,246 @@ | |||
| /* 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 <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "absl/status/statusor.h" | ||||
| #include "mediapipe/calculators/image/image_cropping_calculator.pb.h" | ||||
| #include "mediapipe/calculators/image/warp_affine_calculator.pb.h" | ||||
| #include "mediapipe/calculators/tensor/image_to_tensor_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/rect.pb.h" | ||||
| #include "mediapipe/framework/formats/tensor.h" | ||||
| #include "mediapipe/framework/port/status_macros.h" | ||||
| #include "mediapipe/gpu/gpu_origin.pb.h" | ||||
| #include "mediapipe/tasks/cc/common.h" | ||||
| #include "mediapipe/tasks/cc/components/processors/image_preprocessing_graph.h" | ||||
| #include "mediapipe/tasks/cc/core/model_task_graph.h" | ||||
| #include "mediapipe/tasks/cc/vision/face_stylizer/calculators/tensors_to_image_calculator.pb.h" | ||||
| #include "mediapipe/tasks/cc/vision/face_stylizer/proto/face_stylizer_graph_options.pb.h" | ||||
| 
 | ||||
| namespace mediapipe { | ||||
| namespace tasks { | ||||
| namespace vision { | ||||
| namespace face_stylizer { | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| using ::mediapipe::api2::Input; | ||||
| using ::mediapipe::api2::Output; | ||||
| using ::mediapipe::api2::builder::Graph; | ||||
| using ::mediapipe::api2::builder::Source; | ||||
| using ::mediapipe::tasks::TensorsToImageCalculatorOptions; | ||||
| using ::mediapipe::tasks::core::ModelResources; | ||||
| using ::mediapipe::tasks::vision::face_stylizer::proto:: | ||||
|     FaceStylizerGraphOptions; | ||||
| 
 | ||||
| constexpr char kImageTag[] = "IMAGE"; | ||||
| constexpr char kImageCpuTag[] = "IMAGE_CPU"; | ||||
| constexpr char kImageGpuTag[] = "IMAGE_GPU"; | ||||
| constexpr char kImageSizeTag[] = "IMAGE_SIZE"; | ||||
| constexpr char kMatrixTag[] = "MATRIX"; | ||||
| constexpr char kNormRectTag[] = "NORM_RECT"; | ||||
| constexpr char kOutputSizeTag[] = "OUTPUT_SIZE"; | ||||
| constexpr char kStylizedImageTag[] = "STYLIZED_IMAGE"; | ||||
| constexpr char kTensorsTag[] = "TENSORS"; | ||||
| 
 | ||||
| // Struct holding the different output streams produced by the face stylizer
 | ||||
| // graph.
 | ||||
| struct FaceStylizerOutputStreams { | ||||
|   Source<Image> stylized_image; | ||||
|   Source<Image> original_image; | ||||
| }; | ||||
| 
 | ||||
| void ConfigureTensorsToImageCalculator( | ||||
|     const ImageToTensorCalculatorOptions& image_to_tensor_options, | ||||
|     TensorsToImageCalculatorOptions* tensors_to_image_options) { | ||||
|   tensors_to_image_options->set_gpu_origin(mediapipe::GpuOrigin_Mode_TOP_LEFT); | ||||
|   if (image_to_tensor_options.has_output_tensor_float_range()) { | ||||
|     auto* mutable_range = | ||||
|         tensors_to_image_options->mutable_input_tensor_float_range(); | ||||
|     // TODO: Make the float range flexiable.
 | ||||
|     mutable_range->set_min(0); | ||||
|     mutable_range->set_max(1); | ||||
|   } else if (image_to_tensor_options.has_output_tensor_uint_range()) { | ||||
|     auto* mutable_range = | ||||
|         tensors_to_image_options->mutable_input_tensor_uint_range(); | ||||
|     const auto& reference_range = | ||||
|         image_to_tensor_options.output_tensor_uint_range(); | ||||
|     mutable_range->set_min(reference_range.min()); | ||||
|     mutable_range->set_max(reference_range.max()); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| // A "mediapipe.tasks.vision.face_stylizer.FaceStylizerGraph" performs face
 | ||||
| // stylization.
 | ||||
| //
 | ||||
| // Inputs:
 | ||||
| //   IMAGE - Image
 | ||||
| //     Image to perform face stylization on.
 | ||||
| //   NORM_RECT - NormalizedRect @Optional
 | ||||
| //     Describes region of image to perform classification on.
 | ||||
| //     @Optional: rect covering the whole image is used if not specified.
 | ||||
| //
 | ||||
| // Outputs:
 | ||||
| //   IMAGE - mediapipe::Image
 | ||||
| //     The face stylization output image.
 | ||||
| //
 | ||||
| // Example:
 | ||||
| // node {
 | ||||
| //   calculator: "mediapipe.tasks.vision.face_stylizer.FaceStylizerGraph"
 | ||||
| //   input_stream: "IMAGE:image_in"
 | ||||
| //   input_stream: "NORM_RECT:norm_rect"
 | ||||
| //   output_stream: "IMAGE:image_out"
 | ||||
| //   output_stream: "STYLIZED_IMAGE:stylized_image"
 | ||||
| //   options {
 | ||||
| //     [mediapipe.tasks.vision.face_stylizer.proto.FaceStylizerGraphOptions.ext]
 | ||||
| //     {
 | ||||
| //       base_options {
 | ||||
| //         model_asset {
 | ||||
| //           file_name: "face_stylization.tflite"
 | ||||
| //         }
 | ||||
| //       }
 | ||||
| //     }
 | ||||
| //   }
 | ||||
| // }
 | ||||
| class FaceStylizerGraph : public core::ModelTaskGraph { | ||||
|  public: | ||||
|   absl::StatusOr<CalculatorGraphConfig> GetConfig( | ||||
|       SubgraphContext* sc) override { | ||||
|     ASSIGN_OR_RETURN(const auto* model_resources, | ||||
|                      CreateModelResources<FaceStylizerGraphOptions>(sc)); | ||||
|     Graph graph; | ||||
|     ASSIGN_OR_RETURN( | ||||
|         auto output_streams, | ||||
|         BuildFaceStylizerGraph( | ||||
|             sc->Options<FaceStylizerGraphOptions>(), *model_resources, | ||||
|             graph[Input<Image>(kImageTag)], | ||||
|             graph[Input<NormalizedRect>::Optional(kNormRectTag)], graph)); | ||||
|     output_streams.stylized_image >> graph[Output<Image>(kStylizedImageTag)]; | ||||
|     output_streams.original_image >> graph[Output<Image>(kImageTag)]; | ||||
|     return graph.GetConfig(); | ||||
|   } | ||||
| 
 | ||||
|  private: | ||||
|   absl::StatusOr<FaceStylizerOutputStreams> BuildFaceStylizerGraph( | ||||
|       const FaceStylizerGraphOptions& task_options, | ||||
|       const ModelResources& model_resources, Source<Image> image_in, | ||||
|       Source<NormalizedRect> norm_rect_in, Graph& graph) { | ||||
|     // Adds preprocessing calculators and connects them to the graph input image
 | ||||
|     // stream.
 | ||||
|     auto& preprocessing = graph.AddNode( | ||||
|         "mediapipe.tasks.components.processors.ImagePreprocessingGraph"); | ||||
|     bool use_gpu = | ||||
|         components::processors::DetermineImagePreprocessingGpuBackend( | ||||
|             task_options.base_options().acceleration()); | ||||
|     MP_RETURN_IF_ERROR(components::processors::ConfigureImagePreprocessingGraph( | ||||
|         model_resources, use_gpu, | ||||
|         &preprocessing.GetOptions<tasks::components::processors::proto:: | ||||
|                                       ImagePreprocessingGraphOptions>())); | ||||
|     auto& image_to_tensor_options = | ||||
|         *preprocessing | ||||
|              .GetOptions<components::processors::proto:: | ||||
|                              ImagePreprocessingGraphOptions>() | ||||
|              .mutable_image_to_tensor_options(); | ||||
|     image_to_tensor_options.set_keep_aspect_ratio(true); | ||||
|     image_to_tensor_options.set_border_mode( | ||||
|         mediapipe::ImageToTensorCalculatorOptions::BORDER_ZERO); | ||||
|     image_in >> preprocessing.In(kImageTag); | ||||
|     norm_rect_in >> preprocessing.In(kNormRectTag); | ||||
|     auto preprocessed_tensors = preprocessing.Out(kTensorsTag); | ||||
|     auto transform_matrix = preprocessing.Out(kMatrixTag); | ||||
|     auto image_size = preprocessing.Out(kImageSizeTag); | ||||
| 
 | ||||
|     // Adds inference subgraph and connects its input stream to the output
 | ||||
|     // tensors produced by the ImageToTensorCalculator.
 | ||||
|     auto& inference = AddInference( | ||||
|         model_resources, task_options.base_options().acceleration(), graph); | ||||
|     preprocessed_tensors >> inference.In(kTensorsTag); | ||||
|     auto model_output_tensors = | ||||
|         inference.Out(kTensorsTag).Cast<std::vector<Tensor>>(); | ||||
| 
 | ||||
|     auto& tensors_to_image = | ||||
|         graph.AddNode("mediapipe.tasks.TensorsToImageCalculator"); | ||||
|     ConfigureTensorsToImageCalculator( | ||||
|         image_to_tensor_options, | ||||
|         &tensors_to_image.GetOptions<TensorsToImageCalculatorOptions>()); | ||||
|     model_output_tensors >> tensors_to_image.In(kTensorsTag); | ||||
|     auto tensor_image = tensors_to_image.Out(kImageTag); | ||||
| 
 | ||||
|     auto& inverse_matrix = graph.AddNode("InverseMatrixCalculator"); | ||||
|     transform_matrix >> inverse_matrix.In(kMatrixTag); | ||||
|     auto inverse_transform_matrix = inverse_matrix.Out(kMatrixTag); | ||||
| 
 | ||||
|     auto& warp_affine = graph.AddNode("WarpAffineCalculator"); | ||||
|     auto& warp_affine_options = | ||||
|         warp_affine.GetOptions<WarpAffineCalculatorOptions>(); | ||||
|     warp_affine_options.set_border_mode( | ||||
|         WarpAffineCalculatorOptions::BORDER_ZERO); | ||||
|     warp_affine_options.set_gpu_origin(mediapipe::GpuOrigin_Mode_TOP_LEFT); | ||||
|     tensor_image >> warp_affine.In(kImageTag); | ||||
|     inverse_transform_matrix >> warp_affine.In(kMatrixTag); | ||||
|     image_size >> warp_affine.In(kOutputSizeTag); | ||||
|     auto image_to_crop = warp_affine.Out(kImageTag); | ||||
| 
 | ||||
|     // The following calculators are for cropping and resizing the output image
 | ||||
|     // based on the roi and the model output size. As the WarpAffineCalculator
 | ||||
|     // rotates the image based on the transform matrix, the rotation info in the
 | ||||
|     // rect proto is stripped to prevent the ImageCroppingCalculator from
 | ||||
|     // performing extra rotation.
 | ||||
|     auto& strip_rotation = | ||||
|         graph.AddNode("mediapipe.tasks.StripRotationCalculator"); | ||||
|     norm_rect_in >> strip_rotation.In(kNormRectTag); | ||||
|     auto norm_rect_no_rotation = strip_rotation.Out(kNormRectTag); | ||||
|     auto& from_image = graph.AddNode("FromImageCalculator"); | ||||
|     image_to_crop >> from_image.In(kImageTag); | ||||
|     auto& image_cropping = graph.AddNode("ImageCroppingCalculator"); | ||||
|     auto& image_cropping_opts = | ||||
|         image_cropping.GetOptions<ImageCroppingCalculatorOptions>(); | ||||
|     image_cropping_opts.set_output_max_width( | ||||
|         image_to_tensor_options.output_tensor_width()); | ||||
|     image_cropping_opts.set_output_max_height( | ||||
|         image_to_tensor_options.output_tensor_height()); | ||||
|     norm_rect_no_rotation >> image_cropping.In(kNormRectTag); | ||||
|     auto& to_image = graph.AddNode("ToImageCalculator"); | ||||
|     // ImageCroppingCalculator currently doesn't support mediapipe::Image, the
 | ||||
|     // graph selects its cpu or gpu path based on the image preprocessing
 | ||||
|     // backend.
 | ||||
|     if (use_gpu) { | ||||
|       from_image.Out(kImageGpuTag) >> image_cropping.In(kImageGpuTag); | ||||
|       image_cropping.Out(kImageGpuTag) >> to_image.In(kImageGpuTag); | ||||
|     } else { | ||||
|       from_image.Out(kImageCpuTag) >> image_cropping.In(kImageTag); | ||||
|       image_cropping.Out(kImageTag) >> to_image.In(kImageCpuTag); | ||||
|     } | ||||
| 
 | ||||
|     return {{/*stylized_image=*/to_image.Out(kImageTag).Cast<Image>(), | ||||
|              /*original_image=*/preprocessing.Out(kImageTag).Cast<Image>()}}; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| // clang-format off
 | ||||
| REGISTER_MEDIAPIPE_GRAPH( | ||||
|   ::mediapipe::tasks::vision::face_stylizer::FaceStylizerGraph);  // NOLINT
 | ||||
| // clang-format on
 | ||||
| 
 | ||||
| }  // namespace face_stylizer
 | ||||
| }  // namespace vision
 | ||||
| }  // namespace tasks
 | ||||
| }  // namespace mediapipe
 | ||||
							
								
								
									
										31
									
								
								mediapipe/tasks/cc/vision/face_stylizer/proto/BUILD
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								mediapipe/tasks/cc/vision/face_stylizer/proto/BUILD
									
									
									
									
									
										Normal file
									
								
							|  | @ -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_stylizer_graph_options_proto", | ||||
|     srcs = ["face_stylizer_graph_options.proto"], | ||||
|     deps = [ | ||||
|         "//mediapipe/framework:calculator_options_proto", | ||||
|         "//mediapipe/framework:calculator_proto", | ||||
|         "//mediapipe/tasks/cc/core/proto:base_options_proto", | ||||
|     ], | ||||
| ) | ||||
|  | @ -0,0 +1,34 @@ | |||
| /* 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_stylizer.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.facestylizer.proto"; | ||||
| option java_outer_classname = "FaceStylizerGraphOptionsProto"; | ||||
| 
 | ||||
| message FaceStylizerGraphOptions { | ||||
|   extend mediapipe.CalculatorOptions { | ||||
|     optional FaceStylizerGraphOptions ext = 513916220; | ||||
|   } | ||||
|   // Base options for configuring face stylizer, such as specifying the TfLite | ||||
|   // model file with metadata, accelerator options, etc. | ||||
|   optional core.proto.BaseOptions base_options = 1; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user