Linear Methods for Image Interpolation
imdiff.c
Go to the documentation of this file.
00001 
00033 #include <math.h>
00034 #include <string.h>
00035 #include <ctype.h>
00036 
00037 #include "imageio.h"
00038 #include "conv.h"
00039 
00040 
00042 #define DISPLAY_SCALING     255
00043 
00044 #define MSSIM_K1            0.01
00045 #define MSSIM_K2            0.03
00046 
00047 #define MSSIM_C1            (MSSIM_K1*MSSIM_K1)
00048 #define MSSIM_C2            (MSSIM_K2*MSSIM_K2)
00049 
00050 
00052 typedef enum {DEFAULT_METRICS, MAX_METRIC, MSE_METRIC, RMSE_METRIC,
00053     PSNR_METRIC, MSSIM_METRIC} metric;
00054 
00056 typedef struct
00057 {
00059     char *FileA;
00061     char *FileB;
00063     int JpegQuality;
00065     metric Metric;
00067     int SeparateChannels;
00069     int Pad;
00070 
00072     char *DifferenceFile;
00074     float D;
00075 } programparams;
00076 
00077 
00078 static int ParseParams(programparams *Param, int argc, char *argv[]);
00079 void MakeDifferenceImage(float *A, const float *B,
00080     int Width, int Height, int NumChannels, float D);
00081 void BasicMetrics(float *Max, float *Mse, const float *A, const float *B,
00082     int Width, int Height, int NumChannels, int Pad);
00083 float ComputeMssim(const float *A, const float *B,
00084     int Width, int Height, int NumChannels, int Pad);
00085 
00086 
00088 void PrintHelpMessage()
00089 {
00090     printf("Image difference calculator, P. Getreuer 2010-2011\n\n");
00091     printf("Usage: imdiff [options] <exact file> <distorted file>\n\n"
00092         "Only " READIMAGE_FORMATS_SUPPORTED " images are supported.\n\n");
00093     printf("Options:\n");
00094     printf("   -m <metric>  Metric to use for comparison, choices are\n");
00095     printf("        max     Maximum absolute difference, max_n |A_n - B_n|\n");
00096     printf("        mse     Mean squared error, 1/N sum |A_n - B_n|^2\n");
00097     printf("        rmse    Root mean squared error, (MSE)^1/2\n");
00098     printf("        psnr    Peak signal-to-noise ratio, -10 log10(MSE/255^2)\n");
00099     printf("        mssim   Mean structural similarity index\n\n");
00100     printf("   -s           Compute metric separately for each channel\n");
00101     printf("   -p <pad>     Remove a margin of <pad> pixels before comparison\n");
00102     printf("   -D <number>  D parameter for difference image\n\n");
00103 #ifdef LIBJPEG_SUPPORT
00104     printf("   -q <number>   Quality for saving JPEG images (0 to 100)\n\n");
00105 #endif
00106     printf("Alternatively, a difference image is generated by the syntax\n"
00107            "   imdiff [-D <number>] <exact file> <distorted file> <output file>\n\n");
00108     printf("The difference image is computed as\n"
00109            "   D_n = 255/D (A_n - B_n) + 255/2.\n"
00110            "Values outside of the range [0,255] are saturated.\n\n");
00111     printf("Example:\n"
00112 #ifdef LIBPNG_SUPPORT
00113         "   imdiff -mpsnr frog-exact.png frog-4x.bmp\n");
00114 #else
00115         "   imdiff -mpsnr frog-exact.bmp frog-4x.bmp\n");
00116 #endif
00117 }
00118 
00119 
00120 int main(int argc, char *argv[])
00121 {
00122     struct
00123     {
00124         float *Data;
00125         int Width;
00126         int Height;
00127     } A = {NULL, 0, 0}, B = {NULL, 0, 0};
00128 
00129     programparams Param;
00130     float Max, MaxC[3], Mse, MseC[3], Mssim;
00131     int Channel, Status = 1;
00132 
00133 
00134     if(!ParseParams(&Param, argc, argv))
00135         return 0;
00136 
00137     /* Read the exact image */
00138     if(!(A.Data = (float *)ReadImage(&A.Width, &A.Height, Param.FileA,
00139         IMAGEIO_FLOAT | IMAGEIO_RGB | IMAGEIO_PLANAR)))
00140         goto Catch;
00141 
00142     /* Read the distorted image */
00143     if(!(B.Data = (float *)ReadImage(&B.Width, &B.Height, Param.FileB,
00144         IMAGEIO_FLOAT | IMAGEIO_RGB | IMAGEIO_PLANAR)))
00145         goto Catch;
00146 
00147     if(A.Width != B.Width || A.Height != B.Height)
00148     {
00149         ErrorMessage("Image sizes don't match, %dx%d vs. %dx%d.\n",
00150             A.Width, A.Height, B.Width, B.Height);
00151         goto Catch;
00152     }
00153     else if(A.Width <= 2*Param.Pad || A.Height <= 2*Param.Pad)
00154     {
00155         ErrorMessage(
00156             "Removal of %d-pixel padding removes entire %dx%d image.\n",
00157             Param.Pad, A.Width, A.Height);
00158         goto Catch;
00159     }
00160 
00161     if(Param.DifferenceFile)
00162     {
00163         MakeDifferenceImage(A.Data, B.Data, A.Width, A.Height, 3, Param.D);
00164 
00165         if(!(WriteImage(A.Data, A.Width, A.Height, Param.DifferenceFile,
00166             IMAGEIO_FLOAT | IMAGEIO_RGB | IMAGEIO_PLANAR, Param.JpegQuality)))
00167             goto Catch;
00168     }
00169     else
00170     {
00171         Max = 0;
00172         Mse = 0;
00173 
00174         for(Channel = 0; Channel < 3; Channel++)
00175         {
00176             BasicMetrics(&MaxC[Channel], &MseC[Channel],
00177                     A.Data + Channel*A.Width*A.Height,
00178                     B.Data + Channel*B.Width*B.Height,
00179                     A.Width, A.Height, 1, Param.Pad);
00180 
00181             if(MaxC[Channel] > Max)
00182                 Max = MaxC[Channel];
00183 
00184             Mse += MseC[Channel];
00185         }
00186 
00187         Mse /= 3;
00188 
00189         switch(Param.Metric)
00190         {
00191             case DEFAULT_METRICS:
00192                 if(!Param.SeparateChannels)
00193                 {
00194                     printf("Maximum absolute difference:  %g\n", DISPLAY_SCALING*Max);
00195                     printf("Peak signal-to-noise ratio:   %.4f\n", -10*log10(Mse));
00196                 }
00197                 else
00198                 {
00199                     printf("Maximum absolute difference:  %g %g %g\n",
00200                         DISPLAY_SCALING*MaxC[0], DISPLAY_SCALING*MaxC[1],
00201                         DISPLAY_SCALING*MaxC[2]);
00202                     printf("Peak signal-to-noise ratio:   %.4f %.4f %.4f\n",
00203                         -10*log10(MseC[0]), -10*log10(MseC[1]), -10*log10(MseC[2]));
00204                 }
00205 
00206                 if(A.Width <= 2*(5 + Param.Pad)
00207                     || A.Height <= 2*(5 + Param.Pad))
00208                     printf("Image size is too small to compute MSSIM.\n");
00209                 else
00210                 {
00211 
00212                     Mssim = (Max == 0) ? 1 : ComputeMssim(A.Data, B.Data,
00213                             A.Width, A.Height, 3, Param.Pad);
00214 
00215                     if(Mssim != -1)
00216                         printf("Mean structural similarity:   %.4f\n", Mssim);
00217                 }
00218                 break;
00219             case MAX_METRIC:
00220                 if(!Param.SeparateChannels)
00221                     printf("%g\n", DISPLAY_SCALING*Max);
00222                 else
00223                     printf("%g %g %g\n", DISPLAY_SCALING*MaxC[0],
00224                         DISPLAY_SCALING*MaxC[1], DISPLAY_SCALING*MaxC[2]);
00225                 break;
00226             case MSE_METRIC:
00227                 if(!Param.SeparateChannels)
00228                     printf("%.4f\n", DISPLAY_SCALING*DISPLAY_SCALING*Mse);
00229                 else
00230                     printf("%.4f %.4f %.4f\n",
00231                         DISPLAY_SCALING*DISPLAY_SCALING*MseC[0],
00232                         DISPLAY_SCALING*DISPLAY_SCALING*MseC[1],
00233                         DISPLAY_SCALING*DISPLAY_SCALING*MseC[2]);
00234                 break;
00235             case RMSE_METRIC:
00236                 if(!Param.SeparateChannels)
00237                     printf("%.4f\n", DISPLAY_SCALING*sqrt(Mse));
00238                 else
00239                     printf("%.4f %.4f %.4f\n",
00240                         DISPLAY_SCALING*sqrt(MseC[0]),
00241                         DISPLAY_SCALING*sqrt(MseC[1]),
00242                         DISPLAY_SCALING*sqrt(MseC[2]));
00243                 break;
00244             case PSNR_METRIC:
00245                 if(!Param.SeparateChannels)
00246                     printf("%.4f\n", -10*log10(Mse));
00247                 else
00248                 printf("%.4f %.4f %.4f\n",
00249                     -10*log10(MseC[0]), -10*log10(MseC[1]), -10*log10(MseC[2]));
00250                 break;
00251             case MSSIM_METRIC:
00252                 if(A.Width <= 2*(5 + Param.Pad)
00253                     || A.Height <= 2*(5 + Param.Pad))
00254                     printf("Image size is too small to compute MSSIM.\n");
00255                 else
00256                 {
00257                     Mssim = (Max == 0) ? 1 : ComputeMssim(A.Data, B.Data,
00258                             A.Width, A.Height, 3, Param.Pad);
00259 
00260                     if(Mssim == -1)
00261                         ErrorMessage("Memory allocation failed.\n");
00262                     else
00263                         printf("%.4f\n", Mssim);
00264                 }
00265                 break;
00266         }
00267     }
00268 
00269     Status = 0; /* Finished successfully */
00270 Catch:
00271     Free(B.Data);
00272     Free(A.Data);
00273     return Status;
00274 }
00275 
00276 
00278 void MakeDifferenceImage(float *A, const float *B,
00279     int Width, int Height, int NumChannels, float D)
00280 {
00281     const int NumEl = NumChannels*Width*Height;
00282     int n;
00283 
00284     D /= 255;
00285 
00286     for(n = 0; n < NumEl; n++)
00287         A[n] = (A[n] - B[n])/D + (float)0.5;
00288 }
00289 
00290 
00292 void BasicMetrics(float *Max, float *Mse, const float *A, const float *B,
00293     int Width, int Height, int NumChannels, int Pad)
00294 {
00295     float Diff, CurMax = 0;
00296     double AccumMse = 0;
00297     int x, y, Channel, n;
00298 
00299 
00300     for(Channel = 0; Channel < NumChannels; Channel++)
00301         for(y = Pad; y < Height - Pad; y++)
00302             for(x = Pad; x < Width - Pad; x++)
00303             {
00304                 n = x + Width*(y + Height*Channel);
00305                 Diff = (float)fabs(A[n] - B[n]);
00306 
00307                 if(CurMax < Diff)
00308                     CurMax = Diff;
00309 
00310                 AccumMse += Diff*Diff;
00311             }
00312 
00313     *Max = CurMax;
00314     *Mse = (float)(AccumMse / (NumChannels*(Width - 2*Pad)*(Height - 2*Pad)));
00315 }
00316 
00317 
00319 float ComputeMssim(const float *A, const float *B,
00320     int Width, int Height, int NumChannels, int Pad)
00321 {
00322     /* 11-tap Gaussian filter with standard deviation 1.5 */
00323     const int R = 5;
00324     filter Window = GaussianFilter(1.5, R);
00325     /* Boundary does not matter, convolution is used only in the interior */
00326     boundaryext Boundary = GetBoundaryExt("zpd");
00327 
00328     const int NumPixels = Width*Height;
00329     const int NumEl = NumChannels*NumPixels;
00330     float *Buffer = NULL, *MuA = NULL, *MuB = NULL,
00331         *MuAA = NULL, *MuBB = NULL, *MuAB = NULL;
00332     double MuASqr, MuBSqr, MuAMuB,
00333         SigmaASqr, SigmaBSqr, SigmaAB, Mssim = -1;
00334     int n, x, y, c;
00335 
00336 
00337     if(IsNullFilter(Window)
00338         || !(Buffer = (float *)Malloc(sizeof(float)*NumPixels))
00339         || !(MuA = (float *)Malloc(sizeof(float)*NumEl))
00340         || !(MuB = (float *)Malloc(sizeof(float)*NumEl))
00341         || !(MuAA = (float *)Malloc(sizeof(float)*NumEl))
00342         || !(MuBB = (float *)Malloc(sizeof(float)*NumEl))
00343         || !(MuAB = (float *)Malloc(sizeof(float)*NumEl)))
00344         goto Catch;
00345 
00346     SeparableConv2D(MuA, Buffer, A, Window, Window,
00347         Boundary, Width, Height, NumChannels);
00348     SeparableConv2D(MuB, Buffer, B, Window, Window,
00349         Boundary, Width, Height, NumChannels);
00350 
00351     for(n = 0; n < NumEl; n++)
00352     {
00353         MuAA[n] = A[n]*A[n];
00354         MuBB[n] = B[n]*B[n];
00355         MuAB[n] = A[n]*B[n];
00356     }
00357 
00358     SeparableConv2D(MuAA, Buffer, MuAA, Window, Window,
00359         Boundary, Width, Height, NumChannels);
00360     SeparableConv2D(MuBB, Buffer, MuBB, Window, Window,
00361         Boundary, Width, Height, NumChannels);
00362     SeparableConv2D(MuAB, Buffer, MuAB, Window, Window,
00363         Boundary, Width, Height, NumChannels);
00364     Mssim = 0;
00365 
00366     Pad += R;
00367 
00368     for(c = 0; c < NumChannels; c++)
00369         for(y = Pad; y < Height - Pad; y++)
00370             for(x = Pad; x < Width - Pad; x++)
00371             {
00372                 n = x + Width*(y + Height*c);
00373                 MuASqr = MuA[n]*MuA[n];
00374                 MuBSqr = MuB[n]*MuB[n];
00375                 MuAMuB = MuA[n]*MuB[n];
00376                 SigmaASqr = MuAA[n] - MuASqr;
00377                 SigmaBSqr = MuBB[n] - MuBSqr;
00378                 SigmaAB = MuAB[n] - MuAMuB;
00379 
00380                 Mssim += ((2*MuAMuB + MSSIM_C1)*(2*SigmaAB + MSSIM_C2))
00381                     / ((MuASqr + MuBSqr + MSSIM_C1)
00382                         *(SigmaASqr + SigmaBSqr + MSSIM_C2));
00383             }
00384 
00385     Mssim /= NumChannels*(Width - 2*Pad)*(Height - 2*Pad);
00386 
00387 Catch:
00388     FreeFilter(Window);
00389     Free(MuAB);
00390     Free(MuBB);
00391     Free(MuAA);
00392     Free(MuB);
00393     Free(MuA);
00394     Free(Buffer);
00395     return (float)Mssim;
00396 }
00397 
00398 
00399 
00400 static int ParseParams(programparams *Param, int argc, char *argv[])
00401 {
00402     char *OptionString;
00403     char OptionChar;
00404     int i;
00405 
00406 
00407     if(argc < 2)
00408     {
00409         PrintHelpMessage();
00410         return 0;
00411     }
00412 
00413     /* Set parameter defaults */
00414     Param->FileA = NULL;
00415     Param->FileB = NULL;
00416     Param->Metric = DEFAULT_METRICS;
00417     Param->SeparateChannels = 0;
00418 
00419     Param->Pad = 0;
00420     Param->DifferenceFile = NULL;
00421     Param->JpegQuality = 95;
00422     Param->D = 20;
00423 
00424     for(i = 1; i < argc;)
00425     {
00426         if(argv[i] && argv[i][0] == '-')
00427         {
00428             if((OptionChar = argv[i][1]) == 0)
00429             {
00430                 ErrorMessage("Invalid parameter format.\n");
00431                 return 0;
00432             }
00433 
00434             if(argv[i][2])
00435                 OptionString = &argv[i][2];
00436             else if(++i < argc)
00437                 OptionString = argv[i];
00438             else
00439             {
00440                 ErrorMessage("Invalid parameter format.\n");
00441                 return 0;
00442             }
00443 
00444             switch(OptionChar)
00445             {
00446             case 'p':
00447                 Param->Pad = atoi(OptionString);
00448 
00449                 if(Param->Pad < 0)
00450                 {
00451                     ErrorMessage("Pad must be nonnegative.\n");
00452                     return 0;
00453                 }
00454                 break;
00455             case 's':
00456                 Param->SeparateChannels = 1;
00457                 i--;
00458                 break;
00459             case 'D':
00460                 Param->D = (float)atof(OptionString);
00461 
00462                 if(Param->D <= 0)
00463                 {
00464                     ErrorMessage("D must be positive.\n");
00465                     return 0;
00466                 }
00467                 break;
00468             case 'm':
00469                 if(!strcmp(OptionString, "max"))
00470                     Param->Metric = MAX_METRIC;
00471                 else if(!strcmp(OptionString, "mse"))
00472                     Param->Metric = MSE_METRIC;
00473                 else if(!strcmp(OptionString, "rmse"))
00474                     Param->Metric = RMSE_METRIC;
00475                 else if(!strcmp(OptionString, "psnr"))
00476                     Param->Metric = PSNR_METRIC;
00477                 else if(!strcmp(OptionString, "mssim"))
00478                     Param->Metric = MSSIM_METRIC;
00479                 else
00480                     ErrorMessage("Unknown metric.\n");
00481                 break;
00482 
00483 #ifdef LIBJPEG_SUPPORT
00484             case 'q':
00485                 Param->JpegQuality = atoi(OptionString);
00486 
00487                 if(Param->JpegQuality <= 0 || Param->JpegQuality > 100)
00488                 {
00489                     ErrorMessage("JPEG quality must be between 0 and 100.\n");
00490                     return 0;
00491                 }
00492                 break;
00493 #endif
00494             case '-':
00495                 PrintHelpMessage();
00496                 return 0;
00497             default:
00498                 if(isprint(OptionChar))
00499                     ErrorMessage("Unknown option \"-%c\".\n", OptionChar);
00500                 else
00501                     ErrorMessage("Unknown option.\n");
00502 
00503                 return 0;
00504             }
00505 
00506             i++;
00507         }
00508         else
00509         {
00510             if(!Param->FileA)
00511                 Param->FileA = argv[i];
00512             else if(!Param->FileB)
00513                 Param->FileB = argv[i];
00514             else
00515                 Param->DifferenceFile = argv[i];
00516 
00517             i++;
00518         }
00519     }
00520 
00521     if(!Param->FileA || !Param->FileB)
00522     {
00523         PrintHelpMessage();
00524         return 0;
00525     }
00526 
00527     return 1;
00528 }
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines