ResizeImageOps.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. * Copyright (C) 2019 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #define LOG_TAG "Operations"
  17. #include "CpuOperationUtils.h"
  18. #include "HalInterfaces.h"
  19. #include "OperationResolver.h"
  20. #include "Tracing.h"
  21. #include "tensorflow/lite/kernels/internal/reference/reference_ops.h"
  22. #include <functional>
  23. #include <vector>
  24. namespace android {
  25. namespace nn {
  26. namespace resize_image {
  27. constexpr uint32_t kNumInputs = 4;
  28. constexpr uint32_t kInputTensor = 0;
  29. // The following two scalars represent output shape if INT32, scale if floating point.
  30. constexpr uint32_t kOutputWidthParamScalar = 1;
  31. constexpr uint32_t kOutputHeightParamScalar = 2;
  32. constexpr uint32_t kLayoutScalar = 3;
  33. constexpr uint32_t kNumOutputs = 1;
  34. constexpr uint32_t kOutputTensor = 0;
  35. namespace {
  36. template <typename T>
  37. bool resizeImageOpNhwc(OperationType opType, const T* inputData, const Shape& inputShape,
  38. T* outputData, const Shape& outputShape) {
  39. NNTRACE_TRANS("resizeImageOpNhwc");
  40. int32_t height = static_cast<int32_t>(getSizeOfDimension(outputShape, 1));
  41. int32_t width = static_cast<int32_t>(getSizeOfDimension(outputShape, 2));
  42. // We have to fake a tensor here, to satisfy tflite implementation.
  43. int32_t outDimData[2] = {height, width};
  44. Shape outDimShape;
  45. outDimShape.dimensions = {2};
  46. if (opType == OperationType::RESIZE_BILINEAR) {
  47. NNTRACE_COMP_SWITCH("optimized_ops::ResizeBilinear");
  48. tflite::reference_ops::ResizeBilinear({.align_corners = false},
  49. convertShapeToTflshape(inputShape), inputData,
  50. convertShapeToTflshape(outDimShape), outDimData,
  51. convertShapeToTflshape(outputShape), outputData);
  52. } else if (opType == OperationType::RESIZE_NEAREST_NEIGHBOR) {
  53. // Align corners = true is not supported.
  54. NNTRACE_COMP_SWITCH("optimized_ops::ResizeNearestNeighbor");
  55. tflite::reference_ops::ResizeNearestNeighbor(
  56. {.align_corners = false}, convertShapeToTflshape(inputShape), inputData,
  57. convertShapeToTflshape(outDimShape), outDimData,
  58. convertShapeToTflshape(outputShape), outputData);
  59. }
  60. return true;
  61. }
  62. template <>
  63. bool resizeImageOpNhwc<_Float16>(OperationType opType, const _Float16* inputData,
  64. const Shape& inputShape, _Float16* outputData,
  65. const Shape& outputShape) {
  66. NNTRACE_TRANS("resizeImageOpNhwcFloat16");
  67. std::vector<float> inputData_float32(getNumberOfElements(inputShape));
  68. convertFloat16ToFloat32(inputData, &inputData_float32);
  69. std::vector<float> outputData_float32(getNumberOfElements(outputShape));
  70. NN_RET_CHECK(resizeImageOpNhwc(opType, inputData_float32.data(), inputShape,
  71. outputData_float32.data(), outputShape));
  72. convertFloat32ToFloat16(outputData_float32, outputData);
  73. return true;
  74. }
  75. template <typename T>
  76. bool resizeImageOp(OperationType opType, const T* inputData, const Shape& inputShape, bool useNchw,
  77. T* outputData, const Shape& outputShape) {
  78. InputWithLayout<T> input(useNchw);
  79. OutputWithLayout<T> output(useNchw);
  80. NN_RET_CHECK(input.initialize(inputData, inputShape));
  81. NN_RET_CHECK(output.initialize(outputData, outputShape));
  82. NN_RET_CHECK(resizeImageOpNhwc(opType, input.getNhwcBuffer(), input.getNhwcShape(),
  83. output.getNhwcBuffer(), output.getNhwcShape()));
  84. NN_RET_CHECK(output.commit());
  85. return true;
  86. }
  87. } // namespace
  88. bool validate(OperationType opType, const IOperationValidationContext* context) {
  89. if (opType == OperationType::RESIZE_BILINEAR) {
  90. NN_RET_CHECK(context->getNumInputs() == kNumInputs ||
  91. context->getNumInputs() == kNumInputs - 1);
  92. } else if (opType == OperationType::RESIZE_NEAREST_NEIGHBOR) {
  93. NN_RET_CHECK_EQ(context->getNumInputs(), kNumInputs);
  94. } else {
  95. NN_RET_CHECK_FAIL() << "Unsupported operation " << getOperationName(opType);
  96. }
  97. NN_RET_CHECK_EQ(context->getNumOutputs(), kNumOutputs);
  98. auto inputType = context->getInputType(kInputTensor);
  99. auto scalarType = context->getInputType(kOutputHeightParamScalar);
  100. std::vector<OperandType> inExpectedTypes = {inputType, scalarType, scalarType};
  101. NN_RET_CHECK(inputType == OperandType::TENSOR_FLOAT16 ||
  102. inputType == OperandType::TENSOR_FLOAT32 ||
  103. inputType == OperandType::TENSOR_QUANT8_ASYMM)
  104. << "Unsupported tensor type for operation " << getOperationName(opType);
  105. if (inputType == OperandType::TENSOR_FLOAT16 || inputType == OperandType::TENSOR_QUANT8_ASYMM) {
  106. NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_2));
  107. }
  108. if (scalarType != OperandType::INT32) {
  109. NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_2));
  110. if (inputType == OperandType::TENSOR_FLOAT32) {
  111. NN_RET_CHECK(scalarType == OperandType::FLOAT32);
  112. } else if (inputType == OperandType::TENSOR_FLOAT16) {
  113. NN_RET_CHECK(scalarType == OperandType::FLOAT16);
  114. } else if (inputType == OperandType::TENSOR_QUANT8_ASYMM) {
  115. NN_RET_CHECK(scalarType == OperandType::FLOAT32);
  116. }
  117. }
  118. if (context->getNumInputs() == kNumInputs) {
  119. inExpectedTypes.push_back(OperandType::BOOL);
  120. NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_2));
  121. } else {
  122. NN_RET_CHECK(validateHalVersion(context, HalVersion::V1_0));
  123. }
  124. return validateInputTypes(context, inExpectedTypes) &&
  125. validateOutputTypes(context, {inputType});
  126. }
  127. bool prepare(OperationType opType, IOperationExecutionContext* context) {
  128. Shape input = context->getInputShape(kInputTensor);
  129. NN_RET_CHECK_EQ(getNumberOfDimensions(input), 4);
  130. bool useNchw = false;
  131. if (context->getNumInputs() > kLayoutScalar) {
  132. useNchw = context->getInputValue<bool>(kLayoutScalar);
  133. }
  134. // Only batches can be zero.
  135. uint32_t batches = getSizeOfDimension(input, 0);
  136. uint32_t inHeight = getSizeOfDimension(input, useNchw ? 2 : 1);
  137. uint32_t inWidth = getSizeOfDimension(input, useNchw ? 3 : 2);
  138. uint32_t channels = getSizeOfDimension(input, useNchw ? 1 : 3);
  139. NN_RET_CHECK_GT(inHeight, 0);
  140. NN_RET_CHECK_GT(inWidth, 0);
  141. NN_RET_CHECK_GT(channels, 0);
  142. int32_t height, width;
  143. auto scalarType = context->getInputType(kOutputHeightParamScalar);
  144. if (scalarType == OperandType::INT32) {
  145. height = context->getInputValue<int32_t>(kOutputHeightParamScalar);
  146. width = context->getInputValue<int32_t>(kOutputWidthParamScalar);
  147. } else if (scalarType == OperandType::FLOAT32) {
  148. height = std::floor(static_cast<float>(inHeight) *
  149. context->getInputValue<float>(kOutputHeightParamScalar));
  150. width = std::floor(static_cast<float>(inWidth) *
  151. context->getInputValue<float>(kOutputWidthParamScalar));
  152. } else if (scalarType == OperandType::FLOAT16) {
  153. height = std::floor(
  154. static_cast<float>(inHeight) *
  155. static_cast<float>(context->getInputValue<_Float16>(kOutputHeightParamScalar)));
  156. width = std::floor(
  157. static_cast<float>(inWidth) *
  158. static_cast<float>(context->getInputValue<_Float16>(kOutputWidthParamScalar)));
  159. } else {
  160. NN_RET_CHECK_FAIL() << "Unsupported scalar type for operation " << getOperationName(opType);
  161. }
  162. NN_RET_CHECK_GT(height, 0);
  163. NN_RET_CHECK_GT(width, 0);
  164. Shape output = input;
  165. if (useNchw) {
  166. output.dimensions = {batches, channels, (uint32_t)height, (uint32_t)width};
  167. } else {
  168. output.dimensions = {batches, (uint32_t)height, (uint32_t)width, channels};
  169. }
  170. return context->setOutputShape(kOutputTensor, output);
  171. }
  172. bool execute(OperationType opType, IOperationExecutionContext* context) {
  173. // Bypass execution in the case of zero-sized input.
  174. if (getNumberOfElements(context->getOutputShape(kOutputTensor)) == 0) return true;
  175. bool useNchw = false;
  176. if (context->getNumInputs() > kLayoutScalar) {
  177. useNchw = context->getInputValue<bool>(kLayoutScalar);
  178. }
  179. switch (context->getInputType(kInputTensor)) {
  180. case OperandType::TENSOR_FLOAT16:
  181. return resizeImageOp(opType, context->getInputBuffer<_Float16>(kInputTensor),
  182. context->getInputShape(kInputTensor), useNchw,
  183. context->getOutputBuffer<_Float16>(kOutputTensor),
  184. context->getOutputShape(kOutputTensor));
  185. case OperandType::TENSOR_FLOAT32:
  186. return resizeImageOp(opType, context->getInputBuffer<float>(kInputTensor),
  187. context->getInputShape(kInputTensor), useNchw,
  188. context->getOutputBuffer<float>(kOutputTensor),
  189. context->getOutputShape(kOutputTensor));
  190. case OperandType::TENSOR_QUANT8_ASYMM:
  191. return resizeImageOp(opType, context->getInputBuffer<uint8_t>(kInputTensor),
  192. context->getInputShape(kInputTensor), useNchw,
  193. context->getOutputBuffer<uint8_t>(kOutputTensor),
  194. context->getOutputShape(kOutputTensor));
  195. default:
  196. NN_RET_CHECK_FAIL() << "Unsupported tensor type for operation "
  197. << getOperationName(opType);
  198. }
  199. }
  200. } // namespace resize_image
  201. using std::placeholders::_1;
  202. NN_REGISTER_OPERATION(RESIZE_BILINEAR, "RESIZE_BILINEAR",
  203. std::bind(resize_image::validate, OperationType::RESIZE_BILINEAR, _1),
  204. std::bind(resize_image::prepare, OperationType::RESIZE_BILINEAR, _1),
  205. std::bind(resize_image::execute, OperationType::RESIZE_BILINEAR, _1),
  206. .allowZeroSizedInput = true);
  207. NN_REGISTER_OPERATION(RESIZE_NEAREST_NEIGHBOR, "RESIZE_NEAREST_NEIGHBOR",
  208. std::bind(resize_image::validate, OperationType::RESIZE_NEAREST_NEIGHBOR, _1),
  209. std::bind(resize_image::prepare, OperationType::RESIZE_NEAREST_NEIGHBOR, _1),
  210. std::bind(resize_image::execute, OperationType::RESIZE_NEAREST_NEIGHBOR, _1),
  211. .allowZeroSizedInput = true);
  212. } // namespace nn
  213. } // namespace android