blob: eb052fcf528090204124f663c5ddb6c5ed35a6f9 [file]
// SPDX-FileCopyrightText: The Eigen Authors
// SPDX-License-Identifier: MPL-2.0
// Image deblurring with periodic boundary conditions. Blurring an n x n image
// X with a point spread function G is the 2-D circular convolution G (*) X, so
// the blur matrix A is a Bccb. For a motion blur over L pixels, the box filter,
// the symbol vanishes on the L - 1 frequency lines k = j n / L: A is exactly
// rank deficient, Bccb::rank counts the retained modes, and the pseudo-inverse
// A.solve recovers nothing on those lines while amplifying the noise of the
// recorded image on the lines next to them. Tikhonov regularization,
// x_lambda = (A^H A + lambda I)^{-1} A^H b,
// keeps the structure: A^H A + lambda I is the Bccb whose generator is the
// autocorrelation A^H (A e_0) of the PSF plus lambda at the origin, so each
// candidate lambda costs one O(N log N) solve.
#include <contrib/Eigen/StructuredMatrices>
#include <cmath>
#include <iostream>
#include <random>
using namespace Eigen;
int main() {
const int n = 128, N = n * n;
const int L = 8; // the camera moves L pixels along the columns during the exposure
MatrixXd G = MatrixXd::Zero(n, n);
G.col(0).head(L).setConstant(1.0 / L);
const auto A = makeBccb(G);
// The image: a bright rectangle and a disc on a dark background.
MatrixXd X = MatrixXd::Constant(n, n, 0.1);
X.block(32, 40, 32, 48).setConstant(1.0);
for (int j = 0; j < n; ++j)
for (int i = 0; i < n; ++i)
if ((i - 90) * (i - 90) + (j - 40) * (j - 40) < 400) X(i, j) = 0.6;
const VectorXd x = X.reshaped();
// Blur and add 1% noise from a generator whose sequence the C++ standard fixes.
std::minstd_rand generator(1);
VectorXd b = A * x;
for (int i = 0; i < N; ++i) b[i] += 0.01 * (2.0 * generator() / generator.max() - 1);
const VectorXd s = A.singularValues(); // decreasing
std::cout << "rank " << A.rank() << " of " << N << ", smallest retained singular value " << s[A.rank() - 1] << "\n";
std::cout << "relative error of the blurred image: " << (b - x).norm() / x.norm() << "\n";
std::cout << "relative error of the pseudo-inverse: " << (A.solve(b) - x).norm() / x.norm() << "\n";
VectorXd e0 = VectorXd::Zero(N);
e0[0] = 1;
const VectorXd autocorrelation = A.adjoint() * VectorXd(A * e0); // first column of A^H A
const VectorXd Ahb = A.adjoint() * b;
for (double lambda : {1e-4, 1e-3, 1e-2, 1e-1}) {
VectorXd g = autocorrelation;
g[0] += lambda;
const VectorXd xl = makeBccb(g.reshaped(n, n)).solve(Ahb);
std::cout << "Tikhonov, lambda = " << lambda << ": relative error " << (xl - x).norm() / x.norm() << "\n";
}
}