/
ssim.ts
112 lines (89 loc) · 3.65 KB
/
ssim.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* @preserve
* Copyright 2015 Igor Bezkrovny
* All rights reserved. (MIT Licensed)
*
* ssim.ts - part of Image Quantization Library
*/
// based on https://github.com/rhys-e/structural-similarity
// http://en.wikipedia.org/wiki/Structural_similarity
module IQ.Quality {
var K1 = 0.01,
K2 = 0.03;
var RED_COEFFICIENT = 0.212655,
GREEN_COEFFICIENT = 0.715158,
BLUE_COEFFICIENT = 0.072187;
export class SSIM {
public compare(image1 : Utils.PointContainer, image2 : Utils.PointContainer) {
if (image1.getHeight() !== image2.getHeight() || image1.getWidth() !== image2.getWidth()) {
throw new Error("Images have different sizes!");
}
var bitsPerComponent = 8,
L = (1 << bitsPerComponent) - 1,
c1 = Math.pow((K1 * L), 2),
c2 = Math.pow((K2 * L), 2),
numWindows = 0,
mssim = 0.0;
//calculate ssim for each window
this._iterate(image1, image2, (lumaValues1 : number[], lumaValues2 : number[], averageLumaValue1 : number, averageLumaValue2 : number) => {
//calculate variance and covariance
var sigxy, sigsqx, sigsqy;
sigxy = sigsqx = sigsqy = 0.0;
for (var i = 0; i < lumaValues1.length; i++) {
sigsqx += Math.pow((lumaValues1[i] - averageLumaValue1), 2);
sigsqy += Math.pow((lumaValues2[i] - averageLumaValue2), 2);
sigxy += (lumaValues1[i] - averageLumaValue1) * (lumaValues2[i] - averageLumaValue2);
}
var numPixelsInWin = lumaValues1.length - 1;
sigsqx /= numPixelsInWin;
sigsqy /= numPixelsInWin;
sigxy /= numPixelsInWin;
//perform ssim calculation on window
var numerator = (2 * averageLumaValue1 * averageLumaValue2 + c1) * (2 * sigxy + c2),
denominator = (Math.pow(averageLumaValue1, 2) + Math.pow(averageLumaValue2, 2) + c1) * (sigsqx + sigsqy + c2),
ssim = numerator / denominator;
mssim += ssim;
numWindows++;
});
return mssim / numWindows;
}
private _iterate(image1 : Utils.PointContainer, image2 : Utils.PointContainer, callback : (lumaValues1 : number[], lumaValues2 : number[], averageLumaValue1 : number, averageLumaValue2 : number) => void) {
var windowSize = 8,
width = image1.getWidth(),
height = image1.getHeight();
for (var y = 0; y < height; y += windowSize) {
for (var x = 0; x < width; x += windowSize) {
// avoid out-of-width/height
var windowWidth = Math.min(windowSize, width - x),
windowHeight = Math.min(windowSize, height - y);
var lumaValues1 = this._calculateLumaValuesForWindow(image1, x, y, windowWidth, windowHeight),
lumaValues2 = this._calculateLumaValuesForWindow(image2, x, y, windowWidth, windowHeight),
averageLuma1 = this._calculateAverageLuma(lumaValues1),
averageLuma2 = this._calculateAverageLuma(lumaValues2);
callback(lumaValues1, lumaValues2, averageLuma1, averageLuma2);
}
}
}
private _calculateLumaValuesForWindow(image : Utils.PointContainer, x : number, y : number, width : number, height : number) : number[] {
var pointArray = image.getPointArray(),
lumaValues : number[] = [],
counter = 0;
for (var j = y; j < y + height; j++) {
var offset = j * image.getWidth();
for (var i = x; i < x + width; i++) {
var point = pointArray[offset + i];
lumaValues[counter] = point.r * RED_COEFFICIENT + point.g * GREEN_COEFFICIENT + point.b * BLUE_COEFFICIENT;
counter++;
}
}
return lumaValues;
}
private _calculateAverageLuma(lumaValues : number[]) : number {
var sumLuma = 0.0;
for (var i = 0; i < lumaValues.length; i++) {
sumLuma += lumaValues[i];
}
return sumLuma / lumaValues.length;
}
}
}