From c73ff261adbdcf2dace2fffc2202c3eae22f942f Mon Sep 17 00:00:00 2001 From: Tarun Jain Date: Fri, 24 Feb 2023 19:37:30 +0530 Subject: [PATCH 01/15] Fix Broken Link in Object Detection solutions --- docs/solutions/object_detection.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/solutions/object_detection.md b/docs/solutions/object_detection.md index 7ae5e9aff..71d6063bc 100644 --- a/docs/solutions/object_detection.md +++ b/docs/solutions/object_detection.md @@ -108,9 +108,9 @@ on how to build MediaPipe examples. * With a TensorFlow Model This uses the - [TensorFlow model](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_saved_model) + [TensorFlow model](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection) ( see also - [model info](https://github.com/google/mediapipe/tree/master/mediapipe/models/object_detection_saved_model/README.md)), + [model info](https://github.com/google/mediapipe/tree/master/mediapipe/modules/objectron)), and the pipeline is implemented in this [graph](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection/object_detection_mobile_cpu.pbtxt). From 38ec8ae842ceb4083129a4da325a56e41799bd08 Mon Sep 17 00:00:00 2001 From: Tarun Jain Date: Thu, 2 Mar 2023 21:04:53 +0530 Subject: [PATCH 02/15] link updated --- docs/solutions/object_detection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/solutions/object_detection.md b/docs/solutions/object_detection.md index 7b18c0b08..09c178447 100644 --- a/docs/solutions/object_detection.md +++ b/docs/solutions/object_detection.md @@ -118,7 +118,7 @@ on how to build MediaPipe examples. * With a TensorFlow Model This uses the - [TensorFlow model](https://github.com/google/mediapipe/tree/master/mediapipe/graphs/object_detection) + [TensorFlow model](https://github.com/google/mediapipe/tree/v0.8.10/mediapipe/models/object_detection_saved_model) ( see also [model info](https://github.com/google/mediapipe/tree/master/mediapipe/modules/objectron)), and the pipeline is implemented in this From 54e597216b08f43a74c542347301d791415b8a9c Mon Sep 17 00:00:00 2001 From: Tarun Jain Date: Sat, 4 Mar 2023 22:58:12 +0530 Subject: [PATCH 03/15] np used in the program but not imported --- docs/solutions/pose.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/solutions/pose.md b/docs/solutions/pose.md index 3226ddc2f..41dad28e1 100644 --- a/docs/solutions/pose.md +++ b/docs/solutions/pose.md @@ -268,6 +268,7 @@ Supported configuration options: ```python import cv2 +import numpy as np import mediapipe as mp mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles From ffea85e470cb1b1fa243c759920438044be12eff Mon Sep 17 00:00:00 2001 From: MediaPipe Team Date: Mon, 13 Mar 2023 05:09:52 -0700 Subject: [PATCH 04/15] Internal change PiperOrigin-RevId: 516179167 --- .../quality/padding_effect_generator_test.cc | 18 +++++++++++++++++- .../autoflip/quality/testdata/result_0.3.jpg | Bin 3290 -> 3295 bytes .../autoflip/quality/testdata/result_0.6.jpg | Bin 6203 -> 6211 bytes .../autoflip/quality/testdata/result_1.jpg | Bin 8386 -> 8420 bytes .../autoflip/quality/testdata/result_3.4.jpg | Bin 7803 -> 7797 bytes 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/mediapipe/examples/desktop/autoflip/quality/padding_effect_generator_test.cc b/mediapipe/examples/desktop/autoflip/quality/padding_effect_generator_test.cc index 787baa370..84b229d80 100644 --- a/mediapipe/examples/desktop/autoflip/quality/padding_effect_generator_test.cc +++ b/mediapipe/examples/desktop/autoflip/quality/padding_effect_generator_test.cc @@ -138,7 +138,23 @@ void TestWithAspectRatio(const double aspect_ratio, std::string result_image; MP_ASSERT_OK( mediapipe::file::GetContents(result_string_path, &result_image)); - EXPECT_EQ(result_image, output_string); + if (result_image != output_string) { + // There may be slight differences due to the way the JPEG was encoded or + // the OpenCV version used to generate the reference files. Compare + // pixel-by-pixel using the Peak Signal-to-Noise Ratio instead. + cv::Mat result_mat = + cv::imdecode(cv::Mat(1, result_image.size(), CV_8UC1, + const_cast(result_image.data())), + cv::IMREAD_UNCHANGED); + cv::Mat output_mat = + cv::imdecode(cv::Mat(1, output_string.size(), CV_8UC1, + const_cast(output_string.data())), + cv::IMREAD_UNCHANGED); + ASSERT_EQ(result_mat.rows, output_mat.rows); + ASSERT_EQ(result_mat.cols, output_mat.cols); + ASSERT_EQ(result_mat.type(), output_mat.type()); + EXPECT_GT(cv::PSNR(result_mat, output_mat), 45.0); + } } else { std::string output_string_path = mediapipe::file::JoinPath( absl::GetFlag(FLAGS_output_folder), diff --git a/mediapipe/examples/desktop/autoflip/quality/testdata/result_0.3.jpg b/mediapipe/examples/desktop/autoflip/quality/testdata/result_0.3.jpg index 53ebcf770e4257196d7baeb0791b2dc68af6934a..3602046e6c145b175f4c28563044b046d58455ab 100644 GIT binary patch delta 1324 zcmV+{1=IT48Q&SO%L0Fx&x!_+{L*d}3rZ?8H@z#Apvyaj8--Kz{pm#h^tp=mY`M5n z=Bf#*Rg*OCOM^u8Qv+t5nq*j7D58K0X*i`Op^@=Q<4WN2Vyj6tGFfV)$(n~FrWDDZ zZ+eKzeQFX(r3y_&&D2sK=9&bio3PQa($scDt0}3HYNxc+iJX7aOqoR*amkvnr!__# z@mCVHOyrueEfj*5sK||v6;fxb40x(@Rx+K9sjW(~K21Z6nw6I(l_SZhPUl)_Ard^) z=?zU3Rb^uWv{5mgQmBL4g9o);+9?PXe5PL2ndT2_Rfn3mR8SOBm&_s!}-yw}ftGDOj>)qs|pT$Z0&oT6;2i%?~=1P48LWVW%vnk7gX^ zn#r1!HD|>6t4iEfQjLpD8lY~qVTTo2&MV5P6QxL1Jk)>jnx(%6rjAOgO2kTKD>HDX13>J1kNhrA4}Z^|ss8|eqyGTn=>GtP z$>Hz$^VL7^)PMY4czqNIljaE{2Kr3Va6QM9cnKc~J9Q@No@6^7<&$0tAP5_}mo)Ax z9^;c<3L*nAJ;RfD2_JtxiU6P{=A}t9I+Bd%1iCAQVbgBXzqlrNfXtjeky;092&BRiiay9P9&wN z2Q-c?58NF;<}?vSjotMrA9_7%L;|XXHpg_EMPp-w{cOLLU^S`>J!*( zuT@%3Y4WvJCz|!CtqwUoO?Nz1DNi*s$;DQ4lUxm+l{nZ$>sC;4P{z46X;+HQOF_+v zQl&78RXDD7qceYcmR6rot5f$^WMr&j(5+CtJ({6on$Dz=Q9}(h^rz!ArFg99txBH7 zDMeO2YV4_1S$VH!o6zExtvhWvB^GQhkcxk`Cf+GkY51<1aIu`7%CbjQ z%v{!kEksaPWMyL)5yBx)RK-6OwT+Ejn1E8zMKVOiAzF15nhTMXq}fu@ijG8AF&@fL zwER&`K_Of~C^WQ&tsxP!VxligA*$q@*0Dr3XwgL#kzqv?Pyt00Pyt00Pyt00PytA3 iXrKsPb5IeO`Kx4Ro3^b=#I!z&D5xkXlW_u10{_{;FlRvk delta 1424 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 diff --git a/mediapipe/examples/desktop/autoflip/quality/testdata/result_1.jpg b/mediapipe/examples/desktop/autoflip/quality/testdata/result_1.jpg index c03cd46108d10d57416381874e712adb46f2f71f..741e078e9b1c97c8df6d49f53e2c6d4257514ef8 100644 GIT binary patch literal 8420 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 HA&mYP^T;NJ delta 6490 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 B_GAD6 diff --git a/mediapipe/examples/desktop/autoflip/quality/testdata/result_3.4.jpg b/mediapipe/examples/desktop/autoflip/quality/testdata/result_3.4.jpg index 5ec4ea6ec9b8ee55343a54a70fef50923f352479..4efbe7da295db120484f94cfb8c1aae92f8c1af0 100644 GIT binary patch delta 3915 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 delta 3808 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 From 1d2041b9920371b4c186e7ebc8ec734c8bd8447d Mon Sep 17 00:00:00 2001 From: Jiuqiang Tang Date: Mon, 13 Mar 2023 08:40:12 -0700 Subject: [PATCH 05/15] Internal change PiperOrigin-RevId: 516220827 --- .../java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl b/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl index 89fdb32dc..5c5a154d8 100644 --- a/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl +++ b/mediapipe/tasks/java/com/google/mediapipe/tasks/mediapipe_tasks_aar.bzl @@ -34,18 +34,19 @@ _AUDIO_TASKS_JAVA_PROTO_LITE_TARGETS = [ ] _VISION_TASKS_JAVA_PROTO_LITE_TARGETS = [ - "//mediapipe/tasks/cc/vision/object_detector/proto:object_detector_options_java_proto_lite", - "//mediapipe/tasks/cc/vision/image_classifier/proto:image_classifier_graph_options_java_proto_lite", + "//mediapipe/tasks/cc/vision/face_detector/proto:face_detector_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:gesture_classifier_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:gesture_embedder_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:gesture_recognizer_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/gesture_recognizer/proto:hand_gesture_recognizer_graph_options_java_proto_lite", + "//mediapipe/tasks/cc/vision/image_classifier/proto:image_classifier_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/image_embedder/proto:image_embedder_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/image_segmenter/proto:image_segmenter_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/image_segmenter/proto:segmenter_options_java_proto_lite", "//mediapipe/tasks/cc/vision/hand_detector/proto:hand_detector_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/hand_landmarker/proto:hand_landmarker_graph_options_java_proto_lite", "//mediapipe/tasks/cc/vision/hand_landmarker/proto:hand_landmarks_detector_graph_options_java_proto_lite", + "//mediapipe/tasks/cc/vision/object_detector/proto:object_detector_options_java_proto_lite", ] _TEXT_TASKS_JAVA_PROTO_LITE_TARGETS = [ From 490d1a751624843d249260be8680ebda367d30b3 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 13 Mar 2023 10:41:33 -0700 Subject: [PATCH 06/15] Refactor Web code for InteractiveSegmenter PiperOrigin-RevId: 516254891 --- .../tasks/web/audio/core/audio_task_runner.ts | 6 +- mediapipe/tasks/web/core/task_runner.ts | 10 ++- .../text/text_classifier/text_classifier.ts | 6 +- .../web/text/text_embedder/text_embedder.ts | 6 +- mediapipe/tasks/web/vision/core/BUILD | 10 +++ .../tasks/web/vision/core/render_utils.ts | 78 +++++++++++++++++++ mediapipe/tasks/web/vision/core/types.d.ts | 34 ++++++++ .../web/vision/core/vision_task_runner.ts | 6 +- .../tasks/web/vision/image_segmenter/BUILD | 2 +- .../vision/image_segmenter/image_segmenter.ts | 21 +---- 10 files changed, 139 insertions(+), 40 deletions(-) create mode 100644 mediapipe/tasks/web/vision/core/render_utils.ts create mode 100644 mediapipe/tasks/web/vision/core/types.d.ts diff --git a/mediapipe/tasks/web/audio/core/audio_task_runner.ts b/mediapipe/tasks/web/audio/core/audio_task_runner.ts index ff39185f2..2c327f1ab 100644 --- a/mediapipe/tasks/web/audio/core/audio_task_runner.ts +++ b/mediapipe/tasks/web/audio/core/audio_task_runner.ts @@ -36,11 +36,9 @@ export abstract class AudioTaskRunner extends TaskRunner { /** Sends a single audio clip to the graph and awaits results. */ protected processAudioClip(audioData: Float32Array, sampleRate?: number): T { - // Increment the timestamp by 1 millisecond to guarantee that we send - // monotonically increasing timestamps to the graph. - const syntheticTimestamp = this.getLatestOutputTimestamp() + 1; return this.process( - audioData, sampleRate ?? this.defaultSampleRate, syntheticTimestamp); + audioData, sampleRate ?? this.defaultSampleRate, + this.getSynctheticTimestamp()); } } diff --git a/mediapipe/tasks/web/core/task_runner.ts b/mediapipe/tasks/web/core/task_runner.ts index 79b2ca173..a01bb1c92 100644 --- a/mediapipe/tasks/web/core/task_runner.ts +++ b/mediapipe/tasks/web/core/task_runner.ts @@ -175,9 +175,13 @@ export abstract class TaskRunner { Math.max(this.latestOutputTimestamp, timestamp); } - /** Returns the latest output timestamp. */ - protected getLatestOutputTimestamp() { - return this.latestOutputTimestamp; + /** + * Gets a syncthethic timestamp in ms that can be used to send data to the + * next packet. The timestamp is one millisecond past the last timestamp + * received from the graph. + */ + protected getSynctheticTimestamp(): number { + return this.latestOutputTimestamp + 1; } /** Throws the error from the error listener if an error was raised. */ diff --git a/mediapipe/tasks/web/text/text_classifier/text_classifier.ts b/mediapipe/tasks/web/text/text_classifier/text_classifier.ts index b28817613..2495bf5a9 100644 --- a/mediapipe/tasks/web/text/text_classifier/text_classifier.ts +++ b/mediapipe/tasks/web/text/text_classifier/text_classifier.ts @@ -131,11 +131,9 @@ export class TextClassifier extends TaskRunner { * @return The classification result of the text */ classify(text: string): TextClassifierResult { - // Increment the timestamp by 1 millisecond to guarantee that we send - // monotonically increasing timestamps to the graph. - const syntheticTimestamp = this.getLatestOutputTimestamp() + 1; this.classificationResult = {classifications: []}; - this.graphRunner.addStringToStream(text, INPUT_STREAM, syntheticTimestamp); + this.graphRunner.addStringToStream( + text, INPUT_STREAM, this.getSynctheticTimestamp()); this.finishProcessing(); return this.classificationResult; } diff --git a/mediapipe/tasks/web/text/text_embedder/text_embedder.ts b/mediapipe/tasks/web/text/text_embedder/text_embedder.ts index 1034de033..3b7f4f7e4 100644 --- a/mediapipe/tasks/web/text/text_embedder/text_embedder.ts +++ b/mediapipe/tasks/web/text/text_embedder/text_embedder.ts @@ -135,10 +135,8 @@ export class TextEmbedder extends TaskRunner { * @return The embedding resuls of the text */ embed(text: string): TextEmbedderResult { - // Increment the timestamp by 1 millisecond to guarantee that we send - // monotonically increasing timestamps to the graph. - const syntheticTimestamp = this.getLatestOutputTimestamp() + 1; - this.graphRunner.addStringToStream(text, INPUT_STREAM, syntheticTimestamp); + this.graphRunner.addStringToStream( + text, INPUT_STREAM, this.getSynctheticTimestamp()); this.finishProcessing(); return this.embeddingResult; } diff --git a/mediapipe/tasks/web/vision/core/BUILD b/mediapipe/tasks/web/vision/core/BUILD index a0a008122..5135feecc 100644 --- a/mediapipe/tasks/web/vision/core/BUILD +++ b/mediapipe/tasks/web/vision/core/BUILD @@ -21,6 +21,11 @@ mediapipe_ts_declaration( ], ) +mediapipe_ts_declaration( + name = "types", + srcs = ["types.d.ts"], +) + mediapipe_ts_library( name = "vision_task_runner", srcs = ["vision_task_runner.ts"], @@ -51,6 +56,11 @@ mediapipe_ts_library( ], ) +mediapipe_ts_library( + name = "render_utils", + srcs = ["render_utils.ts"], +) + jasmine_node_test( name = "vision_task_runner_test", deps = [":vision_task_runner_test_lib"], diff --git a/mediapipe/tasks/web/vision/core/render_utils.ts b/mediapipe/tasks/web/vision/core/render_utils.ts new file mode 100644 index 000000000..c5f931b38 --- /dev/null +++ b/mediapipe/tasks/web/vision/core/render_utils.ts @@ -0,0 +1,78 @@ +/** @fileoverview Utility functions used in the vision demos. */ + +/** + * 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. + */ + +// Pre-baked color table for a maximum of 12 classes. +const CM_ALPHA = 128; +const COLOR_MAP = [ + [0, 0, 0, CM_ALPHA], // class 0 is BG = transparent + [255, 0, 0, CM_ALPHA], // class 1 is red + [0, 255, 0, CM_ALPHA], // class 2 is light green + [0, 0, 255, CM_ALPHA], // class 3 is blue + [255, 255, 0, CM_ALPHA], // class 4 is yellow + [255, 0, 255, CM_ALPHA], // class 5 is light purple / magenta + [0, 255, 255, CM_ALPHA], // class 6 is light blue / aqua + [128, 128, 128, CM_ALPHA], // class 7 is gray + [255, 128, 0, CM_ALPHA], // class 8 is orange + [128, 0, 255, CM_ALPHA], // class 9 is dark purple + [0, 128, 0, CM_ALPHA], // class 10 is dark green + [255, 255, 255, CM_ALPHA] // class 11 is white; could do black instead? +]; + + +/** Helper function to draw a confidence mask */ +export function drawConfidenceMask( + + ctx: CanvasRenderingContext2D, image: Float32Array|Uint8Array, + width: number, height: number): void { + const uint8ClampedArray = new Uint8ClampedArray(width * height * 4); + for (let i = 0; i < image.length; i++) { + uint8ClampedArray[4 * i] = 128; + uint8ClampedArray[4 * i + 1] = 0; + uint8ClampedArray[4 * i + 2] = 0; + uint8ClampedArray[4 * i + 3] = image[i] * 255; + } + ctx.putImageData(new ImageData(uint8ClampedArray, width, height), 0, 0); +} + +/** + * Helper function to draw a category mask. For GPU, we only have F32Arrays + * for now. + */ +export function drawCategoryMask( + ctx: CanvasRenderingContext2D, image: Float32Array|Uint8Array, + width: number, height: number): void { + const uint8ClampedArray = new Uint8ClampedArray(width * height * 4); + const isFloatArray = image instanceof Float32Array; + for (let i = 0; i < image.length; i++) { + const colorIndex = isFloatArray ? Math.round(image[i] * 255) : image[i]; + const color = COLOR_MAP[colorIndex]; + + // When we're given a confidence mask by accident, we just log and return. + // TODO: We should fix this. + if (!color) { + console.warn('No color for ', colorIndex); + return; + } + + uint8ClampedArray[4 * i] = color[0]; + uint8ClampedArray[4 * i + 1] = color[1]; + uint8ClampedArray[4 * i + 2] = color[2]; + uint8ClampedArray[4 * i + 3] = color[3]; + } + ctx.putImageData(new ImageData(uint8ClampedArray, width, height), 0, 0); +} diff --git a/mediapipe/tasks/web/vision/core/types.d.ts b/mediapipe/tasks/web/vision/core/types.d.ts new file mode 100644 index 000000000..aaa3e2f78 --- /dev/null +++ b/mediapipe/tasks/web/vision/core/types.d.ts @@ -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. + */ + +/** + * The segmentation tasks return the segmentation result as a Uint8Array + * (when the default mode of `CATEGORY_MASK` is used) or as a Float32Array (for + * output type `CONFIDENCE_MASK`). The `WebGLTexture` output type is reserved + * for future usage. + */ +export type SegmentationMask = Uint8Array|Float32Array|WebGLTexture; + +/** + * A callback that receives the computed masks from the segmentation tasks. The + * callback either receives a single element array with a category mask (as a + * `[Uint8Array]`) or multiple confidence masks (as a `Float32Array[]`). + * The returned data is only valid for the duration of the callback. If + * asynchronous processing is needed, all data needs to be copied before the + * callback returns. + */ +export type SegmentationMaskCallback = + (masks: SegmentationMask[], width: number, height: number) => void; diff --git a/mediapipe/tasks/web/vision/core/vision_task_runner.ts b/mediapipe/tasks/web/vision/core/vision_task_runner.ts index b3e8ed4db..4b34aca92 100644 --- a/mediapipe/tasks/web/vision/core/vision_task_runner.ts +++ b/mediapipe/tasks/web/vision/core/vision_task_runner.ts @@ -74,11 +74,7 @@ export abstract class VisionTaskRunner extends TaskRunner { 'Task is not initialized with image mode. ' + '\'runningMode\' must be set to \'IMAGE\'.'); } - - // Increment the timestamp by 1 millisecond to guarantee that we send - // monotonically increasing timestamps to the graph. - const syntheticTimestamp = this.getLatestOutputTimestamp() + 1; - this.process(image, imageProcessingOptions, syntheticTimestamp); + this.process(image, imageProcessingOptions, this.getSynctheticTimestamp()); } /** Sends a single video frame to the graph and awaits results. */ diff --git a/mediapipe/tasks/web/vision/image_segmenter/BUILD b/mediapipe/tasks/web/vision/image_segmenter/BUILD index d15fe63f1..3ca2a64eb 100644 --- a/mediapipe/tasks/web/vision/image_segmenter/BUILD +++ b/mediapipe/tasks/web/vision/image_segmenter/BUILD @@ -19,8 +19,8 @@ mediapipe_ts_library( "//mediapipe/tasks/cc/vision/image_segmenter/proto:segmenter_options_jspb_proto", "//mediapipe/tasks/web/core", "//mediapipe/tasks/web/vision/core:image_processing_options", + "//mediapipe/tasks/web/vision/core:types", "//mediapipe/tasks/web/vision/core:vision_task_runner", - "//mediapipe/web/graph_runner:graph_runner_image_lib_ts", "//mediapipe/web/graph_runner:graph_runner_ts", ], ) diff --git a/mediapipe/tasks/web/vision/image_segmenter/image_segmenter.ts b/mediapipe/tasks/web/vision/image_segmenter/image_segmenter.ts index 3a2e9f2af..85c222a28 100644 --- a/mediapipe/tasks/web/vision/image_segmenter/image_segmenter.ts +++ b/mediapipe/tasks/web/vision/image_segmenter/image_segmenter.ts @@ -21,6 +21,7 @@ import {ImageSegmenterGraphOptions as ImageSegmenterGraphOptionsProto} from '../ import {SegmenterOptions as SegmenterOptionsProto} from '../../../../tasks/cc/vision/image_segmenter/proto/segmenter_options_pb'; import {WasmFileset} from '../../../../tasks/web/core/wasm_fileset'; import {ImageProcessingOptions} from '../../../../tasks/web/vision/core/image_processing_options'; +import {SegmentationMask, SegmentationMaskCallback} from '../../../../tasks/web/vision/core/types'; import {VisionGraphRunner, VisionTaskRunner} from '../../../../tasks/web/vision/core/vision_task_runner'; import {ImageSource, WasmModule} from '../../../../web/graph_runner/graph_runner'; // Placeholder for internal dependency on trusted resource url @@ -28,27 +29,9 @@ import {ImageSource, WasmModule} from '../../../../web/graph_runner/graph_runner import {ImageSegmenterOptions} from './image_segmenter_options'; export * from './image_segmenter_options'; +export {SegmentationMask, SegmentationMaskCallback}; export {ImageSource}; // Used in the public API -/** - * The ImageSegmenter returns the segmentation result as a Uint8Array (when - * the default mode of `CATEGORY_MASK` is used) or as a Float32Array (for - * output type `CONFIDENCE_MASK`). The `WebGLTexture` output type is reserved - * for future usage. - */ -export type SegmentationMask = Uint8Array|Float32Array|WebGLTexture; - -/** - * A callback that receives the computed masks from the image segmenter. The - * callback either receives a single element array with a category mask (as a - * `[Uint8Array]`) or multiple confidence masks (as a `Float32Array[]`). - * The returned data is only valid for the duration of the callback. If - * asynchronous processing is needed, all data needs to be copied before the - * callback returns. - */ -export type SegmentationMaskCallback = - (masks: SegmentationMask[], width: number, height: number) => void; - const IMAGE_STREAM = 'image_in'; const NORM_RECT_STREAM = 'norm_rect'; const GROUPED_SEGMENTATIONS_STREAM = 'segmented_masks'; From 85600ca326fa13c2b4a5b19454948fa408776f6b Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 13 Mar 2023 13:08:03 -0700 Subject: [PATCH 07/15] Add Keypoint and Region-of-interest PiperOrigin-RevId: 516299794 --- .../tasks/web/components/containers/BUILD | 5 +++ .../web/components/containers/keypoint.d.ts | 33 +++++++++++++++++++ mediapipe/tasks/web/vision/core/BUILD | 3 ++ mediapipe/tasks/web/vision/core/types.d.ts | 8 +++++ 4 files changed, 49 insertions(+) create mode 100644 mediapipe/tasks/web/components/containers/keypoint.d.ts diff --git a/mediapipe/tasks/web/components/containers/BUILD b/mediapipe/tasks/web/components/containers/BUILD index a0db59d0b..0126e83c9 100644 --- a/mediapipe/tasks/web/components/containers/BUILD +++ b/mediapipe/tasks/web/components/containers/BUILD @@ -15,6 +15,11 @@ mediapipe_ts_declaration( deps = [":category"], ) +mediapipe_ts_declaration( + name = "keypoint", + srcs = ["keypoint.d.ts"], +) + mediapipe_ts_declaration( name = "landmark", srcs = ["landmark.d.ts"], diff --git a/mediapipe/tasks/web/components/containers/keypoint.d.ts b/mediapipe/tasks/web/components/containers/keypoint.d.ts new file mode 100644 index 000000000..3aaf9eb06 --- /dev/null +++ b/mediapipe/tasks/web/components/containers/keypoint.d.ts @@ -0,0 +1,33 @@ +/** + * 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. + */ + +/** + * A keypoint, defined by the coordinates (x, y), normalized by the image + * dimensions. + */ +export declare interface NormalizedKeypoint { + /** X in normalized image coordinates. */ + x: number; + + /** Y in normalized image coordinates. */ + y: number; + + /** Optional label of the keypoint. */ + label?: string; + + /** Optional score of the keypoint. */ + score?: number; +} diff --git a/mediapipe/tasks/web/vision/core/BUILD b/mediapipe/tasks/web/vision/core/BUILD index 5135feecc..cd2954eb3 100644 --- a/mediapipe/tasks/web/vision/core/BUILD +++ b/mediapipe/tasks/web/vision/core/BUILD @@ -24,6 +24,9 @@ mediapipe_ts_declaration( mediapipe_ts_declaration( name = "types", srcs = ["types.d.ts"], + deps = [ + "//mediapipe/tasks/web/components/containers:keypoint", + ], ) mediapipe_ts_library( diff --git a/mediapipe/tasks/web/vision/core/types.d.ts b/mediapipe/tasks/web/vision/core/types.d.ts index aaa3e2f78..b88683aae 100644 --- a/mediapipe/tasks/web/vision/core/types.d.ts +++ b/mediapipe/tasks/web/vision/core/types.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import {NormalizedKeypoint} from '../../../../tasks/web/components/containers/keypoint'; + /** * The segmentation tasks return the segmentation result as a Uint8Array * (when the default mode of `CATEGORY_MASK` is used) or as a Float32Array (for @@ -32,3 +34,9 @@ export type SegmentationMask = Uint8Array|Float32Array|WebGLTexture; */ export type SegmentationMaskCallback = (masks: SegmentationMask[], width: number, height: number) => void; + +/** A Region-Of-Interest (ROI) to represent a region within an image. */ +export declare interface RegionOfInterest { + /** The ROI in keypoint format. */ + keypoint: NormalizedKeypoint; +} From eac2e337f64c2366894b4b3566e816c8dfa4b845 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 13 Mar 2023 13:51:30 -0700 Subject: [PATCH 08/15] Sort vision tasks in README.md PiperOrigin-RevId: 516312229 --- mediapipe/tasks/web/vision/README.md | 66 ++++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/mediapipe/tasks/web/vision/README.md b/mediapipe/tasks/web/vision/README.md index 9e86eafd3..c1f15ec26 100644 --- a/mediapipe/tasks/web/vision/README.md +++ b/mediapipe/tasks/web/vision/README.md @@ -2,23 +2,42 @@ This package contains the vision tasks for MediaPipe. -## Object Detection +## Gesture Recognition -The MediaPipe Object Detector task lets you detect the presence and location of -multiple classes of objects within images or videos. +The MediaPipe Gesture Recognizer task lets you recognize hand gestures in real +time, and provides the recognized hand gesture results along with the landmarks +of the detected hands. You can use this task to recognize specific hand gestures +from a user, and invoke application features that correspond to those gestures. ``` const vision = await FilesetResolver.forVisionTasks( "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm" ); -const objectDetector = await ObjectDetector.createFromModelPath(vision, - "https://storage.googleapis.com/mediapipe-tasks/object_detector/efficientdet_lite0_uint8.tflite" +const gestureRecognizer = await GestureRecognizer.createFromModelPath(vision, + "https://storage.googleapis.com/mediapipe-tasks/gesture_recognizer/gesture_recognizer.task" ); const image = document.getElementById("image") as HTMLImageElement; -const detections = objectDetector.detect(image); +const recognitions = gestureRecognizer.recognize(image); ``` -For more information, refer to the [Object Detector](https://developers.google.com/mediapipe/solutions/vision/object_detector/web_js) documentation. +## Hand Landmark Detection + +The MediaPipe Hand Landmarker task lets you detect the landmarks of the hands in +an image. You can use this Task to localize key points of the hands and render +visual effects over the hands. + +``` +const vision = await FilesetResolver.forVisionTasks( + "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm" +); +const handLandmarker = await HandLandmarker.createFromModelPath(vision, + "https://storage.googleapis.com/mediapipe-tasks/hand_landmarker/hand_landmarker.task" +); +const image = document.getElementById("image") as HTMLImageElement; +const landmarks = handLandmarker.detect(image); +``` + +For more information, refer to the [Handlandmark Detection](https://developers.google.com/mediapipe/solutions/vision/hand_landmarker/web_js) documentation. ## Image Classification @@ -56,40 +75,21 @@ imageSegmenter.segment(image, (masks, width, height) => { }); ``` -## Gesture Recognition +## Object Detection -The MediaPipe Gesture Recognizer task lets you recognize hand gestures in real -time, and provides the recognized hand gesture results along with the landmarks -of the detected hands. You can use this task to recognize specific hand gestures -from a user, and invoke application features that correspond to those gestures. +The MediaPipe Object Detector task lets you detect the presence and location of +multiple classes of objects within images or videos. ``` const vision = await FilesetResolver.forVisionTasks( "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm" ); -const gestureRecognizer = await GestureRecognizer.createFromModelPath(vision, - "https://storage.googleapis.com/mediapipe-tasks/gesture_recognizer/gesture_recognizer.task" +const objectDetector = await ObjectDetector.createFromModelPath(vision, + "https://storage.googleapis.com/mediapipe-tasks/object_detector/efficientdet_lite0_uint8.tflite" ); const image = document.getElementById("image") as HTMLImageElement; -const recognitions = gestureRecognizer.recognize(image); +const detections = objectDetector.detect(image); ``` -## Handlandmark Detection - -The MediaPipe Hand Landmarker task lets you detect the landmarks of the hands in -an image. You can use this Task to localize key points of the hands and render -visual effects over the hands. - -``` -const vision = await FilesetResolver.forVisionTasks( - "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@latest/wasm" -); -const handLandmarker = await HandLandmarker.createFromModelPath(vision, - "https://storage.googleapis.com/mediapipe-tasks/hand_landmarker/hand_landmarker.task" -); -const image = document.getElementById("image") as HTMLImageElement; -const landmarks = handLandmarker.detect(image); -``` - -For more information, refer to the [Handlandmark Detection](https://developers.google.com/mediapipe/solutions/vision/hand_landmarker/web_js) documentation. +For more information, refer to the [Object Detector](https://developers.google.com/mediapipe/solutions/vision/object_detector/web_js) documentation. From c32ddcb04c5204d35b237a6837c338b44c86d3ac Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 13 Mar 2023 14:55:35 -0700 Subject: [PATCH 09/15] Add alwayslink to face_stylizer_graph PiperOrigin-RevId: 516330940 --- mediapipe/tasks/cc/vision/face_stylizer/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/mediapipe/tasks/cc/vision/face_stylizer/BUILD b/mediapipe/tasks/cc/vision/face_stylizer/BUILD index 7da4e6e74..f62991d45 100644 --- a/mediapipe/tasks/cc/vision/face_stylizer/BUILD +++ b/mediapipe/tasks/cc/vision/face_stylizer/BUILD @@ -47,6 +47,7 @@ cc_library( "//mediapipe/tasks/cc/vision/face_stylizer/proto:face_stylizer_graph_options_cc_proto", "@com_google_absl//absl/status:statusor", ], + alwayslink = 1, ) cc_library( From 0f58d89992e96da3a2560ae6d8a9a15617092605 Mon Sep 17 00:00:00 2001 From: Esha Uboweja Date: Mon, 13 Mar 2023 15:27:49 -0700 Subject: [PATCH 10/15] Preserves all elements of `BASE_HAND_RECTS` input streams in `HandAssociationCalculator`. PiperOrigin-RevId: 516339343 --- .../vision/hand_landmarker/calculators/BUILD | 1 + .../hand_association_calculator.cc | 83 ++++++--- .../hand_association_calculator_test.cc | 166 ++++++++++++------ .../hand_landmarker/hand_landmarker_graph.cc | 4 +- 4 files changed, 175 insertions(+), 79 deletions(-) diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/calculators/BUILD b/mediapipe/tasks/cc/vision/hand_landmarker/calculators/BUILD index f45681fb3..73d3f38eb 100644 --- a/mediapipe/tasks/cc/vision/hand_landmarker/calculators/BUILD +++ b/mediapipe/tasks/cc/vision/hand_landmarker/calculators/BUILD @@ -36,6 +36,7 @@ cc_library( ":hand_association_calculator_cc_proto", "//mediapipe/calculators/util:association_calculator", "//mediapipe/framework:calculator_framework", + "//mediapipe/framework:collection_item_id", "//mediapipe/framework/api2:node", "//mediapipe/framework/formats:rect_cc_proto", "//mediapipe/framework/port:rectangle", diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator.cc b/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator.cc index dffdbdd38..011bce2b9 100644 --- a/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator.cc +++ b/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator.cc @@ -19,6 +19,7 @@ limitations under the License. #include "mediapipe/framework/api2/node.h" #include "mediapipe/framework/calculator_framework.h" +#include "mediapipe/framework/collection_item_id.h" #include "mediapipe/framework/formats/rect.pb.h" #include "mediapipe/framework/port/rectangle.h" #include "mediapipe/framework/port/status.h" @@ -29,30 +30,55 @@ namespace mediapipe::api2 { using ::mediapipe::NormalizedRect; -// HandAssociationCalculator accepts multiple inputs of vectors of -// NormalizedRect. The output is a vector of NormalizedRect that contains -// rects from the input vectors that don't overlap with each other. When two -// rects overlap, the rect that comes in from an earlier input stream is -// kept in the output. If a rect has no ID (i.e. from detection stream), -// then a unique rect ID is assigned for it. - -// The rects in multiple input streams are effectively flattened to a single -// list. For example: -// Stream1 : rect 1, rect 2 -// Stream2: rect 3, rect 4 -// Stream3: rect 5, rect 6 -// (Conceptually) flattened list : rect 1, 2, 3, 4, 5, 6 -// In the flattened list, if a rect with a higher index overlaps with a rect a -// lower index, beyond a specified IOU threshold, the rect with the lower -// index will be in the output, and the rect with higher index will be -// discarded. +// Input: +// BASE_RECTS - Vector of NormalizedRect. +// RECTS - Vector of NormalizedRect. +// +// Output: +// No tag - Vector of NormalizedRect. +// +// Example use: +// node { +// calculator: "HandAssociationCalculator" +// input_stream: "BASE_RECTS:base_rects" +// input_stream: "RECTS:0:rects0" +// input_stream: "RECTS:1:rects1" +// input_stream: "RECTS:2:rects2" +// output_stream: "output_rects" +// options { +// [mediapipe.HandAssociationCalculatorOptions.ext] { +// min_similarity_threshold: 0.1 +// } +// } +// +// IMPORTANT Notes: +// - Rects from input streams tagged with "BASE_RECTS" are always preserved. +// - This calculator checks for overlap among rects from input streams tagged +// with "RECTS". Rects are prioritized based on their index in the vector and +// input streams to the calculator. When two rects overlap, the rect that +// comes from an input stream with lower tag-index is kept in the output. +// - Example of inputs for the node above: +// "base_rects": rect 0, rect 1 +// "rects0": rect 2, rect 3 +// "rects1": rect 4, rect 5 +// "rects2": rect 6, rect 7 +// (Conceptually) flattened list: 0, 1, 2, 3, 4, 5, 6, 7. +// Rects 0, 1 will be preserved. Rects 2, 3, 4, 5, 6, 7 will be checked for +// overlap. If a rect with a higher index overlaps with a rect with lower +// index, beyond a specified IOU threshold, the rect with the lower index +// will be in the output, and the rect with higher index will be discarded. // TODO: Upgrade this to latest API for calculators class HandAssociationCalculator : public CalculatorBase { public: static absl::Status GetContract(CalculatorContract* cc) { // Initialize input and output streams. - for (auto& input_stream : cc->Inputs()) { - input_stream.Set>(); + for (CollectionItemId id = cc->Inputs().BeginId("BASE_RECTS"); + id != cc->Inputs().EndId("BASE_RECTS"); ++id) { + cc->Inputs().Get(id).Set>(); + } + for (CollectionItemId id = cc->Inputs().BeginId("RECTS"); + id != cc->Inputs().EndId("RECTS"); ++id) { + cc->Inputs().Get(id).Set>(); } cc->Outputs().Index(0).Set>(); @@ -89,7 +115,24 @@ class HandAssociationCalculator : public CalculatorBase { CalculatorContext* cc) { std::vector result; - for (const auto& input_stream : cc->Inputs()) { + for (CollectionItemId id = cc->Inputs().BeginId("BASE_RECTS"); + id != cc->Inputs().EndId("BASE_RECTS"); ++id) { + const auto& input_stream = cc->Inputs().Get(id); + if (input_stream.IsEmpty()) { + continue; + } + + for (auto rect : input_stream.Get>()) { + if (!rect.has_rect_id()) { + rect.set_rect_id(GetNextRectId()); + } + result.push_back(rect); + } + } + + for (CollectionItemId id = cc->Inputs().BeginId("RECTS"); + id != cc->Inputs().EndId("RECTS"); ++id) { + const auto& input_stream = cc->Inputs().Get(id); if (input_stream.IsEmpty()) { continue; } diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator_test.cc b/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator_test.cc index 138164209..c22b1a7e6 100644 --- a/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator_test.cc +++ b/mediapipe/tasks/cc/vision/hand_landmarker/calculators/hand_association_calculator_test.cc @@ -27,6 +27,8 @@ namespace mediapipe { namespace { using ::mediapipe::NormalizedRect; +using ::testing::ElementsAre; +using ::testing::EqualsProto; class HandAssociationCalculatorTest : public testing::Test { protected: @@ -87,9 +89,9 @@ class HandAssociationCalculatorTest : public testing::Test { TEST_F(HandAssociationCalculatorTest, NormRectAssocTest) { CalculatorRunner runner(ParseTextProtoOrDie(R"pb( calculator: "HandAssociationCalculator" - input_stream: "input_vec_0" - input_stream: "input_vec_1" - input_stream: "input_vec_2" + input_stream: "BASE_RECTS:input_vec_0" + input_stream: "RECTS:0:input_vec_1" + input_stream: "RECTS:1:input_vec_2" output_stream: "output_vec" options { [mediapipe.HandAssociationCalculatorOptions.ext] { @@ -103,20 +105,23 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTest) { input_vec_0->push_back(nr_0_); input_vec_0->push_back(nr_1_); input_vec_0->push_back(nr_2_); - runner.MutableInputs()->Index(0).packets.push_back( - Adopt(input_vec_0.release()).At(Timestamp(1))); + runner.MutableInputs() + ->Tag("BASE_RECTS") + .packets.push_back(Adopt(input_vec_0.release()).At(Timestamp(1))); // Input Stream 1: nr_3, nr_4. auto input_vec_1 = std::make_unique>(); input_vec_1->push_back(nr_3_); input_vec_1->push_back(nr_4_); - runner.MutableInputs()->Index(1).packets.push_back( + auto index_id = runner.MutableInputs()->GetId("RECTS", 0); + runner.MutableInputs()->Get(index_id).packets.push_back( Adopt(input_vec_1.release()).At(Timestamp(1))); // Input Stream 2: nr_5. auto input_vec_2 = std::make_unique>(); input_vec_2->push_back(nr_5_); - runner.MutableInputs()->Index(2).packets.push_back( + index_id = runner.MutableInputs()->GetId("RECTS", 1); + runner.MutableInputs()->Get(index_id).packets.push_back( Adopt(input_vec_2.release()).At(Timestamp(1))); MP_ASSERT_OK(runner.Run()) << "Calculator execution failed."; @@ -134,25 +139,18 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTest) { EXPECT_EQ(3, assoc_rects.size()); // Check that IDs are filled in and contents match. - EXPECT_EQ(assoc_rects[0].rect_id(), 1); - assoc_rects[0].clear_rect_id(); - EXPECT_THAT(assoc_rects[0], testing::EqualsProto(nr_0_)); - - EXPECT_EQ(assoc_rects[1].rect_id(), 2); - assoc_rects[1].clear_rect_id(); - EXPECT_THAT(assoc_rects[1], testing::EqualsProto(nr_1_)); - - EXPECT_EQ(assoc_rects[2].rect_id(), 3); - assoc_rects[2].clear_rect_id(); - EXPECT_THAT(assoc_rects[2], testing::EqualsProto(nr_2_)); + nr_0_.set_rect_id(1); + nr_1_.set_rect_id(2); + nr_2_.set_rect_id(3); + EXPECT_THAT(assoc_rects, ElementsAre(EqualsProto(nr_0_), EqualsProto(nr_1_), + EqualsProto(nr_2_))); } TEST_F(HandAssociationCalculatorTest, NormRectAssocTestWithTrackedHands) { CalculatorRunner runner(ParseTextProtoOrDie(R"pb( calculator: "HandAssociationCalculator" - input_stream: "input_vec_0" - input_stream: "input_vec_1" - input_stream: "input_vec_2" + input_stream: "BASE_RECTS:input_vec_0" + input_stream: "RECTS:0:input_vec_1" output_stream: "output_vec" options { [mediapipe.HandAssociationCalculatorOptions.ext] { @@ -169,14 +167,15 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTestWithTrackedHands) { input_vec_0->push_back(nr_0_); nr_1_.set_rect_id(-1); input_vec_0->push_back(nr_1_); - runner.MutableInputs()->Index(0).packets.push_back( - Adopt(input_vec_0.release()).At(Timestamp(1))); + runner.MutableInputs() + ->Tag("BASE_RECTS") + .packets.push_back(Adopt(input_vec_0.release()).At(Timestamp(1))); // Input Stream 1: nr_2, nr_3. Newly detected palms. auto input_vec_1 = std::make_unique>(); input_vec_1->push_back(nr_2_); input_vec_1->push_back(nr_3_); - runner.MutableInputs()->Index(1).packets.push_back( + runner.MutableInputs()->Tag("RECTS").packets.push_back( Adopt(input_vec_1.release()).At(Timestamp(1))); MP_ASSERT_OK(runner.Run()) << "Calculator execution failed."; @@ -192,23 +191,17 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTestWithTrackedHands) { EXPECT_EQ(3, assoc_rects.size()); // Check that IDs are filled in and contents match. - EXPECT_EQ(assoc_rects[0].rect_id(), -2); - EXPECT_THAT(assoc_rects[0], testing::EqualsProto(nr_0_)); - - EXPECT_EQ(assoc_rects[1].rect_id(), -1); - EXPECT_THAT(assoc_rects[1], testing::EqualsProto(nr_1_)); - - EXPECT_EQ(assoc_rects[2].rect_id(), 1); - assoc_rects[2].clear_rect_id(); - EXPECT_THAT(assoc_rects[2], testing::EqualsProto(nr_2_)); + nr_2_.set_rect_id(1); + EXPECT_THAT(assoc_rects, ElementsAre(EqualsProto(nr_0_), EqualsProto(nr_1_), + EqualsProto(nr_2_))); } TEST_F(HandAssociationCalculatorTest, NormRectAssocTestReverse) { CalculatorRunner runner(ParseTextProtoOrDie(R"pb( calculator: "HandAssociationCalculator" - input_stream: "input_vec_0" - input_stream: "input_vec_1" - input_stream: "input_vec_2" + input_stream: "BASE_RECTS:input_vec_0" + input_stream: "RECTS:0:input_vec_1" + input_stream: "RECTS:1:input_vec_2" output_stream: "output_vec" options { [mediapipe.HandAssociationCalculatorOptions.ext] { @@ -220,14 +213,16 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTestReverse) { // Input Stream 0: nr_5. auto input_vec_0 = std::make_unique>(); input_vec_0->push_back(nr_5_); - runner.MutableInputs()->Index(0).packets.push_back( - Adopt(input_vec_0.release()).At(Timestamp(1))); + runner.MutableInputs() + ->Tag("BASE_RECTS") + .packets.push_back(Adopt(input_vec_0.release()).At(Timestamp(1))); // Input Stream 1: nr_4, nr_3 auto input_vec_1 = std::make_unique>(); input_vec_1->push_back(nr_4_); input_vec_1->push_back(nr_3_); - runner.MutableInputs()->Index(1).packets.push_back( + auto index_id = runner.MutableInputs()->GetId("RECTS", 0); + runner.MutableInputs()->Get(index_id).packets.push_back( Adopt(input_vec_1.release()).At(Timestamp(1))); // Input Stream 2: nr_2, nr_1, nr_0. @@ -235,7 +230,8 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTestReverse) { input_vec_2->push_back(nr_2_); input_vec_2->push_back(nr_1_); input_vec_2->push_back(nr_0_); - runner.MutableInputs()->Index(2).packets.push_back( + index_id = runner.MutableInputs()->GetId("RECTS", 1); + runner.MutableInputs()->Get(index_id).packets.push_back( Adopt(input_vec_2.release()).At(Timestamp(1))); MP_ASSERT_OK(runner.Run()) << "Calculator execution failed."; @@ -253,23 +249,78 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocTestReverse) { EXPECT_EQ(3, assoc_rects.size()); // Outputs are in same order as inputs, and IDs are filled in. - EXPECT_EQ(assoc_rects[0].rect_id(), 1); - assoc_rects[0].clear_rect_id(); - EXPECT_THAT(assoc_rects[0], testing::EqualsProto(nr_5_)); + nr_5_.set_rect_id(1); + nr_4_.set_rect_id(2); + nr_0_.set_rect_id(3); + EXPECT_THAT(assoc_rects, ElementsAre(EqualsProto(nr_5_), EqualsProto(nr_4_), + EqualsProto(nr_0_))); +} - EXPECT_EQ(assoc_rects[1].rect_id(), 2); - assoc_rects[1].clear_rect_id(); - EXPECT_THAT(assoc_rects[1], testing::EqualsProto(nr_4_)); +TEST_F(HandAssociationCalculatorTest, NormRectAssocTestReservesBaseRects) { + CalculatorRunner runner(ParseTextProtoOrDie(R"pb( + calculator: "HandAssociationCalculator" + input_stream: "BASE_RECTS:input_vec_0" + input_stream: "RECTS:0:input_vec_1" + input_stream: "RECTS:1:input_vec_2" + output_stream: "output_vec" + options { + [mediapipe.HandAssociationCalculatorOptions.ext] { + min_similarity_threshold: 0.1 + } + } + )pb")); - EXPECT_EQ(assoc_rects[2].rect_id(), 3); - assoc_rects[2].clear_rect_id(); - EXPECT_THAT(assoc_rects[2], testing::EqualsProto(nr_0_)); + // Input Stream 0: nr_5, nr_3, nr_1. + auto input_vec_0 = std::make_unique>(); + input_vec_0->push_back(nr_5_); + input_vec_0->push_back(nr_3_); + input_vec_0->push_back(nr_1_); + runner.MutableInputs() + ->Tag("BASE_RECTS") + .packets.push_back(Adopt(input_vec_0.release()).At(Timestamp(1))); + + // Input Stream 1: nr_4. + auto input_vec_1 = std::make_unique>(); + input_vec_1->push_back(nr_4_); + auto index_id = runner.MutableInputs()->GetId("RECTS", 0); + runner.MutableInputs()->Get(index_id).packets.push_back( + Adopt(input_vec_1.release()).At(Timestamp(1))); + + // Input Stream 2: nr_2, nr_0. + auto input_vec_2 = std::make_unique>(); + input_vec_2->push_back(nr_2_); + input_vec_2->push_back(nr_0_); + index_id = runner.MutableInputs()->GetId("RECTS", 1); + runner.MutableInputs()->Get(index_id).packets.push_back( + Adopt(input_vec_2.release()).At(Timestamp(1))); + + MP_ASSERT_OK(runner.Run()) << "Calculator execution failed."; + const std::vector& output = runner.Outputs().Index(0).packets; + EXPECT_EQ(1, output.size()); + auto assoc_rects = output[0].Get>(); + + // Rectangles are added in the following sequence: + // nr_5 is added because it is in BASE_RECTS input stream. + // nr_3 is added because it is in BASE_RECTS input stream. + // nr_1 is added because it is in BASE_RECTS input stream. + // nr_4 is added because it does not overlap with nr_5. + // nr_2 is NOT added because it overlaps with nr_4. + // nr_0 is NOT added because it overlaps with nr_3. + EXPECT_EQ(4, assoc_rects.size()); + + // Outputs are in same order as inputs, and IDs are filled in. + nr_5_.set_rect_id(1); + nr_3_.set_rect_id(2); + nr_1_.set_rect_id(3); + nr_4_.set_rect_id(4); + EXPECT_THAT(assoc_rects, ElementsAre(EqualsProto(nr_5_), EqualsProto(nr_3_), + EqualsProto(nr_1_), EqualsProto(nr_4_))); } TEST_F(HandAssociationCalculatorTest, NormRectAssocSingleInputStream) { CalculatorRunner runner(ParseTextProtoOrDie(R"pb( calculator: "HandAssociationCalculator" - input_stream: "input_vec" + input_stream: "BASE_RECTS:input_vec" output_stream: "output_vec" options { [mediapipe.HandAssociationCalculatorOptions.ext] { @@ -282,8 +333,9 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocSingleInputStream) { auto input_vec = std::make_unique>(); input_vec->push_back(nr_3_); input_vec->push_back(nr_5_); - runner.MutableInputs()->Index(0).packets.push_back( - Adopt(input_vec.release()).At(Timestamp(1))); + runner.MutableInputs() + ->Tag("BASE_RECTS") + .packets.push_back(Adopt(input_vec.release()).At(Timestamp(1))); MP_ASSERT_OK(runner.Run()) << "Calculator execution failed."; const std::vector& output = runner.Outputs().Index(0).packets; @@ -292,12 +344,12 @@ TEST_F(HandAssociationCalculatorTest, NormRectAssocSingleInputStream) { // Rectangles are added in the following sequence: // nr_3 is added 1st. - // nr_5 is NOT added because it overlaps with nr_3. - EXPECT_EQ(1, assoc_rects.size()); + // nr_5 is added 2nd. The calculator assumes it does not overlap with nr_3. + EXPECT_EQ(2, assoc_rects.size()); - EXPECT_EQ(assoc_rects[0].rect_id(), 1); - assoc_rects[0].clear_rect_id(); - EXPECT_THAT(assoc_rects[0], testing::EqualsProto(nr_3_)); + nr_3_.set_rect_id(1); + nr_5_.set_rect_id(2); + EXPECT_THAT(assoc_rects, ElementsAre(EqualsProto(nr_3_), EqualsProto(nr_5_))); } } // namespace diff --git a/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_graph.cc b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_graph.cc index 4a3db9f4d..21e43fc82 100644 --- a/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_graph.cc +++ b/mediapipe/tasks/cc/vision/hand_landmarker/hand_landmarker_graph.cc @@ -318,9 +318,9 @@ class HandLandmarkerGraph : public core::ModelTaskGraph { .set_min_similarity_threshold( tasks_options.min_tracking_confidence()); prev_hand_rects_from_landmarks >> - hand_association[Input>::Multiple("")][0]; + hand_association[Input>("BASE_RECTS")]; hand_rects_from_hand_detector >> - hand_association[Input>::Multiple("")][1]; + hand_association[Input>("RECTS")]; auto hand_rects = hand_association.Out(""); hand_rects >> clip_hand_rects.In(""); } else { From cb4b0ea93da523dcaca243dad306ad43a253f167 Mon Sep 17 00:00:00 2001 From: Sebastian Schmidt Date: Mon, 13 Mar 2023 15:35:11 -0700 Subject: [PATCH 11/15] Disable OpenCL dependency for OpenCV PiperOrigin-RevId: 516341303 --- third_party/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/third_party/BUILD b/third_party/BUILD index e2044cfd9..7522bab1b 100644 --- a/third_party/BUILD +++ b/third_party/BUILD @@ -112,6 +112,7 @@ cmake_external( "WITH_JPEG": "ON", "WITH_PNG": "ON", "WITH_TIFF": "ON", + "WITH_OPENCL": "OFF", "WITH_WEBP": "OFF", # Optimization flags "CV_ENABLE_INTRINSICS": "ON", From 1b4a835be0a73eaa83bf80fdd991fe4293d29b54 Mon Sep 17 00:00:00 2001 From: Chris McClanahan Date: Mon, 13 Mar 2023 16:07:36 -0700 Subject: [PATCH 12/15] Internal change PiperOrigin-RevId: 516349788 --- mediapipe/calculators/image/BUILD | 1 + .../calculators/image/affine_transformation.h | 3 + .../image/affine_transformation_runner_gl.cc | 141 ++++++++++++++---- .../image/affine_transformation_runner_gl.h | 3 +- .../affine_transformation_runner_opencv.cc | 23 ++- .../affine_transformation_runner_opencv.h | 3 +- .../image/warp_affine_calculator.cc | 28 +++- .../image/warp_affine_calculator.proto | 13 ++ .../image/warp_affine_calculator_test.cc | 101 ++++++++++--- ...with_rotation_border_zero_interp_cubic.png | Bin 0 -> 65093 bytes 10 files changed, 256 insertions(+), 60 deletions(-) create mode 100644 mediapipe/calculators/tensor/testdata/image_to_tensor/medium_sub_rect_with_rotation_border_zero_interp_cubic.png diff --git a/mediapipe/calculators/image/BUILD b/mediapipe/calculators/image/BUILD index 18a1d60ae..d627bdc4a 100644 --- a/mediapipe/calculators/image/BUILD +++ b/mediapipe/calculators/image/BUILD @@ -748,6 +748,7 @@ cc_test( "//mediapipe/calculators/tensor:testdata/image_to_tensor/medium_sub_rect_keep_aspect_with_rotation_border_zero.png", "//mediapipe/calculators/tensor:testdata/image_to_tensor/medium_sub_rect_with_rotation.png", "//mediapipe/calculators/tensor:testdata/image_to_tensor/medium_sub_rect_with_rotation_border_zero.png", + "//mediapipe/calculators/tensor:testdata/image_to_tensor/medium_sub_rect_with_rotation_border_zero_interp_cubic.png", "//mediapipe/calculators/tensor:testdata/image_to_tensor/noop_except_range.png", ], tags = ["desktop_only_test"], diff --git a/mediapipe/calculators/image/affine_transformation.h b/mediapipe/calculators/image/affine_transformation.h index 40793e7a1..3e40e46dc 100644 --- a/mediapipe/calculators/image/affine_transformation.h +++ b/mediapipe/calculators/image/affine_transformation.h @@ -29,6 +29,9 @@ class AffineTransformation { // pixels will be calculated. enum class BorderMode { kZero, kReplicate }; + // Pixel sampling interpolation method. + enum class Interpolation { kLinear, kCubic }; + struct Size { int width; int height; diff --git a/mediapipe/calculators/image/affine_transformation_runner_gl.cc b/mediapipe/calculators/image/affine_transformation_runner_gl.cc index 361dfc902..006416916 100644 --- a/mediapipe/calculators/image/affine_transformation_runner_gl.cc +++ b/mediapipe/calculators/image/affine_transformation_runner_gl.cc @@ -77,8 +77,11 @@ class GlTextureWarpAffineRunner std::unique_ptr> { public: GlTextureWarpAffineRunner(std::shared_ptr gl_helper, - GpuOrigin::Mode gpu_origin) - : gl_helper_(gl_helper), gpu_origin_(gpu_origin) {} + GpuOrigin::Mode gpu_origin, + AffineTransformation::Interpolation interpolation) + : gl_helper_(gl_helper), + gpu_origin_(gpu_origin), + interpolation_(interpolation) {} absl::Status Init() { return gl_helper_->RunInGlContext([this]() -> absl::Status { const GLint attr_location[kNumAttributes] = { @@ -103,28 +106,83 @@ class GlTextureWarpAffineRunner } )"; + // TODO Move bicubic code to common shared place. constexpr GLchar kFragShader[] = R"( - DEFAULT_PRECISION(highp, float) - in vec2 sample_coordinate; - uniform sampler2D input_texture; + DEFAULT_PRECISION(highp, float) - #ifdef GL_ES - #define fragColor gl_FragColor - #else - out vec4 fragColor; - #endif // defined(GL_ES); + in vec2 sample_coordinate; + uniform sampler2D input_texture; + uniform vec2 input_size; - void main() { - vec4 color = texture2D(input_texture, sample_coordinate); - #ifdef CUSTOM_ZERO_BORDER_MODE - float out_of_bounds = - float(sample_coordinate.x < 0.0 || sample_coordinate.x > 1.0 || - sample_coordinate.y < 0.0 || sample_coordinate.y > 1.0); - color = mix(color, vec4(0.0, 0.0, 0.0, 0.0), out_of_bounds); - #endif // defined(CUSTOM_ZERO_BORDER_MODE) - fragColor = color; - } - )"; + #ifdef GL_ES + #define fragColor gl_FragColor + #else + out vec4 fragColor; + #endif // defined(GL_ES); + + #ifdef CUBIC_INTERPOLATION + vec4 sample(sampler2D tex, vec2 tex_coord, vec2 tex_size) { + const vec2 halve = vec2(0.5,0.5); + const vec2 one = vec2(1.0,1.0); + const vec2 two = vec2(2.0,2.0); + const vec2 three = vec2(3.0,3.0); + const vec2 six = vec2(6.0,6.0); + + // Calculate the fraction and integer. + tex_coord = tex_coord * tex_size - halve; + vec2 frac = fract(tex_coord); + vec2 index = tex_coord - frac + halve; + + // Calculate weights for Catmull-Rom filter. + vec2 w0 = frac * (-halve + frac * (one - halve * frac)); + vec2 w1 = one + frac * frac * (-(two+halve) + three/two * frac); + vec2 w2 = frac * (halve + frac * (two - three/two * frac)); + vec2 w3 = frac * frac * (-halve + halve * frac); + + // Calculate weights to take advantage of bilinear texture lookup. + vec2 w12 = w1 + w2; + vec2 offset12 = w2 / (w1 + w2); + + vec2 index_tl = index - one; + vec2 index_br = index + two; + vec2 index_eq = index + offset12; + + index_tl /= tex_size; + index_br /= tex_size; + index_eq /= tex_size; + + // 9 texture lookup and linear blending. + vec4 color = vec4(0.0); + color += texture2D(tex, vec2(index_tl.x, index_tl.y)) * w0.x * w0.y; + color += texture2D(tex, vec2(index_eq.x, index_tl.y)) * w12.x *w0.y; + color += texture2D(tex, vec2(index_br.x, index_tl.y)) * w3.x * w0.y; + + color += texture2D(tex, vec2(index_tl.x, index_eq.y)) * w0.x * w12.y; + color += texture2D(tex, vec2(index_eq.x, index_eq.y)) * w12.x *w12.y; + color += texture2D(tex, vec2(index_br.x, index_eq.y)) * w3.x * w12.y; + + color += texture2D(tex, vec2(index_tl.x, index_br.y)) * w0.x * w3.y; + color += texture2D(tex, vec2(index_eq.x, index_br.y)) * w12.x *w3.y; + color += texture2D(tex, vec2(index_br.x, index_br.y)) * w3.x * w3.y; + return color; + } + #else + vec4 sample(sampler2D tex, vec2 tex_coord, vec2 tex_size) { + return texture2D(tex, tex_coord); + } + #endif // defined(CUBIC_INTERPOLATION) + + void main() { + vec4 color = sample(input_texture, sample_coordinate, input_size); + #ifdef CUSTOM_ZERO_BORDER_MODE + float out_of_bounds = + float(sample_coordinate.x < 0.0 || sample_coordinate.x > 1.0 || + sample_coordinate.y < 0.0 || sample_coordinate.y > 1.0); + color = mix(color, vec4(0.0, 0.0, 0.0, 0.0), out_of_bounds); + #endif // defined(CUSTOM_ZERO_BORDER_MODE) + fragColor = color; + } + )"; // Create program and set parameters. auto create_fn = [&](const std::string& vs, @@ -137,14 +195,28 @@ class GlTextureWarpAffineRunner glUseProgram(program); glUniform1i(glGetUniformLocation(program, "input_texture"), 1); GLint matrix_id = glGetUniformLocation(program, "transform_matrix"); - return Program{.id = program, .matrix_id = matrix_id}; + GLint size_id = glGetUniformLocation(program, "input_size"); + return Program{ + .id = program, .matrix_id = matrix_id, .size_id = size_id}; }; const std::string vert_src = absl::StrCat(mediapipe::kMediaPipeVertexShaderPreamble, kVertShader); - const std::string frag_src = absl::StrCat( - mediapipe::kMediaPipeFragmentShaderPreamble, kFragShader); + std::string interpolation_def; + switch (interpolation_) { + case AffineTransformation::Interpolation::kCubic: + interpolation_def = R"( + #define CUBIC_INTERPOLATION + )"; + break; + case AffineTransformation::Interpolation::kLinear: + break; + } + + const std::string frag_src = + absl::StrCat(mediapipe::kMediaPipeFragmentShaderPreamble, + interpolation_def, kFragShader); ASSIGN_OR_RETURN(program_, create_fn(vert_src, frag_src)); @@ -152,9 +224,9 @@ class GlTextureWarpAffineRunner std::string custom_zero_border_mode_def = R"( #define CUSTOM_ZERO_BORDER_MODE )"; - const std::string frag_custom_zero_src = - absl::StrCat(mediapipe::kMediaPipeFragmentShaderPreamble, - custom_zero_border_mode_def, kFragShader); + const std::string frag_custom_zero_src = absl::StrCat( + mediapipe::kMediaPipeFragmentShaderPreamble, + custom_zero_border_mode_def, interpolation_def, kFragShader); return create_fn(vert_src, frag_custom_zero_src); }; #if GL_CLAMP_TO_BORDER_MAY_BE_SUPPORTED @@ -256,6 +328,7 @@ class GlTextureWarpAffineRunner } glUseProgram(program->id); + // uniforms Eigen::Matrix eigen_mat(matrix.data()); if (IsMatrixVerticalFlipNeeded(gpu_origin_)) { // @matrix describes affine transformation in terms of TOP LEFT origin, so @@ -275,6 +348,10 @@ class GlTextureWarpAffineRunner eigen_mat.transposeInPlace(); glUniformMatrix4fv(program->matrix_id, 1, GL_FALSE, eigen_mat.data()); + if (interpolation_ == AffineTransformation::Interpolation::kCubic) { + glUniform2f(program->size_id, texture.width(), texture.height()); + } + // vao glBindVertexArray(vao_); @@ -327,6 +404,7 @@ class GlTextureWarpAffineRunner struct Program { GLuint id; GLint matrix_id; + GLint size_id; }; std::shared_ptr gl_helper_; GpuOrigin::Mode gpu_origin_; @@ -335,6 +413,8 @@ class GlTextureWarpAffineRunner Program program_; std::optional program_custom_zero_; GLuint framebuffer_ = 0; + AffineTransformation::Interpolation interpolation_ = + AffineTransformation::Interpolation::kLinear; }; #undef GL_CLAMP_TO_BORDER_MAY_BE_SUPPORTED @@ -344,9 +424,10 @@ class GlTextureWarpAffineRunner absl::StatusOr>>> CreateAffineTransformationGlRunner( - std::shared_ptr gl_helper, GpuOrigin::Mode gpu_origin) { - auto runner = - absl::make_unique(gl_helper, gpu_origin); + std::shared_ptr gl_helper, GpuOrigin::Mode gpu_origin, + AffineTransformation::Interpolation interpolation) { + auto runner = absl::make_unique( + gl_helper, gpu_origin, interpolation); MP_RETURN_IF_ERROR(runner->Init()); return runner; } diff --git a/mediapipe/calculators/image/affine_transformation_runner_gl.h b/mediapipe/calculators/image/affine_transformation_runner_gl.h index 677e0720d..826c7b5c1 100644 --- a/mediapipe/calculators/image/affine_transformation_runner_gl.h +++ b/mediapipe/calculators/image/affine_transformation_runner_gl.h @@ -29,7 +29,8 @@ absl::StatusOr>>> CreateAffineTransformationGlRunner( std::shared_ptr gl_helper, - mediapipe::GpuOrigin::Mode gpu_origin); + mediapipe::GpuOrigin::Mode gpu_origin, + AffineTransformation::Interpolation interpolation); } // namespace mediapipe diff --git a/mediapipe/calculators/image/affine_transformation_runner_opencv.cc b/mediapipe/calculators/image/affine_transformation_runner_opencv.cc index 46026a987..c43d73ff7 100644 --- a/mediapipe/calculators/image/affine_transformation_runner_opencv.cc +++ b/mediapipe/calculators/image/affine_transformation_runner_opencv.cc @@ -39,9 +39,22 @@ cv::BorderTypes GetBorderModeForOpenCv( } } +int GetInterpolationForOpenCv( + AffineTransformation::Interpolation interpolation) { + switch (interpolation) { + case AffineTransformation::Interpolation::kLinear: + return cv::INTER_LINEAR; + case AffineTransformation::Interpolation::kCubic: + return cv::INTER_CUBIC; + } +} + class OpenCvRunner : public AffineTransformation::Runner { public: + OpenCvRunner(AffineTransformation::Interpolation interpolation) + : interpolation_(GetInterpolationForOpenCv(interpolation)) {} + absl::StatusOr Run( const ImageFrame& input, const std::array& matrix, const AffineTransformation::Size& size, @@ -142,19 +155,23 @@ class OpenCvRunner cv::warpAffine(in_mat, out_mat, cv_affine_transform, cv::Size(out_mat.cols, out_mat.rows), - /*flags=*/cv::INTER_LINEAR | cv::WARP_INVERSE_MAP, + /*flags=*/interpolation_ | cv::WARP_INVERSE_MAP, GetBorderModeForOpenCv(border_mode)); return out_image; } + + private: + int interpolation_ = cv::INTER_LINEAR; }; } // namespace absl::StatusOr< std::unique_ptr>> -CreateAffineTransformationOpenCvRunner() { - return absl::make_unique(); +CreateAffineTransformationOpenCvRunner( + AffineTransformation::Interpolation interpolation) { + return absl::make_unique(interpolation); } } // namespace mediapipe diff --git a/mediapipe/calculators/image/affine_transformation_runner_opencv.h b/mediapipe/calculators/image/affine_transformation_runner_opencv.h index 200281c95..6de48d4cf 100644 --- a/mediapipe/calculators/image/affine_transformation_runner_opencv.h +++ b/mediapipe/calculators/image/affine_transformation_runner_opencv.h @@ -25,7 +25,8 @@ namespace mediapipe { absl::StatusOr< std::unique_ptr>> -CreateAffineTransformationOpenCvRunner(); +CreateAffineTransformationOpenCvRunner( + AffineTransformation::Interpolation interpolation); } // namespace mediapipe diff --git a/mediapipe/calculators/image/warp_affine_calculator.cc b/mediapipe/calculators/image/warp_affine_calculator.cc index 615d1697c..388701773 100644 --- a/mediapipe/calculators/image/warp_affine_calculator.cc +++ b/mediapipe/calculators/image/warp_affine_calculator.cc @@ -53,6 +53,17 @@ AffineTransformation::BorderMode GetBorderMode( } } +AffineTransformation::Interpolation GetInterpolation( + mediapipe::WarpAffineCalculatorOptions::Interpolation interpolation) { + switch (interpolation) { + case mediapipe::WarpAffineCalculatorOptions::INTER_UNSPECIFIED: + case mediapipe::WarpAffineCalculatorOptions::INTER_LINEAR: + return AffineTransformation::Interpolation::kLinear; + case mediapipe::WarpAffineCalculatorOptions::INTER_CUBIC: + return AffineTransformation::Interpolation::kCubic; + } +} + template class WarpAffineRunnerHolder {}; @@ -61,16 +72,22 @@ template <> class WarpAffineRunnerHolder { public: using RunnerType = AffineTransformation::Runner; - absl::Status Open(CalculatorContext* cc) { return absl::OkStatus(); } + absl::Status Open(CalculatorContext* cc) { + interpolation_ = GetInterpolation( + cc->Options().interpolation()); + return absl::OkStatus(); + } absl::StatusOr GetRunner() { if (!runner_) { - ASSIGN_OR_RETURN(runner_, CreateAffineTransformationOpenCvRunner()); + ASSIGN_OR_RETURN(runner_, + CreateAffineTransformationOpenCvRunner(interpolation_)); } return runner_.get(); } private: std::unique_ptr runner_; + AffineTransformation::Interpolation interpolation_; }; #endif // !MEDIAPIPE_DISABLE_OPENCV @@ -85,12 +102,14 @@ class WarpAffineRunnerHolder { gpu_origin_ = cc->Options().gpu_origin(); gl_helper_ = std::make_shared(); + interpolation_ = GetInterpolation( + cc->Options().interpolation()); return gl_helper_->Open(cc); } absl::StatusOr GetRunner() { if (!runner_) { - ASSIGN_OR_RETURN( - runner_, CreateAffineTransformationGlRunner(gl_helper_, gpu_origin_)); + ASSIGN_OR_RETURN(runner_, CreateAffineTransformationGlRunner( + gl_helper_, gpu_origin_, interpolation_)); } return runner_.get(); } @@ -99,6 +118,7 @@ class WarpAffineRunnerHolder { mediapipe::GpuOrigin::Mode gpu_origin_; std::shared_ptr gl_helper_; std::unique_ptr runner_; + AffineTransformation::Interpolation interpolation_; }; #endif // !MEDIAPIPE_DISABLE_GPU diff --git a/mediapipe/calculators/image/warp_affine_calculator.proto b/mediapipe/calculators/image/warp_affine_calculator.proto index 20e6c1b07..b68f71ac3 100644 --- a/mediapipe/calculators/image/warp_affine_calculator.proto +++ b/mediapipe/calculators/image/warp_affine_calculator.proto @@ -31,6 +31,13 @@ message WarpAffineCalculatorOptions { BORDER_REPLICATE = 2; } + // Pixel sampling interpolation methods. See @interpolation. + enum Interpolation { + INTER_UNSPECIFIED = 0; + INTER_LINEAR = 1; + INTER_CUBIC = 2; + } + // Pixel extrapolation method. // When converting image to tensor it may happen that tensor needs to read // pixels outside image boundaries. Border mode helps to specify how such @@ -43,4 +50,10 @@ message WarpAffineCalculatorOptions { // to be flipped vertically as tensors are expected to start at top. // (DEFAULT or unset interpreted as CONVENTIONAL.) optional GpuOrigin.Mode gpu_origin = 2; + + // Sampling method for neighboring pixels. + // INTER_LINEAR (bilinear) linearly interpolates from the nearest 4 neighbors. + // INTER_CUBIC (bicubic) interpolates a small neighborhood with cubic weights. + // INTER_UNSPECIFIED or unset interpreted as INTER_LINEAR. + optional Interpolation interpolation = 3; } diff --git a/mediapipe/calculators/image/warp_affine_calculator_test.cc b/mediapipe/calculators/image/warp_affine_calculator_test.cc index 959912cc9..b911b66fd 100644 --- a/mediapipe/calculators/image/warp_affine_calculator_test.cc +++ b/mediapipe/calculators/image/warp_affine_calculator_test.cc @@ -63,7 +63,8 @@ void RunTest(const std::string& graph_text, const std::string& tag, const cv::Mat& input, cv::Mat expected_result, float similarity_threshold, std::array matrix, int out_width, int out_height, - absl::optional border_mode) { + std::optional border_mode, + std::optional interpolation) { std::string border_mode_str; if (border_mode) { switch (*border_mode) { @@ -75,8 +76,20 @@ void RunTest(const std::string& graph_text, const std::string& tag, break; } } + std::string interpolation_str; + if (interpolation) { + switch (*interpolation) { + case AffineTransformation::Interpolation::kLinear: + interpolation_str = "interpolation: INTER_LINEAR"; + break; + case AffineTransformation::Interpolation::kCubic: + interpolation_str = "interpolation: INTER_CUBIC"; + break; + } + } auto graph_config = mediapipe::ParseTextProtoOrDie( - absl::Substitute(graph_text, /*$0=*/border_mode_str)); + absl::Substitute(graph_text, /*$0=*/border_mode_str, + /*$1=*/interpolation_str)); std::vector output_packets; tool::AddVectorSink("output_image", &graph_config, &output_packets); @@ -132,7 +145,8 @@ struct SimilarityConfig { void RunTest(cv::Mat input, cv::Mat expected_result, const SimilarityConfig& similarity, std::array matrix, int out_width, int out_height, - absl::optional border_mode) { + std::optional border_mode, + std::optional interpolation) { RunTest(R"( input_stream: "input_image" input_stream: "output_size" @@ -146,12 +160,13 @@ void RunTest(cv::Mat input, cv::Mat expected_result, options { [mediapipe.WarpAffineCalculatorOptions.ext] { $0 # border mode + $1 # interpolation } } } )", "cpu", input, expected_result, similarity.threshold_on_cpu, matrix, - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); RunTest(R"( input_stream: "input_image" @@ -171,6 +186,7 @@ void RunTest(cv::Mat input, cv::Mat expected_result, options { [mediapipe.WarpAffineCalculatorOptions.ext] { $0 # border mode + $1 # interpolation } } } @@ -181,7 +197,7 @@ void RunTest(cv::Mat input, cv::Mat expected_result, } )", "cpu_image", input, expected_result, similarity.threshold_on_cpu, - matrix, out_width, out_height, border_mode); + matrix, out_width, out_height, border_mode, interpolation); RunTest(R"( input_stream: "input_image" @@ -201,6 +217,7 @@ void RunTest(cv::Mat input, cv::Mat expected_result, options { [mediapipe.WarpAffineCalculatorOptions.ext] { $0 # border mode + $1 # interpolation gpu_origin: TOP_LEFT } } @@ -212,7 +229,7 @@ void RunTest(cv::Mat input, cv::Mat expected_result, } )", "gpu", input, expected_result, similarity.threshold_on_gpu, matrix, - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); RunTest(R"( input_stream: "input_image" @@ -237,6 +254,7 @@ void RunTest(cv::Mat input, cv::Mat expected_result, options { [mediapipe.WarpAffineCalculatorOptions.ext] { $0 # border mode + $1 # interpolation gpu_origin: TOP_LEFT } } @@ -253,7 +271,7 @@ void RunTest(cv::Mat input, cv::Mat expected_result, } )", "gpu_image", input, expected_result, similarity.threshold_on_gpu, - matrix, out_width, out_height, border_mode); + matrix, out_width, out_height, border_mode, interpolation); } std::array GetMatrix(cv::Mat input, mediapipe::NormalizedRect roi, @@ -287,10 +305,11 @@ TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspect) { int out_height = 256; bool keep_aspect_ratio = true; std::optional border_mode = {}; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.82}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspectBorderZero) { @@ -312,10 +331,11 @@ TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspectBorderZero) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.81}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspectWithRotation) { @@ -337,10 +357,11 @@ TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspectWithRotation) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kReplicate; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.77}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspectWithRotationBorderZero) { @@ -362,10 +383,11 @@ TEST(WarpAffineCalculatorTest, MediumSubRectKeepAspectWithRotationBorderZero) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.75}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, MediumSubRectWithRotation) { @@ -386,10 +408,11 @@ TEST(WarpAffineCalculatorTest, MediumSubRectWithRotation) { bool keep_aspect_ratio = false; std::optional border_mode = AffineTransformation::BorderMode::kReplicate; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.81}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, MediumSubRectWithRotationBorderZero) { @@ -411,10 +434,38 @@ TEST(WarpAffineCalculatorTest, MediumSubRectWithRotationBorderZero) { bool keep_aspect_ratio = false; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.80}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); +} + +TEST(WarpAffineCalculatorTest, MediumSubRectWithRotationBorderZeroInterpCubic) { + mediapipe::NormalizedRect roi; + roi.set_x_center(0.65f); + roi.set_y_center(0.4f); + roi.set_width(0.5f); + roi.set_height(0.5f); + roi.set_rotation(M_PI * -45.0f / 180.0f); + auto input = GetRgb( + "/mediapipe/calculators/" + "tensor/testdata/image_to_tensor/input.jpg"); + auto expected_output = GetRgb( + "/mediapipe/calculators/" + "tensor/testdata/image_to_tensor/" + "medium_sub_rect_with_rotation_border_zero_interp_cubic.png"); + int out_width = 256; + int out_height = 256; + bool keep_aspect_ratio = false; + std::optional border_mode = + AffineTransformation::BorderMode::kZero; + std::optional interpolation = + AffineTransformation::Interpolation::kCubic; + RunTest(input, expected_output, + {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.78}, + GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, LargeSubRect) { @@ -435,10 +486,11 @@ TEST(WarpAffineCalculatorTest, LargeSubRect) { bool keep_aspect_ratio = false; std::optional border_mode = AffineTransformation::BorderMode::kReplicate; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.95}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, LargeSubRectBorderZero) { @@ -459,10 +511,11 @@ TEST(WarpAffineCalculatorTest, LargeSubRectBorderZero) { bool keep_aspect_ratio = false; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.92}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspect) { @@ -483,10 +536,11 @@ TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspect) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kReplicate; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.97}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspectBorderZero) { @@ -508,10 +562,11 @@ TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspectBorderZero) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.97}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspectWithRotation) { @@ -532,10 +587,11 @@ TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspectWithRotation) { int out_height = 128; bool keep_aspect_ratio = true; std::optional border_mode = {}; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.91}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspectWithRotationBorderZero) { @@ -557,10 +613,11 @@ TEST(WarpAffineCalculatorTest, LargeSubRectKeepAspectWithRotationBorderZero) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.88}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, NoOp) { @@ -581,10 +638,11 @@ TEST(WarpAffineCalculatorTest, NoOp) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kReplicate; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.99}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } TEST(WarpAffineCalculatorTest, NoOpBorderZero) { @@ -605,10 +663,11 @@ TEST(WarpAffineCalculatorTest, NoOpBorderZero) { bool keep_aspect_ratio = true; std::optional border_mode = AffineTransformation::BorderMode::kZero; + std::optional interpolation = {}; RunTest(input, expected_output, {.threshold_on_cpu = 0.99, .threshold_on_gpu = 0.99}, GetMatrix(input, roi, keep_aspect_ratio, out_width, out_height), - out_width, out_height, border_mode); + out_width, out_height, border_mode, interpolation); } } // namespace diff --git a/mediapipe/calculators/tensor/testdata/image_to_tensor/medium_sub_rect_with_rotation_border_zero_interp_cubic.png b/mediapipe/calculators/tensor/testdata/image_to_tensor/medium_sub_rect_with_rotation_border_zero_interp_cubic.png new file mode 100644 index 0000000000000000000000000000000000000000..8d2e266a98ef8016e87e8343e095dc8c945b35f8 GIT binary patch literal 65093 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ⅇ-5IW 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 Date: Mon, 13 Mar 2023 16:41:03 -0700 Subject: [PATCH 13/15] Make ImageToTensorCalculator use kGpuService optionally PiperOrigin-RevId: 516358053 --- mediapipe/calculators/tensor/BUILD | 16 ++-- .../tensor/image_to_tensor_calculator.cc | 4 +- .../tensor/image_to_tensor_calculator_test.cc | 78 +++++++++++++++++++ 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/mediapipe/calculators/tensor/BUILD b/mediapipe/calculators/tensor/BUILD index 7a29c3af8..a76b75494 100644 --- a/mediapipe/calculators/tensor/BUILD +++ b/mediapipe/calculators/tensor/BUILD @@ -997,17 +997,20 @@ cc_library( ":image_to_tensor_converter_gl_buffer", "//mediapipe/gpu:gl_calculator_helper", "//mediapipe/gpu:gpu_buffer", + "//mediapipe/gpu:gpu_service", ], "//mediapipe:apple": [ ":image_to_tensor_converter_metal", "//mediapipe/gpu:gl_calculator_helper", "//mediapipe/gpu:MPPMetalHelper", "//mediapipe/gpu:gpu_buffer", + "//mediapipe/gpu:gpu_service", ], "//conditions:default": [ ":image_to_tensor_converter_gl_buffer", "//mediapipe/gpu:gl_calculator_helper", "//mediapipe/gpu:gpu_buffer", + "//mediapipe/gpu:gpu_service", ], }), ) @@ -1045,6 +1048,10 @@ cc_test( ":image_to_tensor_calculator", ":image_to_tensor_converter", ":image_to_tensor_utils", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "//mediapipe/framework:calculator_framework", "//mediapipe/framework:calculator_runner", "//mediapipe/framework/deps:file_path", @@ -1061,11 +1068,10 @@ cc_test( "//mediapipe/framework/port:opencv_imgproc", "//mediapipe/framework/port:parse_text_proto", "//mediapipe/util:image_test_utils", - "@com_google_absl//absl/flags:flag", - "@com_google_absl//absl/memory", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/strings:str_format", - ], + ] + select({ + "//mediapipe:apple": [], + "//conditions:default": ["//mediapipe/gpu:gl_context"], + }), ) cc_library( diff --git a/mediapipe/calculators/tensor/image_to_tensor_calculator.cc b/mediapipe/calculators/tensor/image_to_tensor_calculator.cc index 499b497b0..d15d35086 100644 --- a/mediapipe/calculators/tensor/image_to_tensor_calculator.cc +++ b/mediapipe/calculators/tensor/image_to_tensor_calculator.cc @@ -45,9 +45,11 @@ #elif MEDIAPIPE_OPENGL_ES_VERSION >= MEDIAPIPE_OPENGL_ES_31 #include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_buffer.h" #include "mediapipe/gpu/gl_calculator_helper.h" +#include "mediapipe/gpu/gpu_service.h" #else #include "mediapipe/calculators/tensor/image_to_tensor_converter_gl_texture.h" #include "mediapipe/gpu/gl_calculator_helper.h" +#include "mediapipe/gpu/gpu_service.h" #endif // MEDIAPIPE_METAL_ENABLED #endif // !MEDIAPIPE_DISABLE_GPU @@ -147,7 +149,7 @@ class ImageToTensorCalculator : public Node { #if MEDIAPIPE_METAL_ENABLED MP_RETURN_IF_ERROR([MPPMetalHelper updateContract:cc]); #else - MP_RETURN_IF_ERROR(mediapipe::GlCalculatorHelper::UpdateContract(cc)); + cc->UseService(kGpuService).Optional(); #endif // MEDIAPIPE_METAL_ENABLED #endif // MEDIAPIPE_DISABLE_GPU diff --git a/mediapipe/calculators/tensor/image_to_tensor_calculator_test.cc b/mediapipe/calculators/tensor/image_to_tensor_calculator_test.cc index ed7d93886..3795b1fa0 100644 --- a/mediapipe/calculators/tensor/image_to_tensor_calculator_test.cc +++ b/mediapipe/calculators/tensor/image_to_tensor_calculator_test.cc @@ -41,6 +41,10 @@ #include "mediapipe/framework/port/status_matchers.h" #include "mediapipe/util/image_test_utils.h" +#if !MEDIAPIPE_DISABLE_GPU && !MEDIAPIPE_METAL_ENABLED +#include "mediapipe/gpu/gl_context.h" +#endif // !MEDIAPIPE_DISABLE_GPU && !MEDIAPIPE_METAL_ENABLED + namespace mediapipe { namespace { @@ -507,5 +511,79 @@ TEST(ImageToTensorCalculatorTest, NoOpExceptRangeAndUseInputImageDims) { /*tensor_width=*/std::nullopt, /*tensor_height=*/std::nullopt, /*keep_aspect=*/false, BorderMode::kZero, roi); } + +TEST(ImageToTensorCalculatorTest, CanBeUsedWithoutGpuServiceSet) { + auto graph_config = + mediapipe::ParseTextProtoOrDie(R"pb( + input_stream: "input_image" + node { + calculator: "ImageToTensorCalculator" + input_stream: "IMAGE:input_image" + output_stream: "TENSORS:tensor" + options { + [mediapipe.ImageToTensorCalculatorOptions.ext] { + output_tensor_float_range { min: 0.0f max: 1.0f } + } + } + } + )pb"); + CalculatorGraph graph; + MP_ASSERT_OK(graph.Initialize(graph_config)); + MP_ASSERT_OK(graph.DisallowServiceDefaultInitialization()); + MP_ASSERT_OK(graph.StartRun({})); + auto image_frame = + std::make_shared(ImageFormat::SRGBA, 128, 256, 4); + Image image = Image(std::move(image_frame)); + Packet packet = MakePacket(std::move(image)); + MP_ASSERT_OK( + graph.AddPacketToInputStream("input_image", packet.At(Timestamp(1)))); + MP_ASSERT_OK(graph.WaitUntilIdle()); + MP_ASSERT_OK(graph.CloseAllPacketSources()); + MP_ASSERT_OK(graph.WaitUntilDone()); +} + +#if !MEDIAPIPE_DISABLE_GPU && !MEDIAPIPE_METAL_ENABLED + +TEST(ImageToTensorCalculatorTest, + FailsGracefullyWhenGpuServiceNeededButNotAvailable) { + auto graph_config = + mediapipe::ParseTextProtoOrDie(R"pb( + input_stream: "input_image" + node { + calculator: "ImageToTensorCalculator" + input_stream: "IMAGE:input_image" + output_stream: "TENSORS:tensor" + options { + [mediapipe.ImageToTensorCalculatorOptions.ext] { + output_tensor_float_range { min: 0.0f max: 1.0f } + } + } + } + )pb"); + CalculatorGraph graph; + MP_ASSERT_OK(graph.Initialize(graph_config)); + MP_ASSERT_OK(graph.DisallowServiceDefaultInitialization()); + MP_ASSERT_OK(graph.StartRun({})); + + MP_ASSERT_OK_AND_ASSIGN(auto context, + GlContext::Create(nullptr, /*create_thread=*/true)); + Packet packet; + context->Run([&packet]() { + auto image_frame = + std::make_shared(ImageFormat::SRGBA, 128, 256, 4); + Image image = Image(std::move(image_frame)); + // Ensure image is available on GPU to force ImageToTensorCalculator to + // run on GPU. + ASSERT_TRUE(image.ConvertToGpu()); + packet = MakePacket(std::move(image)); + }); + MP_ASSERT_OK( + graph.AddPacketToInputStream("input_image", packet.At(Timestamp(1)))); + EXPECT_THAT(graph.WaitUntilIdle(), + StatusIs(absl::StatusCode::kInternal, + HasSubstr("GPU service not available"))); +} +#endif // !MEDIAPIPE_DISABLE_GPU && !MEDIAPIPE_METAL_ENABLED + } // namespace } // namespace mediapipe From 57f106e0a72645f871f2875b1eaa51c6ca7f02a2 Mon Sep 17 00:00:00 2001 From: Jiuqiang Tang Date: Mon, 13 Mar 2023 16:53:22 -0700 Subject: [PATCH 14/15] Wait until the metal backend finishes its work in the TensorsToImageCalculator. PiperOrigin-RevId: 516360846 --- .../face_stylizer/calculators/tensors_to_image_calculator.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mediapipe/tasks/cc/vision/face_stylizer/calculators/tensors_to_image_calculator.cc b/mediapipe/tasks/cc/vision/face_stylizer/calculators/tensors_to_image_calculator.cc index 03760c6b3..d9825b15f 100644 --- a/mediapipe/tasks/cc/vision/face_stylizer/calculators/tensors_to_image_calculator.cc +++ b/mediapipe/tasks/cc/vision/face_stylizer/calculators/tensors_to_image_calculator.cc @@ -294,7 +294,7 @@ absl::Status TensorsToImageCalculator::MetalProcess(CalculatorContext* cc) { threadsPerThreadgroup:threads_per_group]; [compute_encoder endEncoding]; [command_buffer commit]; - + [command_buffer waitUntilCompleted]; kOutputImage(cc).Send(Image(output)); return absl::OkStatus(); } From 46ba1d805155f261787d953d72047d36416f15e3 Mon Sep 17 00:00:00 2001 From: MediaPipe Team Date: Mon, 13 Mar 2023 18:46:54 -0700 Subject: [PATCH 15/15] Use ExternalFile to set metadata of GeometryPipelineCalculator. PiperOrigin-RevId: 516384491 --- mediapipe/tasks/cc/vision/face_geometry/BUILD | 4 +- .../cc/vision/face_geometry/calculators/BUILD | 3 + .../geometry_pipeline_calculator.cc | 33 +++------ .../geometry_pipeline_calculator.proto | 3 +- .../face_geometry_from_landmarks_graph.cc | 10 +-- ...face_geometry_from_landmarks_graph_test.cc | 69 ++++++++++++------ .../tasks/cc/vision/face_geometry/proto/BUILD | 9 +++ .../proto/face_geometry_graph_options.proto | 28 +++++++ .../tasks/cc/vision/face_landmarker/BUILD | 2 + .../face_landmarker/face_landmarker_graph.cc | 21 +++++- .../cc/vision/face_landmarker/proto/BUILD | 1 + .../proto/face_landmarker_graph_options.proto | 5 ++ .../face_landmarker_with_blendshapes.task | Bin 3680528 -> 3700070 bytes third_party/external_files.bzl | 10 +-- 14 files changed, 137 insertions(+), 61 deletions(-) create mode 100644 mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.proto diff --git a/mediapipe/tasks/cc/vision/face_geometry/BUILD b/mediapipe/tasks/cc/vision/face_geometry/BUILD index 265b0dc9e..6bd9912b2 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/BUILD +++ b/mediapipe/tasks/cc/vision/face_geometry/BUILD @@ -19,9 +19,6 @@ package(default_visibility = ["//mediapipe/tasks:internal"]) cc_library( name = "face_geometry_from_landmarks_graph", srcs = ["face_geometry_from_landmarks_graph.cc"], - data = [ - "//mediapipe/tasks/cc/vision/face_geometry/data:geometry_pipeline_metadata_landmarks", - ], deps = [ "//mediapipe/calculators/core:begin_loop_calculator", "//mediapipe/calculators/core:end_loop_calculator", @@ -39,6 +36,7 @@ cc_library( "//mediapipe/tasks/cc/vision/face_geometry/calculators:geometry_pipeline_calculator_cc_proto", "//mediapipe/tasks/cc/vision/face_geometry/proto:environment_cc_proto", "//mediapipe/tasks/cc/vision/face_geometry/proto:face_geometry_cc_proto", + "//mediapipe/tasks/cc/vision/face_geometry/proto:face_geometry_graph_options_cc_proto", "//mediapipe/util:graph_builder_utils", "@com_google_absl//absl/status:statusor", ], diff --git a/mediapipe/tasks/cc/vision/face_geometry/calculators/BUILD b/mediapipe/tasks/cc/vision/face_geometry/calculators/BUILD index b134c81f4..b3d4e604a 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/calculators/BUILD +++ b/mediapipe/tasks/cc/vision/face_geometry/calculators/BUILD @@ -45,6 +45,7 @@ mediapipe_proto_library( srcs = ["geometry_pipeline_calculator.proto"], deps = [ "//mediapipe/framework:calculator_options_proto", + "//mediapipe/tasks/cc/core/proto:external_file_proto", ], ) @@ -59,6 +60,8 @@ cc_library( "//mediapipe/framework/port:ret_check", "//mediapipe/framework/port:status", "//mediapipe/framework/port:statusor", + "//mediapipe/tasks/cc/core:external_file_handler", + "//mediapipe/tasks/cc/core/proto:external_file_cc_proto", "//mediapipe/tasks/cc/vision/face_geometry/libs:geometry_pipeline", "//mediapipe/tasks/cc/vision/face_geometry/libs:validation_utils", "//mediapipe/tasks/cc/vision/face_geometry/proto:environment_cc_proto", diff --git a/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.cc b/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.cc index d6082e62d..78cb1146a 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.cc +++ b/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.cc @@ -24,6 +24,8 @@ #include "mediapipe/framework/port/status.h" #include "mediapipe/framework/port/status_macros.h" #include "mediapipe/framework/port/statusor.h" +#include "mediapipe/tasks/cc/core/external_file_handler.h" +#include "mediapipe/tasks/cc/core/proto/external_file.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/libs/geometry_pipeline.h" #include "mediapipe/tasks/cc/vision/face_geometry/libs/validation_utils.h" @@ -69,8 +71,8 @@ using ::mediapipe::tasks::vision::face_geometry::proto:: // A vector of face geometry data. // // Options: -// metadata_path (`string`, optional): -// Defines a path for the geometry pipeline metadata file. +// metadata_file (`ExternalFile`, optional): +// Defines an ExternalFile for the geometry pipeline metadata file. // // The geometry pipeline metadata file format must be the binary // `GeometryPipelineMetadata` proto. @@ -95,7 +97,7 @@ class GeometryPipelineCalculator : public CalculatorBase { ASSIGN_OR_RETURN( GeometryPipelineMetadata metadata, - ReadMetadataFromFile(options.metadata_path()), + ReadMetadataFromFile(options.metadata_file()), _ << "Failed to read the geometry pipeline metadata from file!"); MP_RETURN_IF_ERROR(ValidateGeometryPipelineMetadata(metadata)) @@ -155,32 +157,19 @@ class GeometryPipelineCalculator : public CalculatorBase { private: static absl::StatusOr ReadMetadataFromFile( - const std::string& metadata_path) { - ASSIGN_OR_RETURN(std::string metadata_blob, - ReadContentBlobFromFile(metadata_path), - _ << "Failed to read a metadata blob from file!"); + const core::proto::ExternalFile& metadata_file) { + ASSIGN_OR_RETURN( + const auto file_handler, + core::ExternalFileHandler::CreateFromExternalFile(&metadata_file)); GeometryPipelineMetadata metadata; - RET_CHECK(metadata.ParseFromString(metadata_blob)) + RET_CHECK( + metadata.ParseFromString(std::string(file_handler->GetFileContent()))) << "Failed to parse a metadata proto from a binary blob!"; return metadata; } - static absl::StatusOr ReadContentBlobFromFile( - const std::string& unresolved_path) { - ASSIGN_OR_RETURN(std::string resolved_path, - mediapipe::PathToResourceAsFile(unresolved_path), - _ << "Failed to resolve path! Path = " << unresolved_path); - - std::string content_blob; - MP_RETURN_IF_ERROR( - mediapipe::GetResourceContents(resolved_path, &content_blob)) - << "Failed to read content blob! Resolved path = " << resolved_path; - - return content_blob; - } - std::unique_ptr geometry_pipeline_; }; diff --git a/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.proto b/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.proto index afcc20a13..a748cdf8b 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.proto +++ b/mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.proto @@ -17,11 +17,12 @@ syntax = "proto2"; package mediapipe.tasks.vision.face_geometry; import "mediapipe/framework/calculator_options.proto"; +import "mediapipe/tasks/cc/core/proto/external_file.proto"; message FaceGeometryPipelineCalculatorOptions { extend mediapipe.CalculatorOptions { optional FaceGeometryPipelineCalculatorOptions ext = 512499200; } - optional string metadata_path = 1; + optional core.proto.ExternalFile metadata_file = 1; } diff --git a/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph.cc b/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph.cc index 08b3d1bf4..8c69a31fd 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph.cc +++ b/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph.cc @@ -28,6 +28,7 @@ limitations under the License. #include "mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/proto/environment.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry.pb.h" +#include "mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.pb.h" #include "mediapipe/util/graph_builder_utils.h" namespace mediapipe::tasks::vision::face_geometry { @@ -49,10 +50,6 @@ constexpr char kIterableTag[] = "ITERABLE"; constexpr char kBatchEndTag[] = "BATCH_END"; constexpr char kItemTag[] = "ITEM"; -constexpr char kGeometryPipelineMetadataPath[] = - "mediapipe/tasks/cc/vision/face_geometry/data/" - "geometry_pipeline_metadata_landmarks.binarypb"; - struct FaceGeometryOuts { Stream> multi_face_geometry; }; @@ -127,6 +124,7 @@ class FaceGeometryFromLandmarksGraph : public Subgraph { } ASSIGN_OR_RETURN(auto outs, BuildFaceGeometryFromLandmarksGraph( + *sc->MutableOptions(), graph.In(kFaceLandmarksTag) .Cast>(), graph.In(kImageSizeTag).Cast>(), @@ -138,6 +136,7 @@ class FaceGeometryFromLandmarksGraph : public Subgraph { private: absl::StatusOr BuildFaceGeometryFromLandmarksGraph( + proto::FaceGeometryGraphOptions& graph_options, Stream> multi_face_landmarks, Stream> image_size, std::optional> environment, Graph& graph) { @@ -185,7 +184,8 @@ class FaceGeometryFromLandmarksGraph : public Subgraph { "mediapipe.tasks.vision.face_geometry.FaceGeometryPipelineCalculator"); auto& geometry_pipeline_options = geometry_pipeline.GetOptions(); - geometry_pipeline_options.set_metadata_path(kGeometryPipelineMetadataPath); + geometry_pipeline_options.Swap( + graph_options.mutable_geometry_pipeline_options()); image_size >> geometry_pipeline.In(kImageSizeTag); multi_face_landmarks_no_iris >> geometry_pipeline.In(kMultiFaceLandmarksTag); diff --git a/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph_test.cc b/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph_test.cc index df935135d..74baff5d8 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph_test.cc +++ b/mediapipe/tasks/cc/vision/face_geometry/face_geometry_from_landmarks_graph_test.cc @@ -20,6 +20,7 @@ limitations under the License. #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" #include "mediapipe/framework/api2/port.h" #include "mediapipe/framework/calculator_framework.h" #include "mediapipe/framework/calculator_runner.h" @@ -31,6 +32,7 @@ limitations under the License. #include "mediapipe/framework/port/gtest.h" #include "mediapipe/framework/port/parse_text_proto.h" #include "mediapipe/framework/tool/sink.h" +#include "mediapipe/tasks/cc/core/proto/external_file.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/proto/environment.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry.pb.h" @@ -49,6 +51,9 @@ constexpr char kTestDataDirectory[] = "/mediapipe/tasks/testdata/vision/"; constexpr char kFaceLandmarksFileName[] = "face_blendshapes_in_landmarks.prototxt"; constexpr char kFaceGeometryFileName[] = "face_geometry_expected_out.pbtxt"; +constexpr char kGeometryPipelineMetadataPath[] = + "mediapipe/tasks/cc/vision/face_geometry/data/" + "geometry_pipeline_metadata_landmarks.binarypb"; std::vector GetLandmarks(absl::string_view filename) { NormalizedLandmarkList landmarks; @@ -89,17 +94,25 @@ void MakeInputPacketsAndRunGraph(CalculatorGraph& graph) { TEST(FaceGeometryFromLandmarksGraphTest, DefaultEnvironment) { CalculatorGraphConfig graph_config = ParseTextProtoOrDie< - CalculatorGraphConfig>(R"pb( - input_stream: "FACE_LANDMARKS:face_landmarks" - input_stream: "IMAGE_SIZE:image_size" - output_stream: "FACE_GEOMETRY:face_geometry" - node { - calculator: "mediapipe.tasks.vision.face_geometry.FaceGeometryFromLandmarksGraph" - input_stream: "FACE_LANDMARKS:face_landmarks" - input_stream: "IMAGE_SIZE:image_size" - output_stream: "FACE_GEOMETRY:face_geometry" - } - )pb"); + CalculatorGraphConfig>(absl::Substitute( + R"pb( + input_stream: "FACE_LANDMARKS:face_landmarks" + input_stream: "IMAGE_SIZE:image_size" + output_stream: "FACE_GEOMETRY:face_geometry" + node { + calculator: "mediapipe.tasks.vision.face_geometry.FaceGeometryFromLandmarksGraph" + input_stream: "FACE_LANDMARKS:face_landmarks" + input_stream: "IMAGE_SIZE:image_size" + output_stream: "FACE_GEOMETRY:face_geometry" + options: { + [mediapipe.tasks.vision.face_geometry.proto.FaceGeometryGraphOptions + .ext] { + geometry_pipeline_options { metadata_file { file_name: "$0" } } + } + } + } + )pb", + kGeometryPipelineMetadataPath)); std::vector output_packets; tool::AddVectorSink("face_geometry", &graph_config, &output_packets); @@ -116,19 +129,27 @@ TEST(FaceGeometryFromLandmarksGraphTest, DefaultEnvironment) { TEST(FaceGeometryFromLandmarksGraphTest, SideInEnvironment) { CalculatorGraphConfig graph_config = ParseTextProtoOrDie< - CalculatorGraphConfig>(R"pb( - input_stream: "FACE_LANDMARKS:face_landmarks" - input_stream: "IMAGE_SIZE:image_size" - input_side_packet: "ENVIRONMENT:environment" - output_stream: "FACE_GEOMETRY:face_geometry" - node { - calculator: "mediapipe.tasks.vision.face_geometry.FaceGeometryFromLandmarksGraph" - input_stream: "FACE_LANDMARKS:face_landmarks" - input_stream: "IMAGE_SIZE:image_size" - input_side_packet: "ENVIRONMENT:environment" - output_stream: "FACE_GEOMETRY:face_geometry" - } - )pb"); + CalculatorGraphConfig>(absl::Substitute( + R"pb( + input_stream: "FACE_LANDMARKS:face_landmarks" + input_stream: "IMAGE_SIZE:image_size" + input_side_packet: "ENVIRONMENT:environment" + output_stream: "FACE_GEOMETRY:face_geometry" + node { + calculator: "mediapipe.tasks.vision.face_geometry.FaceGeometryFromLandmarksGraph" + input_stream: "FACE_LANDMARKS:face_landmarks" + input_stream: "IMAGE_SIZE:image_size" + input_side_packet: "ENVIRONMENT:environment" + output_stream: "FACE_GEOMETRY:face_geometry" + options: { + [mediapipe.tasks.vision.face_geometry.proto.FaceGeometryGraphOptions + .ext] { + geometry_pipeline_options { metadata_file { file_name: "$0" } } + } + } + } + )pb", + kGeometryPipelineMetadataPath)); std::vector output_packets; tool::AddVectorSink("face_geometry", &graph_config, &output_packets); diff --git a/mediapipe/tasks/cc/vision/face_geometry/proto/BUILD b/mediapipe/tasks/cc/vision/face_geometry/proto/BUILD index 9559448f3..c9dd15845 100644 --- a/mediapipe/tasks/cc/vision/face_geometry/proto/BUILD +++ b/mediapipe/tasks/cc/vision/face_geometry/proto/BUILD @@ -44,3 +44,12 @@ mediapipe_proto_library( name = "mesh_3d_proto", srcs = ["mesh_3d.proto"], ) + +mediapipe_proto_library( + name = "face_geometry_graph_options_proto", + srcs = ["face_geometry_graph_options.proto"], + deps = [ + "//mediapipe/framework:calculator_options_proto", + "//mediapipe/tasks/cc/vision/face_geometry/calculators:geometry_pipeline_calculator_proto", + ], +) diff --git a/mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.proto b/mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.proto new file mode 100644 index 000000000..03831d1dc --- /dev/null +++ b/mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.proto @@ -0,0 +1,28 @@ +// Copyright 2023 The MediaPipe Authors. +// +// 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_geometry.proto; + +import "mediapipe/framework/calculator_options.proto"; +import "mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.proto"; + +message FaceGeometryGraphOptions { + extend mediapipe.CalculatorOptions { + optional FaceGeometryGraphOptions ext = 515723506; + } + + optional FaceGeometryPipelineCalculatorOptions geometry_pipeline_options = 1; +} diff --git a/mediapipe/tasks/cc/vision/face_landmarker/BUILD b/mediapipe/tasks/cc/vision/face_landmarker/BUILD index 3df2f2db6..ac78edda5 100644 --- a/mediapipe/tasks/cc/vision/face_landmarker/BUILD +++ b/mediapipe/tasks/cc/vision/face_landmarker/BUILD @@ -210,8 +210,10 @@ cc_library( "//mediapipe/tasks/cc/vision/face_detector:face_detector_graph", "//mediapipe/tasks/cc/vision/face_detector/proto:face_detector_graph_options_cc_proto", "//mediapipe/tasks/cc/vision/face_geometry:face_geometry_from_landmarks_graph", + "//mediapipe/tasks/cc/vision/face_geometry/calculators:geometry_pipeline_calculator_cc_proto", "//mediapipe/tasks/cc/vision/face_geometry/proto:environment_cc_proto", "//mediapipe/tasks/cc/vision/face_geometry/proto:face_geometry_cc_proto", + "//mediapipe/tasks/cc/vision/face_geometry/proto:face_geometry_graph_options_cc_proto", "//mediapipe/tasks/cc/vision/face_landmarker/proto:face_blendshapes_graph_options_cc_proto", "//mediapipe/tasks/cc/vision/face_landmarker/proto:face_landmarker_graph_options_cc_proto", "//mediapipe/tasks/cc/vision/face_landmarker/proto:face_landmarks_detector_graph_options_cc_proto", diff --git a/mediapipe/tasks/cc/vision/face_landmarker/face_landmarker_graph.cc b/mediapipe/tasks/cc/vision/face_landmarker/face_landmarker_graph.cc index 52c8b08a0..d6cc630b2 100644 --- a/mediapipe/tasks/cc/vision/face_landmarker/face_landmarker_graph.cc +++ b/mediapipe/tasks/cc/vision/face_landmarker/face_landmarker_graph.cc @@ -40,8 +40,10 @@ limitations under the License. #include "mediapipe/tasks/cc/core/utils.h" #include "mediapipe/tasks/cc/metadata/utils/zip_utils.h" #include "mediapipe/tasks/cc/vision/face_detector/proto/face_detector_graph_options.pb.h" +#include "mediapipe/tasks/cc/vision/face_geometry/calculators/geometry_pipeline_calculator.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/proto/environment.pb.h" #include "mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry.pb.h" +#include "mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.pb.h" #include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_blendshapes_graph_options.pb.h" #include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarker_graph_options.pb.h" #include "mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.pb.h" @@ -93,6 +95,8 @@ constexpr char kFaceDetectorTFLiteName[] = "face_detector.tflite"; constexpr char kFaceLandmarksDetectorTFLiteName[] = "face_landmarks_detector.tflite"; constexpr char kFaceBlendshapeTFLiteName[] = "face_blendshapes.tflite"; +constexpr char kFaceGeometryPipelineMetadataName[] = + "geometry_pipeline_metadata_landmarks.binarypb"; struct FaceLandmarkerOutputs { Source> landmark_lists; @@ -305,6 +309,7 @@ class FaceLandmarkerGraph : public core::ModelTaskGraph { absl::StatusOr GetConfig( SubgraphContext* sc) override { Graph graph; + bool output_geometry = HasOutput(sc->OriginalNode(), kFaceGeometryTag); if (sc->Options() .base_options() .has_model_asset()) { @@ -318,6 +323,18 @@ class FaceLandmarkerGraph : public core::ModelTaskGraph { sc->MutableOptions(), !sc->Service(::mediapipe::tasks::core::kModelResourcesCacheService) .IsAvailable())); + if (output_geometry) { + // Set the face geometry metdata file for + // FaceGeometryFromLandmarksGraph. + ASSIGN_OR_RETURN(auto face_geometry_pipeline_metadata_file, + model_asset_bundle_resources->GetModelFile( + kFaceGeometryPipelineMetadataName)); + SetExternalFile(face_geometry_pipeline_metadata_file, + sc->MutableOptions() + ->mutable_face_geometry_graph_options() + ->mutable_geometry_pipeline_options() + ->mutable_metadata_file()); + } } std::optional> environment; if (HasSideInput(sc->OriginalNode(), kEnvironmentTag)) { @@ -338,7 +355,6 @@ class FaceLandmarkerGraph : public core::ModelTaskGraph { .face_landmarks_detector_graph_options() .has_face_blendshapes_graph_options())); } - bool output_geometry = HasOutput(sc->OriginalNode(), kFaceGeometryTag); ASSIGN_OR_RETURN( auto outs, BuildFaceLandmarkerGraph( @@ -481,6 +497,9 @@ class FaceLandmarkerGraph : public core::ModelTaskGraph { auto& face_geometry_from_landmarks = graph.AddNode( "mediapipe.tasks.vision.face_geometry." "FaceGeometryFromLandmarksGraph"); + face_geometry_from_landmarks + .GetOptions() + .Swap(tasks_options.mutable_face_geometry_graph_options()); if (environment.has_value()) { *environment >> face_geometry_from_landmarks.SideIn(kEnvironmentTag); } diff --git a/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD b/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD index f943420c6..d3e236619 100644 --- a/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD +++ b/mediapipe/tasks/cc/vision/face_landmarker/proto/BUILD @@ -60,5 +60,6 @@ mediapipe_proto_library( "//mediapipe/framework:calculator_proto", "//mediapipe/tasks/cc/core/proto:base_options_proto", "//mediapipe/tasks/cc/vision/face_detector/proto:face_detector_graph_options_proto", + "//mediapipe/tasks/cc/vision/face_geometry/proto:face_geometry_graph_options_proto", ], ) diff --git a/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarker_graph_options.proto b/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarker_graph_options.proto index 67599295e..dc8654608 100644 --- a/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarker_graph_options.proto +++ b/mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarker_graph_options.proto @@ -21,6 +21,7 @@ import "mediapipe/framework/calculator.proto"; import "mediapipe/framework/calculator_options.proto"; import "mediapipe/tasks/cc/core/proto/base_options.proto"; import "mediapipe/tasks/cc/vision/face_detector/proto/face_detector_graph_options.proto"; +import "mediapipe/tasks/cc/vision/face_geometry/proto/face_geometry_graph_options.proto"; import "mediapipe/tasks/cc/vision/face_landmarker/proto/face_landmarks_detector_graph_options.proto"; option java_package = "com.google.mediapipe.tasks.vision.facelandmarker.proto"; @@ -45,4 +46,8 @@ message FaceLandmarkerGraphOptions { // Minimum confidence for face landmarks tracking to be considered // successfully. optional float min_tracking_confidence = 4 [default = 0.5]; + + // Options for FaceGeometryGraph to get facial transformation matrix. + optional face_geometry.proto.FaceGeometryGraphOptions + face_geometry_graph_options = 5; } diff --git a/mediapipe/tasks/testdata/vision/face_landmarker_with_blendshapes.task b/mediapipe/tasks/testdata/vision/face_landmarker_with_blendshapes.task index d20846326dce2b8956c500b4cee60ee96e865294..04adf1841069b8e884d9158a8e54bcf741c086e6 100644 GIT binary patch delta 20033 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 IWjXhM035k+HUIzs delta 313 zcmW;I%WgqY6vpv$Y(?qT)~)WU7njzhwAeF=nPcK1ya66y55&NvLgGopz+3PFr-^jn zDg2$4{8qlK!OF_|KmVD(2iugxfpABRYlRmhEoai4N!L~itp$VmQVY0o0}s6L;TC=b za0iJX?(u*}gb+pqQN-|sI1)%Ag)}nAB8O+>@q$;p;T;7OQQ9qac^`5vwCwWSvOn;N z3aY4~jxRLO#5aD>LVIV~ozv>NVbhqXt`o)+)ptU2s#31rdefPzd{7v|IP#xjNzpF= Cvv}D6 diff --git a/third_party/external_files.bzl b/third_party/external_files.bzl index c140003bf..4e26dff29 100644 --- a/third_party/external_files.bzl +++ b/third_party/external_files.bzl @@ -67,7 +67,7 @@ def external_files(): http_file( name = "com_google_mediapipe_BUILD", sha256 = "d2b2a8346202691d7f831887c84e9642e974f64ed67851d9a58cf15c94b1f6b3", - urls = ["https://storage.googleapis.com/mediapipe-assets/BUILD?generation=1661875663693976"], + urls = ["https://storage.googleapis.com/mediapipe-assets/BUILD?generation=16618756636939761678323576393653"], ) http_file( @@ -318,8 +318,8 @@ def external_files(): http_file( name = "com_google_mediapipe_face_landmarker_with_blendshapes_task", - sha256 = "a75c1ba70e4b8568000af2ad0b355ed559ab5d5793db50fa9ad241f8dc4fad5f", - urls = ["https://storage.googleapis.com/mediapipe-assets/face_landmarker_with_blendshapes.task?generation=1678323586260800"], + sha256 = "b44e4cae6f5822456d60f33e7c852640d78c7e342aee7eacc22589451a0b9dc2", + urls = ["https://storage.googleapis.com/mediapipe-assets/face_landmarker_with_blendshapes.task?generation=1678504998301299"], ) http_file( @@ -822,8 +822,8 @@ def external_files(): http_file( name = "com_google_mediapipe_portrait_expected_face_geometry_with_attention_pbtxt", - sha256 = "5cc57b8da3ad0527dce581fe1309f6b36043e5837e3f4f5af5e24005a99dc52a", - urls = ["https://storage.googleapis.com/mediapipe-assets/portrait_expected_face_geometry_with_attention.pbtxt?generation=1678323601064393"], + sha256 = "7ed1eed98e61e0a10811bb611c895d87c8023f398a36db01b6d9ba2e1ab09e16", + urls = ["https://storage.googleapis.com/mediapipe-assets/portrait_expected_face_geometry_with_attention.pbtxt?generation=1678505004840652"], ) http_file(