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);
163 static double compute_ti(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y){
165 int *buff_tmp = (int *)malloc(numelem*sizeof(int));
171 // difference between 2nd frame and 1st one to buff_tmp
172 for (int i=0; i<numelem; i++) {
173 buff_tmp[i] = (int)cmp[3*i]-(int)ref[3*i];
176 // find and return the standard deviation of buff_tmp
177 double sdev = std_dev(buff_tmp, numelem);
182 static inline double r_0_1(const double& d) {
183 return std::max(0.0, std::min(1.0, d));
186 static void rgb_2_hsi(unsigned char *p, const int& sz) {
189 S = 1 - ( (3/(R+G+B)) * min(R,G,B))
190 H = cos^-1( ((1/2) * ((R-G) + (R - B))) / ((R-G)^2 + (R-B)*(G-B)) ^(1/2) )
191 H = cos^-1 ( (((R-G)+(R-B))/2)/ (sqrt((R-G)^2 + (R-B)*(G-B) )))
193 const static double PI = 3.14159265;
194 for(int j =0; j < sz; j += 3) {
195 const double r = p[j+0]/255.0,
198 i = r_0_1((r+g+b)/3),
199 s = r_0_1(1.0 - (3.0/(r+g+b) * std::min(r, std::min(g, b)))),
200 h = r_0_1(acos(0.5*(r-g + r-b) / sqrt((r-g)*(r-g) + (r-b)*(g-b)))/PI);
201 p[j+0] = 255.0*h + 0.5;
202 p[j+1] = 255.0*s + 0.5;
203 p[j+2] = 255.0*i + 0.5;
207 static void rgb_2_YCbCr(unsigned char *p, const int& sz) {
208 // http://en.wikipedia.org/wiki/YCbCr
209 for(int j =0; j < sz; j += 3) {
210 const double r = p[j+0]/255.0,
213 p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
214 p[j+1] = 128 + (-37.797*r - 74.203*g + 112.0*b);
215 p[j+2] = 128 + (112.0*r - 93.786*g - 12.214*b);
219 static void rgb_2_Y(unsigned char *p, const int& sz) {
220 // http://en.wikipedia.org/wiki/YCbCr
221 for(int j =0; j < sz; j += 3) {
222 const double r = p[j+0]/255.0,
225 p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
231 static int getCPUcount(void) {
232 std::ifstream cpuinfo("/proc/cpuinfo");
234 std::set<std::string> IDs;
236 std::getline(cpuinfo,line);
239 if (line.find("processor") != 0)
243 if (IDs.empty()) return 1;
247 static mt::ThreadPool __stats_tp(getCPUcount());
249 class psnr_job : public mt::ThreadPool::Job {
250 const unsigned char *_ref,
252 const unsigned int _sz;
255 psnr_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz, double& res) :
256 _ref(ref), _cmp(cmp), _sz(sz), _res(res) {
259 virtual void run(void) {
260 _res = compute_psnr(_ref, _cmp, _sz);
264 static void get_psnr_tp(const VUCHAR& ref, const std::vector<bool>& v_ok, const std::vector<VUCHAR>& streams, std::vector<double>& res) {
265 const unsigned int sz = v_ok.size();
266 std::vector<shared_ptr<psnr_job> > v_jobs;
267 for(unsigned int i =0; i < sz; ++i) {
269 v_jobs.push_back(new psnr_job(&ref[0], &(streams[i][0]), ref.size(), res[i]));
270 __stats_tp.add(v_jobs.rbegin()->get());
274 for(std::vector<shared_ptr<psnr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
280 class ssim_job : public mt::ThreadPool::Job {
281 const unsigned char *_ref,
283 const unsigned int _x,
288 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) :
289 _ref(ref), _cmp(cmp), _x(x), _y(y), _b_sz(b_sz), _res(res) {
292 virtual void run(void) {
293 _res = compute_ssim(_ref, _cmp, _x, _y, _b_sz);
297 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) {
298 const unsigned int sz = v_ok.size();
299 std::vector<shared_ptr<ssim_job> > v_jobs;
300 for(unsigned int i =0; i < sz; ++i) {
302 v_jobs.push_back(new ssim_job(&ref[0], &(streams[i][0]), x, y, b_sz, res[i]));
303 __stats_tp.add(v_jobs.rbegin()->get());
307 for(std::vector<shared_ptr<ssim_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
313 class siti_job : public mt::ThreadPool::Job {
314 const unsigned char *_ref,
316 const unsigned int _x,
318 std::pair<double,double> &_res;
320 siti_job(const unsigned char *ref, const unsigned char *cmp, const unsigned int& x, const unsigned int& y, std::pair<double,double>& res) :
321 _ref(ref), _cmp(cmp), _x(x), _y(y), _res(res) {
324 virtual void run(void) {
325 _res.first = compute_si(_ref, _cmp, _x, _y);
326 _res.second= compute_ti(_ref, _cmp, _x, _y);
330 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) {
331 const unsigned int sz = v_ok.size();
332 std::vector<shared_ptr<siti_job> > v_jobs;
333 for(unsigned int i =0; i < sz; ++i) {
335 v_jobs.push_back(new siti_job(&((*ref)[0]), &(streams[i][0]), x, y, res[i]));
336 __stats_tp.add(v_jobs.rbegin()->get());
343 for(std::vector<shared_ptr<siti_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
349 class hsi_job : public mt::ThreadPool::Job {
351 const unsigned int _sz;
353 hsi_job(unsigned char *buf, const unsigned int& sz) :
357 virtual void run(void) {
358 rgb_2_hsi(_buf, _sz);
362 class YCbCr_job : public mt::ThreadPool::Job {
364 const unsigned int _sz;
366 YCbCr_job(unsigned char *buf, const unsigned int& sz) :
370 virtual void run(void) {
371 rgb_2_YCbCr(_buf, _sz);
375 class Y_job : public mt::ThreadPool::Job {
377 const unsigned int _sz;
379 Y_job(unsigned char *buf, const unsigned int& sz) :
383 virtual void run(void) {
388 static void rgb_2_hsi_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
389 const unsigned int sz = v_ok.size();
390 std::vector<shared_ptr<hsi_job> > v_jobs;
391 v_jobs.push_back(new hsi_job(&ref[0], ref.size()));
392 __stats_tp.add(v_jobs.rbegin()->get());
393 for(unsigned int i =0; i < sz; ++i) {
395 v_jobs.push_back(new hsi_job(&(streams[i][0]), streams[i].size()));
396 __stats_tp.add(v_jobs.rbegin()->get());
400 for(std::vector<shared_ptr<hsi_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
406 static void rgb_2_YCbCr_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
407 const unsigned int sz = v_ok.size();
408 std::vector<shared_ptr<YCbCr_job> > v_jobs;
409 v_jobs.push_back(new YCbCr_job(&ref[0], ref.size()));
410 __stats_tp.add(v_jobs.rbegin()->get());
411 for(unsigned int i =0; i < sz; ++i) {
413 v_jobs.push_back(new YCbCr_job(&(streams[i][0]), streams[i].size()));
414 __stats_tp.add(v_jobs.rbegin()->get());
418 for(std::vector<shared_ptr<YCbCr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
424 static void rgb_2_Y_tp(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
425 const unsigned int sz = v_ok.size();
426 std::vector<shared_ptr<Y_job> > v_jobs;
427 v_jobs.push_back(new Y_job(&ref[0], ref.size()));
428 __stats_tp.add(v_jobs.rbegin()->get());
429 for(unsigned int i =0; i < sz; ++i) {
431 v_jobs.push_back(new Y_job(&(streams[i][0]), streams[i].size()));
432 __stats_tp.add(v_jobs.rbegin()->get());
436 for(std::vector<shared_ptr<Y_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
442 class psnr : public s_base {
443 std::string _colorspace;
445 void print(const int& ref_frame, const std::vector<double>& v_res) {
446 _ostr << ref_frame << ',';
447 for(int i = 0; i < _n_streams; ++i)
448 _ostr << v_res[i] << ',';
452 void process_colorspace(VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
453 if (_colorspace == "hsi") {
454 rgb_2_hsi_tp(ref, v_ok, streams);
455 } else if (_colorspace == "ycbcr") {
456 rgb_2_YCbCr_tp(ref, v_ok, streams);
457 } else if (_colorspace == "y") {
458 rgb_2_Y_tp(ref, v_ok, streams);
462 psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
463 s_base(n_streams, i_width, i_height, ostr), _colorspace("rgb") {
466 virtual void set_parameter(const char* p_name, const char *p_value) {
467 const std::string s_name(p_name);
468 if (s_name == "colorspace") {
469 _colorspace = p_value;
470 if (_colorspace != "rgb" && _colorspace != "hsi"
471 && _colorspace != "ycbcr" && _colorspace != "y")
472 throw std::runtime_error("Invalid colorspace passed to analyzer");
476 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
477 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
478 // process colorspace
479 process_colorspace(ref, v_ok, streams);
481 std::vector<double> v_res(_n_streams);
482 get_psnr_tp(ref, v_ok, streams, v_res);
484 print(ref_frame, v_res);
488 class avg_psnr : public psnr {
492 std::vector<double> _accum_v;
494 void print(const int& ref_frame) {
495 // check if we have to print infor or not
496 if (_accum_f < _fpa) return;
498 for(int i=0; i < _n_streams; ++i)
499 _accum_v[i] /= _accum_f;
500 psnr::print(ref_frame, _accum_v);
501 // clear the vector and set _accum_f to 0
503 _accum_v.resize(_n_streams);
507 avg_psnr(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
508 psnr(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) {
511 virtual void set_parameter(const char* p_name, const char *p_value) {
512 psnr::set_parameter(p_name, p_value);
514 const std::string s_name(p_name);
515 if (s_name == "fpa") {
516 const int fpa = atoi(p_value);
517 if (fpa > 0) _fpa = fpa;
521 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
522 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
524 _last_frame = ref_frame;
525 // process colorspace
526 process_colorspace(ref, v_ok, streams);
528 std::vector<double> v_res(_n_streams);
529 get_psnr_tp(ref, v_ok, streams, v_res);
530 // accumulate for each
531 for(int i = 0; i < _n_streams; ++i) {
533 _accum_v[i] += v_res[i];
534 } else _accum_v[i] += 0.0;
541 virtual ~avg_psnr() {
542 // on exit check if we have some data
544 _ostr << _last_frame << ',';
545 for(int i = 0; i < _n_streams; ++i) {
546 _ostr << _accum_v[i]/_accum_f << ',';
553 class ssim : public s_base {
557 void print(const int& ref_frame, const std::vector<double>& v_res) {
558 _ostr << ref_frame << ',';
559 for(int i = 0; i < _n_streams; ++i)
560 _ostr << v_res[i] << ',';
564 ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
565 s_base(n_streams, i_width, i_height, ostr), _blocksize(8) {
568 virtual void set_parameter(const char* p_name, const char *p_value) {
569 const std::string s_name(p_name);
570 if (s_name == "blocksize") {
571 const int blocksize = atoi(p_value);
572 if (blocksize > 0) _blocksize = blocksize;
576 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
577 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
578 // convert to Y colorspace
579 rgb_2_Y_tp(ref, v_ok, streams);
581 std::vector<double> v_res(_n_streams);
582 get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
584 print(ref_frame, v_res);
588 class avg_ssim : public ssim {
592 std::vector<double> _accum_v;
594 void print(const int& ref_frame) {
595 // check if we have to print infor or not
596 if (_accum_f < _fpa) return;
598 for(int i=0; i < _n_streams; ++i)
599 _accum_v[i] /= _accum_f;
600 ssim::print(ref_frame, _accum_v);
601 // clear the vector and set _accum_f to 0
603 _accum_v.resize(_n_streams);
607 avg_ssim(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
608 ssim(n_streams, i_width, i_height, ostr), _fpa(1), _accum_f(0), _last_frame(-1), _accum_v(n_streams) {
611 virtual void set_parameter(const char* p_name, const char *p_value) {
612 ssim::set_parameter(p_name, p_value);
614 const std::string s_name(p_name);
615 if (s_name == "fpa") {
616 const int fpa = atoi(p_value);
617 if (fpa > 0) _fpa = fpa;
621 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
622 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
624 _last_frame = ref_frame;
625 // convert to Y colorspace
626 rgb_2_Y_tp(ref, v_ok, streams);
628 std::vector<double> v_res(_n_streams);
629 get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
630 // accumulate for each
631 for(int i = 0; i < _n_streams; ++i) {
633 _accum_v[i] += v_res[i];
634 } else _accum_v[i] += 0.0;
641 virtual ~avg_ssim() {
642 // on exit check if we have some data
644 _ostr << _last_frame << ',';
645 for(int i = 0; i < _n_streams; ++i) {
646 _ostr << _accum_v[i]/_accum_f << ',';
653 class siti : public s_base {
656 void print(const int& ref_frame, const std::vector<std::pair<double,double> >& v_res) {
657 _ostr << ref_frame << ',';
658 for(int i = 0; i < _n_streams; ++i)
659 _ostr << v_res[i].first << ',' << v_res[i].second << ",";
663 siti(const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) :
664 s_base(n_streams, i_width, i_height, ostr){
668 virtual void process(const int& ref_frame, VUCHAR& ref, const std::vector<bool>& v_ok, std::vector<VUCHAR>& streams) {
669 if (v_ok.size() != streams.size() || v_ok.size() != _n_streams) throw std::runtime_error("Invalid data size passed to analyzer");
670 // convert to Y colorspace
671 rgb_2_Y_tp(ref, v_ok, streams);
674 _prev=new std::vector<unsigned char> (streams[0]);
676 std::vector<std::pair<double,double> > v_res(_n_streams);
677 get_siti_tp(_prev, v_ok, streams, v_res, _i_width, _i_height);
679 _prev=new std::vector<unsigned char> (streams[0]);
681 print(ref_frame, v_res);
686 stats::s_base* stats::get_analyzer(const char* id, const int& n_streams, const int& i_width, const int& i_height, std::ostream& ostr) {
687 const std::string s_id(id);
688 if (s_id == "psnr") return new psnr(n_streams, i_width, i_height, ostr);
689 else if (s_id == "avg_psnr") return new avg_psnr(n_streams, i_width, i_height, ostr);
690 else if (s_id == "ssim") return new ssim(n_streams, i_width, i_height, ostr);
691 else if (s_id == "avg_ssim") return new avg_ssim(n_streams, i_width, i_height, ostr);
692 else if (s_id == "siti") return new siti(n_streams, i_width, i_height, ostr);
693 throw std::runtime_error("Invalid analyzer id");