photometric stereo

郤旭东
2023-12-01
#include <iostream>
#include <vector>
#include <cmath>


#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>


#include <vtkSmartPointer.h>
#include <vtkPolyDataMapper.h>
#include <vtkPolyData.h>
#include <vtkPLYWriter.h>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkImageViewer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkInteractorStyleImage.h>
#include <vtkLight.h>
#include <vtkLightCollection.h>
#include <vtkRenderer.h>
#include <vtkCellArray.h>
#include <vtkPoints.h>
#include <vtkPointData.h>
#include <vtkFloatArray.h>
#include <vtkTriangle.h>


using namespace cv;
using namespace std;


void displayMesh(int width, int height, cv::Mat Z) {
    
    /* creating visualization pipeline which basically looks like this:
     vtkPoints -> vtkPolyData -> vtkPolyDataMapper -> vtkActor -> vtkRenderer */
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
    vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
    vtkSmartPointer<vtkPolyDataMapper> modelMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    vtkSmartPointer<vtkActor> modelActor = vtkSmartPointer<vtkActor>::New();
    vtkSmartPointer<vtkRenderer> renderer = vtkSmartPointer<vtkRenderer>::New();
    vtkSmartPointer<vtkCellArray> vtkTriangles = vtkSmartPointer<vtkCellArray>::New();
    
    /* insert x,y,z coords */
    for (int y=0; y<height; y++) {
        for (int x=0; x<width; x++) {
            points->InsertNextPoint(x, y, Z.at<float>(y,x));
        }
    }
    
    /* setup the connectivity between grid points */
    vtkSmartPointer<vtkTriangle> triangle = vtkSmartPointer<vtkTriangle>::New();
    triangle->GetPointIds()->SetNumberOfIds(3);
    for (int i=0; i<height-1; i++) {
        for (int j=0; j<width-1; j++) {
            triangle->GetPointIds()->SetId(0, j+(i*width));
            triangle->GetPointIds()->SetId(1, (i+1)*width+j);
            triangle->GetPointIds()->SetId(2, j+(i*width)+1);
            vtkTriangles->InsertNextCell(triangle);
            triangle->GetPointIds()->SetId(0, (i+1)*width+j);
            triangle->GetPointIds()->SetId(1, (i+1)*width+j+1);
            triangle->GetPointIds()->SetId(2, j+(i*width)+1);
            vtkTriangles->InsertNextCell(triangle);
        }
    }
    polyData->SetPoints(points);
    polyData->SetPolys(vtkTriangles);
    
    /* create two lights */
    vtkSmartPointer<vtkLight> light1 = vtkSmartPointer<vtkLight>::New();
    light1->SetPosition(-1, 1, 1);
    renderer->AddLight(light1);
    vtkSmartPointer<vtkLight> light2 = vtkSmartPointer<vtkLight>::New();
    light2->SetPosition(1, -1, -1);
    renderer->AddLight(light2);
    
    /* meshlab-ish background */
    modelMapper->SetInputData(polyData);
    renderer->SetBackground(.45, .45, .9);
    renderer->SetBackground2(.0, .0, .0);
    renderer->GradientBackgroundOn();
    vtkSmartPointer<vtkRenderWindow> renderWindow = vtkSmartPointer<vtkRenderWindow>::New();
    renderWindow->AddRenderer(renderer);
    modelActor->SetMapper(modelMapper);
    
    /* setting some properties to make it look just right */
    modelActor->GetProperty()->SetSpecularColor(1, 1, 1);
    modelActor->GetProperty()->SetAmbient(0.2);
    modelActor->GetProperty()->SetDiffuse(0.2);
    modelActor->GetProperty()->SetInterpolationToPhong();
    modelActor->GetProperty()->SetSpecular(0.8);
    modelActor->GetProperty()->SetSpecularPower(8.0);
    
    renderer->AddActor(modelActor);
    vtkSmartPointer<vtkRenderWindowInteractor> interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New();
    interactor->SetRenderWindow(renderWindow);
    
    /* export mesh */
    vtkSmartPointer<vtkPLYWriter> plyExporter = vtkSmartPointer<vtkPLYWriter>::New();
    plyExporter->SetInputData(polyData);
    plyExporter->SetFileName("export.ply");
    plyExporter->SetColorModeToDefault();
    plyExporter->SetArrayName("Colors");
    plyExporter->Update();
    plyExporter->Write();
    
    /* render mesh */
    renderWindow->Render();
    interactor->Start();
}


cv::Mat globalHeights(cv::Mat Pgrads, cv::Mat Qgrads) {
    
    cv::Mat P(Pgrads.rows, Pgrads.cols, CV_32FC2, cv::Scalar::all(0));
    cv::Mat Q(Pgrads.rows, Pgrads.cols, CV_32FC2, cv::Scalar::all(0));
    cv::Mat Z(Pgrads.rows, Pgrads.cols, CV_32FC2, cv::Scalar::all(0));
    
    float lambda = 1.0f;
    float mu = 1.0f;
    
    cv::dft(Pgrads, P, cv::DFT_COMPLEX_OUTPUT);
    cv::dft(Qgrads, Q, cv::DFT_COMPLEX_OUTPUT);
    for (int i=0; i<Pgrads.rows; i++) {
        for (int j=0; j<Pgrads.cols; j++) {
            if (i != 0 || j != 0) {
                float u = sin((float)(i*2*CV_PI/Pgrads.rows));
                float v = sin((float)(j*2*CV_PI/Pgrads.cols));
                
                float uv = pow(u,2)+pow(v,2);
                float d = (1.0f + lambda)*uv + mu*pow(uv,2);
                Z.at<cv::Vec2f>(i, j)[0] = (u*P.at<cv::Vec2f>(i, j)[1] + v*Q.at<cv::Vec2f>(i, j)[1]) / d;
                Z.at<cv::Vec2f>(i, j)[1] = (-u*P.at<cv::Vec2f>(i, j)[0] - v*Q.at<cv::Vec2f>(i, j)[0]) / d;
            }
        }
    }
    
    /* setting unknown average height to zero */
    Z.at<cv::Vec2f>(0, 0)[0] = 0.0f;
    Z.at<cv::Vec2f>(0, 0)[1] = 0.0f;
    
    cv::dft(Z, Z, cv::DFT_INVERSE | cv::DFT_SCALE | cv::DFT_REAL_OUTPUT);
    
    return Z;
}


