chromium/third_party/mediapipe/src/mediapipe/util/tracking/region_flow_visualization.cc

// Copyright 2019 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/tracking/region_flow_visualization.h"

#include <stddef.h>

#include <memory>
#include <numeric>

#include "absl/log/absl_check.h"
#include "absl/strings/str_cat.h"
#include "mediapipe/util/tracking/measure_time.h"
#include "mediapipe/util/tracking/parallel_invoker.h"
#include "mediapipe/util/tracking/region_flow.h"

namespace mediapipe {

void VisualizeRegionFlowImpl(const RegionFlowFrame& region_flow_frame,
                             cv::Mat* output) {
  // Just draw a green line for each feature.
  for (const auto& region_flow : region_flow_frame.region_flow()) {
    cv::Scalar color(0, 255, 0);

    for (const auto& feature : region_flow.feature()) {
      cv::Point p1(static_cast<int>(feature.x() + 0.5f),
                   static_cast<int>(feature.y() + 0.5f));

      cv::Point p2(static_cast<int>(feature.x() + feature.dx() + 0.5f),
                   static_cast<int>(feature.y() + feature.dy() + 0.5f));

      cv::line(*output, p1, p2, color);
    }
  }
}

void VisualizeRegionFlow(const RegionFlowFrame& region_flow_frame,
                         cv::Mat* output) {
  ABSL_CHECK(output);
  VisualizeRegionFlowImpl(region_flow_frame, output);
}

void VisualizeRegionFlowFeaturesImpl(const RegionFlowFeatureList& feature_list,
                                     const cv::Scalar& color,
                                     const cv::Scalar& outlier,
                                     bool irls_visualization, float scale_x,
                                     float scale_y, cv::Mat* output) {
  const float kLineScaleOneRows = 1080.0;
  const int line_size =
      std::max(std::min<int>(output->rows / kLineScaleOneRows, 4), 1);

  const float text_scale = std::max(output->rows, output->cols) * 3e-4;
  for (const auto& feature : feature_list.feature()) {
    Vector2_i v1 = FeatureIntLocation(feature);
    Vector2_i v2 = FeatureMatchIntLocation(feature);
    cv::Point p1(v1.x() * scale_x, v1.y() * scale_y);
    cv::Point p2(v2.x() * scale_x, v2.y() * scale_y);

    float alpha = 0;
    if (irls_visualization) {
      if (feature.irls_weight() < 0) {
        continue;
      }

      if (feature.irls_weight() == 0.0f) {
        // Ignored feature, draw as circle.
        cv::circle(*output, p1, 4.0 * line_size, outlier, 2 * line_size);
        continue;
      }

      // Scale irls_weight, such that error of one pixel equals alpha of 0.5
      // (evenly mix inlier and outlier color).
      alpha = std::min(1.0f, feature.irls_weight() * 0.5f);

    } else {
      const float feature_stdev_l1 =
          PatchDescriptorColorStdevL1(feature.feature_descriptor());
      if (feature_stdev_l1 >= 0.0f) {
        // Normalize w.r.t. maximum per channel standard deviation (128.0)
        // and scale (scale could be user-defined and was chosen based on some
        // test videos such that only very low textured features are colored
        // with outlier color).
        alpha = std::min(1.0f, feature_stdev_l1 / 128.f * 6.f);
      } else {
        alpha = 0.5;  // Dummy value indicating, descriptor is not present.
      }
    }

#ifdef __APPLE__
    cv::Scalar color_scaled(color.mul(alpha) + outlier.mul(1.0f - alpha));
#else
    cv::Scalar color_scaled(color * alpha + outlier * (1.0f - alpha));
#endif
    cv::line(*output, p1, p2, color_scaled, line_size, cv::LINE_AA);
    cv::circle(*output, p1, 2.0 * line_size, color_scaled, line_size);

    if (feature.has_label()) {
      cv::putText(*output, absl::StrCat(" ", feature.label()), p1,
                  cv::FONT_HERSHEY_SIMPLEX, text_scale, color_scaled,
                  3.0 * text_scale, cv::LINE_AA);
    }
  }
}

void VisualizeRegionFlowFeatures(const RegionFlowFeatureList& feature_list,
                                 const cv::Scalar& color,
                                 const cv::Scalar& outlier,
                                 bool irls_visualization, float scale_x,
                                 float scale_y, cv::Mat* output) {
  ABSL_CHECK(output);
  VisualizeRegionFlowFeaturesImpl(feature_list, color, outlier,
                                  irls_visualization, scale_x, scale_y, output);
}

void VisualizeLongFeatureStreamImpl(const LongFeatureStream& stream,
                                    const cv::Scalar& color,
                                    const cv::Scalar& outlier,
                                    int min_track_length,
                                    int max_points_per_track, float scale_x,
                                    float scale_y, cv::Mat* output) {
  const float text_scale = std::max(output->cols, output->rows) * 3e-4;
  for (const auto& track : stream) {
    std::vector<Vector2_f> pts;
    std::vector<float> irls_weights;
    stream.FlattenTrack(track.second, &pts, &irls_weights, nullptr);

    if (min_track_length > 0 && pts.size() < min_track_length) {
      continue;
    }
    ABSL_CHECK_GT(pts.size(), 1);  // Should have at least two points per track.

    // Tracks are ordered with oldest point first, most recent one last.
    const int start_k =
        max_points_per_track > 0
            ? std::max<int>(0, pts.size() - max_points_per_track)
            : 0;
    // Draw points in last to first order.
    for (int k = start_k; k < pts.size(); ++k) {
      cv::Point p1(pts[k].x() * scale_x, pts[k].y() * scale_y);

      if (irls_weights[k] == 0.0f) {
        // Ignored feature, draw as circle.
        cv::circle(*output, p1, 4.0, outlier, 2);
        continue;
      }

      const float alpha = std::min(1.0f, irls_weights[k] * 0.5f);
#ifdef __APPLE__
      cv::Scalar color_scaled(color.mul(alpha) + outlier.mul(1.0f - alpha));
#else
      cv::Scalar color_scaled(color * alpha + outlier * (1.0f - alpha));
#endif

      if (k < pts.size() - 1) {
        // Draw line connecting points.
        cv::Point p2(pts[k + 1].x() * scale_x, pts[k + 1].y() * scale_y);
        cv::line(*output, p1, p2, color_scaled, 1.0, cv::LINE_AA);
      }
      if (k + 1 == pts.size()) {  // Last iteration.
        cv::circle(*output, p1, 2.0, color_scaled, 1.0);

        const RegionFlowFeature& latest_feature = track.second.back();
        if (latest_feature.has_label()) {
          cv::putText(*output, absl::StrCat(" ", latest_feature.label()), p1,
                      cv::FONT_HERSHEY_SIMPLEX, text_scale, color_scaled,
                      3 * text_scale, cv::LINE_AA);
        }
      }
    }
  }
}

void VisualizeLongFeatureStream(const LongFeatureStream& stream,
                                const cv::Scalar& color,
                                const cv::Scalar& outlier, int min_track_length,
                                int max_points_per_track, float scale_x,
                                float scale_y, cv::Mat* output) {
  ABSL_CHECK(output);
  VisualizeLongFeatureStreamImpl(stream, color, outlier, min_track_length,

                                 max_points_per_track, scale_x, scale_y,
                                 output);
}

}  // namespace mediapipe