2 * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include "Tracking/MFTracker.h"
19 #include <opencv/cv.h>
21 namespace MediaVision {
24 const float FloatEps = 10e-6f;
27 T getMedian(std::vector<T>& values, int size = -1) {
29 size = (int)values.size();
31 std::vector<T> copy(values.begin(), values.begin() + size);
32 std::sort(copy.begin(), copy.end());
34 return (copy[size/2-1]+copy[size/2])/((T)2.0);
36 return copy[(size - 1) / 2];
39 inline float l2distance(cv::Point2f p1, cv::Point2f p2) {
40 const float dx = p1.x - p2.x;
41 const float dy = p1.y - p2.y;
42 return sqrtf(dx * dx + dy * dy);
44 } /* anonymous namespace */
46 MFTracker::Params::Params()
49 mWindowSize = cv::Size(3, 3);
53 MFTracker::MFTracker(Params params) :
56 m_termcrit(cv::TermCriteria::COUNT | cv::TermCriteria::EPS, 20, 0.3),
61 bool MFTracker::track(const cv::Mat& frame, std::vector<cv::Point>& result)
66 if (m_startLocation.empty())
74 m_startLocation.clear();
79 const size_t numberOfContourPoints = m_startLocation.size();
80 result.resize(numberOfContourPoints);
82 for (size_t i = 0; i < numberOfContourPoints; ++i) {
83 result[i].x = (int)(m_boundingBox.x +
84 m_startLocation[i].x * m_boundingBox.width);
85 result[i].y = (int)(m_boundingBox.y +
86 m_startLocation[i].y * m_boundingBox.height);
92 void MFTracker::reinforcement(const std::vector<cv::Point>& location)
96 if (location.size() < 3) {
97 m_startLocation.clear();
100 m_boundingBox.width = 0;
101 m_boundingBox.height = 0;
106 const cv::Rect_<float>& boundingBox = cv::boundingRect(location);
107 m_boundingBox = boundingBox;
109 const size_t numberOfContourPoints = location.size();
110 m_startLocation.resize(numberOfContourPoints);
111 for (size_t i = 0; i < numberOfContourPoints; ++i) {
112 m_startLocation[i].x = (location[i].x - boundingBox.x) / boundingBox.width;
113 m_startLocation[i].y = (location[i].y - boundingBox.y) / boundingBox.height;
117 cv::Ptr<ObjectTracker> MFTracker::clone() const
119 return cv::Ptr<ObjectTracker>(new MFTracker(*this));
122 bool MFTracker::init(const cv::Mat& image)
127 image.copyTo(m_image);
128 buildOpticalFlowPyramid(
131 m_params.mWindowSize,
132 m_params.mPyrMaxLevel);
138 bool MFTracker::update(const cv::Mat& image)
140 if (!m_isInit || image.empty())
143 /* Handles such behaviour when preparation frame has the size
144 * different to the tracking frame size. In such case, we resize preparation
145 *frame and bounding box. Then, track as usually:
147 if (m_image.rows != image.rows || m_image.cols != image.cols) {
148 const float xFactor = (float) image.cols / m_image.cols;
149 const float yFactor = (float) image.rows / m_image.rows;
151 resize(m_image, m_image, cv::Size(), xFactor, yFactor);
153 m_boundingBox.x *= xFactor;
154 m_boundingBox.y *= yFactor;
155 m_boundingBox.width *= xFactor;
156 m_boundingBox.height *= yFactor;
159 cv::Mat oldImage = m_image;
161 cv::Rect_<float> oldBox = m_boundingBox;
162 if (!medianFlowImpl(oldImage, image, oldBox))
165 image.copyTo(m_image);
166 m_boundingBox = oldBox;
171 bool MFTracker::isInited() const
176 float MFTracker::getLastConfidence() const
181 cv::Rect_<float> MFTracker::getLastBoundingBox() const
183 return m_boundingBox;
186 bool MFTracker::medianFlowImpl(
187 cv::Mat oldImage_gray, cv::Mat newImage_gray, cv::Rect_<float>& oldBox)
189 std::vector<cv::Point2f> pointsToTrackOld, pointsToTrackNew;
191 const float gridXStep = oldBox.width / m_params.mPointsInGrid;
192 const float gridYStep = oldBox.height / m_params.mPointsInGrid;
193 for (int i = 0; i < m_params.mPointsInGrid; i++) {
194 for (int j = 0; j < m_params.mPointsInGrid; j++) {
195 pointsToTrackOld.push_back(
196 cv::Point2f(oldBox.x + .5f*gridXStep + 1.f*gridXStep*j,
197 oldBox.y + .5f*gridYStep + 1.f*gridYStep*i));
201 const size_t numberOfPointsToTrackOld = pointsToTrackOld.size();
202 std::vector<uchar> status(numberOfPointsToTrackOld);
203 std::vector<float> errors(numberOfPointsToTrackOld);
205 std::vector<cv::Mat> tempPyramid;
206 cv::buildOpticalFlowPyramid(
209 m_params.mWindowSize,
210 m_params.mPyrMaxLevel);
212 cv::calcOpticalFlowPyrLK(m_pyramid,
218 m_params.mWindowSize,
219 m_params.mPyrMaxLevel,
222 std::vector<cv::Point2f> di;
223 for (size_t idx = 0u; idx < numberOfPointsToTrackOld; idx++) {
224 if (status[idx] == 1)
225 di.push_back(pointsToTrackNew[idx] - pointsToTrackOld[idx]);
228 std::vector<bool> filter_status;
229 check_FB(tempPyramid,
234 check_NCC(oldImage_gray,
240 for (size_t idx = 0u; idx < pointsToTrackOld.size(); idx++) {
241 if (!filter_status[idx]) {
242 pointsToTrackOld.erase(pointsToTrackOld.begin() + idx);
243 pointsToTrackNew.erase(pointsToTrackNew.begin() + idx);
244 filter_status.erase(filter_status.begin() + idx);
249 if (pointsToTrackOld.empty() || di.empty())
252 cv::Point2f mDisplacement;
253 cv::Rect_<float> boxCandidate =
254 vote(pointsToTrackOld, pointsToTrackNew, oldBox, mDisplacement);
256 std::vector<float> displacements;
257 for (size_t idx = 0u; idx < di.size(); idx++) {
258 di[idx] -= mDisplacement;
259 displacements.push_back(sqrt(di[idx].ddot(di[idx])));
262 m_confidence = (10.f - getMedian(displacements, (int)displacements.size())) / 10.f;
264 if (m_confidence < 0.f) {
269 m_pyramid.swap(tempPyramid);
270 oldBox = boxCandidate;
274 cv::Rect_<float> MFTracker::vote(
275 const std::vector<cv::Point2f>& oldPoints,
276 const std::vector<cv::Point2f>& newPoints,
277 const cv::Rect_<float>& oldRect,
280 cv::Rect_<float> newRect;
281 cv::Point2f newCenter(
282 oldRect.x + oldRect.width / 2.f,
283 oldRect.y + oldRect.height / 2.f);
285 const int n = (int)oldPoints.size();
286 std::vector<float> buf(std::max(n*(n-1) / 2, 3), 0.f);
288 if (oldPoints.size() == 1) {
289 newRect.x = oldRect.x+newPoints[0].x-oldPoints[0].x;
290 newRect.y = oldRect.y+newPoints[0].y-oldPoints[0].y;
291 newRect.width = oldRect.width;
292 newRect.height = oldRect.height;
299 for (int i = 0; i < n; i++)
300 buf[i] = newPoints[i].x - oldPoints[i].x;
302 xshift = getMedian(buf, n);
303 newCenter.x += xshift;
304 for (int idx = 0; idx < n; idx++)
305 buf[idx] = newPoints[idx].y - oldPoints[idx].y;
307 yshift = getMedian(buf, n);
308 newCenter.y += yshift;
309 mD = cv::Point2f(xshift, yshift);
311 if (oldPoints.size() == 1) {
312 newRect.x = newCenter.x - oldRect.width / 2.f;
313 newRect.y = newCenter.y - oldRect.height / 2.f;
314 newRect.width = oldRect.width;
315 newRect.height = oldRect.height;
322 for (int i = 0, ctr = 0; i < n; i++) {
323 for (int j = 0; j < i; j++) {
324 nd = l2distance(newPoints[i], newPoints[j]);
325 od = l2distance(oldPoints[i], oldPoints[j]);
326 buf[ctr] = (od == 0.f ? 0.f : nd / od);
331 float scale = getMedian(buf, n*(n-1) / 2);
332 newRect.x = newCenter.x - scale * oldRect.width / 2.f;
333 newRect.y = newCenter.y-scale * oldRect.height / 2.f;
334 newRect.width = scale * oldRect.width;
335 newRect.height = scale * oldRect.height;
340 void MFTracker::check_FB(
341 std::vector<cv::Mat> newPyramid,
342 const std::vector<cv::Point2f>& oldPoints,
343 const std::vector<cv::Point2f>& newPoints,
344 std::vector<bool>& status)
346 const size_t numberOfOldPoints = oldPoints.size();
349 status = std::vector<bool>(numberOfOldPoints, true);
351 std::vector<uchar> LKstatus(numberOfOldPoints);
352 std::vector<float> errors(numberOfOldPoints);
353 std::vector<float> FBerror(numberOfOldPoints);
354 std::vector<cv::Point2f> pointsToTrackReprojection;
356 calcOpticalFlowPyrLK(newPyramid,
359 pointsToTrackReprojection,
362 m_params.mWindowSize,
363 m_params.mPyrMaxLevel,
366 for (size_t idx = 0u; idx < numberOfOldPoints; idx++)
367 FBerror[idx] = l2distance(oldPoints[idx], pointsToTrackReprojection[idx]);
369 float FBerrorMedian = getMedian(FBerror) + FloatEps;
370 for (size_t idx = 0u; idx < numberOfOldPoints; idx++)
371 status[idx] = (FBerror[idx] < FBerrorMedian);
374 void MFTracker::check_NCC(
375 const cv::Mat& oldImage,
376 const cv::Mat& newImage,
377 const std::vector<cv::Point2f>& oldPoints,
378 const std::vector<cv::Point2f>& newPoints,
379 std::vector<bool>& status)
381 std::vector<float> NCC(oldPoints.size(), 0.f);
382 cv::Size patch(30, 30);
386 for (size_t idx = 0u; idx < oldPoints.size(); idx++) {
387 getRectSubPix(oldImage, patch, oldPoints[idx], p1);
388 getRectSubPix(newImage, patch, newPoints[idx], p2);
391 const float s1 = sum(p1)(0);
392 const float s2 = sum(p2)(0);
393 const float n1 = norm(p1);
394 const float n2 = norm(p2);
395 const float prod = p1.dot(p2);
396 const float sq1 = sqrt(n1 * n1 - s1 * s1 / N);
397 const float sq2 = sqrt(n2 * n2 - s2 * s2 / N);
398 NCC[idx] = (sq2 == 0 ? sq1 / std::abs(sq1) : (prod - s1 * s2 / N) / sq1 / sq2);
401 float median = getMedian(NCC) - FloatEps;
402 for (size_t idx = 0u; idx < oldPoints.size(); idx++)
403 status[idx] = status[idx] && (NCC[idx] > median);