Total Variation Deconvolution using Split Bregman
imblur.c
Go to the documentation of this file.
1 
15 #include <math.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <time.h>
19 #include <fftw3.h>
20 #include "cliio.h"
21 #include "kernels.h"
22 #include "randmt.h"
23 
24 #ifndef MIN
25 #define MIN(a,b) (((a) <= (b)) ? (a) : (b))
26 #endif
27 
28 #define _CONCAT(A,B) A ## B
29 
30 #ifdef NUM_SINGLE
31 #define FFT(S) _CONCAT(fftwf_,S)
32 #else
33 #define FFT(S) _CONCAT(fftw_,S)
34 #endif
35 
36 
38 typedef num numcomplex[2];
39 
40 typedef enum {NOISE_GAUSSIAN, NOISE_LAPLACE, NOISE_POISSON} noisetype;
41 
43 typedef struct
44 {
46  const char *InputFile;
48  const char *OutputFile;
51 
53  noisetype NoiseType;
55  num Sigma;
59 
60 
62 static void PrintHelpMessage()
63 {
64  puts("Image blurring utility, P. Getreuer 2011-2012\n\n"
65  "Usage: imblur [param:value ...] input output\n\n"
66  "where \"input\" and \"output\" are "
67  READIMAGE_FORMATS_SUPPORTED " files.\n");
68  puts("Parameters");
69  puts(" K:<kernel> blur kernel for deconvolution");
70  puts(" K:disk:<radius> filled disk kernel");
71  puts(" K:gaussian:<sigma> Gaussian kernel");
72  puts(" K:<file> read kernel from text or image file");
73  puts(" noise:<model>:<sigma> simulate noise with standard deviation sigma");
74  puts(" noise:gaussian:<sigma> additive white Gaussian noise");
75  puts(" noise:laplace:<sigma> Laplace noise");
76  puts(" noise:poisson:<sigma> Poisson noise");
77  puts(" f:<file> input file (alternative syntax)");
78  puts(" u:<file> output file (alternative syntax)");
79 #ifdef USE_LIBJPEG
80  puts(" jpegquality:<number> quality for saving JPEG images (0 to 100)");
81 #endif
82  puts("\nExample: \n"
83  " imblur noise:gaussian:5 K:disk:2 input.bmp blurry.bmp\n");
84 }
85 
86 int ParseParams(programparams *Params, int argc, const char *argv[]);
87 
88 
96 static num WSymExtension(const num *Data, int Width, int Height,
97  int x, int y)
98 {
99  while(1)
100  {
101  if(x < 0)
102  x = -1 - x;
103  else if(x >= Width)
104  x = (2*Width - 1) - x;
105  else
106  break;
107  }
108 
109  while(1)
110  {
111  if(y < 0)
112  y = -1 - y;
113  else if(y >= Height)
114  y = (2*Height - 1) - y;
115  else
116  break;
117  }
118 
119  return Data[x + ((long)Width)*y];
120 }
121 
122 
132 numcomplex *ComputePaddedDFT(num *PadTemp, int PadWidth, int PadHeight,
133  const num *Src, int SrcWidth, int SrcHeight,
134  int IsKernelFlag)
135 {
136  const int PadXOffset = (PadWidth - SrcWidth)/2;
137  const int PadYOffset = (PadHeight - SrcHeight)/2;
138  const long TransNumPixels = ((long)(PadWidth/2 + 1))*((long)PadHeight);
139  numcomplex *Dest = NULL;
140  FFT(plan) Plan = NULL;
141  int x, y;
142 
143  /* Allocate memory */
144  if(!(Dest = (numcomplex *)FFT(malloc)(sizeof(numcomplex)*TransNumPixels)))
145  {
146  fprintf(stderr, "Memory allocation failed.\n");
147  return NULL;
148  }
149 
150  /* Create plan for DFT transform of PadTemp */
151  if(!(Plan = FFT(plan_dft_r2c_2d)(PadHeight, PadWidth, PadTemp,
152  Dest, FFTW_ESTIMATE | FFTW_DESTROY_INPUT)))
153  {
154  fprintf(stderr, "FFTW plan creation failed.\n");
155  FFT(free)(Dest);
156  return NULL;
157  }
158 
159  if(IsKernelFlag) /* Do zero-padding extrapolation for the kernel */
160  {
161  const int SrcHalfWidth = SrcWidth/2;
162  const int SrcHalfHeight = SrcHeight/2;
163 
164  for(y = 0; y < SrcWidth; y++)
165  for(x = 0; x < SrcHeight; x++)
166  PadTemp[x + ((long)PadWidth)*y] = 0;
167 
168  for(y = 0; y < SrcHeight; y++)
169  for(x = 0; x < SrcWidth; x++)
170  PadTemp[((x < SrcHalfWidth) ?
171  (PadWidth - SrcHalfWidth + x) : (x - SrcHalfWidth))
172  + ((long)PadWidth)*((y < SrcHalfHeight) ?
173  (PadHeight - SrcHalfHeight + y) : (y - SrcHalfHeight))]
174  = Src[x + SrcWidth*y];
175  }
176  else /* Whole-sample symmetric extrapolation for the image */
177  for(y = 0; y < PadHeight; y++)
178  for(x = 0; x < PadWidth; x++)
179  PadTemp[x + ((long)PadWidth)*y]
180  = WSymExtension(Src, SrcWidth, SrcHeight,
181  x - PadXOffset, y - PadYOffset);
182 
183  FFT(execute)(Plan);
184  FFT(destroy_plan)(Plan);
185  return Dest;
186 }
187 
188 
198 int ComputePaddedIDFT(num *Dest, int DestWidth, int DestHeight,
199  num *PadTemp, int PadWidth, int PadHeight, numcomplex *Src)
200 {
201  const int PadXOffset = (PadWidth - DestWidth)/2;
202  const int PadYOffset = (PadHeight - DestHeight)/2;
203  FFT(plan) Plan = NULL;
204  int x, y;
205 
206  /* Create plan for DFT transform of PadTemp */
207  if(!(Plan = FFT(plan_dft_c2r_2d)(PadHeight, PadWidth,
208  Src, PadTemp, FFTW_ESTIMATE | FFTW_DESTROY_INPUT)))
209  {
210  fprintf(stderr, "FFTW plan creation failed.\n");
211  return 0;
212  }
213 
214  FFT(execute)(Plan);
215  FFT(destroy_plan)(Plan);
216 
217  PadTemp += PadXOffset + PadWidth*PadYOffset;
218 
219  for(y = 0; y < DestHeight; y++, Dest += DestWidth, PadTemp += PadWidth)
220  for(x = 0; x < DestWidth; x++)
221  Dest[x] = PadTemp[x];
222 
223  return 1;
224 }
225 
226 
235 int BlurImage(num *Image, int ImageWidth, int ImageHeight, int NumChannels,
236  const num *Kernel, int KernelWidth, int KernelHeight)
237 {
238  const int ImageNumPixels = ImageWidth*ImageHeight;
239  num *PadTemp = NULL;
240  numcomplex *ImageFourier = NULL;
241  numcomplex *KernelFourier = NULL;
242  numcomplex G;
243  num Temp;
244  int PadWidth, PadHeight, PadNumPixels;
245  int TransWidth;
246  int i, x, y, Channel, Success = 0;
247 
248 
249  if(!Image || !Kernel || !ImageWidth || !ImageHeight)
250  return 0;
251  else if(KernelWidth*KernelHeight <= 1)
252  return 1;
253 
254  /* Determine padded size of the image */
255  PadWidth = 2*ImageWidth;
256  PadHeight = 2*ImageHeight;
257  PadNumPixels = PadWidth*PadHeight;
258 
259  while(PadWidth < KernelWidth)
260  PadWidth += 2*ImageWidth;
261  while(PadHeight < KernelHeight)
262  PadHeight += 2*KernelHeight;
263 
264  /* Determine size of Fourier transform array */
265  TransWidth = PadWidth/2 + 1;
266 
267  /* Allocate memory */
268  if(!(PadTemp = (num *)FFT(malloc)(sizeof(num)*PadNumPixels)))
269  {
270  fprintf(stderr, "Memory allocation failed.\n");
271  goto Catch;
272  }
273 
274  /* Compute Fourier transform of Kernel */
275  if(!(KernelFourier = ComputePaddedDFT(PadTemp, PadWidth, PadHeight,
276  Kernel, KernelWidth, KernelHeight, 1)))
277  goto Catch;
278 
279  for(Channel = 0; Channel < NumChannels; Channel++)
280  {
281  /* Compute Fourier transform of image channel */
282  if(!(ImageFourier = ComputePaddedDFT(PadTemp, PadWidth, PadHeight,
283  Image + Channel*ImageNumPixels,
284  ImageWidth, ImageHeight, 0)))
285  goto Catch;
286 
287  for(y = i = 0; y < PadHeight; y++)
288  for(x = 0; x < TransWidth; x++, i++)
289  {
290  G[0] = KernelFourier[i][0]/PadNumPixels;
291  G[1] = KernelFourier[i][1]/PadNumPixels;
292 
293  /* Compute the filtered frequency */
294  Temp = G[0]*ImageFourier[i][0] - G[1]*ImageFourier[i][1];
295  ImageFourier[i][1] = G[0]*ImageFourier[i][1]
296  + G[1]*ImageFourier[i][0];
297  ImageFourier[i][0] = Temp;
298  }
299 
300  if(!ComputePaddedIDFT(Image + Channel*ImageNumPixels,
301  ImageWidth, ImageHeight,
302  PadTemp, PadWidth, PadHeight, ImageFourier))
303  goto Catch;
304  }
305 
306  Success = 1;
307 Catch:
308  if(KernelFourier)
309  FFT(free)(KernelFourier);
310  if(ImageFourier)
311  FFT(free)(ImageFourier);
312  if(PadTemp)
313  FFT(free)(PadTemp);
314  return Success;
315 }
316 
317 
325 void GenerateNoise(num *Data, long NumEl, noisetype NoiseType, num Sigma)
326 {
327  long i;
328 
329  switch(NoiseType)
330  {
331  case NOISE_GAUSSIAN:
332  for(i = 0; i < NumEl; i++)
333  Data[i] += (num)(Sigma * rand_normal());
334  break;
335  case NOISE_LAPLACE:
336  {
337  const num Mu = (num)(M_1_SQRT2 * Sigma);
338 
339  for(i = 0; i < NumEl; i++)
340  Data[i] += (num)(rand_exp(Mu)
341  * ((rand_unif() < 0.5) ? -1 : 1));
342  }
343  break;
344  case NOISE_POISSON:
345  {
346  double a, Mean = 0;
347 
348  for(i = 0; i < NumEl; i++)
349  Mean += Data[i];
350 
351  Mean /= NumEl;
352  a = Sigma * Sigma / ((Mean > 0) ? Mean : (0.5/255));
353 
354  for(i = 0; i < NumEl; i++)
355  Data[i] = (num)(rand_poisson(Data[i] / a) * a);
356  }
357  break;
358  }
359 }
360 
361 
362 int main(int argc, char **argv)
363 {
364  programparams Params;
365  image f = NullImage;
366  int Status = 1;
367 
368  if(!ParseParams(&Params, argc, (const char **)argv))
369  goto Catch;
370 
371  /* Initialize random number generator */
373 
374  /* Read the input image */
375  if(!ReadImageObj(&f, Params.InputFile))
376  goto Catch;
377 
378  /* Perform blurring */
379  if(!BlurImage(f.Data, f.Width, f.Height, f.NumChannels,
380  Params.Kernel.Data, Params.Kernel.Width, Params.Kernel.Height))
381  goto Catch;
382 
383  if(Params.Sigma != 0)
385  (((long)f.Width)*((long)f.Height))*f.NumChannels,
386  Params.NoiseType, Params.Sigma);
387 
388  /* Write output */
389  if(!WriteImageObj(f, Params.OutputFile, Params.JpegQuality))
390  fprintf(stderr, "Error writing to \"%s\".\n", Params.OutputFile);
391 
392  Status = 0;
393 Catch:
394  FreeImageObj(f);
395  FreeImageObj(Params.Kernel);
396  return Status;
397 }
398 
399 
401 static int ReadNoise(programparams *Params, const char *String)
402 {
403  const char *ColonPtr;
404  int Length;
405  char NoiseName[32];
406 
407  if(!(ColonPtr = strchr(String, ':'))
408  || (Length = (int)(ColonPtr - String)) > 9)
409  return 0;
410 
411  strncpy(NoiseName, String, Length);
412  NoiseName[Length] = '\0';
413  Params->Sigma = (num)atof(ColonPtr + 1);
414  Params->Sigma /= 255;
415 
416  if(Params->Sigma < 0)
417  {
418  fputs("Noise standard deviation must be nonnegative.\n", stderr);
419  return 0;
420  }
421 
422  if(!strcmp(NoiseName, "Gaussian") || !strcmp(NoiseName, "gaussian"))
423  Params->NoiseType = NOISE_GAUSSIAN;
424  else if(!strcmp(NoiseName, "Laplace") || !strcmp(NoiseName, "laplace"))
425  Params->NoiseType = NOISE_LAPLACE;
426  else if(!strcmp(NoiseName, "Poisson") || !strcmp(NoiseName, "poisson"))
427  Params->NoiseType = NOISE_POISSON;
428  else
429  {
430  fprintf(stderr, "Unknown noise model, \"%s\".\n", NoiseName);
431  return 0;
432  }
433 
434  return 1;
435 }
436 
437 
439 int ParseParams(programparams *Params, int argc, const char *argv[])
440 {
441  static const char *DefaultOutputFile = (char *)"out.bmp";
442  const char *Param, *Value;
443  num NumValue;
444  char TokenBuf[256];
445  int k, kread, Skip;
446 
447 
448  /* Set parameter defaults */
449  Params->InputFile = NULL;
450  Params->OutputFile = DefaultOutputFile;
451  Params->JpegQuality = 85;
452 
453  Params->NoiseType = NOISE_GAUSSIAN;
454  Params->Sigma = 0;
455  Params->Kernel = NullImage;
456 
457  if(argc < 2)
458  {
460  return 0;
461  }
462 
463  k = 1;
464 
465  while(k < argc)
466  {
467  Skip = (argv[k][0] == '-') ? 1 : 0;
468  kread = CliParseArglist(&Param, &Value, TokenBuf, sizeof(TokenBuf),
469  k, &argv[k][Skip], argc, argv, ":");
470 
471  if(!Param)
472  {
473  if(!Params->InputFile)
474  Param = (char *)"f";
475  else
476  Param = (char *)"u";
477  }
478 
479  if(Param[0] == '-') /* Argument begins with two dashes "--" */
480  {
482  return 0;
483  }
484 
485  if(!strcmp(Param, "f") || !strcmp(Param, "input"))
486  {
487  if(!Value)
488  {
489  fprintf(stderr, "Expected a value for option %s.\n", Param);
490  return 0;
491  }
492  Params->InputFile = Value;
493  }
494  else if(!strcmp(Param, "u") || !strcmp(Param, "output"))
495  {
496  if(!Value)
497  {
498  fprintf(stderr, "Expected a value for option %s.\n", Param);
499  return 0;
500  }
501  Params->OutputFile = Value;
502  }
503  else if(!strcmp(Param, "K"))
504  {
505  if(!Value)
506  {
507  fprintf(stderr, "Expected a value for option %s.\n", Param);
508  return 0;
509  }
510  else if(!ReadKernel(&Params->Kernel, Value))
511  return 0;
512  }
513  else if(!strcmp(Param, "noise"))
514  {
515  if(!Value)
516  {
517  fprintf(stderr, "Expected a value for option %s.\n", Param);
518  return 0;
519  }
520  if(!ReadNoise(Params, Value))
521  return 0;
522  }
523  else if(!strcmp(Param, "jpegquality"))
524  {
525  if(!CliGetNum(&NumValue, Value, Param))
526  return 0;
527  else if(NumValue < 0 || 100 < NumValue)
528  {
529  fprintf(stderr, "JPEG quality must be between 0 and 100.\n");
530  return 0;
531  }
532  else
533  Params->JpegQuality = (int)NumValue;
534  }
535  else if(Skip)
536  {
537  fprintf(stderr, "Unknown option \"%s\".\n", Param);
538  return 0;
539  }
540  else
541  {
542  if(!Params->InputFile)
543  Params->InputFile = argv[k];
544  else
545  Params->OutputFile = argv[k];
546 
547  kread = k;
548  }
549 
550  k = kread + 1;
551  }
552 
553  if(!Params->Kernel.Data && !ReadKernel(&Params->Kernel, "disk:0"))
554  return 0;
555 
556  if(!Params->InputFile)
557  {
559  return 0;
560  }
561 
562  return 1;
563 }