1 #include "opencv2/highgui/highgui.hpp"
2 #include "opencv2/imgproc/imgproc.hpp"
19 #include "stickletrack.h"
22 #define BACKSECONDS 30
30 normalprefs normalPrefs;
32 static VideoCapture capture;
34 static bool stopvideo=false;
35 static bool leftbuttondown=false;
36 static Point2f startClickPos;
37 static int ilatetagintime=-1, iearlytagintime=-1;
38 static int startClickFrame;
39 static bool stoppedbefore;
40 static bool dooneframe=false;
41 static vector<tag> tags;
42 static int tagsselected=0;
43 static int nearestTags[2];
44 static vector<tag> **tagintime;
45 static int tagintimeidx=0;
46 static int moresleep=0;
47 static int gotoframe=0;
48 static int maxoutputfps;
49 static double rescalingfactor;
50 static ofstream tanjaLogFile;
54 Props.basedir = getenv("HOMEDRIVE");
55 Props.basedir += getenv("HOMEPATH");
57 Props.basedir = getenv("HOME");
60 Props.basedir += "/.stickletrack";
62 cout << "Using " << Props.basedir << " for data output." << endl;
65 bool isWindowClosed (const char* name) {
66 if ( cvGetWindowHandle(name) == NULL )
73 cout << "Exitting ..." << endl;
76 _mkdir(Props.basedir.c_str());
77 _mkdir((Props.basedir+"/prefs").c_str());
79 mkdir( Props.basedir.c_str(), 0777 );
80 mkdir( (Props.basedir+"/prefs").c_str(), 0777 );
83 cout << "Saving preferences ..." << endl;
84 int scissorpointsize = Prefs.scissorpoints->size();
85 ofstream prefFile( (Props.basedir+"/prefs/"+Props.videohash+".prf").c_str(), ios::binary );
86 prefFile.write((char*)&Prefs, sizeof(Prefs));
87 prefFile.write((char*)&scissorpointsize, sizeof(int));
89 for (int ipoint = 0; ipoint < scissorpointsize; ipoint++)
90 prefFile.write( (char*)&(*Prefs.scissorpoints)[ipoint], sizeof(Point) );
95 void computeNormalizedPrefs() {
96 normalPrefs.contours_minshortaxis = Prefs.contours_minshortaxis * Props.diagonal / 1000.0;
97 normalPrefs.contours_maxshortaxis = Prefs.contours_maxshortaxis * Props.diagonal / 1000.0;
98 normalPrefs.contours_minlongaxis = Prefs.contours_minlongaxis * Props.diagonal / 500.0;
99 normalPrefs.contours_maxlongaxis = Prefs.contours_maxlongaxis * Props.diagonal / 500.0;
101 normalPrefs.contours_minarea = pow(Prefs.contours_minarea,2) * Props.width * Props.height / 1000000.0;
102 normalPrefs.contours_maxarea = pow(Prefs.contours_maxarea,2) * Props.width * Props.height / 1000000.0;
104 normalPrefs.contours_mincircum = Prefs.contours_mincircum * Props.diagonal / 500.0;
105 normalPrefs.contours_maxcircum = Prefs.contours_maxcircum * Props.diagonal / 500.0;
107 normalPrefs.contours_maxspeed = Prefs.contours_maxspeed * Props.diagonal / 100.0;
109 normalPrefs.contours_maxrot = 10 * Prefs.contours_maxrot;
111 normalPrefs.halfdecay = 10.0 * Prefs.halfdecay / Props.fps;
114 void trackbarCallbackUpdateNormPrefs (int trackpos, void *userdata) {
115 computeNormalizedPrefs();
118 void drawTimes(Mat& mContours) {
123 alles << framenum << " : " << fixed << (framenum)/(double)Props.fps << "s : +" << (double)moresleep/Props.fps << "ms";
125 string text = alles.str();
126 int fontFace = FONT_HERSHEY_SCRIPT_SIMPLEX;
128 double fontScale = Props.diagonal/1000.0;
132 Size textSize = getTextSize(text, fontFace,
133 fontScale, thickness, &baseline);
135 Point textOrg(10, 10 + textSize.height);
137 rectangle(mContours, Point(0, 0),
138 Point(Props.width, 20+textSize.height),
139 Scalar(0,0,255), -1);
141 putText(mContours, text, textOrg, fontFace, fontScale,
142 Scalar::all(255), thickness, 8);
145 void readCreatePrefs() {
146 ifstream prefFile( (Props.basedir+"/prefs/"+Props.videohash+".prf").c_str(), ios::binary );
147 if ( prefFile.is_open() ) {
148 int scissorpointsize;
150 prefFile.read((char*)&Prefs, sizeof(Prefs));
151 prefFile.read((char*)&scissorpointsize, sizeof(int));
153 Prefs.scissorpoints = new vector<Point>;
154 for (int ipoint = 0; ipoint < scissorpointsize; ipoint++) {
156 prefFile.read((char*)&tmpPoint, sizeof(Point));
157 Prefs.scissorpoints->push_back(tmpPoint);
162 cout << "Loaded saved preferences." << endl;
165 Prefs.scissorpoints = new vector<Point>;
169 void loadDefaultPrefs () {
170 Prefs.halfdecay = 100;
171 Prefs.forethreshold = 0;
172 Prefs.mincolor[0] = 0; Prefs.mincolor[1] = 0; Prefs.mincolor[2] = 0;
173 Prefs.maxcolor[0] = 255; Prefs.maxcolor[1] = 255; Prefs.maxcolor[2] = 255;
175 Prefs.contours_minshortaxis = 0;
176 Prefs.contours_maxshortaxis = 100;
177 Prefs.contours_minlongaxis = 0;
178 Prefs.contours_maxlongaxis = 100;
179 Prefs.contours_minarea = 0;
180 Prefs.contours_maxarea = 100;
181 Prefs.contours_mincircum = 0;
182 Prefs.contours_maxcircum = 100;
183 Prefs.contours_maxspeed = 100;
184 Prefs.contours_maxrot = 100;
187 unsigned long frameHash(const Mat& frame) {
188 unsigned long hash = 5381;
190 unsigned char *str = (unsigned char*)frame.data;
193 hash = ((hash << 5) + hash) + c;
199 int timeWarp(int x, int y) {
200 int horiMove = startClickPos.x - x;
201 int wannago = startClickFrame + 10*horiMove*Props.fps/Props.width;
203 int maxgo = min(ilatetagintime, Props.totframes);
204 int mingo = max(1, ilatetagintime - BACKSECONDS*Props.fps + 2);
208 else if (wannago < mingo)
214 int rotatingTime( int whereyouwant ) {
215 if ( whereyouwant < 0 )
216 return BACKSECONDS*Props.fps + whereyouwant;
221 void mouseTracking(int evt, int x, int y, int flags, void* param){
222 if ( stopvideo && evt == CV_EVENT_LBUTTONDBLCLK ) {
226 for (int itag = 0; itag < tags.size(); itag++) {
227 double dist = pow(tags[itag].x - x, 2) + pow(tags[itag].y - y, 2);
228 if ( dist < mindist || mindist == -1 ) {
229 nearestTags[tagsselected-1] = itag;
234 if (tagsselected == 2) {
235 if ( nearestTags[0] < tags.size() && nearestTags[0] >= 0
236 && nearestTags[1] < tags.size() && nearestTags[1] >= 0
237 && nearestTags[0] != nearestTags[1] ) {
241 tmp = tags[nearestTags[0]];
242 tags[nearestTags[0]] = tags[nearestTags[1]];
243 tags[nearestTags[1]] = tmp;
244 tags[nearestTags[1]].color = tags[nearestTags[0]].color;
245 tags[nearestTags[0]].color = tmp.color;
247 for (int itagintime = framenum-1; itagintime <= ilatetagintime; itagintime++) {
248 tmp = (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]];
250 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]] =
251 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]];
253 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]] = tmp;
255 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[1]].color =
256 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]].color;
258 (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-itagintime) - 1) ])[nearestTags[0]].color = tmp.color;
263 gotoframe = framenum;
268 if ( evt == CV_EVENT_RBUTTONUP ) {
269 stopvideo = (stopvideo+1)%2;
272 if (evt==CV_EVENT_LBUTTONDOWN) {
273 leftbuttondown = true;
274 startClickPos = Point2f(x,y);
275 startClickFrame = framenum;
278 stoppedbefore = true;
280 stoppedbefore = false;
285 if (evt==CV_EVENT_LBUTTONUP) {
286 leftbuttondown = false;
288 int vertiMove = startClickPos.y - y;
290 if ( ! stoppedbefore )
294 if ( leftbuttondown && startClickPos.x - x != 0 && ! (startClickPos.x > Props.width*5.0/6.0 && x > Props.width*5.0/6.0) ) {
295 gotoframe = timeWarp(x, y);
299 if ( leftbuttondown && startClickPos.y - y != 0) {
300 int vertiMove = startClickPos.y - y;
302 if ( startClickPos.x > Props.width*5.0/6.0 && x > Props.width*5.0/6.0 ) {
303 moresleep -= vertiMove*10000/Props.height;
307 drawTimes(tracking_getFrame());
312 void writeTanjaLog (int writeframenum, vector<tag> *tagstowrite) {
313 stringstream outline;
315 if ( writeframenum%(Props.fps/maxoutputfps) == 0 ) {
316 if ( tagstowrite->size() >= Prefs.manyfish ) {
317 outline << (double)writeframenum/Props.fps;
318 for ( int itag = 0; itag < Prefs.manyfish; itag++) {
319 outline << "," << (*tagstowrite)[itag].x << "," << (*tagstowrite)[itag].y << "," << (double)writeframenum/Props.fps-(*tagstowrite)[itag].lastseen;
322 tanjaLogFile.write( outline.str().c_str(), outline.str().size() );
323 tanjaLogFile.flush();
328 void openTanjaLog () {
329 stringstream tanjalogfilename;
332 _mkdir(Props.basedir.c_str());
333 _mkdir((Props.basedir+"/logs").c_str());
335 mkdir( Props.basedir.c_str(), 0777 );
336 mkdir( (Props.basedir+"/logs").c_str(), 0777 );
340 _mkdir(Props.basedir.c_str());
341 _mkdir((Props.basedir+"/logs/"+Props.videohash).c_str());
343 mkdir( Props.basedir.c_str(), 0777 );
344 mkdir( (Props.basedir+"/logs/"+Props.videohash).c_str(), 0777 );
347 tanjalogfilename << Props.basedir << "/logs/" << Props.videohash << "/" << time(0) << ".dat";
348 tanjaLogFile.open( tanjalogfilename.str().c_str() );
350 if ( ! tanjaLogFile.is_open() ) {
351 cerr << "Could not open tanjalogfile " << tanjalogfilename.str() << "!" << endl;
355 tanjaLogFile.write("# STICKLETRACK - TANJALOG\n", 26);
356 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);
358 tanjaLogFile.flush();
361 int process(VideoCapture& capture) {
364 bool pleaseExit = false;
366 namedWindow("stickletrack_original", CV_WINDOW_KEEPRATIO);
368 Mat frame, origframe, combinedmask;
370 Props.fps = capture.get(CV_CAP_PROP_FPS);
371 Props.width = capture.get(CV_CAP_PROP_FRAME_WIDTH)*rescalingfactor;
372 Props.height = capture.get(CV_CAP_PROP_FRAME_HEIGHT)*rescalingfactor;
373 Props.totframes = capture.get(CV_CAP_PROP_FRAME_COUNT);
374 Props.diagonal = sqrt( pow(Props.width, 2) + pow(Props.height, 2) );
376 Mat frameintime[BACKSECONDS*Props.fps];
378 tagintime = new vector<tag>*[BACKSECONDS*Props.fps];
380 for (int iback = 0; iback < BACKSECONDS*Props.fps; iback++)
381 tagintime[iback] = new vector<tag>;
388 stringstream tmphash;
389 tmphash << frameHash(frame);
390 Props.videohash = tmphash.str();
395 computeNormalizedPrefs();
402 cvSetMouseCallback("contours_picture", mouseTracking, 0);
404 capture.set(CV_CAP_PROP_POS_FRAMES, 0);
406 for (; !pleaseExit;) {
407 if ( !stopvideo || dooneframe ) {
408 framenum = gotoframe;
412 frametime = (framenum-1.0) / Props.fps;
414 if ( framenum <= ilatetagintime ) {
415 origframe = frameintime[ rotatingTime(tagintimeidx - (ilatetagintime-framenum) - 2) ];
416 masking_getCombinedMask(origframe, combinedmask);
419 for (int itag = 0; itag < tagintime[ rotatingTime( tagintimeidx - 2 - (ilatetagintime-framenum) ) ]->size(); itag++)
420 tags.push_back( (*tagintime[ rotatingTime(tagintimeidx - (ilatetagintime-framenum) - 2) ])[itag] );
422 tracking_locateTags (tags, combinedmask);
430 frame.convertTo(origframe, CV_32FC3);
431 resize(origframe, origframe, Size(0,0), rescalingfactor, rescalingfactor);
433 masking_getCombinedMask(origframe, combinedmask);
435 tracking_locateTags (tags, combinedmask);
437 tagintime[tagintimeidx]->clear();
438 for (int itag = 0; itag < tags.size(); itag++)
439 tagintime[tagintimeidx]->push_back(tags[itag]);
441 frameintime[tagintimeidx] = origframe;
443 ilatetagintime = framenum;
444 tagintimeidx = (tagintimeidx+1)%(BACKSECONDS*Props.fps);
446 if ( ilatetagintime >= BACKSECONDS*Props.fps-1 )
447 writeTanjaLog( framenum - (BACKSECONDS*Props.fps-1), tagintime[ tagintimeidx ] );
450 gotoframe = framenum + 1;
452 if ( ! isWindowClosed("stickletrack_original") )
453 imshow("stickletrack_original", origframe/255.0);
456 drawTimes(tracking_getFrame());
458 if ( tagsselected == 1 ) {
459 circle( tracking_getFrame(), Point2f(tags[nearestTags[0]].x, tags[nearestTags[0]].y), Props.diagonal / 100.0, Scalar(0,0,255), -1, 8 );
462 if ( tracking_showFrame() )
468 key = (char)waitKey(5 + (double)moresleep/Props.fps);
470 key = (char)waitKey(5);
482 if ( ilatetagintime >= BACKSECONDS*Props.fps-1 ) {
483 int timeidx = (tagintimeidx+1)%(BACKSECONDS*Props.fps);
484 for ( int itime = 0; itime < BACKSECONDS*Props.fps-1; itime++ ) {
485 writeTanjaLog( framenum+1+itime - (BACKSECONDS*Props.fps-1), tagintime[ timeidx ] );
486 timeidx = (timeidx+1)%(BACKSECONDS*Props.fps);
490 for ( int itime = 0; itime < ilatetagintime+1; itime++ )
491 writeTanjaLog( itime, tagintime[ itime ] );
494 tanjaLogFile.close();
498 cout << "Bye bye." << endl;
503 int main(int ac, char** av) {
505 cout << "Usage: stickletrack <maxoutputfps> <rescalingfactor> <videofilename>" << endl;
509 maxoutputfps = atoi( av[1] );
510 rescalingfactor = atof( av[2] );
512 capture.open( av[3] );
516 if (!capture.isOpened()) {
517 cerr << "Failed to open a video device or video file!\n" << endl;
521 return process(capture);