-
-
Notifications
You must be signed in to change notification settings - Fork 160
/
psmove_tracker.c
1797 lines (1480 loc) · 60.4 KB
/
psmove_tracker.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* PS Move API - An interface for the PS Move Motion Controller
* Copyright (c) 2012 Thomas Perl <m@thp.io>
* Copyright (c) 2012 Benjamin Venditt <benjamin.venditti@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
**/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <math.h>
#include <sys/stat.h>
#include "opencv2/core/core_c.h"
#include "opencv2/imgproc/imgproc_c.h"
#include "opencv2/highgui/highgui_c.h"
#include "psmove_tracker.h"
#include "../psmove_private.h"
#include "camera_control.h"
#include "tracker_helpers.h"
#include "tracker_trace.h"
#ifdef __linux
# include "platform/psmove_linuxsupport.h"
#endif
#define PRINT_DEBUG_STATS // shall graphical statistics be printed to the image
//#define DEBUG_WINDOWS // shall additional windows be shown
#define ROIS 4 // the number of levels of regions of interest (roi)
#define BLINKS 4 // number of diff images to create during calibration
#define BLINK_DELAY 100 // number of milliseconds to wait between a blink
#define CALIB_MIN_SIZE 50 // minimum size of the estimated glowing sphere during calibration process (in pixel)
#define CALIB_SIZE_STD 10 // maximum standard deviation (in %) of the glowing spheres found during calibration process
#define CALIB_MAX_DIST 30 // maximum displacement of the separate found blobs
#define COLOR_FILTER_RANGE_H 12 // +- H-Range of the hsv-colorfilter
#define COLOR_FILTER_RANGE_S 85 // +- s-Range of the hsv-colorfilter
#define COLOR_FILTER_RANGE_V 85 // +- v-Range of the hsv-colorfilter
#define CAMERA_FOCAL_LENGTH 28.3 // focal lenght constant of the ps-eye camera in (degrees)
#define CAMERA_PIXEL_HEIGHT 5 // pixel height constant of the ps-eye camera in (µm)
#define PS_MOVE_DIAMETER 47 // orb diameter constant of the ps-move controller in (mm)
/* Thresholds */
#define ROI_ADJUST_FPS_T 160 // the minimum fps to be reached, if a better roi-center adjusment is to be perfomred
#define CALIBRATION_DIFF_T 20 // during calibration, all grey values in the diff image below this value are set to black
// if tracker thresholds not met, sphere is deemed not to be found
#define TRACKER_QUALITY_T1 0.3 // minimum ratio of number of pixels in blob vs pixel of estimated circle.
#define TRACKER_QUALITY_T2 0.7 // maximum allowed change of the radius in percent, compared to the last estimated radius
#define TRACKER_QUALITY_T3 4 // minimum radius
#define TRACKER_ADAPTIVE_XY 1 // specifies to use a adaptive x/y smoothing
#define TRACKER_ADAPTIVE_Z 1 // specifies to use a adaptive z smoothing
#define COLOR_ADAPTION_QUALITY 35 // maximal distance (calculated by 'psmove_tracker_hsvcolor_diff') between the first estimated color and the newly estimated
#define COLOR_UPDATE_RATE 1 // every x seconds adapt to the color, 0 means no adaption
// if color thresholds not met, color is not adapted
#define COLOR_UPDATE_QUALITY_T1 0.8 // minimum ratio of number of pixels in blob vs pixel of estimated circle.
#define COLOR_UPDATE_QUALITY_T2 0.2 // maximum allowed change of the radius in percent, compared to the last estimated radius
#define COLOR_UPDATE_QUALITY_T3 6 // minimum radius
#ifdef WIN32
#define PSEYE_BACKUP_FILE "PSEye_backup_win.ini"
#else
#define PSEYE_BACKUP_FILE "PSEye_backup_v4l.ini"
#endif
#define INTRINSICS_XML "intrinsics.xml"
#define DISTORTION_XML "distortion.xml"
#define COLOR_MAPPING_DAT "colormapping.dat"
/* Only re-use color mappings "younger" than 2 hours */
#define COLOR_MAPPING_MAX_AGE (2*60*60)
/**
* Syntactic sugar - iterate over all valid controllers of a tracker
*
* Usage example:
*
* TrackedController *tc;
* for_each_controller (tracker, tc) {
* // do something with "tc" here
* }
*
**/
#define for_each_controller(tracker, var) \
for (var=tracker->controllers; var<tracker->controllers+PSMOVE_TRACKER_MAX_CONTROLLERS; var++) \
if (var->move)
struct _TrackedController {
/* Move controller, or NULL if free slot */
PSMove* move;
/* Assigned RGB color of the controller */
struct PSMove_RGBValue color;
CvScalar eFColor; // first estimated color (BGR)
CvScalar eFColorHSV; // first estimated color (HSV)
CvScalar eColor; // estimated color (BGR)
CvScalar eColorHSV; // estimated color (HSV)
int roi_x, roi_y; // x/y - Coordinates of the ROI
int roi_level; // the current index for the level of ROI
float mx, my; // x/y - Coordinates of center of mass of the blob
float x, y, r; // x/y - Coordinates of the controllers sphere and its radius
int search_tile; // current search quadrant when controller is not found (reset to 0 if found)
float rs; // a smoothed variant of the radius
float q1, q2, q3; // Calculated quality criteria from the tracker
int is_tracked; // 1 if tracked 0 otherwise
long last_color_update; // the timestamp when the last color adaption has been performed
enum PSMove_Bool auto_update_leds;
};
typedef struct _TrackedController TrackedController;
/* Has to be 256, so that next_slot automatically wraps */
#define COLOR_MAPPING_RING_BUFFER_SIZE 256
/**
* A ring buffer used for saving color mappings of previous sessions. There
* is a pointer "next_slot" that will point to the next free slot. From there,
* new values can be saved (forward) and old values can be searched (backward).
**/
struct ColorMappingRingBuffer {
struct {
/* The RGB LED value */
struct PSMove_RGBValue from;
/* The dimming factor for which this mapping is valid */
unsigned char dimming;
/* The value of the controller in the camera */
struct PSMove_RGBValue to;
} map[COLOR_MAPPING_RING_BUFFER_SIZE];
unsigned char next_slot;
};
struct _PSMoveTracker {
CameraControl* cc;
IplImage* frame; // the current frame of the camera
IplImage *frame_rgb; // the frame as tightly packed RGB data
int exposure; // the exposure to use
IplImage* roiI[ROIS]; // array of images for each level of roi (colored)
IplImage* roiM[ROIS]; // array of images for each level of roi (greyscale)
IplConvKernel* kCalib; // kernel used for morphological operations during calibration
CvScalar rHSV; // the range of the color filter
enum PSMoveTracker_Exposure exposure_mode; // exposure mode
float dimming_factor; // dimming factor used on LED RGB values
TrackedController controllers[PSMOVE_TRACKER_MAX_CONTROLLERS]; // controller data
struct ColorMappingRingBuffer color_mapping; // remembered color mappings
CvMemStorage* storage; // use to store the result of cvFindContour and cvHughCircles
long duration; // duration of tracking operation, in ms
// size of "search" tiles when tracking is lost
int search_tile_width; // width of a single tile
int search_tile_height; // height of a single tile
int search_tiles_horizontal; // number of search tiles per row
int search_tiles_count; // number of search tiles
// internal variables
float cam_focal_length; // in (mm)
float cam_pixel_height; // in (µm)
float ps_move_diameter; // in (mm)
float user_factor_dist; // user defined factor used in distance calulation
int tracker_adaptive_xy; // should adaptive x/y-smoothing be used
int tracker_adaptive_z; // should adaptive z-smoothing be used
int calibration_t; // the threshold used during calibration to create the diff image
// if one is not met, the tracker is regarded as not found (although something has been found)
float tracker_t1; // quality threshold1 for the tracker
float tracker_t2; // quality threshold2 for the tracker
float tracker_t3; // quality threshold3 for the tracker
float adapt_t1; // quality threshold for the color adaption to discard its estimation again
// if one is not met, the color will not use adaptive color estimation
float color_t1; // quality threshold3 for the color adaption
float color_t2; // quality threshold3 for the color adaption
float color_t3; // quality threshold3 for the color adaption
float color_update_rate; // how often shall the color be adapted (in seconds), 0 means never
// internal variables (debug)
float debug_fps; // the current FPS achieved by "psmove_tracker_update"
enum PSMove_Bool mirror; // mirror camera image horizontally
};
// -------- START: internal functions only
/**
* Adapts the cameras exposure to the current lighting conditions
*
* This function will find the most suitable exposure.
*
* tracker - A valid PSMoveTracker * instance
* target_luminance - The target luminance value (higher = brighter)
*
* Returns: the most suitable exposure
**/
int psmove_tracker_adapt_to_light(PSMoveTracker *tracker, float target_luminance);
/**
* Find the TrackedController * for a given PSMove * instance
*
* if move == NULL, the next free slot will be returned
*
* Returns the TrackedController * instance, or NULL if not found
**/
TrackedController *
psmove_tracker_find_controller(PSMoveTracker *tracker, PSMove *move);
/**
* Wait for a given time for a frame from the tracker
*
* tracker - A valid PSMoveTracker * instance
* frame - A pointer to an IplImage * to store the frame
* delay - The delay to wait for the frame
**/
void
psmove_tracker_wait_for_frame(PSMoveTracker *tracker, IplImage **frame, int delay);
/**
* This function switches the sphere of the given PSMove on to the given color and takes
* a picture via the given capture. Then it switches it of and takes a picture again. A difference image
* is calculated from these two images. It stores the image of the lit sphere and
* of the diff-image in the passed parameter "on" and "diff". Before taking
* a picture it waits for the specified delay (in microseconds).
*
* tracker - the tracker that contains the camera control
* move - the PSMove controller to use
* rgb - the RGB color to use to lit the sphere
* on - the pre-allocated image to store the captured image when the sphere is lit
* diff - the pre-allocated image to store the calculated diff-image
* delay - the time to wait before taking a picture (in microseconds)
**/
void
psmove_tracker_get_diff(PSMoveTracker* tracker, PSMove* move,
struct PSMove_RGBValue rgb, IplImage* on, IplImage* diff, int delay);
/**
* This function seths the rectangle of the ROI and assures that the itis always within the bounds
* of the camera image.
*
* tracker - A valid PSMoveTracker * instance
* tc - The TrackableController containing the roi to check & fix
* roi_x - the x-part of the coordinate of the roi
* roi_y - the y-part of the coordinate of the roi
* roi_width - the width of the roi
* roi_height - the height of the roi
* cam_width - the width of the camera image
* cam_height - the height of the camera image
**/
void psmove_tracker_set_roi(PSMoveTracker* tracker, TrackedController* tc, int roi_x, int roi_y, int roi_width, int roi_height);
/**
* This function is just the internal implementation of "psmove_tracker_update"
*/
int psmove_tracker_update_controller(PSMoveTracker* tracker, TrackedController *tc);
/**
* This draws tracking statistics into the current camera image. This is only used internally.
*
* tracker - the Tracker to use
*/
void psmove_tracker_draw_tracking_stats(PSMoveTracker* tracker);
/*
* This finds the biggest contour within the given image.
*
* img - (in) the binary image to search for contours
* stor - (out) a storage that can be used to save the result of this function
* resContour - (out) points to the biggest contour found within the image
* resSize - (out) the size of that contour in px²
*/
void psmove_tracker_biggest_contour(IplImage* img, CvMemStorage* stor, CvSeq** resContour, float* resSize);
/*
* This calculates the distance of the orb of the controller.
*
* tracker - (in) the PSMoveTracker to use (used to read variables)
* blob_diameter - (in) the diameter size of the orb in pixels
*
* Returns: The distance between the orb and the camera in (mm).
*/
float psmove_tracker_calculate_distance(PSMoveTracker* tracker, float blob_diameter);
/*
* This returns a subjective distance between the first estimated (during calibration process) color and the currently estimated color.
* Subjective, because it takes the different color components not equally into account.
* Result calculates like: abs(c1.h-c2.h) + abs(c1.s-c2.s)*0.5 + abs(c1.v-c2.v)*0.5
*
* tc - The controller whose first/current color estimation distance should be calculated.
*
* Returns: a subjective distance
*/
float psmove_tracker_hsvcolor_diff(TrackedController* tc);
/*
* This will estimate the position and the radius of the orb.
* It will calcualte the radius by findin the two most distant points
* in the contour. And its by choosing the mid point of those two.
*
* cont - (in) The contour representing the orb.
* x - (out) The X coordinate of the center.
* y - (out) The Y coordinate of the center.
* radius - (out) The radius of the contour that is calculated here.
*/
void
psmove_tracker_estimate_circle_from_contour(CvSeq* cont, float *x, float *y, float* radius);
/*
* This function return a optimal ROI center point for a given Tracked controller.
* On very fast movements, it may happen that the orb is visible in the ROI, but resides
* at its border. This function will simply look for the biggest blob in the ROI and return a
* point so that that blob would be in the center of the ROI.
*
* tc - (in) The controller whose ROI centerpoint should be adjusted.
* tracker - (in) The PSMoveTracker to use.
* center - (out) The better center point for the current ROI
*
* Returns: nonzero if a new point was found, zero otherwise
*/
int
psmove_tracker_center_roi_on_controller(TrackedController* tc, PSMoveTracker* tracker, CvPoint *center);
int
psmove_tracker_color_is_used(PSMoveTracker *tracker, struct PSMove_RGBValue color);
enum PSMoveTracker_Status
psmove_tracker_enable_with_color_internal(PSMoveTracker *tracker, PSMove *move, struct PSMove_RGBValue color);
/*
* This function reads old calibration color values and tries to track the controller with that color.
* if it works, the function returns 1, 0 otherwise.
* Can help to speed up calibration process on application startup.
*
* tracker - (in) A valid PSMoveTracker
* move - (in) A valid PSMove controller
* rgb - (in) The color the PSMove controller's sphere will be lit.
*/
int
psmove_tracker_old_color_is_tracked(PSMoveTracker* tracker, PSMove* move, struct PSMove_RGBValue rgb);
/**
* Lookup a camera-visible color value
**/
int
psmove_tracker_lookup_color(PSMoveTracker *tracker, struct PSMove_RGBValue rgb, CvScalar *color);
/**
* Remember a color value after calibration
**/
void
psmove_tracker_remember_color(PSMoveTracker *tracker, struct PSMove_RGBValue rgb, CvScalar color);
// -------- END: internal functions only
PSMoveTracker *psmove_tracker_new() {
int camera = 0;
#if defined(__linux) && defined(PSMOVE_USE_PSEYE)
/**
* On Linux, we might have multiple cameras (e.g. most laptops have
* built-in cameras), so we try looking for the one that is handled
* by the PSEye driver.
**/
camera = linux_find_pseye();
if (camera == -1) {
/* Could not find the PSEye - fallback to first camera */
camera = 0;
}
#endif
int camera_env = psmove_util_get_env_int(PSMOVE_TRACKER_CAMERA_ENV);
if (camera_env != -1) {
camera = camera_env;
psmove_DEBUG("Using camera %d (%s is set)\n", camera,
PSMOVE_TRACKER_CAMERA_ENV);
}
return psmove_tracker_new_with_camera(camera);
}
void
psmove_tracker_set_auto_update_leds(PSMoveTracker *tracker, PSMove *move,
enum PSMove_Bool auto_update_leds)
{
psmove_return_if_fail(tracker != NULL);
psmove_return_if_fail(move != NULL);
TrackedController *tc = psmove_tracker_find_controller(tracker, move);
psmove_return_if_fail(tc != NULL);
tc->auto_update_leds = auto_update_leds;
}
enum PSMove_Bool
psmove_tracker_get_auto_update_leds(PSMoveTracker *tracker, PSMove *move)
{
psmove_return_val_if_fail(tracker != NULL, PSMove_False);
psmove_return_val_if_fail(move != NULL, PSMove_False);
TrackedController *tc = psmove_tracker_find_controller(tracker, move);
psmove_return_val_if_fail(tc != NULL, PSMove_False);
return tc->auto_update_leds;
}
void
psmove_tracker_set_dimming(PSMoveTracker *tracker, float dimming)
{
psmove_return_if_fail(tracker != NULL);
tracker->dimming_factor = dimming;
}
float
psmove_tracker_get_dimming(PSMoveTracker *tracker)
{
psmove_return_val_if_fail(tracker != NULL, 0);
return tracker->dimming_factor;
}
void
psmove_tracker_set_exposure(PSMoveTracker *tracker,
enum PSMoveTracker_Exposure exposure)
{
psmove_return_if_fail(tracker != NULL);
tracker->exposure_mode = exposure;
float target_luminance = 0;
switch (tracker->exposure_mode) {
case Exposure_MEDIUM:
target_luminance = 25;
break;
case Exposure_HIGH:
target_luminance = 50;
break;
default:
psmove_DEBUG("Invalid exposure mode: %s\n", exposure);
break;
}
tracker->exposure = psmove_tracker_adapt_to_light(tracker, target_luminance);
camera_control_set_parameters(tracker->cc, 0, 0, 0, tracker->exposure,
0, 0xffff, 0xffff, 0xffff, -1, -1);
}
enum PSMoveTracker_Exposure
psmove_tracker_get_exposure(PSMoveTracker *tracker)
{
psmove_return_val_if_fail(tracker != NULL, Exposure_INVALID);
return tracker->exposure_mode;
}
void
psmove_tracker_enable_deinterlace(PSMoveTracker *tracker,
enum PSMove_Bool enabled)
{
psmove_return_if_fail(tracker != NULL);
psmove_return_if_fail(tracker->cc != NULL);
camera_control_set_deinterlace(tracker->cc, enabled);
}
void
psmove_tracker_set_mirror(PSMoveTracker *tracker,
enum PSMove_Bool enabled)
{
psmove_return_if_fail(tracker != NULL);
tracker->mirror = enabled;
}
enum PSMove_Bool
psmove_tracker_get_mirror(PSMoveTracker *tracker)
{
psmove_return_val_if_fail(tracker != NULL, PSMove_False);
return tracker->mirror;
}
PSMoveTracker *
psmove_tracker_new_with_camera(int camera) {
PSMoveTracker* tracker = (PSMoveTracker*) calloc(1, sizeof(PSMoveTracker));
tracker->rHSV = cvScalar(COLOR_FILTER_RANGE_H, COLOR_FILTER_RANGE_S, COLOR_FILTER_RANGE_V, 0);
tracker->storage = cvCreateMemStorage(0);
tracker->dimming_factor = 1.;
tracker->cam_focal_length = CAMERA_FOCAL_LENGTH;
tracker->cam_pixel_height = CAMERA_PIXEL_HEIGHT;
tracker->ps_move_diameter = PS_MOVE_DIAMETER;
tracker->user_factor_dist = 1.05;
tracker->calibration_t = CALIBRATION_DIFF_T;
tracker->tracker_t1 = TRACKER_QUALITY_T1;
tracker->tracker_t2 = TRACKER_QUALITY_T2;
tracker->tracker_t3 = TRACKER_QUALITY_T3;
tracker->tracker_adaptive_xy = TRACKER_ADAPTIVE_XY;
tracker->tracker_adaptive_z = TRACKER_ADAPTIVE_Z;
tracker->adapt_t1 = COLOR_ADAPTION_QUALITY;
tracker->color_t1 = COLOR_UPDATE_QUALITY_T1;
tracker->color_t2 = COLOR_UPDATE_QUALITY_T2;
tracker->color_t3 = COLOR_UPDATE_QUALITY_T3;
tracker->color_update_rate = COLOR_UPDATE_RATE;
// start the video capture device for tracking
tracker->cc = camera_control_new(camera);
char *intrinsics_xml = psmove_util_get_file_path(INTRINSICS_XML);
char *distortion_xml = psmove_util_get_file_path(DISTORTION_XML);
camera_control_read_calibration(tracker->cc, intrinsics_xml, distortion_xml);
free(intrinsics_xml);
free(distortion_xml);
// backup the systems settings, if not already backuped
char *filename = psmove_util_get_file_path(PSEYE_BACKUP_FILE);
camera_control_backup_system_settings(tracker->cc, filename);
free(filename);
#ifndef __APPLE__
// try to load color mapping data (not on Mac OS X for now, because the
// automatic white balance means we get different colors every time)
filename = psmove_util_get_file_path(COLOR_MAPPING_DAT);
FILE *fp = NULL;
time_t now = time(NULL);
struct stat st;
memset(&st, 0, sizeof(st));
if (stat(filename, &st) == 0 && now != (time_t)-1) {
if (st.st_mtime >= (now - COLOR_MAPPING_MAX_AGE)) {
fp = fopen(filename, "rb");
} else {
printf("%s is too old - not restoring colors.\n", filename);
}
}
if (fp) {
if (!fread(&(tracker->color_mapping),
sizeof(struct ColorMappingRingBuffer),
1, fp)) {
psmove_WARNING("Cannot read data from: %s\n", filename);
} else {
printf("color mappings restored.\n");
}
fclose(fp);
}
free(filename);
#endif
// use static exposure
psmove_tracker_set_exposure(tracker, Exposure_LOW);
// just query a frame so that we know the camera works
IplImage* frame = NULL;
while (!frame) {
frame = camera_control_query_frame(tracker->cc);
}
// prepare ROI data structures
/* Define the size of the biggest ROI */
int size = psmove_util_get_env_int(PSMOVE_TRACKER_ROI_SIZE_ENV);
if (size == -1) {
size = MIN(frame->width, frame->height) / 2;
} else {
psmove_DEBUG("Using ROI size: %d\n", size);
}
int w = size, h = size;
// We need to grab an image from the camera to determine the frame size
psmove_tracker_update_image(tracker);
tracker->search_tile_width = w;
tracker->search_tile_height = h;
tracker->search_tiles_horizontal = (tracker->frame->width +
tracker->search_tile_width - 1) / tracker->search_tile_width;
int search_tiles_vertical = (tracker->frame->height +
tracker->search_tile_height - 1) / tracker->search_tile_height;
tracker->search_tiles_count = tracker->search_tiles_horizontal *
search_tiles_vertical;
if (tracker->search_tiles_count % 2 == 0) {
/**
* search_tiles_count must be uneven, so that when picking every second
* tile, we still "visit" every tile after two scans when we wrap:
*
* ABA
* BAB
* ABA -> OK, first run = A, second run = B
*
* ABAB
* ABAB -> NOT OK, first run = A, second run = A
*
* Incrementing the count will make the algorithm visit the lower right
* item twice, but will then cause the second run to visit 'B's.
*
* We pick every second tile, so that we only need half the time to
* sweep through the whole image (which usually means faster recovery).
**/
tracker->search_tiles_count++;
}
int i;
for (i = 0; i < ROIS; i++) {
tracker->roiI[i] = cvCreateImage(cvSize(w,h), frame->depth, 3);
tracker->roiM[i] = cvCreateImage(cvSize(w,h), frame->depth, 1);
/* Smaller rois are always square, and 70% of the previous level */
h = w = MIN(w,h) * 0.7f;
}
// prepare structure used for erode and dilate in calibration process
int ks = 5; // Kernel Size
int kc = (ks + 1) / 2; // Kernel Center
tracker->kCalib = cvCreateStructuringElementEx(ks, ks, kc, kc, CV_SHAPE_RECT, NULL);
return tracker;
}
enum PSMoveTracker_Status
psmove_tracker_enable(PSMoveTracker *tracker, PSMove *move)
{
psmove_return_val_if_fail(tracker != NULL, Tracker_CALIBRATION_ERROR);
psmove_return_val_if_fail(move != NULL, Tracker_CALIBRATION_ERROR);
int i;
/* Preset colors - use them in ascending order if not used yet */
struct PSMove_RGBValue preset_colors[] = {
{0xFF, 0x00, 0xFF}, /* magenta */
{0x00, 0xFF, 0xFF}, /* cyan */
{0x00, 0x00, 0xFF}, /* blue */
{0xFF, 0xFF, 0x00}, /* yellow */
{0x00, 0xFF, 0x00}, /* green */
};
for (i=0; i<ARRAY_LENGTH(preset_colors); i++) {
if (!psmove_tracker_color_is_used(tracker, preset_colors[i])) {
return psmove_tracker_enable_with_color_internal(tracker,
move, preset_colors[i]);
}
}
/* No colors are available anymore */
return Tracker_CALIBRATION_ERROR;
}
int
psmove_tracker_old_color_is_tracked(PSMoveTracker* tracker, PSMove* move, struct PSMove_RGBValue rgb)
{
CvScalar color;
if (!psmove_tracker_lookup_color(tracker, rgb, &color)) {
return 0;
}
TrackedController *tc = psmove_tracker_find_controller(tracker, NULL);
if (!tc) {
return 0;
}
tc->move = move;
tc->color = rgb;
tc->auto_update_leds = PSMove_True;
tc->eColor = tc->eFColor = color;
tc->eColorHSV = tc->eFColorHSV = th_brg2hsv(tc->eFColor);
/* Try to track the controller, give up after 100 iterations */
int i;
for (i=0; i<100; i++) {
psmove_set_leds(move,
rgb.r * tracker->dimming_factor,
rgb.g * tracker->dimming_factor,
rgb.b * tracker->dimming_factor);
psmove_update_leds(move);
usleep(1000 * 10); // wait 10ms - ok, since we're not blinking
psmove_tracker_update_image(tracker);
psmove_tracker_update(tracker, move);
if (tc->is_tracked) {
// TODO: Verify quality criteria to avoid bogus tracking
return 1;
}
}
psmove_tracker_disable(tracker, move);
return 0;
}
int
psmove_tracker_lookup_color(PSMoveTracker *tracker, struct PSMove_RGBValue rgb, CvScalar *color)
{
unsigned char current = tracker->color_mapping.next_slot - 1;
unsigned char dimming = 255 * tracker->dimming_factor;
while (current != tracker->color_mapping.next_slot) {
if (memcmp(&rgb, &(tracker->color_mapping.map[current].from),
sizeof(struct PSMove_RGBValue)) == 0 &&
tracker->color_mapping.map[current].dimming == dimming) {
struct PSMove_RGBValue to = tracker->color_mapping.map[current].to;
color->val[0] = to.r;
color->val[1] = to.g;
color->val[2] = to.b;
return 1;
}
current--;
}
return 0;
}
void
psmove_tracker_remember_color(PSMoveTracker *tracker, struct PSMove_RGBValue rgb, CvScalar color)
{
unsigned char dimming = 255 * tracker->dimming_factor;
struct PSMove_RGBValue to;
to.r = color.val[0];
to.g = color.val[1];
to.b = color.val[2];
unsigned char slot = tracker->color_mapping.next_slot++;
tracker->color_mapping.map[slot].from = rgb;
tracker->color_mapping.map[slot].dimming = dimming;
tracker->color_mapping.map[slot].to = to;
char *filename = psmove_util_get_file_path(COLOR_MAPPING_DAT);
FILE *fp = fopen(filename, "wb");
if (fp) {
if (!fwrite(&(tracker->color_mapping),
sizeof(struct ColorMappingRingBuffer),
1, fp)) {
psmove_WARNING("Cannot write data to: %s\n", filename);
} else {
printf("color mappings saved.\n");
}
fclose(fp);
}
free(filename);
}
enum PSMoveTracker_Status
psmove_tracker_enable_with_color(PSMoveTracker *tracker, PSMove *move,
unsigned char r, unsigned char g, unsigned char b)
{
psmove_return_val_if_fail(tracker != NULL, Tracker_CALIBRATION_ERROR);
psmove_return_val_if_fail(move != NULL, Tracker_CALIBRATION_ERROR);
struct PSMove_RGBValue rgb = { r, g, b };
return psmove_tracker_enable_with_color_internal(tracker, move, rgb);
}
enum PSMoveTracker_Status
psmove_tracker_enable_with_color_internal(PSMoveTracker *tracker, PSMove *move,
struct PSMove_RGBValue rgb)
{
// check if the controller is already enabled!
if (psmove_tracker_find_controller(tracker, move)) {
return Tracker_CALIBRATED;
}
if (psmove_tracker_color_is_used(tracker, rgb)) {
return Tracker_CALIBRATION_ERROR;
}
psmove_tracker_update_image(tracker);
IplImage* frame = camera_control_query_frame(tracker->cc);
// try to track the controller with the old color, if it works we are done
if (psmove_tracker_old_color_is_tracked(tracker, move, rgb)) {
return Tracker_CALIBRATED;
}
// clear the calibration html trace
psmove_html_trace_clear();
// check if the frame retrieved, is valid
assert(frame!=NULL);
IplImage* images[BLINKS]; // array of images saved during calibration for estimation of sphere color
IplImage* diffs[BLINKS]; // array of masks saved during calibration for estimation of sphere color
double sizes[BLINKS]; // array of blob sizes saved during calibration for estimation of sphere color
int i;
for (i = 0; i < BLINKS; i++) {
images[i] = cvCreateImage(cvGetSize(frame), frame->depth, 3);
diffs[i] = cvCreateImage(cvGetSize(frame), frame->depth, 1);
}
// DEBUG log the assigned color
CvScalar assignedColor = cvScalar(rgb.b, rgb.g, rgb.r, 0);
psmove_html_trace_put_color_var("assignedColor", assignedColor);
// for each blink
for (i = 0; i < BLINKS; i++) {
// create a diff image
psmove_tracker_get_diff(tracker, move, rgb, images[i], diffs[i], BLINK_DELAY);
// DEBUG log the diff image and the image with the lit sphere
psmove_html_trace_image_at(images[i], i, "originals");
psmove_html_trace_image_at(diffs[i], i, "rawdiffs");
// threshold it to reduce image noise
cvThreshold(diffs[i], diffs[i], tracker->calibration_t, 0xFF /* white */, CV_THRESH_BINARY);
// DEBUG log the thresholded diff image
psmove_html_trace_image_at(diffs[i], i, "threshdiffs");
// use morphological operations to further remove noise
cvErode(diffs[i], diffs[i], tracker->kCalib, 1);
cvDilate(diffs[i], diffs[i], tracker->kCalib, 1);
// DEBUG log the even more cleaned up diff-image
psmove_html_trace_image_at(diffs[i], i, "erodediffs");
}
// create the final mask
IplImage* mask = diffs[0];
// put the diff images together to get hopefully only one intersection region
// the region at which the controllers sphere resides.
for (i = 1; i < BLINKS; i++) {
cvAnd(mask, diffs[i], mask, NULL);
}
// find the biggest contour
float sizeBest = 0;
CvSeq* contourBest = NULL;
psmove_tracker_biggest_contour(diffs[0], tracker->storage, &contourBest, &sizeBest);
// blank out the image and repaint the blob where the sphere is deemed to be
cvSet(mask, TH_COLOR_BLACK, NULL);
if (contourBest)
cvDrawContours(mask, contourBest, TH_COLOR_WHITE, TH_COLOR_WHITE, -1, CV_FILLED, 8, cvPoint(0, 0));
cvClearMemStorage(tracker->storage);
// DEBUG log the final diff-image used for color estimation
psmove_html_trace_image_at(mask, 0, "finaldiff");
// CHECK if the blob contains a minimum number of pixels
if (cvCountNonZero(mask) < CALIB_MIN_SIZE) {
psmove_html_trace_put_log_entry("WARNING", "The final mask my not be representative for color estimation.");
}
// calculate the avg color
CvScalar color = cvAvg(images[0], mask);
CvScalar hsv_assigned = th_brg2hsv(assignedColor); // HSV color sent to controller
CvScalar hsv_color = th_brg2hsv(color); // HSV color seen by camera
psmove_html_trace_put_color_var("estimatedColor", color);
psmove_html_trace_put_int_var("estimated_hue", hsv_color.val[0]);
psmove_html_trace_put_int_var("assigned_hue", hsv_assigned.val[0]);
psmove_html_trace_put_int_var("allowed_hue_difference", tracker->rHSV.val[0]);
// CHECK if the hue of the estimated and the assigned colors differ more than allowed in the color-filter range.s
if (abs(hsv_assigned.val[0] - hsv_color.val[0]) > tracker->rHSV.val[0]) {
psmove_html_trace_put_log_entry("WARNING", "The estimated color seems not to be similar to the color it should be.");
}
int valid_countours = 0;
// calculate upper & lower bounds for the color filter
CvScalar min = th_scalar_sub(hsv_color, tracker->rHSV);
CvScalar max = th_scalar_add(hsv_color, tracker->rHSV);
// for each image (where the sphere was lit)
CvPoint firstPosition;
for (i = 0; i < BLINKS; i++) {
// convert to HSV
cvCvtColor(images[i], images[i], CV_BGR2HSV);
// apply color filter
cvInRangeS(images[i], min, max, mask);
// use morphological operations to further remove noise
cvErode(mask, mask, tracker->kCalib, 1);
cvDilate(mask, mask, tracker->kCalib, 1);
// DEBUG log the color filter and
psmove_html_trace_image_at(mask, i, "filtered");
// find the biggest contour in the image and save its location and size
psmove_tracker_biggest_contour(mask, tracker->storage, &contourBest, &sizeBest);
sizes[i] = 0;
float dist = FLT_MAX;
CvRect bBox;
if (contourBest) {
bBox = cvBoundingRect(contourBest, 0);
if (i == 0) {
firstPosition = cvPoint(bBox.x, bBox.y);
}
dist = sqrt(pow(firstPosition.x - bBox.x, 2) + pow(firstPosition.y - bBox.y, 2));
sizes[i] = sizeBest;
}
// CHECK for errors (no contour, more than one contour, or contour too small)
if (!contourBest) {
psmove_html_trace_array_item_at(i, "contours", "no contour");
} else if (sizes[i] <= CALIB_MIN_SIZE) {
psmove_html_trace_array_item_at(i, "contours", "too small");
} else if (dist >= CALIB_MAX_DIST) {
psmove_html_trace_array_item_at(i, "contours", "too far apart");
} else {
psmove_html_trace_array_item_at(i, "contours", "OK");
// all checks passed, increase the number of valid contours
valid_countours++;
}
cvClearMemStorage(tracker->storage);
}
// clean up all temporary images
for (i = 0; i < BLINKS; i++) {
cvReleaseImage(&images[i]);
cvReleaseImage(&diffs[i]);
mask = NULL;
}
// CHECK if sphere was found in each BLINK image
int has_calibration_errors = 0;
if (valid_countours < BLINKS) {
psmove_html_trace_put_log_entry("ERROR", "The sphere could not be found in all images.");
has_calibration_errors++;
}
// CHECK if the size of the found contours are similar
double sizeVariance, sizeAverage;
th_stats(sizes, BLINKS, &sizeVariance, &sizeAverage);
if (sqrt(sizeVariance) >= (sizeAverage / 100.0 * CALIB_SIZE_STD)) {
psmove_html_trace_put_log_entry("ERROR", "The spheres found differ too much in size.");
has_calibration_errors++;
}
if (has_calibration_errors)
return Tracker_CALIBRATION_ERROR;
// Find the next free slot to use as TrackedController
TrackedController *tc = psmove_tracker_find_controller(tracker, NULL);
if (!tc) {
return Tracker_CALIBRATION_ERROR;
}
tc->move = move;
tc->color = rgb;
tc->auto_update_leds = PSMove_True;
psmove_tracker_remember_color(tracker, rgb, color);
tc->eColor = tc->eFColor = color;
tc->eColorHSV = tc->eFColorHSV = hsv_color;
return Tracker_CALIBRATED;
}
int
psmove_tracker_get_color(PSMoveTracker *tracker, PSMove *move,
unsigned char *r, unsigned char *g, unsigned char *b)
{
psmove_return_val_if_fail(tracker != NULL, 0);
psmove_return_val_if_fail(move != NULL, 0);