Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(262)

Side by Side Diff: experimental/skpdiff/SkPMetric.cpp

Issue 18066004: add yee's perceptual metric (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: document maths Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « experimental/skpdiff/SkPMetric.h ('k') | experimental/skpdiff/main.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #include <cmath>
2
3 #include "SkBitmap.h"
4 #include "skpdiff_util.h"
5 #include "SkPMetric.h"
6
7 struct RGB {
8 float r, g, b;
9 };
10
11 struct LAB {
12 float l, a, b;
13 };
14
15 template<class T>
16 struct Image2D {
17 int width;
18 int height;
19 T* image;
20
21 Image2D(int w, int h)
22 : width(w),
23 height(h) {
24 SkASSERT(w > 0);
25 SkASSERT(h > 0);
26 image = SkNEW_ARRAY(T, w * h);
27 }
28
29 ~Image2D() {
30 SkDELETE_ARRAY(image);
31 }
32
33 void readPixel(int x, int y, T* pixel) const {
34 SkASSERT(x >= 0);
35 SkASSERT(y >= 0);
36 SkASSERT(x < width);
37 SkASSERT(y < height);
38 *pixel = image[y * width + x];
39 }
40
41 void writePixel(int x, int y, const T& pixel) {
42 SkASSERT(x >= 0);
43 SkASSERT(y >= 0);
44 SkASSERT(x < width);
45 SkASSERT(y < height);
46 image[y * width + x] = pixel;
47 }
48 };
49
50 typedef Image2D<float> ImageL;
51 typedef Image2D<RGB> ImageRGB;
52 typedef Image2D<LAB> ImageLAB;
53
54 template<class T>
bsalomon 2013/06/27 18:31:15 It seems like the template param should be restric
Zach Reizner 2013/06/27 18:48:49 I like the idea of moving the template params. Th
55 struct Image3D
56 {
57 int slices;
58 T** image;
59
60 Image3D(int w, int h, int s)
61 : slices(s) {
62 SkASSERT(s > 0);
63 image = SkNEW_ARRAY(T*, s);
64 for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
65 image[sliceIndex] = SkNEW_ARGS(T, (w, h));
66 }
67 }
68
69 ~Image3D() {
70 for (int sliceIndex = 0; sliceIndex < slices; sliceIndex++) {
71 SkDELETE(image[sliceIndex]);
72 }
73 SkDELETE_ARRAY(image);
74 }
75
76 T* getLayer(int z) const {
77 SkASSERT(z >= 0);
78 SkASSERT(z < slices);
79 return image[z];
80 }
81 };
82
83 typedef Image3D<ImageL> ImageL3D;
84
85
86 #define MAT_ROW_MULT(rc,gc,bc) r*rc + g*gc + b*bc
87
88
89 void adobergb_to_cielab(float r, float g, float b, LAB* lab) {
bsalomon 2013/06/27 18:31:15 Does this mean the input is in Adobe RGB? http://e
Zach Reizner 2013/06/27 18:48:49 The input is assumed to be Adobe RGB. Adobe RGB ha
90 // Conversion of Adobe RGB to XYZ taken from from "Adobe RGB (1998) ColorIma ge Encoding"
91 // URL:http://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf
92 // Section: 4.3.5.3
93 // See Also: http://en.wikipedia.org/wiki/Adobe_rgb
94 float x = MAT_ROW_MULT(0.57667f, 0.18556f, 0.18823f);
95 float y = MAT_ROW_MULT(0.29734f, 0.62736f, 0.07529f);
96 float z = MAT_ROW_MULT(0.02703f, 0.07069f, 0.99134f);
97
98 // The following is the white point in XYZ, so it's simply the row wise addi tion of the above
99 // matrix.
100 const float xw = 0.5767f + 0.185556f + 0.188212f;
101 const float yw = 0.297361f + 0.627355f + 0.0752847f;
102 const float zw = 0.0270328f + 0.0706879f + 0.991248f;
103
104 // This is the XYZ color point relative to the white point
105 float f[3] = { x / xw, y / yw, z / zw };
106
107 // Conversion from XYZ to LAB taken from
108 // http://en.wikipedia.org/wiki/CIELAB#Forward_transformation
109 for (int i = 0; i < 3; i++) {
110 if (f[i] >= 0.008856f) {
111 f[i] = powf(f[i], 1.0f / 3.0f);
112 } else {
113 f[i] = 7.787f * f[i] + 4.0f / 29.0f;
114 }
115 }
116 lab->l = 116.0f * f[1] - 16.0f;
117 lab->a = 500.0f * (f[0] - f[1]);
118 lab->b = 200.0f * (f[1] - f[2]);
119 }
120
121 /// Converts a 8888 bitmap to LAB color space and puts it into the output
122 static void bitmap_to_cielab(const SkBitmap* bitmap, ImageLAB* outImageLAB) {
123 SkASSERT(bitmap->config() == SkBitmap::kARGB_8888_Config);
124
125 int width = bitmap->width();
126 int height = bitmap->height();
127 SkASSERT(outImageLAB->width == width);
128 SkASSERT(outImageLAB->height == height);
129
130 bitmap->lockPixels();
131 RGB rgb;
132 LAB lab;
133 for (int y = 0; y < height; y++) {
134 unsigned char* row = (unsigned char*)bitmap->getAddr(0, y);
135 for (int x = 0; x < width; x++) {
136 // Perform gamma correction which is assumed to be 2.2
137 rgb.r = powf(row[x * 4 + 2] / 255.0f, 2.2f);
138 rgb.g = powf(row[x * 4 + 1] / 255.0f, 2.2f);
139 rgb.b = powf(row[x * 4 + 0] / 255.0f, 2.2f);
140 adobergb_to_cielab(rgb.r, rgb.g, rgb.b, &lab);
141 outImageLAB->writePixel(x, y, lab);
142 }
143 }
144 bitmap->unlockPixels();
145 }
146
147 // From Barten SPIE 1989
148 static float contrast_sensitivity(float cyclesPerDegree, float luminance) {
149 float a = 440.0f * powf(1.0f + 0.7f / luminance, -0.2f);
150 float b = 0.3f * powf(1 + 100.0 / luminance, 0.15f);
151 return a *
152 cyclesPerDegree *
153 expf(-b * cyclesPerDegree) *
154 sqrtf(1.0f + 0.06f * expf(b * cyclesPerDegree));
155 }
156
157 // From Daly 1993
158 static float visual_mask(float contrast) {
159 float x = powf(392.498f * contrast, 0.7f);
160 x = powf(0.0153f * x, 4.0f);
161 return powf(1.0f + x, 0.25f);
162 }
163
164 // From Ward Larson Siggraph 1997
165 static float threshold_vs_intensity(float adaptationLuminance) {
166 float logLum = log10f(adaptationLuminance);
167 float x;
168 if (logLum < -3.94f) {
169 x = -2.86f;
170 } else if (logLum < -1.44f) {
171 x = powf(0.405f * logLum + 1.6f, 2.18) - 2.86f;
172 } else if (logLum < -0.0184f) {
173 x = logLum - 0.395f;
174 } else if (logLum < 1.9f) {
175 x = powf(0.249f * logLum + 0.65f, 2.7f) - 0.72f;
176 } else {
177 x = logLum - 1.255f;
178 }
179 return powf(10.0f, x);
180 }
181
182 /// Simply takes the L channel from the input and puts it into the output
183 static void lab_to_l(const ImageLAB* imageLAB, ImageL* outImageL) {
184 for (int y = 0; y < imageLAB->height; y++) {
185 for (int x = 0; x < imageLAB->width; x++) {
186 LAB lab;
187 imageLAB->readPixel(x, y, &lab);
188 outImageL->writePixel(x, y, lab.l);
189 }
190 }
191 }
192
193 /// Convolves an image with the given filter in one direction and saves it to th e output image
194 static void convolve(const ImageL* imageL,
195 bool vertical, const float* matrix, int radius,
196 ImageL* outImageL) {
197 SkASSERT(imageL->width == outImageL->width);
198 SkASSERT(imageL->height == outImageL->height);
199 for (int y = 0; y < imageL->height; y++) {
200 for (int x = 0; x < imageL->width; x++) {
201 float lSum = 0.0f;
202 float l;
203 for (int xx = -radius; xx <= radius; xx++) {
204 int nx = x;
205 int ny = y;
206 if (vertical) {
207 ny += xx;
208 } else {
209 nx += xx;
210 }
211 if (nx < 0) {
bsalomon 2013/06/27 18:31:15 should these four ifs be done inside the vertical/
212 nx = -nx;
213 }
214 if (ny < 0) {
215 ny = -ny;
216 }
217 if (nx >= imageL->width) {
218 nx = imageL->width + (imageL->width - nx - 1);
219 }
220 if (ny >= imageL->height) {
221 ny = imageL->height + (imageL->height - ny - 1);
222 }
223 imageL->readPixel(nx, ny, &l);
224 float weight = matrix[xx + radius];
225 lSum += l * weight;
226 }
227 outImageL->writePixel(x, y, lSum);
228 }
229 }
230 }
231
232 float pmetric(const ImageLAB* baselineLAB, const ImageLAB* testLAB) {
233 int width = baselineLAB->width;
234 int height = baselineLAB->height;
235 int maxLevels = (int)log2(width < height ? width : height);
236
237 const float fov = M_PI / 180.0f * 45.0f;
238 float contrastSensitivityMax = contrast_sensitivity(3.248f, 100.0f);
239 float pixelsPerDegree = width / (2.0f * tanf(fov * 0.5f) * 180.0f / M_PI);
240
241 ImageL3D baselineL(width, height, maxLevels);
242 ImageL3D testL(width, height, maxLevels);
243 ImageL scratchImageL(width, height);
244 float* cyclesPerDegree = SkNEW_ARRAY(float, maxLevels);
245 float* thresholdFactorFrequency = SkNEW_ARRAY(float, maxLevels - 2);
246 float* contrast = SkNEW_ARRAY(float, maxLevels - 2);
247
248 lab_to_l(baselineLAB, baselineL.getLayer(0));
249 lab_to_l(testLAB, testL.getLayer(0));
250
251 // Compute cpd - Cycles per degree on the pyramid
252 cyclesPerDegree[0] = 0.5f * pixelsPerDegree;
253 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
254 cyclesPerDegree[levelIndex] = cyclesPerDegree[levelIndex - 1] * 0.5f;
255 }
256
257 const float filterMatrix[] = { 0.05f, 0.25f, 0.4f, 0.25f, 0.05f };
258 // Compute G - The convolved lum for the baseline
259 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
260 convolve(baselineL.getLayer(levelIndex - 1), false, filterMatrix, 2, &sc ratchImageL);
261 convolve(&scratchImageL, true, filterMatrix, 2, baselineL.getLayer(level Index));
262 }
263 for (int levelIndex = 1; levelIndex < maxLevels; levelIndex++) {
264 convolve(testL.getLayer(levelIndex - 1), false, filterMatrix, 2, &scratc hImageL);
265 convolve(&scratchImageL, true, filterMatrix, 2, testL.getLayer(levelInde x));
266 }
267
268 // Compute F_freq - The elevation f
269 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
270 float cpd = cyclesPerDegree[levelIndex];
271 thresholdFactorFrequency[levelIndex] = contrastSensitivityMax /
272 contrast_sensitivity(cpd, 100.0f) ;
273 }
274
275 int failures = 0;
276 // Calculate F
277 for (int y = 0; y < height; y++) {
278 for (int x = 0; x < width; x++) {
279 float lBaseline;
280 float lTest;
281 baselineL.getLayer(0)->readPixel(x, y, &lBaseline);
282 testL.getLayer(0)->readPixel(x, y, &lTest);
283
284 float avgLBaseline;
285 float avgLTest;
286 baselineL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLBaseline);
287 testL.getLayer(maxLevels - 1)->readPixel(x, y, &avgLTest);
288
289 float lAdapt = 0.5f * (avgLBaseline + avgLTest);
290 if (lAdapt < 1e-5) {
291 lAdapt = 1e-5;
292 }
293
294 float contrastSum = 0.0f;
295 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
296 float baselineL0, baselineL1, baselineL2;
297 float testL0, testL1, testL2;
298 baselineL.getLayer(levelIndex + 0)->readPixel(x, y, &baselineL0) ;
299 testL. getLayer(levelIndex + 0)->readPixel(x, y, &testL0);
300 baselineL.getLayer(levelIndex + 1)->readPixel(x, y, &baselineL1) ;
301 testL. getLayer(levelIndex + 1)->readPixel(x, y, &testL1);
302 baselineL.getLayer(levelIndex + 2)->readPixel(x, y, &baselineL2) ;
303 testL. getLayer(levelIndex + 2)->readPixel(x, y, &testL2);
304
305 float baselineContrast1 = fabsf(baselineL0 - baselineL1);
306 float testContrast1 = fabsf(testL0 - testL1);
307 float numerator = (baselineContrast1 > testContrast1) ?
308 baselineContrast1 : testContrast1;
309
310 float baselineContrast2 = fabsf(baselineL2);
311 float testContrast2 = fabsf(testL2);
312 float denominator = (baselineContrast2 > testContrast2) ?
313 baselineContrast2 : testContrast2;
314
315 // Avoid divides by close to zero
316 if (denominator < 1e-5) {
317 denominator = 1e-5;
318 }
319
320 contrast[levelIndex] = numerator / denominator;
321 contrastSum += contrast[levelIndex];
322 }
323
324 if (contrastSum < 1e-5) {
325 contrastSum = 1e-5;
326 }
327
328 float F = 0.0f;
329 for (int levelIndex = 0; levelIndex < maxLevels - 2; levelIndex++) {
330 float mask = visual_mask(contrast[levelIndex] *
331 contrast_sensitivity(cyclesPerDegree[levelIndex], l Adapt));
332
333 F += contrast[levelIndex] +
334 thresholdFactorFrequency[levelIndex] * mask / contrastSum;
335 }
336
337 if (F < 1.0f) {
338 F = 1.0f;
339 }
340
341 if (F > 10.0f) {
342 F = 10.0f;
343 }
344
345
346 bool isFailure = false;
347 if (fabsf(lBaseline - lTest) > F * threshold_vs_intensity(lAdapt)) {
348 isFailure = true;
349 } else {
350 LAB baselineColor;
351 LAB testColor;
352 baselineLAB->readPixel(x, y, &baselineColor);
353 testLAB->readPixel(x, y, &testColor);
354 float contrastA = baselineColor.a - testColor.a;
355 float contrastB = baselineColor.b - testColor.b;
356 float colorScale = 1.0f;
357 if (lAdapt < 10.0f) {
358 colorScale = lAdapt / 10.0f;
359 }
360 colorScale *= colorScale;
361
362 if ((contrastA * contrastA + contrastB * contrastB) * colorScale > F)
363 {
364 isFailure = true;
365 }
366 }
367
368 if (isFailure) {
369 failures++;
370 }
371 }
372 }
373
374 SkDELETE_ARRAY(cyclesPerDegree);
375 SkDELETE_ARRAY(contrast);
376 SkDELETE_ARRAY(thresholdFactorFrequency);
377 return (double)failures;
378 }
379
380 const char* SkPMetric::getName() {
381 return "perceptual";
382 }
383
384 int SkPMetric::queueDiff(SkBitmap* baseline, SkBitmap* test) {
385 int diffID = fQueuedDiffs.count();
386 double startTime = get_seconds();
387 QueuedDiff* diff = fQueuedDiffs.push();
388
389 // Ensure the images are comparable
390 if (baseline->width() != test->width() || baseline->height() != test->height () ||
391 baseline->width() <= 0 || baseline->height() <= 0) {
392 diff->finished = true;
393 diff->result = 0.0;
394 return diffID;
395 }
396
397 ImageLAB baselineLAB(baseline->width(), baseline->height());
398 ImageLAB testLAB(baseline->width(), baseline->height());
399
400 bitmap_to_cielab(baseline, &baselineLAB);
401 bitmap_to_cielab(test, &testLAB);
402
403 diff->result = pmetric(&baselineLAB, &testLAB);
404
405 SkDebugf("Time: %f\n", (get_seconds() - startTime));
406
407 return diffID;
408 }
409
410
411 bool SkPMetric::isFinished(int id) {
412 return fQueuedDiffs[id].finished;
413 }
414
415 double SkPMetric::getResult(int id) {
416 return fQueuedDiffs[id].result;
417 }
OLDNEW
« no previous file with comments | « experimental/skpdiff/SkPMetric.h ('k') | experimental/skpdiff/main.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698