cv::Vec3f getLightDirFromSphere(Mat Image, Rect boundingbox) {
    
    const int THRESH    = 254;
    const float radius  = boundingbox.width / 2.0f;
    
    Mat Binary;
    threshold(Image, Binary, THRESH, 255, CV_THRESH_BINARY);
    Mat SubImage(Binary, boundingbox);
    
    /* calculate center of pixels */
    Moments m = moments(SubImage, false);
    Point center(m.m10/m.m00, m.m01/m.m00);
    
    /* x,y are swapped here */
    float x = (center.y - radius) / radius;
    float y = (center.x - radius) / radius;
    float z = sqrt(1.0 - pow(x, 2.0) - pow(y, 2.0));
    
    return Vec3f(x, y, z);
}


cv::Rect getBoundingBox(cv::Mat Mask) {
    
    std::vector<std::vector<cv::Point> > v;
    cv::findContours(Mask.clone(), v, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
    assert(v.size() > 0);
    return cv::boundingRect(v[0]);
}


int main(int argc, char *argv[]) {
    
    const int NUM_IMGS          = 12;
    const string CALIBRATION    = "../../images/chrome/chrome.";
    const string MODEL          = "../../images/rock/rock.";
    
    vector<Mat> calibImages;
    vector<Mat> modelImages;
    Mat Lights(NUM_IMGS, 3, CV_32F);
    Mat Mask = imread(CALIBRATION + "mask.png", CV_LOAD_IMAGE_GRAYSCALE);
    Mat ModelMask = imread(MODEL + "mask.png", CV_LOAD_IMAGE_GRAYSCALE);
    Rect bb = getBoundingBox(Mask);
    for (int i = 0; i < NUM_IMGS; i++) {
        Mat Calib = imread(CALIBRATION + to_string(i) + ".png",
                           CV_LOAD_IMAGE_GRAYSCALE);
        Mat tmp = imread(MODEL + to_string(i) + ".png",
                           CV_LOAD_IMAGE_GRAYSCALE);
        cv::Mat Model;
        tmp.copyTo(Model, ModelMask);
        Vec3f light = getLightDirFromSphere(Calib, bb);
        Lights.at<float>(i, 0) = light[0];
        Lights.at<float>(i, 1) = light[1];
        Lights.at<float>(i, 2) = light[2];
        calibImages.push_back(Calib);
        modelImages.push_back(Model);
    }
    
    const int height    = calibImages[0].rows;
    const int width     = calibImages[0].cols;
    /* light directions, surface normals, p,q gradients */
    cv::Mat LightsInv;
    cv::invert(Lights, LightsInv, cv::DECOMP_SVD);


    cv::Mat Normals(height, width, CV_32FC3, cv::Scalar::all(0));
    cv::Mat Pgrads(height, width, CV_32F, cv::Scalar::all(0));
    cv::Mat Qgrads(height, width, CV_32F, cv::Scalar::all(0));
    /* estimate surface normals and p,q gradients */
    for (int x=0; x<width; x++) {
        for (int y=0; y<height; y++) {
            Vec<float, NUM_IMGS> I;
            for (int i = 0; i < NUM_IMGS; i++) {
                I[i] = modelImages[i].at<uchar>(Point(x,y));
            }
            
            cv::Mat n = LightsInv * cv::Mat(I);
            float p = sqrt(cv::Mat(n).dot(n));
            if (p > 0) { n = n/p; }
            if (n.at<float>(2,0) == 0) { n.at<float>(2,0) = 1.0; }
            int legit = 1;
            /* avoid spikes ad edges */
            for (int i = 0; i < NUM_IMGS; i++) {
                legit *= modelImages[i].at<uchar>(Point(x,y)) >= 0;
            }
            if (legit) {
                Normals.at<cv::Vec3f>(cv::Point(x,y)) = n;
                Pgrads.at<float>(cv::Point(x,y)) = n.at<float>(0,0)/n.at<float>(2,0);
                Qgrads.at<float>(cv::Point(x,y)) = n.at<float>(1,0)/n.at<float>(2,0);
            } else {
                cv::Vec3f nullvec(0.0f, 0.0f, 1.0f);
                Normals.at<cv::Vec3f>(cv::Point(x,y)) = nullvec;
                Pgrads.at<float>(cv::Point(x,y)) = 0.0f;
                Qgrads.at<float>(cv::Point(x,y)) = 0.0f;
            }
            
        }
    }


    cv::Mat Normalmap;
    cv::cvtColor(Normals, Normalmap, CV_BGR2RGB);
    cv::imshow("Normalmap", Normalmap);
    
    /* global integration of surface normals */
    cv::Mat Z = globalHeights(Pgrads, Qgrads);


    /* display reconstruction */
    displayMesh(Pgrads.cols, Pgrads.rows, Z);


    cv::waitKey();
    return 0;
}
 类似资料:

相关阅读

相关文章

相关问答