Repository: vojirt/asms Branch: master Commit: 779a828a8b3f Files: 14 Total size: 42.8 KB Directory structure: gitextract__7wd_hn1/ ├── .gitignore ├── CMakeLists.txt ├── README.md ├── main_demo.cpp ├── main_trax.cpp ├── main_vot.cpp ├── src/ │ ├── CMakeLists.txt │ ├── colotracker.cpp │ ├── colotracker.h │ ├── histogram.cpp │ ├── histogram.h │ ├── region.cpp │ └── region.h └── vot.hpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ build/ ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8) project(tracker) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -O3") if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wextra -pedantic -Wno-long-long") endif() FIND_PACKAGE( OpenCV REQUIRED ) link_directories ( ${OpenCV_LIB_DIR} ) MESSAGE(STATUS "OpenCV_LIB_DIR: ${OpenCV_LIB_DIR}") include_directories ( ${OpenCV_INCLUDE_DIRS} ) MESSAGE(STATUS "OpenCV_INCLUDE_DIRS: ${OpenCV_INCLUDE_DIRS}") include_directories(${CMAKE_SOURCE_DIR}/src/) add_subdirectory(src) add_executable(asms_vot main_vot.cpp) target_link_libraries(asms_vot ${OpenCV_LIBS} color) add_executable(asms_demo main_demo.cpp ) target_link_libraries(asms_demo ${OpenCV_LIBS} color ) # Try to find TraX header and library ... FIND_PACKAGE(trax REQUIRED COMPONENTS core opencv) IF (trax_FOUND) MESSAGE(STATUS "Trax protocol found: building asms_trax") INCLUDE_DIRECTORIES(${TRAX_INCLUDE_DIRS}) LINK_DIRECTORIES(${TRAX_LIBRARY_DIRS}) add_executable(asms_trax main_trax.cpp) target_link_libraries(asms_trax ${OpenCV_LIBS} color ${TRAX_LIBRARIES}) ENDIF() ================================================ FILE: README.md ================================================ ## Robust scale-adaptive mean-shift for tracking Authors : Tomas Vojir, Jana Noskova, Jiri Matas ________________ This C++ code implements a tracking pipeline of Scale Adaptive Mean-Shift method. It is free for research use. If you find it useful or use it in your research, please cite the [1] paper. The code depends on OpenCV 2.4+ library and is build via cmake toolchain. _________________ Quick start guide for linux: open terminal in the directory with the code $ mkdir build; cd build; cmake .. ; make This code compiles into two binaries **demo** and **asms_vot** ./asms_demo - run live demo with visual output - control : object is selected by mouse, click and drag mouse to select rectandle - ESC = quit - i = init new object ./asms_vot - using VOT 2015 methodology (http://www.votchallenge.net/), is backward compatible with older ones - INPUT : expecting two files, images.txt (list of sequence images with absolute path) and region.txt with initial bounding box in the first frame in format "top_left_x, top_left_y, width, height" - OUTPUT : output.txt containing the bounding boxes in the same format as region.txt ./asms_trax - using VOT2014+ trax protocol - require [trax](https://github.com/votchallenge/trax) library to be compiled with opencv support and installed. See trax instruction for compiling and installing. __________ References [1] Tomas Vojir, Jana Noskova and Jiri Matas, “Robust scale-adaptive mean-shift for tracking“. Pattern Recognition Letters 2014. _____________________________________ Copyright (c) 2014, Tomáš Vojíř Permission to use, copy, modify, and distribute this software for research purposes is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: main_demo.cpp ================================================ #include #include #include #include "highgui.h" #include "colotracker.h" #include "region.h" #include using namespace cv; cv::Point g_topLeft(0,0); cv::Point g_botRight(0,0); cv::Point g_botRight_tmp(0,0); bool plot = false; bool g_trackerInitialized = false; ColorTracker * g_tracker = NULL; static void onMouse( int event, int x, int y, int, void* param) { cv::Mat img = ((cv::Mat *)param)->clone(); if( event == cv::EVENT_LBUTTONDOWN && !g_trackerInitialized){ std::cout << "DOWN " << std::endl; g_topLeft = Point(x,y); plot = true; }else if (event == cv::EVENT_LBUTTONUP && !g_trackerInitialized){ std::cout << "UP " << std::endl; g_botRight = Point(x,y); plot = false; if (g_tracker != NULL) delete g_tracker; g_tracker = new ColorTracker(); g_tracker->init(*(cv::Mat *)param, g_topLeft.x, g_topLeft.y, g_botRight.x, g_botRight.y); g_trackerInitialized = true; }else if (event == cv::EVENT_MOUSEMOVE && !g_trackerInitialized){ //plot bbox g_botRight_tmp = Point(x,y); // if (plot){ // cv::rectangle(img, g_topLeft, current, cv::Scalar(0,255,0), 2); // imshow("output", img); // } } } int main(int argc, char **argv) { BBox * bb = NULL; cv::Mat img; int captureDevice = 0; if (argc > 1) captureDevice = atoi(argv[1]); cv::VideoCapture webcam = cv::VideoCapture(captureDevice); webcam.set(CV_CAP_PROP_FRAME_WIDTH, 640); webcam.set(CV_CAP_PROP_FRAME_HEIGHT, 480); if (!webcam.isOpened()){ webcam.release(); std::cerr << "Error during opening capture device!" << std::endl; return 1; } cv::namedWindow( "output", 0 ); cv::setMouseCallback( "output", onMouse, &img); for(;;){ webcam >> img; int c = waitKey(10); if( (c & 255) == 27 ) { std::cout << "Exiting ..." << std::endl; break; } //some control switch( (char)c ){ case 'i': g_trackerInitialized = false; g_topLeft = cv::Point(0,0); g_botRight_tmp = cv::Point(0,0); break; default:; } if (g_trackerInitialized && g_tracker != NULL){ bb = g_tracker->track(img); } if (!g_trackerInitialized && plot && g_botRight_tmp.x > 0){ cv::rectangle(img, g_topLeft, g_botRight_tmp, cv::Scalar(0,255,0), 2); } if (bb != NULL){ cv::rectangle(img, Point2i(bb->x, bb->y), Point2i(bb->x + bb->width, bb->y + bb->height), Scalar(255, 0, 0), 3); //cv::rectangle(img, Point2i(bb->x-5, bb->y-5), Point2i(bb->x + bb->width+5, bb->y + bb->height+5), Scalar(0, 0, 255), 1); delete bb; bb = NULL; } cv::imshow("output", img); } if (g_tracker != NULL) delete g_tracker; return 0; } ================================================ FILE: main_trax.cpp ================================================ #include #include #include "colotracker.h" #include "region.h" #include #include #include #include using namespace cv; int main(int, char **) { trax::ImageList img; trax::Region reg; std::unique_ptr tracker; cv::Mat image; cv::Rect rectangle; trax::Server handle(trax::Metadata(TRAX_REGION_RECTANGLE, TRAX_IMAGE_PATH | TRAX_IMAGE_MEMORY | TRAX_IMAGE_BUFFER, TRAX_CHANNEL_COLOR), trax_no_log); while(true) { trax::Properties prop; trax::Region status; int tr = handle.wait(img, reg, prop); if (tr == TRAX_INITIALIZE) { rectangle = trax::region_to_rect(reg); image = trax::image_to_mat(img.get(TRAX_CHANNEL_COLOR)); tracker.reset(new ColorTracker()); tracker->init(image, rectangle.x, rectangle.y, rectangle.x + rectangle.width, rectangle.y + rectangle.height); status = trax::rect_to_region(rectangle); } else if (tr == TRAX_FRAME) { image = trax::image_to_mat(img.get(TRAX_CHANNEL_COLOR)); BBox* bb = tracker->track(image); if (bb != NULL) { rectangle = cv::Rect(bb->x, bb->y, bb->width, bb->height); status = trax::rect_to_region(rectangle); delete bb; } else { status = trax::Region::create_special(0); } } else { break; } handle.reply(status, trax::Properties()); } return EXIT_SUCCESS; } ================================================ FILE: main_vot.cpp ================================================ #include #include #include "colotracker.h" #include "region.h" #include #include #include "vot.hpp" using namespace cv; int main(int, char **) { ColorTracker tracker; cv::Mat image; //load region, images and prepare for output VOT vot_io("region.txt", "images.txt", "output.txt"); //img = firts frame, initPos = initial position in the first frame cv::Rect init_rect = vot_io.getInitRectangle(); vot_io.outputBoundingBox(init_rect); vot_io.getNextImage(image); //tracker initialization tracker.init(image, init_rect.x, init_rect.y, init_rect.x + init_rect.width, init_rect.y + init_rect.height); //track double average_speed_ms = 0.0; double num_frames = 0.0; while (vot_io.getNextImage(image) == 1){ double time_profile_counter = cv::getCPUTickCount(); BBox * bb = tracker.track(image); time_profile_counter = cv::getCPUTickCount() - time_profile_counter; average_speed_ms += time_profile_counter/((double)cvGetTickFrequency()*1000); num_frames += 1.0; if (bb != NULL){ vot_io.outputBoundingBox(cv::Rect(bb->x, bb->y, bb->width, bb->height)); delete bb; } else { vot_io.outputBoundingBox(cv::Rect(std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN(), std::numeric_limits::quiet_NaN())); } } std::cout << "Average speed " << average_speed_ms/num_frames << "ms. (" << 1000.0/(average_speed_ms/num_frames) << "fps)" << std::endl; return 0; } ================================================ FILE: src/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 2.8) set(COLOR_LIB_SRC colotracker.cpp histogram.cpp region.cpp) add_library(color STATIC ${COLOR_LIB_SRC}) target_link_libraries(color ${OpenCV_LIBS}) set_target_properties(color PROPERTIES VERSION 1.0.0 SOVERSION 1) ================================================ FILE: src/colotracker.cpp ================================================ #include "colotracker.h" #include #include void ColorTracker::init(cv::Mat & img, int x1, int y1, int x2, int y2) { im1 = cv::Mat( img.rows, img.cols, CV_8UC1 ); im2 = cv::Mat( img.rows, img.cols, CV_8UC1 ); im3 = cv::Mat( img.rows, img.cols, CV_8UC1 ); //boundary checks y1 = std::max(0, y1); y2 = std::min(img.rows-1, y2); x1 = std::max(0, x1); x2 = std::min(img.cols-1, x2); preprocessImage(img); extractForegroundHistogram(x1, y1, x2, y2, q_hist); q_orig_hist = q_hist; extractBackgroundHistogram(x1, y1, x2, y2, b_hist); Histogram b_weights = b_hist; b_weights.transformToWeights(); q_hist.multiplyByWeights(&b_weights); lastPosition.setBBox(x1, y1, x2-x1, y2-y1, 1, 1); defaultWidth = x2-x1; defaultHeight = y2-y1; sumIter = 0; frame = 0; double w2 = (x2-x1)/2.; double h2 = (y2-y1)/2.; double cx = x1 + w2; double cy = y1 + h2; double wh = w2+5.; double hh = h2+5.; double Sbg = 0, Sfg = 0; for(int i = y1; i < y2+1; i++) { const uchar *Mi1 = im1.ptr(i); const uchar *Mi2 = im2.ptr(i); const uchar *Mi3 = im3.ptr(i); double tmp_y = std::pow((cy - i) / hh, 2); for (int j = x1; j < x2+1; j++) { double arg = std::pow((cx - j) / wh, 2) + tmp_y; if (arg > 1) continue; //likelihood weights double wqi = 1.0; double wbi = sqrt(b_hist.getValue(Mi1[j], Mi2[j], Mi3[j]) / q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j])); Sbg += (wqi < wbi) ? q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j]) : 0.0; Sfg += q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j]); } } //wAvgBg = 0.5 wAvgBg = std::max(0.1, std::min(Sbg/Sfg, 0.5)); bound1 = 0.05; bound2 = 0.1; } cv::Point ColorTracker::histMeanShift(double x1, double y1, double x2, double y2) { int maxIter = 15; double w2 = (x2-x1+1)/2; double h2 = (y2-y1+1)/2; double borderX = 5; double borderY = 5; double cx = x1 + w2; double cy = y1 + h2; Histogram y1hist; int ii; for (ii = 0; ii < maxIter; ++ii){ double wh = (w2+borderX); double hh = (h2+borderY); int rowMin = std::max(0, (int)(cy-hh)); int rowMax = std::min(im1.rows, (int)(cy+hh)); int colMin = std::max(0, (int)(cx-wh)); int colMax = std::min(im1.cols, (int)(cx+wh)); extractForegroundHistogram(colMin, rowMin, colMax, rowMax, y1hist); double batta_q = y1hist.computeSimilarity(&q_orig_hist); double batta_b = y1hist.computeSimilarity(&b_hist); //MeanShift Vector double m0 = 0, m1x = 0, m1y = 0; for(int i = rowMin; i < rowMax; i++){ const uchar* Mi1 = im1.ptr(i); const uchar* Mi2 = im2.ptr(i); const uchar* Mi3 = im3.ptr(i); double tmp_y = std::pow((cy-(float)i)/hh,2); for(int j = colMin; j < colMax; j++){ double arg = std::pow((cx-(float)j)/wh,2) + tmp_y; if (arg>1) continue; double wqi = sqrt(q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double wbi = sqrt(b_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double wg = std::max(wqi/batta_q - wbi/batta_b, 0.0)*(-kernelProfile_EpanechnikovDeriv(arg)); m0 += wg; m1x += (j-cx)*wg; m1y += (i-cy)*wg; } } double xn_1 = m1x/m0 + cx; double yn_1 = m1y/m0 + cy; if (std::pow(xn_1 - cx,2) + std::pow(yn_1 - cy,2) < 0.1) break; if (m0==m0 && !std::isinf(m0) && m0 > 0){ cx = xn_1; cy = yn_1; } } return cv::Point(cx, cy); } cv::Point ColorTracker::histMeanShiftIsotropicScale(double x1, double y1, double x2, double y2, double * scale, int * iter) { int maxIter = 15; double w2 = (x2-x1)/2; double h2 = (y2-y1)/2; double borderX = 5; double borderY = 5; double cx = x1 + w2; double cy = y1 + h2; Histogram y1hist; double h0 = 1; int ii; for (ii = 0; ii < maxIter; ++ii){ double wh = h0*w2+borderX; double hh = h0*h2+borderY; int rowMin = std::max(0, (int)(cy-hh)); int rowMax = std::min(im1.rows-1, (int)(cy+hh)); int colMin = std::max(0, (int)(cx-wh)); int colMax = std::min(im1.cols-1, (int)(cx+wh)); extractForegroundHistogram(colMin, rowMin, colMax, rowMax, y1hist); double batta_q = y1hist.computeSimilarity(&q_orig_hist); double batta_b = y1hist.computeSimilarity(&b_hist); //MeanShift Vector double m0 = 0, m1x = 0, m1y = 0; double wg_dist_sum = 0, wk_sum = 0, Sbg = 0, Sfg = 0; for(int i = rowMin; i < rowMax; i++){ const uchar* Mi1 = im1.ptr(i); const uchar* Mi2 = im2.ptr(i); const uchar* Mi3 = im3.ptr(i); double tmp_y = std::pow((cy-i)/hh,2); for(int j = colMin; j < colMax; j++){ double arg = std::pow((cx-j)/wh,2) + tmp_y; if (arg>1) continue; //likelihood weights double wqi = sqrt(q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double wbi = sqrt(b_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double w = std::max(wqi/batta_q - wbi/batta_b, 0.0); //weights based on "Robust mean-shift tracking with corrected background-weighted histogram" // double w = sqrt(q_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); //orig weights // double w = sqrt(q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double wg = w*(-kernelProfile_EpanechnikovDeriv(arg)); double dist = std::sqrt(std::pow((j-cx)/w2,2) + std::pow((i-cy)/h2,2)); wg_dist_sum += wg*dist; wk_sum += w*(kernelProfile_Epanechnikov(arg)); //orig weights // Sbg += (q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j]) == 0) ? y1hist.getValue(Mi1[j], Mi2[j], Mi3[j]) : 0; // Sfg += q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j]); //likelihood Sbg += (wqi < wbi) ? y1hist.getValue(Mi1[j], Mi2[j], Mi3[j]) : 0; Sfg += q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j]); //SCIA // Sbg += (w == 0) ? y1hist.getValue(Mi1[j], Mi2[j], Mi3[j]) : 0; // Sfg += q_hist.getValue(Mi1[j], Mi2[j], Mi3[j]); m0 += wg; m1x += (j-cx)*wg; m1y += (i-cy)*wg; } } double xn_1 = m1x/m0 + cx; double yn_1 = m1y/m0 + cy; //Rebularization double reg2 = 0, reg1 = 0; reg1 = (wAvgBg - Sbg/Sfg); if (std::abs(reg1) > bound1) reg1 = reg1 > 0 ? bound1 : -bound1; reg2 = -(log(h0)); if (std::abs(reg2) > bound2) reg2 = reg2 > 0 ? bound2 : -bound2; double h_tmp = (1.0 - wk_sum/m0)*h0 + (1.0/h0)*(wg_dist_sum/m0) + reg1 + reg2; if (std::pow(xn_1 - cx,2) + std::pow(yn_1 - cy,2) < 0.1) break; if (m0==m0 && !std::isinf(m0) && m0 > 0){ cx = xn_1; cy = yn_1; h0 = 0.7*h0 + 0.3*h_tmp; if (borderX > 5){ borderX /= 3; borderY /= 3; } }else if (ii == 0){ //if in first iteration is m0 not valid => fail (maybe too fast movement) // try to enlarge the search region borderX = 3*borderX; borderY = 3*borderY; } } *scale = h0; if (iter != NULL) *iter = ii; return cv::Point(cx, cy); } cv::Point ColorTracker::histMeanShiftAnisotropicScale(double x1, double y1, double x2, double y2, double * width, double * height) { int maxIter = 15; double w2 = (x2-x1)/2.; double h2 = (y2-y1)/2.; double borderX = 5.; double borderY = 5.; double cx = x1 + w2; double cy = y1 + h2; double h0_1 = 1.; double h0_2 = 1.; double wh; double hh; Histogram y1hist; int ii; for (ii = 0; ii < maxIter; ++ii){ wh = w2*h0_1+borderX; hh = h2*h0_2+borderY; int rowMin = std::max(0, (int)(cy-hh)); int rowMax = std::min(im1.rows-1, (int)(cy+hh)); int colMin = std::max(0, (int)(cx-wh)); int colMax = std::min(im1.cols-1, (int)(cx+wh)); extractForegroundHistogram(colMin, rowMin, colMax, rowMax, y1hist); double batta_q = y1hist.computeSimilarity(&q_orig_hist); double batta_b = y1hist.computeSimilarity(&b_hist); //MeanShift Vector double m0 = 0, m1x = 0, m1y = 0; double Swigdist_1 = 0, Swigdist_2 = 0; double wk_sum = 0, Sbg = 0, Sfg = 0; for(int i = rowMin; i < rowMax; i++){ const uchar* Mi1 = im1.ptr(i); const uchar* Mi2 = im2.ptr(i); const uchar* Mi3 = im3.ptr(i); double tmp_y = std::pow((cy-(float)i)/hh,2); for(int j = colMin; j < colMax; j++){ double arg = std::pow((cx-(float)j)/wh,2) + tmp_y; if (arg>1) continue; //likelihood weights double wqi = sqrt(q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double wbi = sqrt(b_hist.getValue(Mi1[j], Mi2[j], Mi3[j])/y1hist.getValue(Mi1[j], Mi2[j], Mi3[j])); double w = std::max(wqi/batta_q - wbi/batta_b, 0.0); double wg = w*(-kernelProfile_EpanechnikovDeriv(arg)); wk_sum += (w * kernelProfile_Epanechnikov(arg)); Swigdist_1 += wg*std::pow((cx-(float)j),2); Swigdist_2 += wg*std::pow((cy-(float)i),2); //likelihood Sbg += (wqi < wbi) ? y1hist.getValue(Mi1[j], Mi2[j], Mi3[j]) : 0; Sfg += q_orig_hist.getValue(Mi1[j], Mi2[j], Mi3[j]); m0 += wg; m1x += (j-cx)*wg; m1y += (i-cy)*wg; } } double a2 = std::pow(w2,2); double b2 = std::pow(h2,2); float mx = (h0_2/h0_1)*(m1x/m0); float my = (h0_1/h0_2)*(m1y/m0); double reg1 = (wAvgBg - Sbg/Sfg); if (std::abs(reg1) > bound1) reg1 = reg1 > 0 ? bound1 : -bound1; double reg2_1 = -(log(h0_1)); if (std::abs(reg2_1) > bound2) reg2_1 = reg2_1 > 0 ? bound2 : -bound2; double reg2_2 = -(log(h0_2)); if (std::abs(reg2_2) > bound2) reg2_2 = reg2_2 > 0 ? bound2 : -bound2; double h_1 = h0_1 - (h0_2/2)*(wk_sum / m0) + (h0_2 / (h0_1 * h0_1 * a2)) * (Swigdist_1 / m0) + reg1 + reg2_1; double h_2 = h0_2 - (h0_1/2)*(wk_sum / m0) + (h0_1 / (h0_2 * h0_2 * b2)) * (Swigdist_2 / m0) + reg1 + reg2_2; if (std::pow(mx,2) + std::pow(my,2) < 0.1) break; if (m0==m0 && !std::isinf(m0) && m0 > 0){ cx = cx + mx; cy = cy + my; h0_1 = 0.7*h0_1 + 0.3*h_1; h0_2 = 0.7*h0_2 + 0.3*h_2; if (borderX > 5){ borderX /= 3; borderY /= 3; } }else if (ii == 0){ //if in first iteration is m0 not valid => fail (maybe too fast movement) // try to enlarge the search region borderX = 3*borderX; borderY = 3*borderY; } } *width = 2*w2*h0_1; *height = 2*h2*h0_2; return cv::Point(cx, cy); } BBox * ColorTracker::track(cv::Mat & img, double x1, double y1, double x2, double y2) { double width = x2-x1; double height = y2-y1; im1_old = im1.clone(); im2_old = im2.clone(); im3_old = im3.clone(); preprocessImage(img); //MS with scale estimation double scale = 1; int iter = 0; cv::Point modeCenter = histMeanShiftIsotropicScale(x1, y1, x2, y2, &scale, &iter); width = 0.7*width + 0.3*width*scale; height = 0.7*height + 0.3*height*scale; //MS with anisotropic scale estimation // double scale = 1.; // double new_width = 1., new_height = 1.; // cv::Point modeCenter = histMeanShiftAnisotropicScale(x1, y1, x2, y2, &new_width, &new_height); // width = new_width; // height = new_height; //Forward-Backward validation if (std::abs(std::log(scale)) > 0.05){ cv::Mat tmp_im1 = im1; cv::Mat tmp_im2 = im2; cv::Mat tmp_im3 = im3; im1 = im1_old; im2 = im2_old; im3 = im3_old; double scaleB = 1; histMeanShiftIsotropicScale(modeCenter.x - width/2, modeCenter.y - height/2, modeCenter.x + width/2, modeCenter.y + height/2, &scaleB); im1 = tmp_im1; im2 = tmp_im2; im3 = tmp_im3; if (std::abs(std::log(scale*scaleB)) > 0.1){ double alfa = 0.1*(defaultWidth/(float)(x2-x1)); width = (0.9 - alfa)*(x2-x1) + 0.1*(x2-x1)*scale + alfa*defaultWidth; height = (0.9 - alfa)*(y2-y1) + 0.1*(y2-y1)*scale + alfa*defaultHeight; } } BBox * retBB = new BBox(); retBB->setBBox(modeCenter.x - width/2, modeCenter.y - height/2, width, height, 1, 1); lastPosition.setBBox(modeCenter.x - width/2, modeCenter.y - height/2, width, height, 1, 1); frame++; return retBB; } void ColorTracker::preprocessImage(cv::Mat &img) { cv::Mat ra[3] = {im1, im2, im3}; cv::split(img, ra); } void ColorTracker::extractBackgroundHistogram(int x1, int y1, int x2, int y2, Histogram & hist) { int offsetX = (x2-x1)/2; int offsetY = (y2-y1)/2; int rowMin = std::max(0, (int)(y1-offsetY)); int rowMax = std::min(im1.rows, (int)(y2+offsetY+1)); int colMin = std::max(0, (int)(x1-offsetX)); int colMax = std::min(im1.cols, (int)(x2+offsetX+1)); int numData = (rowMax-rowMin)*(colMax-colMin) - (y2-y1)*(x2-x1); if (numData < 1) numData = (rowMax-rowMin)*(colMax-colMin)/2 + 1; std::vector d1, d2, d3; std::vector weights; d1.reserve(numData); d2.reserve(numData); d3.reserve(numData); for (int y = rowMin; y < rowMax; ++y){ const uchar * M1 = im1.ptr(y); const uchar * M2 = im2.ptr(y); const uchar * M3 = im3.ptr(y); for (int x = colMin; x < colMax; ++x){ if (x >= x1 && x <= x2 && y >= y1 && y <= y2) continue; d1.push_back(M1[x]); d2.push_back(M2[x]); d3.push_back(M3[x]); } } hist.clear(); hist.insertValues(d1, d2, d3, weights); } void ColorTracker::extractForegroundHistogram(int x1, int y1, int x2, int y2, Histogram & hist) { hist.clear(); std::vector data1; std::vector data2; std::vector data3; std::vector weights; int numData = (y2-y1)*(x2-x1); if (numData <= 0){ return; } data1.reserve(numData); data2.reserve(numData); data3.reserve(numData); weights.reserve(numData); double w2 = (x2-x1)/2; double h2 = (y2-y1)/2; double cx = x1 + w2; double cy = y1 + h2; double wh_i = 1.0/(w2*1.4142+1); //sqrt(2) double hh_i = 1.0/(h2*1.4142+1); for (int y = y1; y < y2+1; ++y){ const uchar * M1 = im1.ptr(y); const uchar * M2 = im2.ptr(y); const uchar * M3 = im3.ptr(y); double tmp_y = std::pow((cy-y)*hh_i,2); for (int x = x1; x < x2+1; ++x){ data1.push_back(M1[x]); data2.push_back(M2[x]); data3.push_back(M3[x]); weights.push_back(kernelProfile_Epanechnikov(std::pow((cx-x)*wh_i,2) + tmp_y)); } } hist.clear(); hist.insertValues(data1, data2, data3, weights); } ================================================ FILE: src/colotracker.h ================================================ #ifndef COLOTRACKER_H #define COLOTRACKER_H #include "cv.h" #include "highgui.h" #include "region.h" #include "histogram.h" #include //#define SHOWDEBUGWIN #define BIN_1 16 #define BIN_2 16 #define BIN_3 16 class ColorTracker { private: BBox lastPosition; cv::Mat im1; cv::Mat im2; cv::Mat im3; cv::Mat im1_old; cv::Mat im2_old; cv::Mat im3_old; Histogram q_hist; Histogram q_orig_hist; Histogram b_hist; double defaultWidth; double defaultHeight; double wAvgBg; double bound1; double bound2; cv::Point histMeanShift(double x1, double y1, double x2, double y2); cv::Point histMeanShiftIsotropicScale(double x1, double y1, double x2, double y2, double * scale, int * msIter = NULL); cv::Point histMeanShiftAnisotropicScale(double x1, double y1, double x2, double y2, double * width, double * height); void preprocessImage(cv::Mat & img); void extractBackgroundHistogram(int x1, int y1, int x2, int y2, Histogram &hist); void extractForegroundHistogram(int x1, int y1, int x2, int y2, Histogram &hist); inline double kernelProfile_Epanechnikov(double x) { return (x <= 1) ? (2.0/3.14)*(1-x) : 0; } //{ return (x <= 1) ? (1-x) : 0; } inline double kernelProfile_EpanechnikovDeriv(double x) { return (x <= 1) ? (-2.0/3.14) : 0; } //{ return (x <= 1) ? -1 : 0; } public: int frame; int sumIter; // Init methods void init(cv::Mat & img, int x1, int y1, int x2, int y2); // Set last object position - starting position for next tracking step inline void setLastBBox(int x1, int y1, int x2, int y2) { lastPosition.setBBox(x1, y1, x2-x1, y2-y1, 1, 1); } inline BBox * getBBox() { BBox * bbox = new BBox(); *bbox = lastPosition; return bbox; } // frame-to-frame object tracking BBox * track(cv::Mat & img, double x1, double y1, double x2, double y2); inline BBox * track(cv::Mat & img) { return track(img, lastPosition.x, lastPosition.y, lastPosition.x + lastPosition.width, lastPosition.y + lastPosition.height); } }; #endif // COLOTRACKER_H ================================================ FILE: src/histogram.cpp ================================================ #include "histogram.h" #include #include #include #include #include #include #include Histogram::Histogram(int _dimSize, int _range) { dimSize = _dimSize; range = _range; } void Histogram::insertValues(std::vector & data1, std::vector & data2, std::vector & data3, std::vector &weight) { if (data.size() < (unsigned int)dimSize*dimSize*dimSize) data.resize(dimSize*dimSize*dimSize); bool useWeights = true; if (weight.size() != data1.size()) useWeights = false; rangePerBin = range/dimSize; rangePerBinInv = 1./(float)rangePerBin; double sum = 0; for (unsigned int i=0; i < data1.size(); ++i){ int id1 = rangePerBinInv*data1[i]; int id2 = rangePerBinInv*data2[i]; int id3 = rangePerBinInv*data3[i]; int id = id1*dimSize*dimSize + id2*dimSize + id3; double w = useWeights ? weight[i] : 1; data[id] += w; sum += w; } normalize(); } double Histogram::computeSimilarity(Histogram * hist) { double conf = 0; for (unsigned int i=0; i < data.size(); ++i) { conf += sqrt(data[i]*hist->data[i]); } return conf; } double Histogram::getValue(int val1, int val2, int val3) { int id1 = rangePerBinInv*val1; int id2 = rangePerBinInv*val2; int id3 = rangePerBinInv*val3; int id = id1*dimSize*dimSize + id2*dimSize + id3; return data[id]; } void Histogram::transformToWeights() { double min = 0; /* std::ifstream alfa_file; alfa_file.open("param.txt"); if (alfa_file.is_open()){ double sum = 0; for (unsigned int i=0; i < data.size(); ++i) { sum += data[i]; } double alfa; alfa_file >> alfa; min = (alfa/100.0)*sum; alfa_file.close(); }else*/ min = getMin(); transformByWeight(min); } void Histogram::transformByWeight(double min) { for (unsigned int i=0; i < data.size(); ++i){ if (data[i] > 0){ data[i] = min/data[i]; if (data[i] > 1) data[i] = 1; }else data[i] = 1; } } void Histogram::multiplyByWeights(Histogram * hist) { double sum = 0; for (unsigned int i=0; i < data.size(); ++i) { data[i] *= hist->data[i]; sum += data[i]; } normalize(); } void Histogram::clear() { for (unsigned int i=0; i < data.size(); ++i) data[i] = 0; } void Histogram::normalize() { double sum = 0; for (unsigned int i=0; i < data.size(); ++i) sum += data[i]; for (unsigned int i=0; i < data.size(); ++i) data[i] /= sum; } double Histogram::getMin() { double min = 1; for (unsigned int i=0; i < data.size(); ++i) { if (data[i] < min && data[i] != 0) min = data[i]; } return min; } void Histogram::addExpHist(double alpha, Histogram & hist) { double beta = 1-alpha; for (unsigned int i=0; i < data.size(); ++i){ data[i] = beta*data[i] + alpha*hist.data[i]; } } ================================================ FILE: src/histogram.h ================================================ #ifndef HISTOGRAM_H #define HISTOGRAM_H #include #include class Bin; class Histogram { public: std::vector data; int dimSize; int range; int rangePerBin; float rangePerBinInv; bool initialized; Histogram(int _dimSize = 16, int _range = 256); ~Histogram(){} void insertValues(std::vector & data1, std::vector & data2, std::vector & data3, std::vector & weight); double computeSimilarity(Histogram * hist); double getValue(int val1, int val2, int val3); void addValue(int val1, int val2, int val3, double weight); double getMin(); void transformToWeights(); void transformByWeight(double min); void multiplyByWeights(Histogram * hist); void clear(); void normalize(); void addExpHist(double alpha, Histogram & hist); }; #endif //HISTOGRAM_H ================================================ FILE: src/region.cpp ================================================ /////////////////////////////////////////////////////////// // region.cpp // Implementation of the Class BBox // Created on: 15-II-2010 18:00:19 // Original author: Tomas Vojir /////////////////////////////////////////////////////////// #include "region.h" #include void BBox::setBBox(double _x, double _y, double _width, double _height, double _accuracy, double _normCross){ accuracy = _accuracy; height = _height; width = _width; x = _x; y = _y; normCross = _normCross; } double * BBox::getTopLeftWidthHeight() { double * ret = new double[4]; ret[0] = x; ret[1] = y; ret[2] = width; ret[3] = height; return ret; } std::vector BBox::bbOverlap(std::vector & vect, double overLap) { std::vector ret; std::vector retP; double x1, y1; BBox * tmp; double intersection; double over = overLap; if (over == 0) over = 0.7; for(std::vector::iterator it = vect.begin(); it != vect.end(); ++it){ tmp = *it; x1 = std::min(x + width, tmp->x + tmp->width) - std::max(x, tmp->x) + 1; if (x1 <= 0) { ret.push_back(*it); continue; } y1 = std::min(y + height, tmp->y + tmp->height) - std::max(y, tmp->y) + 1; if (y1 <= 0) { ret.push_back(*it); continue; } intersection = x1 * y1; if ( (intersection / (width * height + tmp->width * tmp->height - intersection)) >= over) retP.push_back(*it); else ret.push_back(*it); } vect = retP; return (ret); } double BBox::bbOverlap(BBox * tmp) { double x1, y1; double intersection; x1 = std::min(x + width, tmp->x + tmp->width) - std::max(x, tmp->x) + 1; if (x1 <= 0) return 0; y1 = std::min(y + height, tmp->y + tmp->height) - std::max(y, tmp->y) + 1; if (y1 <= 0) return 0; intersection = x1 * y1; double area1 = (width+1)*(height+1); double area2 = (tmp->width+1)*(tmp->height+1); return (intersection / (area1 + area2 - intersection)); } double BBox::bbCoverage(BBox * tmp) { double x1, y1; double intersection; x1 = std::min(x + width, tmp->x + tmp->width) - std::max(x, tmp->x) + 1; if (x1 <= 0) return 0; y1 = std::min(y + height, tmp->y + tmp->height) - std::max(y, tmp->y) + 1; if (y1 <= 0) return 0; intersection = x1 * y1; return (intersection); } std::vector BBox::clusterBBoxes(std::vector & BB) { std::vector ret; std::vector tmpV1; std::vector tmpV2; std::vector tmpV3; BBox * tmp; if (BB.size() == 0) return ret; while(1){ tmp = BB[0]; tmpV1 = tmp->bbOverlap(BB); tmpV3 = BB; for (std::vector::iterator it = BB.begin(); it != BB.end(); ++it){ tmpV2 = (*it)->bbOverlap(tmpV1); for (unsigned int i=0; isetBBox(0,0,0,0,0); bbox->normCross = 0; for (std::vector::iterator it = tmpV3.begin(); it != tmpV3.end(); ++it){ bbox->x += (*it)->x; bbox->y += (*it)->y; bbox->width += (*it)->width; bbox->height += (*it)->height; if ((*it)->normCross > bbox->normCross) bbox->normCross = (*it)->normCross; if ((*it)->accuracy > bbox->accuracy) bbox->accuracy = (*it)->accuracy; delete *it; } bbox->x /= tmpV3.size(); bbox->y /= tmpV3.size(); bbox->width /= tmpV3.size(); bbox->height /= tmpV3.size(); ret.push_back(bbox); tmpV3.clear(); tmpV2.clear(); } BB.swap(tmpV1); if (BB.size() == 0) break; } return ret; } std::vector BBox::findDiff(std::vector & A, std::vector & B) { bool equal; std::vector ret; if (B.size()==0){ ret = A; return (ret); } for(std::vector::iterator it = A.begin(); it != A.end(); ++it){ equal = false; for (std::vector::iterator it2 = B.begin(); it2 != B.end(); ++it2) if (*it == *it2) { equal = true; break; } if (!equal) ret.push_back(*it); } return (ret); } ================================================ FILE: src/region.h ================================================ /////////////////////////////////////////////////////////// // region.h // Implementation of the Class BBox // Created on: 15-II-2010 18:00:18 // Original author: Tomas Vojir /////////////////////////////////////////////////////////// #if !defined(BBOX_C3F6FA38_1DD3_4796_8FB6_62A80862095F__INCLUDED_) #define BBOX_C3F6FA38_1DD3_4796_8FB6_62A80862095F__INCLUDED_ #include class BBox { public: BBox() { } ~BBox() { } double accuracy; double normCross; double height; double width; double x; double y; double * getTopLeftWidthHeight(); void setBBox(double _x, double _y, double _width, double _height, double _accuracy, double _normCross = 0); std::vector bbOverlap(std::vector & vect, double overLap = 0.0); double bbOverlap(BBox * b_box); double bbCoverage(BBox * tmp); static std::vector clusterBBoxes(std::vector & BB); static std::vector findDiff(std::vector & A, std::vector & B); bool operator==(const BBox & right) const { if ( (this->x - right.x) != 0 || (this->y - right.y) != 1 || this->width != right.width || this->height != right.height ) return false; return true; } bool operator!=(const BBox & right) const { return !(*this == right); } }; #endif // !defined(BBOX_C3F6FA38_1DD3_4796_8FB6_62A80862095F__INCLUDED_) ================================================ FILE: vot.hpp ================================================ /* * Author : Tomas Vojir * Date : 2013-06-05 * Desc : Simple class for parsing VOT inputs and providing * interface for image loading and storing output. */ #ifndef CPP_VOT_H #define CPP_VOT_H #include #include #include #include // Bounding box type /* format: 2 3 1 4 */ typedef struct { float x1; //bottom left float y1; float x2; //top left float y2; float x3; //top right float y3; float x4; //bottom right float y4; } VOTPolygon; class VOT { public: VOT(const std::string & region_file, const std::string & images, const std::string & ouput) { _images = images; p_region_stream.open(region_file.c_str()); VOTPolygon p; if (p_region_stream.is_open()){ std::string line; std::getline(p_region_stream, line); std::vector numbers; std::istringstream s( line ); float x; char ch; while (s >> x){ numbers.push_back(x); s >> ch; } if (numbers.size() == 4) { float x = numbers[0], y = numbers[1], w = numbers[2], h = numbers[3]; p.x1 = x; p.y1 = y + h; p.x2 = x; p.y2 = y; p.x3 = x + w; p.y3 = y; p.x4 = x + w; p.y4 = y + h; } else if (numbers.size() == 8) { p.x1 = numbers[0]; p.y1 = numbers[1]; p.x2 = numbers[2]; p.y2 = numbers[3]; p.x3 = numbers[4]; p.y3 = numbers[5]; p.x4 = numbers[6]; p.y4 = numbers[7]; } else { std::cerr << "Error loading initial region in file - unknow format " << region_file << "!" << std::endl; p.x1=0; p.y1=0; p.x2=0; p.y2=0; p.x3=0; p.y3=0; p.x4=0; p.y4=0; } }else{ std::cerr << "Error loading initial region in file " << region_file << "!" << std::endl; p.x1=0; p.y1=0; p.x2=0; p.y2=0; p.x3=0; p.y3=0; p.x4=0; p.y4=0; } p_init_polygon = p; p_images_stream.open(images.c_str()); if (!p_images_stream.is_open()) std::cerr << "Error loading image file " << images << "!" << std::endl; p_output_stream.open(ouput.c_str()); if (!p_output_stream.is_open()) std::cerr << "Error opening output file " << ouput << "!" << std::endl; } ~VOT() { p_region_stream.close(); p_images_stream.close(); p_output_stream.close(); } inline cv::Rect getInitRectangle() const { // read init box from ground truth file VOTPolygon initPolygon = getInitPolygon(); float x1 = std::min(initPolygon.x1, std::min(initPolygon.x2, std::min(initPolygon.x3, initPolygon.x4))); float x2 = std::max(initPolygon.x1, std::max(initPolygon.x2, std::max(initPolygon.x3, initPolygon.x4))); float y1 = std::min(initPolygon.y1, std::min(initPolygon.y2, std::min(initPolygon.y3, initPolygon.y4))); float y2 = std::max(initPolygon.y1, std::max(initPolygon.y2, std::max(initPolygon.y3, initPolygon.y4))); return cv::Rect(x1, y1, x2-x1, y2-y1); } inline VOTPolygon getInitPolygon() const { return p_init_polygon; } inline void outputBoundingBox(const cv::Rect & bbox) { p_output_stream << bbox.x << "," << bbox.y << ","; p_output_stream << bbox.width << "," << bbox.height << std::endl; } inline void outputPolygon(const VOTPolygon & poly) { p_output_stream << poly.x1 << "," << poly.y1 << ","; p_output_stream << poly.x2 << "," << poly.y2 << ","; p_output_stream << poly.x3 << "," << poly.y3 << ","; p_output_stream << poly.x4 << "," << poly.y4 << std::endl; } inline int getNextFileName(char * fName) { if (p_images_stream.eof() || !p_images_stream.is_open()) return -1; std::string line; std::getline (p_images_stream, line); strcpy(fName, line.c_str()); return 1; } inline int getNextImage(cv::Mat & img) { if (p_images_stream.eof() || !p_images_stream.is_open()) return -1; std::string line; std::getline (p_images_stream, line); if (line.empty() && p_images_stream.eof()) return -1; img = cv::imread(line, CV_LOAD_IMAGE_COLOR); return 1; } private: std::string _images; VOTPolygon p_init_polygon; std::ifstream p_region_stream; std::ifstream p_images_stream; std::ofstream p_output_stream; }; #endif //CPP_VOT_H