Francesco Milano, Manuel López-Antequera, Naina Dhingra, Roland Siegwart, Robert Thiel
Recovering a 3D surface from its surface normal map, a problem known as normal integration, is a key component for photometric shape reconstruction techniques such as shape-from-shading and photometric stereo. The vast majority of existing approaches for normal integration handle only implicitly the presence of depth discontinuities and are limited to orthographic or ideal pinhole cameras. In this paper, we propose a novel formulation that allows modeling discontinuities explicitly and handling generic central cameras. Our key idea is based on a local planarity assumption, that we model through constraints between surface normals and ray directions. Compared to existing methods, our approach more accurately approximates the relation between depth and surface normals, achieves state-of-the-art results on the standard normal integration benchmark, and is the first to directly handle generic central camera models.
The reference code in this repository was tested on Ubuntu 22.04, using a Python 3.11.6 virtual environment with installed packages specified in the requirements.txt file.
To run experiments on the DiLiGenT dataset, download and set up the dataset in a folder ${DILIGENT_ROOT} of your choice as follows:
- Download the per-object folders containing the camera intrinsics, the PNG-encoded normal maps, and the normal masks from this folder in the BiNI repository. Extract the folders into a subfolder named 
normalsin${DILIGENT_ROOT}. - Download the ground-truth depth, provided by the BiNI repository (cf. here), from here. Extract the 
.matfiles in a subfolder nameddepth_gtin${DILIGENT_ROOT}. - Download the high-resolution normal maps from the link provided on the original DiLiGenT dataset webpage. Extract the 
pmsDatasubfolder into${DILIGENT_ROOT}. 
Your ${DILIGENT_ROOT} folder should now look as:
${DILIGENT_ROOT}
├── depth_gt
|   ├── bear_gt.mat
|   ├── buddha_gt.mat
|   └── ...
├── normals
|   ├── bear
|   |   ├── K.txt
|   |   ├── mask.png
|   |   └── normal_map.png
|   ├── buddha
|   |   └── ...
|   └── ...
└── pmsData
    ├── bearPNG
    |   ├── Normal_gt.mat
    |   └── ...
    ├── buddhaPNG
    |   └── ...
    └── ...
To run experiments on DiLiGenT-MV, download and extract the dataset from the link provided on the official DiLiGenT-MV dataset website.
Optionally, to render ground-truth depth maps from the ground-truth meshes, to use for evaluation, we provide a representative script, which relies on BlenderProc. After installing BlenderProc (for instance, using pip install blenderproc~=2.8.0), you can render the depth maps by running in the root of this repository the following command:
blenderproc run render_depth_DiLiGenT-MV.py -- \
    --mvpmsData-folder ${DILIGENT_MV_ROOT}/mvpmsData \
    --output-folder ${DILIGENT_MV_ROOT}/depth_from_mesh \
    --obj-name ${OBJ_NAME} \
    --view-idx ${VIEW_IDX}
where ${DILIGENT_MV_ROOT} is the folder where you have extracted the DiLiGenT-MV dataset, ${OBJ_NAME} is the name of one of the objects in the dataset (bear, buddha, cow, pot2, and reading) or ${VIEW_IDX} is the index of the view to render (from 1 to 20).
Results similar to the experiments in the paper can be obtained with the following command:
python main.py \
    --data-dir ${DATASET_ROOT} \
    --dataset-type ${DATASET_TYPE} \
    --obj-name ${OBJ_NAME} \
    --num-shifts ${NUM_SHIFTS} \
    --normal-type ${NORMAL_TYPE} \
    --q-beta ${Q_BETA} \
    --rho-beta ${RHO_BETA} \
    --gamma-type ${GAMMA_TYPE} \
    --lambda-m ${LAMBDA_M} \
    --w-b-to-a-outlier-th ${W_B_TO_A_OUTLIER_TH} \
    --num-ms ${NUM_MS} \
    --max-iter ${MAX_ITER} \
    --force-all-iters \
    --output-folder ${OUTPUT_FOLDER}
