// 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.
// A container for a dense optical flow field that provides convenient
// visualization and serialization. The flow field stores dx, dy displacement
// for each pixel in absolute pixel values.
#ifndef MEDIAPIPE_FRAMEWORK_FORMATS_MOTION_OPTICAL_FLOW_FIELD_H_
#define MEDIAPIPE_FRAMEWORK_FORMATS_MOTION_OPTICAL_FLOW_FIELD_H_
#include "absl/strings/string_view.h"
#include "mediapipe/framework/formats/location.h"
#include "mediapipe/framework/formats/motion/optical_flow_field_data.pb.h"
#include "mediapipe/framework/port/opencv_imgproc_inc.h"
#include "mediapipe/framework/port/status.h"
#include "tensorflow/core/framework/tensor.h"
namespace mediapipe {
class OpticalFlowField {
public:
OpticalFlowField() {}
explicit OpticalFlowField(const cv::Mat_<cv::Point2f>& flow);
OpticalFlowField(const OpticalFlowField&) = delete;
OpticalFlowField& operator=(const OpticalFlowField&) = delete;
int width() const { return flow_data_.cols; }
int height() const { return flow_data_.rows; }
// Returns the maximum magnitude of motion in the flow field, ignoring values
// larger than 1e9, which are assumed to be outliers. This function can
// return a value of 0.0f for a zero-motion flow field.
float GetRobustMaximumMagnitude() const;
// Computes the color wheel visualization of the flow field. Normalizes based
// on the maximum magnitude in the field. Hue represents angle and saturation
// corresponds to the relative magnitude. The resulting Mat is RGB.
cv::Mat GetVisualization() const;
// Computes the color wheel visualization as if the specified maximum
// magnitude were the true maximum magnitude of the flow. Magnitudes outside
// the allowed range will saturate for visualization. The actual stored flow
// field is not affected. The resulting Mat is RGB.
cv::Mat GetVisualizationSaturatedAt(float max_magnitude) const;
// Allocates internal storage for optical flow field of specified size.
// After calling this function flow_data() and mutable_flow_data() return
// allocated cv::Mat's.
void Allocate(int width, int height);
// Resizes this flow field in place to [new_width, new_height]. Pixel
// displacements are rescaled to correspond to pixels in the new image size.
void Resize(int new_width, int new_height);
// Returns the raw flow data.
const cv::Mat& flow_data() const { return flow_data_; }
cv::Mat& mutable_flow_data() { return flow_data_; }
// Converts from a tensorflow H x W x 2 float Tensor. The internal storage
// for the optical flow field is reallocated.
void CopyFromTensor(const tensorflow::Tensor& tensor);
// Converts to/from associated proto.
void SetFromProto(const OpticalFlowFieldData& proto);
void ConvertToProto(OpticalFlowFieldData* proto) const;
// Propagates the point at (x,y) to the point at (new_x, new_y) based on the
// computed flow. Uses bilinear interpolation to calculate flow values at
// sub-pixel location. Returns false if x is not in [0, width-1] or y is not
// in [0, height-1].
bool FollowFlow(float x, float y, float* new_x, float* new_y) const;
// Returns the (sub-)pixel correspondences implied by the flow field. The
// returned cv::Mat has entries (x + dx, y + dy) at location (x, y).
cv::Mat ConvertToCorrespondences() const;
// Returns true if this OpticalFlowField has the same dimensions as other and
// the values at every pixel are within the specified margin.
bool AllWithinMargin(const OpticalFlowField& other, float margin) const;
// Estimates occluded and disoccluded pixels between two frames based on a
// forward-backward motion consistency check. An occluded pixel is detected
// when following the flow to the opposite frame and then back again moves the
// point more than the specified spatial_distance_threshold. Occluded pixels
// in the first frame and disoccluded pixels in the second frame are marked
// with non-zero values in the respective masks. Occluded or disoccluded may
// be nullptr to skip the computation of the corresponding mask.
// For a forward-backward consistency check that also considers the
// difference in appearance of the corresponding pixels, see
// FlowFollower::FollowFlowWithAllChecks().
static void EstimateMotionConsistencyOcclusions(
const OpticalFlowField& forward, const OpticalFlowField& backward,
double spatial_distance_threshold, Location* occluded_mask,
Location* disoccluded_mask);
private:
// If enforce_max_magnitude is false, max_magnitude will be reset to be
// the actual maximum magnitude of the flow in this frame.
cv::Mat GetVisualizationInternal(float max_magnitude,
bool enforce_max_magnitude) const;
// Computes flow at a valid sub-pixel location using bilinear interpolation.
cv::Point2f InterpolatedFlowAt(float x, float y) const;
// Returns a mask Location with non-zero values for pixels that fail a
// forward-backward motion-consistency check. Pixels fail the check if
// following flow into the next frame and back again moves a point by more
// than the specified spatial_distance_threshold.
static Location FindMotionInconsistentPixels(
const OpticalFlowField& forward, const OpticalFlowField& backward,
double spatial_distance_threshold);
cv::Mat_<cv::Point2f> flow_data_;
};
} // namespace mediapipe
#endif // MEDIAPIPE_FRAMEWORK_FORMATS_MOTION_OPTICAL_FLOW_FIELD_H_