Skip to content

Commit ffbdea1

Browse files
committed
added fft normal integration (and some debug).
1 parent 19ef1cd commit ffbdea1

10 files changed

+221
-12
lines changed

relight/mainwindow.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,7 @@ void MainWindow::loadLP(QString lp) {
867867
std::vector<Vector3f> directions;
868868

869869
try {
870-
vector<QString> filenames;
871-
parseLP(lp, project.dome.directions, filenames);
870+
parseLP(lp, directions, filenames);
872871

873872
} catch(QString error) {
874873
QMessageBox::critical(this, "LP file invalid: ", error);
@@ -897,6 +896,7 @@ void MainWindow::loadLP(QString lp) {
897896
}
898897

899898
project.dome.lightConfiguration = Dome::DIRECTIONAL;
899+
project.dome.directions.resize(directions.size());
900900
if(success) {
901901
for(size_t i = 0; i < project.size(); i++)
902902
project.dome.directions[i] = ordered_dir[i];

relight/normalstask.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ void NormalsTask::run() {
3939

4040
ImageSet imageSet;
4141
imageSet.images = (*this)["images"].value.toStringList();
42-
imageSet.initFromDome(project->dome);
42+
4343
imageSet.initImages(input_folder.toStdString().c_str());
44+
imageSet.initFromDome(project->dome);
4445

4546
if(hasParameter("crop")) {
4647
QRect rect = (*this)["crop"].value.toRect();
@@ -182,7 +183,7 @@ void NormalsWorker::run()
182183

183184
void NormalsWorker::solveL2()
184185
{
185-
std::vector<Vector3f> &m_Lights = m_Imageset.lights1;
186+
std::vector<Vector3f> &m_Lights = m_Imageset.lights();
186187

187188
// Pixel data
188189
Eigen::MatrixXd mLights(m_Lights.size(), 3);

relightlab/normalsplan.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,13 @@ NormalsSurfaceRow::NormalsSurfaceRow(NormalsParameters &_parameters, QFrame *par
148148
label->help->setId("normals/flattening");
149149

150150
none = new QLabelButton("None", "Do generate a mesh.");
151+
fft = new QLabelButton("Fourier Normal Integration", "Dense, very fast");
151152
bni = new QLabelButton("Bilateral Normal Integration", "Dense, allows discontinuity");
152153
assm = new QLabelButton("Adaptive Surface Meshing.", "Adaptive, no discontinuities.");
153154

154155
buttons->addWidget(none, 0, Qt::AlignCenter);
155156

157+
buttons->addWidget(fft);
156158
{
157159
QVBoxLayout *bni_layout =new QVBoxLayout;
158160
bni_layout->addWidget(bni);
@@ -185,6 +187,7 @@ NormalsSurfaceRow::NormalsSurfaceRow(NormalsParameters &_parameters, QFrame *par
185187
}
186188

187189
connect(none, &QAbstractButton::clicked, this, [this](){ setSurfaceMethod(SURFACE_NONE); });
190+
connect(fft, &QAbstractButton::clicked, this, [this](){ setSurfaceMethod(SURFACE_FFT); });
188191
connect(bni, &QAbstractButton::clicked, this, [this](){ setSurfaceMethod(SURFACE_BNI); });
189192
connect(assm, &QAbstractButton::clicked, this, [this](){ setSurfaceMethod(SURFACE_ASSM); });
190193

@@ -198,6 +201,7 @@ NormalsSurfaceRow::NormalsSurfaceRow(NormalsParameters &_parameters, QFrame *par
198201

199202
QButtonGroup *group = new QButtonGroup(this);
200203
group->addButton(none);
204+
group->addButton(fft);
201205
group->addButton(bni);
202206
group->addButton(assm);
203207

@@ -266,7 +270,7 @@ void NormalsExportRow::selectOutput() {
266270
void NormalsExportRow::suggestPath() {
267271
QDir input = qRelightApp->project().dir;
268272
input.cdUp();
269-
QString filename = input.filePath("normals");
273+
QString filename = input.filePath("normals.jpg");
270274
setPath(filename);
271275
}
272276

relightlab/normalsplan.h

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class NormalsSurfaceRow: public NormalsPlanRow {
5555
void setSurfaceMethod(SurfaceIntegration surface);
5656

5757
QLabelButton *none = nullptr;
58+
QLabelButton *fft = nullptr;
5859
QLabelButton *bni = nullptr;
5960
QLabelButton *assm = nullptr;
6061

relightlab/normalstask.cpp

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "../src/imageset.h"
55
#include "../src/relight_threadpool.h"
66
#include "../src/bni_normal_integration.h"
7+
#include "../src/fft_normal_integration.h"
78
#include "../src/flatnormals.h"
89

910
#include <assm/Grid.h>
@@ -171,13 +172,18 @@ void NormalsTask::run() {
171172
QString filename = output.left(output.size() -4) + ".obj";
172173

173174
assm(filename, normals, parameters.assm_error);
174-
} else if(parameters.surface_integration == SURFACE_BNI) {
175-
bool proceed = progressed("Integrating normals assm...", 0);
175+
176+
} else if(parameters.surface_integration == SURFACE_BNI || parameters.surface_integration == SURFACE_FFT) {
177+
bool proceed = progressed("Bilateral normal integration...", 0);
176178
if(!proceed)
177179
return;
178180

179181
vector<float> z;
180-
bni_integrate(callback, width, height, normals, z, parameters.bni_k);
182+
if(parameters.surface_integration == SURFACE_BNI)
183+
bni_integrate(callback, width, height, normals, z, parameters.bni_k);
184+
else
185+
fft_integrate(callback, width, height, normals, z);
186+
181187
if(z.size() == 0) {
182188
error = "Failed to integrate normals";
183189
status = FAILED;

relightlab/normalstask.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
enum NormalSolver { NORMALS_L2, NORMALS_SBL, NORMALS_RPCA };
1515
enum FlatMethod { FLAT_NONE, FLAT_RADIAL, FLAT_FOURIER };
16-
enum SurfaceIntegration { SURFACE_NONE, SURFACE_BNI, SURFACE_ASSM };
16+
enum SurfaceIntegration { SURFACE_NONE, SURFACE_BNI, SURFACE_ASSM, SURFACE_FFT };
1717

1818
class NormalsParameters {
1919
public:

relightlab/relightlab.pro

+5-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ SOURCES += main.cpp \
112112
planrow.cpp \
113113
normalsplan.cpp \
114114
brdfframe.cpp \
115-
metadataframe.cpp
115+
metadataframe.cpp \
116+
../src/fft_normal_integration.cpp
116117

117118
RESOURCES += \
118119
res.qrc
@@ -198,7 +199,9 @@ HEADERS += \
198199
planrow.h \
199200
normalsplan.h \
200201
brdfframe.h \
201-
metadataframe.h
202+
metadataframe.h \
203+
../src/fft_normal_integration.h \
204+
../src/pocketfft.h
202205

203206
FORMS +=
204207

src/bni_normal_integration.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ bool savePly(const QString &filename, int w, int h, std::vector<float> &z) {
7070
start[0] = x;
7171
start[1] = y;
7272
start[2] = z[pos];
73+
assert(!isnan(start[2]));
7374
}
7475
}
7576
std::vector<uint8_t> indices(13*2*(w-1)*(h-1));
@@ -425,7 +426,7 @@ void bni_integrate(std::function<bool(QString s, int n)> progressed, int w, int
425426
double relative_energy = fabs(energy - energy_old) / energy_old;
426427
double total_progress = fabs(energy - start_energy) / start_energy;
427428
if(progressed) {
428-
bool proceed = progressed("Integrating normals...", 100*(log(relative_energy) - log(tolerance))/(log(total_progress) - log(tolerance)));
429+
bool proceed = progressed("Integrating normals...", 100*(1 - (log(relative_energy) - log(tolerance))/(log(total_progress) - log(tolerance))));
429430
if(!proceed) break;
430431
}
431432
if(relative_energy < tolerance)

src/fft_normal_integration.cpp

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#include "fft_normal_integration.h"
2+
3+
#include "pocketfft.h"
4+
#include <Eigen/Dense>
5+
6+
#include <vector>
7+
#include <complex>
8+
#include <cmath>
9+
10+
11+
using namespace std;
12+
using namespace Eigen;
13+
14+
typedef Matrix<std::complex<double>, Dynamic, Dynamic> ComplexMatrix;
15+
16+
// Helper function to generate meshgrid-like matrices
17+
void meshgrid(MatrixXd& wx, MatrixXd& wy, int cols, int rows) {
18+
wx.resize(rows, cols);
19+
wy.resize(rows, cols);
20+
21+
double colMid = (cols / 2) + 1;
22+
double rowMid = (rows / 2) + 1;
23+
double colDiv = cols - (cols % 2);
24+
double rowDiv = rows - (rows % 2);
25+
26+
for (int i = 0; i < rows; ++i) {
27+
for (int j = 0; j < cols; ++j) {
28+
wx(i, j) = (j + 1 - colMid) / colDiv;
29+
wy(i, j) = (i + 1 - rowMid) / rowDiv;
30+
}
31+
}
32+
}
33+
34+
MatrixXd ifftshift(const MatrixXd& input) {
35+
int rows = input.rows();
36+
int cols = input.cols();
37+
MatrixXd shifted(rows, cols);
38+
39+
int rowShift = rows / 2;
40+
int colShift = cols / 2;
41+
42+
for (int i = 0; i < rows; ++i) {
43+
for (int j = 0; j < cols; ++j) {
44+
int newRow = (i + rowShift) % rows;
45+
int newCol = (j + colShift) % cols;
46+
shifted(newRow, newCol) = input(i, j);
47+
}
48+
}
49+
return shifted;
50+
}
51+
void fft2(const MatrixXd& input, ComplexMatrix& output) {
52+
int rows = input.rows();
53+
int cols = input.cols();
54+
55+
// Prepare data
56+
std::vector<std::complex<double>> data(rows * cols);
57+
for (int y = 0; y < rows; ++y) {
58+
for (int x = 0; x < cols; ++x) {
59+
data[y * cols + x] = input(y, x);
60+
}
61+
}
62+
63+
// Perform FFT
64+
size_t element_size = sizeof(std::complex<double>);
65+
pocketfft::shape_t shape = {size_t(cols), size_t(rows)};
66+
pocketfft::stride_t stride = { element_size, size_t(cols)*element_size };
67+
pocketfft::shape_t axes{0, 1};
68+
69+
pocketfft::c2c(shape, stride, stride, axes, pocketfft::FORWARD, data.data(), data.data(), 1.0);
70+
71+
// Fill output
72+
output.resize(rows, cols);
73+
for (int y = 0; y < rows; ++y)
74+
for (int x = 0; x < cols; ++x) {
75+
output(y, x) = data[y * cols + x];
76+
}
77+
}
78+
79+
// Function to compute 2D IFFT using PocketFFT
80+
void ifft2(const ComplexMatrix& input, MatrixXd& output) {
81+
int rows = input.rows();
82+
int cols = input.cols();
83+
84+
// Prepare data
85+
std::vector<std::complex<double>> data(rows * cols);
86+
for (int i = 0; i < rows; ++i)
87+
for (int j = 0; j < cols; ++j) {
88+
data[i * cols + j] = input(i, j);
89+
}
90+
91+
// Perform IFFT
92+
size_t element_size = sizeof(std::complex<double>);
93+
pocketfft::shape_t shape = {size_t(cols), size_t(rows)};
94+
pocketfft::stride_t stride = { element_size, size_t(cols)*element_size };
95+
pocketfft::shape_t axes{0, 1};
96+
pocketfft::c2c(shape, stride, stride, axes, pocketfft::BACKWARD, data.data(), data.data(), 1.0/(4*sqrt(2)* rows * cols));
97+
98+
// Fill output and normalize
99+
output.resize(rows, cols);
100+
for (int i = 0; i < rows; ++i)
101+
for (int j = 0; j < cols; ++j) {
102+
output(i, j) = data[i * cols + j].real();
103+
}
104+
}
105+
106+
void fft_integrate(std::function<bool(QString s, int n)> progressed,
107+
int cols, int rows, std::vector<float> &normals, std::vector<float> &heights) {
108+
109+
110+
111+
MatrixXd dzdx(rows, cols);
112+
MatrixXd dzdy(rows, cols);
113+
for (int i = 0; i < rows; ++i) {
114+
for (int j = 0; j < cols; ++j) {
115+
float *normal = &normals[3*(i * cols + j)];
116+
dzdx(i, j) = normal[0] / normal[2]; // dz/dx = -nx/nz
117+
dzdy(i, j) = -normal[1] / normal[2]; // dz/dy = -ny/nz
118+
assert(!isnan(dzdx(i, j)));
119+
assert(!isnan(dzdy(i, j)));
120+
}
121+
}
122+
123+
MatrixXd wx, wy;
124+
meshgrid(wx, wy, cols, rows);
125+
126+
wx = ifftshift(wx);
127+
wy = ifftshift(wy);
128+
129+
// Fourier Transforms of gradients
130+
ComplexMatrix DZDX, DZDY;
131+
fft2(dzdx, DZDX);
132+
fft2(dzdy, DZDY);
133+
134+
// Frequency domain integration
135+
ComplexMatrix Z(rows, cols);
136+
std::complex<double> j(0, 1); // Imaginary unit
137+
138+
for (int y = 0; y < rows; ++y) {
139+
for (int x = 0; x < cols; ++x) {
140+
double wx2_wy2 = wx(y, x) * wx(y, x) + wy(y, x) * wy(y, x) + 1e-12; // Avoid division by zero
141+
Z(y, x) = (-j * wx(y, x) * DZDX(y, x) - j * wy(y, x) * DZDY(y, x)) / wx2_wy2;
142+
}
143+
}
144+
145+
// Inverse FFT to reconstruct z
146+
MatrixXd z;
147+
ifft2(Z, z);
148+
149+
heights.resize(rows* cols);
150+
for (int i = 0; i < rows; ++i) {
151+
for (int j = 0; j < cols; ++j) {
152+
heights[i * cols + j] = static_cast<float>(z(i, j));
153+
}
154+
}
155+
156+
/*
157+
[wx, wy] = meshgrid(([1:cols]-(fix(cols/2)+1))/(cols-mod(cols,2)), ...
158+
([1:rows]-(fix(rows/2)+1))/(rows-mod(rows,2)));
159+
160+
% Quadrant shift to put zero frequency at the appropriate edge
161+
wx = ifftshift(wx); wy = ifftshift(wy);
162+
163+
DZDX = fft2(dzdx); % Fourier transforms of gradients
164+
DZDY = fft2(dzdy);
165+
166+
% Integrate in the frequency domain by phase shifting by pi/2 and
167+
% weighting the Fourier coefficients by their frequencies in x and y and
168+
% then dividing by the squared frequency. eps is added to the
169+
% denominator to avoid division by 0.
170+
171+
Z = (-j*wx.*DZDX -j*wy.*DZDY)./(wx.^2 + wy.^2 + eps); % Equation 21
172+
173+
z = real(ifft2(Z)); % Reconstruction
174+
*/
175+
}

src/fft_normal_integration.h

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef FFT_NORMAL_INTEGRATION_H
2+
#define FFT_NORMAL_INTEGRATION_H
3+
4+
5+
#include <QString>
6+
#include <vector>
7+
#include <functional>
8+
/* Robert T. Frankot and Rama Chellappa
9+
* A Method for Enforcing Integrability in Shape from Shading
10+
* IEEE PAMI Vol 10, No 4 July 1988. pp 439-451
11+
*/
12+
13+
void fft_integrate(std::function<bool(QString s, int n)> progressed,
14+
int w, int h, std::vector<float> &normalmap, std::vector<float> &heights);
15+
16+
17+
18+
#endif // FFT_NORMAL_INTEGRATION_H

0 commit comments

Comments
 (0)