// 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. #ifndef MEDIAPIPE_UTIL_TRACKING_BOX_TRACKER_H_ #define MEDIAPIPE_UTIL_TRACKING_BOX_TRACKER_H_ #include #include #include #include #include "absl/strings/str_format.h" #include "absl/synchronization/mutex.h" #include "mediapipe/framework/port/threadpool.h" #include "mediapipe/util/tracking/box_tracker.pb.h" #include "mediapipe/util/tracking/flow_packager.pb.h" #include "mediapipe/util/tracking/tracking.h" #include "mediapipe/util/tracking/tracking.pb.h" namespace mediapipe { // Describes a rectangle box at an instance of time. struct TimedBox { static const int kNumQuadVertices = 4; // Dimensions are in normalized coordinates [0, 1]. float top = 0; float left = 0; float bottom = 0; float right = 0; // Rotation of box w.r.t. center in radians. float rotation = 0; int64 time_msec = 0; // Confidence of the tracked box in range [0, 1]. float confidence = 0; std::vector quad_vertices; // Aspect ratio (width / height) for the tracked rectangle in physical space. float aspect_ratio = -1.0; // Whether we want this box to be potentially grouped with other boxes // to track together. This is useful for tracking small boxes that lie // on a plane. For example, when we detect a plane, // track the plane, then all boxes within the plane can share the same // homography transform. bool request_grouping = false; bool operator<(const TimedBox& rhs) const { return time_msec < rhs.time_msec; } // Returns (1.0 - alpha) * lhs + alpha * rhs; static TimedBox Blend(const TimedBox& lhs, const TimedBox& rhs, double alpha); // Returns lhs * alpha + rhs * beta; static TimedBox Blend(const TimedBox& lhs, const TimedBox& rhs, double alpha, double beta); std::string ToString() const { return absl::StrFormat( "top: %.3f left: %.3f bottom: %.3f right: %.3f " "rot: %.3f t: %d", top, left, bottom, right, rotation, static_cast(time_msec)); } // Returns corners of TimedBox in the requested domain. std::array Corners(float width, float height) const; static TimedBox FromProto(const TimedBoxProto& proto); TimedBoxProto ToProto() const; }; // TimedBox augment with internal states. struct InternalTimedBox : public TimedBox { InternalTimedBox() = default; // Convenience constructor. InternalTimedBox(const TimedBox& box, const MotionBoxState* state_) : TimedBox(box), state(state_) {} // Corresponding MotionBoxState a TimedBox. std::shared_ptr state; }; // Initializes beginning tracking state from a TimedBox. void MotionBoxStateFromTimedBox(const TimedBox& box, MotionBoxState* state); // Retrieves box position and time from a tracking state. void TimedBoxFromMotionBoxState(const MotionBoxState& state, TimedBox* box); // Downgrade tracking degrees of TrackStepOptions to // TRACKING_DEGREE_OBJECT_ROTATION_SCALE if originally // specified TRACKING_DEGREE_OBJECT_PERSPECTIVE, but start pos // does not contain quad or contains invalid quad. template void ChangeTrackingDegreesBasedOnStartPos( const T& start_pos, TrackStepOptions* track_step_options) { if (track_step_options->tracking_degrees() == TrackStepOptions::TRACKING_DEGREE_OBJECT_PERSPECTIVE && (!start_pos.has_quad() || start_pos.quad().vertices_size() != 8)) { track_step_options->set_tracking_degrees( TrackStepOptions::TRACKING_DEGREE_OBJECT_ROTATION_SCALE); VLOG(1) << "Originally specified TRACKING_DEGREE_OBJECT_PERSPECTIVE, but " "changed to TRACKING_DEGREE_OBJECT_ROTATION_SCALE"; } } // Set of boxes for a particular checkpoint. typedef std::deque PathSegment; // Stores the PathSegment for each checkpoint time. typedef std::map Path; // Returns box at specified time for a specific path segment. // Returns true on success, otherwise box is left untouched. // Optionally returns closest known MotionBoxState if state is not null. bool TimedBoxAtTime(const PathSegment& segment, int64 time_msec, TimedBox* box, MotionBoxState* state = nullptr); // Tracks timed boxes from cached TrackingDataChunks created by // FlowPackagerCalculator. For usage see accompanying test. class BoxTracker { public: // Initializes a new BoxTracker to work on cached TrackingData from a chunk // directory. BoxTracker(const std::string& cache_dir, const BoxTrackerOptions& options); // Initializes a new BoxTracker to work on the passed TrackingDataChunks. // If copy_data is true, BoxTracker will retain its own copy of the data; // otherwise the passed pointer need to be valid for the lifetime of the // BoxTracker. BoxTracker(const std::vector& tracking_data, bool copy_data, const BoxTrackerOptions& options); // Add single TrackingDataChunk. This chunk must be correctly aligned with // existing chunks. If chunk starting timestamp is larger than next valid // chunk timestamp, empty chunks will be added to fill the gap. If copy_data // is true, BoxTracker will retain its own copy of the data; otherwise the // passed pointers need to be valid for the lifetime of the BoxTracker. void AddTrackingDataChunk(const TrackingDataChunk* chunk, bool copy_data); // Add new TrackingDataChunks. This method allows to streamline BoxTracker // usage. Instead of collecting all tracking chunks and then calling the // constructor, it makes possible to pass only minimally required chunks to // constructor, and then append new chunks when available. // Caller is responsible to guarantee that all chunks that are necessary for // tracking are at the disposal of BoxTracker before calling NewBoxTrack, i.e. // min_msec and max_msec parameters must be in the range of chunks passed to // BoxTracker. // Chunks will be added in the order they are specified in tracking_data. Each // chunk must be correctly aligned with existing chunks. If chunk starting // timestamp is larger than next valid chunk timestamp, empty chunks will be // added to fill the gap. If copy_data is true, BoxTracker will retain its own // copy of the data; otherwise the passed pointers need to be valid for the // lifetime of the BoxTracker. void AddTrackingDataChunks( const std::vector& tracking_data, bool copy_data); // Starts a new track for the specified box until tracking terminates or // until track length achieves max_length. // Does not block caller, returns immediately. // Note: Use positive integers for id, we reserve negative ones for debugging // and visualization purposes. void NewBoxTrack(const TimedBox& initial_pos, int id, int64 min_msec = 0, int64 max_msec = std::numeric_limits::max()); // Returns interval for which the state of the specified box is known. // (Returns -1, -1 if id is missing or no tracking has been done). std::pair TrackInterval(int id); // Returns box position for requested box at specified time. // Returns false if no such box exists. // Optional, if record_path_states is enabled in options outputs the // corresponding states for the resulting TimedBox. Note, as TimedBoxes are // generally interpolated from the underlying data and blended across // checkpoints, the returned states are the closest (snapped) known tracking // states for the left and right checkpoint path. Therefore the size of states // is either two or one (if only one checkpoint exists). bool GetTimedPosition(int id, int64 time_msec, TimedBox* result, std::vector* states = nullptr); // Returns chunk index for specified time. int ChunkIdxFromTime(int64 msec) const { return msec / options_.caching_chunk_size_msec(); } // Returns true if any tracking is ongoing for the specified id. bool IsTrackingOngoingForId(int id) ABSL_LOCKS_EXCLUDED(status_mutex_); // Returns true if any tracking is ongoing. bool IsTrackingOngoing() ABSL_LOCKS_EXCLUDED(status_mutex_); // Cancels all ongoing tracks. To avoid race conditions all NewBoxTrack's in // flight will also be canceled. Future NewBoxTrack's will be canceled. // NOTE: To resume execution, you have to call ResumeTracking() before // issuing more NewBoxTrack calls. void CancelAllOngoingTracks() ABSL_LOCKS_EXCLUDED(status_mutex_); void ResumeTracking() ABSL_LOCKS_EXCLUDED(status_mutex_); // Waits for all ongoing tracks to complete. // Optionally accepts a timeout in microseconds (== 0 for infinite wait). // Returns true on success, false if timeout is reached. // NOTE: If WaitForAllOngoingTracks timed out, CancelAllOngoingTracks() must // be called before destructing the BoxTracker object or dangeling running // threads might try to access invalid data. bool WaitForAllOngoingTracks(int timeout_us = 0) ABSL_LOCKS_EXCLUDED(status_mutex_); // Debug function to obtain raw TrackingData closest to the specified // timestamp. This call will read from disk on every invocation so it is // expensive. // To not interfere with other tracking requests it is recommended that you // use a unique id here. // Returns true on success. bool GetTrackingData(int id, int64 request_time_msec, TrackingData* tracking_data, int* tracking_data_msec = nullptr); private: // Asynchronous implementation function for box tracking. Schedules forward // and backward tracking. void NewBoxTrackAsync(const TimedBox& initial_pos, int id, int64 min_msec, int64 max_msec); typedef std::pair AugmentedChunkPtr; // Attempts to read chunk at chunk_idx if it exists. Reads from cache // directory or from in memory cache. // Important: 2nd part of return value indicates if returned tracking data // will be owned by the caller (if true). In that case caller is responsible // for releasing the returned chunk. AugmentedChunkPtr ReadChunk(int id, int checkpoint, int chunk_idx); // Attempts to read specified chunk from caching directory. Blocks and waits // until chunk is available or internal time out is reached. // Returns nullptr if data could not be read. std::unique_ptr ReadChunkFromCache(int id, int checkpoint, int chunk_idx); // Waits with timeout for chunkfile to become available. Returns true on // success, false if waited till timeout or when canceled. bool WaitForChunkFile(int id, int checkpoint, const std::string& chunk_file) ABSL_LOCKS_EXCLUDED(status_mutex_); // Determines closest index in passed TrackingDataChunk int ClosestFrameIndex(int64 msec, const TrackingDataChunk& chunk) const; // Adds new TimedBox to specified checkpoint with state. void AddBoxResult(const TimedBox& box, int id, int checkpoint, const MotionBoxState& state); // Callback can only handle 5 args max. // Set own_data to true for args to assume ownership. struct TrackingImplArgs { TrackingImplArgs(AugmentedChunkPtr chunk_ptr, const MotionBoxState& start_state_, int start_frame_, int chunk_idx_, int id_, int checkpoint_, bool forward_, bool first_call_, int64 min_msec_, int64 max_msec_) : start_state(start_state_), start_frame(start_frame_), chunk_idx(chunk_idx_), id(id_), checkpoint(checkpoint_), forward(forward_), first_call(first_call_), min_msec(min_msec_), max_msec(max_msec_) { if (chunk_ptr.second) { chunk_data_buffer.reset(chunk_ptr.first); } chunk_data = chunk_ptr.first; } TrackingImplArgs(const TrackingImplArgs&) = default; // Storage for tracking data. std::shared_ptr chunk_data_buffer; // Pointer to the actual tracking data. Usually points to the buffer // but can also point to external data for performance reasons. const TrackingDataChunk* chunk_data; MotionBoxState start_state; int start_frame; int chunk_idx; int id; int checkpoint; bool forward = true; bool first_call = true; int64 min_msec; // minimum timestamp to track to int64 max_msec; // maximum timestamp to track to }; // Actual tracking algorithm. void TrackingImpl(const TrackingImplArgs& args); // Ids are scheduled exclusively, run this method to acquire lock. // Returns false if id could not be scheduled (e.g. id got canceled during // waiting). bool WaitToScheduleId(int id) ABSL_LOCKS_EXCLUDED(status_mutex_); // Signals end of scheduling phase. Requires status mutex to be held. void DoneSchedulingId(int id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(status_mutex_); // Removes all checkpoints within vicinity of new checkpoint. void RemoveCloseCheckpoints(int id, int checkpoint) ABSL_EXCLUSIVE_LOCKS_REQUIRED(status_mutex_); // Removes specific checkpoint. void ClearCheckpoint(int id, int checkpoint) ABSL_EXCLUSIVE_LOCKS_REQUIRED(status_mutex_); // Terminates tracking for specific id and checkpoint. void CancelTracking(int id, int checkpoint) ABSL_EXCLUSIVE_LOCKS_REQUIRED(status_mutex_); // Implementation function for IsTrackingOngoing assuming mutex is already // held. bool IsTrackingOngoingMutexHeld() ABSL_EXCLUSIVE_LOCKS_REQUIRED(status_mutex_); // Captures tracking status for each checkpoint struct TrackStatus { // Indicates that all current tracking processes should be canceled. bool canceled = false; // Number of tracking requests than are currently ongoing. int tracks_ongoing = 0; }; private: // Stores computed tracking paths_ for all boxes. std::unordered_map paths_ ABSL_GUARDED_BY(path_mutex_); absl::Mutex path_mutex_; // For each id and each checkpoint stores current tracking status. std::unordered_map> track_status_ ABSL_GUARDED_BY(status_mutex_); // Keeps track which ids are currently processing in NewBoxTrack. std::unordered_map new_box_track_ ABSL_GUARDED_BY(status_mutex_); absl::Mutex status_mutex_; bool canceling_ ABSL_GUARDED_BY(status_mutex_) = false; // Use to signal changes to status_condvar_; absl::CondVar status_condvar_ ABSL_GUARDED_BY(status_mutex_); BoxTrackerOptions options_; // Caching directory for TrackingData stored on disk. std::string cache_dir_; // Pointers to tracking data stored in memory. std::vector tracking_data_; // Buffer for tracking data in case we retain a deep copy. std::vector> tracking_data_buffer_; // Workers that run the tracking algorithm. std::unique_ptr tracking_workers_; }; } // namespace mediapipe #endif // MEDIAPIPE_UTIL_TRACKING_BOX_TRACKER_H_