diff --git a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/CXX.includecache b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/CXX.includecache
index 49e0ac8d23ca6c3988b74935376072cbbbae6381..bb2a916458b90e7a5d622425aab8edc689bc9e8c 100644
--- a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/CXX.includecache
+++ b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/CXX.includecache
@@ -30,6 +30,16 @@ opencv2/aruco/charuco.hpp
 opencv2/calib3d.hpp
 -
 
+/home/thomas/Documents/Minimisation/apps/specular_estimation/src/Macbeth.h
+stdio.h
+-
+opencv/cv.h
+-
+opencv/highgui.h
+-
+opencv2/calib3d/calib3d.hpp
+-
+
 /home/thomas/Documents/Minimisation/apps/specular_estimation/src/OpenCV.h
 opencv2/opencv.hpp
 -
@@ -175,6 +185,8 @@ OpenGL.h
 /home/thomas/Documents/Minimisation/apps/specular_estimation/src/OpenGL.h
 Ceres.h
 /home/thomas/Documents/Minimisation/apps/specular_estimation/src/Ceres.h
+Macbeth.h
+/home/thomas/Documents/Minimisation/apps/specular_estimation/src/Macbeth.h
 
 /home/thomas/Documents/Minimisation/apps/specular_estimation/src/texture.cpp
 stdio.h
@@ -1726,10 +1738,28 @@ ceres/internal/reenable_warnings.h
 
 /usr/local/include/ceres/version.h
 
+/usr/local/include/opencv/cv.h
+opencv2/core/core_c.h
+/usr/local/include/opencv/opencv2/core/core_c.h
+opencv2/imgproc/imgproc_c.h
+/usr/local/include/opencv/opencv2/imgproc/imgproc_c.h
+opencv2/photo/photo_c.h
+/usr/local/include/opencv/opencv2/photo/photo_c.h
+opencv2/video/tracking_c.h
+/usr/local/include/opencv/opencv2/video/tracking_c.h
+opencv2/objdetect/objdetect_c.h
+/usr/local/include/opencv/opencv2/objdetect/objdetect_c.h
+
 /usr/local/include/opencv/cxcore.h
 opencv2/core/core_c.h
 /usr/local/include/opencv/opencv2/core/core_c.h
 
+/usr/local/include/opencv/highgui.h
+opencv2/core/core_c.h
+/usr/local/include/opencv/opencv2/core/core_c.h
+opencv2/highgui/highgui_c.h
+/usr/local/include/opencv/opencv2/highgui/highgui_c.h
+
 /usr/local/include/opencv2/aruco.hpp
 opencv2/core.hpp
 -
@@ -1760,6 +1790,10 @@ opencv2/core/affine.hpp
 opencv2/calib3d/calib3d_c.h
 /usr/local/include/opencv2/opencv2/calib3d/calib3d_c.h
 
+/usr/local/include/opencv2/calib3d/calib3d.hpp
+opencv2/calib3d.hpp
+/usr/local/include/opencv2/calib3d/opencv2/calib3d.hpp
+
 /usr/local/include/opencv2/calib3d/calib3d_c.h
 opencv2/core/core_c.h
 /usr/local/include/opencv2/calib3d/opencv2/core/core_c.h
diff --git a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.internal b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.internal
index b7a1fba24400a549e257513c931aec4a1ec544db..450084ec7c1319b635a68007a30b83ec396e10dc 100644
--- a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.internal
+++ b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.internal
@@ -10,6 +10,7 @@ apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/shader.cpp.o
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o
  /home/thomas/Documents/Minimisation/apps/specular_estimation/src/Ceres.h
  /home/thomas/Documents/Minimisation/apps/specular_estimation/src/Charuco.h
+ /home/thomas/Documents/Minimisation/apps/specular_estimation/src/Macbeth.h
  /home/thomas/Documents/Minimisation/apps/specular_estimation/src/OpenCV.h
  /home/thomas/Documents/Minimisation/apps/specular_estimation/src/OpenGL.h
  /home/thomas/Documents/Minimisation/apps/specular_estimation/src/objloader.h
@@ -271,11 +272,14 @@ apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimat
  /usr/local/include/ceres/solver.h
  /usr/local/include/ceres/types.h
  /usr/local/include/ceres/version.h
+ /usr/local/include/opencv/cv.h
  /usr/local/include/opencv/cxcore.h
+ /usr/local/include/opencv/highgui.h
  /usr/local/include/opencv2/aruco.hpp
  /usr/local/include/opencv2/aruco/charuco.hpp
  /usr/local/include/opencv2/aruco/dictionary.hpp
  /usr/local/include/opencv2/calib3d.hpp
+ /usr/local/include/opencv2/calib3d/calib3d.hpp
  /usr/local/include/opencv2/calib3d/calib3d_c.h
  /usr/local/include/opencv2/core.hpp
  /usr/local/include/opencv2/core/affine.hpp
