# Pseudo-code of our algorithm, in python-like syntax.
# This code is for demonstration purposes only; our actual implementation is in C++11
# Input: Image half buffers `colorA`, `colorB`, with samples evenly split between them
# Array of auxiliary feature half buffers `featuresA`, `featuresB`, with samples evenly split between them
# Sample mean variance of combined color image (colorA + colorB)/2, `colorVar`
# Array of sample mean variances of combined auxiliary feature buffers, `featureVar`
#
# All of these inputs are images of the same size.
# Arithmetic operations are assumed to operate on a per-pixel basis, i.e.
# (A + B)/2 computes the per-pixel average of two images A and B and returns another image
def filterMain(colorA, colorB, featuresA, featuresB, colorVar, featureVars):
color = (colorA + colorB)/2
# Feature cross-prefiltering (section 5.1)
filteredFeaturesA = []
filteredFeaturesB = []
for i in range(len(featureVars)):
# Cross filtering
filteredFeaturesA.append(nlMeans(featuresA[i], featuresB[i], 2*featureVars[i], F=3, R=5, k=0.5)
filteredFeaturesB.append(nlMeans(featuresB[i], featuresA[i], 2*featureVars[i], F=3, R=5, k=0.5)
# Main regression (section 5.2)
ks = [0.5, 1.0]
filteredColorsA = []
filteredColorsB = []
mses = []
for k in ks:
# Regression pass
filteredColorA = collaborativeRegression(colorA, filteredFeaturesB, 2*colorVar, k)
filteredColorB = collaborativeRegression(colorB, filteredFeaturesA, 2*colorVar, k)
filteredColorsA.append(filteredColorA)
filteredColorsB.append(filteredColorB)
# MSE estimation (section 5.3)
mseA = (colorB - filteredColorA)^2 - 2*colorVar
mseB = (colorA - filteredColorB)^2 - 2*colorVar
residualColorVariance = (filteredColorB - filteredColorA)^2/4
noisyMse = (mseA + mseB)/2 - residualColorVariance
# MSE filtering
mses.append(nlMeans(noisyMse, color, colorVar, F=1, R=9, k=1.0))
# Bandwidth selection (section 5.3)
resultA = 0
resultB = 0
for i in range(len(ks)):
# Generate selection map
noisySelection = (mses[i] == min(mses))
# Filter selection map
selection = nlMeans(noisySelection, color, colorVar, F=1, R=9, k=1.0)
# Apply selection map
resultA += filteredColorsA[i]*selection
resultB += filteredColorsB[i]*selection
# Second filter pass (section 5.4)
finalFeatures = []
for i in range(len(featureVars)):
combinedFeature = (filteredFeaturesA[i] + filteredFeaturesB[i])/2
combinedFeatureVar = (filteredFeaturesB[i] - filteredFeaturesA[i])^2/4
finalFeatures.append(nlMeans(combinedFeature, combinedFeature, combinedFeatureVar, F=3, R=2, k=0.5))
combinedResult = (resultA + resultB)/2
combinedResultVar = (resultB - resultA)^2/4
return collaborativeRegression(combinedResult, finalFeatures, combinedResultVar, 1.0)
def collaborativeRegression(image, features, imageVar, k):
# notation:
# P: list of pixel indices in a patch. N = len(P)
# image_P: returns N x 3 matrix of RGB image pixels contained in patch P
# features_P: returns N x len(features) matrix of auxiliary feature vectors contained in patch P
result = 0
weights = 0
for P in patches_19x19(image):
w = nlmWeights(image_P, imageVar_P, F=3, R=9, k=k)
W = diagMatrix(w)
Y = buffer_P
X = features_P
reconstruction = X (X^T W X)^-1 X^T W Y
result_P += w*reconstruction;
weights_P += w
return result/weights
# Standard NL-Means filter, filtering input image `image`, using distances computed
# on guide image `guide`, with guide variance `variance`
# with patch radius `F`, filter radius `R`, and bandwidth `k`
# Please see Rousselle et al., "Robust Denoising using Feature and Color Information", for details
def nlMeans(image, guide, variance, F, R, k):
# [omitted for brevity]