1 /*
2 * Copyright (c) 2010 The WebM project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11
12 #include "vpx_scale/yv12config.h"
13 #include "math.h"
14
15 #define C1 (float)(64 * 64 * 0.01*255*0.01*255)
16 #define C2 (float)(64 * 64 * 0.03*255*0.03*255)
17
18 static int width_y;
19 static int height_y;
20 static int height_uv;
21 static int width_uv;
22 static int stride_uv;
23 static int stride;
24 static int lumimask;
25 static int luminance;
26 static double plane_summed_weights = 0;
27
28 static short img12_sum_block[8*4096*4096*2] ;
29
30 static short img1_sum[8*4096*2];
31 static short img2_sum[8*4096*2];
32 static int img1_sq_sum[8*4096*2];
33 static int img2_sq_sum[8*4096*2];
34 static int img12_mul_sum[8*4096*2];
35
36
vp8_similarity(int mu_x,int mu_y,int pre_mu_x2,int pre_mu_y2,int pre_mu_xy2)37 double vp8_similarity
38 (
39 int mu_x,
40 int mu_y,
41 int pre_mu_x2,
42 int pre_mu_y2,
43 int pre_mu_xy2
44 )
45 {
46 int mu_x2, mu_y2, mu_xy, theta_x2, theta_y2, theta_xy;
47
48 mu_x2 = mu_x * mu_x;
49 mu_y2 = mu_y * mu_y;
50 mu_xy = mu_x * mu_y;
51
52 theta_x2 = 64 * pre_mu_x2 - mu_x2;
53 theta_y2 = 64 * pre_mu_y2 - mu_y2;
54 theta_xy = 64 * pre_mu_xy2 - mu_xy;
55
56 return (2 * mu_xy + C1) * (2 * theta_xy + C2) / ((mu_x2 + mu_y2 + C1) * (theta_x2 + theta_y2 + C2));
57 }
58
vp8_ssim(const unsigned char * img1,const unsigned char * img2,int stride_img1,int stride_img2,int width,int height)59 double vp8_ssim
60 (
61 const unsigned char *img1,
62 const unsigned char *img2,
63 int stride_img1,
64 int stride_img2,
65 int width,
66 int height
67 )
68 {
69 int x, y, x2, y2, img1_block, img2_block, img1_sq_block, img2_sq_block, img12_mul_block, temp;
70
71 double plane_quality, weight, mean;
72
73 short *img1_sum_ptr1, *img1_sum_ptr2;
74 short *img2_sum_ptr1, *img2_sum_ptr2;
75 int *img1_sq_sum_ptr1, *img1_sq_sum_ptr2;
76 int *img2_sq_sum_ptr1, *img2_sq_sum_ptr2;
77 int *img12_mul_sum_ptr1, *img12_mul_sum_ptr2;
78
79 plane_quality = 0;
80
81 if (lumimask)
82 plane_summed_weights = 0.0f;
83 else
84 plane_summed_weights = (height - 7) * (width - 7);
85
86 //some prologue for the main loop
87 temp = 8 * width;
88
89 img1_sum_ptr1 = img1_sum + temp;
90 img2_sum_ptr1 = img2_sum + temp;
91 img1_sq_sum_ptr1 = img1_sq_sum + temp;
92 img2_sq_sum_ptr1 = img2_sq_sum + temp;
93 img12_mul_sum_ptr1 = img12_mul_sum + temp;
94
95 for (x = 0; x < width; x++)
96 {
97 img1_sum[x] = img1[x];
98 img2_sum[x] = img2[x];
99 img1_sq_sum[x] = img1[x] * img1[x];
100 img2_sq_sum[x] = img2[x] * img2[x];
101 img12_mul_sum[x] = img1[x] * img2[x];
102
103 img1_sum_ptr1[x] = 0;
104 img2_sum_ptr1[x] = 0;
105 img1_sq_sum_ptr1[x] = 0;
106 img2_sq_sum_ptr1[x] = 0;
107 img12_mul_sum_ptr1[x] = 0;
108 }
109
110 //the main loop
111 for (y = 1; y < height; y++)
112 {
113 img1 += stride_img1;
114 img2 += stride_img2;
115
116 temp = (y - 1) % 9 * width;
117
118 img1_sum_ptr1 = img1_sum + temp;
119 img2_sum_ptr1 = img2_sum + temp;
120 img1_sq_sum_ptr1 = img1_sq_sum + temp;
121 img2_sq_sum_ptr1 = img2_sq_sum + temp;
122 img12_mul_sum_ptr1 = img12_mul_sum + temp;
123
124 temp = y % 9 * width;
125
126 img1_sum_ptr2 = img1_sum + temp;
127 img2_sum_ptr2 = img2_sum + temp;
128 img1_sq_sum_ptr2 = img1_sq_sum + temp;
129 img2_sq_sum_ptr2 = img2_sq_sum + temp;
130 img12_mul_sum_ptr2 = img12_mul_sum + temp;
131
132 for (x = 0; x < width; x++)
133 {
134 img1_sum_ptr2[x] = img1_sum_ptr1[x] + img1[x];
135 img2_sum_ptr2[x] = img2_sum_ptr1[x] + img2[x];
136 img1_sq_sum_ptr2[x] = img1_sq_sum_ptr1[x] + img1[x] * img1[x];
137 img2_sq_sum_ptr2[x] = img2_sq_sum_ptr1[x] + img2[x] * img2[x];
138 img12_mul_sum_ptr2[x] = img12_mul_sum_ptr1[x] + img1[x] * img2[x];
139 }
140
141 if (y > 6)
142 {
143 //calculate the sum of the last 8 lines by subtracting the total sum of 8 lines back from the present sum
144 temp = (y + 1) % 9 * width;
145
146 img1_sum_ptr1 = img1_sum + temp;
147 img2_sum_ptr1 = img2_sum + temp;
148 img1_sq_sum_ptr1 = img1_sq_sum + temp;
149 img2_sq_sum_ptr1 = img2_sq_sum + temp;
150 img12_mul_sum_ptr1 = img12_mul_sum + temp;
151
152 for (x = 0; x < width; x++)
153 {
154 img1_sum_ptr1[x] = img1_sum_ptr2[x] - img1_sum_ptr1[x];
155 img2_sum_ptr1[x] = img2_sum_ptr2[x] - img2_sum_ptr1[x];
156 img1_sq_sum_ptr1[x] = img1_sq_sum_ptr2[x] - img1_sq_sum_ptr1[x];
157 img2_sq_sum_ptr1[x] = img2_sq_sum_ptr2[x] - img2_sq_sum_ptr1[x];
158 img12_mul_sum_ptr1[x] = img12_mul_sum_ptr2[x] - img12_mul_sum_ptr1[x];
159 }
160
161 //here we calculate the sum over the 8x8 block of pixels
162 //this is done by sliding a window across the column sums for the last 8 lines
163 //each time adding the new column sum, and subtracting the one which fell out of the window
164 img1_block = 0;
165 img2_block = 0;
166 img1_sq_block = 0;
167 img2_sq_block = 0;
168 img12_mul_block = 0;
169
170 //prologue, and calculation of simularity measure from the first 8 column sums
171 for (x = 0; x < 8; x++)
172 {
173 img1_block += img1_sum_ptr1[x];
174 img2_block += img2_sum_ptr1[x];
175 img1_sq_block += img1_sq_sum_ptr1[x];
176 img2_sq_block += img2_sq_sum_ptr1[x];
177 img12_mul_block += img12_mul_sum_ptr1[x];
178 }
179
180 if (lumimask)
181 {
182 y2 = y - 7;
183 x2 = 0;
184
185 if (luminance)
186 {
187 mean = (img2_block + img1_block) / 128.0f;
188
189 if (!(y2 % 2 || x2 % 2))
190 *(img12_sum_block + y2 / 2 * width_uv + x2 / 2) = img2_block + img1_block;
191 }
192 else
193 {
194 mean = *(img12_sum_block + y2 * width_uv + x2);
195 mean += *(img12_sum_block + y2 * width_uv + x2 + 4);
196 mean += *(img12_sum_block + (y2 + 4) * width_uv + x2);
197 mean += *(img12_sum_block + (y2 + 4) * width_uv + x2 + 4);
198
199 mean /= 512.0f;
200 }
201
202 weight = mean < 40 ? 0.0f :
203 (mean < 50 ? (mean - 40.0f) / 10.0f : 1.0f);
204 plane_summed_weights += weight;
205
206 plane_quality += weight * vp8_similarity(img1_block, img2_block, img1_sq_block, img2_sq_block, img12_mul_block);
207 }
208 else
209 plane_quality += vp8_similarity(img1_block, img2_block, img1_sq_block, img2_sq_block, img12_mul_block);
210
211 //and for the rest
212 for (x = 8; x < width; x++)
213 {
214 img1_block = img1_block + img1_sum_ptr1[x] - img1_sum_ptr1[x - 8];
215 img2_block = img2_block + img2_sum_ptr1[x] - img2_sum_ptr1[x - 8];
216 img1_sq_block = img1_sq_block + img1_sq_sum_ptr1[x] - img1_sq_sum_ptr1[x - 8];
217 img2_sq_block = img2_sq_block + img2_sq_sum_ptr1[x] - img2_sq_sum_ptr1[x - 8];
218 img12_mul_block = img12_mul_block + img12_mul_sum_ptr1[x] - img12_mul_sum_ptr1[x - 8];
219
220 if (lumimask)
221 {
222 y2 = y - 7;
223 x2 = x - 7;
224
225 if (luminance)
226 {
227 mean = (img2_block + img1_block) / 128.0f;
228
229 if (!(y2 % 2 || x2 % 2))
230 *(img12_sum_block + y2 / 2 * width_uv + x2 / 2) = img2_block + img1_block;
231 }
232 else
233 {
234 mean = *(img12_sum_block + y2 * width_uv + x2);
235 mean += *(img12_sum_block + y2 * width_uv + x2 + 4);
236 mean += *(img12_sum_block + (y2 + 4) * width_uv + x2);
237 mean += *(img12_sum_block + (y2 + 4) * width_uv + x2 + 4);
238
239 mean /= 512.0f;
240 }
241
242 weight = mean < 40 ? 0.0f :
243 (mean < 50 ? (mean - 40.0f) / 10.0f : 1.0f);
244 plane_summed_weights += weight;
245
246 plane_quality += weight * vp8_similarity(img1_block, img2_block, img1_sq_block, img2_sq_block, img12_mul_block);
247 }
248 else
249 plane_quality += vp8_similarity(img1_block, img2_block, img1_sq_block, img2_sq_block, img12_mul_block);
250 }
251 }
252 }
253
254 if (plane_summed_weights == 0)
255 return 1.0f;
256 else
257 return plane_quality / plane_summed_weights;
258 }
259
vp8_calc_ssim(YV12_BUFFER_CONFIG * source,YV12_BUFFER_CONFIG * dest,int lumamask,double * weight)260 double vp8_calc_ssim
261 (
262 YV12_BUFFER_CONFIG *source,
263 YV12_BUFFER_CONFIG *dest,
264 int lumamask,
265 double *weight
266 )
267 {
268 double a, b, c;
269 double frame_weight;
270 double ssimv;
271
272 width_y = source->y_width;
273 height_y = source->y_height;
274 height_uv = source->uv_height;
275 width_uv = source->uv_width;
276 stride_uv = dest->uv_stride;
277 stride = dest->y_stride;
278
279 lumimask = lumamask;
280
281 luminance = 1;
282 a = vp8_ssim(source->y_buffer, dest->y_buffer,
283 source->y_stride, dest->y_stride, source->y_width, source->y_height);
284 luminance = 0;
285
286 frame_weight = plane_summed_weights / ((width_y - 7) * (height_y - 7));
287
288 if (frame_weight == 0)
289 a = b = c = 1.0f;
290 else
291 {
292 b = vp8_ssim(source->u_buffer, dest->u_buffer,
293 source->uv_stride, dest->uv_stride, source->uv_width, source->uv_height);
294
295 c = vp8_ssim(source->v_buffer, dest->v_buffer,
296 source->uv_stride, dest->uv_stride, source->uv_width, source->uv_height);
297 }
298
299 ssimv = a * .8 + .1 * (b + c);
300
301 *weight = frame_weight;
302
303 return ssimv;
304 }
305
306 // Google version of SSIM
307 // SSIM
308 #define KERNEL 3
309 #define KERNEL_SIZE (2 * KERNEL + 1)
310
311 typedef unsigned char uint8;
312 typedef unsigned int uint32;
313
314 static const int K[KERNEL_SIZE] =
315 {
316 1, 4, 11, 16, 11, 4, 1 // 16 * exp(-0.3 * i * i)
317 };
318 static const double ki_w = 1. / 2304.; // 1 / sum(i:0..6, j..6) K[i]*K[j]
get_ssimg(const uint8 * org,const uint8 * rec,int xo,int yo,int W,int H,const int stride1,const int stride2)319 double get_ssimg(const uint8 *org, const uint8 *rec,
320 int xo, int yo, int W, int H,
321 const int stride1, const int stride2
322 )
323 {
324 // TODO(skal): use summed tables
325 int y, x;
326
327 const int ymin = (yo - KERNEL < 0) ? 0 : yo - KERNEL;
328 const int ymax = (yo + KERNEL > H - 1) ? H - 1 : yo + KERNEL;
329 const int xmin = (xo - KERNEL < 0) ? 0 : xo - KERNEL;
330 const int xmax = (xo + KERNEL > W - 1) ? W - 1 : xo + KERNEL;
331 // worst case of accumulation is a weight of 48 = 16 + 2 * (11 + 4 + 1)
332 // with a diff of 255, squares. That would a max error of 0x8ee0900,
333 // which fits into 32 bits integers.
334 uint32 w = 0, xm = 0, ym = 0, xxm = 0, xym = 0, yym = 0;
335 org += ymin * stride1;
336 rec += ymin * stride2;
337
338 for (y = ymin; y <= ymax; ++y, org += stride1, rec += stride2)
339 {
340 const int Wy = K[KERNEL + y - yo];
341
342 for (x = xmin; x <= xmax; ++x)
343 {
344 const int Wxy = Wy * K[KERNEL + x - xo];
345 // TODO(skal): inlined assembly
346 w += Wxy;
347 xm += Wxy * org[x];
348 ym += Wxy * rec[x];
349 xxm += Wxy * org[x] * org[x];
350 xym += Wxy * org[x] * rec[x];
351 yym += Wxy * rec[x] * rec[x];
352 }
353 }
354
355 {
356 const double iw = 1. / w;
357 const double iwx = xm * iw;
358 const double iwy = ym * iw;
359 double sxx = xxm * iw - iwx * iwx;
360 double syy = yym * iw - iwy * iwy;
361
362 // small errors are possible, due to rounding. Clamp to zero.
363 if (sxx < 0.) sxx = 0.;
364
365 if (syy < 0.) syy = 0.;
366
367 {
368 const double sxsy = sqrt(sxx * syy);
369 const double sxy = xym * iw - iwx * iwy;
370 static const double C11 = (0.01 * 0.01) * (255 * 255);
371 static const double C22 = (0.03 * 0.03) * (255 * 255);
372 static const double C33 = (0.015 * 0.015) * (255 * 255);
373 const double l = (2. * iwx * iwy + C11) / (iwx * iwx + iwy * iwy + C11);
374 const double c = (2. * sxsy + C22) / (sxx + syy + C22);
375
376 const double s = (sxy + C33) / (sxsy + C33);
377 return l * c * s;
378
379 }
380 }
381
382 }
383
get_ssimfull_kernelg(const uint8 * org,const uint8 * rec,int xo,int yo,int W,int H,const int stride1,const int stride2)384 double get_ssimfull_kernelg(const uint8 *org, const uint8 *rec,
385 int xo, int yo, int W, int H,
386 const int stride1, const int stride2)
387 {
388 // TODO(skal): use summed tables
389 // worst case of accumulation is a weight of 48 = 16 + 2 * (11 + 4 + 1)
390 // with a diff of 255, squares. That would a max error of 0x8ee0900,
391 // which fits into 32 bits integers.
392 int y_, x_;
393 uint32 xm = 0, ym = 0, xxm = 0, xym = 0, yym = 0;
394 org += (yo - KERNEL) * stride1;
395 org += (xo - KERNEL);
396 rec += (yo - KERNEL) * stride2;
397 rec += (xo - KERNEL);
398
399 for (y_ = 0; y_ < KERNEL_SIZE; ++y_, org += stride1, rec += stride2)
400 {
401 const int Wy = K[y_];
402
403 for (x_ = 0; x_ < KERNEL_SIZE; ++x_)
404 {
405 const int Wxy = Wy * K[x_];
406 // TODO(skal): inlined assembly
407 const int org_x = org[x_];
408 const int rec_x = rec[x_];
409 xm += Wxy * org_x;
410 ym += Wxy * rec_x;
411 xxm += Wxy * org_x * org_x;
412 xym += Wxy * org_x * rec_x;
413 yym += Wxy * rec_x * rec_x;
414 }
415 }
416
417 {
418 const double iw = ki_w;
419 const double iwx = xm * iw;
420 const double iwy = ym * iw;
421 double sxx = xxm * iw - iwx * iwx;
422 double syy = yym * iw - iwy * iwy;
423
424 // small errors are possible, due to rounding. Clamp to zero.
425 if (sxx < 0.) sxx = 0.;
426
427 if (syy < 0.) syy = 0.;
428
429 {
430 const double sxsy = sqrt(sxx * syy);
431 const double sxy = xym * iw - iwx * iwy;
432 static const double C11 = (0.01 * 0.01) * (255 * 255);
433 static const double C22 = (0.03 * 0.03) * (255 * 255);
434 static const double C33 = (0.015 * 0.015) * (255 * 255);
435 const double l = (2. * iwx * iwy + C11) / (iwx * iwx + iwy * iwy + C11);
436 const double c = (2. * sxsy + C22) / (sxx + syy + C22);
437 const double s = (sxy + C33) / (sxsy + C33);
438 return l * c * s;
439 }
440 }
441 }
442
calc_ssimg(const uint8 * org,const uint8 * rec,const int image_width,const int image_height,const int stride1,const int stride2)443 double calc_ssimg(const uint8 *org, const uint8 *rec,
444 const int image_width, const int image_height,
445 const int stride1, const int stride2
446 )
447 {
448 int j, i;
449 double SSIM = 0.;
450
451 for (j = 0; j < KERNEL; ++j)
452 {
453 for (i = 0; i < image_width; ++i)
454 {
455 SSIM += get_ssimg(org, rec, i, j, image_width, image_height, stride1, stride2);
456 }
457 }
458
459 for (j = KERNEL; j < image_height - KERNEL; ++j)
460 {
461 for (i = 0; i < KERNEL; ++i)
462 {
463 SSIM += get_ssimg(org, rec, i, j, image_width, image_height, stride1, stride2);
464 }
465
466 for (i = KERNEL; i < image_width - KERNEL; ++i)
467 {
468 SSIM += get_ssimfull_kernelg(org, rec, i, j,
469 image_width, image_height, stride1, stride2);
470 }
471
472 for (i = image_width - KERNEL; i < image_width; ++i)
473 {
474 SSIM += get_ssimg(org, rec, i, j, image_width, image_height, stride1, stride2);
475 }
476 }
477
478 for (j = image_height - KERNEL; j < image_height; ++j)
479 {
480 for (i = 0; i < image_width; ++i)
481 {
482 SSIM += get_ssimg(org, rec, i, j, image_width, image_height, stride1, stride2);
483 }
484 }
485
486 return SSIM;
487 }
488
489
vp8_calc_ssimg(YV12_BUFFER_CONFIG * source,YV12_BUFFER_CONFIG * dest,double * ssim_y,double * ssim_u,double * ssim_v)490 double vp8_calc_ssimg
491 (
492 YV12_BUFFER_CONFIG *source,
493 YV12_BUFFER_CONFIG *dest,
494 double *ssim_y,
495 double *ssim_u,
496 double *ssim_v
497 )
498 {
499 double ssim_all = 0;
500 int ysize = source->y_width * source->y_height;
501 int uvsize = ysize / 4;
502
503 *ssim_y = calc_ssimg(source->y_buffer, dest->y_buffer,
504 source->y_width, source->y_height,
505 source->y_stride, dest->y_stride);
506
507
508 *ssim_u = calc_ssimg(source->u_buffer, dest->u_buffer,
509 source->uv_width, source->uv_height,
510 source->uv_stride, dest->uv_stride);
511
512
513 *ssim_v = calc_ssimg(source->v_buffer, dest->v_buffer,
514 source->uv_width, source->uv_height,
515 source->uv_stride, dest->uv_stride);
516
517 ssim_all = (*ssim_y + *ssim_u + *ssim_v) / (ysize + uvsize + uvsize);
518 *ssim_y /= ysize;
519 *ssim_u /= uvsize;
520 *ssim_v /= uvsize;
521 return ssim_all;
522 }
523