A complete pipeline for light field photography using a 3Γ3 multi-camera array, including calibration, preprocessing, and interactive refocusing visualization.
- Overview
- Features
- Pipeline Architecture
- Repository Structure
- Requirements
- Installation
- Usage
- Algorithm Details
- Sample Data
- Configuration
- Troubleshooting
- License
This project implements an end-to-end light field imaging system using a 3Γ3 planar camera array (9 cameras). Light field photography captures not only the intensity but also the direction of light rays, enabling post-capture refocusing, depth estimation, and novel view synthesis.
The pipeline consists of three main stages:
- Calibration β Align all 9 cameras to a common reference using a chessboard pattern and homography estimation
- Preprocessing β Apply calibration matrices to raw images for geometric rectification
- Rendering β Interactive MATLAB GUI for shift-and-sum refocusing at arbitrary depths
| Feature | Description |
|---|---|
| Multi-camera calibration | Chessboard-based homography estimation with sub-pixel corner detection |
| Geometric rectification | Perspective warp alignment of all sub-aperture images |
| Batch preprocessing | Recursive processing of nested sample directories |
| Interactive refocusing | GUI with live slider control over focal depth |
| Click-to-focus | Automatic depth selection via Sobel edge sharpness maximization |
| Focal stack generation | Export a full sequence of refocused images across a depth range |
| Chinese path support | OpenCV read/write wrappers for non-ASCII file paths (Windows) |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RAW CAMERA IMAGES β
β (9 cameras Γ N sample sets) β
ββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββΌββββββββββββββββ
β findHomography.py β
β CALIBRATION STAGE β
β β
β β’ Chessboard corner detection β
β β’ Sub-pixel refinement β
β β’ Homography estimation β
β β’ Save: 0.npy ~ 8.npy β
βββββββββββββββββ¬ββββββββββββββββ
β Homography matrices
βββββββββββββββββΌββββββββββββββββ
β do_change.py β
β PREPROCESSING STAGE β
β β
β β’ Load .npy matrices β
β β’ warpPerspective() β
β β’ Batch apply to raw images β
β β’ Save aligned output β
βββββββββββββββββ¬ββββββββββββββββ
β Aligned light field
βββββββββββββββββΌββββββββββββββββ
β Main4.m (MATLAB GUI) β
β RENDERING STAGE β
β β
β β’ Build LF tensor (MΓNΓHΓWΓC) β
β β’ Shift-and-sum refocusing β
β β’ Interactive slider β
β β’ Click-to-focus β
β β’ Focal stack export β
βββββββββββββββββββββββββββββββββ
β
βΌ
REFOCUSED IMAGES
(arbitrary focal planes)
Light-field/
βββ Main4.m # MATLAB GUI β Light field refocusing application
βββ findHomography.py # Stage 1: Chessboard calibration β homography matrices
βββ do_change.py # Stage 2: Apply calibration matrices to raw images
β
βββ CalibrationPic/ # Calibration images (chessboard, 9 views, 0.bmp β 8.bmp)
βββ calibration/ # Output of findHomography.py
β βββ 0.npy β¦ 8.npy # 9 homography matrices (3Γ3 camera array)
β βββ pic/ # Calibrated chessboard images (verification)
β
βββ pics/ # Input: Raw light field images (organized by sample)
β βββ polyethylene+kieselguhr_1/
β β βββ 1-1.bmp, 1-2.bmp, 1-3.bmp
β β βββ 2-1.bmp, 2-2.bmp, 2-3.bmp
β β βββ 3-1.bmp, 3-2.bmp, 3-3.bmp
β βββ polyethylene+kieselguhr_2/
β
βββ result/ # Output: Calibrated & aligned images (mirrors pics/ tree)
βββ polyethylene+kieselguhr_1/
βββ polyethylene+kieselguhr_2/
Images follow a row-column naming scheme: {row}-{col}.bmp (1-indexed), matching the physical camera layout:
Camera Array (M=3 rows Γ N=3 cols):
(1,1) (1,2) (1,3) βββββββββ¬ββββββββ¬ββββββββ
(2,1) (2,2) (2,3) β β 1-1 β 1-2 β 1-3 β β Center camera (2,2) is the reference
(3,1) (3,2) (3,3) βββββββββΌββββββββΌββββββββ€
β 2-1 β 2-2 β
β 2-3 β
βββββββββΌββββββββΌββββββββ€
β 3-1 β 3-2 β 3-3 β
βββββββββ΄ββββββββ΄ββββββββ
| Dependency | Version | Purpose |
|---|---|---|
| Python | β₯ 3.6 | Runtime |
OpenCV (opencv-python) |
β₯ 4.0 | Chessboard detection, homography, warpPerspective |
| NumPy | β₯ 1.18 | Matrix operations, .npy file I/O |
| Component | Purpose |
|---|---|
| MATLAB | Core environment |
| Image Processing Toolbox | imshow, imread, imwrite, interpn |
| No extra toolboxes required |
- Camera array: 3Γ3 planar arrangement (9 cameras)
- Calibration target: Chessboard with 5Γ7 inner corners
- Storage: ~9.4 MB per BMP image (9 calibration + 9 per sample)
# Clone the repository
git clone https://github.com/ofen1996/Light-field.git
cd Light-field
# (Optional) Create a virtual environment
python -m venv venv
# Windows:
venv\Scripts\activate
# Linux/macOS:
source venv/bin/activate
# Install dependencies
pip install opencv-python numpyNo installation needed β simply open MATLAB and run Main4.m (ensure the file is on the MATLAB path).
Capture 9 images of a chessboard from your 3Γ3 camera array, all focusing on the same planar target. Place them in CalibrationPic/ as 0.bmp through 8.bmp (indexed left-to-right, top-to-bottom across the array).
Then run:
python findHomography.pyWhat it does:
- Loads all 9 calibration images from
CalibrationPic/ - Uses the center image (index 4 = middle camera) as the reference frame
- Detects 5Γ7 chessboard corners in each image via
cv2.findChessboardCorners - Refines corner locations to sub-pixel accuracy with
cv2.cornerSubPix - Computes a 3Γ3 homography matrix
Hfor each camera mapping its corners to the reference - Saves each matrix as
calibration/{index}.npy - Saves the rectified chessboard images to
calibration/pic/for visual verification
Parameter to adjust β cornersSize in findHomography.py (line ~36):
cornersSize = (5, 7) # inner corner count of your chessboardNote: The index ordering (0β8 vs row-col) must be consistent with how your hardware triggers cameras. Verify that the center image is indeed the reference before proceeding.
Place raw light field captures into pics/, organized into subdirectories by sample (each containing exactly 9 images). Then run:
python do_change.pyWhat it does:
- Loads all homography matrices from
calibration/*.npy - Walks recursively through
pics/(supports nested subdirectories) - For each sample folder containing 9
.bmpimages:- Applies
cv2.warpPerspective(image, H, image_size)with the matching calibration matrix - Saves aligned images to
result/with row-col naming (1-1.bmp,1-2.bmp, ...,3-3.bmp)
- Applies
Parameters to adjust β in do_change.py:
a, b = (3, 3) # Camera array dimensions (rows Γ cols)
folder = './pics/' # Input directory
save_folder = './result/' # Output directoryOpen MATLAB and run:
Main4The GUI window will appear with:
| Control | Description |
|---|---|
| File path | Directory containing the 9 aligned images (from result/ subfolder) |
| Camera array rows (M) | Number of rows in your camera array (default: 3) |
| Camera array cols (N) | Number of columns in your camera array (default: 3) |
| Refocus parameter (L) | Initial focus slope β negative = focus in front, positive = focus behind |
| Run button | Compute refocused image at the given L value |
| Test button | Generate a full focal stack across all slider positions |
| Slider | Drag to smoothly sweep through focal depths |
| Click on image | Auto-select the refocus depth maximizing local Sobel edge sharpness |
- Enter the full path to one sample's aligned images (e.g.,
C:\...\result\polyethylene+kieselguhr_1) - Set M and N to match your camera array dimensions
- Click Run for a single refocused image, or Test to generate a focal stack
- Use the slider to interactively sweep focal planes
- Click anywhere on the image to auto-focus on that region using edge sharpness
The focal stack sequence is saved to {result_path}/ιθη¦εΊε/ (Chinese for "refocus sequence") with filenames like -5.0.bmp, -4.9.bmp, ...
The calibration finds a 3Γ3 projective transformation H for each camera such that:
p_ref ~ H Β· p_cam
where p_cam are the detected chessboard corners in camera i, and p_ref are the corresponding corners in the center (reference) camera.
OpenCV solves H using the Direct Linear Transform (DLT) algorithm with RANSAC outlier rejection (cv2.findHomography). This accounts for perspective distortion, camera tilt, and planar misalignment β producing a rectified light field where all sub-aperture images share a common epipolar geometry.
Given a rectified light field L(u, v, x, y) where (u, v) indexes the camera and (x, y) indexes pixels, refocusing at depth alpha (slope) is performed as:
I_alpha(x, y) = sum_u sum_v L(u, v, x + alpha*u, y + alpha*v)
Each sub-aperture image is shifted by alpha * u along the horizontal and alpha * v along the vertical camera axis, then all shifted images are averaged. This is equivalent to focusing the synthetic aperture at depth 1/alpha.
In Main4.m, this is implemented as:
VVec = linspace(-0.5, 0.5, LFSize(1)) * Slope * LFSize(1);
UVec = linspace(-0.5, 0.5, LFSize(2)) * Slope * LFSize(2);
% Shift each sub-aperture with interpn, then sumClick-to-focus works by evaluating the Sobel edge response in a local window around the click point across all refocused images in the focal stack, selecting the depth with maximum sharpness.
The repository includes calibration data and two sample scenes:
| Scene | Description | Camera Count |
|---|---|---|
| CalibrationPic/ | Chessboard (5Γ7 inner corners) | 9 |
| pics/θδΉη―ι’η²ε η‘ θ»ε1/ | Polyethylene particles + diatomaceous earth, sample 1 | 9 |
| pics/θδΉη―ι’η²ε η‘ θ»ε2/ | Polyethylene particles + diatomaceous earth, sample 2 | 9 |
These scenes were captured with a 3Γ3 camera array for industrial particle analysis.
| File | Parameter | Default | Description |
|---|---|---|---|
findHomography.py |
cornersSize |
(5, 7) |
Chessboard inner corners (rows Γ cols) |
do_change.py |
a, b |
(3, 3) |
Camera array dimensions |
do_change.py |
folder |
./pics/ |
Input raw images |
do_change.py |
save_folder |
./result/ |
Output aligned images |
Main4.m |
Slider range | [-15, -5] |
Refocus slope range |
Main4.m |
Window size | [0 0 1280 720] |
GUI resolution |
The project provides cv_imread() / cv_imwrite() wrappers using cv2.imdecode + np.fromfile to handle Unicode paths. If you add new image I/O code, use these wrappers instead of cv2.imread / cv2.imwrite.
- Ensure the chessboard is fully visible and approximately planar in all 9 calibration images
- Increase lighting contrast β the algorithm needs clear black-white edges
- Verify
cornersSizematches your chessboard inner corner count (not squares) - Try adding
cv2.CALIB_CB_ADAPTIVE_THRESHto the flags
do_change.py skips folders where the image count is not equal to rows x cols (e.g., not equal to 9 for 3Γ3). Check if a sample folder has exactly the right number of BMP files.
- M and N must exactly match your camera array dimensions
- Ensure the images in your result folder are properly aligned (run the calibration stage again if needed)
- If using a different camera layout, adjust the row-col naming convention in
do_change.py
- Confirm the calibration stage completed successfully (
calibration/should contain 0.npy through 8.npy) - Verify
numpyversion compatibility
This project is shared for educational and research purposes. Please cite the repository if you use this code in published work.
Author: ofen1996
Email: 526083628@qq.com
Last Updated: 2026-08-13