]> sjero.net Git - qpsnr/blob - src/stats.cpp
Initial Commit of QPSNR (version 0.2.1)
[qpsnr] / src / stats.cpp
1 /*
2 *       qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> it
3 *
4 *       This file is part of qpsnr.
5 *
6 *       qpsnr is free software: you can redistribute it and/or modify
7 *       it under the terms of the GNU General Public License as published by
8 *       the Free Software Foundation, either version 3 of the License, or
9 *       (at your option) any later version.
10 *
11 *       qpsnr is distributed in the hope that it will be useful,
12 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *       GNU General Public License for more details.
15 *
16 *       You should have received a copy of the GNU General Public License
17 *       along with qpsnr.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "stats.h"
21 #include "mt.h"
22 #include "shared_ptr.h"
23 #include "settings.h"
24 #include <cmath>
25 #include <string>
26 #include <stdexcept>
27 #include <set>
28 #include <fstream>
29 #include <algorithm>
30 #include <cstdlib>
31
32 // define these classes just locally
33 namespace stats {
34         static double compute_psnr(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz) {
35                 double mse = 0.0;
36                 for(unsigned int i = 0; i < sz; ++i) {
37                         const int       diff = ref[i]-cmp[i];
38                         mse += (diff*diff);
39                 }
40                 mse /= (double)sz;
41                 if (0.0 == mse) mse = 1e-10;
42                 return 10.0*log10(65025.0/mse);
43         }
44
45         static double compute_ssim(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y, const unsigned int& b_sz) {
46                 // we return the average of all the blocks
47                 const unsigned int      x_bl_num = x/b_sz,
48                                         y_bl_num = y/b_sz;
49                 if (!x_bl_num || !y_bl_num) return 0.0;
50                 std::vector<double>     ssim_accum;
51                 // for each block do it
52                 for(int yB = 0; yB < y_bl_num; ++yB)
53                         for(int xB = 0; xB < x_bl_num; ++xB) {
54                                 const unsigned int      base_offset = xB*b_sz + yB*b_sz*x;
55                                 double                  ref_acc = 0.0,
56                                                         ref_acc_2 = 0.0,
57                                                         cmp_acc = 0.0,
58                                                         cmp_acc_2 = 0.0,
59                                                         ref_cmp_acc = 0.0;
60                                 for(int j = 0; j < b_sz; ++j)
61                                         for(int i = 0; i < b_sz; ++i) {
62                                                 // we have to multiply by 3, colorplanes are Y Cb Cr, we need
63                                                 // only Y component
64                                                 const unsigned char     c_ref = ref[3*(base_offset + j*x + i)],
65                                                                         c_cmp = cmp[3*(base_offset + j*x + i)];
66                                                 ref_acc += c_ref;
67                                                 ref_acc_2 += (c_ref*c_ref);
68                                                 cmp_acc += c_cmp;
69                                                 cmp_acc_2 += (c_cmp*c_cmp);
70                                                 ref_cmp_acc += (c_ref*c_cmp);
71                                         }
72                                 // now finally get the ssim for this block
73                                 // http://en.wikipedia.org/wiki/SSIM
74                                 // http://en.wikipedia.org/wiki/Variance
75                                 // http://en.wikipedia.org/wiki/Covariance
76                                 const double    n_samples = (b_sz*b_sz),
77                                                 ref_avg = ref_acc/n_samples,
78                                                 ref_var = ref_acc_2/n_samples - (ref_avg*ref_avg),
79                                                 cmp_avg = cmp_acc/n_samples,
80                                                 cmp_var = cmp_acc_2/n_samples - (cmp_avg*cmp_avg),
81                                                 ref_cmp_cov = ref_cmp_acc/n_samples - (ref_avg*cmp_avg),
82                                                 c1 = 6.5025, // (0.01*255.0)^2
83                                                 c2 = 58.5225, // (0.03*255)^2
84                                                 ssim_num = (2.0*ref_avg*cmp_avg + c1)*(2.0*ref_cmp_cov + c2),
85                                                 ssim_den = (ref_avg*ref_avg + cmp_avg*cmp_avg + c1)*(ref_var + cmp_var + c2),
86                                                 ssim = ssim_num/ssim_den;
87                                 ssim_accum.push_back(ssim);
88                         }
89
90                 double  avg = 0.0;
91                 for(std::vector<double>::const_iterator it = ssim_accum.begin(); it != ssim_accum.end(); ++it)
92                         avg += *it;
93                 return avg/ssim_accum.size();
94         }
95
96         static inline double r_0_1(const double& d) {
97                 return std::max(0.0, std::min(1.0, d));
98         }
99
100         static void rgb_2_hsi(unsigned char *p, const int& sz) {
101                 /*
102                 I = (1/3) *(R+G+B)
103                 S = 1 - ( (3/(R+G+B)) * min(R,G,B))
104                 H = cos^-1( ((1/2) * ((R-G) + (R - B))) / ((R-G)^2 + (R-B)*(G-B)) ^(1/2) )
105                 H = cos^-1 ( (((R-G)+(R-B))/2)/ (sqrt((R-G)^2 + (R-B)*(G-B) )))
106                 */
107                 const static double PI = 3.14159265;
108                 for(int j =0; j < sz; j += 3) {
109                         const double    r = p[j+0]/255.0,
110                                         g = p[j+1]/255.0,
111                                         b = p[j+2]/255.0,
112                                         i = r_0_1((r+g+b)/3),
113                                         s = r_0_1(1.0 - (3.0/(r+g+b) * std::min(r, std::min(g, b)))),
114                                         h = r_0_1(acos(0.5*(r-g + r-b) / sqrt((r-g)*(r-g) + (r-b)*(g-b)))/PI);
115                         p[j+0] = 255.0*h + 0.5;
116                         p[j+1] = 255.0*s + 0.5;
117                         p[j+2] = 255.0*i + 0.5;
118                 }
119         }
120
121         static void rgb_2_YCbCr(unsigned char *p, const int& sz) {
122                 // http://en.wikipedia.org/wiki/YCbCr
123                 for(int j =0; j < sz; j += 3) {
124                         const double    r = p[j+0]/255.0,
125                                         g = p[j+1]/255.0,
126                                         b = p[j+2]/255.0;
127                         p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
128                         p[j+1] = 128 + (-37.797*r - 74.203*g + 112.0*b);
129                         p[j+2] = 128 + (112.0*r - 93.786*g - 12.214*b);
130                 }
131         }
132
133         static void rgb_2_Y(unsigned char *p, const int& sz) {
134                 // http://en.wikipedia.org/wiki/YCbCr
135                 for(int j =0; j < sz; j += 3) {
136                         const double    r = p[j+0]/255.0,
137                                         g = p[j+1]/255.0,
138                                         b = p[j+2]/255.0;
139                         p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
140                         p[j+1] = 0.0;
141                         p[j+2] = 0.0;
142                 }
143         }
144
145         static int getCPUcount(void) {
146                 std::ifstream           cpuinfo("/proc/cpuinfo");
147                 std::string             line;
148                 std::set<std::string>   IDs;
149                 while (cpuinfo){
150                         std::getline(cpuinfo,line);
151                         if (line.empty())
152                                 continue;
153                         if (line.find("processor") != 0)
154                                 continue;
155                         IDs.insert(line);
156                 }
157                 if (IDs.empty()) return 1;
158                 return IDs.size();
159         }
160
161         static mt::ThreadPool   __stats_tp(getCPUcount());
162
163         class psnr_job : public mt::ThreadPool::Job {
164                 const unsigned char     *_ref,
165                                         *_cmp;
166                 const unsigned int      _sz;
167                 double                  &_res;
168         public:
169                 psnr_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz, double& res) :
170                 _ref(ref), _cmp(cmp), _sz(sz), _res(res) {
171                 }
172
173                 virtual void run(void) {
174                         _res = compute_psnr(_ref, _cmp, _sz);
175                 }
176         };
177
178         static void get_psnr_tp(const VUCHAR& ref, const std::vector<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<double>& res) {
179                 const unsigned int                      sz = v_ok.size();
180                 std::vector<shared_ptr<psnr_job> >      v_jobs;
181                 for(unsigned int i =0; i < sz; ++i) {
182                         if (v_ok[i]) {
183                                 v_jobs.push_back(new psnr_job(&ref[0], &(streams[i][0]), ref.size(), res[i]));
184                                 __stats_tp.add(v_jobs.rbegin()->get());
185                         } else res[i] = 0.0;
186                 }
187                 //wait for all
188                 for(std::vector<shared_ptr<psnr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
189                         (*it)->wait();
190                         (*it) = 0;
191                 }
192         }
193
194         class ssim_job : public mt::ThreadPool::Job {
195                 const unsigned char     *_ref,
196                                         *_cmp;
197                 const unsigned int      _x,
198                                         _y,
199                                         _b_sz;
200                 double                  &_res;
201         public:
202                 ssim_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y, const unsigned int b_sz, double& res) :
203                 _ref(ref), _cmp(cmp), _x(x), _y(y), _b_sz(b_sz), _res(res) {
204                 }
205
206                 virtual void run(void) {
207                         _res = compute_ssim(_ref, _cmp, _x, _y, _b_sz);
208                 }
209         };
210
211         static void get_ssim_tp(const VUCHAR& ref, const std::vector<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<double>& res, const unsigned int& x, const unsigned int& y, const unsigned int& b_sz) {
212                 const unsigned int                      sz = v_ok.size();
213                 std::vector<shared_ptr<ssim_job> >      v_jobs;
214                 for(unsigned int i =0; i < sz; ++i) {
215                         if (v_ok[i]) {
216                                 v_jobs.push_back(new ssim_job(&ref[0], &(streams[i][0]), x, y, b_sz, res[i]));
217                                 __stats_tp.add(v_jobs.rbegin()->get());
218                         } else res[i] = 0.0;
219                 }
220                 //wait for all
221                 for(std::vector<shared_ptr<ssim_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
222                         (*it)->wait();
223                         (*it) = 0;
224                 }
225         }
226
227         class hsi_job : public mt::ThreadPool::Job {
228                 unsigned char           *_buf;
229                 const unsigned int      _sz;
230         public:
231                 hsi_job(unsigned char *buf, const unsigned int& sz) :
232                 _buf(buf), _sz(sz) {
233                 }
234
235                 virtual void run(void) {
236                         rgb_2_hsi(_buf, _sz);
237                 }
238         };
239
240         class YCbCr_job : public mt::ThreadPool::Job {
241                 unsigned char           *_buf;
242                 const unsigned int      _sz;
243         public:
244                 YCbCr_job(unsigned char *buf, const unsigned int& sz) :
245                 _buf(buf), _sz(sz) {
246                 }
247
248                 virtual void run(void) {
249                         rgb_2_YCbCr(_buf, _sz);
250                 }
251         };
252
253         class Y_job : public mt::ThreadPool::Job {
254                 unsigned char           *_buf;
255                 const unsigned int      _sz;
256         public:
257                 Y_job(unsigned char *buf, const unsigned int& sz) :
258                 _buf(buf), _sz(sz) {
259                 }
260
261                 virtual void run(void) {
262                         rgb_2_Y(_buf, _sz);
263                 }
264         };
265
266         static void rgb_2_hsi_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
267                 const unsigned int                      sz = v_ok.size();
268                 std::vector<shared_ptr<hsi_job> >       v_jobs;
269                 v_jobs.push_back(new hsi_job(&ref[0], ref.size()));
270                 __stats_tp.add(v_jobs.rbegin()->get());
271                 for(unsigned int i =0; i < sz; ++i) {
272                         if (v_ok[i]) {
273                                 v_jobs.push_back(new hsi_job(&(streams[i][0]), streams[i].size()));
274                                 __stats_tp.add(v_jobs.rbegin()->get());
275                         }
276                 }
277                 //wait for all
278                 for(std::vector<shared_ptr<hsi_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
279                         (*it)->wait();
280                         (*it) = 0;
281                 }
282         }
283
284         static void rgb_2_YCbCr_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
285                 const unsigned int                      sz = v_ok.size();
286                 std::vector<shared_ptr<YCbCr_job> >     v_jobs;
287                 v_jobs.push_back(new YCbCr_job(&ref[0], ref.size()));
288                 __stats_tp.add(v_jobs.rbegin()->get());
289                 for(unsigned int i =0; i < sz; ++i) {
290                         if (v_ok[i]) {
291                                 v_jobs.push_back(new YCbCr_job(&(streams[i][0]), streams[i].size()));
292                                 __stats_tp.add(v_jobs.rbegin()->get());
293                         }
294                 }
295                 //wait for all
296                 for(std::vector<shared_ptr<YCbCr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
297                         (*it)->wait();
298                         (*it) = 0;
299                 }
300         }
301
302         static void rgb_2_Y_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
303                 const unsigned int                      sz = v_ok.size();
304                 std::vector<shared_ptr<Y_job> >         v_jobs;
305                 v_jobs.push_back(new Y_job(&ref[0], ref.size()));
306                 __stats_tp.add(v_jobs.rbegin()->get());
307                 for(unsigned int i =0; i < sz; ++i) {
308                         if (v_ok[i]) {
309                                 v_jobs.push_back(new Y_job(&(streams[i][0]), streams[i].size()));
310                                 __stats_tp.add(v_jobs.rbegin()->get());
311                         }
312                 }
313                 //wait for all
314                 for(std::vector<shared_ptr<Y_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
315                         (*it)->wait();
316                         (*it) = 0;
317                 }
318         }
319
320         class psnr : public s_base {
321                 std::string     _colorspace;
322         protected:
323                 void print(const int& ref_frame, const std::vector<double>& v_res) {
324                         _ostr << ref_frame << ',';
325                         for(int i = 0; i < _n_streams; ++i)
326                                 _ostr << v_res[i] << ',';
327                         _ostr << std::endl;
328                 }
329
330                 void process_colorspace(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
331                         if (_colorspace == "hsi") {
332                                 rgb_2_hsi_tp(ref, v_ok, streams);
333                         } else if (_colorspace == "ycbcr") {
334                                 rgb_2_YCbCr_tp(ref, v_ok, streams);
335                         } else if (_colorspace == "y") {
336                                 rgb_2_Y_tp(ref, v_ok, streams);
337                         }
338                 }
339         public:
340                 psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
341                 s_base(n_streams, i_width, i_height, ostr), _colorspace("rgb") {
342                 }
343
344                 virtual void set_parameter(const char* p_name, const char *p_value) {
345                         const std::string s_name(p_name);
346                         if (s_name == "colorspace") {
347                                 _colorspace = p_value;
348                                 if (_colorspace != "rgb" && _colorspace != "hsi"
349                                 && _colorspace != "ycbcr" && _colorspace != "y")
350                                         throw std::runtime_error("Invalid colorspace passed to analyzer");
351                         }
352                 }
353
354                 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
355                         if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
356                         // process colorspace
357                         process_colorspace(ref, v_ok, streams);
358                         //
359                         std::vector<double>     v_res(_n_streams);
360                         get_psnr_tp(ref, v_ok, streams, v_res);
361                         //
362                         print(ref_frame, v_res);
363                 }
364         };
365
366         class avg_psnr : public psnr {
367                 int                     _fpa,
368                                         _accum_f,
369                                         _last_frame;
370                 std::vector<double>     _accum_v;
371         protected:
372                 void print(const int& ref_frame) {
373                         // check if we have to print infor or not
374                         if (_accum_f < _fpa) return;
375                         // else print data
376                         for(int i=0; i < _n_streams; ++i)
377                                 _accum_v[i] /= _accum_f;
378                         psnr::print(ref_frame, _accum_v);
379                         // clear the vector and set _accum_f to 0
380                         _accum_v.clear();
381                         _accum_v.resize(_n_streams);
382                         _accum_f = 0;
383                 }
384         public:
385                 avg_psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
386                 psnr(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) {
387                 }
388
389                 virtual void set_parameter(const char* p_name, const char *p_value) {
390                         psnr::set_parameter(p_name, p_value);
391                         //
392                         const std::string s_name(p_name);
393                         if (s_name == "fpa") {
394                                 const int fpa = atoi(p_value);
395                                 if (fpa > 0) _fpa = fpa;
396                         }
397                 }
398
399                 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
400                         if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
401                         // set last frame
402                         _last_frame = ref_frame;
403                         // process colorspace
404                         process_colorspace(ref, v_ok, streams);
405                         // compute the psnr
406                         std::vector<double>     v_res(_n_streams);
407                         get_psnr_tp(ref, v_ok, streams, v_res);
408                         // accumulate for each
409                         for(int i = 0; i < _n_streams; ++i) {
410                                 if (v_ok[i]) {
411                                          _accum_v[i] += v_res[i];
412                                 } else _accum_v[i] += 0.0;
413                         }
414                         ++_accum_f;
415                         // try to print data
416                         print(ref_frame);
417                 }
418
419                 virtual ~avg_psnr() {
420                         // on exit check if we have some data
421                         if (_accum_f > 0) {
422                                 _ostr << _last_frame << ',';
423                                 for(int i = 0; i < _n_streams; ++i) {
424                                         _ostr << _accum_v[i]/_accum_f << ',';
425                                 }
426                                 _ostr << std::endl;
427                         }
428                 }
429         };
430
431         class ssim : public s_base {
432         protected:
433                 int     _blocksize;
434
435                 void print(const int& ref_frame, const std::vector<double>& v_res) {
436                         _ostr << ref_frame << ',';
437                         for(int i = 0; i < _n_streams; ++i)
438                                 _ostr << v_res[i] << ',';
439                         _ostr << std::endl;
440                 }
441         public:
442                 ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
443                 s_base(n_streams, i_width, i_height, ostr), _blocksize(8) {
444                 }
445
446                 virtual void set_parameter(const char* p_name, const char *p_value) {
447                         const std::string s_name(p_name);
448                         if (s_name == "blocksize") {
449                                 const int blocksize = atoi(p_value);
450                                 if (blocksize > 0) _blocksize = blocksize;
451                         }
452                 }
453
454                 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
455                         if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
456                         // convert to Y colorspace
457                         rgb_2_Y_tp(ref, v_ok, streams);
458                         //
459                         std::vector<double>     v_res(_n_streams);
460                         get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
461                         //
462                         print(ref_frame, v_res);
463                 }
464         };
465
466         class avg_ssim : public ssim {
467                 int                     _fpa,
468                                         _accum_f,
469                                         _last_frame;
470                 std::vector<double>     _accum_v;
471         protected:
472                 void print(const int& ref_frame) {
473                         // check if we have to print infor or not
474                         if (_accum_f < _fpa) return;
475                         // else print data
476                         for(int i=0; i < _n_streams; ++i)
477                                 _accum_v[i] /= _accum_f;
478                         ssim::print(ref_frame, _accum_v);
479                         // clear the vector and set _accum_f to 0
480                         _accum_v.clear();
481                         _accum_v.resize(_n_streams);
482                         _accum_f = 0;
483                 }
484         public:
485                 avg_ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
486                 ssim(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) {
487                 }
488
489                 virtual void set_parameter(const char* p_name, const char *p_value) {
490                         ssim::set_parameter(p_name, p_value);
491                         //
492                         const std::string s_name(p_name);
493                         if (s_name == "fpa") {
494                                 const int fpa = atoi(p_value);
495                                 if (fpa > 0) _fpa = fpa;
496                         }
497                 }
498
499                 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
500                         if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
501                         // set last frame
502                         _last_frame = ref_frame;
503                         // convert to Y colorspace
504                         rgb_2_Y_tp(ref, v_ok, streams);
505                         //
506                         std::vector<double>     v_res(_n_streams);
507                         get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
508                         // accumulate for each
509                         for(int i = 0; i < _n_streams; ++i) {
510                                 if (v_ok[i]) {
511                                          _accum_v[i] += v_res[i];
512                                 } else _accum_v[i] += 0.0;
513                         }
514                         ++_accum_f;
515                         // try to print data
516                         print(ref_frame);
517                 }
518
519                 virtual ~avg_ssim() {
520                         // on exit check if we have some data
521                         if (_accum_f > 0) {
522                                 _ostr << _last_frame << ',';
523                                 for(int i = 0; i < _n_streams; ++i) {
524                                         _ostr << _accum_v[i]/_accum_f << ',';
525                                 }
526                                 _ostr << std::endl;
527                         }
528                 }
529         };
530 }
531
532 stats::s_base* stats::get_analyzer(const char* id, const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) {
533         const std::string       s_id(id);
534         if (s_id == "psnr") return new psnr(n_streams, i_width, i_height, ostr);
535         else if (s_id == "avg_psnr") return new avg_psnr(n_streams, i_width, i_height, ostr);
536         else if (s_id == "ssim") return new ssim(n_streams, i_width, i_height, ostr);
537         else if (s_id == "avg_ssim") return new avg_ssim(n_streams, i_width, i_height, ostr);
538         throw std::runtime_error("Invalid analyzer id");
539 }