459 lines
12 KiB
C++
459 lines
12 KiB
C++
#pragma once
|
|
#include <string>
|
|
#include <boost/filesystem.hpp>
|
|
#include <Magick++.h>
|
|
|
|
#include <bitset> //in which we store our feature vectors
|
|
#include <algorithm> //for sorting
|
|
|
|
using namespace std;
|
|
using namespace Magick;
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
//Virtual base class
|
|
//Holds definitions for all common functions of descriptors,
|
|
//but should be overloaded by a child class
|
|
class Descriptor {
|
|
public:
|
|
virtual bitset<64> feature(Image* img) = 0;
|
|
|
|
//Interpret the bitsets as integers and calculate their distance.
|
|
virtual unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
return abs(((long long)a.to_ullong() - (long long)b.to_ullong()));
|
|
};
|
|
virtual string ToString() = 0;
|
|
virtual fs::path ToPath() {
|
|
return fs::path(this->ToString());
|
|
}
|
|
|
|
//should return true if a > b
|
|
virtual bool bigger(bitset<64> a, bitset<64> b) {
|
|
return true;
|
|
}
|
|
|
|
//Function which should output some example(s) to file
|
|
//that show how this descriptor works.
|
|
virtual void preview(Image* img) {
|
|
cout << "No preview defined. :-( " << endl;
|
|
}
|
|
|
|
//Helper functions
|
|
//TODO: Move these somewhere else. Maybe a friend-class?
|
|
// double* CalculateDCT(Image* img) {
|
|
// double pi = 3.14159265359;
|
|
// img->type(GrayscaleType);
|
|
// img->sample(Geometry("8x8!")); //64 pixels
|
|
// ssize_t n = 8;
|
|
// ssize_t m = 8;
|
|
// Pixels view(*img);
|
|
// int chan = (int)img->channels(); //should be 1, just intensity
|
|
// const Quantum *p = view.getConst(0, 0, n, m); //entire image
|
|
// bitset<64> retval(0);
|
|
// int N = n*m;
|
|
// Image* newimg = new Image(Geometry(n, m), Color("white"));
|
|
// newimg->type(GrayscaleType);
|
|
// Pixels conview(*newimg);
|
|
// Quantum *c = conview.get(0, 0, n, m); //entire converted image
|
|
|
|
// static double converted[64];
|
|
// double* q = converted;
|
|
// for (ssize_t j = 0; j < m; j++) {
|
|
// for (ssize_t i = 0; i < n; i++) {
|
|
// double sum = 0;
|
|
// int k = (j*m) + i;
|
|
// const Quantum *p_s = view.getConst(0, 0, n, m); //entire image
|
|
// for (ssize_t x = 0; x < m; x++) {
|
|
// for (ssize_t y = 0; y < n; y++) {
|
|
// int n = (x*m) + y;
|
|
// double pixel = ((double)p_s[0] / QuantumRange); //rescaled to [0..1]
|
|
// sum += pixel * std::cos((pi / N)*(n + 0.5)*k);
|
|
// p_s = p_s + chan;
|
|
// }
|
|
// }
|
|
// q[0] = sum;
|
|
// q = q++;
|
|
// p = p + chan;
|
|
// }
|
|
// }
|
|
// for (int i = 0; i < 64; i++) {
|
|
// c[0] = converted[i] * 255.0;
|
|
// c++;
|
|
// }
|
|
// delete newimg;
|
|
// //newimg->write("Out.png"); //for debugging purposes
|
|
// return converted;
|
|
// }
|
|
};
|
|
|
|
//Average color of the image.
|
|
//the least significant 24 bits of the feature represent
|
|
//the color intensity of the average color in RGB
|
|
//with 8 bits per color.
|
|
class averageColor : public Descriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
ssize_t n = img->columns();
|
|
ssize_t m = img->rows();
|
|
Pixels view(*img);
|
|
const Quantum *p = view.getConst(0, 0, n, m); //entire image
|
|
unsigned long long red = 0, green = 0, blue = 0;
|
|
float numpixels = (float)(n*m);
|
|
if (img->channels() < 3) return bitset<64>(0);
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
//LTR scanning
|
|
red += p[0];
|
|
green += p[1];
|
|
blue += p[2];
|
|
p = p + 3;
|
|
}
|
|
}
|
|
//get the color values.. implicit cast to int
|
|
int r = ((red/numpixels) / QuantumRange) * 255;
|
|
int g = ((green/numpixels) / QuantumRange) * 255;
|
|
int b = ((blue/numpixels) / QuantumRange) * 255;
|
|
//now store all this into a bitset.. assuming 8 bits per color, if everything went correctly.. which we should check.. probably..
|
|
bitset<64> retval(r);
|
|
retval <<= 8;
|
|
retval |= bitset<64>(g);
|
|
retval <<= 8;
|
|
retval |= bitset<64>(b);
|
|
return retval;
|
|
}
|
|
unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
//do some bit shifting magic to get ints back
|
|
int red_a = (a >> 16).to_ulong();
|
|
int green_a = (a << (64-16) >> (64-8)).to_ulong();
|
|
int blue_a = (a << (64-8) >> (64-8)).to_ulong();
|
|
int red_b = (b >> 16).to_ulong();
|
|
int green_b = (b << (64 - 16) >> (64 - 8)).to_ulong();
|
|
int blue_b = (b << (64 - 8) >> (64 - 8)).to_ulong();
|
|
return abs(red_a - red_b) + abs(green_a - green_b) + abs(blue_a - blue_b);
|
|
}
|
|
string ToString() {
|
|
return "averageColor";
|
|
}
|
|
};
|
|
|
|
//Counts the total number of colors
|
|
//and stores it in bit representation.
|
|
//Upper limit 16777216
|
|
//Only uses least significant 24 bits
|
|
class numColors : public Descriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
return bitset<64>(img->totalColors()-1);
|
|
}
|
|
string ToString() {
|
|
return "numColors";
|
|
}
|
|
};
|
|
|
|
//Median descriptor. See relevant literature.
|
|
//Stores a map of the image (LTR scanned) with 1 iff
|
|
//higher than median and 0 otherwise.
|
|
class median : public Descriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
local_image->type(GrayscaleType);
|
|
local_image->sample(Geometry("8x8!")); //64 pixels
|
|
ssize_t n = 8;
|
|
ssize_t m = 8;
|
|
Pixels view(*local_image);
|
|
float intensity = 0;
|
|
int numpixels = (int)(n*m);
|
|
|
|
const Quantum *p = view.getConst(0, 0, n, m); //entire image
|
|
int* intensities = new int[numpixels];
|
|
int index = 0;
|
|
int chan = (int)local_image->channels(); //should be 1, for a grayscale img
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
//LTR scanning
|
|
intensities[index] = p[0];
|
|
p = p + chan;
|
|
index++;
|
|
}
|
|
}
|
|
//Calculate median:
|
|
sort(intensities, intensities + numpixels);
|
|
//We know where the median is going to be:
|
|
intensity = (float)(intensities[31] + intensities[32]) / 2;
|
|
bitset<64> retval(0);
|
|
const Quantum *q = view.getConst(0, 0, n, m); //entire image
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
retval <<= 1;
|
|
if (q[0] > intensity)
|
|
retval |= bitset<64>(1);
|
|
q = q + local_image->channels();
|
|
}
|
|
}
|
|
delete local_image;
|
|
delete intensities;
|
|
return retval;
|
|
}
|
|
|
|
//biterror
|
|
unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
int biterror = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
if (a[i] != b[i]) biterror++;
|
|
}
|
|
return biterror;
|
|
}
|
|
string ToString() {
|
|
return "median";
|
|
}
|
|
};
|
|
|
|
//Sobel descriptor
|
|
//Calculates average intensity of the image with
|
|
//sobel convolution applied in both directions
|
|
class sobel : public Descriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
//TODO: Some preprocessing for this?
|
|
//img->reduceNoise(4.0);
|
|
local_image->type(GrayscaleType);
|
|
|
|
//Apply sobel convolution
|
|
local_image->convolve(3, sobelX);
|
|
local_image->convolve(3, sobelY);
|
|
|
|
ssize_t n = local_image->rows();
|
|
ssize_t m = local_image->columns();
|
|
|
|
Pixels view(*local_image);
|
|
const Quantum *p = view.getConst(0, 0, m, n); //entire image
|
|
unsigned long long intensity = 0;
|
|
int chan = (int)local_image->channels();
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
//LTR scanning
|
|
intensity += p[0];
|
|
//cout << p[0] << " ";
|
|
p = p + chan;
|
|
}
|
|
//cout << endl;
|
|
}
|
|
int scaledIntensity = ((float)(intensity / (n*m)) / QuantumRange) * 255;
|
|
delete local_image;
|
|
return bitset<64>(scaledIntensity);
|
|
}
|
|
string ToString() {
|
|
return "sobel";
|
|
}
|
|
private:
|
|
const double sobelX[9] = { 1, 0, -1, 2, 0, -2, 1, 0, -1 };
|
|
const double sobelY[9] = { 1, 2, 1, 0, 0, 0, -1, -2, -1 };
|
|
};
|
|
|
|
//Pearson code
|
|
//Creates a map of the 8*8 image which has 1
|
|
//iff the pixel is higher than the next pixel,
|
|
//0 otherwise
|
|
class pearson : public Descriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
local_image->type(GrayscaleType);
|
|
local_image->sample(Geometry("8x8!")); //64 pixels
|
|
ssize_t n = 8;
|
|
ssize_t m = 8;
|
|
Pixels view(*local_image);
|
|
int chan = (int)local_image->channels();
|
|
const Quantum *p = view.getConst(0, 0, n, m); //entire image
|
|
bitset<64> retval(0);
|
|
int prev = p[0]; //so we always start with a 0
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
//LTR scanning
|
|
//NOTE: Scanning order might be quite relevant for this descriptor
|
|
int cur = p[0];
|
|
if(prev < cur) retval |= bitset<64>(1); //XOR
|
|
prev = cur;
|
|
|
|
p = p + chan;
|
|
retval <<= 1;
|
|
}
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
int biterror = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
if (a[i] != b[i]) biterror++;
|
|
}
|
|
return biterror;
|
|
}
|
|
string ToString() {
|
|
return "pearson";
|
|
}
|
|
};
|
|
|
|
//Uses an adaptive thresholding algorithm on an 8*8
|
|
//images. Re-calculates the threshold for each
|
|
//neighbourhood and stores it in a LTR map
|
|
class threshold : public Descriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
local_image->type(GrayscaleType);
|
|
local_image->sample(Geometry("8x8!")); //64 pixels
|
|
local_image->adaptiveThreshold(3, 3); //Thresholding in a moving neighborhood
|
|
ssize_t n = 8;
|
|
ssize_t m = 8;
|
|
Pixels view(*local_image);
|
|
int chan = (int)local_image->channels();
|
|
const Quantum *p = view.getConst(0, 0, n, m); //entire image
|
|
bitset<64> retval(0);
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
if(p[0] != 0)
|
|
retval |= bitset<64>(1); //XOR
|
|
retval <<= 1;
|
|
p = p + chan;
|
|
|
|
}
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
int biterror = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
if (a[i] != b[i]) biterror++;
|
|
}
|
|
return biterror;
|
|
}
|
|
string ToString() {
|
|
return "threshold";
|
|
}
|
|
};
|
|
|
|
//subclass of descriptor which has a DCT converstion function
|
|
//to be subclassed by all descriptors that use DCT.
|
|
class DCTDescriptor : public Descriptor {
|
|
public:
|
|
double* CalculateDCT(Image* pimg) {
|
|
//make a local copy of the image and convert to 8x8 grayscale:
|
|
Image* img = new Image(*pimg);
|
|
double pi = 3.14159265359;
|
|
img->type(GrayscaleType);
|
|
img->sample(Geometry("8x8!")); //64 pixels
|
|
ssize_t n = 8;
|
|
ssize_t m = 8;
|
|
int N = n*m;
|
|
|
|
//make pointers and views of the image
|
|
Pixels view(*img);
|
|
int chan = (int)img->channels(); //should be 1, just intensity
|
|
double* converted = new double[64];
|
|
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
double sum = 0;
|
|
const Quantum *p_s = view.getConst(0, 0, n, m); //entire image
|
|
int k = (i*m) + j;
|
|
for (ssize_t x = 0; x < m; x++) {
|
|
for (ssize_t y = 0; y < n; y++) {
|
|
int n = (y*m) + x;
|
|
double pixel = (((double)p_s[0] / QuantumRange)-0.5)*255; //rescaled to [-127..128]
|
|
sum += pixel * std::cos((pi / N)*(n + 0.5)*k);
|
|
p_s = p_s + chan; //next pixel
|
|
}
|
|
}
|
|
converted[k] = sum;
|
|
}
|
|
}
|
|
return converted;
|
|
}
|
|
};
|
|
|
|
|
|
//Uses an adaptive thresholding algorithm on an 8*8
|
|
//images. Re-calculates the threshold for each 2*2
|
|
//neighbourhood and stores it in a LTR map
|
|
class DCTPearson : public DCTDescriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
double* converted = CalculateDCT(local_image);
|
|
|
|
double prev = 0;
|
|
bitset<64> retval(0);
|
|
for (int i = 0; i < 64; i++) {
|
|
if (converted[i] > prev) {
|
|
retval |= bitset<64>(1); //XOR
|
|
}
|
|
retval <<= 1;
|
|
prev = converted[i];
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
int biterror = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
if (a[i] != b[i]) biterror++;
|
|
}
|
|
return biterror;
|
|
}
|
|
string ToString() {
|
|
return "DCTPearson";
|
|
}
|
|
void preview(Image* img){
|
|
Image* dct = new Image(*img);
|
|
Image* local_image = new Image(*img);
|
|
double* converted = CalculateDCT(dct);
|
|
cout << "---" << endl;
|
|
for (int i = 0; i < 64; i++) {
|
|
cout << setw(11) << converted[i] << " | ";
|
|
if ( i % 8 == 7) cout << endl;
|
|
}
|
|
cout << "---" << endl;
|
|
//Write transformed image
|
|
local_image->type(GrayscaleType);
|
|
local_image->sample(Geometry("8x8!")); //64 pixels
|
|
local_image->write("DCTPearson.png");
|
|
}
|
|
};
|
|
|
|
class DCTMedian : public DCTDescriptor {
|
|
public:
|
|
bitset<64> feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
double* converted = CalculateDCT(local_image);
|
|
double bullshit[64];
|
|
for (ssize_t i = 0; i < 64; i++) {
|
|
bullshit[i] = converted[i];
|
|
}
|
|
sort(bullshit, bullshit + 64);
|
|
float median = (float)(bullshit[31] + bullshit[32]) / 2;
|
|
|
|
bitset<64> retval(0);
|
|
for (ssize_t i = 0; i < 64; i++) {
|
|
retval <<= 1;
|
|
if (converted[i] > median)
|
|
retval |= bitset<64>(1);
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
unsigned long long distance(bitset<64> a, bitset<64> b) {
|
|
int biterror = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
if (a[i] != b[i]) biterror++;
|
|
}
|
|
return biterror;
|
|
}
|
|
string ToString() {
|
|
return "DCTMedian";
|
|
}
|
|
};
|