diff --git a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.make b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.make
index 7ea700576081f9ff069fdc9db06f3765ae4ce359..d5d6ff3b92d4a5ffad53528354716b1f5b536957 100644
--- a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.make
+++ b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/depend.make
@@ -9,6 +9,7 @@ apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/shader.cpp.o: ap
 
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: apps/specular_estimation/src/Ceres.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: apps/specular_estimation/src/Charuco.h
+apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: apps/specular_estimation/src/Macbeth.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: apps/specular_estimation/src/OpenCV.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: apps/specular_estimation/src/OpenGL.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: apps/specular_estimation/src/objloader.h
@@ -270,11 +271,14 @@ apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimat
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/ceres/solver.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/ceres/types.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/ceres/version.h
+apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv/cv.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv/cxcore.h
+apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv/highgui.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/aruco.hpp
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/aruco/charuco.hpp
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/aruco/dictionary.hpp
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/calib3d.hpp
+apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/calib3d/calib3d.hpp
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/calib3d/calib3d_c.h
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/core.hpp
 apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o: /usr/local/include/opencv2/core/affine.hpp
diff --git a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o
index 3273a97207e4569bb6e22b3d76378e7bb4294a07..18508dc3b6318979144f5530d7859577e9466e71 100644
Binary files a/apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o and b/apps/specular_estimation/CMakeFiles/specular_estimation.dir/src/specular_estimation.cc.o differ
diff --git a/apps/specular_estimation/src/Macbeth.h b/apps/specular_estimation/src/Macbeth.h
new file mode 100644
index 0000000000000000000000000000000000000000..23463ff7a90fed6cb94af44dcc982dbb061043f8
--- /dev/null
+++ b/apps/specular_estimation/src/Macbeth.h
@@ -0,0 +1,671 @@
+#pragma once
+#ifndef MACBETH_H
+#define MACBETH_H
+
+#include <stdio.h>
+#include <opencv/cv.h>
+#include <opencv/highgui.h>
+#include <opencv2/calib3d/calib3d.hpp>
+
+#define MACBETH_WIDTH   6
+#define MACBETH_HEIGHT  4
+#define MACBETH_SQUARES MACBETH_WIDTH * MACBETH_HEIGHT
+
+#define MAX_CONTOUR_APPROX  7
+
+#define MAX_RGB_DISTANCE 444
+
+// BabelColor averages in sRGB:
+//   http://www.babelcolor.com/main_level/ColorChecker.htm
+// (converted to BGR order for comparison)
+CvScalar colorchecker_srgb[MACBETH_HEIGHT][MACBETH_WIDTH] = {
+	{
+		cvScalar(67,81,115),
+		cvScalar(129,149,196),
+		cvScalar(157,123,93),
+		cvScalar(65,108,90),
+		cvScalar(176,129,130),
+		cvScalar(171,191,99)
+	},
+	{
+		cvScalar(45,123,220),
+		cvScalar(168,92,72),
+		cvScalar(98,84,195),
+		cvScalar(105,59,91),
+		cvScalar(62,189,160),
+		cvScalar(41,161,229)
+	},
+	{
+		cvScalar(147,62,43),
+		cvScalar(72,149,71),
+		cvScalar(56,48,176),
+		cvScalar(22,200,238),
+		cvScalar(150,84,188),
+		cvScalar(166,136,0)
+	},
+	{
+		cvScalar(240,245,245),
+		cvScalar(201,201,200),
+		cvScalar(161,161,160),
+		cvScalar(121,121,120),
+		cvScalar(85,84,83),
+		cvScalar(50,50,50)
+	}
+};
+    
+ struct ColorChecker {
+    double error;
+    CvMat * values;
+    CvMat * points;
+    double size;
+};
+
+cv::Mat detectMacbeth(std::string imageName);
+double euclidean_distance(CvScalar p_1, CvScalar p_2);
+double euclidean_distance(CvPoint p_1, CvPoint p_2);
+double euclidean_distance_lab(CvScalar p_1, CvScalar p_2);
+CvRect contained_rectangle(CvBox2D box);
+CvScalar rect_average(CvRect rect, IplImage* image);
+CvScalar contour_average(CvContour* contour, IplImage* image);
+void rotate_box(CvPoint2D32f * box_corners);
+double check_colorchecker(CvMat * colorchecker);
+void draw_colorchecker(CvMat * colorchecker_values, CvMat * colorchecker_points, IplImage * image, int size);
+ColorChecker find_colorchecker(CvSeq * quads, CvSeq * boxes, CvMemStorage *storage, IplImage *image, IplImage *original_image);
+CvSeq * find_quad( CvSeq * src_contour, CvMemStorage *storage, int min_size);
+IplImage * find_macbeth(const char *img);
+
+cv::Mat detectMacbeth(std::string imageName) {
+    //if( argc < 2 )
+    //{
+        //fprintf( stderr, "Usage: %s image_file [output_image]\n", argv[0] );
+        //return 1;
+    //}
+
+    const char *img_file = imageName.c_str();
+
+    IplImage *out = find_macbeth(img_file);
+    
+    //if( argc == 3) {
+        //cvSaveImage( argv[2], out );
+    //}
+    
+    cv::Mat macbeth = cv::cvarrToMat(out);
+    
+    cvReleaseImage( &out );
+    
+    //cv::Mat macbeth cv::cvarrToMat(find_macbeth(img_file));
+    
+    return macbeth;
+}
+
+double euclidean_distance(CvScalar p_1, CvScalar p_2) {   
+    double sum = 0;
+    for(int i = 0; i < 3; i++) {
+        sum += std::pow(p_1.val[i]-p_2.val[i],2.);
+    }
+    return std::sqrt(sum);
+}
+
+double euclidean_distance(CvPoint p_1, CvPoint p_2) {
+    return euclidean_distance(cvScalar(p_1.x,p_1.y,0),cvScalar(p_2.x,p_2.y,0));
+}
+
+double euclidean_distance_lab(CvScalar p_1, CvScalar p_2) {
+    // convert to Lab for better perceptual distance
+    IplImage * convert = cvCreateImage( cvSize(2,1), 8, 3);
+    cvSet2D(convert,0,0,p_1);
+    cvSet2D(convert,0,1,p_2);
+    cvCvtColor(convert,convert,CV_BGR2Lab);
+    p_1 = cvGet2D(convert,0,0);
+    p_2 = cvGet2D(convert,0,1);
+    cvReleaseImage(&convert);
+    
+    return euclidean_distance(p_1, p_2);
+}
+
+CvRect contained_rectangle(CvBox2D box) {
+    return cvRect(box.center.x - box.size.width/4,
+                  box.center.y - box.size.height/4,
+                  box.size.width/2,
+                  box.size.height/2);
+}
+
+CvScalar rect_average(CvRect rect, IplImage* image) {       
+    CvScalar average = cvScalarAll(0);
+    int count = 0;
+    for(int x = rect.x; x < (rect.x+rect.width); x++) {
+        for(int y = rect.y; y < (rect.y+rect.height); y++) {
+            if((x >= 0) && (y >= 0) && (x < image->width) && (y < image->height)) {
+                CvScalar s = cvGet2D(image,y,x);
+                average.val[0] += s.val[0];
+                average.val[1] += s.val[1];
+                average.val[2] += s.val[2];
+            
+                count++;
+            }
+        }
+    }
+    
+    for(int i = 0; i < 3; i++) {
+        average.val[i] /= count;
+    }
+    
+    return average;
+}
+
+CvScalar contour_average(CvContour* contour, IplImage* image) {
+    CvRect rect = ((CvContour*)contour)->rect;
+    
+    CvScalar average = cvScalarAll(0);
+    int count = 0;
+    for(int x = rect.x; x < (rect.x+rect.width); x++) {
+        for(int y = rect.y; y < (rect.y+rect.height); y++) {
+            if(cvPointPolygonTest(contour, cvPointTo32f(cvPoint(x,y)),0) == 100) {
+                CvScalar s = cvGet2D(image,y,x);
+                average.val[0] += s.val[0];
+                average.val[1] += s.val[1];
+                average.val[2] += s.val[2];
+                
+                count++;
+            }
+        }
+    }
+    
+    for(int i = 0; i < 3; i++) {
+        average.val[i] /= count;
+    }
+    
+    return average;
+}
+
+void rotate_box(CvPoint2D32f * box_corners) {
+    CvPoint2D32f last = box_corners[3];
+    for(int i = 3; i > 0; i--) {
+        box_corners[i] = box_corners[i-1];
+    }
+    box_corners[0] = last;
+}
+
+double check_colorchecker(CvMat * colorchecker) {
+    double difference = 0;
+    
+    for(int x = 0; x < MACBETH_WIDTH; x++) {
+        for(int y = 0; y < MACBETH_HEIGHT; y++) {
+            CvScalar known_value = colorchecker_srgb[y][x];
+            CvScalar test_value = cvGet2D(colorchecker,y,x);
+            for(int i = 0; i < 3; i++){
+                difference += std::pow(known_value.val[i]-test_value.val[i],2);
+            }
+        }
+    }
+    
+    return difference;
+}
+
+void draw_colorchecker(CvMat * colorchecker_values, CvMat * colorchecker_points, IplImage * image, int size) {
+    for(int x = 0; x < MACBETH_WIDTH; x++) {
+        for(int y = 0; y < MACBETH_HEIGHT; y++) {
+            CvScalar this_color = cvGet2D(colorchecker_values,y,x);
+            CvScalar this_point = cvGet2D(colorchecker_points,y,x);
+            
+            cvCircle(
+                image,
+                cvPoint(this_point.val[0],this_point.val[1]),
+                size,
+                colorchecker_srgb[y][x],
+                -1
+            );
+            
+            cvCircle(
+                image,
+                cvPoint(this_point.val[0],this_point.val[1]),
+                size/2,
+                this_color,
+                -1
+            );
+        }
+    }
+}
+
+ColorChecker find_colorchecker(CvSeq * quads, CvSeq * boxes, CvMemStorage *storage, IplImage *image, IplImage *original_image) {
+    CvPoint2D32f box_corners[4];
+    bool passport_box_flipped = false;
+    bool rotated_box = false;
+    
+    CvMat* points = cvCreateMat( boxes->total , 1, CV_32FC2 );
+    for(int i = 0; i < boxes->total; i++)
+    {
+        CvBox2D box = (*(CvBox2D*)cvGetSeqElem(boxes, i));
+        cvSet1D(points, i, cvScalar(box.center.x,box.center.y));
+    }
+    CvBox2D passport_box = cvMinAreaRect2(points,storage);
+    fprintf(stderr,"Box:\n\tCenter: %f,%f\n\tSize: %f,%f\n\tAngle: %f\n",passport_box.center.x,passport_box.center.y,passport_box.size.width,passport_box.size.height,passport_box.angle);
+    if(passport_box.angle < 0.0) {
+      passport_box_flipped = true;
+    }
+    
+    cvBoxPoints(passport_box, box_corners);
+    // for(int i = 0; i < 4; i++)
+    // {
+    //   fprintf(stderr,"Box corner %d: %d,%d\n",i,cvPointFrom32f(box_corners[i]).x,cvPointFrom32f(box_corners[i]).y);
+    // }
+    
+    // cvBox(passport_box, image, cvScalarAll(128), 10);
+    
+    if(euclidean_distance(cvPointFrom32f(box_corners[0]),cvPointFrom32f(box_corners[1])) <
+       euclidean_distance(cvPointFrom32f(box_corners[1]),cvPointFrom32f(box_corners[2]))) {
+        fprintf(stderr,"Box is upright, rotating\n");
+        rotate_box(box_corners);
+        rotated_box = true && passport_box_flipped;
+    }
+
+    double horizontal_spacing = euclidean_distance(
+        cvPointFrom32f(box_corners[0]),cvPointFrom32f(box_corners[1]))/(double)(MACBETH_WIDTH-1);
+    double vertical_spacing = euclidean_distance(
+        cvPointFrom32f(box_corners[1]),cvPointFrom32f(box_corners[2]))/(double)(MACBETH_HEIGHT-1);
+    double horizontal_slope = (box_corners[1].y - box_corners[0].y)/(box_corners[1].x - box_corners[0].x);
+    double horizontal_mag = std::sqrt(1+std::pow(horizontal_slope,2));
+    double vertical_slope = (box_corners[3].y - box_corners[0].y)/(box_corners[3].x - box_corners[0].x);
+    double vertical_mag = std::sqrt(1+std::pow(vertical_slope,2));
+    double horizontal_orientation = box_corners[0].x < box_corners[1].x ? -1 : 1;
+    double vertical_orientation = box_corners[0].y < box_corners[3].y ? -1 : 1;
+        
+    fprintf(stderr,"Spacing is %f %f\n",horizontal_spacing,vertical_spacing);
+    fprintf(stderr,"Slope is %f %f\n", horizontal_slope,vertical_slope);
+    
+    int average_size = 0;
+    for(int i = 0; i < boxes->total; i++)
+    {
+        CvBox2D box = (*(CvBox2D*)cvGetSeqElem(boxes, i));
+        
+        CvRect rect = contained_rectangle(box);
+        average_size += MIN(rect.width, rect.height);
+    }
+    average_size /= boxes->total;
+    
+    fprintf(stderr,"Average contained rect size is %d\n", average_size);
+    
+    CvMat * this_colorchecker = cvCreateMat(MACBETH_HEIGHT, MACBETH_WIDTH, CV_32FC3);
+    CvMat * this_colorchecker_points = cvCreateMat( MACBETH_HEIGHT, MACBETH_WIDTH, CV_32FC2 );
+    
+    // calculate the averages for our oriented colorchecker
+    for(int x = 0; x < MACBETH_WIDTH; x++) {
+        for(int y = 0; y < MACBETH_HEIGHT; y++) {
+            CvPoint2D32f row_start;
+            
+            if ( ((image->origin == IPL_ORIGIN_BL) || !rotated_box) && !((image->origin == IPL_ORIGIN_BL) && rotated_box) )
+            {
+                row_start.x = box_corners[0].x + vertical_spacing * y * (1 / vertical_mag);
+                row_start.y = box_corners[0].y + vertical_spacing * y * (vertical_slope / vertical_mag);
+            }
+            else
+            {
+                row_start.x = box_corners[0].x - vertical_spacing * y * (1 / vertical_mag);
+                row_start.y = box_corners[0].y - vertical_spacing * y * (vertical_slope / vertical_mag);
+            }
+            
+            CvRect rect = cvRect(0,0,average_size,average_size);
+            
+            rect.x = row_start.x - horizontal_spacing * x * ( 1 / horizontal_mag ) * horizontal_orientation;
+            rect.y = row_start.y - horizontal_spacing * x * ( horizontal_slope / horizontal_mag ) * vertical_orientation;
+            
+            cvSet2D(this_colorchecker_points, y, x, cvScalar(rect.x,rect.y));
+            
+            rect.x = rect.x - average_size / 2;
+            rect.y = rect.y - average_size / 2;
+            
+            // cvRectangle(
+            //     image,
+            //     cvPoint(rect.x,rect.y),
+            //     cvPoint(rect.x+rect.width, rect.y+rect.height),
+            //     cvScalarAll(0),
+            //     10
+            // );
+            
+            CvScalar average_color = rect_average(rect, original_image);
+            
+            cvSet2D(this_colorchecker,y,x,average_color);
+        }
+    }
+    
+    double orient_1_error = check_colorchecker(this_colorchecker);
+    cvFlip(this_colorchecker,NULL,-1);
+    double orient_2_error = check_colorchecker(this_colorchecker);
+    
+    fprintf(stderr,"Orientation 1: %f\n",orient_1_error);
+    fprintf(stderr,"Orientation 2: %f\n",orient_2_error);
+    
+    if(orient_1_error < orient_2_error) {
+        cvFlip(this_colorchecker,NULL,-1);
+    }
+    else {
+        cvFlip(this_colorchecker_points,NULL,-1);
+    }
+    
+    // draw_colorchecker(this_colorchecker,this_colorchecker_points,image,average_size);
+    
+    ColorChecker found_colorchecker;
+    
+    found_colorchecker.error = MIN(orient_1_error,orient_2_error);
+    found_colorchecker.values = this_colorchecker;
+    found_colorchecker.points = this_colorchecker_points;
+    found_colorchecker.size = average_size;
+    
+    return found_colorchecker;
+}
+
+CvSeq * find_quad( CvSeq * src_contour, CvMemStorage *storage, int min_size) {
+    // stolen from icvGenerateQuads
+    CvMemStorage * temp_storage = cvCreateChildMemStorage( storage );
+    
+    int flags = CV_CALIB_CB_FILTER_QUADS;
+    CvSeq *dst_contour = 0;
+    
+    const int min_approx_level = 2, max_approx_level = MAX_CONTOUR_APPROX;
+    int approx_level;
+    for( approx_level = min_approx_level; approx_level <= max_approx_level; approx_level++ )
+    {
+        dst_contour = cvApproxPoly( src_contour, sizeof(CvContour), temp_storage,
+                                    CV_POLY_APPROX_DP, (float)approx_level );
+        // we call this again on its own output, because sometimes
+        // cvApproxPoly() does not simplify as much as it should.
+        dst_contour = cvApproxPoly( dst_contour, sizeof(CvContour), temp_storage,
+                                    CV_POLY_APPROX_DP, (float)approx_level );
+
+        if( dst_contour->total == 4 )
+            break;
+    }
+
+    // reject non-quadrangles
+    if( dst_contour->total == 4 && cvCheckContourConvexity(dst_contour) )
+    {
+        CvPoint pt[4];
+        double d1, d2, p = cvContourPerimeter(dst_contour);
+        double area = fabs(cvContourArea(dst_contour, CV_WHOLE_SEQ));
+        double dx, dy;
+
+        for( int i = 0; i < 4; i++ )
+            pt[i] = *(CvPoint*)cvGetSeqElem(dst_contour, i);
+
+        dx = pt[0].x - pt[2].x;
+        dy = pt[0].y - pt[2].y;
+        d1 = std::sqrt(dx*dx + dy*dy);
+
+        dx = pt[1].x - pt[3].x;
+        dy = pt[1].y - pt[3].y;
+        d2 = std::sqrt(dx*dx + dy*dy);
+
+        // philipg.  Only accept those quadrangles which are more square
+        // than rectangular and which are big enough
+        double d3, d4;
+        dx = pt[0].x - pt[1].x;
+        dy = pt[0].y - pt[1].y;
+        d3 = std::sqrt(dx*dx + dy*dy);
+        dx = pt[1].x - pt[2].x;
+        dy = pt[1].y - pt[2].y;
+        d4 = std::sqrt(dx*dx + dy*dy);
+        if( !(flags & CV_CALIB_CB_FILTER_QUADS) ||
+            (d3*1.1 > d4 && d4*1.1 > d3 && d3*d4 < area*1.5 && area > min_size &&
+            d1 >= 0.15 * p && d2 >= 0.15 * p) )
+        {
+            // CvContourEx* parent = (CvContourEx*)(src_contour->v_prev);
+            // parent->counter++;
+            // if( !board || board->counter < parent->counter )
+            //     board = parent;
+            // dst_contour->v_prev = (CvSeq*)parent;
+            //for( i = 0; i < 4; i++ ) cvLine( debug_img, pt[i], pt[(i+1)&3], cvScalar(200,255,255), 1, CV_AA, 0 );
+            // cvSeqPush( root, &dst_contour );
+            return dst_contour;
+        }
+    }
+    
+    return NULL;
+}
+
+IplImage * find_macbeth(const char *img) {
+    IplImage * macbeth_img = cvLoadImage( img,
+        CV_LOAD_IMAGE_ANYCOLOR|CV_LOAD_IMAGE_ANYDEPTH );
+        
+    IplImage * macbeth_original = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), macbeth_img->depth, macbeth_img->nChannels );
+    cvCopy(macbeth_img, macbeth_original);
+        
+    IplImage * macbeth_split[3];
+    IplImage * macbeth_split_thresh[3];
+    
+    for(int i = 0; i < 3; i++) {
+        macbeth_split[i] = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), macbeth_img->depth, 1 );
+        macbeth_split_thresh[i] = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), macbeth_img->depth, 1 );
+    }
+    
+    cvSplit(macbeth_img, macbeth_split[0], macbeth_split[1], macbeth_split[2], NULL);
+    
+    if( macbeth_img )
+    {
+        int adaptive_method = CV_ADAPTIVE_THRESH_MEAN_C;
+        int threshold_type = CV_THRESH_BINARY_INV;
+        int block_size = cvRound(
+            MIN(macbeth_img->width,macbeth_img->height)*0.02)|1;
+        fprintf(stderr,"Using %d as block size\n", block_size);
+        
+        double offset = 6;
+        
+        // do an adaptive threshold on each channel
+        for(int i = 0; i < 3; i++) {
+            cvAdaptiveThreshold(macbeth_split[i], macbeth_split_thresh[i], 255, adaptive_method, threshold_type, block_size, offset);
+        }
+        
+        IplImage * adaptive = cvCreateImage( cvSize(macbeth_img->width, macbeth_img->height), IPL_DEPTH_8U, 1 );
+        
+        // OR the binary threshold results together
+        cvOr(macbeth_split_thresh[0],macbeth_split_thresh[1],adaptive);
+        cvOr(macbeth_split_thresh[2],adaptive,adaptive);
+        
+        for(int i = 0; i < 3; i++) {
+            cvReleaseImage( &(macbeth_split[i]) );
+            cvReleaseImage( &(macbeth_split_thresh[i]) );
+        }
+                
+        int element_size = (block_size/10)+2;
+        fprintf(stderr,"Using %d as element size\n", element_size);
+        
+        // do an opening on the threshold image
+        IplConvKernel * element = cvCreateStructuringElementEx(element_size,element_size,element_size/2,element_size/2,CV_SHAPE_RECT);
+        cvMorphologyEx(adaptive,adaptive,NULL,element,CV_MOP_OPEN);
+        cvReleaseStructuringElement(&element);
+        
+        CvMemStorage* storage = cvCreateMemStorage(0);
+        
+        CvSeq* initial_quads = cvCreateSeq( 0, sizeof(*initial_quads), sizeof(void*), storage );
+        CvSeq* initial_boxes = cvCreateSeq( 0, sizeof(*initial_boxes), sizeof(CvBox2D), storage );
+        
+        // find contours in the threshold image
+        CvSeq * contours = NULL;
+        cvFindContours(adaptive,storage,&contours);
+        
+        int min_size = (macbeth_img->width*macbeth_img->height)/
+            (MACBETH_SQUARES*100);
+        
+        if(contours) {
+            int count = 0;
+            
+            for( CvSeq* c = contours; c != NULL; c = c->h_next) {
+                CvRect rect = ((CvContour*)c)->rect;
+                // only interested in contours with these restrictions
+                if(CV_IS_SEQ_HOLE(c) && rect.width*rect.height >= min_size) {
+                    // only interested in quad-like contours
+                    CvSeq * quad_contour = find_quad(c, storage, min_size);
+                    if(quad_contour) {
+                        cvSeqPush( initial_quads, &quad_contour );
+                        count++;
+                        rect = ((CvContour*)quad_contour)->rect;
+                        
+                        CvScalar average = contour_average((CvContour*)quad_contour, macbeth_img);
+                        
+                        CvBox2D box = cvMinAreaRect2(quad_contour,storage);
+                        cvSeqPush( initial_boxes, &box );
+                        
+                        // fprintf(stderr,"Center: %f %f\n", box.center.x, box.center.y);
+                        
+                        double min_distance = MAX_RGB_DISTANCE;
+                        CvPoint closest_color_idx = cvPoint(-1,-1);
+                        for(int y = 0; y < MACBETH_HEIGHT; y++) {
+                            for(int x = 0; x < MACBETH_WIDTH; x++) {
+                                double distance = euclidean_distance_lab(average,colorchecker_srgb[y][x]);
+                                if(distance < min_distance) {
+                                    closest_color_idx.x = x;
+                                    closest_color_idx.y = y;
+                                    min_distance = distance;
+                                }
+                            }
+                        }
+                        
+                        CvScalar closest_color = colorchecker_srgb[closest_color_idx.y][closest_color_idx.x];
+                        // fprintf(stderr,"Closest color: %f %f %f (%d %d)\n",
+                        //     closest_color.val[2],
+                        //     closest_color.val[1],
+                        //     closest_color.val[0],
+                        //     closest_color_idx.x,
+                        //     closest_color_idx.y
+                        // );
+                        
+                        // cvDrawContours(
+                        //     macbeth_img,
+                        //     quad_contour,
+                        //     cvScalar(255,0,0),
+                        //     cvScalar(0,0,255),
+                        //     0,
+                        //     element_size
+                        // );
+                        // cvCircle(
+                        //     macbeth_img,
+                        //     cvPointFrom32f(box.center),
+                        //     element_size*6,
+                        //     cvScalarAll(255),
+                        //     -1
+                        // );
+                        // cvCircle(
+                        //     macbeth_img,
+                        //     cvPointFrom32f(box.center),
+                        //     element_size*6,
+                        //     closest_color,
+                        //     -1
+                        // );
+                        // cvCircle(
+                        //     macbeth_img,
+                        //     cvPointFrom32f(box.center),
+                        //     element_size*4,
+                        //     average,
+                        //     -1
+                        // );
+                        // CvRect rect = contained_rectangle(box);
+                        // cvRectangle(
+                        //     macbeth_img,
+                        //     cvPoint(rect.x,rect.y),
+                        //     cvPoint(rect.x+rect.width, rect.y+rect.height),
+                        //     cvScalarAll(0),
+                        //     element_size
+                        // );
+                    }
+                }
+            }
+            
+            ColorChecker found_colorchecker;
+
+            fprintf(stderr,"%d initial quads found", initial_quads->total);
+            if(count > MACBETH_SQUARES) {
+                fprintf(stderr," (probably a Passport)\n");
+                
+                CvMat* points = cvCreateMat( initial_quads->total , 1, CV_32FC2 );
+                CvMat* clusters = cvCreateMat( initial_quads->total , 1, CV_32SC1 );
+                
+                CvSeq* partitioned_quads[2];
+                CvSeq* partitioned_boxes[2];
+                for(int i = 0; i < 2; i++) {
+                    partitioned_quads[i] = cvCreateSeq( 0, sizeof(**partitioned_quads), sizeof(void*), storage );
+                    partitioned_boxes[i] = cvCreateSeq( 0, sizeof(**partitioned_boxes), sizeof(CvBox2D), storage );
+                }
+                
+                // set up the points sequence for cvKMeans2, using the box centers
+                for(int i = 0; i < initial_quads->total; i++) {
+                    CvBox2D box = (*(CvBox2D*)cvGetSeqElem(initial_boxes, i));
+                    
+                    cvSet1D(points, i, cvScalar(box.center.x,box.center.y));
+                }
+                
+                // partition into two clusters: passport and colorchecker
+                cvKMeans2( points, 2, clusters, 
+                           cvTermCriteria( CV_TERMCRIT_EPS+CV_TERMCRIT_ITER,
+                                           10, 1.0 ) );
+        
+                for(int i = 0; i < initial_quads->total; i++) {
+                    CvPoint2D32f pt = ((CvPoint2D32f*)points->data.fl)[i];
+                    int cluster_idx = clusters->data.i[i];
+                    
+                    cvSeqPush( partitioned_quads[cluster_idx],
+                               cvGetSeqElem(initial_quads, i) );
+                    cvSeqPush( partitioned_boxes[cluster_idx],
+                               cvGetSeqElem(initial_boxes, i) );
+
+                    // cvCircle(
+                    //     macbeth_img,
+                    //     cvPointFrom32f(pt),
+                    //     element_size*2,
+                    //     cvScalar(255*cluster_idx,0,255-(255*cluster_idx)),
+                    //     -1
+                    // );
+                }
+                
+                ColorChecker partitioned_checkers[2];
+                
+                // check each of the two partitioned sets for the best colorchecker
+                for(int i = 0; i < 2; i++) {
+                    partitioned_checkers[i] =
+                        find_colorchecker(partitioned_quads[i], partitioned_boxes[i],
+                                      storage, macbeth_img, macbeth_original);
+                }
+                
+                // use the colorchecker with the lowest error
+                found_colorchecker = partitioned_checkers[0].error < partitioned_checkers[1].error ?
+                    partitioned_checkers[0] : partitioned_checkers[1];
+                
+                cvReleaseMat( &points );
+                cvReleaseMat( &clusters );
+            }
+            else { // just one colorchecker to test
+                fprintf(stderr,"\n");
+                found_colorchecker = find_colorchecker(initial_quads, initial_boxes,
+                                  storage, macbeth_img, macbeth_original);
+            }
+            
+            // render the found colorchecker
+            draw_colorchecker(found_colorchecker.values,found_colorchecker.points,macbeth_img,found_colorchecker.size);
+            
+            // print out the colorchecker info
+            for(int y = 0; y < MACBETH_HEIGHT; y++) {            
+                for(int x = 0; x < MACBETH_WIDTH; x++) {
+                    CvScalar this_value = cvGet2D(found_colorchecker.values,y,x);
+                    CvScalar this_point = cvGet2D(found_colorchecker.points,y,x);
+                    
+                    printf("%.0f,%.0f,%.0f,%.0f,%.0f\n",
+                        this_point.val[0],this_point.val[1],
+                        this_value.val[2],this_value.val[1],this_value.val[0]);
+                }
+            }
+            printf("%0.f\n%f\n",found_colorchecker.size,found_colorchecker.error);
+            
+        }
+                
+        cvReleaseMemStorage( &storage );
+        
+        if( macbeth_original ) cvReleaseImage( &macbeth_original );
+        if( adaptive ) cvReleaseImage( &adaptive );
+        
+        return macbeth_img;
+    }
+
+    if( macbeth_img ) cvReleaseImage( &macbeth_img );
+
+    return NULL;
+}
+
+#endif
diff --git a/apps/specular_estimation/src/OpenCV.h b/apps/specular_estimation/src/OpenCV.h
index db32c9f82163825f8c15d9e670b1825880fe36fc..c34fa9095f31dfc50d17775c9df13512d6e065f5 100644
--- a/apps/specular_estimation/src/OpenCV.h
+++ b/apps/specular_estimation/src/OpenCV.h
@@ -515,7 +515,7 @@ void loadCalibrationImages(std::vector< cv::Mat >& calibrationImages, std::strin
 	std::cout << std::endl;
 }
 
