From 67cf895ef6d35267c1f0b9681729881397ecee2f Mon Sep 17 00:00:00 2001 From: Alex Schmidt Date: Sat, 5 Oct 2013 11:40:39 +0200 Subject: [PATCH] ... --- .gitignore | 5 + CMakeLists.txt | 11 + masking.cpp | 110 ++++++++++ masking.h | 8 + stickletrack.cpp | 511 +++++++++++++++++++++++++++++++++++++++++++++++ stickletrack.h | 70 +++++++ tracking.cpp | 357 +++++++++++++++++++++++++++++++++ tracking.h | 26 +++ 8 files changed, 1098 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 masking.cpp create mode 100644 masking.h create mode 100755 stickletrack.cpp create mode 100644 stickletrack.h create mode 100644 tracking.cpp create mode 100644 tracking.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf0d71b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake +stickletrack diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7fd670a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 2.6) +project( stickletrack ) + +SET(CMAKE_BUILD_TYPE Release) + +# Uncomment if you want to do profiling with gprof +# SET(CMAKE_CXX_FLAGS "-pg") + +find_package( OpenCV REQUIRED ) +add_executable( stickletrack stickletrack.cpp masking.cpp tracking.cpp ) +target_link_libraries( stickletrack ${OpenCV_LIBS} ) diff --git a/masking.cpp b/masking.cpp new file mode 100644 index 0000000..d76a8b4 --- /dev/null +++ b/masking.cpp @@ -0,0 +1,110 @@ +#include "masking.h" +#include "stickletrack.h" + +static Mat background, foregroundmask, colormask, mScissors, scissorsmask; +static double Z = 1; + +void mouseScissors(int evt, int x, int y, int flags, void* param){ + if (evt==CV_EVENT_LBUTTONDOWN) { + Prefs.scissorpoints->push_back( Point(x, y) ); + } + + else if (evt==CV_EVENT_RBUTTONDOWN) { + int nearestid = -1; + double nearestdist; + + for ( int ipoint = 0; ipoint < Prefs.scissorpoints->size(); ipoint++ ) { + double dist = pow( x - (*Prefs.scissorpoints)[ipoint].x, 2 ) + pow( y - (*Prefs.scissorpoints)[ipoint].y, 2 ); + + if ( dist < nearestdist || nearestid == -1 ) { + nearestdist = dist; + nearestid = ipoint; + } + } + if ( nearestid != -1 ) + Prefs.scissorpoints->erase( Prefs.scissorpoints->begin() + nearestid ); + } +} + +void masking_init() { + background = Mat::zeros(Props.height, Props.width, CV_32FC3); + + namedWindow("background", CV_WINDOW_KEEPRATIO); + namedWindow("foregroundmask", CV_WINDOW_KEEPRATIO); + namedWindow("colormask", CV_WINDOW_KEEPRATIO); + namedWindow("scissorsmask", CV_WINDOW_KEEPRATIO); + + createTrackbar("Decay", "background", &Prefs.halfdecay, 100, &trackbarCallbackUpdateNormPrefs, 0); + createTrackbar("Threshold", "foregroundmask", &Prefs.forethreshold, 255, &trackbarCallbackUpdateNormPrefs, 0); + + createTrackbar("min H", "colormask", &Prefs.mincolor[0], 255, NULL, 0); + createTrackbar("min S", "colormask", &Prefs.mincolor[1], 255, NULL, 0); + createTrackbar("min V", "colormask", &Prefs.mincolor[2], 255, NULL, 0); + createTrackbar("max H", "colormask", &Prefs.maxcolor[0], 255, NULL, 0); + createTrackbar("max S", "colormask", &Prefs.maxcolor[1], 255, NULL, 0); + createTrackbar("max V", "colormask", &Prefs.maxcolor[2], 255, NULL, 0); + + cvSetMouseCallback("scissorsmask", mouseScissors, 0); +} + +void computeBackground (const Mat& origframe, Mat& background, double& Z, const double& decayfac) { + background = ( exp(-decayfac) * background * Z + origframe ) / ( exp(-decayfac) * Z + 1 ); + Z = exp(-decayfac) * Z + 1; + + imshow("background", background/255.0); +} + +void computeForegroundMask (const Mat& origframe, const Mat& background, Mat& foregroundMask) { + absdiff(origframe, background, foregroundMask); + cvtColor(foregroundMask, foregroundMask, CV_RGB2GRAY); + threshold(foregroundMask, foregroundMask, Prefs.forethreshold, 255, CV_THRESH_BINARY); + foregroundMask.convertTo(foregroundMask, CV_8UC3); + + imshow("foregroundmask", foregroundmask); +} + +void computeColorMask (const Mat& origframe, Mat& colormask) { + origframe.convertTo(colormask, CV_8UC3); + cvtColor(colormask, colormask, CV_RGB2HSV); + inRange(colormask, Scalar(Prefs.mincolor[0],Prefs.mincolor[1],Prefs.mincolor[2]), Scalar(Prefs.maxcolor[0], Prefs.maxcolor[1], Prefs.maxcolor[2]), colormask); + colormask.convertTo(colormask, CV_8UC3); + + imshow("colormask", colormask); +} + +void computeScissors (const Mat& origframe, Mat& mScissors) { + origframe.convertTo(mScissors, CV_8UC3); + for (int ipoint = 0; ipoint < Prefs.scissorpoints->size(); ipoint++) { + circle(mScissors, (*Prefs.scissorpoints)[ipoint], 5, Scalar(0,0,255), -1, 8); + line(mScissors, (*Prefs.scissorpoints)[ipoint], (*Prefs.scissorpoints)[ (ipoint+1)%Prefs.scissorpoints->size() ], Scalar(0,0,255), 1, 8); + } +} + +void computeScissorsMask (Mat& scissorsmask) { + if ( Prefs.scissorpoints->size() >= 3) { + const Point* elementPoints[1] = { &((*Prefs.scissorpoints)[0]) }; + int numberOfPoints = (int)Prefs.scissorpoints->size(); + scissorsmask = Mat::zeros(Props.height, Props.width, CV_8U); + fillPoly (scissorsmask, elementPoints, &numberOfPoints, 1, Scalar (255, 255, 255), 8); + } + else + scissorsmask = Mat::ones(Props.height, Props.width, CV_8U); + + imshow("scissorsmask", mScissors); +} + +void masking_getCombinedMask(const Mat& origframe, Mat& combinedmask) { + double decayfac = (log(2) / normalPrefs.halfdecay) / Props.fps; + + computeBackground(origframe, background, Z, decayfac); + computeForegroundMask(origframe, background, foregroundmask ); + + computeColorMask(origframe, colormask); + + + computeScissors(origframe, mScissors); + computeScissorsMask(scissorsmask); + + bitwise_and(foregroundmask, colormask, combinedmask); + bitwise_and(combinedmask, scissorsmask, combinedmask); +} diff --git a/masking.h b/masking.h new file mode 100644 index 0000000..d0025fe --- /dev/null +++ b/masking.h @@ -0,0 +1,8 @@ +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/imgproc/imgproc.hpp" + +using namespace cv; +using namespace std; + +void masking_getCombinedMask(const Mat& origframe, Mat& combinedmask); +void masking_init(); diff --git a/stickletrack.cpp b/stickletrack.cpp new file mode 100755 index 0000000..7462af7 --- /dev/null +++ b/stickletrack.cpp @@ -0,0 +1,511 @@ +#include "opencv2/highgui/highgui.hpp" +#include "opencv2/imgproc/imgproc.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "masking.h" +#include "stickletrack.h" +#include "tracking.h" + +#define BACKSECONDS 30 + +double frametime = 0; +int framenum = 0; + +prefs Prefs; +props Props; + +normalprefs normalPrefs; + +static VideoCapture capture; + +static bool stopvideo=false; +static bool leftbuttondown=false; +static Point2f startClickPos; +static int ilatetagintime=-1, iearlytagintime=-1; +static int startClickFrame; +static bool stoppedbefore; +static bool dooneframe=false; +static vector tags; +static int tagsselected=0; +static int nearestTags[2]; +static vector **tagintime; +static int tagintimeidx=0; +static int moresleep=0; +static int gotoframe=0; +static int maxoutputfps; +static double rescalingfactor; +static ofstream tanjaLogFile; + +void genBaseDir() { +#if defined(_WIN32) + Props.basedir = getenv("HOMEDRIVE"); + Props.basedir += getenv("HOMEPATH"); +#else + Props.basedir = getenv("HOME"); +#endif + + Props.basedir += "/.stickletrack"; + + cout << "Using " << Props.basedir << " for data output." << endl; +} + +void beforeExit() { + cout << "Exitting ..." << endl; + +#if defined(_WIN32) + _mkdir(Props.basedir.c_str()); + _mkdir((Props.basedir+"/prefs").c_str()); +#else + mkdir( Props.basedir.c_str(), 0777 ); + mkdir( (Props.basedir+"/prefs").c_str(), 0777 ); +#endif + + cout << "Saving preferences ..." << endl; + int scissorpointsize = Prefs.scissorpoints->size(); + ofstream prefFile( (Props.basedir+"/prefs/"+Props.videohash+".prf").c_str(), ios::binary ); + prefFile.write((char*)&Prefs, sizeof(Prefs)); + prefFile.write((char*)&scissorpointsize, sizeof(int)); + + for (int ipoint = 0; ipoint < scissorpointsize; ipoint++) + prefFile.write( (char*)&(*Prefs.scissorpoints)[ipoint], sizeof(Point) ); + + prefFile.close(); +} + +void computeNormalizedPrefs() { + normalPrefs.contours_minshortaxis = Prefs.contours_minshortaxis * Props.diagonal / 1000.0; + normalPrefs.contours_maxshortaxis = Prefs.contours_maxshortaxis * Props.diagonal / 1000.0; + normalPrefs.contours_minlongaxis = Prefs.contours_minlongaxis * Props.diagonal / 500.0; + normalPrefs.contours_maxlongaxis = Prefs.contours_maxlongaxis * Props.diagonal / 500.0; + + normalPrefs.contours_minarea = pow(Prefs.contours_minarea,2) * Props.width * Props.height / 1000000.0; + normalPrefs.contours_maxarea = pow(Prefs.contours_maxarea,2) * Props.width * Props.height / 1000000.0; + + normalPrefs.contours_mincircum = Prefs.contours_mincircum * Props.diagonal / 500.0; + normalPrefs.contours_maxcircum = Prefs.contours_maxcircum * Props.diagonal / 500.0; + + normalPrefs.contours_maxspeed = Prefs.contours_maxspeed * Props.diagonal / 100.0; + + normalPrefs.contours_maxrot = 10 * Prefs.contours_maxrot; + + normalPrefs.halfdecay = 10.0 * Prefs.halfdecay / Props.fps; +} + +void trackbarCallbackUpdateNormPrefs (int trackpos, void *userdata) { + computeNormalizedPrefs(); +} + +void drawTimes(Mat& mContours) { + stringstream alles; + + alles.precision(2); + + alles << framenum << " : " << fixed << (framenum)/(double)Props.fps << "s : +" << (double)moresleep/Props.fps << "ms"; + + string text = alles.str(); + int fontFace = FONT_HERSHEY_SCRIPT_SIMPLEX; + + double fontScale = Props.diagonal/1000.0; + int thickness = 3; + + int baseline=0; + Size textSize = getTextSize(text, fontFace, + fontScale, thickness, &baseline); + + Point textOrg(10, 10 + textSize.height); + + rectangle(mContours, Point(0, 0), + Point(Props.width, 20+textSize.height), + Scalar(0,0,255), -1); + + putText(mContours, text, textOrg, fontFace, fontScale, + Scalar::all(255), thickness, 8); +} + +void readCreatePrefs() { + ifstream prefFile( (Props.basedir+"/prefs/"+Props.videohash+".prf").c_str(), ios::binary ); + if ( prefFile.is_open() ) { + int scissorpointsize; + + prefFile.read((char*)&Prefs, sizeof(Prefs)); + prefFile.read((char*)&scissorpointsize, sizeof(int)); + + Prefs.scissorpoints = new vector; + for (int ipoint = 0; ipoint < scissorpointsize; ipoint++) { + Point tmpPoint; + prefFile.read((char*)&tmpPoint, sizeof(Point)); + Prefs.scissorpoints->push_back(tmpPoint); + } + + prefFile.close(); + + cout << "Loaded saved preferences." << endl; + } + else { + Prefs.scissorpoints = new vector; + } +} + +void loadDefaultPrefs () { + Prefs.halfdecay = 100; + Prefs.forethreshold = 0; + Prefs.mincolor[0] = 0; Prefs.mincolor[1] = 0; Prefs.mincolor[2] = 0; + Prefs.maxcolor[0] = 255; Prefs.maxcolor[1] = 255; Prefs.maxcolor[2] = 255; + Prefs.manyfish = 3; + Prefs.contours_minshortaxis = 0; + Prefs.contours_maxshortaxis = 100; + Prefs.contours_minlongaxis = 0; + Prefs.contours_maxlongaxis = 100; + Prefs.contours_minarea = 0; + Prefs.contours_maxarea = 100; + Prefs.contours_mincircum = 0; + Prefs.contours_maxcircum = 100; + Prefs.contours_maxspeed = 100; + Prefs.contours_maxrot = 100; +} + +unsigned long frameHash(const Mat& frame) { + unsigned long hash = 5381; + int c; + unsigned char *str = (unsigned char*)frame.data; + + while (c = *str++) { + hash = ((hash << 5) + hash) + c; + } + + return hash; +} + +int timeWarp(int x, int y) { + int horiMove = startClickPos.x - x; + int wannago = startClickFrame + 10*horiMove*Props.fps/Props.width; + + int maxgo = min(ilatetagintime, Props.totframes); + int mingo = max(1, ilatetagintime - BACKSECONDS*Props.fps + 2); + + if (wannago > maxgo) + return maxgo; + else if (wannago < mingo) + return mingo; + else + return wannago; +} + +int rotatingTime( int whereyouwant ) { + if ( whereyouwant < 0 ) + return BACKSECONDS*Props.fps + whereyouwant; + else + return whereyouwant; +} + +void mouseTracking(int evt, int x, int y, int flags, void* param){ + if ( stopvideo && evt == CV_EVENT_LBUTTONDBLCLK ) { + tagsselected++; + + double mindist = -1; + for (int itag = 0; itag < tags.size(); itag++) { + double dist = pow(tags[itag].x - x, 2) + pow(tags[itag].y - y, 2); + if ( dist < mindist || mindist == -1 ) { + nearestTags[tagsselected-1] = itag; + mindist = dist; + } + } + + if (tagsselected == 2) { + if ( nearestTags[0] < tags.size() && nearestTags[0] >= 0 + && nearestTags[1] < tags.size() && nearestTags[1] >= 0 + && nearestTags[0] != nearestTags[1] ) { + + tag tmp; + + tmp = tags[nearestTags[0]]; + tags[nearestTags[0]] = tags[nearestTags[1]]; + tags[nearestTags[1]] = tmp; + tags[nearestTags[1]].color = tags[nearestTags[0]].color; + tags[nearestTags[0]].color = tmp.color; + + for (int itagintime = framenum-1; itagintime <= ilatetagintime; itagintime++) { + tmp = (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]]; + + (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]] = + (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]]; + + (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]] = tmp; + + (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]].color = + (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]].color; + + (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]].color = tmp.color; + } + } + tagsselected = 0; + + gotoframe = framenum; + dooneframe = true; + } + } + + if ( evt == CV_EVENT_RBUTTONUP ) { + stopvideo = (stopvideo+1)%2; + } + + if (evt==CV_EVENT_LBUTTONDOWN) { + leftbuttondown = true; + startClickPos = Point2f(x,y); + startClickFrame = framenum; + + if (stopvideo) + stoppedbefore = true; + else + stoppedbefore = false; + + stopvideo = true; + } + + if (evt==CV_EVENT_LBUTTONUP) { + leftbuttondown = false; + + int vertiMove = startClickPos.y - y; + + if ( ! stoppedbefore ) + stopvideo = false; + } + + if ( leftbuttondown && startClickPos.x - x != 0 && ! (startClickPos.x > Props.width*5.0/6.0 && x > Props.width*5.0/6.0) ) { + gotoframe = timeWarp(x, y); + dooneframe = true; + } + + if ( leftbuttondown && startClickPos.y - y != 0) { + int vertiMove = startClickPos.y - y; + + if ( startClickPos.x > Props.width*5.0/6.0 && x > Props.width*5.0/6.0 ) { + moresleep -= vertiMove*10000/Props.height; + if ( moresleep < 0 ) + moresleep = 0; + + drawTimes(tracking_getFrame()); + } + } +} + +void writeTanjaLog (int writeframenum, vector *tagstowrite) { + stringstream outline; + + if ( writeframenum%(Props.fps/maxoutputfps) == 0 ) { + if ( tagstowrite->size() >= Prefs.manyfish ) { + outline << (double)writeframenum/Props.fps; + for ( int itag = 0; itag < Prefs.manyfish; itag++) { + outline << "," << (*tagstowrite)[itag].x << "," << (*tagstowrite)[itag].y << "," << (double)writeframenum/Props.fps-(*tagstowrite)[itag].lastseen; + } + outline << endl; + tanjaLogFile.write( outline.str().c_str(), outline.str().size() ); + tanjaLogFile.flush(); + } + } +} + +void openTanjaLog () { + stringstream tanjalogfilename; + +#if defined(_WIN32) + _mkdir(Props.basedir.c_str()); + _mkdir((Props.basedir+"/logs").c_str()); +#else + mkdir( Props.basedir.c_str(), 0777 ); + mkdir( (Props.basedir+"/logs").c_str(), 0777 ); +#endif + +#if defined(_WIN32) + _mkdir(Props.basedir.c_str()); + _mkdir((Props.basedir+"/logs/"+Props.videohash).c_str()); +#else + mkdir( Props.basedir.c_str(), 0777 ); + mkdir( (Props.basedir+"/logs/"+Props.videohash).c_str(), 0777 ); +#endif + + tanjalogfilename << Props.basedir << "/logs/" << Props.videohash << "/" << time(0) << ".dat"; + tanjaLogFile.open( tanjalogfilename.str().c_str() ); + + if ( ! tanjaLogFile.is_open() ) { + cerr << "Could not open tanjalogfile " << tanjalogfilename.str() << "!" << endl; + exit(1); + } + + tanjaLogFile.write("# STICKLETRACK - TANJALOG\n", 26); + tanjaLogFile.write("# format: