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"
32 // define these classes just locally
34 static double compute_psnr(const unsigned char *ref, const unsigned char *cmp, const unsigned int& sz) {
36 for(unsigned int i = 0; i < sz; ++i) {
37 const int diff = ref[i]-cmp[i];
41 if (0.0 == mse) mse = 1e-10;
42 return 10.0*log10(65025.0/mse);
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,
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;
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
64 const unsigned char c_ref = ref[3*(base_offset + j*x + i)],
65 c_cmp = cmp[3*(base_offset + j*x + i)];
67 ref_acc_2 += (c_ref*c_ref);
69 cmp_acc_2 += (c_cmp*c_cmp);
70 ref_cmp_acc += (c_ref*c_cmp);
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);
91 for(std::vector<double>::const_iterator it = ssim_accum.begin(); it != ssim_accum.end(); ++it)
93 return avg/ssim_accum.size();
96 static inline double r_0_1(const double& d) {
97 return std::max(0.0, std::min(1.0, d));
100 static void rgb_2_hsi(unsigned char *p, const int& sz) {
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) )))
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,
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;
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,
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);
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,
139 p[j+0] = 16 + (65.481*r + 128.553*g + 24.966*b);
145 static int getCPUcount(void) {
146 std::ifstream cpuinfo("/proc/cpuinfo");
148 std::set<std::string> IDs;
150 std::getline(cpuinfo,line);
153 if (line.find("processor") != 0)
157 if (IDs.empty()) return 1;
161 static mt::ThreadPool __stats_tp(getCPUcount());
163 class psnr_job : public mt::ThreadPool::Job {
164 const unsigned char *_ref,
166 const unsigned int _sz;
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) {
173 virtual void run(void) {
174 _res = compute_psnr(_ref, _cmp, _sz);
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) {
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());
188 for(std::vector<shared_ptr<psnr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
194 class ssim_job : public mt::ThreadPool::Job {
195 const unsigned char *_ref,
197 const unsigned int _x,
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) {
206 virtual void run(void) {
207 _res = compute_ssim(_ref, _cmp, _x, _y, _b_sz);
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) {
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());
221 for(std::vector<shared_ptr<ssim_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
227 class hsi_job : public mt::ThreadPool::Job {
229 const unsigned int _sz;
231 hsi_job(unsigned char *buf, const unsigned int& sz) :
235 virtual void run(void) {
236 rgb_2_hsi(_buf, _sz);
240 class YCbCr_job : public mt::ThreadPool::Job {
242 const unsigned int _sz;
244 YCbCr_job(unsigned char *buf, const unsigned int& sz) :
248 virtual void run(void) {
249 rgb_2_YCbCr(_buf, _sz);
253 class Y_job : public mt::ThreadPool::Job {
255 const unsigned int _sz;
257 Y_job(unsigned char *buf, const unsigned int& sz) :
261 virtual void run(void) {
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) {
273 v_jobs.push_back(new hsi_job(&(streams[i][0]), streams[i].size()));
274 __stats_tp.add(v_jobs.rbegin()->get());
278 for(std::vector<shared_ptr<hsi_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
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) {
291 v_jobs.push_back(new YCbCr_job(&(streams[i][0]), streams[i].size()));
292 __stats_tp.add(v_jobs.rbegin()->get());
296 for(std::vector<shared_ptr<YCbCr_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
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) {
309 v_jobs.push_back(new Y_job(&(streams[i][0]), streams[i].size()));
310 __stats_tp.add(v_jobs.rbegin()->get());
314 for(std::vector<shared_ptr<Y_job> >::iterator it = v_jobs.begin(); it != v_jobs.end(); ++it) {
320 class psnr : public s_base {
321 std::string _colorspace;
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] << ',';
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);
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") {
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");
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);
359 std::vector<double> v_res(_n_streams);
360 get_psnr_tp(ref, v_ok, streams, v_res);
362 print(ref_frame, v_res);
366 class avg_psnr : public psnr {
370 std::vector<double> _accum_v;
372 void print(const int& ref_frame) {
373 // check if we have to print infor or not
374 if (_accum_f < _fpa) return;
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
381 _accum_v.resize(_n_streams);
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) {
389 virtual void set_parameter(const char* p_name, const char *p_value) {
390 psnr::set_parameter(p_name, p_value);
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;
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");
402 _last_frame = ref_frame;
403 // process colorspace
404 process_colorspace(ref, v_ok, streams);
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) {
411 _accum_v[i] += v_res[i];
412 } else _accum_v[i] += 0.0;
419 virtual ~avg_psnr() {
420 // on exit check if we have some data
422 _ostr << _last_frame << ',';
423 for(int i = 0; i < _n_streams; ++i) {
424 _ostr << _accum_v[i]/_accum_f << ',';
431 class ssim : public s_base {
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] << ',';
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) {
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;
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);
459 std::vector<double> v_res(_n_streams);
460 get_ssim_tp(ref, v_ok, streams, v_res, _i_width, _i_height, _blocksize);
462 print(ref_frame, v_res);
466 class avg_ssim : public ssim {
470 std::vector<double> _accum_v;
472 void print(const int& ref_frame) {
473 // check if we have to print infor or not
474 if (_accum_f < _fpa) return;
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
481 _accum_v.resize(_n_streams);
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) {
489 virtual void set_parameter(const char* p_name, const char *p_value) {
490 ssim::set_parameter(p_name, p_value);
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;
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");
502 _last_frame = ref_frame;
503 // convert to Y colorspace
504 rgb_2_Y_tp(ref, v_ok, streams);
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) {
511 _accum_v[i] += v_res[i];
512 } else _accum_v[i] += 0.0;
519 virtual ~avg_ssim() {
520 // on exit check if we have some data
522 _ostr << _last_frame << ',';
523 for(int i = 0; i < _n_streams; ++i) {
524 _ostr << _accum_v[i]/_accum_f << ',';
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");