From: Alex Schmidt Date: Sat, 5 Oct 2013 09:40:39 +0000 (+0200) Subject: ... X-Git-Url: http://git.treefish.org/~alex/stickletrack.git/commitdiff_plain/67cf895ef6d35267c1f0b9681729881397ecee2f ... --- 67cf895ef6d35267c1f0b9681729881397ecee2f 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: