https://doi.org/10.5201/ipol.2014.104
commonUtil.cpp
/*
* Copyright 2009-2013 Yi-Qing WANG
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file commonUtil.cpp
* @brief common routines for image IO
* @author Yi-Qing WANG <yiqing.wang@polytechnique.edu>
*/
#include <iostream>
#include <algorithm>
#include <fstream>
#include <sys/time.h>
#include <string>
#include <limits>
#include <utility>
#include <vector>
#include "io_png.h"
#include "commonUtil.h"
#include "Detector.h"
#include <Eigen/Dense>
#define VERBOSE true
#define UNIT 1e2
#define STORAGE "trainData"
using namespace std;
using namespace Eigen;
typedef Matrix<long double,Dynamic,Dynamic> MatrixXld;
//fail and messaging
void fail(string const & message){
cerr << "ERROR: " << message << endl;
exit(EXIT_FAILURE);
}
//timer for routines
void times(const char * which){
/* If which is not empty, print the times since the previous call. */
static double last_wall = 0.0, last_cpu = 0.0;
double wall, cpu;
struct timeval tv;
clock_t stamp = 0;
wall = last_wall;
cpu = last_cpu;
if (gettimeofday(&tv,NULL) != 0 || (stamp = clock()) == (clock_t)-1){
string message("Unable to get times for ");
fail(message + which);
}
last_wall = tv.tv_sec+1.0e-6*tv.tv_usec;
last_cpu = stamp/(double)CLOCKS_PER_SEC;
if (strlen(which) > 0){
wall = last_wall-wall;
cpu = last_cpu-cpu;
cout << endl << which << " time = " << wall << " seconds, CPU = " << cpu << " seconds" << endl << endl;
}
}
//read in an image in two modes
//selectable with outputGray flag
void imread(
const char * fileName
, int & nRows
, int & nCols
, int & nChannels
, MatrixXf *& image
, bool outputGray
){
//readin mode selection
size_t nx, ny, nc;
float * pixelStream = NULL;
pixelStream = outputGray ? read_png_f32_gray(fileName, &nx, &ny) : read_png_f32(fileName, &nx, &ny, &nc);
if(pixelStream == NULL){
string message("Unable to get the image ");
fail(message+fileName);
}
//return these parameters
nCols = (int)nx;
nRows = (int)ny;
const char * end = outputGray ? " as gray." : ".";
nChannels = outputGray ? 1 : (int)nc;
//reduce log
if(VERBOSE && false)
cout << "INFO: read in " << fileName << " of dimension: (" << ny << " , " << nx << " , " << nChannels << ")" << end << endl;
//input stream assumes row-major while Eigen defaults to column-major
Map<MatrixXf> parallel(pixelStream, nCols, nRows*nChannels);
image = new MatrixXf [nChannels];
for( int ch = 0; ch < nChannels; ch++ )
image[ch] = parallel.block(0, ch*nRows, nCols, nRows).transpose();
//release
free(pixelStream);
}
//image write in MATLAB style, after which MatrixXf * image is released
void imwrite(
const char * fileName
, MatrixXf * image
, int nChannels
){
//allocate
int nCols = image[0].cols();
int nRows = image[0].rows();
int pixelsPerChannel = nCols*nRows;
float * output = new float [pixelsPerChannel*nChannels];
//this part should be straightforward but still be careful with the order
#pragma omp parallel for schedule( static )
for(int j = 0; j < nCols; j++)
for(int i = 0; i < nRows; i++)
for(int ch = 0; ch < nChannels; ch++)
output[ch*pixelsPerChannel + i*nCols + j] = image[ch](i,j);
//output image
write_png_f32(fileName, output, (size_t) nCols, (size_t) nRows, (size_t) nChannels);
//reduce log
if(VERBOSE && false)
cout << "INFO: write the image " << fileName << " to local folder." << endl;
//release
delete [] output;
delete [] image;
}
//compute an integral image in linear time
void buildIntegralImage(
MatrixXf & image
, MatrixXld & integralImage
){
int nRows = image.rows();
int nCols = image.cols();
//O(n) complexity: however no parallel should be used
for(int i = 0; i < nRows; i++)
for(int j = 0; j < nCols; j++){
integralImage(i,j) = image(i,j);
integralImage(i,j) += i > 0 ? integralImage(i-1,j) : 0;
integralImage(i,j) += j > 0 ? integralImage(i,j-1) : 0;
integralImage(i,j) -= min(i,j) > 0 ? integralImage(i-1,j-1) : 0;
}
}
//inverse of buildIntegralImage()
long double sumImagePart(
MatrixXld & integralImage
, int ui
, int uj
, int ir //column length
, int jr //row length
){
//rectangle's other pair of coordinates
int di = ui + ir - 1;
int dj = uj + jr - 1;
//standard operation
long double sum = integralImage(di,dj);
sum -= ui > 0 ? integralImage(ui-1,dj) : 0;
sum -= uj > 0 ? integralImage(di,uj-1) : 0;
sum += min(ui, uj) > 0 ? integralImage(ui-1,uj-1) : 0;
return sum;
}
//convert the pathFile to char array and return path count
//if a valid blackList is provided, delete blackListed (1) samples
//update the second argument while others are purely inputs
int pathFile2charArray(
const char * pathFile
, char **& addr
, VectorXi * blackList
, int sign
){
//delete these blackListed paths
bool doNothing = blackList == NULL ? true : false;
int listSize = blackList == NULL ? 0 : blackList[sign].size();
//a temporary file storing sample to retain
char buffer [100];
sprintf(buffer, "temporary%s", pathFile);
const char * keeped = buffer;
//sanity check
fileExist(keeped);
//actually create this file if !doNothing
if(!doNothing){
FILE * pFile;
pFile = fopen(keeped, "w");
if( pFile == NULL ){
string path("Unable to create an empty ");
fail(path+keeped);
}else
fclose(pFile);
}
//initialize the returned value
int pathCount = 0;
//meat
//if doNothing first loop determines file size, second loop reads in paths
//if not doNothing, first loop deletes blackListed lines, second loop reads in paths
for( int loop = 0; loop < 2; loop++ ){
//read in pathFile
ifstream myfile(pathFile);
if(!myfile.is_open()){
string path("Please provide a valid ");
fail(path+pathFile);
}
string line;
//an offset to make the line count (total) right
//this will also be reflected the condition 5 lines later
int total = -1;
while( myfile.good() ){
//count lines
getline( myfile, line );
total += 1;
if(!doNothing && total < listSize && loop == 0){
//not blackListed
if(blackList[sign][total] == 0){
ofstream output;
output.open(keeped, ios_base::app);
output << line << endl;
output.close();
}
}
//only do this at the second loop
if( loop == 1 && total < pathCount ){
//convert string to char array
addr[total] = new char [line.size()+1];
addr[total][line.size()] = 0;
memcpy(addr[total], line.c_str(), line.size());
}
}
myfile.close();
//first loop just count the number of lines in the file
if( loop == 0 ){
//remove those blackListed
pathCount = blackList == NULL ? total : total - blackList[sign].sum();
//now delete samples if necessary
if(!doNothing){
removeFile(pathFile);
if(rename(keeped, pathFile)){
string message("Unable to rename ");
fail(message+keeped+" to "+pathFile);
}
}
//now if there's a problem, I'll be able to track it down
if(pathCount < 1){
cout << "pathCount = " << pathCount << " No grayscale PNG examples available in " << pathFile << endl;
return 0;
}
//allocate
addr = new char * [pathCount];
}
}
return pathCount;
}
//collect training image set provided by a pathFile
//plus a blackList
//and return these images in matrix form
int readImagesFromPathFile(
const char * pathFile
, MatrixXf ** & images
, VectorXi * blackList
, int sign
){
char ** addr = NULL;
int sampleCount = pathFile2charArray(pathFile, addr, blackList, sign);
if(sampleCount == 0)
return sampleCount;
//now read in all the images in grayscale
//their addresses are stored in addr[]
//rnRows and rnCols are the reference's size
//all samples should have the same size
//otherwise, the algorithm will abort
int nRows, nCols, nChannels;
//g++ complains about uninitialized values
int rnRows = 0;
int rnCols = 0;
images = new MatrixXf * [sampleCount];
for(int sample = 0; sample < sampleCount; sample++){
const char * file = addr[sample];
imread(file, nRows, nCols, nChannels, images[sample], true);
//sanity check
if(sample == 0){
rnRows = nRows;
rnCols = nCols;
if(VERBOSE)
cout << "INFO: this is the size reference image (" << rnRows << ", " << rnCols << ")." << endl;
}
if(abs(rnRows-nRows)+abs(rnCols-nCols)){
string message("Sample size should not vary.");
fail(message);
}
}
//release
for(int sample = 0; sample < sampleCount; sample++)
delete [] addr[sample];
delete [] addr;
//return
return sampleCount;
}
//write out a vector to disk if there isn't enough memory available
void writeToDisk(
VectorXf & dataVector
, char const * prefix
, int index
){
//cut the dataVector into pieces so that it won't be costly to retrieve the coordinate values later
//each piece shall be no greater than UNIT
int size = dataVector.size();
int unit = UNIT;
int pieceCount = ceil(size/(float)unit);
int remainder = size%unit;
//flush out
#pragma omp parallel for schedule(static)
for(int piece = 0; piece < pieceCount; piece++){
char buffer[100];
sprintf(buffer, "%s/%s_%d_%d", STORAGE, prefix, index, piece);
const char * fname = buffer;
//sanity check
fileExist(fname);
//write out a segment of the feature vector
ofstream output;
//start anew so that I don't have to delete them
output.open(fname, ios_base::trunc);
output.precision(10);
output.setf(ios::fixed, ios::floatfield);
int start = piece * unit;
int length = unit;
if(piece == pieceCount - 1 && remainder)
length = remainder;
output << dataVector.segment(start, length) << endl;
output.close();
}
}
//test where a file exists
bool fileExist(
const char * fname
, bool del
){
ifstream test(fname);
if(test){
cerr << "ERROR: curiously already a " << fname << " here in the folder!" << endl;
if(del)
removeFile(fname);
return true;
}else{
test.close();
return false;
}
}
//usually one cannot expect a lot memory available for the training task
//so instead I write every feature onto the disk and then read in
//whenever necessary
float readFromDisk(
char const * prefix
, int index
, int entry
){
//hash
char buffer[100];
int unit = UNIT;
sprintf(buffer, "%s/%s_%d_%d", STORAGE, prefix, index, entry/unit);
const char * fname = buffer;
ifstream fileIn(fname);
string line;
//with a reduced file size, it's ok to do this
if(fileIn.is_open()){
int localCoordinate = entry%unit;
fileIn.seekg(ios_base::beg);
for(int i = 0; i < localCoordinate; i++)
fileIn.ignore(numeric_limits<streamsize>::max(),'\n');
//read in
fileIn >> line;
fileIn.close();
}else{
string message("unable to open ");
fail(message+fname);
}
return atof(line.c_str());
}
//order definition for this type of pairs
//compare only the feature values
bool myPairOrder(
const pair<float, int>& one
, const pair<float, int>& other
){
return one.first < other.first;
}
//read in a feature and put them in ascending order
//and record at the same time the permuted example order
vector< pair<float, int> > * writeOrganizedFeatures(
int featureCount
, int sampleCount
, RowVectorXf * & featureVectors
){
bool allInMemory = featureVectors == NULL ? false : true;
vector< pair<float, int> > * toReturn = NULL;
if(allInMemory){
toReturn = new vector< pair<float, int> > [featureCount];
cout << "INFO: store sorted ascending features in memory." << endl;
}else
cout << "INFO: store sorted ascending features in disk." << endl;
//start sorting examples according to individual feature values
times("");
for(int feature = 0; feature < featureCount; feature++){
vector< pair<float, int> > ascendingFeatures;
//read in features from the disk or memory
for(int sample = 0; sample < sampleCount; sample++){
if(allInMemory)
ascendingFeatures.push_back(make_pair(featureVectors[feature][sample], sample));
else
ascendingFeatures.push_back(make_pair(readFromDisk("FeatureVector", sample, feature), sample));
}
//sort
sort(ascendingFeatures.begin(), ascendingFeatures.end(), myPairOrder);
if(allInMemory){
featureVectors[feature].resize(0);
toReturn[feature] = ascendingFeatures;
}else{
//transfer to disk
VectorXf permutedFeatures;
permutedFeatures.setZero(sampleCount);
VectorXf permutedSamples;
permutedSamples.setZero(sampleCount);
for(int k = 0; k < sampleCount; k++){
permutedFeatures(k) = ascendingFeatures[k].first;
permutedSamples(k) = ascendingFeatures[k].second;
}
//write
writeToDisk(permutedSamples, "OrganizedSample", feature);
writeToDisk(permutedFeatures, "OrganizedFeatures", feature);
}
}
times("featureReadingSorting");
if(allInMemory)
delete [] featureVectors;
else
removeFeatures(featureCount, sampleCount);
return toReturn;
}
//remove either feature vectors
void removeFeatures(
int featureCount
, int sampleCount
){
//here we delete feature vectors
int unit = UNIT;
int pieceCount = ceil(featureCount/(float)unit);
for(int k = 0; k < sampleCount; k++){
char buffer[100];
const char * fname = buffer;
for(int piece = 0; piece < pieceCount; piece++){
sprintf(buffer, "%s/%s_%d_%d", STORAGE, "FeatureVector", k, piece);
removeFile(fname);
}
}
}
void removeFile(const char * fname){
if(remove(fname)){
string message("Unable to delete ");
fail(message+fname);
}
}
//print some info of the feature
void whatFeature(int featureIndex){
int * patternParam = Detector::getFeature(featureIndex);
int pattern = patternParam[0];
cout << "\tFeature: " << featureIndex;
string graphicPattern;
if(pattern == 1){
cout << " Vertical Range: " << patternParam[3];
cout << " Horizontal Range: " << patternParam[4]+patternParam[5]+patternParam[6];
graphicPattern = "\n\t11111111111111111100000000000000000\n\t11111111111111111100000000000000000\n";
}else if(pattern == 2){
cout << " Vertical Range: " << patternParam[3];
cout << " Horizontal Range: " << patternParam[4]+patternParam[5]+patternParam[6];
graphicPattern = "\n\t11111111111100000000000011111111111\n\t11111111111100000000000011111111111\n";
}else if(pattern == 3){
cout << " Vertical Range: " << patternParam[3]+patternParam[4]+patternParam[5];
cout << " Horizontal Range: " << patternParam[6];
graphicPattern = "\n\t11111111111111111111111111111111111\n\t00000000000000000000000000000000000\n";
}else if(pattern == 4){
cout << " Vertical Range: " << patternParam[3]+patternParam[4]+patternParam[5];
cout << " Horizontal Range: " << patternParam[6];
graphicPattern = "\n\t11111111111111111111111111111111111\n\t00000000000000000000000000000000000\n\t11111111111111111111111111111111111\n";
}else if(pattern == 5){
cout << " Vertical Range: " << patternParam[3]+patternParam[4];
cout << " Horizontal Range: " << patternParam[6]+patternParam[5];
graphicPattern = "\n\t11111111111111111000000000000000000\n\t00000000000000000111111111111111111\n";
}else{
graphicPattern = "ERROR ERROR ERROR";
}
int i = patternParam[1];
int j = patternParam[2];
cout << " Computed at (" << i << ", " << j << ")\n" << graphicPattern << endl;
}