You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

777 lines
24 KiB

/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
/*
* This is an example of a stationary tracker. It only reports the initial
* position for all frames and is used for testing purposes.
* The main function of this example is to show the developers how to modify
* their trackers to work with the evaluation environment.
*
* Copyright (c) 2015, Luka Cehovin
* 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 OWNER 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.
*
* The views and conclusions contained in the software and documentation are those
* of the authors and should not be interpreted as representing official policies,
* either expressed or implied, of the FreeBSD Project.
*
*/
//#define _BSD_SOURCE
#include <stdio.h>
#include <ctype.h>
#include <math.h>
#include <time.h>
#include <stdexcept>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <streambuf>
#include <stdarg.h>
#include <trax/client.hpp>
#ifdef TRAX_BUILD_OPENCV
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <trax/opencv.hpp>
#endif
#if defined(__OS2__) || defined(__WINDOWS__) || defined(WIN32) || defined(WIN64) || defined(_MSC_VER)
#include <ctype.h>
#include <windows.h>
#define strcmpi _strcmpi
__inline void sleep(long time) {
Sleep(time * 1000);
}
#else
#ifdef _MAC_
#else
#include <unistd.h>
#endif
#include <signal.h>
#define strcmpi strcasecmp
#endif
#include "getopt.h"
#include "region.h"
using namespace std;
using namespace trax;
#define CMD_OPTIONS "hsdI:G:f:O:S:r:t:T:p:e:xXQ"
#ifndef MAX
#define MAX(a,b) ((a) > (b)) ? (a) : (b)
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b)) ? (a) : (b)
#endif
#ifndef TRAX_BUILD_DATE
#define TRAX_BUILD_DATE __DATE__
#endif
void print_help() {
cout << "TraX Client" << "\n\n";
cout << "Built on " << TRAX_BUILD_DATE << "\n";
cout << "Library version: " << trax_version() << "\n";
cout << "Protocol version: " << TRAX_VERSION << "\n\n";
cout << "Usage: traxclient [-h] [-d] [-I image_list] [-O output_file] \n";
cout << "\t [-f threshold] [-r frames] [-G groundtruth_file] [-e name=value] \n";
cout << "\t [-p name=value] [-t timeout] [-s] [-T timing_file] [-x] [-X]\n";
cout << "\t -- <command_part1> <command_part2> ...";
cout << "\n\nProgram arguments: \n";
cout << "\t-h\tPrint this help and exit\n";
cout << "\t-d\tEnable debug\n";
cout << "\t-t\tSet timeout period\n";
cout << "\t-G\tGroundtruth annotations file\n";
cout << "\t-I\tFile that lists the image sequence files\n";
cout << "\t-O\tOutput region file\n";
cout << "\t-S\tInitialization region file (if different than groundtruth)\n";
cout << "\t-T\tOutput timings file\n";
cout << "\t-f\tFailure threshold\n";
cout << "\t-r\tReinitialization offset\n";
cout << "\t-e\tEnvironmental variable (multiple occurences allowed)\n";
cout << "\t-p\tTracker parameter (multiple occurences allowed)\n";
cout << "\t-Q\tWait for tracker to respond, then output its information and quit.\n";
cout << "\t-x\tUse explicit streams, not standard ones.\n";
cout << "\t-X\tUse TCP/IP sockets instead of file streams.\n";
cout << "\n";
cout << "\n";
}
static bool exit_cache = false;
void handle_signal(int s) {
exit_cache = true;
}
void configure_signals() {
#if defined(__OS2__) || defined(__WINDOWS__) || defined(WIN32) || defined(WIN64) || defined(_MSC_VER)
// TODO: anything to do here?
#else
struct sigaction sa;
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; //SA_NOCLDWAIT;
if (sigaction(SIGCHLD, &sa, 0) == -1) {
perror(0);
exit(1);
}
sa.sa_handler = handle_signal;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, 0) == -1) {
perror(0);
exit(1);
}
#endif
}
bool must_exit() {
return exit_cache;
}
#include <string.h>
typedef struct image_size {
int width;
int height;
} image_size;
// Based on the example from: http://www.wischik.com/lu/programmer/get-image-size.html
// Retrieve image size from image header
image_size image_get_size(Image image) {
int len;
image_size result;
unsigned char buf[24];
if (image.type() == TRAX_IMAGE_MEMORY) {
image.get_memory_header(&result.width, &result.height, NULL);
return result;
}
if (image.type() == TRAX_IMAGE_PATH) {
const string filename = image.get_path();
int r;
result.width = -1;
result.height = -1;
FILE *f=fopen(filename.c_str(),"rb");
if ( f==0 ) return result;
fseek(f,0,SEEK_END);
int len=ftell(f);
fseek(f,0,SEEK_SET);
if (len<24) {fclose(f); return result;}
// Strategy:
// reading GIF dimensions requires the first 10 bytes of the file
// reading PNG dimensions requires the first 24 bytes of the file
// reading JPEG dimensions requires scanning through jpeg chunks
// In all formats, the file is at least 24 bytes big, so we'll read that always
r = fread(buf,1,24,f);
if (r == -1) {
return result;
}
// For JPEGs, we need to read the first 12 bytes of each chunk.
// We'll read those 12 bytes at buf+2...buf+14, i.e. overwriting the existing buf.
if (buf[0]==0xFF && buf[1]==0xD8 && buf[2]==0xFF && buf[3]==0xE0 && buf[6]=='J' && buf[7]=='F' && buf[8]=='I' && buf[9]=='F') {
long pos=2;
while (buf[2]==0xFF) {
if (buf[3]==0xC0 || buf[3]==0xC1 || buf[3]==0xC2 || buf[3]==0xC3 || buf[3]==0xC9 || buf[3]==0xCA || buf[3]==0xCB) break;
pos += 2+(buf[4]<<8)+buf[5];
if (pos+12>len) break;
fseek(f,pos,SEEK_SET);
r = fread(buf+2,1,12,f);
if (r == -1) { return result; }
}
}
fclose(f);
} else if (image.type() == TRAX_IMAGE_BUFFER) {
const char* src = image.get_buffer(&len, NULL);
if (len<24) { return result; }
memcpy(buf, src, 24);
}
// JPEG: (first two bytes of buf are first two bytes of the jpeg file; rest of buf is the DCT frame
if (buf[0]==0xFF && buf[1]==0xD8 && buf[2]==0xFF) {
result.height = (buf[7]<<8) + buf[8];
result.width = (buf[9]<<8) + buf[10];
}
// GIF: first three bytes say "GIF", next three give version number. Then dimensions
if (buf[0]=='G' && buf[1]=='I' && buf[2]=='F') {
result.width = buf[6] + (buf[7]<<8);
result.height = buf[8] + (buf[9]<<8);
}
// PNG: the first frame is by definition an IHDR frame, which gives dimensions
if ( buf[0]==0x89 && buf[1]=='P' && buf[2]=='N' && buf[3]=='G' && buf[4]==0x0D && buf[5]==0x0A && buf[6]==0x1A && buf[7]==0x0A
&& buf[12]=='I' && buf[13]=='H' && buf[14]=='D' && buf[15]=='R') {
result.width = (buf[16]<<24) + (buf[17]<<16) + (buf[18]<<8) + (buf[19]<<0);
result.height = (buf[20]<<24) + (buf[21]<<16) + (buf[22]<<8) + (buf[23]<<0);
}
return result;
}
image_size image_get_size(ImageList image) {
if (image.has(TRAX_CHANNEL_COLOR)) {
return image_get_size(image.get(TRAX_CHANNEL_COLOR));
}
if (image.has(TRAX_CHANNEL_DEPTH)) {
return image_get_size(image.get(TRAX_CHANNEL_DEPTH));
}
if (image.has(TRAX_CHANNEL_IR)) {
return image_get_size(image.get(TRAX_CHANNEL_IR));
}
image_size size;
return size;
}
#define DELIMITER ";"
void read_images(const string& file, vector<vector<string> >& list) {
std::ifstream input;
input.open(file.c_str(), std::ifstream::in);
if (!input.is_open())
throw std::runtime_error("Image list file not available.");
string delimiter(DELIMITER);
while (true) {
string line;
vector<string> images;
getline(input, line);
if (!input.good()) break;
size_t pos = 0;
std::string token;
while ((pos = line.find(delimiter)) != std::string::npos) {
token = line.substr(0, pos);
images.push_back(token);
line.erase(0, pos + delimiter.length());
}
images.push_back(line);
if (images.size() < TRAX_CHANNELS) {
images.resize(TRAX_CHANNELS);
}
list.push_back(images);
}
input.close();
}
void save_timings(const string& file, vector<double>& timings) {
std::ofstream output;
output.open(file.c_str(), std::ofstream::out);
for (vector<double>::iterator it = timings.begin(); it != timings.end(); it++) {
output << *it << endl;
}
output.close();
}
Image load_image(string& path, int formats) {
int image_format = 0;
if TRAX_SUPPORTS(formats, TRAX_IMAGE_PATH)
image_format = TRAX_IMAGE_PATH;
else if TRAX_SUPPORTS(formats, TRAX_IMAGE_BUFFER)
image_format = TRAX_IMAGE_BUFFER;
#ifdef TRAX_BUILD_OPENCV
else if TRAX_SUPPORTS(formats, TRAX_IMAGE_MEMORY)
image_format = TRAX_IMAGE_MEMORY;
#endif
else
throw std::runtime_error("No supported image format allowed");
Image image;
if (path.empty())
return image;
switch (image_format) {
case TRAX_IMAGE_PATH: {
return Image::create_path(path);
}
case TRAX_IMAGE_BUFFER: {
// Read the file to memory
std::ifstream t(path.c_str());
t.seekg(0, std::ios::end);
size_t size = t.tellg();
std::string buffer(size, ' ');
t.seekg(0);
t.read(&buffer[0], size);
return Image::create_buffer(buffer.size(), buffer.c_str());
break;
}
#ifdef TRAX_BUILD_OPENCV
case TRAX_IMAGE_MEMORY: {
cv::Mat cvmat = cv::imread(path);
image = trax::mat_to_image(cvmat);
break;
}
#endif
}
return image;
}
ImageList load_images(vector<string>& path, int channels, int formats) {
ImageList list;
if (TRAX_SUPPORTS(channels, TRAX_CHANNEL_COLOR)) {
list.set(load_image(path[0], formats), TRAX_CHANNEL_COLOR);
}
if (TRAX_SUPPORTS(channels, TRAX_CHANNEL_DEPTH)) {
list.set(load_image(path[1], formats), TRAX_CHANNEL_DEPTH);
}
if (TRAX_SUPPORTS(channels, TRAX_CHANNEL_IR)) {
list.set(load_image(path[2], formats), TRAX_CHANNEL_IR);
}
return list;
}
ConnectionMode connection = CONNECTION_DEFAULT;
VerbosityMode verbosity = VERBOSITY_DEFAULT;
void print_debug(const char *format, ...) {
if (verbosity != VERBOSITY_DEBUG)
return;
va_list args;
va_start(args, format);
fprintf(stdout, "CLIENT: ");
vfprintf(stdout, format, args);
va_end(args);
}
int main( int argc, char** argv) {
int c;
int result = 0;
bool overlap_bounded = false;
bool query_mode = false;
opterr = 0;
float threshold = -1;
int timeout = 30;
int reinitialize = 0;
string timing_file;
string tracker_command;
string images_file("images.txt");
string groundtruth_file("groundtruth.txt");
string output_file("output.txt");
string initialization_file;
Properties properties;
map<string, string> environment;
configure_signals();
try {
while ((c = getopt(argc, argv, CMD_OPTIONS)) != -1)
switch (c) {
case 'h':
print_help();
exit(0);
case 'd':
verbosity = VERBOSITY_DEBUG;
break;
case 's':
verbosity = VERBOSITY_SILENT;
break;
case 'x':
connection = CONNECTION_EXPLICIT;
break;
case 'X':
connection = CONNECTION_SOCKETS;
break;
case 'Q':
query_mode = true;
break;
case 'I':
images_file = string(optarg);
break;
case 'G':
groundtruth_file = string(optarg);
break;
case 'O':
output_file = string(optarg);
break;
case 'S':
initialization_file = string(optarg);
break;
case 'f':
threshold = (float) MIN(1, MAX(0, (float)atof(optarg)));
break;
case 'r':
reinitialize = MAX(1, atoi(optarg));
break;
case 't':
timeout = MAX(0, atoi(optarg));
break;
case 'T':
timing_file = string(optarg);
break;
case 'e': {
char* var = optarg;
char* ptr = strchr(var, '=');
if (!ptr) break;
environment[string(var, ptr-var)] = string(ptr+1);
break;
}
case 'p': {
char* var = optarg;
char* ptr = strchr(var, '=');
if (!ptr) break;
string key(var, ptr-var);
string value(ptr+1);
properties.set(key, value);
break;
}
default:
print_help();
throw std::runtime_error(string("Unknown switch -") + string(1, (char) optopt));
}
if (optind < argc) {
stringstream buffer;
for (int i = optind; i < argc; i++) {
buffer << " \"" << string(argv[i]) << "\" ";
}
tracker_command = buffer.str();
} else {
print_help();
exit(-1);
}
if(getenv("TRAX_BOUNDED_OVERLAP")) {
printf("TRAX_BOUNDED_OVERLAP: %s \n", getenv("TRAX_BOUNDED_OVERLAP"));
overlap_bounded = strcmpi(getenv("TRAX_BOUNDED_OVERLAP"), "true") == 0;
if (overlap_bounded)
print_debug("Using bounded region overlap calculation\n");
}
if(getenv("TRAX_REGION_LEGACY")) {
printf("TRAX_REGION_LEGACY: %s \n", getenv("TRAX_REGION_LEGACY"));
if (strcmpi(getenv("TRAX_REGION_LEGACY"), "true") == 0)
region_set_flags(REGION_LEGACY_RASTERIZATION);
}
print_debug("Tracker command: '%s'\n", tracker_command.c_str());
if (threshold >= 0)
print_debug("Failure overlap threshold: %.2f\n", threshold);
if (timeout >= 1)
print_debug("Timeout: %d\n", timeout);
if (query_mode) {
// Query mode: in query mode we only check if the client successfuly connects to the tracker and receives introduction message.
// In the future we can also output some basic data about the tracker.
TrackerProcess tracker(tracker_command, environment, timeout, connection, verbosity);
tracker.register_watchdog(must_exit);
if (tracker.query()) {
Metadata metadata = tracker.metadata();
cout << "Tracker name: " << metadata.tracker_name() << endl;
cout << "Tracker description: " << metadata.tracker_description() << endl;
cout << "Tracker family: " << metadata.tracker_family() << endl;
result = 0;
} else {
result = -1;
}
} else {
// Tracking mode: the default mode where the entire sequence is processed.
vector<vector<string> > images;
vector<Region> groundtruth;
vector<Region> initialization;
vector<Region> output;
vector<double> timings;
read_images(images_file, images);
load_trajectory(groundtruth_file, groundtruth);
print_debug("Images loaded from file %s.\n", images_file.c_str());
print_debug("Groundtruth loaded from file %s.\n", groundtruth_file.c_str());
if (images.size() < groundtruth.size()) {
print_debug("Warning: Image sequence shorter that groundtruth. Truncating.\n");
groundtruth = vector<Region>(groundtruth.begin(), groundtruth.begin() + images.size());
}
if (images.size() > groundtruth.size()) {
print_debug("Warning: Image sequence longer that groundtruth. Truncating.\n");
images = vector<vector<string> >(images.begin(), images.begin() + groundtruth.size());
}
if (!initialization_file.empty()) {
load_trajectory(initialization_file, initialization);
print_debug("Initialization loaded from file %s.\n", initialization_file.c_str());
} else {
initialization = vector<Region>(groundtruth.begin(), groundtruth.end());
}
print_debug("Sequence length: %d frames.\n", (int) images.size());
TrackerProcess tracker(tracker_command, environment, timeout, connection, verbosity);
tracker.register_watchdog(must_exit);
tracker.query();
size_t frame = 0;
while (frame < images.size()) {
for (; frame < images.size(); frame++) {
if (!initialization[frame].empty()) break;
print_debug("Skipping frame %d, no initialization data. \n", (int) frame);
output.push_back(Region::create_special(0));
}
if (frame == images.size()) break;
if (!tracker.ready()) {
throw std::runtime_error("Tracker process not alive anymore.");
}
print_debug("Loading initialization images.\n");
Metadata metadata = tracker.metadata();
Region initialize = initialization[frame];
ImageList image = load_images(images[frame], metadata.image_formats(), metadata.channels());
// Start timing a frame
double timing_elapsed;
timer_state timing_start = timer_clock();
if (!tracker.initialize(image, initialize, properties)) {
throw std::runtime_error("Unable to initialize tracker.");
}
bool initialized = true;
while (true) {
// Repeat while tracking the target.
Region status;
Properties additional;
bool result = tracker.wait(status, additional);
// Stop timing a frame
timing_elapsed = timer_elapsed(timing_start);
if (result) {
// Default option, the tracker returns a valid status.
Region reference = groundtruth[frame];
Bounds bounds;
if (overlap_bounded) {
image_size is = image_get_size(image);
print_debug("Bounds for overlap calculation %dx%d\n", is.width, is.height);
bounds = Bounds(0, 0, is.width, is.height);
}
float overlap = reference.overlap(status, bounds);
print_debug("Region overlap: %.2f\n", overlap);
// Check the failure criterion.
if (threshold >= 0 && overlap <= threshold) {
// Break the tracking loop if the tracker failed.
break;
}
if (!initialized)
output.push_back(status);
else {
output.push_back(Region::create_special(1));
initialized = false;
}
timings.push_back(timing_elapsed);
} else {
if (tracker.ready()) {
// The tracker has requested termination of connection.
print_debug("Termination requested by tracker.\n");
break;
} else {
// In case of an error ...
throw std::runtime_error("Unable to contact tracker.");
}
}
frame++;
if (frame >= images.size()) break;
print_debug("Loading frame images.\n");
ImageList image = load_images(images[frame], metadata.image_formats(), metadata.channels());
// Start timing a frame
timing_start = timer_clock();
Properties no_properties;
if (!tracker.frame(image, no_properties))
throw std::runtime_error("Unable to send new frame.");
}
if (frame < images.size()) {
// If the tracker was not successful and we have to consider the remaining frames.
if (reinitialize > 0) {
// If reinitialization is specified after 1 or more frames ...
size_t j = frame + 1;
output.push_back(Region::create_special(2));
timings.push_back(0);
for (; j < frame + reinitialize && j < images.size(); j++) {
output.push_back(Region::create_special(0));
timings.push_back(0);
}
frame = j;
if (frame < images.size()) {
tracker.reset();
tracker.query();
}
} else {
// ... otherwise just fill the remaining part of sequence with empty frames.
size_t j = frame + 1;
output.push_back(Region::create_special(2));
timings.push_back(0);
for (; j < images.size(); j++) {
output.push_back(Region::create_special(0));
timings.push_back(0);
}
break;
}
} else {
}
}
if (output.size() > 0)
save_trajectory(output_file, output);
if (timings.size() > 0 && timing_file.size() > 0)
save_timings(timing_file, timings);
}
} catch (const std::runtime_error &e) {
fprintf(stderr, "Error: %s\n", e.what());
result = -1;
}
exit(result);
}