388 lines
10 KiB
C++
388 lines
10 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 uint64_t feature(Image* img) = 0;
|
|
|
|
//Interpret the bitsets as integers and calculate their distance.
|
|
virtual uint64_t distance(uint64_t a, uint64_t b) {
|
|
return abs((int64_t)(a - b));
|
|
};
|
|
virtual string ToString() = 0;
|
|
virtual fs::path ToPath() {
|
|
return fs::path(this->ToString());
|
|
}
|
|
|
|
//should return true if a > b
|
|
virtual bool bigger(uint64_t a, uint64_t 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;
|
|
}
|
|
};
|
|
|
|
//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:
|
|
uint64_t 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
|
|
uint64_t red = 0, green = 0, blue = 0;
|
|
float numpixels = (float)(n*m);
|
|
if (img->channels() < 3) return 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..
|
|
uint64_t retval = r;
|
|
retval <<= 8;
|
|
retval += g;
|
|
retval <<= 8;
|
|
retval += b;
|
|
return retval;
|
|
}
|
|
uint64_t distance(uint64_t a, uint64_t b) {
|
|
//do some bit shifting magic to get ints back
|
|
int red_a = (a >> 16);
|
|
int green_a = (a << (64-16) >> (64-8));
|
|
int blue_a = (a << (64-8) >> (64-8));
|
|
int red_b = (b >> 16);
|
|
int green_b = (b << (64 - 16) >> (64 - 8));
|
|
int blue_b = (b << (64 - 8) >> (64 - 8));
|
|
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:
|
|
uint64_t feature(Image* img) {
|
|
return 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:
|
|
uint64_t 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;
|
|
uint64_t 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++;
|
|
q = q + local_image->channels();
|
|
}
|
|
}
|
|
delete local_image;
|
|
delete intensities;
|
|
return retval;
|
|
}
|
|
|
|
uint64_t distance(uint64_t a, uint64_t b) {
|
|
return (uint64_t)__builtin_popcountll(a^b);
|
|
}
|
|
string ToString() {
|
|
return "median";
|
|
}
|
|
};
|
|
|
|
//Sobel descriptor
|
|
//Calculates average intensity of the image with
|
|
//sobel convolution applied in both directions
|
|
class sobel : public Descriptor {
|
|
public:
|
|
uint64_t 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
|
|
uint64_t 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;
|
|
}
|
|
uint64_t scaledIntensity = ((float)(intensity / (n*m)) / QuantumRange) * 255;
|
|
delete local_image;
|
|
return 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:
|
|
uint64_t 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
|
|
uint64_t 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++;
|
|
retval <<= 1;
|
|
|
|
p = p + chan;
|
|
prev = cur;
|
|
}
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
uint64_t distance(uint64_t a, uint64_t b) {
|
|
return (uint64_t)__builtin_popcountll(a^b);
|
|
}
|
|
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:
|
|
uint64_t 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
|
|
uint64_t retval = 0;
|
|
for (ssize_t j = 0; j < m; j++) {
|
|
for (ssize_t i = 0; i < n; i++) {
|
|
if(p[0] != 0) retval++;
|
|
retval <<= 1;
|
|
p = p + chan;
|
|
|
|
}
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
uint64_t distance(uint64_t a, uint64_t b) {
|
|
return (uint64_t)__builtin_popcountll(a^b);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
delete img;
|
|
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:
|
|
uint64_t feature(Image* img) {
|
|
Image* local_image = new Image(*img);
|
|
double* converted = CalculateDCT(local_image);
|
|
|
|
double prev = 0;
|
|
uint64_t retval = 0;
|
|
for (int i = 0; i < 64; i++) {
|
|
if (converted[i] > prev) retval++;
|
|
retval <<= 1;
|
|
prev = converted[i];
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
uint64_t distance(uint64_t a, uint64_t b) {
|
|
return (uint64_t)__builtin_popcountll(a^b);
|
|
}
|
|
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:
|
|
uint64_t 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;
|
|
|
|
uint64_t retval = 0;
|
|
for (ssize_t i = 0; i < 64; i++) {
|
|
retval <<= 1;
|
|
if (converted[i] > median) retval++;
|
|
}
|
|
delete local_image;
|
|
return retval;
|
|
}
|
|
uint64_t distance(uint64_t a, uint64_t b) {
|
|
return (uint64_t)__builtin_popcountll(a^b);
|
|
}
|
|
string ToString() {
|
|
return "DCTMedian";
|
|
}
|
|
};
|