This is the final project for CMU 15663 Computational Photography Fall 2021 course, teached by Prof. Ioannis (Yannis) Gkioulekas.
In this project, I build a complete Depth from Focus pipeline in Python that takes in an unaligned focal stack and outputs the estimated depth map. A large part of the code is derived from this Github repo [1]. Please read section Introduction for more detail.
Conda is recommended for creating a virtual environment. Install and activate by:
conda env create -f environment.yml
conda activate dff
However, you need to install gco-wrapper by following their instructions. The original pygco is out of maintainace.
Execute:
bash run.sh
This will run both image_alignment.py
and depth_from_focus_dffmp.py
with the default parameter specified in the script. Final results will be in results/keyboard_homography
.
Image alginment step:
python src/image_alignment.py [-h] --method {flow,homography,RAFT}
[--flow_path FLOW_PATH]
img_path save_path match_save_path
Image Alignment
positional arguments:
img_path Dir to input focal stack
save_path Root dir to result
match_save_path Dir to frame-by-frame alignment visualization
optional arguments:
-h, --help show this help message and exit
--method {flow,homography,RAFT}
Alignment method
--flow_path FLOW_PATH
Dir to optical flow .npy files
Depth estimation step:
python src/depth_from_focus_dffmp.py [-h]
[--LoG_gaussian_ksize LOG_GAUSSIAN_KSIZE]
[--LoG_laplacian_ksize LOG_LAPLACIAN_KSIZE]
[--AIF_sharpness_sigma AIF_SHARPNESS_SIGMA]
[--AIF_depth_ksize AIF_DEPTH_KSIZE]
[--graphcut_sharpness_sigma GRAPHCUT_SHARPNESS_SIGMA]
[--graphcut_unary_scale GRAPHCUT_UNARY_SCALE]
[--graphcut_pair_scale GRAPHCUT_PAIR_SCALE]
[--reverse_input_order]
base_path
Depth from Focus
positional arguments:
base_path Root dir to result from alignment step
optional arguments:
-h, --help show this help message and exit
--LoG_gaussian_ksize LOG_GAUSSIAN_KSIZE
LoG gaussian kernel size
--LoG_laplacian_ksize LOG_LAPLACIAN_KSIZE
LoG laplacian kernel size
--AIF_sharpness_sigma AIF_SHARPNESS_SIGMA
Sharpness gaussian sigma for all-in-focus image
--AIF_depth_ksize AIF_DEPTH_KSIZE
Depth gaussian sigma for all-in-focus image
--graphcut_sharpness_sigma GRAPHCUT_SHARPNESS_SIGMA
Sharpness gaussian sigma for graphcut
--graphcut_unary_scale GRAPHCUT_UNARY_SCALE
Unary term weight for graphcut
--graphcut_pair_scale GRAPHCUT_PAIR_SCALE
Pairwise term weight for graphcut
--reverse_input_order
Reverse aligned image reading order
In this project I deal with focal stack captured by mobile phone, so a explicit alignment step is required to first align focal stack images. I provide 3 alignment methods: homography [2], OpenCV optical flow [3], explicit optical flow.
Homography
- Convert RGB images A and B to grayscale images A’ and B’
- Detect SIFT features A’ and B’
- Match features between A’ and B’
- Compute homography between A’ and B’ with RANSAC
- Align A to B using homography
- Repeat 1)~5) for all image sequence(focal stack).
OpenCV optical flow
- Convert RGB images A and B to grayscale images A’ and B’
- Compute optical flow between A’ and B’
- Warp A to B using optical flow
- Repeat 1)~3) for all image sequence(focal stack).
External optical flow
- Read optical flow between image A and B
- Warp A to B using optical flow
- Repeat 1)~2) for all image sequence(focal stack).
Focus measure
The focus measure calculates in which image each pixel is in-focus. We can infer whether a pixel is in-focus by examining its "sharpness". Here, I adopt the Laplacian of Gaussian (LoG) as sharpness measure.
- Convert aligned RGB images A to grayscale image A’
- First use gaussian blur to smooth out gradient noise.
- Second, use laplacian filter to find edges.
- We take the absolute value of LoG
Initial depth estimate and all-in-focus image
After computing focus measure, we know the sharpness of each pixel in each image. Depth could be simply inferred by taking the image index of max sharpness value for each pixel (i.e. argmax). An all-in-focus could also be stitched by copying pixel color from their corresponding max sharpness image. [4]
- Blur the focus measure value with gaussian.
- Take argmax of each pixel, finding the image index at which each pixel appears sharpest.
- By normalizing it, we can obtain a depth map. Blur the depth map with gaussian.
- Stitch the all-in-focus image by taking pixel from the image at which it appears sharpest.
Depth estimation with global optimization
The initial depth estimate is far from perfect. One of its problem is focus measure could not compute sharpness for non-texture or flat color area without edges. By posing depth estimate (or estimating in which image each pixel appears sharpest) as a MRF multi-label problem, we could solve it with graph cut [3]. We aim to minimize an energy function, where the node energy is inverse to sharpness and edge energy is in proportion to image index difference. After solving it, we apply weighted median filter to remove holes and peckles.
- Blur the focus measure value.
- Take the inverse of focus measure as the node energy.
- Take image index difference as edge energy.
- Solve multi-label problem with graphcut.
- Post-process with weighted median filter.
All-in-focus image | Depth map |
---|---|
- https://github.com/sangminwoo/Depth_from_Focus
- Surh, Jaeheung, et al. "Noise robust depth from focus using a ring difference filter." Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2017. [paper]
- Suwajanakorn, Supasorn, Carlos Hernandez, and Steven M. Seitz. "Depth from focus with your mobile phone." Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition. 2015. [paper][author page]
- CMU 15663 lecture: Focal stacks and depth from (de)focus [course website]