├── .gitattributes ├── README.md ├── objects ├── circle_aperture.png ├── ghost.png ├── pacman.png ├── pacman_64px.mat └── square_aperture.png ├── sp_demo.m └── sp_demo.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # single_pixel_demo 2 | Two implementations: matlab + python 3 | 4 | Demo for single-pixel imaging using Hadamard patterns as sensing basis. 5 | Loads an object, generates the sensing patterns (Hadamard), does the measurements 6 | (with and without noise), and recovers the image. 7 | 8 | Patterns are supposed to be sent with a DMD, so we generate couples of patterns(H+ and H-), 9 | because Hadamard patterns are conformed of +1/-1 entries. The measurement is done in two steps, with 10 | noise added. 11 | 12 | Reconstruction is done by inversion. 13 | 14 | Might add some compressive sensing in the future to do subsampling, though there are million examples out there of that. 15 | -------------------------------------------------------------------------------- /objects/circle_aperture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbasedlf/single_pixel_demo/6893caa992190f56fb0348fb916bc95deed032d3/objects/circle_aperture.png -------------------------------------------------------------------------------- /objects/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbasedlf/single_pixel_demo/6893caa992190f56fb0348fb916bc95deed032d3/objects/ghost.png -------------------------------------------------------------------------------- /objects/pacman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbasedlf/single_pixel_demo/6893caa992190f56fb0348fb916bc95deed032d3/objects/pacman.png -------------------------------------------------------------------------------- /objects/pacman_64px.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbasedlf/single_pixel_demo/6893caa992190f56fb0348fb916bc95deed032d3/objects/pacman_64px.mat -------------------------------------------------------------------------------- /objects/square_aperture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cbasedlf/single_pixel_demo/6893caa992190f56fb0348fb916bc95deed032d3/objects/square_aperture.png -------------------------------------------------------------------------------- /sp_demo.m: -------------------------------------------------------------------------------- 1 | % Demo for single-pixel imaging using Hadamard patterns as sensing basis 2 | % Loads an object, generates the sensing patterns, does the measurements 3 | % (with and without noise), and recovers the image. 4 | 5 | close all 6 | clearvars 7 | clc 8 | 9 | %% Path 10 | addpath('.\objects'); 11 | 12 | %% Load object (64x64 pixels) 13 | load('pacman_64px.mat'); 14 | 15 | %% Generate measurement patterns. Each row (column) is a Hadamard pattern 16 | H = hadamard(64^2); %True Hadamard (+1 and -1 elements) 17 | Hplus = (H+1)/2; %H+ Hadamard (1s and 0s) 18 | Hminus = (1-H)/2; %H- Hadamard (0s and 1s) 19 | 20 | %% Generate measurements simulating H+ and H- (as if were generated on a DMD) 21 | Mplus = Hplus*obj(:); %Project H+ 22 | Mminus = Hminus*obj(:); %Project H- 23 | 24 | %Generate measurements corrupted by noise 25 | snr = 35; %signal-to-noise ratio (dB) 26 | MplusNoise = awgn(Mplus,snr,'measured'); 27 | MminusNoise = awgn(Mminus,snr,'measured'); 28 | 29 | %Substract H+ and H- to generate true Hadamard coefficients 30 | M = Mplus - Mminus; 31 | MNoise = MplusNoise - MminusNoise; 32 | 33 | %% Recover objects 34 | recovery = H\M; %Inversion from measurements 35 | recovery = reshape(recovery,[64 64]); %Reshape into image 36 | 37 | recoveryNoise = H\MNoise; %Inversion from measurements 38 | recoveryNoise = reshape(recoveryNoise,[64 64]); %Reshape into image 39 | 40 | %% Show results 41 | figure(1) 42 | subplot(2,3,1) 43 | imagesc(obj);axis square; title('object'); 44 | subplot(2,3,2) 45 | imagesc(recovery);axis square; title('recovery (no noise)'); 46 | subplot(2,3,3) 47 | imagesc(recoveryNoise);axis square; title('recovery (with noise)'); 48 | subplot(2,3,[4 5 6]) 49 | plot(M); 50 | hold on 51 | plot(MNoise); 52 | title('measurements'); legend('no noise','with noise'); 53 | hold off -------------------------------------------------------------------------------- /sp_demo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | #%% Demo for single-pixel imaging using Hadamard patterns as sensing basis 3 | # Loads an object, generates the sensing patterns (Hadamard), 4 | # does the measurements (with and without noise), and recovers the image. 5 | 6 | #%% Import libraries 7 | from typing import Tuple 8 | from matplotlib import pyplot as plt 9 | import numpy as np 10 | # Generate hadamard matrices 11 | from scipy.linalg import hadamard 12 | # Working with images 13 | from skimage.transform import resize 14 | from PIL import Image 15 | 16 | #%% Define useful functions 17 | def noisify(signal: np.ndarray, end_snr: float) -> Tuple[np.ndarray, np.ndarray]: 18 | ''' 19 | Add white gaussian noise to a signal so it ends with end_snr 20 | signal-to-noise ratio. 21 | More info at: https://en.wikipedia.org/wiki/Signal-to-noise_ratio#Decibels 22 | 23 | Parameters 24 | ---------- 25 | signal : np.ndarray 26 | Input signal 27 | end_snr : float 28 | Desired SNR. 29 | Returns 30 | ------- 31 | noisy_signal : np.ndarray 32 | Signal with added noise. 33 | noise : np.ndarray 34 | Noise added to the original signal. 35 | ''' 36 | # Calculate signal average (~power) 37 | signal_avg = np.mean(signal) 38 | # Convert to dB 39 | signal_avg_db = 10 * np.log10(signal_avg) 40 | # Calculate noise power in dB, using objective SNR 41 | # SNR = P_signal(in dB) - P_noise(in dB) 42 | noise_avg_db = signal_avg_db - end_snr 43 | # Convert to noise average (~power) 44 | noise_avg = 10**(noise_avg_db/10) 45 | # Build noise with desired power 46 | noise = np.random.normal(0, np.sqrt(noise_avg), signal.size) 47 | # Add noise to signal 48 | noisy_signal = signal + noise 49 | 50 | return noisy_signal, noise 51 | 52 | # %% Load image & generate object 53 | # load image, convert to grayscale, store as array (matrix) 54 | ground_obj = np.asarray(Image.open("./objects/ghost.png").convert('L')) 55 | # Choose new size (don't go higher than 128x128 or Hadamard will kill you) 56 | px = 32 57 | # Resize image to smaller size for simulation 58 | test_obj = resize(ground_obj,(px,px)) 59 | 60 | #%% Generate measurement patterns using a Hadamard matrix. 61 | ## Each row is a 2D pattern (after reshaping) 62 | H = hadamard(px**2) # Complete Hadamard matrix (+1s and -1s) 63 | Hplus = (H + 1) / 2 # H+ Hadamard matrix (1s and 0s) 64 | Hminus = (1 - H) / 2 # H- Hadamard matrix (0s and 1s) 65 | 66 | #%% Generate measurements simulating H+ and H- 67 | # (as if patterns were generated on a DMD) 68 | Mplus = Hplus @ test_obj.flatten() # Project H+ patterns, store intensity 69 | Mminus = Hminus @ test_obj.flatten() # Project H- patterns, store intensity 70 | M = Mplus - Mminus # Substract values (H+ - H-) 71 | 72 | # Generate measurements from a noisy object 73 | desired_SNR = 20 # SNR desired for the object 74 | # Add noise to the true coefficients 75 | Mplus_noisy, _ = noisify(Mplus, desired_SNR) # Project H+ 76 | Mminus_noisy, _ = noisify(Mminus, desired_SNR) # Project H- 77 | M_noisy = Mplus_noisy - Mminus_noisy # Substract values (H+ - H-) 78 | 79 | #%% Recover objects 80 | # Inversion from measurements (solve the eq. systems with/without noise) 81 | recovery = np.linalg.solve(H, M) 82 | recovery_noise = np.linalg.solve(H, M_noisy) 83 | # Reshape from vector into image 84 | recovery = recovery.reshape((px, px)) 85 | recovery_noise = recovery_noise.reshape((px, px)) 86 | 87 | #%% Show the results 88 | # Show recovery without noise 89 | plt.figure() 90 | plt.imshow(recovery, cmap = 'hot') 91 | plt.axis('on') 92 | plt.title('Recovered image without noise') 93 | plt.colorbar() 94 | plt.show() 95 | # Show recovery with noise 96 | plt.figure() 97 | plt.imshow(recovery_noise, cmap = 'hot') 98 | plt.axis('on') 99 | plt.title('Recovered image with noise') 100 | plt.colorbar() 101 | plt.show() 102 | --------------------------------------------------------------------------------