where:
- 
${DATASET_ROOT}is${DILIGENT_ROOT}for the experiments on DiLiGenT and${DILIGENT_MV_ROOT}for the experiments on DiLiGenT-MV; - 
${DATASET_TYPE}isdiligentfor the experiments on DiLiGenT anddiligent_mvfor the experiments on DiLiGenT-MV; - 
${OBJ_NAME}is the object name (e.g.,bear) for the experiments on DiLiGenT and the object name followed by the view index for the experiments on DiLiGenT-MV (e.g.,,bear_view_01); - 
${NUM_SHIFTS}is either4,4_diag, or8(we use4in our main experiments); - 
${NORMAL_TYPE}isgtfor the experiments on DiLiGenT and eithergtorTIP19Lifor the experiments on DiLiGenT-MV (respectively, for ground-truth normals and for normals from photometric stereo); - 
${Q_BETA}and${RHO_BETA}control the discontinuity activation term$\beta_{b\rightarrow a}$ in our approach (cf. eq. (16) in our paper), which we set respectively to50and0.25. Set both toNoneto run our method without$\alpha_{b\rightarrow a}$ computation; - 
${GAMMA_TYPE}controls the$\gamma_{b\rightarrow a}$ factor in our method. Set it tobinito use the default term from BiNI and tooursto use our more generic term (cf. eq. (13) in our paper); - 
${LAMBDA_M}is the$\lambda_m$ factor that controls the sub-pixel interpolation of$\boldsymbol{\tau_m}$ in our method (cf. Appendix D in our paper). We set this to0.5; - 
${W_B_TO_A_OUTLIER_TH}and${NUM_MS}control our outlier filtering strategy through$\boldsymbol{\tau_m}$ (cf. Appendix H in our paper), which is useful for DiLIGenT-MV (we set${W_B_TO_A_OUTLIER_TH}to1.1and${NUM_MS}to15); - 
${MAX_ITER}is the maximum number of iterations to use. We set this to1200in our experiments. The flag--force-all-iterscan be included to force the method to be run for all the${MAX_ITER}iterations; - 
${OUTPUT_FOLDER}is the path to the folder that should store the output of the experiments. For each experiment, a subfolder is created that is indexed by the experiment's starting time and by the object name. 
Example run on DiLiGenT:
python main.py \
    --data-dir ${DILIGENT_ROOT} \
    --dataset-type diligent \
    --obj-name buddha \
    --num-shifts 4 \
    --normal-type gt \
    --q-beta 50 \
    --rho-beta 0.25 \
    --gamma-type bini \
    --lambda-m 0.5 \
    --w-b-to-a-outlier-th 1.1 \
    --num-ms 15 \
    --max-iter 1200 \
    --force-all-iters \
    --output-folder ${OUTPUT_FOLDER}
Example run on DiLiGenT:
python main.py \
    --data-dir ${DILIGENT_MV_ROOT} \
    --dataset-type diligent_mv \
    --obj-name buddha_view_01 \
    --num-shifts 4 \
    --normal-type gt \
    --q-beta None \
    --rho-beta None \
    --gamma-type bini \
    --lambda-m 0.5 \
    --w-b-to-a-outlier-th 1.1 \
    --num-ms 15 \
    --max-iter 1200 \
    --force-all-iters \
    --output-folder ${OUTPUT_FOLDER}
If you find our code or paper useful, please cite:
@inproceedings{Milano2025DiscontinuityAwareNormalIntegration,
  author    = {Milano, Francesco and López-Antequera, Manuel and Dhingra, Naina and Siegwart, Roland and Thiel, Robert},
  title     = {{Discontinuity-aware Normal Integration for Generic Central Camera Models}},
  booktitle = {IEEE/CVF International Conference on Computer Vision (ICCV)},
  year      = {2025}
}See the CONTRIBUTING file for how to help out.
surface_normal_integration is GPLv3 licensed, as found in the LICENSE file.
Parts of the code are based on BiNI. This includes in particular the files in the folder bini, which were refactored and partly restructured to comply with standard camera conventions, the basic structure of the optimization procedure in the main script, and the computation of the MADE metric in utils/metrics.py.