2 * qpsnr (C) 2010 E. Oriani, ema <AT> fastwebnet <DOT> it
4 * This file is part of qpsnr.
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.
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.
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/>.
22 #include "shared_ptr.h"
35 // define these classes just locally
37 static double compute_psnr(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz) {
39 for(unsigned int i = 0; i < sz; ++i) {
40 const int diff = ref[i]-cmp[i];
44 if (0.0 == mse) mse = 1e-10;
45 return 10.0*log10(65025.0/mse);
48 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) {
49 // we return the average of all the blocks
50 const unsigned int x_bl_num = x/b_sz,
52 if (!x_bl_num || !y_bl_num) return 0.0;
53 std::vector<double> ssim_accum;
54 // for each block do it
55 for(int yB = 0; yB < y_bl_num; ++yB)
56 for(int xB = 0; xB < x_bl_num; ++xB) {
57 const unsigned int base_offset = xB*b_sz + yB*b_sz*x;
63 for(int j = 0; j < b_sz; ++j)
64 for(int i = 0; i < b_sz; ++i) {
65 // we have to multiply by 3, colorplanes are Y Cb Cr, we need
67 const unsigned char c_ref = ref[3*(base_offset + j*x + i)],
68 c_cmp = cmp[3*(base_offset + j*x + i)];
70 ref_acc_2 += (c_ref*c_ref);
72 cmp_acc_2 += (c_cmp*c_cmp);
73 ref_cmp_acc += (c_ref*c_cmp);
75 // now finally get the ssim for this block
76 // http://en.wikipedia.org/wiki/SSIM
77 // http://en.wikipedia.org/wiki/Variance
78 // http://en.wikipedia.org/wiki/Covariance
79 const double n_samples = (b_sz*b_sz),
80 ref_avg = ref_acc/n_samples,
81 ref_var = ref_acc_2/n_samples - (ref_avg*ref_avg),
82 cmp_avg = cmp_acc/n_samples,
83 cmp_var = cmp_acc_2/n_samples - (cmp_avg*cmp_avg),
84 ref_cmp_cov = ref_cmp_acc/n_samples - (ref_avg*cmp_avg),
85 c1 = 6.5025, // (0.01*255.0)^2
86 c2 = 58.5225, // (0.03*255)^2
87 ssim_num = (2.0*ref_avg*cmp_avg + c1)*(2.0*ref_cmp_cov + c2),
88 ssim_den = (ref_avg*ref_avg + cmp_avg*cmp_avg + c1)*(ref_var + cmp_var + c2),
89 ssim = ssim_num/ssim_den;
90 ssim_accum.push_back(ssim);
94 for(std::vector<double>::const_iterator it = ssim_accum.begin(); it != ssim_accum.end(); ++it)
96 return avg/ssim_accum.size();
99 double std_dev(int *buffer, int numelem)
103 for (int i=0; i<numelem; i++) sum+=buffer[i];
104 int mean = sum/numelem;
106 // sum of the squares
107 long long sum_squares = 0;
108 for (int i=0; i<numelem; i++)
109 sum_squares += (long long)pow((float)(buffer[i]-mean),2);
111 // return the square root
112 return sqrt((double)(sum_squares/numelem));
115 void conv_sobel(int *buffer, int width, int height)
117 int mask1[9] = {-1,-2,-1,0,0,0,1,2,1}; // TODO right values
118 int mask2[9] = {-1,0,1,-2,0,2,-1,0,1};
121 int buffsize = width*height;
122 int *buffer_tmp = (int *)malloc(buffsize*sizeof(int));
123 memcpy(buffer_tmp, buffer, buffsize*sizeof(int));
124 for (int l=1; l<height-1; l++) {
125 for (int c=1; c<width-1; c++) {
127 int p1 = (l-1)*width+(c-1); // pos first pixel
128 int p2 = l*width+(c-1); // pos 2nd line first pixel
129 int p3 = (l+1)*width+(c-1); // pos 3nd line first pixel
131 // apply the mask and set the result
133 gv += buffer[p1]*mask1[0] + buffer[p1+1]*mask1[1] + buffer[p1+2]*mask1[2];
134 gv += buffer[p2]*mask1[3] + buffer[p2+1]*mask1[4] + buffer[p2+2]*mask1[5];
135 gv += buffer[p3]*mask1[6] + buffer[p3+1]*mask1[7] + buffer[p3+2]*mask1[8];
136 gh += buffer[p1]*mask2[0] + buffer[p1+1]*mask2[1] + buffer[p1+2]*mask2[2];
137 gh += buffer[p2]*mask2[3] + buffer[p2+1]*mask2[4] + buffer[p2+2]*mask2[5];
138 gh += buffer[p3]*mask2[6] + buffer[p3+1]*mask2[7] + buffer[p3+2]*mask2[8];
140 buffer_tmp[l*width+c] = sqrt(pow((double)gv,2)+pow((double)gh,2));
143 memcpy(buffer, buffer_tmp, buffsize*sizeof(int));
147 static double compute_si(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y){
149 int *buff_sob1 = (int *)malloc(sizebuff*sizeof(int));
152 // create new buffer and apply both sobel filters
153 for (int i=0; i<sizebuff; i++)
154 buff_sob1[i] = cmp[3*i];
156 conv_sobel(buff_sob1, x, y);
158 // get the standard deviation
159 double sdev = std_dev(buff_sob1, sizebuff);
164 static double compute_ti(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y){
166 int *buff_tmp = (int *)malloc(numelem*sizeof(int));
172 // difference between 2nd frame and 1st one to buff_tmp
173 for (int i=0; i<numelem; i++) {
174 buff_tmp[i] = (int)cmp[3*i]-(int)ref[3*i];
177 // find and return the standard deviation of buff_tmp
178 double sdev = std_dev(buff_tmp, numelem);
183 static inline double r_0_1(const double& d) {
184 return std::max(0.0, std::min(1.0, d));
187 static void rgb_2_hsi(unsigned char *p, const int& sz) {
190 S = 1 - ( (3/(R+G+B)) * min(R,G,B))
191 H = cos^-1( ((1/2) * ((R-G) + (R - B))) / ((R-G)^2 + (R-B)*(G-B)) ^(1/2) )
192 H = cos^-1 ( (((R-G)+(R-B))/2)/ (sqrt((R-G)^2 + (R-B)*(G-B) )))
194 const static double PI = 3.14159265;
195 for(int j =0; j < sz; j += 3) {
196 const double r = p[j+0]/255.0,
199 i = r_0_1((r+g+b)/3),
200 s = r_0_1(1.0 - (3.0/(r+g+b) * std::min(r, std::min(g, b)))),
201 h = r_0_1(acos(0.5*(r-g + r-b) / sqrt((r-g)*(r-g) + (r-b)*(g-b)))/PI);
202 p[j+0] = 255.0*h + 0.5;
203 p[j+1] = 255.0*s + 0.5;
204 p[j+2] = 255.0*i + 0.5;
208 static void rgb_2_YCbCr(unsigned char *p, const int& sz) {
209 // http://en.wikipedia.org/wiki/YCbCr
210 for(int j =0; j < sz; j += 3) {
211 const double r = p[j+0]/255.0,
214 p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
215 p[j+1] = 128 + (-37.797*r - 74.203*g + 112.0*b);
216 p[j+2] = 128 + (112.0*r - 93.786*g - 12.214*b);
220 static void rgb_2_Y(unsigned char *p, const int& sz) {
221 // http://en.wikipedia.org/wiki/YCbCr
222 for(int j =0; j < sz; j += 3) {
223 const double r = p[j+0]/255.0,
226 p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
232 static int getCPUcount(void) {
233 std::ifstream cpuinfo("/proc/cpuinfo");
235 std::set<std::string> IDs;
237 std::getline(cpuinfo,line);
240 if (line.find("processor") != 0)
244 if (IDs.empty()) return 1;
248 static mt::ThreadPool __stats_tp(getCPUcount());
250 class psnr_job : public mt::ThreadPool::Job {
251 const unsigned char *_ref,
253 const unsigned int _sz;
256 psnr_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz, double& res) :
257 _ref(ref), _cmp(cmp), _sz(sz), _res(res) {
260 virtual void run(void) {
261 _res = compute_psnr(_ref, _cmp, _sz);
265 static void get_psnr_tp(const VUCHAR& ref, const std::vector<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<double>& res) {
266 const unsigned int sz = v_ok.size();
267 std::vector<shared_ptr<psnr_job> > v_jobs;
268 for(unsigned int i =0; i < sz; ++i) {
270 v_jobs.push_back(new psnr_job(&ref[0], &(streams[i][0]), ref.size(), res[i]));
271 __stats_tp.add(v_jobs.rbegin()->get());
275 for(std::vector<shared_ptr<psnr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
281 class ssim_job : public mt::ThreadPool::Job {
282 const unsigned char *_ref,
284 const unsigned int _x,
289 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) :
290 _ref(ref), _cmp(cmp), _x(x), _y(y), _b_sz(b_sz), _res(res) {
293 virtual void run(void) {
294 _res = compute_ssim(_ref, _cmp, _x, _y, _b_sz);
298 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) {
299 const unsigned int sz = v_ok.size();
300 std::vector<shared_ptr<ssim_job> > v_jobs;
301 for(unsigned int i =0; i < sz; ++i) {
303 v_jobs.push_back(new ssim_job(&ref[0], &(streams[i][0]), x, y, b_sz, res[i]));
304 __stats_tp.add(v_jobs.rbegin()->get());
308 for(std::vector<shared_ptr<ssim_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
314 class siti_job : public mt::ThreadPool::Job {
315 const unsigned char *_ref,
317 const unsigned int _x,
319 std::pair<double,double> &_res;
321 siti_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y, std::pair<double,double>& res) :
322 _ref(ref), _cmp(cmp), _x(x), _y(y), _res(res) {
325 virtual void run(void) {
326 _res.first = compute_si(_ref, _cmp, _x, _y);
327 _res.second= compute_ti(_ref, _cmp, _x, _y);
331 static void get_siti_tp(const VUCHAR* ref, const std::vector<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<std::pair<double,double> >& res, const unsigned int& x, const unsigned int& y) {
332 const unsigned int sz = v_ok.size();
333 std::vector<shared_ptr<siti_job> > v_jobs;
334 for(unsigned int i =0; i < sz; ++i) {
336 v_jobs.push_back(new siti_job(&((*ref)[0]), &(streams[i][0]), x, y, res[i]));
337 __stats_tp.add(v_jobs.rbegin()->get());
344 for(std::vector<shared_ptr<siti_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
350 class hsi_job : public mt::ThreadPool::Job {
352 const unsigned int _sz;
354 hsi_job(unsigned char *buf, const unsigned int& sz) :
358 virtual void run(void) {
359 rgb_2_hsi(_buf, _sz);
363 class YCbCr_job : public mt::ThreadPool::Job {
365 const unsigned int _sz;
367 YCbCr_job(unsigned char *buf, const unsigned int& sz) :
371 virtual void run(void) {
372 rgb_2_YCbCr(_buf, _sz);
376 class Y_job : public mt::ThreadPool::Job {
378 const unsigned int _sz;
380 Y_job(unsigned char *buf, const unsigned int& sz) :
384 virtual void run(void) {
389 static void rgb_2_hsi_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
390 const unsigned int sz = v_ok.size();
391 std::vector<shared_ptr<hsi_job> > v_jobs;
392 v_jobs.push_back(new hsi_job(&ref[0], ref.size()));
393 __stats_tp.add(v_jobs.rbegin()->get());
394 for(unsigned int i =0; i < sz; ++i) {
396 v_jobs.push_back(new hsi_job(&(streams[i][0]), streams[i].size()));
397 __stats_tp.add(v_jobs.rbegin()->get());
401 for(std::vector<shared_ptr<hsi_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
407 static void rgb_2_YCbCr_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
408 const unsigned int sz = v_ok.size();
409 std::vector<shared_ptr<YCbCr_job> > v_jobs;
410 v_jobs.push_back(new YCbCr_job(&ref[0], ref.size()));
411 __stats_tp.add(v_jobs.rbegin()->get());
412 for(unsigned int i =0; i < sz; ++i) {
414 v_jobs.push_back(new YCbCr_job(&(streams[i][0]), streams[i].size()));
415 __stats_tp.add(v_jobs.rbegin()->get());
419 for(std::vector<shared_ptr<YCbCr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
425 static void rgb_2_Y_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
426 const unsigned int sz = v_ok.size();
427 std::vector<shared_ptr<Y_job> > v_jobs;
428 v_jobs.push_back(new Y_job(&ref[0], ref.size()));
429 __stats_tp.add(v_jobs.rbegin()->get());
430 for(unsigned int i =0; i < sz; ++i) {
432 v_jobs.push_back(new Y_job(&(streams[i][0]), streams[i].size()));
433 __stats_tp.add(v_jobs.rbegin()->get());
437 for(std::vector<shared_ptr<Y_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
443 class psnr : public s_base {
444 std::string _colorspace;
446 void print(const int& ref_frame, const std::vector<double>& v_res) {
447 _ostr << ref_frame << ',';
448 for(int i = 0; i < _n_streams; ++i)
449 _ostr << v_res[i] << ',';
453 void process_colorspace(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
454 if (_colorspace == "hsi") {
455 rgb_2_hsi_tp(ref, v_ok, streams);
456 } else if (_colorspace == "ycbcr") {
457 rgb_2_YCbCr_tp(ref, v_ok, streams);
458 } else if (_colorspace == "y") {
459 rgb_2_Y_tp(ref, v_ok, streams);
463 psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
464 s_base(n_streams, i_width, i_height, ostr), _colorspace("rgb") {
467 virtual void set_parameter(const char* p_name, const char *p_value) {
468 const std::string s_name(p_name);
469 if (s_name == "colorspace") {
470 _colorspace = p_value;
471 if (_colorspace != "rgb" && _colorspace != "hsi"
472 && _colorspace != "ycbcr" && _colorspace != "y")
473 throw std::runtime_error("Invalid colorspace passed to analyzer");
477 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
478 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
479 // process colorspace
480 process_colorspace(ref, v_ok, streams);
482 std::vector<double> v_res(_n_streams);
483 get_psnr_tp(ref, v_ok, streams, v_res);
485 print(ref_frame, v_res);
489 class avg_psnr : public psnr {
493 std::vector<double> _accum_v;
495 void print(const int& ref_frame) {
496 // check if we have to print infor or not
497 if (_accum_f < _fpa) return;
499 for(int i=0; i < _n_streams; ++i)
500 _accum_v[i] /= _accum_f;
501 psnr::print(ref_frame, _accum_v);
502 // clear the vector and set _accum_f to 0
504 _accum_v.resize(_n_streams);
508 avg_psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
509 psnr(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) {
512 virtual void set_parameter(const char* p_name, const char *p_value) {
513 psnr::set_parameter(p_name, p_value);
515 const std::string s_name(p_name);
516 if (s_name == "fpa") {
517 const int fpa = atoi(p_value);
518 if (fpa > 0) _fpa = fpa;
522 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
523 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
525 _last_frame = ref_frame;
526 // process colorspace
527 process_colorspace(ref, v_ok, streams);
529 std::vector<double> v_res(_n_streams);
530 get_psnr_tp(ref, v_ok, streams, v_res);
531 // accumulate for each
532 for(int i = 0; i < _n_streams; ++i) {
534 _accum_v[i] += v_res[i];
535 } else _accum_v[i] += 0.0;
542 virtual ~avg_psnr() {
543 // on exit check if we have some data
545 _ostr << _last_frame << ',';
546 for(int i = 0; i < _n_streams; ++i) {
547 _ostr << _accum_v[i]/_accum_f << ',';
554 class ssim : public s_base {
558 void print(const int& ref_frame, const std::vector<double>& v_res) {
559 _ostr << ref_frame << ',';
560 for(int i = 0; i < _n_streams; ++i)
561 _ostr << v_res[i] << ',';
565 ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
566 s_base(n_streams, i_width, i_height, ostr), _blocksize(8) {
569 virtual void set_parameter(const char* p_name, const char *p_value) {
570 const std::string s_name(p_name);
571 if (s_name == "blocksize") {
572 const int blocksize = atoi(p_value);
573 if (blocksize > 0) _blocksize = blocksize;
577 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
578 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
579 // convert to Y colorspace
580 rgb_2_Y_tp(ref, v_ok, streams);
582 std::vector<double> v_res(_n_streams);
583 get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
585 print(ref_frame, v_res);
589 class avg_ssim : public ssim {
593 std::vector<double> _accum_v;
595 void print(const int& ref_frame) {
596 // check if we have to print infor or not
597 if (_accum_f < _fpa) return;
599 for(int i=0; i < _n_streams; ++i)
600 _accum_v[i] /= _accum_f;
601 ssim::print(ref_frame, _accum_v);
602 // clear the vector and set _accum_f to 0
604 _accum_v.resize(_n_streams);
608 avg_ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
609 ssim(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) {
612 virtual void set_parameter(const char* p_name, const char *p_value) {
613 ssim::set_parameter(p_name, p_value);
615 const std::string s_name(p_name);
616 if (s_name == "fpa") {
617 const int fpa = atoi(p_value);
618 if (fpa > 0) _fpa = fpa;
622 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
623 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
625 _last_frame = ref_frame;
626 // convert to Y colorspace
627 rgb_2_Y_tp(ref, v_ok, streams);
629 std::vector<double> v_res(_n_streams);
630 get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
631 // accumulate for each
632 for(int i = 0; i < _n_streams; ++i) {
634 _accum_v[i] += v_res[i];
635 } else _accum_v[i] += 0.0;
642 virtual ~avg_ssim() {
643 // on exit check if we have some data
645 _ostr << _last_frame << ',';
646 for(int i = 0; i < _n_streams; ++i) {
647 _ostr << _accum_v[i]/_accum_f << ',';
654 class siti : public s_base {
657 void print(const int& ref_frame, const std::vector<std::pair<double,double> >& v_res) {
658 _ostr << ref_frame << ',';
659 for(int i = 0; i < _n_streams; ++i)
660 _ostr << v_res[i].first << ',' << v_res[i].second << ",";
664 siti(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
665 s_base(n_streams, i_width, i_height, ostr){
669 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
670 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
671 // convert to Y colorspace
672 rgb_2_Y_tp(ref, v_ok, streams);
675 _prev=new std::vector<unsigned char> (streams[0]);
677 std::vector<std::pair<double,double> > v_res(_n_streams);
678 get_siti_tp(_prev, v_ok, streams, v_res, _i_width, _i_height);
680 _prev=new std::vector<unsigned char> (streams[0]);
682 print(ref_frame, v_res);
687 stats::s_base* stats::get_analyzer(const char* id, const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) {
688 const std::string s_id(id);
689 if (s_id == "psnr") return new psnr(n_streams, i_width, i_height, ostr);
690 else if (s_id == "avg_psnr") return new avg_psnr(n_streams, i_width, i_height, ostr);
691 else if (s_id == "ssim") return new ssim(n_streams, i_width, i_height, ostr);
692 else if (s_id == "avg_ssim") return new avg_ssim(n_streams, i_width, i_height, ostr);
693 else if (s_id == "siti") return new siti(n_streams, i_width, i_height, ostr);
694 throw std::runtime_error("Invalid analyzer id");