├── Containers.cpp ├── calpoints.txt ├── FeatureDetector.h ├── FaceDetector.h ├── GtkStore.h ├── GtkStore.cpp ├── opengazer.cpp ├── GazeTrackerGtk.h ├── utils.cpp ├── EyeExtractor.h ├── GazeArea.h ├── LeastSquares.h ├── TrackingSystem.h ├── HeadTracker.h ├── Point.h ├── HeadCompensation.cpp ├── OutputMethods.h ├── LeastSquares.cpp ├── FeatureDetector.cpp ├── Alert.cpp ├── GraphicalPointer.h ├── PointTracker.h ├── MainGazeTracker.h ├── FMatrixAffineCompute.cpp ├── Makefile ├── FaceDetector.cpp ├── Containers.h ├── GraphicalPointer.cpp ├── GazeTracker.h ├── Point.cpp ├── Calibrator.h ├── EyeExtractor.cpp ├── OutputMethods.cpp ├── GazeTrackerGtk.cpp ├── GaussianProcess.cpp ├── BlinkDetector.h ├── GazeArea.cpp ├── BlinkDetector.cpp ├── Alert.h ├── Calibrator.cpp ├── utils.h ├── README ├── GazeTracker.cpp ├── TrackingSystem.cpp ├── MainGazeTracker.cpp ├── PointTracker.cpp ├── HeadTracker.cpp └── LICENSE /Containers.cpp: -------------------------------------------------------------------------------- 1 | #include "Containers.h" 2 | #include 3 | 4 | -------------------------------------------------------------------------------- /calpoints.txt: -------------------------------------------------------------------------------- 1 | 0.5 0.5 2 | 0.1 0.5 3 | 0.9 0.5 4 | 0.5 0.1 5 | 0.5 0.9 6 | 0.1 0.1 7 | 0.1 0.9 8 | 0.9 0.9 9 | 0.9 0.1 10 | 0.3 0.3 11 | 0.3 0.7 12 | 0.7 0.7 13 | 0.7 0.3 14 | -------------------------------------------------------------------------------- /FeatureDetector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | 4 | class FeatureDetector { 5 | CvSize eyesize; 6 | scoped_ptr sumimage, sum2image, temp; 7 | int samples; 8 | public: 9 | FeatureDetector(CvSize eyesize); 10 | void addSample(const IplImage *source); 11 | shared_ptr getMean(); 12 | shared_ptr getVariance(); 13 | }; 14 | -------------------------------------------------------------------------------- /FaceDetector.h: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include 3 | 4 | class FaceDetector { 5 | CvMemStorage* storage; 6 | CvHaarClassifierCascade* cascade ; 7 | 8 | public: 9 | static FaceDetector facedetector; 10 | FaceDetector(char *cascadename="haarcascade_frontalface_alt.xml"); 11 | ~FaceDetector(); 12 | vector detect(const IplImage *img); 13 | }; 14 | 15 | 16 | -------------------------------------------------------------------------------- /GtkStore.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "OutputMethods.h" 4 | #include "GraphicalPointer.h" 5 | 6 | 7 | class WindowStore: public AbstractStore { 8 | WindowPointer pointer, target; 9 | public: 10 | WindowStore(const WindowPointer::PointerSpec& pointerspec, 11 | const WindowPointer::PointerSpec& targetspec); 12 | virtual void store(const TrackerOutput &output); 13 | }; 14 | -------------------------------------------------------------------------------- /GtkStore.cpp: -------------------------------------------------------------------------------- 1 | #include "GtkStore.h" 2 | 3 | WindowStore::WindowStore(const WindowPointer::PointerSpec& pointerspec, 4 | const WindowPointer::PointerSpec& targetspec): 5 | pointer(pointerspec), target(targetspec) 6 | { 7 | } 8 | 9 | void WindowStore::store(const TrackerOutput &output) { 10 | pointer.setPosition((int) output.gazepoint.x, (int) output.gazepoint.y); 11 | target.setPosition((int) output.target.x, (int) output.target.y); 12 | } 13 | -------------------------------------------------------------------------------- /opengazer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "GazeTrackerGtk.h" 4 | #include "OutputMethods.h" 5 | #include "GtkStore.h" 6 | 7 | int main(int argc, char **argv) 8 | { 9 | Gtk::Main kit(argc, argv); 10 | Glib::thread_init(); 11 | 12 | // CalibrationWindow calwindow; 13 | // calwindow.show(); 14 | 15 | GazeTrackerGtk helloworld(argc, argv); 16 | 17 | helloworld.show(); 18 | 19 | Gtk::Main::run(helloworld); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /GazeTrackerGtk.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "GazeArea.h" 7 | 8 | class GazeTrackerGtk: public Gtk::Window { 9 | protected: 10 | //Member widgets: 11 | Gtk::Button calibratebutton, loadbutton, savebutton, clearbutton; 12 | Gtk::VBox vbox; 13 | Gtk::HBox buttonbar; 14 | 15 | public: 16 | GazeArea picture; 17 | GazeTrackerGtk(int argc, char **argv); 18 | virtual ~GazeTrackerGtk(); 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | void releaseImage(IplImage *image) { 4 | // cout << "deleting shared image" << endl; 5 | cvReleaseImage(&image); 6 | } 7 | 8 | shared_ptr createImage(const CvSize &size, int depth, int channels) { 9 | return shared_ptr(cvCreateImage(size, depth, channels), 10 | releaseImage); 11 | } 12 | 13 | namespace boost { 14 | template<> 15 | void checked_delete(IplImage *image) { 16 | // cout << "deleting scoped image" << endl; 17 | if (image) 18 | cvReleaseImage(&image); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /EyeExtractor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | #include "PointTracker.h" 4 | 5 | class EyeExtractor { 6 | const PointTracker &tracker; /* dangerous */ 7 | scoped_ptr eyefloat2; 8 | 9 | void processEye(void); 10 | 11 | public: 12 | static const int eyedx; 13 | static const int eyedy; 14 | static const CvSize eyesize; 15 | 16 | scoped_ptr eyegrey, eyefloat, eyeimage; 17 | 18 | EyeExtractor(const PointTracker &tracker); 19 | void extractEye(const IplImage *origimage) throw (TrackingException); 20 | ~EyeExtractor(void); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /GazeArea.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "MainGazeTracker.h" 4 | 5 | class GazeArea: public Gtk::DrawingArea { 6 | friend class GazeTrackerGtk; 7 | int lastPointId; 8 | int clickCount; 9 | public: 10 | MainGazeTracker gazetracker; 11 | 12 | GazeArea(int argc, char **argv, 13 | const vector > &stores); 14 | virtual ~GazeArea(); 15 | 16 | protected: 17 | virtual bool on_expose_event(GdkEventExpose *event); 18 | virtual bool on_button_press_event(GdkEventButton *event); 19 | virtual bool on_button_release_event(GdkEventButton *event); 20 | bool on_idle(); 21 | }; 22 | -------------------------------------------------------------------------------- /LeastSquares.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | typedef vnl_vector Vector; 6 | typedef vnl_matrix Matrix; 7 | 8 | class LeastSquares { 9 | Matrix X; 10 | Vector Y; 11 | public: 12 | const int n; 13 | 14 | LeastSquares(int n): X(n,n), Y(n), n(n) {} 15 | 16 | void addSample(double xs[], double y); 17 | void addSample(double x1, double x2, double y); 18 | void addSample(double x1, double x2, double x3, double y); 19 | 20 | Vector solve(void); 21 | void solve(double &a0, double &a1); 22 | void solve(double &a0, double &a1, double &a2); 23 | }; 24 | 25 | 26 | -------------------------------------------------------------------------------- /TrackingSystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | #include "PointTracker.h" 4 | #include "HeadTracker.h" 5 | #include "HeadCompensation.cpp" 6 | #include "EyeExtractor.h" 7 | #include "OutputMethods.h" 8 | #include "GazeTracker.h" 9 | 10 | struct TrackingSystem { 11 | PointTracker tracker; 12 | HeadTracker headtracker; 13 | HeadCompensation headcomp; 14 | EyeExtractor eyex; 15 | GazeTracker gazetracker; 16 | 17 | TrackingSystem(CvSize size); 18 | void doprocessing(const IplImage *frame, 19 | IplImage *image); 20 | void displayeye(IplImage *image, int basex, int basey, 21 | int stepx, int stepy); 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /HeadTracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "PointTracker.h" 6 | 7 | using namespace std; 8 | 9 | class HeadTracker { 10 | vector depths; 11 | 12 | vector detectinliers(vector const &prev, 13 | vector const &now, 14 | double radius = 30.0); 15 | 16 | void predictpoints(double xx0, double yy0, double xx1, double yy1, 17 | double rotx, double roty, double atx, double aty); 18 | 19 | public: 20 | PointTracker &tracker; 21 | 22 | double rotx, roty, atx, aty; 23 | 24 | void draw(IplImage *image); 25 | void updatetracker(void); 26 | 27 | HeadTracker(PointTracker &tracker) : tracker(tracker) {} 28 | }; 29 | -------------------------------------------------------------------------------- /Point.h: -------------------------------------------------------------------------------- 1 | struct Point { 2 | double x, y; 3 | 4 | Point(): x(0.0), y(0.0) {} 5 | Point(double x, double y): x(x), y(y) {} 6 | Point(CvPoint2D32f const& point): x(point.x), y(point.y) {} 7 | Point(CvPoint const& point): x(point.x), y(point.y) {} 8 | 9 | void operator=(CvPoint2D32f const& point); 10 | void operator=(CvPoint const& point); 11 | 12 | double distance(Point other) const; 13 | Point operator+(const Point &other) const; 14 | Point operator-(const Point &other) const; 15 | 16 | void save(CvFileStorage *out, const char* name) const; 17 | void load(CvFileStorage *in, CvFileNode *node); 18 | 19 | CvPoint cvpoint(void) const; 20 | CvPoint2D32f cvpoint32(void) const; 21 | 22 | int closestPoint(const vector &points) const; 23 | }; 24 | 25 | ostream& operator<< (ostream& out, const Point& p); 26 | istream& operator>> (istream& in, Point& p); 27 | void convert(const Point& point, CvPoint2D32f& p); 28 | 29 | -------------------------------------------------------------------------------- /HeadCompensation.cpp: -------------------------------------------------------------------------------- 1 | #include "LeastSquares.h" 2 | 3 | class HeadCompensation { 4 | LeastSquares xparams, yparams; 5 | const HeadTracker &head; 6 | 7 | double xa0, xa1, xa2, ya0, ya1, ya2; 8 | int samples; 9 | public: 10 | 11 | HeadCompensation(HeadTracker const& head): 12 | xparams(3), yparams(3), head(head), 13 | xa0(0.0), xa1(0.0), xa2(0.0), 14 | ya0(0.0), ya1(0.0), ya2(0.0), samples(0) 15 | {} 16 | 17 | void addCorrection(Point correction) { 18 | xparams.addSample(head.rotx, head.roty, 1.0, correction.x); 19 | yparams.addSample(head.rotx, head.roty, 1.0, correction.y); 20 | samples++; 21 | } 22 | 23 | void updateFactors(void) { 24 | if (samples > 0) { 25 | xparams.solve(xa0, xa1, xa2); 26 | yparams.solve(ya0, ya1, ya2); 27 | } 28 | } 29 | 30 | Point estimateCorrection(void) { 31 | return Point(xa0 * head.rotx + xa1 * head.roty + xa2, 32 | ya0 * head.rotx + ya1 * head.roty + ya2); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /OutputMethods.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | #include "GazeTracker.h" 4 | #include 5 | 6 | class AbstractStore { 7 | public: 8 | virtual void store(const TrackerOutput& output) = 0; 9 | virtual ~AbstractStore(); 10 | }; 11 | 12 | class MmapStore: public AbstractStore { 13 | int fd; 14 | int *positiontable; 15 | public: 16 | MmapStore(const char *filename="/tmp/gaze-mouse"); 17 | virtual void store(const TrackerOutput& output); 18 | virtual ~MmapStore(); 19 | }; 20 | 21 | class StreamStore: public AbstractStore { 22 | ostream &stream; 23 | public: 24 | StreamStore(ostream &stream); 25 | virtual void store(const TrackerOutput& output); 26 | virtual ~StreamStore(); 27 | }; 28 | 29 | class SocketStore: public AbstractStore { 30 | int mysocket; 31 | struct sockaddr_in destaddr; 32 | public: 33 | SocketStore(int port=20320); 34 | virtual void store(const TrackerOutput& output); 35 | virtual ~SocketStore(); 36 | }; 37 | -------------------------------------------------------------------------------- /LeastSquares.cpp: -------------------------------------------------------------------------------- 1 | #include "LeastSquares.h" 2 | #include 3 | #include 4 | 5 | void LeastSquares::addSample(double xs[], double y) { 6 | for(int i=0; i FeatureDetector::getMean() { 22 | shared_ptr mean(createImage(eyesize, IPL_DEPTH_32F, 1)); 23 | cvConvertScale(sumimage.get(), mean.get(), 1.0 / samples); 24 | return mean; 25 | } 26 | 27 | shared_ptr FeatureDetector::getVariance() { 28 | shared_ptr variance(createImage(eyesize, IPL_DEPTH_32F, 1)); 29 | cvMul(sumimage.get(), sumimage.get(), temp.get(), -1.0/samples); 30 | cvAdd(temp.get(), sum2image.get(), temp.get()); 31 | cvScale(temp.get(), variance.get(), 1.0/samples); 32 | return variance; 33 | } 34 | -------------------------------------------------------------------------------- /Alert.cpp: -------------------------------------------------------------------------------- 1 | #include "Alert.h" 2 | 3 | AlertWindow::AlertWindow(const string& text): text(text) { 4 | window.set_title(text); 5 | window.show(); 6 | } 7 | 8 | AlertWindow::~AlertWindow() {} 9 | 10 | NodeResult AlertWindow::handleEvent(StateEventType event) { 11 | StateNode::handleEvent(event); 12 | if (ticks >= 50) 13 | return NodeResult(shared_ptr 14 | (new AlertWindow(text + " x"))); 15 | else 16 | return NodeResult(); 17 | } 18 | 19 | 20 | // MessageNode::MessageNode(const shared_ptr& oknode, 21 | // const shared_ptr& cancelnode, 22 | // const string& text): 23 | // oknode(oknode), cancelnode(cancelnode), text(text) 24 | // { 25 | // } 26 | 27 | // shared_ptr MessageNode::handleEvent(StateEventType event) { 28 | // switch (event) { 29 | // case EVENT_2BLINK: return oknode; 30 | // case EVENT_TIMEOUT: return cancelnode; 31 | // case EVENT_DESELECTED: 32 | // alert = 0; 33 | // return StateNode::handleEvent(event); 34 | // case EVENT_SELECTED: 35 | // alert = scoped_ptr(new AlertWindow(text)); 36 | // return StateNode::handleEvent(event); 37 | // default: return StateNode::handleEvent(event); 38 | // } 39 | // } 40 | 41 | 42 | -------------------------------------------------------------------------------- /GraphicalPointer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Containers.h" 4 | 5 | /* represents the pointer as a small window and moves that window */ 6 | class WindowPointer { 7 | public: 8 | struct PointerSpec { 9 | int width, height; 10 | double red, green, blue; 11 | PointerSpec(int width, int height, 12 | double red, double green, double blue); 13 | }; 14 | private: 15 | class GtkPointerDrawingArea: public Gtk::DrawingArea { 16 | PointerSpec spec; 17 | public: 18 | GtkPointerDrawingArea(const PointerSpec &pointerspec); 19 | virtual bool on_expose_event(GdkEventExpose *event); 20 | }; 21 | class GtkPointerWindow: public Gtk::Window { 22 | GtkPointerDrawingArea area; 23 | public: 24 | GtkPointerWindow(const PointerSpec &pointerspec); 25 | }; 26 | GtkPointerWindow pointerwindow; 27 | public: 28 | WindowPointer(const PointerSpec &pointerspec); 29 | void setPosition(int x, int y); 30 | }; 31 | 32 | /* class PointerMark: public Gtk::DrawingArea { */ 33 | /* virtual bool on_expose_event(GdkEventExpose *event); */ 34 | /* }; */ 35 | 36 | /* class CalibrationMark: public Gtk::DrawingArea { */ 37 | /* virtual bool on_expose_event(GdkEventExpose *event); */ 38 | /* }; */ 39 | 40 | -------------------------------------------------------------------------------- /PointTracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "utils.h" 7 | 8 | using namespace std; 9 | typedef vgl_vector_2d HomPoint; 10 | 11 | class TrackingException: public exception {}; 12 | 13 | class PointTracker { 14 | public: 15 | static const int eyepoint1 = 0; 16 | static const int eyepoint2 = 1; 17 | vector status; 18 | vector origpoints, currentpoints, lastpoints; 19 | 20 | private: 21 | static const int win_size = 11; 22 | int flags; 23 | 24 | scoped_ptr grey, orig_grey, pyramid, orig_pyramid, 25 | last_grey, last_pyramid; 26 | 27 | void synchronizepoints(); 28 | 29 | public: 30 | PointTracker(const CvSize &size); 31 | void cleartrackers(); 32 | void addtracker(const Point &point); 33 | void updatetracker(int id, const Point &point); 34 | void removetracker(int id); 35 | int getClosestTracker(const Point &point); 36 | void track(const IplImage *frame, int pyramiddepth=1); 37 | int countactivepoints(void); 38 | bool areallpointsactive(void); 39 | int pointcount(); 40 | void draw(IplImage *canvas); 41 | 42 | vector 43 | getpoints(const vector PointTracker::*points, 44 | bool allpoints=true); 45 | 46 | void save(string filename, string newname, const IplImage *frame); 47 | void load(string filename, string newname, const IplImage *frame); 48 | }; 49 | -------------------------------------------------------------------------------- /MainGazeTracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | #include "TrackingSystem.h" 4 | //#include "Alert.h" 5 | #include "Calibrator.h" 6 | #include 7 | #include 8 | 9 | struct CommandLineArguments { 10 | vector parameters; 11 | vector options; 12 | 13 | CommandLineArguments(int argc, char **argv); 14 | bool isoption(const char* option); 15 | }; 16 | 17 | 18 | 19 | class VideoInput { 20 | CvCapture* capture; 21 | 22 | public: 23 | int framecount; 24 | const IplImage* frame; 25 | const CvSize size; 26 | VideoInput(); 27 | VideoInput(const char* avifile); 28 | ~VideoInput(); 29 | void updateFrame(); 30 | }; 31 | 32 | class VideoWriter; 33 | /* class FileInput; */ 34 | 35 | class MainGazeTracker { 36 | scoped_ptr video; 37 | int framestoreload; 38 | vector > stores; 39 | int framecount; 40 | bool autoreload; 41 | // StateMachine statemachine; 42 | 43 | public: 44 | shared_ptr tracking; 45 | 46 | FrameProcessing framefunctions; 47 | scoped_ptr canvas; 48 | scoped_ptr videoinput; 49 | 50 | MainGazeTracker(int argc, char** argv, 51 | const vector > &stores); 52 | void doprocessing(void); 53 | ~MainGazeTracker(void); 54 | void addTracker(Point point); 55 | void addExemplar(Point exemplar); 56 | void startCalibration(); 57 | void startTesting(); 58 | void savepoints(); 59 | void loadpoints(); 60 | void clearpoints(); 61 | }; 62 | -------------------------------------------------------------------------------- /FMatrixAffineCompute.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | 5 | T sum(vector const& vector) { 6 | T sum = vector[0]; 7 | 8 | for(int i=1; i const& points1, 15 | vector const& points2) 16 | { 17 | assert(points1.size() == points2.size()); 18 | 19 | int n = points1.size(); 20 | 21 | Vector centroid(4, 0.0); 22 | 23 | for(int i=0; i svd(matrix); 44 | 45 | // cout << "U = " << endl << svd.U() << endl << endl; 46 | // cout << "W = " << endl << svd.W() << endl << endl; 47 | // cout << "V = " << endl << svd.V() << endl << endl; 48 | 49 | Vector v = svd.V().get_column(3); 50 | 51 | Vector result(5); 52 | 53 | result.update(v); // a,b,c,d 54 | result[4] = inner_product(v, centroid); 55 | 56 | if (result[4] < 0) 57 | result = -result; 58 | 59 | 60 | 61 | // cout << "mat: " << v << endl; 62 | // cout << "test: " << matrix * v << endl; 63 | 64 | return result.normalize(); 65 | } 66 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # required libraries: vxl, opencv, boost, gtkmm 2 | 3 | VXLDIR = /opt 4 | VERSION = opengazer-0.1.2 5 | CPPFLAGS = -Wall -g -O3 6 | LINKER = -L$(VXLDIR)/lib -L/usr/local/lib -lm -ldl -lvnl -lmvl -lvnl_algo -lvgl -lgthread-2.0 7 | 8 | # change the following line if your vxl library is installed elsewhere 9 | INCLUDES = $(foreach prefix,/usr/local/include $(VXLDIR)/include $(VXLDIR)/include/vxl, \ 10 | $(foreach suffix,/core /vcl /contrib/oxl,-I$(prefix)$(suffix))) 11 | 12 | # -I/usr/include/ffmpeg 13 | # -lcv0.9.7 -lhighgui0.9.7 14 | # -lvgui 15 | 16 | sources = opengazer.cpp Calibrator.cpp GazeTrackerGtk.cpp HeadTracker.cpp LeastSquares.cpp EyeExtractor.cpp GazeTracker.cpp MainGazeTracker.cpp OutputMethods.cpp PointTracker.cpp FaceDetector.cpp GazeArea.cpp TrackingSystem.cpp GtkStore.cpp Containers.cpp GraphicalPointer.cpp Point.cpp utils.cpp BlinkDetector.cpp FeatureDetector.cpp Alert.cpp 17 | 18 | objects = $(patsubst %.cpp,%.o,$(sources)) 19 | 20 | %.o.depends: %.cpp 21 | g++ -MM $< > $@ 22 | 23 | %.o: %.cpp 24 | g++ -c $(CPPFLAGS) -o $@ `pkg-config cairomm-1.0 opencv gtkmm-2.4 --cflags` $(INCLUDES) $< 25 | 26 | opengazer: $(objects) 27 | g++ $(CPPFLAGS) -o $@ `pkg-config cairomm-1.0 opencv gtkmm-2.4 --libs` $(LINKER) $^ 28 | 29 | include $(patsubst %.cpp,%.o.depends,$(sources)) 30 | 31 | TAGS: always 32 | etags *.cpp *.h 33 | 34 | ship: always 35 | mkdir $(VERSION) || rm -fr $(VERSION)/* 36 | cp LICENSE README Makefile calpoints.txt haarcascade_frontalface_alt.xml $$(for file in $$(cat $(addsuffix .depends,$(objects))) ; do test -f $$file && echo $$file ; done | sort -u) $(VERSION)/ 37 | tar czf $(VERSION).tar.gz $(VERSION) 38 | cp $(VERSION).tar.gz README /home/ftp/pub/www/opengazer/ 39 | 40 | .PHONY: always ship -------------------------------------------------------------------------------- /FaceDetector.cpp: -------------------------------------------------------------------------------- 1 | #include "FaceDetector.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | FaceDetector FaceDetector::facedetector; 14 | 15 | FaceDetector::FaceDetector(char *cascadename): 16 | cascade((CvHaarClassifierCascade*)cvLoad(cascadename, 0, 0, 0)), 17 | storage(cvCreateMemStorage(0)) 18 | {} 19 | 20 | 21 | FaceDetector::~FaceDetector() { 22 | cvReleaseMemStorage(&storage); 23 | // fixme: release the cascade somehow 24 | } 25 | 26 | vector FaceDetector::detect(const IplImage *img) { 27 | double scale = 1.3; 28 | IplImage* gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 ); 29 | IplImage* small_img = 30 | cvCreateImage( cvSize( cvRound (img->width/scale), 31 | cvRound (img->height/scale)), 8, 1 ); 32 | int i; 33 | 34 | cvCvtColor( img, gray, CV_BGR2GRAY ); 35 | cvResize( gray, small_img, CV_INTER_LINEAR ); 36 | cvEqualizeHist( small_img, small_img ); 37 | cvClearMemStorage( storage ); 38 | 39 | CvSeq* faces = 40 | cvHaarDetectObjects( small_img, cascade, storage, 41 | 1.1, 2, 0/*CV_HAAR_DO_CANNY_PRUNING*/, 42 | cvSize(30, 30) ); 43 | 44 | vector result; 45 | for( i = 0; i < (faces ? faces->total : 0); i++ ) { 46 | CvRect *rect = (CvRect*)cvGetSeqElem( faces, i ); 47 | result.push_back(cvRect((int)(rect->x * scale), 48 | (int)(rect->y * scale), 49 | (int)(rect->width * scale), 50 | (int)(rect->height * scale))); 51 | } 52 | 53 | cvReleaseImage(&gray); 54 | cvReleaseImage(&small_img); 55 | // cvReleaseSeq(&faces); 56 | 57 | return result; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /Containers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | #include 4 | #include 5 | 6 | #define xforeachactive(iter,container) \ 7 | for(typeof(container.begin()) iter = container.begin(); \ 8 | iter != container.end(); iter++) \ 9 | if ((*iter)->parent == this) 10 | 11 | template class Container; 12 | 13 | template 14 | class Containee { 15 | protected: 16 | void detach() { parent = 0; } 17 | public: 18 | ParentType *parent; /* set to null to request removal */ 19 | Containee(): parent(0) {} 20 | virtual ~Containee() {} 21 | }; 22 | 23 | template 24 | class Container { 25 | typedef shared_ptr ChildPtr; 26 | static bool isFinished(const ChildPtr &object) { 27 | return !(object && object->parent); 28 | } 29 | protected: 30 | std::vector objects; 31 | 32 | void removeFinished() { 33 | objects.erase(remove_if(objects.begin(), objects.end(), isFinished), 34 | objects.end()); 35 | } 36 | 37 | public: 38 | void clear() { 39 | xforeachactive(iter, objects) 40 | (*iter)->parent = 0; 41 | removeFinished(); 42 | } 43 | 44 | 45 | static void addchild(ParentType *parent, const ChildPtr &child) { 46 | parent->objects.push_back(child); 47 | child->parent = parent; 48 | parent->removeFinished(); 49 | } 50 | 51 | virtual ~Container() { 52 | clear(); 53 | } 54 | }; 55 | 56 | template 57 | class ProcessContainer: public Container { 58 | public: 59 | virtual void process() { 60 | xforeachactive(iter, this->objects) 61 | (*iter)->process(); 62 | this->removeFinished(); 63 | } 64 | virtual ~ProcessContainer() {}; 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /GraphicalPointer.cpp: -------------------------------------------------------------------------------- 1 | #include "GraphicalPointer.h" 2 | #include 3 | 4 | WindowPointer::PointerSpec::PointerSpec(int width, int height, 5 | double red, double green, double blue): 6 | width(width), height(height), red(red), green(green), blue(blue) 7 | { 8 | } 9 | 10 | WindowPointer::GtkPointerDrawingArea:: 11 | GtkPointerDrawingArea(const PointerSpec &pointerspec): 12 | spec(pointerspec) 13 | { 14 | set_size_request(spec.width, spec.height); 15 | } 16 | 17 | bool WindowPointer::GtkPointerDrawingArea:: 18 | on_expose_event(GdkEventExpose *event) 19 | { 20 | Glib::RefPtr window = get_window(); 21 | if (window) { 22 | Cairo::RefPtr cr(new Cairo::Context(gdk_cairo_create(window->gobj()), true)); 23 | if (event) { 24 | cr->rectangle(event->area.x, event->area.y, 25 | event->area.width, event->area.height); 26 | cr->clip(); 27 | } 28 | cr->set_source_rgb(1.0, 1.0, 1.0); 29 | cr->paint(); 30 | cr->set_source_rgb(spec.red, spec.green, spec.blue); 31 | cr->arc(get_width()/2, get_height()/2, get_width()/3, 0, 2*M_PI); 32 | cr->fill(); 33 | // Glib::RefPtr gc = Gdk::GC::create(window); 34 | // color.set_blue(100); 35 | // gc->set_foreground(color); 36 | // window->draw_arc(gc, true, 0, 0, get_width(), get_height(), 0, 360*64); 37 | } 38 | return false; 39 | } 40 | 41 | WindowPointer::GtkPointerWindow:: 42 | GtkPointerWindow(const PointerSpec &pointerspec): 43 | area(pointerspec) 44 | { 45 | add(area); 46 | area.show(); 47 | set_decorated(false); 48 | set_keep_above(true); 49 | } 50 | 51 | WindowPointer::WindowPointer(const PointerSpec &pointerspec): 52 | pointerwindow(pointerspec) 53 | { 54 | pointerwindow.show(); 55 | } 56 | 57 | void WindowPointer::setPosition(int x, int y) { 58 | pointerwindow.move(x, y); 59 | } 60 | -------------------------------------------------------------------------------- /GazeTracker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | #include "GaussianProcess.cpp" 4 | 5 | typedef MeanAdjustedGaussianProcess ImProcess; 6 | 7 | struct Targets { 8 | vector targets; 9 | 10 | Targets(void) {}; 11 | Targets(vector const& targets): targets(targets) {} 12 | int getCurrentTarget(Point point); 13 | }; 14 | 15 | struct CalTarget { 16 | Point point; 17 | SharedImage image, origimage; 18 | 19 | CalTarget(); 20 | CalTarget(Point point, const IplImage* image, const IplImage* origimage); 21 | 22 | void save(CvFileStorage* out, const char* name=NULL); 23 | void load(CvFileStorage* in, CvFileNode *node); 24 | }; 25 | 26 | struct TrackerOutput { 27 | Point gazepoint; 28 | Point target; 29 | int targetid; 30 | 31 | TrackerOutput(Point gazepoint, Point target, int targetid); 32 | }; 33 | 34 | class GazeTracker { 35 | scoped_ptr gpx, gpy; 36 | vector caltargets; 37 | scoped_ptr targets; 38 | 39 | static double imagedistance(const IplImage *im1, const IplImage *im2); 40 | static double covariancefunction(const SharedImage& im1, 41 | const SharedImage& im2); 42 | 43 | void updateGPs(void); 44 | 45 | public: 46 | TrackerOutput output; 47 | 48 | GazeTracker(): targets(new Targets), 49 | output(Point(0,0), Point(0,0), -1) {} 50 | 51 | bool isActive() { return gpx.get() && gpy.get(); } 52 | 53 | void clear(); 54 | void addExemplar(Point point, 55 | const IplImage *eyefloat, const IplImage *eyegrey); 56 | void draw(IplImage *canvas, int eyedx, int eyedy); 57 | void save(void); 58 | void save(CvFileStorage *out, const char *name); 59 | void load(void); 60 | void load(CvFileStorage *in, CvFileNode *node); 61 | void update(const IplImage *image); 62 | int getTargetId(Point point); 63 | Point getTarget(int id); 64 | }; 65 | -------------------------------------------------------------------------------- /Point.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | void convert(const Point& point, CvPoint2D32f& p) { 4 | p.x = point.x; 5 | p.y = point.y; 6 | } 7 | 8 | ostream& operator<< (ostream& out, const Point& p) { 9 | out << p.x << " " << p.y << endl; 10 | return out; 11 | } 12 | 13 | istream& operator>> (istream& in, Point& p) { 14 | in >> p.x >> p.y; 15 | return in; 16 | } 17 | 18 | void Point::operator=(CvPoint2D32f const& point) { 19 | x = point.x; 20 | y = point.y; 21 | } 22 | 23 | void Point::operator=(CvPoint const& point) { 24 | x = point.x; 25 | y = point.y; 26 | } 27 | 28 | 29 | double Point::distance(Point other) const { 30 | return fabs(other.x - x) + fabs(other.y - y); 31 | } 32 | 33 | Point Point::operator+(const Point &other) const { 34 | return Point(x + other.x, y + other.y); 35 | } 36 | 37 | Point Point::operator-(const Point &other) const { 38 | return Point(x - other.x, y - other.y); 39 | } 40 | 41 | void Point::save(CvFileStorage *out, const char* name) const { 42 | cvStartWriteStruct(out, name, CV_NODE_MAP); 43 | cvWriteReal(out, "x", x); 44 | cvWriteReal(out, "y", y); 45 | cvEndWriteStruct(out); 46 | } 47 | 48 | void Point::load(CvFileStorage *in, CvFileNode *node) { 49 | x = cvReadRealByName(in, node, "x"); 50 | y = cvReadRealByName(in, node, "y"); 51 | } 52 | 53 | CvPoint Point::cvpoint(void) const { 54 | return cvPoint(cvRound(x), cvRound(y)); 55 | } 56 | 57 | CvPoint2D32f Point::cvpoint32(void) const { 58 | return cvPoint2D32f(x, y); 59 | } 60 | 61 | int Point::closestPoint(const vector &points) const { 62 | if (points.empty()) 63 | return -1; 64 | 65 | vector distances(points.size()); 66 | transform(points.begin(), points.end(), distances.begin(), 67 | sigc::mem_fun(*this, &Point::distance)); 68 | return min_element(distances.begin(), distances.end()) - distances.begin(); 69 | } 70 | -------------------------------------------------------------------------------- /Calibrator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "utils.h" 5 | #include "TrackingSystem.h" 6 | #include "Containers.h" 7 | #include "GraphicalPointer.h" 8 | #include "FeatureDetector.h" 9 | 10 | class FrameProcessing; 11 | 12 | class FrameFunction: 13 | public Containee 14 | { 15 | const int &frameno; 16 | int startframe; 17 | protected: 18 | int getFrame() { return frameno - startframe; } 19 | public: 20 | FrameFunction(const int &frameno): frameno(frameno), startframe(frameno) {} 21 | virtual void process()=0; 22 | virtual ~FrameFunction(); 23 | }; 24 | 25 | class FrameProcessing: 26 | public ProcessContainer {}; 27 | 28 | class MovingTarget: public FrameFunction { 29 | shared_ptr pointer; 30 | public: 31 | MovingTarget(const int &frameno, 32 | const vector& points, 33 | const shared_ptr &pointer, 34 | int dwelltime=20); 35 | virtual ~MovingTarget(); 36 | virtual void process(); 37 | protected: 38 | vector points; 39 | const int dwelltime; 40 | int getPointNo(); 41 | int getPointFrame(); 42 | bool active(); 43 | }; 44 | 45 | class Calibrator: public MovingTarget { 46 | static const Point defaultpointarr[]; 47 | shared_ptr trackingsystem; 48 | scoped_ptr averageeye; 49 | public: 50 | static vector defaultpoints; 51 | static vector loadpoints(istream& in); 52 | Calibrator(const int &frameno, 53 | const shared_ptr &trackingsystem, 54 | const vector& points, 55 | const shared_ptr &pointer, 56 | int dwelltime=20); 57 | virtual ~Calibrator(); 58 | virtual void process(); 59 | static vector scaled(const vector& points, double x, double y); 60 | }; 61 | 62 | 63 | -------------------------------------------------------------------------------- /EyeExtractor.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include "EyeExtractor.h" 3 | 4 | const int EyeExtractor::eyedx = 32; 5 | const int EyeExtractor::eyedy = 16; 6 | const CvSize EyeExtractor::eyesize = cvSize(eyedx*2, eyedy*2); 7 | 8 | void EyeExtractor::processEye(void) { 9 | cvConvertScale(eyegrey.get(), eyefloat2.get()); 10 | // todo: equalize it somehow first! 11 | cvSmooth(eyefloat2.get(), eyefloat.get(), CV_GAUSSIAN, 3); 12 | cvEqualizeHist(eyegrey.get(), eyegrey.get()); 13 | } 14 | 15 | 16 | EyeExtractor::EyeExtractor(const PointTracker &tracker): 17 | tracker(tracker), 18 | eyefloat2(cvCreateImage( eyesize, IPL_DEPTH_32F, 1 )), 19 | eyegrey(cvCreateImage( eyesize, 8, 1 )), 20 | eyefloat(cvCreateImage( eyesize, IPL_DEPTH_32F, 1 )), 21 | eyeimage(cvCreateImage( eyesize, 8, 3 )) 22 | { 23 | } 24 | 25 | void EyeExtractor::extractEye(const IplImage *origimage) 26 | throw (TrackingException) 27 | { 28 | if (!tracker.status[tracker.eyepoint1]) 29 | throw TrackingException(); 30 | 31 | double x0 = tracker.currentpoints[tracker.eyepoint1].x; 32 | double y0 = tracker.currentpoints[tracker.eyepoint1].y; 33 | double x1 = tracker.currentpoints[tracker.eyepoint2].x; 34 | double y1 = tracker.currentpoints[tracker.eyepoint2].y; 35 | double factor = 0.17; 36 | double xfactor = 0.05; 37 | double yfactor = 0.20 * (x0 < x1 ? -1 : 1); 38 | double L = factor / eyedx; 39 | double LL = x0 < x1? L : -L; 40 | float matrix[6] = 41 | {LL*(x1-x0), LL*(y0-y1), 42 | x0 + factor * ((1-xfactor)*(x1-x0) + yfactor * (y0-y1)), 43 | LL*(y1-y0), LL*(x1-x0), 44 | y0 + factor * ((1-xfactor)*(y1-y0) + yfactor * (x1-x0))}; 45 | CvMat M = cvMat( 2, 3, CV_32F, matrix ); 46 | 47 | cvGetQuadrangleSubPix( origimage, eyeimage.get(), &M); 48 | cvCvtColor(eyeimage.get(), eyegrey.get(), CV_RGB2GRAY); 49 | 50 | processEye(); 51 | } 52 | 53 | EyeExtractor::~EyeExtractor(void) { 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /OutputMethods.cpp: -------------------------------------------------------------------------------- 1 | #include "OutputMethods.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | AbstractStore::~AbstractStore() { 13 | } 14 | 15 | MmapStore::MmapStore(const char *filename) { 16 | fd = open(filename, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); 17 | write(fd, &fd, sizeof(fd)); 18 | write(fd, &fd, sizeof(fd)); 19 | if (fd < 0) {perror("open");return;} 20 | positiontable = (int*) mmap(0, getpagesize(), PROT_READ | PROT_WRITE, 21 | MAP_SHARED, fd, 0); 22 | } 23 | 24 | void MmapStore::store(const TrackerOutput& output) { 25 | positiontable[0] = (int) output.gazepoint.x - 320; 26 | positiontable[1] = (int) output.gazepoint.y - 240; 27 | } 28 | 29 | MmapStore::~MmapStore() { 30 | munmap(positiontable, getpagesize()); 31 | close(fd); 32 | } 33 | 34 | StreamStore::StreamStore(ostream &stream): stream(stream) { 35 | } 36 | 37 | StreamStore::~StreamStore() { 38 | } 39 | 40 | void StreamStore::store(const TrackerOutput& output) { 41 | stream << (int) output.gazepoint.x << " " 42 | << (int) output.gazepoint.y << " -> " 43 | << output.targetid << endl; 44 | stream.flush(); 45 | } 46 | 47 | SocketStore::SocketStore(int port) { 48 | mysocket = socket(PF_INET, SOCK_DGRAM, 0); 49 | 50 | destaddr.sin_family = AF_INET; 51 | destaddr.sin_port = htons(port); 52 | destaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 53 | } 54 | 55 | void SocketStore::store(const TrackerOutput& output) { 56 | ostringstream stream; 57 | stream << "x " << (int) output.gazepoint.x << endl 58 | << "y " << (int) output.gazepoint.y << endl; 59 | string str = stream.str(); 60 | sendto(mysocket, str.c_str(), str.size(), 0, 61 | (sockaddr*)&destaddr, sizeof(destaddr)); 62 | } 63 | 64 | SocketStore::~SocketStore(void) { 65 | close(mysocket); 66 | } 67 | 68 | -------------------------------------------------------------------------------- /GazeTrackerGtk.cpp: -------------------------------------------------------------------------------- 1 | #include "GazeTrackerGtk.h" 2 | #include 3 | #include 4 | #include "GtkStore.h" 5 | 6 | static vector > getStores() { 7 | vector > stores; 8 | 9 | stores.push_back(shared_ptr(new SocketStore())); 10 | stores.push_back(shared_ptr(new StreamStore(cout))); 11 | stores.push_back(shared_ptr 12 | (new WindowStore(WindowPointer::PointerSpec(20, 20, 0, 0, 1), 13 | WindowPointer::PointerSpec(30, 30, 1, 0, 1)))); 14 | 15 | return stores; 16 | } 17 | 18 | GazeTrackerGtk::GazeTrackerGtk(int argc, char **argv): 19 | calibratebutton("Calibrate"), 20 | loadbutton("Load points"), 21 | savebutton("Save points"), 22 | clearbutton("Clear points"), 23 | picture(argc, argv, getStores()) 24 | { 25 | set_title("opengazer 0.1.1"); 26 | 27 | add(vbox); 28 | vbox.pack_start(picture); 29 | vbox.pack_start(buttonbar); 30 | 31 | buttonbar.pack_start(calibratebutton); 32 | Gtk::Button *testbutton = manage(new Gtk::Button("Test")); 33 | buttonbar.pack_start(*testbutton); 34 | buttonbar.pack_start(savebutton); 35 | buttonbar.pack_start(loadbutton); 36 | buttonbar.pack_start(clearbutton); 37 | 38 | calibratebutton.signal_clicked(). 39 | connect(sigc::mem_fun(&picture.gazetracker, 40 | &MainGazeTracker::startCalibration)); 41 | testbutton->signal_clicked(). 42 | connect(sigc::mem_fun(&picture.gazetracker, 43 | &MainGazeTracker::startTesting)); 44 | savebutton.signal_clicked(). 45 | connect(sigc::mem_fun(&picture.gazetracker, 46 | &MainGazeTracker::savepoints)); 47 | loadbutton.signal_clicked(). 48 | connect(sigc::mem_fun(&picture.gazetracker, 49 | &MainGazeTracker::loadpoints)); 50 | clearbutton.signal_clicked(). 51 | connect(sigc::mem_fun(&picture.gazetracker, 52 | &MainGazeTracker::clearpoints)); 53 | 54 | picture.show(); 55 | testbutton->show(); 56 | calibratebutton.show(); 57 | savebutton.show(); 58 | loadbutton.show(); 59 | clearbutton.show(); 60 | buttonbar.show(); 61 | vbox.show(); 62 | } 63 | 64 | 65 | GazeTrackerGtk::~GazeTrackerGtk() { 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /GaussianProcess.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class MultiGaussian { 4 | public: 5 | Vector mean; 6 | Matrix covariance; 7 | 8 | MultiGaussian(Vector const& mean, Matrix const& covariance): 9 | mean(mean), covariance(covariance) {} 10 | }; 11 | 12 | template 13 | class GaussianProcess { 14 | public: 15 | typedef double (*CovarianceFunction)(const T&, const T&); 16 | private: 17 | vector inputs; 18 | CovarianceFunction covariancefunction; 19 | private: 20 | Matrix L; 21 | Vector alpha; 22 | 23 | Matrix getcovariancematrix(vector const& in1, 24 | vector const& in2) const 25 | { 26 | Matrix K(in1.size(), in2.size()); 27 | 28 | for(int i=0; i const& inputs, 37 | Vector const& targets, 38 | CovarianceFunction covariancefunction, 39 | double noise=0.0): 40 | inputs(inputs), covariancefunction(covariancefunction) 41 | { 42 | Matrix K = getcovariancematrix(inputs, inputs); 43 | for(int i=0; i const &tests) const { 52 | Matrix KK = getcovariancematrix(inputs, tests); 53 | return KK.transpose() * alpha; 54 | } 55 | 56 | double getmean(T const& test) const { 57 | return getmeans(vector(1, test))[0]; 58 | } 59 | }; 60 | 61 | template 62 | class MeanAdjustedGaussianProcess { 63 | double mean; 64 | const GaussianProcess gp; 65 | 66 | public: 67 | MeanAdjustedGaussianProcess(vector const& inputs, 68 | Vector const& targets, 69 | typename GaussianProcess::CovarianceFunction 70 | covariancefunction, 71 | double noise=0.0): 72 | mean(targets.mean()), 73 | gp(GaussianProcess(inputs, targets - mean, 74 | covariancefunction, noise)) 75 | {} 76 | 77 | double getmean(T const& test) const { 78 | return gp.getmean(test) + mean; 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /BlinkDetector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | 4 | using namespace std; 5 | using namespace boost; 6 | 7 | struct StateNode { 8 | int minduration, maxduration; 9 | double threshold; 10 | StateNode(int minduration, int maxduration, double threshold); 11 | bool isSatisfied(double value) const; 12 | }; 13 | 14 | class LinearStateSystem { 15 | const vector states; 16 | int currentState, duration; 17 | public: 18 | LinearStateSystem(const vector &states); 19 | void updateState(double value); 20 | void setState(int state); 21 | int getState(); 22 | bool isFinalState(); 23 | }; 24 | 25 | class LambdaAccumulator { 26 | const double lambda; 27 | double current; 28 | public: 29 | LambdaAccumulator(double lambda, double initial); 30 | void update(double value); 31 | double getValue(); 32 | }; 33 | 34 | class BlinkDetector { 35 | scoped_ptr averageEye; 36 | LambdaAccumulator acc; 37 | LinearStateSystem states; 38 | static vector constructStates(); 39 | public: 40 | BlinkDetector(); 41 | void update(const scoped_ptr &image); 42 | int getState(); 43 | }; 44 | 45 | 46 | 47 | /* template */ 48 | /* class TwoClassDetector { */ 49 | /* const T class0, class1; */ 50 | /* public: */ 51 | /* TwoClassDetector(const T& class0, const T& class1): */ 52 | /* class0(class0), class1(class1) {} */ 53 | 54 | /* double classify(const T& object) { */ 55 | /* double dist0 = distance(class0, object); */ 56 | /* double dist1 = distance(class0, object); */ 57 | /* return dist0 / (dist0 + dist1); */ 58 | /* }; */ 59 | /* }; */ 60 | 61 | /* template */ 62 | /* class Accumulator { */ 63 | /* T accumulator; */ 64 | /* Func func; */ 65 | /* public: */ 66 | /* Accumulator(const T& initial, const Func& func): */ 67 | /* accumulator(initial), func(func) */ 68 | /* {}; */ 69 | /* void update(const T& object) { */ 70 | /* accumulator = func(accumulator, object); */ 71 | /* } */ 72 | /* T get() { */ 73 | /* return accumulator; */ 74 | /* } */ 75 | /* }; */ 76 | 77 | /* template */ 78 | /* class GetFurtherObject { */ 79 | /* T referenceobject; */ 80 | /* public: */ 81 | /* T operator()(const T& one, const T& two) { */ 82 | /* if (distance(referenceobject, one) > distance(referenceobject, two)) */ 83 | /* return one; */ 84 | /* else */ 85 | /* return two; */ 86 | /* } */ 87 | /* }; */ 88 | 89 | /* Accumulator > */ 90 | /* blink(currentimage, GetFurtherObject(average)); */ 91 | 92 | /* typedef TwoClassDetector > BlinkDetector(average, blink.get()); */ 93 | -------------------------------------------------------------------------------- /GazeArea.cpp: -------------------------------------------------------------------------------- 1 | #include "GazeArea.h" 2 | #include "OutputMethods.h" 3 | #include 4 | 5 | GazeArea::GazeArea(int argc, char **argv, 6 | const vector > &stores): 7 | lastPointId(-1), gazetracker(argc, argv, stores) 8 | { 9 | set_size_request(gazetracker.canvas->width, gazetracker.canvas->height); 10 | Glib::signal_idle().connect(sigc::mem_fun(*this, &GazeArea::on_idle)); 11 | add_events(Gdk::BUTTON_PRESS_MASK); 12 | add_events(Gdk::BUTTON_RELEASE_MASK); 13 | } 14 | 15 | GazeArea::~GazeArea(void) {} 16 | 17 | bool GazeArea::on_idle() { 18 | gazetracker.doprocessing(); 19 | queue_draw(); 20 | return true; 21 | } 22 | 23 | bool GazeArea::on_expose_event(GdkEventExpose *event) { 24 | Glib::RefPtr window = get_window(); 25 | if (window) { 26 | Gtk::Allocation allocation = get_allocation(); 27 | const int width = allocation.get_width(); 28 | const int height = allocation.get_height(); 29 | 30 | Glib::RefPtr gc = Gdk::GC::create(window); 31 | const IplImage *image = gazetracker.canvas.get(); 32 | Glib::RefPtr pixbuf = 33 | Gdk::Pixbuf::create_from_data((guint8*) image->imageData, 34 | Gdk::COLORSPACE_RGB, 35 | false, 36 | image->depth, 37 | image->width, 38 | image->height, 39 | image->widthStep); 40 | window->draw_pixbuf(gc, pixbuf, 0,0,0,0, width, height, 41 | Gdk::RGB_DITHER_NONE, 0, 0); 42 | } 43 | return true; 44 | } 45 | 46 | bool GazeArea::on_button_press_event(GdkEventButton *event) { 47 | if (event->button == 1) { 48 | switch(event->type) { 49 | case GDK_BUTTON_PRESS: clickCount = 1; break; 50 | case GDK_2BUTTON_PRESS: clickCount = 2; break; 51 | case GDK_3BUTTON_PRESS: clickCount = 3; break; 52 | default: break; 53 | } 54 | 55 | if (event->type == GDK_BUTTON_PRESS) { 56 | Point point(event->x, event->y); 57 | PointTracker &tracker = gazetracker.tracking->tracker; 58 | int closest = tracker.getClosestTracker(point); 59 | if (closest >= 0 && 60 | point.distance(tracker.currentpoints[closest]) <= 10) 61 | lastPointId = closest; 62 | else 63 | lastPointId = -1; 64 | } 65 | return true; 66 | } 67 | else 68 | return false; 69 | } 70 | 71 | bool GazeArea::on_button_release_event(GdkEventButton *event) { 72 | if (event->button == 1) { 73 | PointTracker &tracker = gazetracker.tracking->tracker; 74 | Point point(event->x, event->y); 75 | if (lastPointId >= 0) 76 | switch(clickCount) { 77 | case 1: tracker.updatetracker(lastPointId, point); break; 78 | case 2: tracker.removetracker(lastPointId); break; 79 | } 80 | else 81 | tracker.addtracker(point); 82 | return true; 83 | } 84 | else 85 | return false; 86 | } 87 | -------------------------------------------------------------------------------- /BlinkDetector.cpp: -------------------------------------------------------------------------------- 1 | #include "BlinkDetector.h" 2 | #include 3 | #include "EyeExtractor.h" 4 | #include "utils.h" 5 | 6 | StateNode::StateNode(int minduration, int maxduration, double threshold): 7 | minduration(minduration), maxduration(maxduration), threshold(threshold) 8 | { 9 | } 10 | 11 | bool StateNode::isSatisfied(double value) const { 12 | if (threshold > 0) 13 | return value >= threshold; 14 | else 15 | return value <= -threshold; 16 | } 17 | 18 | LinearStateSystem::LinearStateSystem(const vector &states): 19 | states(states), currentState(0), duration(0) 20 | { 21 | assert(!states.empty()); 22 | } 23 | 24 | void LinearStateSystem::updateState(double value) { 25 | cout << "update state: " << value << endl; 26 | duration++; 27 | if (currentState > 0 && 28 | ((duration < states[currentState].minduration && 29 | !states[currentState].isSatisfied(value)) || 30 | duration > states[currentState].maxduration)) 31 | setState(0); 32 | 33 | if (!isFinalState() && states[currentState+1].isSatisfied(value)) 34 | setState(currentState+1); 35 | } 36 | 37 | void LinearStateSystem::setState(int state) { 38 | currentState = state; 39 | duration = 0; 40 | } 41 | 42 | int LinearStateSystem::getState() { 43 | return currentState; 44 | } 45 | 46 | bool LinearStateSystem::isFinalState() { 47 | return currentState == (int)states.size() - 1; 48 | } 49 | 50 | LambdaAccumulator::LambdaAccumulator(double lambda, double initial): 51 | lambda(lambda), current(initial) 52 | { 53 | } 54 | 55 | void LambdaAccumulator::update(double value) { 56 | current = (1-lambda)*current + lambda*value; 57 | } 58 | 59 | double LambdaAccumulator::getValue() { 60 | return current; 61 | } 62 | 63 | BlinkDetector::BlinkDetector(): 64 | averageEye(cvCreateImage(EyeExtractor::eyesize, IPL_DEPTH_32F, 1)), 65 | acc(0.1, 1000.0), 66 | states(constructStates()) 67 | { 68 | } 69 | 70 | vector BlinkDetector::constructStates() { 71 | vector states; 72 | states.push_back(StateNode(0, 0, +0.00)); 73 | states.push_back(StateNode(1, 8, +1.50)); 74 | states.push_back(StateNode(1, 8, -1.20)); // blink 75 | states.push_back(StateNode(1, 8, +1.50)); 76 | states.push_back(StateNode(1, 8, -1.20)); // double blink 77 | return states; 78 | } 79 | 80 | void BlinkDetector::update(const scoped_ptr &eyefloat) { 81 | double distance = cvNorm(eyefloat.get(), averageEye.get(), CV_L2); 82 | acc.update(distance); 83 | cout << "update distance" << distance << " -> " << acc.getValue() << endl; 84 | states.updateState(distance / acc.getValue()); 85 | cvRunningAvg(eyefloat.get(), averageEye.get(), 0.05); 86 | } 87 | 88 | int BlinkDetector::getState() { 89 | return states.getState(); 90 | } 91 | -------------------------------------------------------------------------------- /Alert.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "utils.h" 3 | 4 | /* class Alert { */ 5 | /* shared_ptr blinkdet; */ 6 | /* public: */ 7 | /* Alert(const shared_ptr& blinkdet); */ 8 | /* void alert(const string& string); */ 9 | /* }; */ 10 | 11 | enum StateEventType { 12 | EVENT_NOTHING, EVENT_SELECTED, EVENT_DESELECTED, EVENT_TICK, 13 | EVENT_BLINK, EVENT_2BLINK}; 14 | 15 | 16 | template class StateNode; 17 | 18 | template 19 | struct NodeResult { 20 | shared_ptr > nextNode; 21 | shared_ptr result; 22 | NodeResult(const shared_ptr > &nextNode = 23 | shared_ptr >(), 24 | const shared_ptr &result = shared_ptr()): 25 | nextNode(nextNode), result(result) {} 26 | }; 27 | 28 | template 29 | class StateNode { 30 | protected: 31 | int ticks; 32 | public: 33 | StateNode(): ticks(0) {} 34 | virtual NodeResult handleEvent(StateEventType event); 35 | virtual ~StateNode() {}; 36 | }; 37 | 38 | template 39 | class StateMachine: public StateNode{ 40 | shared_ptr > currentNode; 41 | public: 42 | StateMachine(const shared_ptr >& initialNode); 43 | virtual NodeResult handleEvent(StateEventType event); 44 | }; 45 | 46 | template 47 | NodeResult StateNode::handleEvent(StateEventType event) { 48 | switch(event) { 49 | case EVENT_SELECTED: 50 | case EVENT_DESELECTED: ticks = 0; break; 51 | case EVENT_TICK: ticks++; break; 52 | default:; 53 | } 54 | return NodeResult(); 55 | } 56 | 57 | template 58 | StateMachine::StateMachine(const shared_ptr >& initialNode): 59 | currentNode(initialNode) 60 | { 61 | } 62 | 63 | template 64 | NodeResult StateMachine::handleEvent(StateEventType event) { 65 | for(;;) { 66 | NodeResult result = currentNode->handleEvent(event); 67 | if (result.result.get() != 0) 68 | return result; 69 | if (result.nextNode.get() == 0) 70 | return result; 71 | currentNode->handleEvent(EVENT_DESELECTED); 72 | currentNode = result.nextNode; 73 | event = EVENT_SELECTED; 74 | } 75 | } 76 | 77 | 78 | 79 | class AlertWindow: public StateNode { 80 | Gtk::Window window; 81 | string text; 82 | public: 83 | AlertWindow(const string &text); 84 | virtual ~AlertWindow(); 85 | virtual NodeResult handleEvent(StateEventType event); 86 | }; 87 | 88 | 89 | 90 | /* class MessageNode: public StateNode { */ 91 | /* shared_ptr oknode, cancelnode; */ 92 | /* scoped_ptr alert; */ 93 | /* string text; */ 94 | /* public: */ 95 | /* MessageNode(const shared_ptr& oknode, */ 96 | /* const shared_ptr& cancelnode, */ 97 | /* const string& text); */ 98 | 99 | /* virtual shared_ptr handleEvent(StateEventType event); */ 100 | /* }; */ 101 | 102 | -------------------------------------------------------------------------------- /Calibrator.cpp: -------------------------------------------------------------------------------- 1 | #include "Calibrator.h" 2 | 3 | Calibrator::~Calibrator() { 4 | #ifdef DEBUG 5 | cout << "Destroying calibrator" << endl; 6 | #endif 7 | } 8 | 9 | FrameFunction::~FrameFunction() { 10 | #ifdef DEBUG 11 | cout << "Destroying framefunction" << endl; 12 | #endif 13 | } 14 | 15 | MovingTarget::MovingTarget(const int &frameno, 16 | const vector& points, 17 | const shared_ptr &pointer, 18 | int dwelltime): 19 | FrameFunction(frameno), 20 | points(points), dwelltime(dwelltime), pointer(pointer) 21 | { 22 | }; 23 | 24 | MovingTarget::~MovingTarget() { 25 | int id = getFrame() / dwelltime; 26 | } 27 | 28 | void MovingTarget::process() { 29 | if (active()) { 30 | int id = getPointNo(); 31 | if (getPointFrame() == 1) 32 | pointer->setPosition((int)points[id].x, (int)points[id].y); 33 | } 34 | else 35 | detach(); 36 | } 37 | 38 | bool MovingTarget::active() { 39 | return getPointNo() < (int) points.size(); 40 | } 41 | 42 | int MovingTarget::getPointNo() { 43 | return getFrame() / dwelltime; 44 | } 45 | 46 | int MovingTarget::getPointFrame() { 47 | return getFrame() % dwelltime; 48 | } 49 | 50 | Calibrator::Calibrator(const int &framecount, 51 | const shared_ptr &trackingsystem, 52 | const vector& points, 53 | const shared_ptr &pointer, 54 | int dwelltime): 55 | MovingTarget(framecount, points, pointer, dwelltime), 56 | trackingsystem(trackingsystem) 57 | { 58 | trackingsystem->gazetracker.clear(); 59 | // todo: remove all calibration points 60 | } 61 | 62 | 63 | void Calibrator::process() { 64 | if (active()) { 65 | int id = getPointNo(); 66 | int frame = getPointFrame(); 67 | if (frame == 1) // start 68 | averageeye.reset(new FeatureDetector(EyeExtractor::eyesize)); 69 | 70 | if (frame >= dwelltime/2) // middle 71 | averageeye->addSample(trackingsystem->eyex.eyefloat.get()); 72 | 73 | if (frame == dwelltime-1) { // end 74 | trackingsystem->gazetracker. 75 | addExemplar(points[id], averageeye->getMean().get(), 76 | trackingsystem->eyex.eyegrey.get()); 77 | } 78 | } 79 | MovingTarget::process(); 80 | } 81 | 82 | const Point Calibrator::defaultpointarr[] = {Point(0.5, 0.5), 83 | Point(0.1, 0.5), Point(0.9, 0.5), 84 | Point(0.5, 0.1), Point(0.5, 0.9), 85 | Point(0.1, 0.1), Point(0.1, 0.9), 86 | Point(0.9, 0.9), Point(0.9, 0.1), 87 | Point(0.3, 0.3), Point(0.3, 0.7), 88 | Point(0.7, 0.7), Point(0.7, 0.3)}; 89 | 90 | vector 91 | Calibrator::defaultpoints(Calibrator::defaultpointarr, 92 | Calibrator::defaultpointarr+ 93 | (sizeof(Calibrator::defaultpointarr) / 94 | sizeof(Calibrator::defaultpointarr[0]))); 95 | 96 | vector Calibrator::loadpoints(istream& in) { 97 | vector result; 98 | 99 | for(;;) { 100 | double x, y; 101 | in >> x >> y; 102 | if (in.rdstate()) break; // break if any error 103 | result.push_back(Point(x, y)); 104 | } 105 | 106 | return result; 107 | } 108 | 109 | vector Calibrator::scaled(const vector &points, 110 | double x, double y) 111 | { 112 | // double dx = x > y ? (x-y)/2 : 0.0; 113 | // double dy = y > x ? (y-x)/2 : 0.0; 114 | // double scale = x > y ? y : x; 115 | 116 | vector result; 117 | 118 | xforeach(iter, points) 119 | result.push_back(Point(iter->x * x, iter->y * y)); 120 | // result.push_back(Point(iter->x * scale + dx, iter->y * scale + dy)); 121 | 122 | return result; 123 | } 124 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //#define DEBUG 13 | 14 | using namespace std; 15 | using namespace boost; 16 | 17 | #define xforeach(iter,container) \ 18 | for(typeof(container.begin()) iter = container.begin(); \ 19 | iter != container.end(); iter++) 20 | 21 | #define xforeachback(iter,container) \ 22 | for(typeof(container.rbegin()) iter = container.rbegin(); \ 23 | iter != container.rend(); iter++) 24 | 25 | typedef vnl_vector Vector; 26 | typedef vnl_matrix Matrix; 27 | 28 | 29 | 30 | template 31 | inline T square(T a) { 32 | return a * a; 33 | } 34 | 35 | template 36 | ostream& operator<< (ostream& out, vector const& vec) { 37 | out << vec.size() << endl; 38 | xforeach(iter, vec) 39 | out << *iter << endl; 40 | return out; 41 | } 42 | 43 | template 44 | istream& operator>> (istream& in, vector &vec) { 45 | int size; 46 | T element; 47 | 48 | vec.clear(); 49 | in >> size; 50 | for(int i=0; i> element; 52 | vec.push_back(element); 53 | } 54 | return in; 55 | } 56 | 57 | 58 | template 59 | T teefunction(T source, char* prefix, char *postfix="\n") { 60 | cout << prefix << source << postfix; 61 | return source; 62 | } 63 | 64 | #define debugtee(x) teefunction(x, #x ": ") 65 | 66 | template 67 | void savevector(CvFileStorage *out, const char* name, vector& vec) { 68 | cvStartWriteStruct(out, name, CV_NODE_SEQ); 69 | for(int i=0; i 75 | vector loadvector(CvFileStorage *in, CvFileNode *node) { 76 | CvSeq *seq = node->data.seq; 77 | CvSeqReader reader; 78 | cvStartReadSeq(seq, &reader, 0); 79 | vector result(seq->total); 80 | for(int i=0; itotal; i++) { 81 | CvFileNode *item = (CvFileNode*) reader.ptr; 82 | result[i].load(in, item); 83 | CV_NEXT_SEQ_ELEM(seq->elem_size, reader); 84 | } 85 | return result; 86 | } 87 | 88 | #include 89 | 90 | #include "Point.h" 91 | 92 | template 93 | void convert(const From& from, To& to) { 94 | to = from; 95 | } 96 | 97 | template 98 | void convert(const vector &from, vector &to) { 99 | to.resize(from.size()); 100 | for(int i=0; i< (int) from.size(); i++) 101 | convert(from[i], to[i]); 102 | } 103 | 104 | class ConstancyDetector { 105 | int value; 106 | int counter; 107 | int maxcounter; 108 | public: 109 | ConstancyDetector(int maxcounter) : 110 | value(-1), counter(0), maxcounter(maxcounter) {} 111 | 112 | bool isStable(void) { 113 | return counter >= maxcounter; 114 | } 115 | 116 | bool isStableExactly(void) { 117 | return counter == maxcounter; 118 | } 119 | 120 | bool observe(int newvalue) { 121 | if (newvalue != value || newvalue < 0) 122 | counter = 0; 123 | else 124 | counter++; 125 | value = newvalue; 126 | return isStable(); 127 | } 128 | 129 | void reset(void) { 130 | counter = 0; 131 | value = -1; 132 | } 133 | }; 134 | 135 | // #define output(X) { cout << #X " = " << X << endl; } 136 | 137 | 138 | template 139 | int maxabsindex(T const& vec, int size) { 140 | int maxindex = 0; 141 | for(int i=0; i fabs(vec[maxindex])) 143 | maxindex = i; 144 | return maxindex; 145 | } 146 | 147 | namespace boost { 148 | template <> 149 | void checked_delete(IplImage *image); 150 | } 151 | 152 | void releaseImage(IplImage *image); 153 | shared_ptr createImage(const CvSize &size, int depth, int channels); 154 | 155 | typedef shared_ptr SharedImage; 156 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Download: 2 | 3 | 1. Tarball from 4 | 5 | http://www.inference.phy.cam.ac.uk/opengazer/ 6 | 7 | 2. Or from sourceforge 8 | 9 | http://sourceforge.net/projects/opengazer 10 | 11 | $ svn co https://opengazer.svn.sourceforge.net/svnroot/opengazer 12 | 13 | Requirements: 14 | vxl >= 1.5.1 http://vxl.sourceforge.net/ 15 | opencv >= 0.9.7 http://sourceforge.net/projects/opencvlibrary 16 | gtkmm-2.4 >= 2.8.0 http://www.gtkmm.org/ 17 | cairomm-1.0 >= 0.6.0 http://cairographics.org/cairomm/ 18 | boost >= 1.32.0 http://www.boost.org/ 19 | 20 | Under Debian testing: 21 | 22 | # apt-get install libcv-dev libhighgui-dev libcvaux-dev \ 23 | libgtkmm-2.4-dev libcairomm-1.0-dev libboost-dev 24 | 25 | (vxl must be installed from the source) 26 | Important: in vxl's ccmake, set BUILD_SHARED_LIBS to ON. 27 | 28 | Compilation: 29 | 30 | > tar xzf opengazer-0.1.tar.gz 31 | > cd opengazer-0.1 32 | 33 | Edit the VXLDIR variable in the Makefile to point to your vxl 34 | include headers 35 | 36 | > make 37 | 38 | Define the variable LD_LIBRARY_PATH to point to your vxl shared libraries 39 | 40 | > export LD_LIBRARY_PATH=$VXLDIR/lib 41 | 42 | Instructions: 43 | 44 | 0. Make sure that you have read access to webcam device file 45 | (/dev/video0). In Debian/Ubuntu you can achieve that by adding 46 | yourself to the video group 47 | 48 | > sudo addgroup $USER video 49 | 50 | Then logout and login again (important). 51 | 52 | 1. Set up the drivers to your camera correctly. Put the camera into 53 | 640x480 mode. With pwc drivers you have to say 54 | 55 | > sudo rmmod pwc 56 | > sudo modprobe pwc size=vga 57 | 58 | 2. Put the camera centrally under you monitor, so that the height of 59 | your face in the image is about half of the height of the image, 60 | and close to the centre of the image. Make sure the background 61 | light level is reasonably constant across the image. 62 | 63 | 3. Make VXLDIR point to your vxl libraries, for example 64 | 65 | > export VXLDIR=/opt 66 | 67 | Define the variable LD_LIBRARY_PATH to point to your vxl shared libraries 68 | 69 | > export LD_LIBRARY_PATH=$VXLDIR/lib:$LD_LIBRARY_PATH 70 | 71 | Start the opengazer program 72 | 73 | > ./opengazer 74 | 75 | 4. While keeping your head still, use the mouse to select points on 76 | your face that will be used to track your head. The first two 77 | points much be eye corners. Select at least two other salient 78 | points (recommended 7-8 in total). Double-clicking on an existing 79 | point removes it. The first two points (corresponding to eye 80 | corners) will be blue, the other will be green. When a point 81 | becomes red, it means that it lost track. When you are happy with 82 | your selection, click "Save points". 83 | 84 | You need at least four points in total, but I recommend 7-8. Once 85 | they have been selected, the position of your head will be tracked 86 | and the rotation estimates displayed as white and blue lines from 87 | the center of the screen. The blue line indicates the difference 88 | in head position between the first frame and the current position: 89 | make sure that it is as short as possible at all times! 90 | 91 | 5. Now you can exit the program, run it again, click "load points", 92 | and the points you selected in point 3 should appear. You have 93 | probably noted a white and blue line in the center of the image; 94 | they indicate the current rotation of your head. The shorter the 95 | blue line, the closer your head position is to the original. While 96 | using the gaze tracker, try to orient your head so that the length 97 | of the blue line is as small as possible. 98 | 99 | 6. The next step is calibration. Remember, in order for this gaze 100 | tracker to work, you must keep your head absolutely still. Press 101 | "Calibrate". You will now be asked to look at various points on 102 | the screen, indicated by a red circle. Remember: do not move your 103 | head, only your eyes! 104 | 105 | 7. During the calibration and afterwards, a small blue will indicate 106 | the estimated position of your gaze focus. 107 | 108 | 109 | 110 | DEVELOPING OPENGAZER 111 | 112 | 1. The coordinates of the calibration points are stored in 113 | "calpoints.txt". Feel free to change them. For example, by having 114 | only two calibration points: one on the left of the screen, and the 115 | other on the right, you can use opengazer as a binary switch. 116 | 117 | 2. Opengazer shows the current estimate of the gaze point as a small 118 | purle circle on the screen. 119 | 120 | It also outputs this information to the standard output. Each line 121 | has the format 122 | 123 | Xcoordinate Ycoordinate -> IDoftheclosestcalibrationpoint 124 | 125 | Opengazer also opens a local UDP socket at port 20230 to which it 126 | sends the current estimates X and Y. 127 | 128 | To add more output methods, or to change the existing one, take a 129 | look at files "OutputMethods.cpp" and "OutputMethods.h". 130 | 131 | 3. Other files used by opengazer 132 | 133 | calpoints.txt Coordinates calibration points 134 | (as fraction of the screen) 135 | 136 | points.txt Coordinates of the tracking points on the user face, in 137 | pixels. This file is saved when the user clicks 138 | "Save points" 139 | 140 | -------------------------------------------------------------------------------- /GazeTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "GazeTracker.h" 2 | 3 | int Targets::getCurrentTarget(Point point) { 4 | vector distances(targets.size()); 5 | // debugtee(targets); 6 | transform(targets.begin(), targets.end(), distances.begin(), 7 | sigc::mem_fun(point, &Point::distance)); 8 | // debugtee(distances); 9 | return min_element(distances.begin(), distances.end()) - distances.begin(); 10 | // for(int i=0; i 47 | vector getsubvector(vector const& input, S T::*ptr) { 48 | vector output(input.size()); 49 | for(int i=0; i images = 77 | getsubvector(caltargets, &CalTarget::image); 78 | 79 | gpx.reset(new ImProcess(images, xlabels, covariancefunction, 0.01)); 80 | gpy.reset(new ImProcess(images, ylabels, covariancefunction, 0.01)); 81 | targets.reset(new Targets(getsubvector(caltargets, &CalTarget::point))); 82 | } 83 | 84 | void GazeTracker::clear() { 85 | caltargets.clear(); 86 | // updateGPs() 87 | } 88 | 89 | void GazeTracker::addExemplar(Point point, 90 | const IplImage *eyefloat, 91 | const IplImage *eyegrey) 92 | { 93 | caltargets.push_back(CalTarget(point, eyefloat, eyegrey)); 94 | updateGPs(); 95 | } 96 | 97 | // void GazeTracker::updateExemplar(int id, 98 | // const IplImage *eyefloat, 99 | // const IplImage *eyegrey) 100 | // { 101 | // cvConvertScale(eyegrey, caltargets[id].origimage.get()); 102 | // cvAdd(caltargets[id].image.get(), eyefloat, caltargets[id].image.get()); 103 | // cvConvertScale(caltargets[id].image.get(), caltargets[id].image.get(), 0.5); 104 | // updateGPs(); 105 | // } 106 | 107 | void GazeTracker::draw(IplImage *destimage, int eyedx, int eyedy) { 108 | // for(int i=0; i(in, cvGetFileNodeByName(in, node, 144 | "caltargets")); 145 | } 146 | 147 | static void ignore(const IplImage *) {} 148 | 149 | void GazeTracker::update(const IplImage *image) { 150 | if (isActive()) { 151 | output.gazepoint = Point(gpx->getmean(SharedImage(image, &ignore)), 152 | gpy->getmean(SharedImage(image, &ignore))); 153 | output.targetid = getTargetId(output.gazepoint); 154 | output.target = getTarget(output.targetid); 155 | } 156 | } 157 | 158 | int GazeTracker::getTargetId(Point point) { 159 | return targets->getCurrentTarget(point); 160 | } 161 | 162 | Point GazeTracker::getTarget(int id) { 163 | return targets->targets[id]; 164 | } 165 | 166 | 167 | -------------------------------------------------------------------------------- /TrackingSystem.cpp: -------------------------------------------------------------------------------- 1 | #include "TrackingSystem.h" 2 | #include "FeatureDetector.h" 3 | #include "BlinkDetector.h" 4 | 5 | static double quadraticminimum(double xm1, double x0, double xp1) { 6 | // cout << "values:" << xm1 << " " << x0 << " " << xp1 << endl; 7 | return (xm1 - xp1) / (2*(xp1 + xm1 - 2*x0)); 8 | } 9 | 10 | static Point subpixelminimum(const IplImage *values) { 11 | CvPoint maxpoint; 12 | cvMinMaxLoc(values, NULL, NULL, &maxpoint); 13 | // cout << "max: " << maxpoint.x << " " << maxpoint.y << endl; 14 | 15 | int x = maxpoint.x; 16 | int y = maxpoint.y; 17 | 18 | Point p(x, y); 19 | 20 | if (x > 0 && x < 6) 21 | p.x += quadraticminimum(cvGetReal2D(values, y, x-1), 22 | cvGetReal2D(values, y, x+0), 23 | cvGetReal2D(values, y, x+1)); 24 | 25 | if (y > 0 && y < 4) 26 | p.y += quadraticminimum(cvGetReal2D(values, y-1, x), 27 | cvGetReal2D(values, y+0, x), 28 | cvGetReal2D(values, y+1, x)); 29 | 30 | return p; 31 | } 32 | 33 | 34 | TrackingSystem::TrackingSystem(CvSize size): 35 | tracker(size), headtracker(tracker), headcomp(headtracker), eyex(tracker) 36 | {} 37 | 38 | 39 | void TrackingSystem::doprocessing(const IplImage *frame, 40 | IplImage *image) 41 | { 42 | tracker.track(frame, 2); 43 | if (tracker.countactivepoints() < 4) { 44 | tracker.draw(image); 45 | throw TrackingException(); 46 | } 47 | 48 | headtracker.updatetracker(); 49 | eyex.extractEye(frame); // throws Tracking Exception 50 | gazetracker.update(eyex.eyefloat.get()); 51 | 52 | displayeye(image, 0, 0, 0, 2); 53 | tracker.draw(image); 54 | headtracker.draw(image); 55 | } 56 | 57 | void TrackingSystem::displayeye(IplImage *image, 58 | int basex, int basey, int stepx, int stepy) 59 | { 60 | CvSize eyesize = EyeExtractor::eyesize; 61 | int eyedx = EyeExtractor::eyedx; 62 | int eyedy = EyeExtractor::eyedy; 63 | 64 | static IplImage *eyegreytemp = cvCreateImage( eyesize, 8, 1 ); 65 | static FeatureDetector features(EyeExtractor::eyesize); 66 | 67 | features.addSample(eyex.eyegrey.get()); 68 | 69 | basex *= 2*eyedx; basey *= 2*eyedy; 70 | stepx *= 2*eyedx; stepy *= 2*eyedy; 71 | 72 | gazetracker.draw(image, eyedx, eyedy); 73 | 74 | cvSetImageROI(image, cvRect(basex, basey, eyedx*2, eyedy*2)); 75 | cvCvtColor(eyex.eyegrey.get(), image, CV_GRAY2RGB); 76 | 77 | cvSetImageROI(image, cvRect(basex + stepx*1, basey + stepy*1, 78 | eyedx*2, eyedy*2)); 79 | cvCvtColor(eyex.eyegrey.get(), image, CV_GRAY2RGB); 80 | 81 | cvConvertScale(features.getMean().get(), eyegreytemp); 82 | cvSetImageROI(image, cvRect(basex, basey, eyedx*2, eyedy*2)); 83 | cvCvtColor(eyegreytemp, image, CV_GRAY2RGB); 84 | 85 | // // features.getVariance(eyegreytemp); 86 | // // cvSetImageROI(image, cvRect(basex, basey+stepy*2, eyedx*2, eyedy*2)); 87 | // // cvCvtColor(eyegreytemp, image, CV_GRAY2RGB); 88 | 89 | // // compute the x-derivative 90 | 91 | // static IplImage *eyegreytemp1 = cvCreateImage( eyesize, IPL_DEPTH_32F, 1 ); 92 | // static scoped_ptr 93 | // eyegreytemp2(cvCreateImage(eyesize, IPL_DEPTH_32F, 1)); 94 | 95 | // // static IplImage *eyegreytemp3 = cvCreateImage( eyesize, IPL_DEPTH_32F, 1 ); 96 | // static IplImage *eyegreytemp4 = cvCreateImage(cvSize(7,5),IPL_DEPTH_32F,1); 97 | // features.getMean(eyegreytemp1); 98 | // cvConvertScale(eyex.eyegrey, eyegreytemp2.get()); 99 | // double distance = cvNorm(eyegreytemp1, eyegreytemp2.get(), CV_L2); 100 | // static BlinkDetector blinkdet; 101 | // blinkdet.update(eyegreytemp2); 102 | // cout << "distance: " << distance 103 | // << " blink: " << blinkdet.getState() < 3 | #include "MainGazeTracker.h" 4 | 5 | class VideoWriter { 6 | CvVideoWriter *video; 7 | public: 8 | VideoWriter(CvSize size) : 9 | video(cvCreateVideoWriter("out.avi", 0x58564944, 15.0, size)) 10 | {} 11 | 12 | void write(const IplImage *image) { 13 | cvWriteFrame(video, image); 14 | } 15 | 16 | ~VideoWriter() { 17 | cvReleaseVideoWriter(&video); 18 | } 19 | }; 20 | 21 | 22 | 23 | CommandLineArguments::CommandLineArguments(int argc, char** argv) { 24 | for(int i=1; iwidth, frame->height)) 42 | {} 43 | 44 | VideoInput::VideoInput(const char* filename): 45 | capture(cvCaptureFromFile(filename)), framecount(0), 46 | frame(cvQueryFrame(capture)), size(cvSize(frame->width, frame->height)) 47 | {} 48 | 49 | void VideoInput::updateFrame() { 50 | framecount++; 51 | frame = cvQueryFrame(capture); 52 | } 53 | 54 | VideoInput::~VideoInput() { 55 | cvReleaseCapture( &capture ); 56 | } 57 | 58 | MainGazeTracker::MainGazeTracker(int argc, char** argv, 59 | const vector > 60 | &stores): 61 | framestoreload(-1), stores(stores), autoreload(false) 62 | // , statemachine(shared_ptr(new AlertWindow("start"))) 63 | { 64 | CommandLineArguments args(argc, argv); 65 | 66 | if (args.parameters.size() == 0) { 67 | videoinput.reset(new VideoInput()); 68 | if (args.isoption("--record")) 69 | video.reset(new VideoWriter(videoinput->size)); 70 | } 71 | else { 72 | videoinput.reset(new VideoInput(args.parameters[0])); 73 | // if (args.parameters.size() >= 2) 74 | // fileinput.reset(new FileInput(parameters[1])); 75 | } 76 | 77 | 78 | canvas.reset(cvCreateImage(videoinput->size, 8, 3)); 79 | tracking.reset(new TrackingSystem(videoinput->size)); 80 | 81 | } 82 | 83 | void MainGazeTracker::addTracker(Point point) { 84 | tracking->tracker.addtracker(point); 85 | } 86 | 87 | void MainGazeTracker::savepoints() { 88 | try { 89 | tracking->tracker.save("tracker", "points.txt", videoinput->frame); 90 | autoreload = true; 91 | } 92 | catch (ios_base::failure &e) { 93 | cout << e.what() << endl; 94 | } 95 | } 96 | 97 | void MainGazeTracker::loadpoints() { 98 | try { 99 | tracking->tracker.load("tracker", "points.txt", videoinput->frame); 100 | autoreload = true; 101 | } 102 | catch (ios_base::failure &e) { 103 | cout << e.what() << endl; 104 | } 105 | } 106 | 107 | void MainGazeTracker::clearpoints() { 108 | tracking->tracker.cleartrackers(); 109 | autoreload = false; 110 | } 111 | 112 | void MainGazeTracker::doprocessing(void) { 113 | framecount++; 114 | videoinput->updateFrame(); 115 | const IplImage *frame = videoinput->frame; 116 | 117 | if (video.get()) 118 | video->write(frame); 119 | 120 | canvas->origin = frame->origin; 121 | cvCopy( frame, canvas.get(), 0 ); 122 | 123 | try { 124 | tracking->doprocessing(frame, canvas.get()); 125 | if (tracking->gazetracker.isActive()) { 126 | xforeach(iter, stores) 127 | (*iter)->store(tracking->gazetracker.output); 128 | // cout << point.x << " " << point.y << " -> " 129 | // << tracking->gazetracker.getTargetId(point) << endl; 130 | } 131 | // if (!tracking->tracker.areallpointsactive()) 132 | // throw TrackingException(); 133 | framestoreload = 20; 134 | } 135 | catch (TrackingException&) { 136 | framestoreload--; 137 | } 138 | 139 | framefunctions.process(); 140 | // statemachine.handleEvent(EVENT_TICK); 141 | 142 | if (autoreload && framestoreload <= 0) 143 | loadpoints(); 144 | } 145 | 146 | 147 | MainGazeTracker::~MainGazeTracker(void) { 148 | } 149 | 150 | 151 | void MainGazeTracker::addExemplar(Point exemplar) { 152 | if (exemplar.x >= EyeExtractor::eyedx && 153 | exemplar.x + EyeExtractor::eyedx < videoinput->size.width && 154 | exemplar.y >= EyeExtractor::eyedy && 155 | exemplar.y + EyeExtractor::eyedy < videoinput->size.height) 156 | tracking->gazetracker.addExemplar(exemplar, 157 | tracking->eyex.eyefloat.get(), 158 | tracking->eyex.eyegrey.get()); 159 | } 160 | 161 | static vector scalebyscreen(const vector &points) { 162 | Glib::RefPtr screen = 163 | Gdk::Display::get_default()->get_default_screen(); 164 | return Calibrator::scaled(points, screen->get_width(), screen->get_height()); 165 | } 166 | 167 | void MainGazeTracker::startCalibration() { 168 | shared_ptr 169 | pointer(new WindowPointer(WindowPointer::PointerSpec(30,30,1,0,0))); 170 | 171 | ifstream calfile("calpoints.txt"); 172 | 173 | shared_ptr 174 | calibrator(new Calibrator(framecount, tracking, 175 | scalebyscreen(Calibrator::loadpoints(calfile)), 176 | pointer)); 177 | 178 | framefunctions.clear(); 179 | framefunctions.addchild(&framefunctions, calibrator); 180 | } 181 | 182 | void MainGazeTracker::startTesting() { 183 | vector points; 184 | for(double x=0.2; x<0.9; x+=0.2) 185 | for(double y=0.2; y<0.9; y+=0.2) 186 | points.push_back(Point(x,y)); 187 | 188 | shared_ptr 189 | pointer(new WindowPointer(WindowPointer::PointerSpec(30,30,0,1,0))); 190 | 191 | shared_ptr 192 | moving(new MovingTarget(framecount, scalebyscreen(points), pointer)); 193 | 194 | framefunctions.clear(); 195 | framefunctions.addchild(&framefunctions, moving); 196 | } 197 | -------------------------------------------------------------------------------- /PointTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "PointTracker.h" 2 | #include "FaceDetector.h" 3 | #include 4 | #include 5 | 6 | 7 | PointTracker::PointTracker(const CvSize &size): 8 | flags(CV_LKFLOW_INITIAL_GUESSES), 9 | grey(cvCreateImage(size, 8, 1)), 10 | orig_grey(cvCreateImage(size, 8, 1)), 11 | pyramid(cvCreateImage(size, 8, 1)), 12 | orig_pyramid(cvCreateImage(size, 8, 1)), 13 | last_grey(cvCreateImage(size, 8, 1)), 14 | last_pyramid(cvCreateImage(size, 8, 1)) 15 | // origpoints(new CvPoint2D32f[MAX_COUNT]), 16 | // currentpoints(new CvPoint2D32f[MAX_COUNT]), 17 | // status(new char[MAX_COUNT]), 18 | {} 19 | 20 | static Point pointbetweenrects(const Point &point, CvRect source, CvRect dest) { 21 | return Point((point.x-source.x)*(double(dest.width)/source.width)+dest.x, 22 | (point.y-source.y)*(double(dest.height)/source.height)+dest.y); 23 | 24 | } 25 | 26 | static vector pointbetweenrects(const vector &points, 27 | CvRect source, CvRect dest) 28 | { 29 | vector result; 30 | result.reserve(points.size()); 31 | xforeach(iter, points) 32 | result.push_back(pointbetweenrects(*iter, source, dest)); 33 | return result; 34 | } 35 | 36 | void PointTracker::save(string filename, string newpoints, 37 | const IplImage *frame) 38 | { 39 | vector faces = FaceDetector::facedetector.detect(frame); 40 | if (faces.size() == 1) { 41 | cvSaveImage((filename + "-orig-grey.png").c_str(), orig_grey.get()); 42 | cvSaveImage((filename + "-orig-pyramid.png").c_str(), orig_pyramid.get()); 43 | 44 | ofstream origfile((filename + "-orig-points.txt").c_str()); 45 | origfile << origpoints; 46 | 47 | CvRect face = faces[0]; 48 | ofstream facefile(newpoints.c_str()); 49 | vector temppoints; 50 | convert(currentpoints, temppoints); 51 | facefile << pointbetweenrects(temppoints, face, cvRect(0, 0, 1, 1)); 52 | } 53 | else 54 | throw ios_base::failure("No face found in the image"); 55 | } 56 | 57 | void PointTracker::load(string filename, string newpoints, 58 | const IplImage *frame) 59 | { 60 | vector faces = FaceDetector::facedetector.detect(frame); 61 | 62 | if (faces.size() == 1) { 63 | ifstream origfile((filename + "-orig-points.txt").c_str()); 64 | ifstream facefile(newpoints.c_str()); 65 | if (!origfile.is_open() || !facefile.is_open()) 66 | throw ios_base::failure("File not found"); 67 | 68 | // todo: memory leak here, change to scoped_ptr! 69 | orig_grey.reset(cvLoadImage((filename + "-orig-grey.png").c_str(), 0)); 70 | orig_pyramid.reset(cvLoadImage((filename + "-orig-pyramid.png").c_str(), 0)); 71 | 72 | vector temppoints; 73 | origfile >> temppoints; 74 | convert(temppoints, origpoints); 75 | 76 | facefile >> temppoints; 77 | temppoints = pointbetweenrects(temppoints, cvRect(0,0,1,1), faces[0]); 78 | convert(temppoints, currentpoints); 79 | lastpoints = currentpoints; 80 | } 81 | else 82 | throw ios_base::failure("No face found in the image"); 83 | } 84 | 85 | int PointTracker::getClosestTracker(const Point &point) { 86 | vector points; 87 | convert(currentpoints, points); 88 | return point.closestPoint(points); 89 | } 90 | 91 | void PointTracker::removetracker(int id) { 92 | currentpoints.erase(currentpoints.begin()+id); 93 | lastpoints.erase(lastpoints.begin()+id); 94 | origpoints.erase(origpoints.begin()+id); 95 | } 96 | 97 | void PointTracker::synchronizepoints() { 98 | swap(orig_grey, grey); 99 | swap(orig_pyramid, pyramid); 100 | origpoints = lastpoints = currentpoints; 101 | } 102 | 103 | void PointTracker::updatetracker(int id, const Point &point) { 104 | currentpoints[id] = point.cvpoint32(); 105 | synchronizepoints(); 106 | } 107 | 108 | void PointTracker::addtracker(const Point &point) { 109 | currentpoints.push_back(point.cvpoint32()); 110 | synchronizepoints(); 111 | } 112 | 113 | void PointTracker::cleartrackers() { 114 | currentpoints.clear(); 115 | synchronizepoints(); 116 | } 117 | 118 | void PointTracker::track(const IplImage *frame, int pyramiddepth) 119 | { 120 | assert(lastpoints.size() == currentpoints.size()); 121 | assert(origpoints.size() == currentpoints.size()); 122 | status.resize(currentpoints.size()); 123 | cvCvtColor(frame, grey.get(), CV_BGR2GRAY ); 124 | if (!currentpoints.empty()) { 125 | // first calculate the new position of the features based 126 | // on the (pyramidal) last frame and position estimations 127 | cvCalcOpticalFlowPyrLK(last_grey.get(), grey.get(), 128 | last_pyramid.get(), pyramid.get(), 129 | &lastpoints[0], ¤tpoints[0], pointcount(), 130 | cvSize(win_size,win_size), 2, &status[0], 0, 131 | cvTermCriteria(CV_TERMCRIT_ITER| 132 | CV_TERMCRIT_EPS,20,0.01), 133 | flags); 134 | 135 | // then calculate the position based on the original 136 | // template without any pyramids 137 | cvCalcOpticalFlowPyrLK(orig_grey.get(), grey.get(), 138 | orig_pyramid.get(), pyramid.get(), 139 | &origpoints[0], ¤tpoints[0], pointcount(), 140 | cvSize(win_size, win_size), 141 | pyramiddepth, &status[0], 0, 142 | cvTermCriteria(CV_TERMCRIT_ITER| 143 | CV_TERMCRIT_EPS,20,0.01), 144 | flags); 145 | 146 | 147 | flags |= CV_LKFLOW_PYR_A_READY; 148 | } 149 | 150 | cvCopy(grey.get(), last_grey.get(), 0); 151 | cvCopy(pyramid.get(), last_pyramid.get(), 0); 152 | lastpoints = currentpoints; 153 | } 154 | 155 | int PointTracker::countactivepoints(void) { 156 | return count_if(status.begin(), status.end(), 157 | bind1st(not_equal_to(), 0)); 158 | } 159 | 160 | bool PointTracker::areallpointsactive(void) { 161 | return count(status.begin(), status.end(), 0) == 0; 162 | } 163 | 164 | void PointTracker::draw(IplImage *canvas) { 165 | for(int i=0; i< (int) currentpoints.size(); i++) 166 | cvCircle( canvas, cvPointFrom32f(currentpoints[i]), 3, 167 | status[i]?(i == eyepoint1 || i == eyepoint2 ? 168 | CV_RGB(255,0,0): 169 | CV_RGB(0,255,0)): 170 | CV_RGB(0,0,255), 171 | -1, 8,0); 172 | } 173 | 174 | int PointTracker::pointcount() { 175 | return currentpoints.size(); 176 | } 177 | 178 | vector 179 | PointTracker::getpoints(const vector PointTracker::*points, 180 | bool allpoints) 181 | { 182 | vector vec; 183 | for(int i=0; i*points)[i].x, 186 | (this->*points)[i].y)); 187 | return vec; 188 | } 189 | -------------------------------------------------------------------------------- /HeadTracker.cpp: -------------------------------------------------------------------------------- 1 | #include "HeadTracker.h" 2 | #include "FMatrixAffineCompute.cpp" 3 | 4 | static double squarenorm(HomPoint point) { 5 | return square(point.x()) + square(point.y()); 6 | } 7 | 8 | 9 | static double mean(vector const& vec, 10 | double (HomPoint::*func)() const) 11 | { 12 | double sum = 0.0; 13 | for(int i=0; i HeadTracker::detectinliers(vector const &prev, 43 | vector const &now, 44 | double radius) 45 | { 46 | assert(prev.size() == now.size()); 47 | 48 | vector transitions; 49 | for(int i=0; i closepoints(transitions.size()); 63 | for(int i=0; i inliers(transitions.size()); 74 | for(int i=0; i points = 101 | tracker.getpoints(&PointTracker::origpoints, true); 102 | 103 | for(int i=0; i diff2 ? diff2 : diff1; 117 | 118 | // dubious code, I'm not sure about it 119 | if (!tracker.status[i]) { 120 | tracker.currentpoints[i].x = 121 | 0.5 * tracker.currentpoints[i].x + 0.5 * p1.x(); 122 | tracker.currentpoints[i].y = 123 | 0.5 * tracker.currentpoints[i].y + 0.5 * p1.y(); 124 | } 125 | } 126 | } 127 | 128 | void HeadTracker::updatetracker(void) { 129 | depths.resize(tracker.pointcount()); 130 | detectinliers(tracker.getpoints(&PointTracker::origpoints, true), 131 | tracker.getpoints(&PointTracker::currentpoints, true)); 132 | 133 | vector origpoints = 134 | tracker.getpoints(&PointTracker::origpoints, false); 135 | vector currentpoints = 136 | tracker.getpoints(&PointTracker::currentpoints, false); 137 | 138 | double xx0 = mean(origpoints, &HomPoint::x); 139 | double yy0 = mean(origpoints, &HomPoint::y); 140 | double xx1 = mean(currentpoints, &HomPoint::x); 141 | double yy1 = mean(currentpoints, &HomPoint::y); 142 | 143 | Vector fmatrix = computeAffineFMatrix(origpoints, currentpoints); 144 | 145 | double a = fmatrix[0]; 146 | double b = fmatrix[1]; 147 | double c = fmatrix[2]; 148 | double d = fmatrix[3]; 149 | double e = fmatrix[4]; 150 | 151 | // compute the change 152 | 153 | vector offsets(tracker.pointcount()); 154 | 155 | double depthsum = 0.0001; 156 | double offsetsum = 0.0001; 157 | 158 | for(int i=0; i newdepths(tracker.pointcount()); 187 | 188 | for(int i=0; i newdepths[2]) { 193 | rotx = -rotx; 194 | roty = -roty; 195 | for(int i=0; i distance2) { 215 | // rotx = -rotx; 216 | // roty = -roty; 217 | // depths[i] = -newdepths[i]; 218 | // } 219 | // else 220 | // depths[i] = newdepths[i]; 221 | 222 | predictpoints(xx0, yy0, xx1, yy1, 223 | rotx, roty, atx, aty); 224 | 225 | } 226 | 227 | void HeadTracker::draw(IplImage *image) { 228 | // cout << "state: "<< rotx << " " << roty << " " 229 | // << atx << " " << aty << endl; 230 | 231 | cvLine(image, 232 | cvPoint(320, 240), 233 | cvPoint(320 + int(atx * 50), 240 + int(aty * 50)), 234 | CV_RGB(255,255,255)); 235 | 236 | // for(int i=0; i 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------