+#include <stdio.h>
+#include <iostream>
+
+using namespace std;
+
+#include "tracking.h"
+#include "stickletrack.h"
+
+#define SEPCOST_AREA 0
+#define SEPCOST_CIRCUM 1
+#define SEPCOST_SHORTAXIS 2
+#define SEPCOST_LONGAXIS 3
+#define SEPCOST_ANGLE 4
+#define SEPCOST_DIST 5
+
+struct fishblobb {
+ double x;
+ double y;
+ bool identified;
+ int contourid;
+ vector<Point> *points;
+ double longaxis;
+ double shortaxis;
+ double angle;
+ int area;
+ int circum;
+ Point2f head, tail;
+};
+
+static int enablecontours=0;
+static Mat mContours;
+
+struct tagcost {
+ int tagid;
+ double tagcost;
+};
+
+struct blobbcost {
+ int blobbid;
+ vector<tagcost> tagcosts;
+};
+
+bool sort_tags (tagcost i, tagcost j) {
+ return i.tagcost < j.tagcost;
+}
+
+bool sort_blobbs (blobbcost i, blobbcost j) {
+ return i.tagcosts[0].tagcost < j.tagcosts[0].tagcost;
+}
+
+Mat& tracking_getFrame() {
+ return mContours;
+}
+
+void tracking_showFrame() {
+ imshow("contours_picture", mContours);
+}
+
+bool tracking_isEnabled () {
+ return enablecontours;
+}
+
+void tracking_init() {
+ namedWindow("contours", CV_WINDOW_KEEPRATIO);
+ namedWindow("contours_picture", CV_WINDOW_KEEPRATIO);
+
+ createTrackbar("Enable", "contours", &enablecontours, 1, NULL, 0);
+ createTrackbar("Manyfish", "contours", &Prefs.manyfish, 10, NULL, 0);
+ createTrackbar("min area", "contours", &Prefs.contours_minarea, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("max area", "contours", &Prefs.contours_maxarea, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("min circum", "contours", &Prefs.contours_mincircum, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("max circum", "contours", &Prefs.contours_maxcircum, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("min shortaxis", "contours", &Prefs.contours_minshortaxis, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("max shortaxis", "contours", &Prefs.contours_maxshortaxis, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("min longaxis", "contours", &Prefs.contours_minlongaxis, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("max longaxis", "contours", &Prefs.contours_maxlongaxis, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("max speed", "contours", &Prefs.contours_maxspeed, 100, &trackbarCallbackUpdateNormPrefs, 0);
+ createTrackbar("max rotation speed", "contours", &Prefs.contours_maxrot, 100, &trackbarCallbackUpdateNormPrefs, 0);
+
+ mContours = Mat::zeros(Props.height, Props.width, CV_8UC3);
+}
+
+double kitvalue (double min, double max) {
+ return min + pow( cos(frametime*10), 2)*(max-min);
+}
+
+void findFishBlobbs(vector<fishblobb>& fishblobbs, vector< vector<Point> >& contours, vector<Vec4i>& hierarchy) {
+ for (int icontour=0; icontour >= 0; icontour = hierarchy[icontour][0]) {
+ fishblobb newblobb;
+
+ if ( contours[icontour].size() < 5 )
+ continue;
+
+ newblobb.circum = contours[icontour].size();
+ if ( newblobb.circum < normalPrefs.contours_mincircum || newblobb.circum > normalPrefs.contours_maxcircum )
+ continue;
+
+ newblobb.area = contourArea(contours[icontour]);
+ if ( newblobb.area < normalPrefs.contours_minarea || newblobb.area > normalPrefs.contours_maxarea )
+ continue;
+
+ RotatedRect minEllipse = fitEllipse(contours[icontour]);
+ newblobb.longaxis = minEllipse.size.height;
+ newblobb.shortaxis = minEllipse.size.width;
+ if ( newblobb.shortaxis < normalPrefs.contours_minshortaxis || newblobb.shortaxis > normalPrefs.contours_maxshortaxis
+ || newblobb.longaxis < normalPrefs.contours_minlongaxis || newblobb.longaxis > normalPrefs.contours_maxlongaxis )
+ continue;
+
+ newblobb.x = minEllipse.center.x;
+ newblobb.y = minEllipse.center.y;
+
+ RotatedRect minRect;
+ Mat mFish = Mat::zeros(Props.height, Props.width, CV_8U);
+ Moments fishMom;
+ Point2f massCenter;
+ Point2f recHead, recTail;
+
+ newblobb.identified = false;
+ newblobb.points = &contours[icontour];
+
+ newblobb.contourid = icontour;
+
+ /* find head/tail sensitive angle
+ maybe the method could be improved */
+ drawContours( mFish, contours, icontour, Scalar(255, 255, 255), CV_FILLED );
+ fishMom = moments(mFish);
+
+ massCenter = Point2f( fishMom.m10/fishMom.m00 , fishMom.m01/fishMom.m00 );
+ minRect = minAreaRect(contours[ icontour ]);
+
+ recHead = Point2f( minRect.center.x + newblobb.longaxis*cos(minEllipse.angle * M_PI / 180 + M_PI/2),
+ minRect.center.y + newblobb.longaxis*sin(minEllipse.angle * M_PI / 180 + M_PI/2) );
+ recTail = Point2f( minRect.center.x + newblobb.longaxis*cos(minEllipse.angle * M_PI / 180 - M_PI/2),
+ minRect.center.y + newblobb.longaxis*sin(minEllipse.angle * M_PI / 180 - M_PI/2) );
+
+ if ( pow( recHead.x - massCenter.x, 2 ) + pow( recHead.y - massCenter.y, 2 ) > pow( recTail.x - massCenter.x, 2 ) + pow( recTail.y - massCenter.y, 2 ) ) {
+ newblobb.angle = minEllipse.angle + 180;
+
+ if (newblobb.angle > 360)
+ newblobb.angle = newblobb.angle - 360;
+ }
+ else {
+ newblobb.angle = minEllipse.angle;
+ }
+
+ newblobb.tail = Point2f( minEllipse.center.x + newblobb.longaxis*cos(newblobb.angle * M_PI / 180 - M_PI/2),
+ minEllipse.center.y + newblobb.longaxis*sin(newblobb.angle * M_PI / 180 - M_PI/2) );
+ newblobb.head = Point2f( minEllipse.center.x + newblobb.longaxis*cos(newblobb.angle * M_PI / 180 + M_PI/2),
+ minEllipse.center.y + newblobb.longaxis*sin(newblobb.angle * M_PI / 180 + M_PI/2) );
+
+ fishblobbs.push_back(newblobb);
+ }
+}
+
+void tagCare(vector<tag>& tags) {
+ while ( tags.size() > Prefs.manyfish )
+ tags.pop_back();
+
+ while ( tags.size() < Prefs.manyfish ) {
+ tag tmp;
+ tmp.x = 0;
+ tmp.y = 0;
+ tmp.propZ = 0;
+ tmp.lastseen = frametime;
+
+ switch ( tags.size() ) {
+ case 0:
+ tmp.color = Scalar( 0, 255, 255 );
+ break;
+ case 1:
+ tmp.color = Scalar( 255, 0, 255 );
+ break;
+ case 2:
+ tmp.color = Scalar( 255, 255, 0 );
+ break;
+ case 3:
+ tmp.color = Scalar( 255, 0, 0 );
+ break;
+ case 4:
+ tmp.color = Scalar( 0, 255, 0 );
+ break;
+ case 5:
+ tmp.color = Scalar( 0, 0, 255 );
+ break;
+ default:
+ tmp.color = Scalar( rand()%255, rand()%255, rand()%255 );
+ }
+
+ tmp.angle = 0;
+ tmp.area = 0;
+ tmp.circum = 0;
+ tmp.shortaxis = 0;
+ tmp.longaxis = 0;
+ tmp.virgin = true;
+ tags.push_back(tmp);
+ }
+}
+
+void matchBlobbsNTags(vector<tag>& tags, vector<fishblobb>& fishblobbs) {
+ vector<blobbcost> blobbcosts;
+
+ for (int iblobb = 0; iblobb < fishblobbs.size(); iblobb++) {
+ double sepcost[tags.size()][6];
+ double maxcost[6];
+ blobbcost blobbCost;
+
+ blobbCost.blobbid = iblobb;
+
+ for (int imax = 0; imax < 6; imax++)
+ maxcost[imax] = 0;
+
+ for (int itag = 0; itag < tags.size(); itag++) {
+ double anglediff = fabs (fishblobbs[iblobb].angle - tags[itag].angle);
+
+ if ( anglediff > 180 )
+ anglediff = 360 - anglediff;
+
+ sepcost[itag][SEPCOST_ANGLE] = anglediff;
+
+ sepcost[itag][SEPCOST_AREA] = fabs ( fishblobbs[iblobb].area - tags[itag].area );
+ sepcost[itag][SEPCOST_CIRCUM] = fabs ( fishblobbs[iblobb].circum - tags[itag].circum );
+ sepcost[itag][SEPCOST_SHORTAXIS] = fabs ( fishblobbs[iblobb].shortaxis - tags[itag].shortaxis );
+ sepcost[itag][SEPCOST_LONGAXIS] = fabs ( fishblobbs[iblobb].longaxis - tags[itag].longaxis );
+ sepcost[itag][SEPCOST_DIST] = sqrt( pow(tags[itag].x - fishblobbs[iblobb].x, 2) + pow(tags[itag].y - fishblobbs[iblobb].y, 2) );
+
+ for (int imax = 0; imax < 6; imax++)
+ if ( maxcost[imax] < sepcost[itag][imax] )
+ maxcost[imax] = sepcost[itag][imax];
+ }
+
+ for (int itag = 0; itag < tags.size(); itag++) {
+ tagcost newTagCost;
+
+ newTagCost.tagid = itag;
+ newTagCost.tagcost = 0;
+
+ if ( sepcost[itag][SEPCOST_DIST] > ( frametime - tags[itag].lastseen )*normalPrefs.contours_maxspeed
+ || sepcost[itag][SEPCOST_ANGLE] > ( frametime - tags[itag].lastseen )*normalPrefs.contours_maxrot )
+ continue;
+
+ for (int isep = 0; isep < 6; isep++)
+ newTagCost.tagcost += sepcost[itag][isep] / maxcost[isep];
+
+ blobbCost.tagcosts.push_back( newTagCost );
+ }
+
+ if ( blobbCost.tagcosts.size() > 0 ) {
+ sort( blobbCost.tagcosts.begin(), blobbCost.tagcosts.end(), sort_tags );
+ blobbcosts.push_back( blobbCost );
+ }
+ }
+
+ for (int itag = 0; itag < tags.size() && blobbcosts.size() > 0; itag++) {
+ sort( blobbcosts.begin(), blobbcosts.end(), sort_blobbs );
+
+ int tagnow = blobbcosts[0].tagcosts[0].tagid;
+
+ tags[tagnow].virgin = false;
+ tags[tagnow].x = fishblobbs[ blobbcosts[0].blobbid ].x;
+ tags[tagnow].y = fishblobbs[ blobbcosts[0].blobbid ].y;
+ tags[tagnow].shortaxis = fishblobbs[ blobbcosts[0].blobbid ].shortaxis;
+ tags[tagnow].longaxis = fishblobbs[ blobbcosts[0].blobbid ].longaxis;
+ tags[tagnow].angle = fishblobbs[ blobbcosts[0].blobbid ].angle;
+ tags[tagnow].lastseen = frametime;
+ tags[tagnow].area = fishblobbs[ blobbcosts[0].blobbid ].area;
+ tags[tagnow].circum = fishblobbs[ blobbcosts[0].blobbid ].circum;
+ tags[tagnow].head = fishblobbs[ blobbcosts[0].blobbid ].head;
+ tags[tagnow].tail = fishblobbs[ blobbcosts[0].blobbid ].tail;
+
+ blobbcosts.erase( blobbcosts.begin() );
+
+ for (int iblobb = 0; iblobb < blobbcosts.size(); iblobb++) {
+
+ for (int itag = 0; itag < blobbcosts[iblobb].tagcosts.size(); itag++)
+ if ( blobbcosts[iblobb].tagcosts[itag].tagid == tagnow ) {
+ blobbcosts[iblobb].tagcosts.erase( blobbcosts[iblobb].tagcosts.begin() + itag );
+ break;
+ }
+
+ if ( blobbcosts[iblobb].tagcosts.size() == 0 ) {
+ blobbcosts.erase( blobbcosts.begin() + iblobb );
+ iblobb--;
+ }
+ }
+ }
+}
+
+void drawTags (const vector<tag>& tags) {
+ RotatedRect rRect;
+ Point2f vertices[4];
+
+ for (int itag = 0; itag < tags.size(); itag++) {
+ double recside = sqrt( kitvalue( normalPrefs.contours_minarea, normalPrefs.contours_maxarea) );
+ rRect = RotatedRect( Point(tags[itag].x, tags[itag].y), Size2f(recside, recside), tags[itag].angle );
+ rRect.points(vertices);
+ for (int i = 0; i < 4; i++)
+ line(mContours, vertices[i], vertices[(i+1)%4], Scalar(255,255,255), Props.diagonal/1000.0);
+
+ ellipse(mContours, Point(tags[itag].x, tags[itag].y),
+ Size( kitvalue( normalPrefs.contours_minshortaxis, normalPrefs.contours_maxshortaxis ),
+ kitvalue( normalPrefs.contours_minlongaxis, normalPrefs.contours_maxlongaxis ) ),
+ tags[itag].angle, 0, 360, Scalar(255,255,255), Props.diagonal/1000.0, 8);
+
+ circle(mContours, Point(tags[itag].x, tags[itag].y), kitvalue( normalPrefs.contours_mincircum, normalPrefs.contours_maxcircum )/(2*M_PI), Scalar(255,255,255),
+ Props.diagonal/1000.0, 8);
+
+ if ( ! tags[itag].virgin ) {
+ float searchradius = ( frametime - tags[itag].lastseen )*normalPrefs.contours_maxspeed;
+ float searchangle = ( frametime - tags[itag].lastseen )*normalPrefs.contours_maxrot;
+ ellipse(mContours, Point(tags[itag].x, tags[itag].y), Size(searchradius, searchradius), tags[itag].angle, 90-searchangle, 90+searchangle,
+ tags[itag].color, Props.diagonal/1000.0, 8);
+
+ rRect = RotatedRect( Point(tags[itag].x, tags[itag].y), Size2f(sqrt(tags[itag].area), sqrt(tags[itag].area)), tags[itag].angle );
+ rRect.points(vertices);
+ for (int i = 0; i < 4; i++)
+ line(mContours, vertices[i], vertices[(i+1)%4], tags[itag].color, 2);
+
+ ellipse(mContours, Point(tags[itag].x, tags[itag].y),
+ Size( tags[itag].shortaxis, tags[itag].longaxis ), tags[itag].angle, 0, 360, tags[itag].color, 2*Props.diagonal/1000.0, 8);
+
+ circle(mContours, Point(tags[itag].x, tags[itag].y), tags[itag].circum / (2*M_PI), tags[itag].color, 2*Props.diagonal/1000.0, 8);
+
+ circle(mContours, tags[itag].head, 5*Props.diagonal/1000.0, tags[itag].color, -1, 8);
+ }
+ }
+}
+
+void tracking_locateTags (vector<tag>& tags, Mat combinedmask_contour) {
+ mContours = Mat::zeros(Props.height, Props.width, CV_8UC3);
+
+ if (enablecontours) {
+ vector< vector<Point> > contours;
+ vector<Vec4i> hierarchy;
+ vector <fishblobb> fishblobbs;
+
+ findContours( combinedmask_contour, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE );
+
+ if ( contours.size() > 0 ) {
+
+ for(int idx = 0 ; idx >= 0; idx = hierarchy[idx][0] )
+ drawContours(mContours, contours, idx, Scalar(255, 255, 255), CV_FILLED, 8, hierarchy);
+
+ findFishBlobbs(fishblobbs, contours, hierarchy);
+
+ for ( int iblobb = 0; iblobb < fishblobbs.size(); iblobb++) {
+ Scalar color( rand()%255, rand()%255, rand()%255 );
+ drawContours( mContours, contours, fishblobbs[iblobb].contourid, color, CV_FILLED, 8, hierarchy );
+ }
+
+ tagCare(tags);
+ matchBlobbsNTags(tags, fishblobbs);
+
+ }
+
+ drawTags(tags);
+ }
+}