1 #include "opencv2/highgui/highgui.hpp"
2 #include "opencv2/imgproc/imgproc.hpp"
19 #include "stickletrack.h"
28 normalprefs normalPrefs;
30 static VideoCapture capture;
32 static bool stopvideo=false;
33 static bool leftbuttondown=false;
34 static Point2f startClickPos;
35 static int ilatetagintime=-1, iearlytagintime=-1;
36 static int startClickFrame;
37 static bool stoppedbefore;
38 static bool dooneframe=false;
39 static vector<tag> tags;
40 static int tagsselected=0;
41 static int nearestTags[2];
42 static vector<tag> **tagintime;
43 static int tagintimeidx=0;
44 static int moresleep=0;
45 static int gotoframe=0;
46 static ofstream tanjaLogFile;
52 double rescalingFactor;
55 static comargs comArgs;
59 Props.basedir = getenv("HOMEDRIVE");
60 Props.basedir += getenv("HOMEPATH");
62 Props.basedir = getenv("HOME");
65 Props.basedir += "/.stickletrack";
67 cout << "Using " << Props.basedir << " for data output." << endl;
70 bool isWindowClosed (const char* name) {
71 if ( cvGetWindowHandle(name) == NULL )
78 cout << "Exitting ..." << endl;
81 _mkdir(Props.basedir.c_str());
82 _mkdir((Props.basedir+"/prefs").c_str());
84 mkdir( Props.basedir.c_str(), 0777 );
85 mkdir( (Props.basedir+"/prefs").c_str(), 0777 );
88 cout << "Saving preferences ..." << endl;
89 int scissorpointsize = Prefs.scissorpoints->size();
90 ofstream prefFile( (Props.basedir+"/prefs/"+Props.videohash+".prf").c_str(), ios::binary );
91 prefFile.write((char*)&Prefs, sizeof(Prefs));
92 prefFile.write((char*)&scissorpointsize, sizeof(int));
94 for (int ipoint = 0; ipoint < scissorpointsize; ipoint++)
95 prefFile.write( (char*)&(*Prefs.scissorpoints)[ipoint], sizeof(Point) );
100 void computeNormalizedPrefs() {
101 normalPrefs.contours_minshortaxis = Prefs.contours_minshortaxis * Props.diagonal / 1000.0;
102 normalPrefs.contours_maxshortaxis = Prefs.contours_maxshortaxis * Props.diagonal / 1000.0;
103 normalPrefs.contours_minlongaxis = Prefs.contours_minlongaxis * Props.diagonal / 500.0;
104 normalPrefs.contours_maxlongaxis = Prefs.contours_maxlongaxis * Props.diagonal / 500.0;
106 normalPrefs.contours_minarea = pow(Prefs.contours_minarea,2) * Props.width * Props.height / 1000000.0;
107 normalPrefs.contours_maxarea = pow(Prefs.contours_maxarea,2) * Props.width * Props.height / 1000000.0;
109 normalPrefs.contours_mincircum = Prefs.contours_mincircum * Props.diagonal / 500.0;
110 normalPrefs.contours_maxcircum = Prefs.contours_maxcircum * Props.diagonal / 500.0;
112 normalPrefs.contours_maxspeed = Prefs.contours_maxspeed * Props.diagonal / 100.0;
114 normalPrefs.contours_maxrot = 10 * Prefs.contours_maxrot;
116 normalPrefs.halfdecay = 10.0 * Prefs.halfdecay / Props.fps;
119 void trackbarCallbackUpdateNormPrefs (int trackpos, void *userdata) {
120 computeNormalizedPrefs();
123 void drawTimes(Mat& mContours) {
128 alles << framenum << " : " << fixed << (framenum)/(double)Props.fps << "s : +" << (double)moresleep/Props.fps << "ms";
130 string text = alles.str();
131 int fontFace = FONT_HERSHEY_DUPLEX;
133 double fontScale = Props.width / 600.0;
134 int thickness = Props.width / 250.0;
137 Size textSize = getTextSize(text, fontFace,
138 fontScale, thickness, &baseline);
140 Point textOrg(Props.width*0.01, Props.width*0.01 + textSize.height);
142 rectangle(mContours, Point(0, 0),
143 Point(Props.width, Props.width*0.02 + textSize.height),
144 Scalar(0,0,255), -1);
146 putText(mContours, text, textOrg, fontFace, fontScale,
147 Scalar::all(255), thickness, 8);
150 void readCreatePrefs() {
151 ifstream prefFile( (Props.basedir+"/prefs/"+Props.videohash+".prf").c_str(), ios::binary );
152 if ( prefFile.is_open() ) {
153 int scissorpointsize;
155 prefFile.read((char*)&Prefs, sizeof(Prefs));
156 prefFile.read((char*)&scissorpointsize, sizeof(int));
158 Prefs.scissorpoints = new vector<Point>;
159 for (int ipoint = 0; ipoint < scissorpointsize; ipoint++) {
161 prefFile.read((char*)&tmpPoint, sizeof(Point));
162 Prefs.scissorpoints->push_back(tmpPoint);
167 cout << "Loaded saved preferences." << endl;
170 Prefs.scissorpoints = new vector<Point>;
174 void loadDefaultPrefs () {
175 Prefs.halfdecay = 100;
176 Prefs.forethreshold = 0;
177 Prefs.mincolor[0] = 0; Prefs.mincolor[1] = 0; Prefs.mincolor[2] = 0;
178 Prefs.maxcolor[0] = 255; Prefs.maxcolor[1] = 255; Prefs.maxcolor[2] = 255;
180 Prefs.contours_minshortaxis = 0;
181 Prefs.contours_maxshortaxis = 100;
182 Prefs.contours_minlongaxis = 0;
183 Prefs.contours_maxlongaxis = 100;
184 Prefs.contours_minarea = 0;
185 Prefs.contours_maxarea = 100;
186 Prefs.contours_mincircum = 0;
187 Prefs.contours_maxcircum = 100;
188 Prefs.contours_maxspeed = 100;
189 Prefs.contours_maxrot = 100;
192 unsigned long frameHash(const Mat& frame) {
193 unsigned long hash = 5381;
195 unsigned char *str = (unsigned char*)frame.data;
198 hash = ((hash << 5) + hash) + c;
204 int timeWarp(int x, int y) {
205 int horiMove = startClickPos.x - x;
206 int wannago = startClickFrame + 10*horiMove*Props.fps/Props.width;
208 int maxgo = min(ilatetagintime, Props.totframes);
209 int mingo = max(1, ilatetagintime - comArgs.backBufferSize*Props.fps + 2);
213 else if (wannago < mingo)
219 int rotatingTime( int whereyouwant ) {
220 if ( whereyouwant < 0 )
221 return comArgs.backBufferSize*Props.fps + whereyouwant;
226 void mouseTracking(int evt, int x, int y, int flags, void* param){
227 if ( stopvideo && evt == CV_EVENT_LBUTTONDBLCLK ) {
231 for (int itag = 0; itag < tags.size(); itag++) {
232 double dist = pow(tags[itag].x - x, 2) + pow(tags[itag].y - y, 2);
233 if ( dist < mindist || mindist == -1 ) {
234 nearestTags[tagsselected-1] = itag;
239 if (tagsselected == 2) {
240 if ( nearestTags[0] < tags.size() && nearestTags[0] >= 0
241 && nearestTags[1] < tags.size() && nearestTags[1] >= 0
242 && nearestTags[0] != nearestTags[1] ) {
246 tmp = tags[nearestTags[0]];
247 tags[nearestTags[0]] = tags[nearestTags[1]];
248 tags[nearestTags[1]] = tmp;
249 tags[nearestTags[1]].color = tags[nearestTags[0]].color;
250 tags[nearestTags[0]].color = tmp.color;
252 for (int itagintime = framenum-1; itagintime <= ilatetagintime; itagintime++) {
253 tmp = (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]];
255 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]] =
256 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]];
258 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]] = tmp;
260 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]].color =
261 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]].color;
263 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]].color = tmp.color;
268 gotoframe = framenum;
273 if ( evt == CV_EVENT_RBUTTONUP ) {
274 stopvideo = (stopvideo+1)%2;
277 if (evt==CV_EVENT_LBUTTONDOWN) {
278 leftbuttondown = true;
279 startClickPos = Point2f(x,y);
280 startClickFrame = framenum;
283 stoppedbefore = true;
285 stoppedbefore = false;
290 if (evt==CV_EVENT_LBUTTONUP) {
291 leftbuttondown = false;
293 int vertiMove = startClickPos.y - y;
295 if ( ! stoppedbefore )
299 if ( leftbuttondown && startClickPos.x - x != 0 && ! (startClickPos.x > Props.width*5.0/6.0 && x > Props.width*5.0/6.0) ) {
300 gotoframe = timeWarp(x, y);
304 if ( leftbuttondown && startClickPos.y - y != 0) {
305 int vertiMove = startClickPos.y - y;
307 if ( startClickPos.x > Props.width*5.0/6.0 && x > Props.width*5.0/6.0 ) {
308 moresleep -= vertiMove*10000/Props.height;
312 drawTimes(tracking_getFrame());
317 void writeTanjaLog (int writeframenum, vector<tag> *tagstowrite) {
318 stringstream outline;
320 if ( writeframenum%(Props.fps/comArgs.maxOutputFPS) == 0 ) {
321 if ( tagstowrite->size() >= Prefs.manyfish ) {
322 outline << (double)writeframenum/Props.fps;
323 for ( int itag = 0; itag < Prefs.manyfish; itag++) {
324 outline << "," << (*tagstowrite)[itag].x << "," << (*tagstowrite)[itag].y << "," << (double)writeframenum/Props.fps-(*tagstowrite)[itag].lastseen;
327 tanjaLogFile.write( outline.str().c_str(), outline.str().size() );
328 tanjaLogFile.flush();
333 void openTanjaLog () {
334 stringstream tanjalogfilename;
337 _mkdir(Props.basedir.c_str());
338 _mkdir((Props.basedir+"/logs").c_str());
340 mkdir( Props.basedir.c_str(), 0777 );
341 mkdir( (Props.basedir+"/logs").c_str(), 0777 );
345 _mkdir(Props.basedir.c_str());
346 _mkdir((Props.basedir+"/logs/"+Props.videohash).c_str());
348 mkdir( Props.basedir.c_str(), 0777 );
349 mkdir( (Props.basedir+"/logs/"+Props.videohash).c_str(), 0777 );
352 tanjalogfilename << Props.basedir << "/logs/" << Props.videohash << "/" << time(0) << ".dat";
353 tanjaLogFile.open( tanjalogfilename.str().c_str() );
355 if ( ! tanjaLogFile.is_open() ) {
356 cerr << "Could not open tanjalogfile " << tanjalogfilename.str() << "!" << endl;
360 tanjaLogFile.write("# STICKLETRACK - TANJALOG\n", 26);
361 tanjaLogFile.write("# format: <time in s>,<tag_1 xpos>,<tag_1 ypos>,<tag_1 losttime>,...,<tag_n xpos>,<tag_n ypos>,<tag_n losttime>\n", 112);
363 tanjaLogFile.flush();
366 int process(VideoCapture& capture) {
369 bool pleaseExit = false;
371 namedWindow("stickletrack_original", CV_WINDOW_KEEPRATIO);
373 Mat frame, origframe, combinedmask;
375 Props.fps = capture.get(CV_CAP_PROP_FPS);
376 Props.width = capture.get(CV_CAP_PROP_FRAME_WIDTH)*comArgs.rescalingFactor;
377 Props.height = capture.get(CV_CAP_PROP_FRAME_HEIGHT)*comArgs.rescalingFactor;
378 Props.totframes = capture.get(CV_CAP_PROP_FRAME_COUNT);
379 Props.diagonal = sqrt( pow(Props.width, 2) + pow(Props.height, 2) );
381 if ( Props.width == 0 || Props.height == 0 || Props.fps == 0 || Props.totframes == 0 ) {
382 cerr << "Something got wrong while reading video-file info!" << endl;
383 cerr << "Width: " << Props.width << endl;
384 cerr << "Height: " << Props.height << endl;
385 cerr << "FPS: " << Props.fps << endl;
386 cerr << "Total frames: " << Props.totframes << endl;
390 Mat frameintime[comArgs.backBufferSize*Props.fps];
392 tagintime = new vector<tag>*[comArgs.backBufferSize*Props.fps];
394 for (int iback = 0; iback < comArgs.backBufferSize*Props.fps; iback++)
395 tagintime[iback] = new vector<tag>;
402 stringstream tmphash;
403 tmphash << frameHash(frame);
404 Props.videohash = tmphash.str();
409 computeNormalizedPrefs();
414 tracking_init(&mouseTracking);
416 capture.set(CV_CAP_PROP_POS_FRAMES, 0);
418 for (; !pleaseExit;) {
419 if ( !stopvideo || dooneframe ) {
420 framenum = gotoframe;
424 frametime = (framenum-1.0) / Props.fps;
426 if ( framenum <= ilatetagintime ) {
427 origframe = frameintime[ rotatingTime(tagintimeidx - (ilatetagintime-framenum) - 2) ];
428 masking_getCombinedMask(origframe, combinedmask);
431 for (int itag = 0; itag < tagintime[ rotatingTime( tagintimeidx - 2 - (ilatetagintime-framenum) ) ]->size(); itag++)
432 tags.push_back( (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-framenum) - 2) ])[itag] );
434 tracking_locateTags (tags, combinedmask);
442 frame.convertTo(origframe, CV_32FC3);
443 resize(origframe, origframe, Size(0,0), comArgs.rescalingFactor, comArgs.rescalingFactor);
445 masking_getCombinedMask(origframe, combinedmask);
447 tracking_locateTags (tags, combinedmask);
449 tagintime[tagintimeidx]->clear();
450 for (int itag = 0; itag < tags.size(); itag++)
451 tagintime[tagintimeidx]->push_back(tags[itag]);
453 frameintime[tagintimeidx] = origframe;
455 ilatetagintime = framenum;
456 tagintimeidx = (tagintimeidx+1)%(comArgs.backBufferSize*Props.fps);
458 if ( ilatetagintime >= comArgs.backBufferSize*Props.fps-1 )
459 writeTanjaLog( framenum - (comArgs.backBufferSize*Props.fps-1), tagintime[ tagintimeidx ] );
462 gotoframe = framenum + 1;
464 if ( ! isWindowClosed("stickletrack_original") )
465 imshow("stickletrack_original", origframe/255.0);
468 drawTimes(tracking_getFrame());
470 if ( tagsselected == 1 ) {
471 circle( tracking_getFrame(), Point2f(tags[nearestTags[0]].x, tags[nearestTags[0]].y), Props.diagonal / 100.0, Scalar(0,0,255), -1, 8 );
474 if ( tracking_showFrame() )
480 key = (char)waitKey(5 + (double)moresleep/Props.fps);
482 key = (char)waitKey(5);
494 if ( ilatetagintime >= comArgs.backBufferSize*Props.fps-1 ) {
495 int timeidx = (tagintimeidx+1)%(comArgs.backBufferSize*Props.fps);
496 for ( int itime = 0; itime < comArgs.backBufferSize*Props.fps-1; itime++ ) {
497 writeTanjaLog( framenum+1+itime - (comArgs.backBufferSize*Props.fps-1), tagintime[ timeidx ] );
498 timeidx = (timeidx+1)%(comArgs.backBufferSize*Props.fps);
502 for ( int itime = 0; itime < ilatetagintime+1; itime++ )
503 writeTanjaLog( itime, tagintime[ itime ] );
506 tanjaLogFile.close();
510 cout << "Bye bye." << endl;
516 cout << "Usage: stickletrack [OPTIONS]... <path to videofile>" << endl;
517 cout << endl << "Available options:" << endl;
518 cout << "-x <rescalingfactor>" << "\t" << "rescale video by a factor <rescalingfactor>" << endl;
519 cout << "-o <maxfps>" << "\t" << "write to log maximally <maxfps> times per second" << endl;
520 cout << "-b <bsize>" << "\t" << "buffer <bsize> seconds for going back in time" << endl;
523 bool parseComArgs (int ac, char* av[]) {
524 if ( ac < 2 || ac%2 ) {
529 comArgs.videoFileName = NULL;
530 comArgs.rescalingFactor = 1.0;
531 comArgs.maxOutputFPS = 25;
532 comArgs.backBufferSize = 10;
534 for (int i = 1; i < ac-2; i = i+2)
535 if ( strcmp(av[i], "-x") == 0 ) {
536 comArgs.rescalingFactor = atof(av[i + 1]);
537 } else if ( strcmp(av[i], "-o") == 0 ) {
538 comArgs.maxOutputFPS = atoi(av[i + 1]);
539 } else if ( strcmp(av[i], "-b") == 0 ) {
540 comArgs.backBufferSize = atoi(av[i + 1]);
542 cout << "Unknown command line parameters!\n";
547 comArgs.videoFileName = av[ac-1];
550 int main(int ac, char* av[]) {
551 parseComArgs(ac, av);
553 capture.open( comArgs.videoFileName );
557 if (!capture.isOpened()) {
558 cerr << "Failed to open a video device or video file!\n" << endl;
562 return process(capture);