Linear Methods for Image Interpolation
|
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 }