diff --git a/modules/quality/include/opencv2/quality.hpp b/modules/quality/include/opencv2/quality.hpp index 8470f08fef..2322dcc0f2 100644 --- a/modules/quality/include/opencv2/quality.hpp +++ b/modules/quality/include/opencv2/quality.hpp @@ -11,5 +11,6 @@ #include "quality/qualityssim.hpp" #include "quality/qualitygmsd.hpp" #include "quality/qualitybrisque.hpp" +#include "quality/qualitymae.hpp" #endif \ No newline at end of file diff --git a/modules/quality/include/opencv2/quality/qualitymae.hpp b/modules/quality/include/opencv2/quality/qualitymae.hpp new file mode 100644 index 0000000000..7c967300d9 --- /dev/null +++ b/modules/quality/include/opencv2/quality/qualitymae.hpp @@ -0,0 +1,88 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#ifndef OPENCV_QUALITY_MAE_HPP +#define OPENCV_QUALITY_MAE_HPP + +#include "qualitybase.hpp" + + +namespace cv +{ + +namespace quality +{ + +/** @brief Flags to choose which algorithm MAE should use. + */ +enum MAEStatsFlags +{ + MAE_MAX, + MAE_MEAN +}; + +/** @brief This class implement two algorithm which commonly refered as MAE in the litterature. +Both definition shares the absolute error, which can be defined as: \f[ absolute\_error(x,y) = |I_{ref}(x,y) - I_{cmp}(x,y)|\f]. +The two algorithms follows the mathematic: +- **MAE_MAX** + \f[score = \fork{\texttt{absolute\_error(x,y)}}{if \(src(x,y) > score\)}{score}{otherwise}\f] +- **MAE_MEAN** + \f[score = \frac{\sum_{r=0}^{nb\_rows}\sum_{c=0}^{nb\_cols} \texttt{absolute\_error(r,c)}}{nb\_rows \times \nb\_cols}\f] +More informations about the the Mean of Absolute Error can be found here: https://en.wikipedia.org/wiki/Mean_absolute_error +*/ +class CV_EXPORTS_W QualityMAE : public QualityBase +{ +public: + /** @brief Computes MAE for reference images supplied in class constructor and provided comparison images + @param cmpImgs Comparison image(s) + @returns cv::Scalar with per-channel quality values. Values range from 0 (best) to potentially max float (worst) + */ + CV_WRAP Scalar compute( InputArray cmpImgs ) CV_OVERRIDE; + + /** @brief Implements Algorithm::empty() */ + CV_WRAP bool empty() const CV_OVERRIDE { return _ref.empty() && QualityBase::empty(); } + + /** @brief Implements Algorithm::clear() */ + CV_WRAP void clear() CV_OVERRIDE { _ref = _mat_type(); QualityBase::clear(); } + + /** + @brief Create an object which calculates quality + @param ref input image to use as the reference for comparison + @param statsProc statistical method to apply on the error + */ + CV_WRAP static Ptr create(InputArray ref, int statsProc = MAE_MEAN); + + /** + @brief static method for computing quality + @param ref reference image + @param cmp comparison image= + @param qualityMap output quality map, or cv::noArray() + @param statsProc which statistical method should be apply on the absolute error + @returns cv::Scalar with per-channel quality values. Values range from 0 (best) to max float (worst) + */ + CV_WRAP static Scalar compute( InputArray ref, InputArray cmp, OutputArray qualityMap, int statsProc = MAE_MEAN ); + + +protected: + + /** @brief Reference image, converted to internal mat type */ + QualityBase::_mat_type _ref; + + /** @brief What statistics analysis to apply on the absolute error */ + int _flag; + + /** + @brief Constructor + @param ref reference image, converted to internal type + @param statsProc statistical method to apply on the error + */ + QualityMAE(QualityBase::_mat_type ref, int statsProc); + +}; + +} // quality + +} // cv + +#endif // OPENCV_QUALITY_MAE_HPP diff --git a/modules/quality/src/qualitymae.cpp b/modules/quality/src/qualitymae.cpp new file mode 100644 index 0000000000..a5bce73079 --- /dev/null +++ b/modules/quality/src/qualitymae.cpp @@ -0,0 +1,77 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "precomp.hpp" +#include "opencv2/quality/qualitymae.hpp" +#include "opencv2/quality/quality_utils.hpp" + +namespace cv +{ + +namespace quality +{ + +using namespace quality_utils; + + +// Static +Ptr QualityMAE::create(InputArray ref, int statsProc) +{ + return Ptr(new QualityMAE(quality_utils::expand_mat<_mat_type>(ref), statsProc)); +} + +// Static +Scalar QualityMAE::compute(InputArray ref, InputArray cmp, OutputArray qualityMap, int statsProc) +{ + CV_Assert_3(ref.channels() <= 4, + cmp.channels() <= 4, + (statsProc == MAE_MAX) || (statsProc == MAE_MEAN) ); + + _mat_type err; + int wdepth = std::max(std::max(ref.depth(), cmp.depth()), CV_32F); + int cn = ref.channels(); + int wtype = CV_MAKETYPE(wdepth, cn); + + absdiff(extract_mat<_mat_type>(ref, wtype), extract_mat<_mat_type>(cmp, wtype), err); + + if(qualityMap.needed()) + qualityMap.assign(statsProc == MAE_MAX ? err : err.clone()); + + if(statsProc == MAE_MEAN) + { + return mean(err); + } + + Scalar scores; + _mat_type tmp = err.reshape(err.channels(), 1); + + reduce(tmp, tmp, 1, REDUCE_MAX, wdepth); + + tmp.convertTo(Mat(tmp.size(), CV_64FC(cn), scores.val), CV_64F); + + return scores; +} + +// Not static +Scalar QualityMAE::compute( InputArray cmpImg ) +{ + CV_Assert(cmpImg.isMat() || cmpImg.isUMat() || cmpImg.isMatx()); + + if(cmpImg.empty()) + return Scalar(); + + // If the input is a set of images. + _mat_type cmp = extract_mat<_mat_type>(cmpImg, std::max(cmpImg.depth(), CV_32F)); + + return QualityMAE::compute(this->_ref, cmp, this->_qualityMap, this->_flag); +} + +QualityMAE::QualityMAE(QualityBase::_mat_type ref, int flag) + : _ref(std::move(ref)), + _flag(flag) +{} + +} // quality + +} // cv diff --git a/modules/quality/test/test_mae.cpp b/modules/quality/test/test_mae.cpp new file mode 100644 index 0000000000..ef7532729e --- /dev/null +++ b/modules/quality/test/test_mae.cpp @@ -0,0 +1,73 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" + +#define TEST_CASE_NAME CV_Quality_MAE + +namespace opencv_test +{ +namespace quality_test +{ + +namespace +{ +const cv::Scalar + MAE_MAX_EXPECTED_1 = { 203. }, + MAE_MEAN_EXPECTED_1 = { 33.5824 }, + MAE_MAX_EXPECTED_2 = { 138., 145., 156. }, + MAE_MEAN_EXPECTED_2 = { 5.7918, 6.0645, 5.5609} + ; +} // anonymous + +// static method +TEST(TEST_CASE_NAME, static_max ) +{ + // Max + cv::Mat qMat = {}; + quality_expect_near(quality::QualityMAE::compute(get_testfile_1a(), get_testfile_1a(), qMat, quality::MAE_MAX), cv::Scalar(0.)); // ref vs ref == 0 + check_quality_map(qMat); +} + +// static method +TEST(TEST_CASE_NAME, static_mean ) +{ + // Mean + cv::Mat qMat = {}; + quality_expect_near(quality::QualityMAE::compute(get_testfile_1a(), get_testfile_1a(), qMat, quality::MAE_MEAN), cv::Scalar(0.)); // ref vs ref == 0 + check_quality_map(qMat); +} + +// single channel, with and without opencl +TEST(TEST_CASE_NAME, single_channel_max ) +{ + auto fn = []() { quality_test(quality::QualityMAE::create(get_testfile_1a(), quality::MAE_MAX), get_testfile_1b(), MAE_MAX_EXPECTED_1); }; + + OCL_OFF( fn() ); + OCL_ON( fn() ); +} + +// single channel, with and without opencl +TEST(TEST_CASE_NAME, single_channel_mean ) +{ + auto fn = []() { quality_test(quality::QualityMAE::create(get_testfile_1a(), quality::MAE_MEAN), get_testfile_1b(), MAE_MEAN_EXPECTED_1); }; + + OCL_OFF( fn() ); + OCL_ON( fn() ); +} + +// multi-channel max +TEST(TEST_CASE_NAME, multi_channel_max) +{ + quality_test(quality::QualityMAE::create(get_testfile_2a(), quality::MAE_MAX), get_testfile_2b(), MAE_MAX_EXPECTED_2); +} + +// multi-channel mean +TEST(TEST_CASE_NAME, multi_channel_mean) +{ + quality_test(quality::QualityMAE::create(get_testfile_2a(), quality::MAE_MEAN), get_testfile_2b(), MAE_MEAN_EXPECTED_2); +} + +} // namespace quality_test +} // namespace opencv_test