https://doi.org/10.5201/ipol.2012.llm-ksvd
Tip revision: c78cdcf65f9f2fd89008647de7d7776cb2454f08 authored by Software Heritage on 01 January 2011, 00:00:00 UTC
ipol: Deposit 1191 in collection ipol
ipol: Deposit 1191 in collection ipol
Tip revision: c78cdcf
ksvd.cpp
/*
* Copyright (c) 2011, Marc Lebrun <marc.lebrun@cmla.ens-cachan.fr>
* All rights reserved.
*
* This program is free software: you can use, modify and/or
* redistribute it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either
* version 3 of the License, or (at your option) any later
* version. You should have received a copy of this license along
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file ksvd.cpp
* @brief K-SVD denoising functions
*
* @author Marc Lebrun <marc.lebrun@cmla.ens-cachan.fr>
**/
#include <iostream>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ksvd.h"
#include "lib_ormp.h"
#include "utilities.h"
#include "lib_svd.h"
#include "addnoise_function.h"
#include "io_png.h"
//! For randperm function
#include <time.h>
#ifdef _OPENMP
#include <omp.h>
#endif
using namespace std;
/**
* @brief Run K-SVD
*
* @param sigma: noise value;
* @param img_noisy : pointer to an allocated array of pixel,
* containing the noisy image;
* @param img_denoised : pointer to an allocated array which
* will contain the final denoised image;
* @param width : width of the image;
* @param height : height of the image;
* @param chnls : number of channels of the image
* @param useT : if true, use the trick acceleration in order to
* speed up the algorithm by learning the dictionary
* on a sub sample of the full patches.
*
* @return none.
**/
void ksvd_ipol(const double sigma ,
double * img_noisy ,
float * img_denoised,
const unsigned width ,
const unsigned height ,
const unsigned chnls ,
const bool useT )
{
//! Initializations
const unsigned N1 = (sigma * 255.0l <= 20 ? 5 :
(sigma * 255.0l <= 60 ? 7 : 9)); //! Size of patches
const unsigned N2 = 256; //! size of the dictionary
const unsigned N1_2 = N1 * N1;
const unsigned N_iter = 15; //! Number of iterations
const unsigned T = (sigma * 255.0l > 40.0l ? 8 :
(sigma * 255.0l > 10.0l ? 16 : 32));
const double gamma = 5.25l;
const unsigned num_patches = (width - N1 + 1) * (height - N1 + 1);
//! C = sqrt(1/(chnls * N1 * N1)*chi2inv(0.93,chnls * N1 * N1));
const double C = (chnls == 1 ? (N1 == 5 ? 1.2017729876383829 : (N1 == 7 ? 1.1456550151825420 : 1.1139195378939404))
: (N1 == 5 ? 1.1182997771678573 : (N1 == 7 ? 1.0849724948297015 : 1.0662877194412401)));
//! Assuming that img_noisy \isin [0, 255]
for (unsigned k = 0; k < width * height * chnls; k++)
img_noisy[k] /= 255.0l;
//! Declarations
matD_t patches(num_patches, vecD_t(N1_2 * chnls));
matD_t dictionary(N2, vecD_t(N1_2 * chnls));
//! Decompose the image into patches
im2patches(img_noisy, patches, width, height, chnls, N1);
//! Keep (1 / T) patches to learn the dictionary
if (useT || floor(num_patches / T) > N2)
{
//! Obtain less patches (divided by T)
const unsigned num_sub_patches = (unsigned) floor(num_patches / T);
matD_t sub_patches(num_sub_patches, vecD_t(N1_2 * chnls));
for (unsigned k = 0; k < num_sub_patches; k++)
sub_patches[k] = patches[k * T];
//! Obtain the initial dictionary
obtain_dict(dictionary, sub_patches);
//! Learn dictionary
ksvd_process(img_noisy, img_denoised, sub_patches, dictionary, \
sigma, N1, N2, N_iter, gamma, C, width, \
height, chnls, false);
//! Denoising
ksvd_process(img_noisy, img_denoised, patches, dictionary, \
sigma, N1, N2, 1, gamma, C, width, \
height, chnls, true);
}
else
{
//! Obtain the initial dictionary
obtain_dict(dictionary, patches);
//! Denoising
ksvd_process(img_noisy, img_denoised, patches, dictionary, sigma, \
N1, N2, N_iter, gamma, C, width, height, chnls, true);
}
//! Back to the [0, 255] for the image value
for (unsigned k = 0; k < width * height * chnls; k++)
img_denoised[k] *= 255.0f;
}
/**
* @brief Decompose the image into patches
*
* @param img : pointer to an allocated array containing
* the image to decompose;
* @param patches : will contain all patches (whose size
* is N x N) of img;
* @param width : width of img;
* @param height : height of img;
* @param chnls : number of channels of img.
*
* @return none.
**/
void im2patches(const double * img,
matD_t &patches,
const unsigned width,
const unsigned height,
const unsigned chnls,
const unsigned N)
{
//! Declarations
const unsigned h_p = height - N + 1;
const unsigned wh_p = (width - N + 1) * h_p;
matD_t::iterator it_p = patches.begin();
for (unsigned k = 0; k < wh_p; k++, it_p++)
{
unsigned dk = k / h_p + (k % h_p) * width;
for (unsigned c = 0; c < chnls; c++)
{
unsigned dc = c * width * height + dk;
iterD_t it = (*it_p).begin() + c * N * N;
for (unsigned p = 0; p < N; p++, dc++)
for (unsigned q = 0; q < N; q++, it++)
(*it) = (double) img[dc + q * width];
}
}
}
/**
* @brief Reconstruct images by aggregating patches of size N x N
*
* @param patches : matrix containing patches;
* @param img : pointer to an allocated array
* which will contain the denoised image;
* @param img_ref : pointer to an allocated array which
* contains the noisy image;
* @param with : width of both images;
* @param height : height of both images;
* @param chnls : number of channels of both images;
* @param lambda : weighting coefficient used for the addition
* of img_ref to img;
* @param N : size of patches (N x N).
*
* @return none.
**/
void patches2im(matD_t &patches,
float * img,
const double * img_ref,
const unsigned width,
const unsigned height,
const unsigned chnls,
const double lambda,
const unsigned N)
{
//! Declarations
const unsigned h_p = height - N + 1;
const unsigned wh_p = (width - N + 1) * h_p;
for (unsigned c = 0; c < chnls; c++)
{
vecD_t denominator(height * width, 0.0f);
vecD_t numerator (height * width, 0.0f);
matD_t::iterator it_p = patches.begin();
//! Aggregation
for (unsigned k = 0; k < wh_p; k++, it_p++)
{
unsigned ind = (k % h_p) * width + k / h_p;
iterD_t it = (*it_p).begin() + c * N * N;
for (unsigned q = 0; q < N; q++, ind++)
for (unsigned p = 0; p < N; p++, it++)
{
numerator [p * width + ind] += (*it);
denominator[p * width + ind] ++;
}
}
//! Weighting
unsigned dc_i = c * width * height;
iterD_t it_d = denominator.begin();
iterD_t it_n = numerator.begin();
for (unsigned k = 0; k < height * width; k++, dc_i++, it_d++, it_n++)
img[dc_i] = ((*it_n) + lambda * img_ref[dc_i]) / ((*it_d) + lambda);
}
}
/**
* @brief Obtain random permutation for a tabular
*
* @param perm : will contain a random sequence of [1, ..., N]
* where N is the size of perm.
*
* @return none.
**/
void randperm(vecU_t &perm)
{
//! Initializations
const unsigned N = perm.size();
vecU_t tmp(N + 1, 0);
tmp[1] = 1;
srand(unsigned(time(NULL)));
for (unsigned i = 2; i < N + 1; i++)
{
unsigned j = rand() % i + 1;
tmp[i] = tmp[j];
tmp[j] = i;
}
iterU_t it_t = tmp.begin() + 1;
for (iterU_t it_p = perm.begin(); it_p < perm.end(); it_p++, it_t++)
(*it_p) = (*it_t) - 1;
}
/**
* @brief Obtain the initial dictionary, which
* its columns are normalized
*
* @param dictionary : will contain random patches from patches,
* with its columns normalized;
* @param patches : contains all patches in the noisy image.
*
* @return none.
**/
void obtain_dict(matD_t &dictionary,
matD_t const& patches)
{
//! Declarations
vecU_t perm(patches.size());
//! Obtain random indices
randperm(perm);
//! Getting the initial random dictionary from patches
iterU_t it_p = perm.begin();
for (matD_t::iterator it_d = dictionary.begin(); it_d < dictionary.end();
it_d++, it_p++)
(*it_d) = patches[*it_p];
//! Normalize column
double norm;
for (matD_t::iterator it = dictionary.begin(); it < dictionary.end(); it++)
{
norm = 0.0l;
for (iterD_t it_d = (*it).begin(); it_d < (*it).end(); it_d++)
norm += (*it_d) * (*it_d);
norm = 1 / sqrtl(norm);
for (iterD_t it_d = (*it).begin(); it_d < (*it).end(); it_d++)
(*it_d) *= norm;
}
}
/**
* @brief Apply the whole algorithm of K-SVD
*
* @param img_noisy : pointer to an allocated array containing
* the original noisy image;
* @param img_denoised : pointer to an allocated array which
* will contain the final denoised image;
* @param patches : matrix containing all patches including in
* img_noisy;
* @param dictionary : initial random dictionary, which will be
* updated in each iteration of the algo;
* @param sigma : noise value;
* @param N1 : size of patches (N1 x N1);
* @param N2 : number of atoms in the dictionary;
* @param N_iter : number of iteration;
* @param gamma : value used in the correction matrix in the
* case of color image;
* @param C : coefficient used for the stopping criteria of
* the ORMP;
* @param width : width of both images;
* @param height : height of both images;
* @param chnls : number of channels of both images;
* @param doReconstruction : if true, do the reconstruction of
* the final denoised image from patches
* (only in the case of the acceleration
* trick).
*
* @return none.
**/
void ksvd_process(const double *img_noisy,
float *img_denoised,
matD_t &patches,
matD_t &dictionary,
const double sigma,
const unsigned N1,
const unsigned N2,
const unsigned N_iter,
const double gamma,
const double C,
const unsigned width,
const unsigned height,
const unsigned chnls,
const bool doReconstruction)
{
//! Declarations
const unsigned N1_2 = N1 * N1;
const double corr = (sqrtl(1.0l + gamma) - 1.0l) / ((double) N1_2);
const double eps = ((double) (chnls * N1_2)) * C * C * sigma * sigma;
const unsigned h_p = patches[0].size();
const unsigned w_p = patches.size();
//! Mat & Vec initializations
matD_t dict_ormp (N2 , vecD_t(h_p, 0.0l));
matD_t patches_ormp(w_p, vecD_t(h_p, 0.0l));
matD_t tmp (h_p, vecD_t(N2, 0.0l));
vecD_t normCol (N2);
matD_t Corr (h_p, vecD_t(h_p, 0.0l));
vecD_t U (h_p);
vecD_t V;
matD_t E (w_p, vecD_t(h_p));
//! Vector for ORMP
matD_t ormp_val (w_p, vecD_t ());
matU_t ormp_ind (w_p, vecU_t ());
matD_t res_ormp (N2, vecD_t (w_p));
matU_t omega_table (N2, vecU_t ());
vecU_t omega_size_table(N2, 0);
matD_t alpha (N2, vecD_t ());
//! To avoid reallocation of memory
for (unsigned k = 0; k < w_p; k++)
{
ormp_val[k].reserve(N2);
ormp_ind[k].reserve(N2);
}
for (matU_t::iterator it = omega_table.begin(); it < omega_table.end(); it++)
it->reserve(w_p);
V.reserve(w_p);
//! Correcting matrix
for (unsigned i = 0; i < h_p; i++)
Corr[i][i] = 1.0l;
for (unsigned c = 0; c < chnls; c++)
{
matD_t::iterator it_Corr = Corr.begin() + N1_2 * c;
for (unsigned i = 0; i < N1_2; i++, it_Corr++)
{
iterD_t it = it_Corr->begin() + N1_2 * c;
for (unsigned j = 0; j < N1_2; j++, it++)
(*it) += corr;
}
}
#pragma omp parallel for
for (unsigned j = 0; j < w_p; j++)
{
for (unsigned c = 0; c < chnls; c++)
{
iterD_t it_ormp = patches_ormp[j].begin() + c * N1_2;
iterD_t it = patches[j].begin() + c * N1_2;
for (unsigned i = 0; i < N1_2; i++, it++, it_ormp++)
{
double val = 0.0l;
iterD_t it_tmp = patches[j].begin() + c * N1_2;
for (unsigned k = 0; k < N1_2; k++, it_tmp++)
val += corr * (*it_tmp);
(*it_ormp) = val + (*it);
}
}
}
//! Big loop
for (unsigned iter = 0; iter < N_iter; iter++)
{
//! Sparse coding
if (doReconstruction)
cout << "Final Step :" << endl;
else
cout << "Step " << iter + 1 << ":" << endl;
cout << " - Sparse coding" << endl;
for (unsigned i = 0; i < h_p; i++)
{
iterD_t it_tmp = tmp[i].begin();
for (unsigned j = 0; j < N2; j++, it_tmp++)
{
double val = 0.0l;
iterD_t it_corr_i = Corr[i].begin();
iterD_t it_dict_j = dictionary[j].begin();
for (unsigned k = 0; k < h_p; k++, it_corr_i++, it_dict_j++)
val += (*it_corr_i) * (*it_dict_j);
(*it_tmp) = val * val;
}
}
iterD_t it_normCol = normCol.begin();
for (unsigned j = 0; j < N2; j++, it_normCol++)
{
double val = 0.0l;
for (unsigned i = 0; i < h_p; i++)
val += tmp[i][j];
(*it_normCol) = 1.0l / sqrtl(val);
}
for (unsigned i = 0; i < h_p; i++)
{
iterD_t it_normCol_j = normCol.begin();
for (unsigned j = 0; j < N2; j++, it_normCol_j++)
{
double val = 0.0l;
iterD_t it_corr_i = Corr[i].begin();
iterD_t it_dict_j = dictionary[j].begin();
for (unsigned k = 0; k < h_p; k++, it_corr_i++, it_dict_j++)
val += (*it_corr_i) * (*it_dict_j);
dict_ormp[j][i] = val * (*it_normCol_j);
}
}
//! ORMP process
cout << " - ORMP process" << endl;
ormp_process(patches_ormp, dict_ormp, ormp_ind, ormp_val, N2, eps);
for (unsigned i = 0; i < w_p; i++)
{
iterU_t it_ind = ormp_ind[i].begin();
iterD_t it_val = ormp_val[i].begin();
const unsigned size = ormp_val[i].size();
for (unsigned j = 0; j < size; j++, it_ind++, it_val++)
(*it_val) *= normCol[*it_ind];
}
//! Residus
for (unsigned i = 0; i < N2; i++)
{
omega_size_table[i] = 0;
omega_table[i].clear();
alpha[i].clear();
for (iterD_t it = res_ormp[i].begin(); it < res_ormp[i].end(); it++)
*it = 0.0l;
}
for (unsigned i = 0; i < w_p; i++)
{
iterU_t it_ind = ormp_ind[i].begin();
iterD_t it_val = ormp_val[i].begin();
for (unsigned j = 0; j < ormp_val[i].size(); j++, it_ind++, it_val++)
{
omega_table[*it_ind].push_back(i);
omega_size_table[*it_ind]++;
alpha[*it_ind].push_back(*it_val);
res_ormp[*it_ind][i] = *it_val;
}
}
//! Dictionary update
cout << " - Dictionary update" << endl;
for (unsigned l = 0; l < N2; l++)
{
//! Initializations
const unsigned omega_size = omega_size_table[l];
iterD_t it_dict_l = dictionary[l].begin();
iterD_t it_alpha_l = alpha[l].begin();
iterU_t it_omega_l = omega_table[l].begin();
U.assign(U.size(), 0.0l);
if (omega_size > 0)
{
iterD_t it_a = it_alpha_l;
iterU_t it_o = it_omega_l;
for (unsigned j = 0; j < omega_size; j++, it_a++, it_o++)
{
iterD_t it_d = it_dict_l;
iterD_t it_e = E[j].begin();
iterD_t it_p = patches[*it_o].begin();
for (unsigned i = 0; i < h_p; i++, it_d++, it_e++, it_p++)
(*it_e) = (*it_p) + (*it_d) * (*it_a);
}
matD_t::iterator it_res = res_ormp.begin();
for (unsigned k = 0; k < N2; k++, it_res++)
{
iterU_t it_o = it_omega_l;
iterD_t it_dict_k = dictionary[k].begin();
for (unsigned j = 0; j < omega_size; j++, it_o++)
{
const double val = (*it_res)[*it_o];
if (fabs(val) > 0.0l)
{
iterD_t it_d = it_dict_k;
iterD_t it_e = E[j].begin();
for (unsigned i = 0; i < h_p; i++, it_d++, it_e++)
(*it_e) -= (*it_d) * val;
}
}
}
//! SVD truncated
V.resize(omega_size);
double S = svd_trunc(E, U, V);
dictionary[l] = U;
it_a = it_alpha_l;
iterD_t it_v = V.begin();
it_o = it_omega_l;
for (unsigned j = 0; j < omega_size; j++, it_a++, it_v++, it_o++)
res_ormp[l][*it_o] = (*it_a) = (*it_v) * S;
}
}
cout << " - done." << endl;
}
if (doReconstruction)
{
//! Final patches estimations
cout << " - Aggregation" << endl;
for (matD_t::iterator it = patches.begin(); it < patches.end(); it++)
for (iterD_t it_p = it->begin(); it_p < it->end(); it_p++)
*it_p = 0.0l;
#pragma omp parallel for
for (unsigned l = 0; l < N2; l++)
{
iterD_t it_a = alpha[l].begin();
iterU_t it_o = omega_table[l].begin();
const unsigned omega_size = omega_size_table[l];
for (unsigned j = 0; j < omega_size; j++, it_a++, it_o++)
{
const double val = (*it_a);
iterD_t it_d = dictionary[l].begin();
iterD_t it_p = patches[*it_o].begin();
for (unsigned k = 0; k < h_p; k++, it_d++, it_p++)
(*it_p) += (*it_d) * val;
}
}
//! First, obtention of the denoised image without weighting with lambda
patches2im(patches, img_denoised, img_noisy, width, height, chnls, 0.0l, N1);
//! Second, obtention of lambda from the norm between img_denoised and img_noisy
double d = 0.0l;
for (unsigned k = 0; k < width * height * chnls; k++)
d += (img_denoised[k] - img_noisy[k]) * (img_denoised[k] - img_noisy[k]);
d /= (height * width * chnls * sigma * sigma);
const double lambda = abs(sqrtl(d) - 1.0l);
//! Finally, obtention of the denoised image with lambda
patches2im(patches, img_denoised, img_noisy, width, height, chnls, lambda, N1);
}
}