-void loadModelImages(std::vector< cv::Mat >& modelImages, std::vector< cv::Mat >& textureImages, std::string modelPath, int imageScale, int& originalWidth, int& originalHeight, int& width, int& height) {
+void loadModelImages(std::vector<cv::Mat>& modelImages, std::vector<cv::Mat>& textureImages, std::string modelPath, int imageScale, int& originalWidth, int& originalHeight, int& width, int& height) {
 	bool loadedImages = false;
 	int imageNumber = 0;
 
diff --git a/apps/specular_estimation/src/specular_estimation.cc b/apps/specular_estimation/src/specular_estimation.cc
index 7eaf008a0bf4edd9a8822dda2849d2b9b041aec8..22a6d7d06195e15b99491cf2f266e49e14d566c1 100644
--- a/apps/specular_estimation/src/specular_estimation.cc
+++ b/apps/specular_estimation/src/specular_estimation.cc
@@ -53,6 +53,8 @@ int main(int argc, char** argv) {
 	const std::string calibrationPath = imagesPath + folderPath + "/" + calibration + "/" + calibration + ".";
 	const std::string texturePath     = modelPath + "texture.png";
 
+	detectMacbeth("/media/thomas/ESD-USB/2017-12-04/macbeth/macbeth.0.png");
+
 	// A vector of Mats allow multiple images to be allocated to the same object
 	// textureImages are the full colour photographs that are used to create the texture
 	// modelImages are the greyscale version of textureImages which are used to calculate the surface normals
diff --git a/apps/specular_estimation/src/stdafx.h b/apps/specular_estimation/src/stdafx.h
index 6fb1e3b2951814d5813da11c262efc09ae9cc04f..cc35b5476994dbbc2711830d34b97729ccfd4261 100644
--- a/apps/specular_estimation/src/stdafx.h
+++ b/apps/specular_estimation/src/stdafx.h
@@ -42,3 +42,4 @@ using namespace glm;
 #include "Charuco.h"
 #include "OpenGL.h"
 #include "Ceres.h"
+#include "Macbeth.h"
diff --git a/bin/specular_estimation b/bin/specular_estimation
index 8776e9adcc6672f9e56738805e253acf44e835b3..0e8da448870bd7cf723e3efcb59428ad5610ee5d 100755
Binary files a/bin/specular_estimation and b/bin/specular_estimation differ