//Orthogonal Basis Transforms //Grant Schindler, 2007
int N = 256, H = 192; //Image Dimensions float[][] T = new float[N][N]; //Transform Matrix float[][] T_; //Inverse Transform Matrix float[][] X = new float[H][N]; //Transformed Image float[][] X_ = new float[H][N]; //Transformed Image with Zeroed Elements float[][] I; //Original Image float[][] I_ = new float[H][N]; //Reconstructed Image PImage imgI,imgI_,imgT,imgX; //Displayable Image for Each Matrix
//------------------------------------------------------------------------------------- // Orthonormal Basis Transform: 2 Simple Functions ------------------------------------ //-------------------------------------------------------------------------------------
//Transform - Use T to Compute Coefficients for Each Image Row void Transform(){ for (int y=0; y < H; y++) X[y] = MatrixVectorMultiply(T,I[y],N,N); }
//InverseTransform - Reconstruct Original Image by Inverting Transform void InverseTransform(){ for (int y=0; y < H; y++) I_[y] = MatrixVectorMultiply(T_,X_[y],N,N); }
//------------------------------------------------------------------------------------- // Discrete Cosine Transform (Fourier Variant) ---------------------------------------- //-------------------------------------------------------------------------------------
//Create Matrix T of Cosine Waves void GetCosineMatrix(){ for (int k=0; k < N; k++){ for (int n=0; n < N; n++){ T[k][n] = cos(PI * k * (n+0.5)/N); T[k][n] *= sqrt(2.0/N); //Make T orthogonal (inverse=transpose) if (k==0) T[k][n] *= 1.0/sqrt(2.0); //Make T orthogonal (inverse=transpose) } } T_ = Transpose(T,N,N); //Create Transpose/Inverse to Invert Transform Later }
//------------------------------------------------------------------------------------- // Haar Wavelets ---------------------------------------------------------------------- //-------------------------------------------------------------------------------------
//Create Matrix T of Haar Wavelets void GetHaarMatrix(){ for (int k=0; k < N; k++){ float m = 8.0 - floor(log(max(1,k))/log(2)); //Level float o = k - pow(2.0, 8.0 - m); //Offset for (int n=0; n < N; n++){ T[k][n] = Haar(pow(2.0,-m)*n - o); if (k==0) T[k][n] = 1.0; T[k][n] *= pow(2.0,-m/2); //Make T orthogonal (inverse=transpose) } } T_ = Transpose(T,N,N); //Create Transpose/Inverse to Invert Transform Later }
float Haar(float x) { if (x >= 0.0 && x < 0.5) {return 1.0;} else if (x >= 0.5 && x < 1.0) {return -1.0;} else {return 0.0;} }
//------------------------------------------------------------------------------------- // Identity Matrix as Basis ----------------------------------------------------------- //-------------------------------------------------------------------------------------
//Create Identity Matrix void GetIdentity(){ T = Identity(N); T_ = Transpose(T,N,N); //Create Transpose/Inverse to Invert Transform Later }
//------------------------------------------------------------------------------------- // Simple PCA - Power Iteration -> Eigenvectors --------------------------------------- //-------------------------------------------------------------------------------------
void GetEigenvectors(boolean truncate) { //Create Eigenvalue Matrix - Set to All Zeros float[][] V = Zeros(N,N);
//Construct Covariance Matrix (I-Transpose x I) - Has Same Eigenvectors as I float[][] A = MatrixMultiply(Transpose(I,H,N),I,N,H); float[] b = new float[N]; float Av = 1.0, lambda = 1.0;
//Compute Largest Eigenvector, Subtract, Iterate for (int e = 0; e < N; e++){
//Randomly Initialize Eigenvector b = VectorRandom(N);
//Iteratively Multiply Matrix A by Vector x - Coverges to Largest Eigenvector of Columns of A for (int i = 0; i < 3; i++){ //Amazing that 3 iterations is enough b = MatrixVectorMultiply(A,b,N,N); Av = b[0]; //A*v = lambda*v -> lambda = (A*v)[0]/v[0]; b = VectorNormalize(b,N); }
//Copy New Eigenvector into Matrix arraycopy(b,V[e]);
//Project Each Column of A onto Eigenvector to Get Coefficients float[] c = MatrixVectorMultiply(Transpose(A,N,N),b,N,N);
//Reconstruct Covariance Matrix Using Only the Current Eigenvector float[][] R = OuterProduct(b,c,N,N);
//Update A by Subtracting Out Reconstructed Matrix (Eigenvector x Coefficients) A = MatrixSubtract(A,R,N,N);
//Check Eigenvalue Size -- Terminate if Below Threshold (Just Modelling Noise) lambda = Av/b[0]; if (lambda < 0.1 && truncate) e = N+1; }
//Set Transform Matrix to Eigenvalue Matrix (Ditto for Inverse-Transform), Update Image T = V; T_ = Transpose(T,N,N); }
//------------------------------------------------------------------------------------- // Random Orthonormal Basis ----------------------------------------------------------- //-------------------------------------------------------------------------------------
void GetRandomOrthogonalBasis() { float[][] V = Zeros(N,N); float[] b = new float[N];
//Choose Random Vector, Orthogonalize, Iterate for (int e = 0; e < N; e++){
//Randomly Initialize Vector b = VectorRandom(N); b = VectorNormalize(b,N);
if (e > 0){ //Project Random Vector onto Basis-So-Far float[] c = MatrixVectorMultiply(V,b,e,N);
//Reconstruct Random Vector From Basis-So-Far float[] d = MatrixVectorMultiply(Transpose(V,e,N),c,N,e);
//Subtract Reconstruction From Random Vector: Residual is Orthogonal to Subspace Spanned by Basis b = VectorSubtract(b,d,N); b = VectorNormalize(b,N); }
//Copy New Vector into Matrix arraycopy(b,V[e]); }
//Set Transform Matrix to Eigenvalue Matrix (Ditto for Inverse-Transform), Update Image T = V; T_ = Transpose(T,N,N); }
//------------------------------------------------------------------------------------- // Matrix Functions (Basic) ----------------------------------------------------------- //-------------------------------------------------------------------------------------
//Empty Matrix float[][] Zeros(int m, int n){ float[][] Z = new float[m][n]; for (int i = 0; i < m; i++){ for (int j=0; j < n; j++){ Z[i][j] = 0.0;}} return Z; }
//Identity Matrix float[][] Identity(int n){ float[][] ID = Zeros(n,n); for (int i=0; i < n; i++){ ID[i][i] = 1.0;} return ID; }
//Transpose of a Matrix float[][] Transpose(float [][] M, int m, int n){ float[][] MT = new float[n][m]; for (int i=0; i < m; i++){ for (int j=0; j < n; j++){ MT[j][i] = M[i][j];}} return MT; }
//Muliply two (nx1) vectors v1 and v2 float InnerProduct(float[] v1, float[] v2, int n){ float sum = 0.0; for (int i=0; i < n; i++){ sum += v1[i] * v2[i];} return sum; }
//Multiply mx1, 1xn vectors to get mxn matrix float[][] OuterProduct(float[] v1, float[] v2, int m, int n){ float[][] C = new float[m][n]; for (int i=0; i < m; i++){ for (int j=0; j < n; j++){ C[i][j] = v1[i] * v2[j];}} return C; }
//Multiply (mxn) matrix M with (nx1) vector v float[] MatrixVectorMultiply(float[][] M, float[] v, int m, int n){ float[] x = new float[m]; for (int i=0; i < m; i++){ x[i] = InnerProduct(M[i],v,n);} return x; }
float[][] MatrixMultiply(float[][] A, float[][] B, int m, int n){ float[][] B_ = Transpose(B,n,m); float[][] C = new float[m][m]; for (int i=0; i < m; i++){ for (int j=0; j < m; j++){ C[i][j] = InnerProduct(A[i],B_[j],n);}} return C; }
//Multiply a vector by a scalar float[] VectorScalarMultiply(float[] v, float s, int n){ float[] result = new float[n]; for (int i=0; i < n; i++){ result[i] = s * v[i];} return result; }
//Subtract matrix B from matrix A float[][] MatrixSubtract(float [][] A, float [][] B, int m, int n){ float[][] C = new float[m][n]; for (int i=0; i < m; i++){ C[i] = VectorSubtract(A[i],B[i],n);} return C; }
float[] VectorSubtract(float[] a, float[] b, int n){ float[] c = new float[n]; for (int j=0; j < n; j++){ c[j] = a[j] - b[j];} return c; }
//Normalize a vector to unit length float[] VectorNormalize(float[] v, int n){ float L = InnerProduct(v,v,n); return VectorScalarMultiply(v, 1.0/(sqrt(L)), n); }
float[] VectorRandom(int n){ float[] b = new float[n]; for (int i=0; i < n; i++){ b[i] = random(-1,1);} return b; }
//------------------------------------------------------------------------------------- // Coefficient Matrix Editing -------------------------------------------------------- //-------------------------------------------------------------------------------------
void ZeroColumns() { float k3 = map(sliderVal,slidersLeft,slidersRight,0,256); for (int y = 0; y < H; y++) {arraycopy(X[y],X_[y]);} for (int k = (int)k3; k < N; k++) {Zero(X_,H,N,-1,k);} }
//Zero Out Specified Row or Column of a Matrix void Zero(float [][] M, int m, int n, int r, int c) { for (int i=0; i < m; i++) for (int j=0; j < n; j++) if (i == r || j == c) M[i][j] = 0.0; }
//------------------------------------------------------------------------------------- // Matrix-Image Conversion ------------------------------------------------------------ //-------------------------------------------------------------------------------------
//Convert a matrix to an image which can be displayed or saved PImage MatrixToImage(float[][] M, int m, int n, float lo, float hi) { PImage img = createImage(n,m,RGB); img.loadPixels();
for (int x=0; x < n; x++) for (int y=0; y < m; y++) img.pixels[y*n+x] = color(map(M[y][x],lo,hi,0,255));
img.updatePixels(); return img; }
//Convert image to a matrix float[][] MatrixOfImage(PImage img) { float[][] I = new float[img.height][img.width]; for (int y=0; y < H; y++) for (int x = 0; x < N; x++) I[y][x] = map(brightness(img.pixels[y*N+x]),0,255,-1,1); return I; }
//------------------------------------------------------------------------------------- // Image Handling --------------------------------------------------------------------- //-------------------------------------------------------------------------------------
float[] s = {1, 0.25, sqrt(2.0/N), 0.001, 0.25}; //Basis Image Contrast Scaling Factors
void initTransform() { if (mode == 1) GetEigenvectors(true); //PCA if (mode == 2) GetCosineMatrix(); //Fourier if (mode == 0) GetIdentity(); //Identity if (mode == 4) GetRandomOrthogonalBasis(); //Random Orthogonal Basis if (mode == 3) GetHaarMatrix(); //Haar Wavelet Basis
Transform(); ZeroColumns(); InverseTransform();
imgT = MatrixToImage(T,N,N,-s[mode],s[mode]); imgI_ = MatrixToImage(I_,H,N,-1,1); imgX = MatrixToImage(X,H,N,-1,1); }
void LoadImage(int i) { //Load Image loaded = i; imgI = loadImage((i+1) + ".jpg"); imgI.filter(GRAY); imgI.loadPixels();
//Convert Current Image to Matrix I = MatrixOfImage(imgI); }
void updateReconstructedImage() { //Only Recompute Inverse Transform if New Columns Have Been Zeroed Out of the Coefficient Matrix if (valid < 3){ ZeroColumns(); InverseTransform(); imgI_ = MatrixToImage(I_,H,N,-1,1); imgX = MatrixToImage(X_,H,N,-1,1); } }
//------------------------------------------------------------------------------------- // User Interaction ------------------------------------------------------------------- //------------------------------------------------------------------------------------- int buttonsY = 240 , buttonsX[] = new int[7]; //UI Element Positions int tabsY = N + 38, tabsX[] = new int[5]; float slidersLeft = N + 35; float slidersRight= N + slidersLeft + 1; float sliderVal = slidersRight; int slidersY = 225; PFont font10, font12; int loaded = 0, mode = 1, valid = 0; boolean sliderActive = false; String[] tabText = {"Identity", "PCA", "Fourier", "Haar", "Random"};
//Initialization void setup() { noLoop(); size(5+(N+30)*3-25,N+30 + 30); frameRate(15); font12 = loadFont("Helvetica-Bold-12.vlw"); font10 = loadFont("Helvetica-Bold-10.vlw"); buttonsX[0] = 42; //Image Number Buttons for (int i=1; i < 7; i++) {buttonsX[i] = buttonsX[i-1] + 30;} tabsX[0] = 30 + (N+30)*2; //Basis Tabs for (int i=1; i < 5; i++) {tabsX[i] = tabsX[i-1] + 51;}
LoadImage(0); //Load Image initTransform(); //Initialize and Perform PCA valid = 0; loop(); }
//Draw function only really executes when a change has invalidated the current image reconstruction void draw() { if (valid < 3){ background(221,221,204); stroke(0); fill(255);
//Draw Images with Frames rect(3 ,23,N+3,H+3); image(imgI_, 5 , 25); rect(3 + N+30 ,23,N+3,H+3); image(imgX , 5 + N+30 , 25); rect(3 + (N+30)*2,23,N+3,N+3); image(imgT , 5 + (N+30)*2, 25);
//Draw Text textFont(font12); textAlign(CENTER); fill(0); text("=",276,110+20); text("x",563,110+20); text("Reconstructed Image", 133 , 17); text("Transformed Image" , 133+ N+30 , 17); text("Orthogonal Basis" , 133+(N+30)*2 , 17); //Draw Image Number Buttons for (int i=0; i < 7; i++){ fill((loaded==i) ? 200:255); rect(buttonsX[i]-9,buttonsY-12,16,16); fill(0); text(i+1,buttonsX[i],buttonsY); } //Draw Basis Buttons textFont(font10); textAlign(CENTER); for (int i=0; i < 5; i++){ fill((mode==i) ? 200:255); rect(tabsX[i]-25,tabsY-12,51,16); fill(0); text(tabText[i],tabsX[i],tabsY); }
//Draw Sliders fill(255); rect(slidersLeft,slidersY+3,slidersRight-slidersLeft,4); fill(128); rect(sliderVal+5,slidersY+3,slidersRight-(sliderVal+5),4); fill(0); rect(sliderVal-5,slidersY,10,10); int nrComponents = (int) map(sliderVal,slidersLeft,slidersRight,0,256); text(nrComponents + " Basis Vectors Used", 133+ N+30 , N + 5); valid = max(0,valid+1); //Formerly Boolean, but Sometimes Didn't Draw at Start :( } }
//------------------------------------------------------------------------------------- // Mouse and Keyboard Interaction ----------------------------------------------------- //------------------------------------------------------------------------------------- char[] numKeys = {'1','2','3','4','5','6','7'}; char[] tabKeys = {'i','p','f','h','r'}; void mouseReleased() {sliderActive = false;}
void keyPressed(){ if (key == ',') {sliderVal = max(sliderVal-1,slidersLeft+2); valid--;} if (key == '.') {sliderVal = min(sliderVal+1,slidersRight); valid--;} for (int i=0; i < 7; i++) {if (key == numKeys[i]) {LoadImage(i); initTransform(); valid--;}} for (int i=0; i < 5; i++) {if (key == tabKeys[i]) {mode = i; initTransform(); valid--;}} updateReconstructedImage(); }
void mouseDragged(){ if (sliderActive){sliderVal = max(min(mouseX,slidersRight),slidersLeft+2); valid--;} updateReconstructedImage(); }
void mousePressed(){ for (int i=0; i < 7; i++){ //Image Number Buttons if (sq(mouseX-buttonsX[i]) + sq(mouseY-buttonsY) < sq(10)){ LoadImage(i); initTransform(); valid--;}} for (int i=0; i < 5; i++){ //Basis Tabs if (sq(mouseX-tabsX[i]) < sq(25) && sq(mouseY-tabsY) < sq(10)){ mode = i; initTransform(); valid--;}} if (mouseX > (slidersLeft-10) && mouseX <= (slidersRight+10)){ sliderActive = true;} //Slider mouseDragged(); }
|