Project import generated by Copybara.

GitOrigin-RevId: 283c1a295de0a53e47d7a94996bda0c52dcfd677
This commit is contained in:
MediaPipe Team
2021-09-13 21:35:51 -04:00
committed by chuoling
parent 6abec128ed
commit 137e1cc763
31 changed files with 2051 additions and 53 deletions
@@ -0,0 +1,555 @@
// Copyright 2021 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.
#include "mediapipe/util/tflite/operations/landmarks_to_transform_matrix.h"
#include <vector>
#include "tensorflow/lite/delegates/gpu/common/mediapipe/landmarks_to_transform_matrix.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/kernels/internal/common.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/internal/tensor.h"
#include "tensorflow/lite/kernels/padding.h"
#include "tensorflow/lite/schema/schema_generated.h"
using ::tflite::gpu::BHWC;
using ::tflite::gpu::float2;
using ::tflite::gpu::float3;
using ::tflite::gpu::int2;
using ::tflite::gpu::int3;
using ::tflite::gpu::LandmarksToTransformMatrixV1Attributes;
using ::tflite::gpu::LandmarksToTransformMatrixV2Attributes;
using ::tflite::GetInput;
using ::tflite::GetOutput;
using ::tflite::GetTensorData;
using ::tflite::GetTensorShape;
using ::tflite::NumDimensions;
using ::tflite::NumInputs;
using ::tflite::NumOutputs;
using ::tflite::RuntimeShape;
namespace mediapipe {
namespace tflite_operations {
namespace {
constexpr int kDataInputTensor = 0;
constexpr int kOutputTensor = 0;
constexpr int3 kTensformMatrixShape(1, 4, 4);
float2 Read3DLandmarkXY(const float* data, int idx) {
float2 result;
result.x = data[idx * 3];
result.y = data[idx * 3 + 1];
return result;
}
float3 Read3DLandmarkXYZ(const float* data, int idx) {
float3 result;
result.x = data[idx * 3];
result.y = data[idx * 3 + 1];
result.z = data[idx * 3 + 2];
return result;
}
struct Mat3 {
Mat3() { data.resize(9); }
Mat3(float x00, float x01, float x02, float x10, float x11, float x12,
float x20, float x21, float x22)
: data{x00, x01, x02, x10, x11, x12, x20, x21, x22} {}
Mat3 operator*(const Mat3& other) {
Mat3 result;
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
float sum = 0;
for (int k = 0; k < 3; k++) {
sum += this->Get(r, k) * other.Get(k, c);
}
result.Set(r, c, sum);
}
}
return result;
}
float3 operator*(const float3& vec) const {
float3 result;
for (int r = 0; r < 3; r++) {
float sum = 0;
for (int k = 0; k < 3; k++) {
sum += this->Get(r, k) * vec[k];
}
result[r] = sum;
}
return result;
}
float Get(int x, int y) const { return data[x * 3 + y]; }
void Set(int x, int y, float val) { data[x * 3 + y] = val; }
std::vector<float> data;
};
struct Mat4 {
Mat4() { data.resize(16); }
Mat4(float x00, float x01, float x02, float x03, float x10, float x11,
float x12, float x13, float x20, float x21, float x22, float x23,
float x30, float x31, float x32, float x33)
: data{x00, x01, x02, x03, x10, x11, x12, x13,
x20, x21, x22, x23, x30, x31, x32, x33} {}
void operator*=(const Mat4& other) {
Mat4 result;
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
float sum = 0;
for (int k = 0; k < 4; k++) {
sum += this->Get(r, k) * other.Get(k, c);
}
result.Set(r, c, sum);
}
}
std::memcpy(this->data.data(), result.data.data(),
result.data.size() * sizeof(float));
}
float Get(int x, int y) const { return data[x * 4 + y]; }
void Set(int x, int y, float val) { data[x * 4 + y] = val; }
std::vector<float> data;
};
namespace v1 {
inline void LandmarksToTransformMatrixV1(
const LandmarksToTransformMatrixV1Attributes& params,
const RuntimeShape& input0_shape, const float* landmarks,
const RuntimeShape& output_shape, float* output_data) {
TFLITE_CHECK_EQ(input0_shape.DimensionsCount(), 4);
TFLITE_CHECK_EQ(output_shape.DimensionsCount(), 3);
TFLITE_CHECK_EQ(input0_shape.Dims(0), 1);
TFLITE_CHECK_EQ(input0_shape.Dims(1), 1);
TFLITE_CHECK_EQ(input0_shape.Dims(2), 1);
float2 left_landmark = Read3DLandmarkXY(landmarks, params.left_rotation_idx);
float2 right_landmark =
Read3DLandmarkXY(landmarks, params.right_rotation_idx);
float alpha = -std::atan((right_landmark.y - left_landmark.y) /
(right_landmark.x - left_landmark.x));
float2 max_value(-100000, -100000);
float2 min_value(100000, 100000);
for (int i = 0; i < params.subset.size(); i++) {
for (int j = 0; j < 2; j++) {
float2 landmark_current =
Read3DLandmarkXY(landmarks, params.subset[i][j]);
float2 rotated(
landmark_current.x * cos(alpha) - landmark_current.y * sin(alpha),
landmark_current.x * sin(alpha) + landmark_current.y * cos(alpha));
max_value = float2(std::max(max_value.x, rotated.x),
std::max(max_value.y, rotated.y));
min_value = float2(std::min(min_value.x, rotated.x),
std::min(min_value.y, rotated.y));
}
}
float2 bbox_size((max_value.x - min_value.x) * params.bbox_size_multiplier,
(max_value.y - min_value.y) * params.bbox_size_multiplier);
Mat3 scale_matrix(
bbox_size.x / params.landmarks_range, 0.0, 0.0, // first row
0.0, bbox_size.y / params.landmarks_range, 0.0, // second row
0.0, 0.0, 1.0); // third row
float2 middle((max_value.x + min_value.x) / 2.0,
(max_value.y + min_value.y) / 2.0);
float2 rotated_middle(middle.x * cos(-alpha) - middle.y * sin(-alpha),
middle.x * sin(-alpha) + middle.y * cos(-alpha));
Mat3 rotation_matrix(
cos(-alpha), -sin(-alpha),
(rotated_middle.x / params.landmarks_range) * 2.0 - 1.0, // first row
sin(-alpha), cos(-alpha),
(rotated_middle.y / params.landmarks_range) * 2.0 - 1.0, // second row
0, 0, 1); // third row
Mat3 to_relative(2.0 / (params.output_hw.w - 1.0), 0.0, -1.0, // first row
0.0, 2.0 / (params.output_hw.h - 1.0), -1.0, // second row
0.0, 0.0, 1.0); // third row
Mat3 to_absolute((params.input_hw.w - 1.0) / 2.0, 0.0,
(params.input_hw.w - 1.0) / 2.0, // first row
0.0, (params.input_hw.h - 1.0) / 2.0,
(params.input_hw.h - 1.0) / 2.0, // second row
0.0, 0.0, 1.0); // third row
// Inverse Transformstion Matrix
Mat3 itm = to_absolute * rotation_matrix * scale_matrix * to_relative;
output_data[0] = itm.Get(0, 0);
output_data[1] = itm.Get(0, 1);
output_data[2] = 0.0;
output_data[3] = itm.Get(0, 2);
output_data[4] = itm.Get(1, 0);
output_data[5] = itm.Get(1, 1);
output_data[6] = 0.0;
output_data[7] = itm.Get(1, 2);
output_data[8] = itm.Get(2, 0);
output_data[9] = itm.Get(2, 1);
output_data[10] = itm.Get(2, 2);
output_data[11] = 0.0;
output_data[12] = 0.0;
output_data[13] = 0.0;
output_data[14] = 0.0;
output_data[15] = 1.0;
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
const TfLiteTensor* input = GetInput(context, node, kDataInputTensor);
TF_LITE_ENSURE(context, input != nullptr);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 4);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(3);
output_size->data[0] = kTensformMatrixShape.x;
output_size->data[1] = kTensformMatrixShape.y;
output_size->data[2] = kTensformMatrixShape.z;
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
LandmarksToTransformMatrixV1Attributes op_params;
BHWC output_shape;
auto status = tflite::gpu::ParseLandmarksToTransformMatrixV1Attributes(
node->custom_initial_data, node->custom_initial_data_size, &op_params,
&output_shape);
if (!status.ok()) {
context->ReportError(context, status.message().data());
return kTfLiteError;
}
if (op_params.bbox_size_multiplier == 0) {
context->ReportError(context, "Incorrect bbox_size_multiplier: %d",
op_params.bbox_size_multiplier);
return kTfLiteError;
}
if (op_params.dimensions != 3) {
context->ReportError(context, "Incorrect dimensions: %d",
op_params.dimensions);
return kTfLiteError;
}
if (op_params.input_hw.h <= 0 || op_params.input_hw.w <= 0) {
context->ReportError(context, "Incorrect input_hw: h = %d w = %d",
op_params.input_hw.h, op_params.input_hw.w);
return kTfLiteError;
}
if (op_params.output_hw.h <= 0 || op_params.output_hw.w <= 0) {
context->ReportError(context, "Incorrect output_hw: h = %d w = %d",
op_params.output_hw.h, op_params.output_hw.w);
return kTfLiteError;
}
if (op_params.landmarks_range <= 0) {
context->ReportError(context, "Incorrect landmarks_range: %d",
op_params.landmarks_range);
return kTfLiteError;
}
if (op_params.left_rotation_idx < 0) {
context->ReportError(context, "Incorrect left_rotation_idx: %d",
op_params.left_rotation_idx);
return kTfLiteError;
}
if (op_params.right_rotation_idx < 0) {
context->ReportError(context, "Incorrect right_rotation_idx: %d",
op_params.right_rotation_idx);
return kTfLiteError;
}
if (op_params.subset.empty()) {
context->ReportError(context, "Subset parameter is empty");
return kTfLiteError;
}
int counter = 0;
for (auto& val : op_params.subset) {
for (int i = 0; i < 2; i++) {
if (val[i] < 0) {
context->ReportError(context,
"Incorrect subset value: index = %d, value = %d",
counter, val[i]);
return kTfLiteError;
}
counter++;
}
}
const TfLiteTensor* input0 = GetInput(context, node, kDataInputTensor);
TF_LITE_ENSURE(context, input0 != nullptr);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
LandmarksToTransformMatrixV1(
op_params, GetTensorShape(input0), GetTensorData<float>(input0),
GetTensorShape(output), GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace v1
namespace v2 {
void EstimateRotationRadians(const float* input_data_0, int left_rotation_idx,
int right_rotation_idx,
float target_rotation_radians,
float* rotation_radians) {
const float3 left_landmark =
Read3DLandmarkXYZ(input_data_0, left_rotation_idx);
const float3 right_landmark =
Read3DLandmarkXYZ(input_data_0, right_rotation_idx);
const float left_x = left_landmark[0];
const float left_y = left_landmark[1];
const float right_x = right_landmark[0];
const float right_y = right_landmark[1];
float rotation = std::atan2(right_y - left_y, right_x - left_x);
rotation = target_rotation_radians - rotation;
*rotation_radians = rotation;
}
void EstimateCenterAndSize(const float* input_data_0,
std::vector<tflite::gpu::int2> subset_idxs,
float rotation_radians, float* crop_x, float* crop_y,
float* crop_width, float* crop_height) {
std::vector<float3> landmarks;
landmarks.reserve(subset_idxs.size() * 2);
for (int i = 0; i < subset_idxs.size(); i++) {
landmarks.push_back(Read3DLandmarkXYZ(input_data_0, subset_idxs[i][0]));
landmarks.push_back(Read3DLandmarkXYZ(input_data_0, subset_idxs[i][1]));
}
for (int i = 0; i < landmarks.size(); i++) {
landmarks[i].z = 1.0;
}
const float& r = rotation_radians;
// clang-format off
const Mat3 t_rotation = Mat3(std::cos(r), -std::sin(r), 0.0,
std::sin(r), std::cos(r), 0.0,
0.0, 0.0, 1.0);
const Mat3 t_rotation_inverse =
Mat3(std::cos(-r), -std::sin(-r), 0.0,
std::sin(-r), std::cos(-r), 0.0,
0.0, 0.0, 1.0);
// clang-format on
for (int i = 0; i < landmarks.size(); i++) {
landmarks[i] = t_rotation * landmarks[i];
}
float3 xy1_max = landmarks[0], xy1_min = landmarks[0];
for (int i = 1; i < landmarks.size(); i++) {
if (xy1_max.x < landmarks[i].x) xy1_max.x = landmarks[i].x;
if (xy1_max.y < landmarks[i].y) xy1_max.y = landmarks[i].y;
if (xy1_min.x > landmarks[i].x) xy1_min.x = landmarks[i].x;
if (xy1_min.y > landmarks[i].y) xy1_min.y = landmarks[i].y;
}
*crop_width = xy1_max.x - xy1_min.x;
*crop_height = xy1_max.y - xy1_min.y;
float3 crop_xy1 = xy1_min;
crop_xy1.x += xy1_max.x;
crop_xy1.y += xy1_max.y;
crop_xy1.x /= 2;
crop_xy1.y /= 2;
crop_xy1 = t_rotation_inverse * crop_xy1;
*crop_x = crop_xy1.x;
*crop_y = crop_xy1.y;
}
inline void LandmarksToTransformMatrixV2(
const LandmarksToTransformMatrixV2Attributes& params,
const RuntimeShape& input0_shape, const float* landmarks,
const RuntimeShape& output_shape, float* output_data) {
float rotation_radians = 0.0;
EstimateRotationRadians(landmarks, params.left_rotation_idx,
params.right_rotation_idx,
params.target_rotation_radians, &rotation_radians);
float crop_x = 0.0, crop_y = 0.0, crop_width = 0.0, crop_height = 0.0;
EstimateCenterAndSize(landmarks, params.subset_idxs, rotation_radians,
&crop_x, &crop_y, &crop_width, &crop_height);
// Turn off clang formatting to make matrices initialization more readable.
// clang-format off
Mat4 t = Mat4(1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
const Mat4 t_shift = Mat4(1.0, 0.0, 0.0, crop_x,
0.0, 1.0, 0.0, crop_y,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
t *= t_shift;
const float& r = -rotation_radians;
const Mat4 t_rotation = Mat4(std::cos(r), -std::sin(r), 0.0, 0.0,
std::sin(r), std::cos(r), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
t *= t_rotation;
const float scale_x = params.scale_x * crop_width / params.output_width;
const float scale_y = params.scale_y * crop_height / params.output_height;
const Mat4 t_scale = Mat4(scale_x, 0.0, 0.0, 0.0,
0.0, scale_y, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
t *= t_scale;
const float shift_x = -1.0 * (params.output_width / 2.0);
const float shift_y = -1.0 * (params.output_height / 2.0);
const Mat4 t_shift2 = Mat4(1.0, 0.0, 0.0, shift_x,
0.0, 1.0, 0.0, shift_y,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
t *= t_shift2;
std::memcpy(output_data, t.data.data(), 16 * sizeof(float));
// clang-format on
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, NumInputs(node), 1);
TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
const TfLiteTensor* input = GetInput(context, node, kDataInputTensor);
TF_LITE_ENSURE(context, input != nullptr);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TF_LITE_ENSURE_EQ(context, NumDimensions(input), 3);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(3);
output_size->data[0] = kTensformMatrixShape.x;
output_size->data[1] = kTensformMatrixShape.y;
output_size->data[2] = kTensformMatrixShape.z;
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
LandmarksToTransformMatrixV2Attributes op_params;
BHWC output_shape;
auto status = tflite::gpu::ParseLandmarksToTransformMatrixV2Attributes(
node->custom_initial_data, node->custom_initial_data_size, &op_params,
&output_shape);
if (!status.ok()) {
context->ReportError(context, status.message().data());
return kTfLiteError;
}
if (op_params.left_rotation_idx < 0) {
context->ReportError(context, "Incorrect left_rotation_idx: %d",
op_params.left_rotation_idx);
return kTfLiteError;
}
if (op_params.right_rotation_idx < 0) {
context->ReportError(context, "Incorrect right_rotation_idx: %d",
op_params.right_rotation_idx);
return kTfLiteError;
}
if (op_params.output_height <= 0) {
context->ReportError(context, "Incorrect output_height: %d",
op_params.output_height);
return kTfLiteError;
}
if (op_params.output_width <= 0) {
context->ReportError(context, "Incorrect output_width: %d",
op_params.output_width);
return kTfLiteError;
}
if (op_params.scale_x <= 0) {
context->ReportError(context, "Incorrect scale_x: %d", op_params.scale_x);
return kTfLiteError;
}
if (op_params.scale_y <= 0) {
context->ReportError(context, "Incorrect scale_y: %d", op_params.scale_y);
return kTfLiteError;
}
int counter = 0;
for (auto& val : op_params.subset_idxs) {
for (int i = 0; i < 2; i++) {
if (val[i] < 0) {
context->ReportError(context,
"Incorrect subset value: index = %d, value = %d",
counter, val[i]);
return kTfLiteError;
}
counter++;
}
}
const TfLiteTensor* input0 = GetInput(context, node, kDataInputTensor);
TF_LITE_ENSURE(context, input0 != nullptr);
TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
LandmarksToTransformMatrixV2(
op_params, GetTensorShape(input0), GetTensorData<float>(input0),
GetTensorShape(output), GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace v2
} // namespace
TfLiteRegistration* RegisterLandmarksToTransformMatrixV1() {
static TfLiteRegistration reg = {
/*.init=*/nullptr,
/*.free=*/nullptr,
/*.prepare=*/v1::Prepare,
/*.invoke=*/v1::Eval,
/*.profiling_string=*/nullptr,
/*.builtin_code=*/tflite::BuiltinOperator_CUSTOM,
/*.custom_name=*/"Landmarks2TransformMatrix",
/*.version=*/1,
};
return &reg;
}
TfLiteRegistration* RegisterLandmarksToTransformMatrixV2() {
static TfLiteRegistration reg = {
/*.init=*/nullptr,
/*.free=*/nullptr,
/*.prepare=*/v2::Prepare,
/*.invoke=*/v2::Eval,
/*.profiling_string=*/nullptr,
/*.builtin_code=*/tflite::BuiltinOperator_CUSTOM,
/*.custom_name=*/"Landmarks2TransformMatrix",
/*.version=*/2,
};
return &reg;
}
} // namespace tflite_operations
} // namespace mediapipe
@@ -0,0 +1,30 @@
// Copyright 2021 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.
#ifndef MEDIAPIPE_UTIL_TFLITE_OPERATIONS_LANDMARKS_TO_TRANSFORM_MATRIX_H_
#define MEDIAPIPE_UTIL_TFLITE_OPERATIONS_LANDMARKS_TO_TRANSFORM_MATRIX_H_
#include "tensorflow/lite/kernels/kernel_util.h"
namespace mediapipe {
namespace tflite_operations {
TfLiteRegistration* RegisterLandmarksToTransformMatrixV1();
TfLiteRegistration* RegisterLandmarksToTransformMatrixV2();
} // namespace tflite_operations
} // namespace mediapipe
#endif // MEDIAPIPE_UTIL_TFLITE_OPERATIONS_LANDMARKS_TO_TRANSFORM_MATRIX_H_
@@ -0,0 +1,298 @@
// Copyright 2021 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.
#include "mediapipe/util/tflite/operations/transform_landmarks.h"
#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_landmarks.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/kernels/internal/common.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/internal/tensor.h"
#include "tensorflow/lite/kernels/padding.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace mediapipe {
namespace tflite_operations {
namespace {
constexpr int kDataInput0Tensor = 0;
constexpr int kDataInput1Tensor = 1;
constexpr int kOutputTensor = 0;
float DotProduct(const tflite::gpu::float4& l, const tflite::gpu::float4& r) {
return l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w;
}
namespace v1 {
inline void TransformLandmarks(
const tflite::gpu::TransformLandmarksAttributes& params,
const tflite::RuntimeShape& input0_shape, const float* landmarks,
const tflite::RuntimeShape& input1_shape, const float* transform_matrix,
const tflite::RuntimeShape& output_shape, float* output_data) {
TFLITE_CHECK_EQ(input0_shape.DimensionsCount(), 4);
TFLITE_CHECK_EQ(output_shape.DimensionsCount(), 4);
const int output_height = output_shape.Dims(1);
const int output_width = output_shape.Dims(2);
const int output_channels = output_shape.Dims(3);
TFLITE_CHECK_EQ(input0_shape.Dims(3) % params.dimensions, 0);
TFLITE_CHECK_NE(params.scale, 0);
tflite::RuntimeShape input_shape_with_batch{/*batch=*/1, input0_shape.Dims(1),
input0_shape.Dims(2),
input0_shape.Dims(3)};
tflite::RuntimeShape output_shape_with_batch{
/*batch=*/1, output_shape.Dims(1), output_shape.Dims(2),
output_shape.Dims(3)};
// Read first two rows of transformation matrix
tflite::gpu::float4 x_transform(transform_matrix[0], transform_matrix[1],
transform_matrix[2],
transform_matrix[3] * params.scale);
tflite::gpu::float4 y_transform(transform_matrix[4], transform_matrix[5],
transform_matrix[6],
transform_matrix[7] * params.scale);
for (int out_y = 0; out_y < output_height; ++out_y) {
for (int out_x = 0; out_x < output_width; ++out_x) {
for (int landmark = 0; landmark < output_channels / params.dimensions;
++landmark) {
const int offset = Offset(output_shape_with_batch, 0, out_y, out_x,
landmark * params.dimensions);
if (params.dimensions == 2) {
tflite::gpu::float4 lv(landmarks[offset], landmarks[offset + 1],
static_cast<float>(0.0),
static_cast<float>(1.0));
tflite::gpu::float2 transformed(DotProduct(x_transform, lv),
DotProduct(y_transform, lv));
output_data[offset] = transformed.x;
output_data[offset + 1] = transformed.y;
}
if (params.dimensions == 3) {
tflite::gpu::float4 lv(landmarks[offset], landmarks[offset + 1],
static_cast<float>(0.0),
static_cast<float>(1.0));
tflite::gpu::float3 transformed(DotProduct(x_transform, lv),
DotProduct(y_transform, lv), lv.z);
output_data[offset] = transformed.x;
output_data[offset + 1] = transformed.y;
output_data[offset + 2] = landmarks[offset + 2];
}
}
}
}
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, tflite::NumInputs(node), 2);
TF_LITE_ENSURE_EQ(context, tflite::NumOutputs(node), 1);
const TfLiteTensor* input =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TF_LITE_ENSURE_EQ(context, tflite::NumDimensions(input), 4);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(4);
output_size->data[0] = input->dims->data[0];
output_size->data[1] = input->dims->data[1];
output_size->data[2] = input->dims->data[2];
output_size->data[3] = input->dims->data[3];
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
tflite::gpu::TransformLandmarksAttributes op_params;
tflite::gpu::BHWC output_shape;
auto status = tflite::gpu::ParseTransformLandmarksV1Attributes(
node->custom_initial_data, node->custom_initial_data_size, &op_params,
&output_shape);
if (!status.ok()) {
context->ReportError(context, status.message().data());
return kTfLiteError;
}
if (op_params.dimensions != 3 && op_params.dimensions != 2) {
context->ReportError(context, "Incorrect dimensions size: %d",
op_params.dimensions);
return kTfLiteError;
}
if (op_params.scale == 0) {
context->ReportError(context, "Incorrect scale value: %d", op_params.scale);
return kTfLiteError;
}
const TfLiteTensor* input0 =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input0 != nullptr);
const TfLiteTensor* input1 =
tflite::GetInput(context, node, kDataInput1Tensor);
TF_LITE_ENSURE(context, input1 != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TransformLandmarks(
op_params, tflite::GetTensorShape(input0),
tflite::GetTensorData<float>(input0), tflite::GetTensorShape(input1),
tflite::GetTensorData<float>(input1), tflite::GetTensorShape(output),
tflite::GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace v1
namespace v2 {
inline void TransformLandmarksV2(
const tflite::gpu::TransformLandmarksAttributes& params,
const tflite::RuntimeShape& input0_shape, const float* landmarks,
const float* transform_matrix, // transformation matrix
const tflite::RuntimeShape& output_shape, float* output_data) {
TFLITE_CHECK_EQ(input0_shape.DimensionsCount(), 3);
TFLITE_CHECK_EQ(output_shape.DimensionsCount(), 3);
const int output_width = output_shape.Dims(1);
TFLITE_CHECK_EQ(input0_shape.Dims(2) % params.dimensions, 0);
tflite::RuntimeShape input_shape_with_batch{/*batch=*/1, input0_shape.Dims(0),
input0_shape.Dims(1),
input0_shape.Dims(2)};
tflite::RuntimeShape output_shape_with_batch{
/*batch=*/1, output_shape.Dims(0), output_shape.Dims(1),
output_shape.Dims(2)};
// Read first two rows of transformation matrix
tflite::gpu::float4 x_transform(transform_matrix[0], transform_matrix[1],
transform_matrix[2], transform_matrix[3]);
tflite::gpu::float4 y_transform(transform_matrix[4], transform_matrix[5],
transform_matrix[6], transform_matrix[7]);
for (int landmark = 0; landmark < output_width; ++landmark) {
const int offset = Offset(input_shape_with_batch, 0, 0, landmark, 0);
if (params.dimensions == 2) {
tflite::gpu::float4 lv(landmarks[offset], landmarks[offset + 1],
static_cast<float>(0.0), static_cast<float>(1.0));
tflite::gpu::float2 transformed(DotProduct(x_transform, lv),
DotProduct(y_transform, lv));
output_data[offset] = transformed.x;
output_data[offset + 1] = transformed.y;
}
if (params.dimensions == 3) {
tflite::gpu::float4 lv(landmarks[offset], landmarks[offset + 1],
static_cast<float>(0.0), static_cast<float>(1.0));
tflite::gpu::float3 transformed(DotProduct(x_transform, lv),
DotProduct(y_transform, lv), lv.z);
output_data[offset] = transformed.x;
output_data[offset + 1] = transformed.y;
output_data[offset + 2] = landmarks[offset + 2];
}
}
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, tflite::NumInputs(node), 2);
TF_LITE_ENSURE_EQ(context, tflite::NumOutputs(node), 1);
const TfLiteTensor* input =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TF_LITE_ENSURE_EQ(context, tflite::NumDimensions(input), 3);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
TfLiteIntArray* output_size = TfLiteIntArrayCreate(3);
output_size->data[0] = input->dims->data[0];
output_size->data[1] = input->dims->data[1];
output_size->data[2] = input->dims->data[2];
return context->ResizeTensor(context, output, output_size);
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
tflite::gpu::TransformLandmarksAttributes op_params;
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
tflite::RuntimeShape runtime_output_shape = tflite::GetTensorShape(output);
tflite::gpu::BHWC output_shape(1, runtime_output_shape.Dims(0),
runtime_output_shape.Dims(1),
runtime_output_shape.Dims(2));
auto status = tflite::gpu::ParseTransformLandmarksV2Attributes(
node->custom_initial_data, node->custom_initial_data_size, &op_params,
&output_shape);
if (!status.ok()) {
context->ReportError(context, status.message().data());
return kTfLiteError;
}
if (op_params.dimensions != 3 && op_params.dimensions != 2) {
context->ReportError(context, "Incorrect dimensions size: %d",
op_params.dimensions);
return kTfLiteError;
}
const TfLiteTensor* input0 =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input0 != nullptr);
const TfLiteTensor* input1 =
tflite::GetInput(context, node, kDataInput1Tensor);
TF_LITE_ENSURE(context, input1 != nullptr);
TransformLandmarksV2(op_params, tflite::GetTensorShape(input0),
tflite::GetTensorData<float>(input0),
tflite::GetTensorData<float>(input1),
tflite::GetTensorShape(output),
tflite::GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace v2
} // namespace
TfLiteRegistration* RegisterTransformLandmarksV1() {
static TfLiteRegistration reg = {
/*.init=*/nullptr,
/*.free=*/nullptr,
/*.prepare=*/v1::Prepare,
/*.invoke=*/v1::Eval,
/*.profiling_string=*/nullptr,
/*.builtin_code=*/tflite::BuiltinOperator_CUSTOM,
/*.custom_name=*/"TransformLandmarks",
/*.version=*/1,
};
return &reg;
}
TfLiteRegistration* RegisterTransformLandmarksV2() {
static TfLiteRegistration reg = {
/*.init=*/nullptr,
/*.free=*/nullptr,
/*.prepare=*/v2::Prepare,
/*.invoke=*/v2::Eval,
/*.profiling_string=*/nullptr,
/*.builtin_code=*/tflite::BuiltinOperator_CUSTOM,
/*.custom_name=*/"TransformLandmarks",
/*.version=*/2,
};
return &reg;
}
} // namespace tflite_operations
} // namespace mediapipe
@@ -0,0 +1,30 @@
// Copyright 2021 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.
#ifndef MEDIAPIPE_UTIL_TFLITE_OPERATIONS_TRANSFORM_LANDMARKS_H_
#define MEDIAPIPE_UTIL_TFLITE_OPERATIONS_TRANSFORM_LANDMARKS_H_
#include "tensorflow/lite/kernels/kernel_util.h"
namespace mediapipe {
namespace tflite_operations {
TfLiteRegistration* RegisterTransformLandmarksV1();
TfLiteRegistration* RegisterTransformLandmarksV2();
} // namespace tflite_operations
} // namespace mediapipe
#endif // MEDIAPIPE_UTIL_TFLITE_OPERATIONS_TRANSFORM_LANDMARKS_H_
@@ -0,0 +1,332 @@
// Copyright 2021 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.
#include "mediapipe/util/tflite/operations/transform_tensor_bilinear.h"
#include "tensorflow/lite/delegates/gpu/common/mediapipe/transform_tensor_bilinear.h"
#include "tensorflow/lite/delegates/gpu/common/types.h"
#include "tensorflow/lite/kernels/internal/common.h"
#include "tensorflow/lite/kernels/internal/compatibility.h"
#include "tensorflow/lite/kernels/internal/tensor.h"
#include "tensorflow/lite/kernels/padding.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace mediapipe {
namespace tflite_operations {
namespace {
constexpr int kDataInput0Tensor = 0;
constexpr int kDataInput1Tensor = 1;
constexpr int kOutputTensor = 0;
float DotProduct(const tflite::gpu::float4& l, const tflite::gpu::float4& r) {
return l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w;
}
namespace v1 {
inline void TransformTensor(
const tflite::gpu::TransformTensorBilinearAttributes& params,
const tflite::RuntimeShape& input0_shape,
const float* input_data_0, // data
const tflite::RuntimeShape& input1_shape,
const float* input_data_1, // transformation matrix
const tflite::RuntimeShape& output_shape, float* output_data) {
TFLITE_CHECK_EQ(input0_shape.DimensionsCount(), 4);
TFLITE_CHECK_EQ(output_shape.DimensionsCount(), 4);
const int output_height = output_shape.Dims(1);
const int output_width = output_shape.Dims(2);
const int output_channels = output_shape.Dims(3);
const int input_height = input0_shape.Dims(1);
const int input_width = input0_shape.Dims(2);
const int input_channels = input0_shape.Dims(3);
tflite::RuntimeShape input_shape_with_batch{/*batch=*/1, input_height,
input_width, input_channels};
tflite::RuntimeShape output_shape_with_batch{/*batch=*/1, output_height,
output_width, output_channels};
// Read first two rows of transformation matrix
tflite::gpu::float4 x_transform(input_data_1[0], input_data_1[1],
input_data_1[2], input_data_1[3]);
tflite::gpu::float4 y_transform(input_data_1[4], input_data_1[5],
input_data_1[6], input_data_1[7]);
for (int out_y = 0; out_y < output_height; ++out_y) {
for (int out_x = 0; out_x < output_width; ++out_x) {
tflite::gpu::float4 coord(
static_cast<float>(out_x), static_cast<float>(out_y),
static_cast<float>(0.0), static_cast<float>(1.0));
// Transformed coordinates.
tflite::gpu::float2 tc(DotProduct(x_transform, coord),
DotProduct(y_transform, coord));
bool out_of_bound = tc.x < 0.0 || tc.x > input_width - 1 || tc.y < 0.0 ||
tc.y > input_height - 1;
for (int out_z = 0; out_z < output_channels; ++out_z) {
float result = 0;
if (!out_of_bound) {
// Corners position:
// q_11 --- q_21
// ---- ----
// q_12 --- q_22
auto ReadValue = [&](int h, int w) -> float {
return h < 0 || w < 0 || h >= input_height || w >= input_width
? 0
: input_data_0[Offset(input_shape_with_batch, 0, h, w,
out_z)];
};
float q_11 = ReadValue(floor(tc.y), floor(tc.x));
float q_21 = ReadValue(floor(tc.y), floor(tc.x) + 1);
float q_12 = ReadValue(floor(tc.y) + 1, floor(tc.x));
float q_22 = ReadValue(floor(tc.y) + 1, floor(tc.x) + 1);
float right_contrib = tc.x - floor(tc.x);
float lower_contrib = tc.y - floor(tc.y);
float upper = (1.0 - right_contrib) * q_11 + right_contrib * q_21;
float lower = (1.0 - right_contrib) * q_12 + right_contrib * q_22;
result = lower_contrib * lower + (1.0 - lower_contrib) * upper;
}
const int out_offset =
Offset(output_shape_with_batch, 0, out_y, out_x, out_z);
output_data[out_offset] = result;
}
}
}
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, tflite::NumInputs(node), 2);
TF_LITE_ENSURE_EQ(context, tflite::NumOutputs(node), 1);
const TfLiteTensor* input =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TF_LITE_ENSURE_EQ(context, tflite::NumDimensions(input), 4);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
tflite::gpu::TransformTensorBilinearAttributes op_params;
tflite::gpu::BHWC output_shape;
auto status = tflite::gpu::ParseTransformTensorBilinearV1Attributes(
node->custom_initial_data, node->custom_initial_data_size, &op_params,
&output_shape);
if (!status.ok()) {
context->ReportError(context, status.message().data());
return kTfLiteError;
}
const TfLiteTensor* input0 =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input0 != nullptr);
const TfLiteTensor* input1 =
tflite::GetInput(context, node, kDataInput1Tensor);
TF_LITE_ENSURE(context, input1 != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TransformTensor(
op_params, tflite::GetTensorShape(input0),
tflite::GetTensorData<float>(input0), tflite::GetTensorShape(input1),
tflite::GetTensorData<float>(input1), tflite::GetTensorShape(output),
tflite::GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace v1
namespace v2 {
inline void TransformTensorBilinearV2(
const tflite::gpu::TransformTensorBilinearAttributes& params,
const tflite::RuntimeShape& input0_shape,
const float* input_data_0, // data
const tflite::RuntimeShape& input1_shape,
const float* input_data_1, // transformation matrix
const tflite::RuntimeShape& output_shape, float* output_data) {
TFLITE_CHECK_EQ(input0_shape.DimensionsCount(), 4);
TFLITE_CHECK_EQ(output_shape.DimensionsCount(), 4);
const int output_height = output_shape.Dims(1);
const int output_width = output_shape.Dims(2);
const int output_channels = output_shape.Dims(3);
const int input_height = input0_shape.Dims(1);
const int input_width = input0_shape.Dims(2);
const int input_channels = input0_shape.Dims(3);
tflite::RuntimeShape input_shape_with_batch{/*batch=*/1, input_height,
input_width, input_channels};
tflite::RuntimeShape output_shape_with_batch{/*batch=*/1, output_height,
output_width, output_channels};
// Read first two rows of transformation matrix
tflite::gpu::float4 x_transform(input_data_1[0], input_data_1[1],
input_data_1[2], input_data_1[3]);
tflite::gpu::float4 y_transform(input_data_1[4], input_data_1[5],
input_data_1[6], input_data_1[7]);
// Align corners correction: T -> S * ( T * A ), where T is a
// transformation matrix, and subtruction and addition matrices are:
// S A
// 1 0 0 -0.5 1 0 0 0.5
// 0 1 0 -0.5 0 1 0 0.5
// 0 0 1 0 0 0 1 0
// 0 0 0 1 0 0 0 1
// Transformation matrix column 3 and rows 3, 4 are identity, which makes
// the final formula pretty simple and easy to get if doing a manual
// multiuplication.
x_transform[3] += x_transform[0] * 0.5 + x_transform[1] * 0.5 - 0.5;
y_transform[3] += y_transform[0] * 0.5 + y_transform[1] * 0.5 - 0.5;
for (int out_y = 0; out_y < output_height; ++out_y) {
for (int out_x = 0; out_x < output_width; ++out_x) {
tflite::gpu::float4 coord(
static_cast<float>(out_x), static_cast<float>(out_y),
static_cast<float>(0.0), static_cast<float>(1.0));
// Transformed coordinates.
tflite::gpu::float2 tc(DotProduct(x_transform, coord),
DotProduct(y_transform, coord));
bool out_of_bound = tc.x < 0.0 || tc.x > input_width - 1 || tc.y < 0.0 ||
tc.y > input_height - 1;
for (int out_z = 0; out_z < output_channels; ++out_z) {
float result = 0;
if (!out_of_bound) {
// Corners position:
// q_11 --- q_21
// ---- ----
// q_12 --- q_22
auto ReadValue = [&](int h, int w) -> float {
return h < 0 || w < 0 || h >= input_height || w >= input_width
? 0
: input_data_0[Offset(input_shape_with_batch, 0, h, w,
out_z)];
};
float q_11 = ReadValue(floor(tc.y), floor(tc.x));
float q_21 = ReadValue(floor(tc.y), floor(tc.x) + 1);
float q_12 = ReadValue(floor(tc.y) + 1, floor(tc.x));
float q_22 = ReadValue(floor(tc.y) + 1, floor(tc.x) + 1);
float right_contrib = tc.x - floor(tc.x);
float lower_contrib = tc.y - floor(tc.y);
float upper = (1.0 - right_contrib) * q_11 + right_contrib * q_21;
float lower = (1.0 - right_contrib) * q_12 + right_contrib * q_22;
result = lower_contrib * lower + (1.0 - lower_contrib) * upper;
}
const int out_offset =
Offset(output_shape_with_batch, 0, out_y, out_x, out_z);
output_data[out_offset] = result;
}
}
}
}
TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_EQ(context, tflite::NumInputs(node), 2);
TF_LITE_ENSURE_EQ(context, tflite::NumOutputs(node), 1);
const TfLiteTensor* input =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TF_LITE_ENSURE_EQ(context, tflite::NumDimensions(input), 4);
TF_LITE_ENSURE_EQ(context, input->type, kTfLiteFloat32);
TF_LITE_ENSURE_EQ(context, output->type, kTfLiteFloat32);
return kTfLiteOk;
}
TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
tflite::gpu::TransformTensorBilinearAttributes op_params;
tflite::gpu::BHWC output_shape;
auto status = tflite::gpu::ParseTransformTensorBilinearV2Attributes(
node->custom_initial_data, node->custom_initial_data_size, &op_params,
&output_shape);
if (!status.ok()) {
context->ReportError(context, status.message().data());
return kTfLiteError;
}
const TfLiteTensor* input0 =
tflite::GetInput(context, node, kDataInput0Tensor);
TF_LITE_ENSURE(context, input0 != nullptr);
const TfLiteTensor* input1 =
tflite::GetInput(context, node, kDataInput1Tensor);
TF_LITE_ENSURE(context, input1 != nullptr);
TfLiteTensor* output = tflite::GetOutput(context, node, kOutputTensor);
TF_LITE_ENSURE(context, output != nullptr);
TransformTensorBilinearV2(
op_params, tflite::GetTensorShape(input0),
tflite::GetTensorData<float>(input0), tflite::GetTensorShape(input1),
tflite::GetTensorData<float>(input1), tflite::GetTensorShape(output),
tflite::GetTensorData<float>(output));
return kTfLiteOk;
}
} // namespace v2
} // namespace
TfLiteRegistration* RegisterTransformTensorBilinearV1() {
static TfLiteRegistration reg = {
/*.init=*/nullptr,
/*.free=*/nullptr,
/*.prepare=*/v1::Prepare,
/*.invoke=*/v1::Eval,
/*.profiling_string=*/nullptr,
/*.builtin_code=*/tflite::BuiltinOperator_CUSTOM,
/*.custom_name=*/"TransformTensor",
/*.version=*/1,
};
return &reg;
}
TfLiteRegistration* RegisterTransformTensorBilinearV2() {
static TfLiteRegistration reg = {
/*.init=*/nullptr,
/*.free=*/nullptr,
/*.prepare=*/v2::Prepare,
/*.invoke=*/v2::Eval,
/*.profiling_string=*/nullptr,
/*.builtin_code=*/tflite::BuiltinOperator_CUSTOM,
/*.custom_name=*/"TransformTensorBilinear",
/*.version=*/2,
};
return &reg;
}
} // namespace tflite_operations
} // namespace mediapipe
@@ -0,0 +1,30 @@
// Copyright 2021 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.
#ifndef MEDIAPIPE_UTIL_TFLITE_OPERATIONS_TRANSFORM_TENSOR_BILINEAR_H_
#define MEDIAPIPE_UTIL_TFLITE_OPERATIONS_TRANSFORM_TENSOR_BILINEAR_H_
#include "tensorflow/lite/kernels/kernel_util.h"
namespace mediapipe {
namespace tflite_operations {
TfLiteRegistration* RegisterTransformTensorBilinearV1();
TfLiteRegistration* RegisterTransformTensorBilinearV2();
} // namespace tflite_operations
} // namespace mediapipe
#endif // MEDIAPIPE_UTIL_TFLITE_OPERATIONS_TRANSFORM_TENSOR_BILINEAR_H_