absl::Status ImagePreprocessor::Preprocess()

in tensorflow_lite_support/cc/task/processor/image_preprocessor.cc [104:226]


absl::Status ImagePreprocessor::Preprocess(const FrameBuffer& frame_buffer,
                                           const BoundingBox& roi) {
  // Input data to be normalized (if needed) and used for inference. In most
  // cases, this is the result of image preprocessing. In case no image
  // preprocessing is needed (see below), this points to the input frame
  // buffer raw data.
  const uint8* input_data;
  size_t input_data_byte_size;

  // Optional buffers in case image preprocessing is needed.
  std::unique_ptr<FrameBuffer> preprocessed_frame_buffer;
  std::vector<uint8> preprocessed_data;

  if (IsImagePreprocessingNeeded(frame_buffer, roi)) {
    // Preprocess input image to fit model requirements.
    // For now RGB is the only color space supported, which is ensured by
    // `InitInternal`.
    input_specs_.image_width =
        is_width_mutable_ ? roi.width() : input_specs_.image_width;
    input_specs_.image_height =
        is_height_mutable_ ? roi.height() : input_specs_.image_height;

    FrameBuffer::Dimension to_buffer_dimension = {input_specs_.image_width,
                                                  input_specs_.image_height};
    input_data_byte_size =
        GetBufferByteSize(to_buffer_dimension, FrameBuffer::Format::kRGB);
    preprocessed_data.resize(input_data_byte_size / sizeof(uint8), 0);
    input_data = preprocessed_data.data();

    FrameBuffer::Plane preprocessed_plane = {
        /*buffer=*/preprocessed_data.data(),
        /*stride=*/{input_specs_.image_width * kRgbPixelBytes, kRgbPixelBytes}};
    preprocessed_frame_buffer = FrameBuffer::Create(
        {preprocessed_plane}, to_buffer_dimension, FrameBuffer::Format::kRGB,
        FrameBuffer::Orientation::kTopLeft);

    RETURN_IF_ERROR(frame_buffer_utils_->Preprocess(
        frame_buffer, roi, preprocessed_frame_buffer.get()));
  } else {
    // Input frame buffer already targets model requirements: skip image
    // preprocessing. For RGB, the data is always stored in a single plane.
    input_data = frame_buffer.plane(0).buffer;
    input_data_byte_size = frame_buffer.plane(0).stride.row_stride_bytes *
                           frame_buffer.dimension().height;
  }

  // If dynamic, it will re-dim the entire graph as per the input.
  if (is_height_mutable_ || is_width_mutable_) {
    engine_->interpreter()->ResizeInputTensorStrict(
        0, {GetTensor()->dims->data[0], input_specs_.image_height,
            input_specs_.image_width, GetTensor()->dims->data[3]});

    engine_->interpreter()->AllocateTensors();
  }
  // Then normalize pixel data (if needed) and populate the input tensor.
  switch (input_specs_.tensor_type) {
    case kTfLiteUInt8:
      if (GetTensor()->bytes != input_data_byte_size) {
        return tflite::support::CreateStatusWithPayload(
            absl::StatusCode::kInternal,
            "Size mismatch or unsupported padding bytes between pixel data "
            "and input tensor.");
      }
      // No normalization required: directly populate data.
      RETURN_IF_ERROR(tflite::task::core::PopulateTensor(
          input_data, input_data_byte_size / sizeof(uint8), GetTensor()));
      break;
    case kTfLiteFloat32: {
      if (GetTensor()->bytes / sizeof(float) !=
          input_data_byte_size / sizeof(uint8)) {
        return tflite::support::CreateStatusWithPayload(
            absl::StatusCode::kInternal,
            "Size mismatch or unsupported padding bytes between pixel data "
            "and input tensor.");
      }
      // Normalize and populate.
      ASSIGN_OR_RETURN(
          float* normalized_input_data,
          tflite::task::core::AssertAndReturnTypedTensor<float>(GetTensor()));
      const tflite::task::vision::NormalizationOptions& normalization_options =
          input_specs_.normalization_options.value();
      for (int i = 0; i < normalization_options.num_values; i++) {
        if (std::abs(normalization_options.std_values[i]) <
            std::numeric_limits<float>::epsilon()) {
          return tflite::support::CreateStatusWithPayload(
              absl::StatusCode::kInternal,
              "NormalizationOptions.std_values can't be 0. Please check if the "
              "tensor metadata has been populated correctly.");
        }
      }
      if (normalization_options.num_values == 1) {
        float mean_value = normalization_options.mean_values[0];
        float inv_std_value = (1.0f / normalization_options.std_values[0]);
        for (size_t i = 0; i < input_data_byte_size / sizeof(uint8);
             i++, input_data++, normalized_input_data++) {
          *normalized_input_data =
              inv_std_value * (static_cast<float>(*input_data) - mean_value);
        }
      } else {
        std::array<float, 3> inv_std_values = {
            1.0f / normalization_options.std_values[0],
            1.0f / normalization_options.std_values[1],
            1.0f / normalization_options.std_values[2]};
        for (size_t i = 0; i < input_data_byte_size / sizeof(uint8);
             i++, input_data++, normalized_input_data++) {
          *normalized_input_data = inv_std_values[i % 3] *
                                   (static_cast<float>(*input_data) -
                                    normalization_options.mean_values[i % 3]);
        }
      }
      break;
    }
    case kTfLiteInt8:
      return tflite::support::CreateStatusWithPayload(
          absl::StatusCode::kUnimplemented,
          "kTfLiteInt8 input type is not implemented yet.");
    default:
      return tflite::support::CreateStatusWithPayload(
          absl::StatusCode::kInternal, "Unexpected input tensor type.");
  }

  return absl::OkStatus();
}