diff --git a/modules/quality/CMakeLists.txt b/modules/quality/CMakeLists.txt index 0c259ba86..2a66e3151 100644 --- a/modules/quality/CMakeLists.txt +++ b/modules/quality/CMakeLists.txt @@ -1,5 +1,9 @@ set(the_description "Image Quality Analysis API") ocv_define_module(quality opencv_core opencv_imgproc opencv_ml WRAP python) -ocv_add_testdata(samples/ contrib/quality - FILES_MATCHING PATTERN "*.yml" -) \ No newline at end of file + +# add test data from samples dir to contrib/quality +ocv_add_testdata(samples/ contrib/quality FILES_MATCHING PATTERN "*.yml") + +# add brisque model, range files to installation +file(GLOB QUALITY_MODEL_DATA samples/*.yml) +install(FILES ${QUALITY_MODEL_DATA} DESTINATION ${OPENCV_OTHER_INSTALL_PATH}/quality COMPONENT libs) \ No newline at end of file diff --git a/modules/quality/README.md b/modules/quality/README.md index ab4d5396b..a710eda19 100644 --- a/modules/quality/README.md +++ b/modules/quality/README.md @@ -46,14 +46,14 @@ Quick Start/Usage ```cpp #include - cv::Mat img1, img2; /* your cv::Mat images */ - std::vector quality_maps; /* output quality map(s) (optional) */ + cv::Mat img1, img2; /* your cv::Mat images to compare */ + cv::Mat quality_map; /* output quality map (optional) */ /* compute MSE via static method */ - cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_maps); /* or cv::noArray() if not interested in output quality maps */ + cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_map); /* or cv::noArray() if not interested in output quality maps */ /* alternatively, compute MSE via instance */ cv::Ptr ptr = quality::QualityMSE::create(img1); cv::Scalar result = ptr->compute( img2 ); /* compute MSE, compare img1 vs img2 */ - ptr->getQualityMaps(quality_maps); /* optionally, access output quality maps */ + ptr->getQualityMap(quality_map); /* optionally, access output quality maps */ ``` **For No Reference IQA Algorithm (BRISQUE)** @@ -81,11 +81,11 @@ model_path, range_path); img1 = cv2.imread(img1, 1) # specify img1 img2 = cv2.imread(img2_path, 1) # specify img2_path # compute MSE score and quality maps via static method - result_static, quality_maps = cv2.quality.QualityMSE_compute(img1, img2) + result_static, quality_map = cv2.quality.QualityMSE_compute(img1, img2) # compute MSE score and quality maps via Instance obj = cv2.quality.QualityMSE_create(img1) result = obj.compute(img2) - quality_maps = obj.getQualityMaps() + quality_map = obj.getQualityMap() ``` **For No Reference IQA Algorithm (BRISQUE)** @@ -94,28 +94,26 @@ model_path, range_path); import cv2 # read image img = cv2.imread(img_path, 1) # mention img_path - # make a list of image to be passed - img_list = [img] # compute brisque quality score via static method - score = cv2.quality.QualityBRISQUE_compute(img_list, model_path, + score = cv2.quality.QualityBRISQUE_compute(img, model_path, range_path) # specify model_path and range_path # compute brisque quality score via instance # specify model_path and range_path obj = cv2.quality.QualityBRISQUE_create(model_path, range_path) - score = obj.compute(img_list) + score = obj.compute(img) ``` Library Design ----------------------------------------- Each implemented algorithm shall: - Inherit from `QualityBase`, and properly implement/override `compute`, `empty` and `clear` instance methods, along with a static `compute` method. -- Accept one or more `cv::Mat` or `cv::UMat` via `InputArrayOfArrays` for computation. Each input `cv::Mat` or `cv::UMat` may contain one or more channels. If the algorithm does not support multiple channels or multiple inputs, it should be documented and an appropriate assertion should be in place. -- Return a `cv::Scalar` with per-channel computed value. If multiple input images are provided, the resulting scalar should return the average result per channel. +- Accept one `cv::Mat` or `cv::UMat` via `InputArray` for computation. Each input `cv::Mat` or `cv::UMat` may contain one or more channels. If the algorithm does not support multiple channels, it should be documented and an appropriate assertion should be in place. +- Return a `cv::Scalar` with per-channel computed value - Compute result via a single, static method named `compute` and via an overridden instance method (see `compute` in `qualitybase.hpp`). -- Perform any setup and/or pre-processing of reference images in the constructor, allowing for efficient computation when comparing the reference image(s) versus multiple comparison image(s). No-reference algorithms should accept images for evaluation in the `compute` method. -- Optionally compute resulting quality maps. Instance `compute` method should store them in `QualityBase::_qualityMaps` as the mat type defined by `QualityBase::_quality_map_type`, or override `QualityBase::getQualityMaps`. Static `compute` method should return them in an `OutputArrayOfArrays` parameter. -- Document algorithm in this readme and in its respective header. Documentation should include interpretation for the results of `compute` as well as the format of the output quality maps (if supported), along with any other notable usage information. -- Implement tests of static `compute` method and instance methods using single- and multi-channel images, multi-frame images, and OpenCL enabled and disabled +- Perform any setup and/or pre-processing of reference images in the constructor, allowing for efficient computation when comparing the reference image versus multiple comparison image(s). No-reference algorithms should accept images for evaluation in the `compute` method. +- Optionally compute resulting quality map. Instance `compute` method should store them in `QualityBase::_qualityMap` as the mat type defined by `QualityBase::_mat_type`, or override `QualityBase::getQualityMap`. Static `compute` method should return the quality map in an `OutputArray` parameter. +- Document algorithm in this readme and in its respective header. Documentation should include interpretation for the results of `compute` as well as the format of the output quality map (if supported), along with any other notable usage information. +- Implement tests of static `compute` method and instance methods using single- and multi-channel images and OpenCL enabled and disabled To Do ----------------------------------------- diff --git a/modules/quality/include/opencv2/quality/quality_utils.hpp b/modules/quality/include/opencv2/quality/quality_utils.hpp index 0a7aad83e..4b3df51a0 100644 --- a/modules/quality/include/opencv2/quality/quality_utils.hpp +++ b/modules/quality/include/opencv2/quality/quality_utils.hpp @@ -5,7 +5,6 @@ #ifndef OPENCV_QUALITY_QUALITY_UTILS_HPP #define OPENCV_QUALITY_QUALITY_UTILS_HPP -#include // numeric_limits #include "qualitybase.hpp" namespace cv @@ -18,53 +17,33 @@ namespace quality_utils // default type of matrix to expand to static CV_CONSTEXPR const int EXPANDED_MAT_DEFAULT_TYPE = CV_32F; -// convert input array to vector of specified mat types. set type == -1 to preserve existing type +// convert inputarray to specified mat type. set type == -1 to preserve existing type template -inline std::vector extract_mats( InputArrayOfArrays arr, const int type = -1 ) +inline R extract_mat(InputArray in, const int type = -1) { - std::vector result = {}; - std::vector umats = {}; - std::vector mats = {}; - - if (arr.isUMatVector()) - arr.getUMatVector(umats); - else if (arr.isUMat()) - umats.emplace_back(arr.getUMat()); - else if (arr.isMatVector()) - arr.getMatVector(mats); - else if (arr.isMat()) - mats.emplace_back(arr.getMat()); + R result = {}; + if ( in.isMat() ) + in.getMat().convertTo( result, (type != -1) ? type : in.getMat().type()); + else if ( in.isUMat() ) + in.getUMat().convertTo( result, (type != -1) ? type : in.getUMat().type()); else CV_Error(Error::StsNotImplemented, "Unsupported input type"); - // convert umats, mats to desired type - for (auto& umat : umats) - { - result.emplace_back(R{}); - umat.convertTo(result.back(), ( type != -1 ) ? type : umat.type() ); - } - - for (auto& mat : mats) - { - result.emplace_back(R{}); - mat.convertTo(result.back(), (type != -1) ? type : mat.type() ); - } - return result; } -// expand matrix to target type -template -inline OutT expand_mat(const InT& src, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE) +// extract and expand matrix to target type +template +inline R expand_mat( InputArray src, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE) { - OutT result = {}; + auto result = extract_mat(src, -1); // by default, expand to 32F unless we already have >= 32 bits, then go to 64 // if/when we can detect OpenCL CV_16F support, opt for that when input depth == 8 // note that this may impact the precision of the algorithms and would need testing int type = TYPE_DEFAULT; - switch (src.depth()) + switch (result.depth()) { case CV_32F: case CV_32S: @@ -72,40 +51,10 @@ inline OutT expand_mat(const InT& src, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_T type = CV_64F; }; // switch - src.convertTo(result, type); + result.convertTo(result, type); return result; } -// convert input array to vector of expanded mat types -template -inline std::vector expand_mats(InputArrayOfArrays arr, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE) -{ - std::vector result = {}; - - auto mats = extract_mats(arr, -1); - for (auto& mat : mats) - result.emplace_back(expand_mat(mat, TYPE_DEFAULT)); - - return result; -} - -// convert mse to psnr -inline double mse_to_psnr(double mse, double max_pixel_value) -{ - return (mse == 0.) - ? std::numeric_limits::infinity() - : 10. * std::log10((max_pixel_value * max_pixel_value) / mse) - ; -} - -// convert scalar of mses to psnrs -inline cv::Scalar mse_to_psnr(cv::Scalar mse, double max_pixel_value) -{ - for (int i = 0; i < mse.rows; ++i) - mse(i) = mse_to_psnr(mse(i), max_pixel_value); - return mse; -} - // return mat of observed min/max pair per column // row 0: min per column // row 1: max per column diff --git a/modules/quality/include/opencv2/quality/qualitybase.hpp b/modules/quality/include/opencv2/quality/qualitybase.hpp index 785da7a17..1f4887fca 100644 --- a/modules/quality/include/opencv2/quality/qualitybase.hpp +++ b/modules/quality/include/opencv2/quality/qualitybase.hpp @@ -5,7 +5,6 @@ #ifndef OPENCV_QUALITYBASE_HPP #define OPENCV_QUALITYBASE_HPP -#include #include /** @@ -21,7 +20,6 @@ namespace quality //! @{ /************************************ Quality Base Class ************************************/ - class CV_EXPORTS_W QualityBase : public virtual Algorithm { @@ -32,34 +30,31 @@ public: /** @brief Compute quality score per channel with the per-channel score in each element of the resulting cv::Scalar. See specific algorithm for interpreting result scores - @param cmpImgs comparison image(s), or image(s) to evalute for no-reference quality algorithms + @param img comparison image, or image to evalute for no-reference quality algorithms */ - virtual CV_WRAP cv::Scalar compute( InputArrayOfArrays cmpImgs ) = 0; + virtual CV_WRAP cv::Scalar compute( InputArray img ) = 0; - /** @brief Returns output quality map images that were generated during computation, if supported by the algorithm */ - virtual CV_WRAP void getQualityMaps(OutputArrayOfArrays dst) const + /** @brief Returns output quality map that was generated during computation, if supported by the algorithm */ + virtual CV_WRAP void getQualityMap(OutputArray dst) const { - if (!dst.needed() || _qualityMaps.empty() ) + if (!dst.needed() || _qualityMap.empty() ) return; - - auto qMaps = InputArray(_qualityMaps); - dst.create(qMaps.size(), qMaps.type()); - dst.assign(_qualityMaps); + dst.assign(_qualityMap); } /** @brief Implements Algorithm::clear() */ - CV_WRAP void clear() CV_OVERRIDE { _qualityMaps.clear(); Algorithm::clear(); } + CV_WRAP void clear() CV_OVERRIDE { _qualityMap = _mat_type(); Algorithm::clear(); } /** @brief Implements Algorithm::empty() */ - CV_WRAP bool empty() const CV_OVERRIDE { return _qualityMaps.empty(); } + CV_WRAP bool empty() const CV_OVERRIDE { return _qualityMap.empty(); } protected: - /** @brief internal quality map type default */ - using _quality_map_type = cv::UMat; + /** @brief internal mat type default */ + using _mat_type = cv::UMat; /** @brief Output quality maps if generated by algorithm */ - std::vector<_quality_map_type> _qualityMaps; + _mat_type _qualityMap; }; // QualityBase //! @} diff --git a/modules/quality/include/opencv2/quality/qualitybrisque.hpp b/modules/quality/include/opencv2/quality/qualitybrisque.hpp index f7529732f..9732f82d2 100644 --- a/modules/quality/include/opencv2/quality/qualitybrisque.hpp +++ b/modules/quality/include/opencv2/quality/qualitybrisque.hpp @@ -26,19 +26,18 @@ C++ code for the BRISQUE LIVE-R2 trainer and TID2008 evaluator are also provided class CV_EXPORTS_W QualityBRISQUE : public QualityBase { public: - /** @brief Computes BRISQUE quality score for input images - @param imgs Images for which to compute quality (should be passed as a vector in C++ and list of images in Python) - @returns Score (averaged over individual scores of all images) ranging from 0 to 100 - (0 denotes the best quality and 100 denotes the worst quality). The format of the score is: {score, 0., 0., 0.} + /** @brief Computes BRISQUE quality score for input image + @param img Image for which to compute quality + @returns cv::Scalar with the score in the first element. The score ranges from 0 (best quality) to 100 (worst quality) */ - CV_WRAP cv::Scalar compute( InputArrayOfArrays imgs ) CV_OVERRIDE; + CV_WRAP cv::Scalar compute( InputArray img ) CV_OVERRIDE; /** @brief Create an object which calculates quality - @param model_file_path cv::String which contains a path to the BRISQUE model data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_model_live.yml - @param range_file_path cv::String which contains a path to the BRISQUE range data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_range_live.yml + @param model_file_path cv::String which contains a path to the BRISQUE model data, eg. /path/to/brisque_model_live.yml + @param range_file_path cv::String which contains a path to the BRISQUE range data, eg. /path/to/brisque_range_live.yml */ - CV_WRAP static Ptr create( const cv::String& model_file_path = "", const cv::String& range_file_path = "" ); + CV_WRAP static Ptr create( const cv::String& model_file_path, const cv::String& range_file_path ); /** @brief Create an object which calculates quality @@ -49,12 +48,12 @@ public: /** @brief static method for computing quality - @param imgs image(s) for which to compute quality (passed as Mat or vector in C++ and as list of images in Python) - @param model_file_path cv::String which contains a path to the BRISQUE model data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_model_live.yml - @param range_file_path cv::String which contains a path to the BRISQUE range data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_range_live.yml - @returns cv::Scalar result of format {std::double score, 0., 0., 0.}. Score ranges from 0 to 100 (100 means worst and 0 means best) + @param img image for which to compute quality + @param model_file_path cv::String which contains a path to the BRISQUE model data, eg. /path/to/brisque_model_live.yml + @param range_file_path cv::String which contains a path to the BRISQUE range data, eg. /path/to/brisque_range_live.yml + @returns cv::Scalar with the score in the first element. The score ranges from 0 (best quality) to 100 (worst quality) */ - CV_WRAP static cv::Scalar compute( InputArrayOfArrays imgs, const cv::String& model_file_path, const cv::String& range_file_path ); + CV_WRAP static cv::Scalar compute( InputArray img, const cv::String& model_file_path, const cv::String& range_file_path ); /** @brief static method for computing image features used by the BRISQUE algorithm diff --git a/modules/quality/include/opencv2/quality/qualitygmsd.hpp b/modules/quality/include/opencv2/quality/qualitygmsd.hpp index 16bd25d1a..3a9cd0b69 100644 --- a/modules/quality/include/opencv2/quality/qualitygmsd.hpp +++ b/modules/quality/include/opencv2/quality/qualitygmsd.hpp @@ -22,65 +22,67 @@ public: /** @brief Compute GMSD - @param cmpImgs Comparison images - @returns Per-channel GMSD + @param cmp comparison image + @returns cv::Scalar with per-channel quality value. Values range from 0 (worst) to 1 (best) */ - CV_WRAP cv::Scalar compute(InputArrayOfArrays cmpImgs) CV_OVERRIDE; + CV_WRAP cv::Scalar compute( InputArray cmp ) CV_OVERRIDE; /** @brief Implements Algorithm::empty() */ CV_WRAP bool empty() const CV_OVERRIDE { return _refImgData.empty() && QualityBase::empty(); } /** @brief Implements Algorithm::clear() */ - CV_WRAP void clear() CV_OVERRIDE { _refImgData.clear(); QualityBase::clear(); } + CV_WRAP void clear() CV_OVERRIDE { _refImgData = _mat_data(); QualityBase::clear(); } /** @brief Create an object which calculates image quality - @param refImgs input image(s) to use as the source for comparison + @param ref reference image */ - CV_WRAP static Ptr create(InputArrayOfArrays refImgs); + CV_WRAP static Ptr create( InputArray ref ); /** @brief static method for computing quality - @param refImgs reference image(s) - @param cmpImgs comparison image(s) - @param qualityMaps output quality map(s), or cv::noArray() + @param ref reference image + @param cmp comparison image + @param qualityMap output quality map, or cv::noArray() @returns cv::Scalar with per-channel quality value. Values range from 0 (worst) to 1 (best) */ - CV_WRAP static cv::Scalar compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps); + CV_WRAP static cv::Scalar compute( InputArray ref, InputArray cmp, OutputArray qualityMap ); protected: - // holds computed values for an input mat + // holds computed values for a mat struct _mat_data { - using mat_type = QualityBase::_quality_map_type; + // internal mat type + using mat_type = QualityBase::_mat_type; mat_type gradient_map , gradient_map_squared ; + // allow default construction + _mat_data() = default; + + // construct from mat_type _mat_data(const mat_type&); - // converts mat/umat to vector of mat_data - static std::vector<_mat_data> create(InputArrayOfArrays arr); + // construct from inputarray + _mat_data(InputArray); + + // returns flag if empty + bool empty() const { return this->gradient_map.empty() && this->gradient_map_squared.empty(); } // compute for a single frame static std::pair compute(const _mat_data& lhs, const _mat_data& rhs); - // compute for vector of inputs - static cv::Scalar compute(const std::vector<_mat_data>& lhs, const std::vector<_mat_data>& rhs, OutputArrayOfArrays qualityMaps); - }; // mat_data /** @brief Reference image data */ - std::vector<_mat_data> _refImgData; + _mat_data _refImgData; - /** - @brief Constructor - @param refImgData vector of reference images, converted to internal type - */ - QualityGMSD(std::vector<_mat_data> refImgData) + // internal constructor + QualityGMSD(_mat_data refImgData) : _refImgData(std::move(refImgData)) {} diff --git a/modules/quality/include/opencv2/quality/qualitymse.hpp b/modules/quality/include/opencv2/quality/qualitymse.hpp index 1ab17fd2c..7763a58b7 100644 --- a/modules/quality/include/opencv2/quality/qualitymse.hpp +++ b/modules/quality/include/opencv2/quality/qualitymse.hpp @@ -25,37 +25,37 @@ public: CV_WRAP cv::Scalar compute( InputArrayOfArrays cmpImgs ) CV_OVERRIDE; /** @brief Implements Algorithm::empty() */ - CV_WRAP bool empty() const CV_OVERRIDE { return _refImgs.empty() && QualityBase::empty(); } + CV_WRAP bool empty() const CV_OVERRIDE { return _ref.empty() && QualityBase::empty(); } /** @brief Implements Algorithm::clear() */ - CV_WRAP void clear() CV_OVERRIDE { _refImgs.clear(); QualityBase::clear(); } + CV_WRAP void clear() CV_OVERRIDE { _ref = _mat_type(); QualityBase::clear(); } /** @brief Create an object which calculates quality - @param refImgs input image(s) to use as the source for comparison + @param ref input image to use as the reference for comparison */ - CV_WRAP static Ptr create(InputArrayOfArrays refImgs); + CV_WRAP static Ptr create(InputArray ref); /** @brief static method for computing quality - @param refImgs reference image(s) - @param cmpImgs comparison image(s) - @param qualityMaps output quality map(s), or cv::noArray() - @returns cv::Scalar with per-channel quality values. Values range from 0 (best) to potentially max float (worst) + @param ref reference image + @param cmp comparison image= + @param qualityMap output quality map, or cv::noArray() + @returns cv::Scalar with per-channel quality values. Values range from 0 (best) to max float (worst) */ - CV_WRAP static cv::Scalar compute( InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps ); + CV_WRAP static cv::Scalar compute( InputArray ref, InputArray cmp, OutputArray qualityMap ); protected: - /** @brief Reference images, converted to internal mat type */ - std::vector _refImgs; + /** @brief Reference image, converted to internal mat type */ + QualityBase::_mat_type _ref; /** @brief Constructor - @param refImgs vector of reference images, converted to internal type + @param ref reference image, converted to internal type */ - QualityMSE(std::vector refImgs) - : _refImgs(std::move(refImgs)) + QualityMSE(QualityBase::_mat_type ref) + : _ref(std::move(ref)) {} }; // QualityMSE diff --git a/modules/quality/include/opencv2/quality/qualitypsnr.hpp b/modules/quality/include/opencv2/quality/qualitypsnr.hpp index 1888b55e9..59b732550 100644 --- a/modules/quality/include/opencv2/quality/qualitypsnr.hpp +++ b/modules/quality/include/opencv2/quality/qualitypsnr.hpp @@ -5,9 +5,9 @@ #ifndef OPENCV_QUALITY_QUALITYPSNR_HPP #define OPENCV_QUALITY_QUALITYPSNR_HPP +#include // numeric_limits #include "qualitybase.hpp" #include "qualitymse.hpp" -#include "quality_utils.hpp" namespace cv { @@ -31,26 +31,25 @@ public: #endif /** - @brief Create an object which calculates quality via mean square error - @param refImgs input image(s) to use as the source for comparison + @brief Create an object which calculates quality + @param ref input image to use as the source for comparison @param maxPixelValue maximum per-channel value for any individual pixel; eg 255 for uint8 image */ - CV_WRAP static Ptr create(InputArrayOfArrays refImgs, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT ) + CV_WRAP static Ptr create( InputArray ref, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT ) { - return Ptr(new QualityPSNR(QualityMSE::create(refImgs), maxPixelValue)); + return Ptr(new QualityPSNR(QualityMSE::create(ref), maxPixelValue)); } /** - @brief compute the PSNR - @param cmpImgs Comparison images + @brief Compute the PSNR + @param cmp Comparison image @returns Per-channel PSNR value, or std::numeric_limits::infinity() if the MSE between the two images == 0 - The PSNR for multi-frame images is computed by calculating the average MSE of all frames and then generating the PSNR from that value */ - CV_WRAP cv::Scalar compute(InputArrayOfArrays cmpImgs) CV_OVERRIDE + CV_WRAP cv::Scalar compute( InputArray cmp ) CV_OVERRIDE { - auto result = _qualityMSE->compute(cmpImgs); - _qualityMSE->getQualityMaps(_qualityMaps); // copy from internal obj to this obj - return quality_utils::mse_to_psnr( + auto result = _qualityMSE->compute( cmp ); + _qualityMSE->getQualityMap(_qualityMap); // copy from internal obj to this obj + return _mse_to_psnr( result , _maxPixelValue ); @@ -64,17 +63,16 @@ public: /** @brief static method for computing quality - @param refImgs reference image(s) - @param cmpImgs comparison image(s) - @param qualityMaps output quality map(s), or cv::noArray() + @param ref reference image + @param cmp comparison image + @param qualityMap output quality map, or cv::noArray() @param maxPixelValue maximum per-channel value for any individual pixel; eg 255 for uint8 image @returns PSNR value, or std::numeric_limits::infinity() if the MSE between the two images == 0 - The PSNR for multi-frame images is computed by calculating the average MSE of all frames and then generating the PSNR from that value */ - CV_WRAP static cv::Scalar compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT) + CV_WRAP static cv::Scalar compute( InputArray ref, InputArray cmp, OutputArray qualityMap, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT) { - return quality_utils::mse_to_psnr( - QualityMSE::compute(refImgs, cmpImgs, qualityMaps) + return _mse_to_psnr( + QualityMSE::compute(ref, cmp, qualityMap) , maxPixelValue ); } @@ -99,6 +97,23 @@ protected: , _maxPixelValue(maxPixelValue) {} + // convert mse to psnr + static double _mse_to_psnr(double mse, double max_pixel_value) + { + return (mse == 0.) + ? std::numeric_limits::infinity() + : 10. * std::log10((max_pixel_value * max_pixel_value) / mse) + ; + } + + // convert scalar of mses to psnrs + static cv::Scalar _mse_to_psnr(cv::Scalar mse, double max_pixel_value) + { + for (int i = 0; i < mse.rows; ++i) + mse(i) = _mse_to_psnr(mse(i), max_pixel_value); + return mse; + } + }; // QualityPSNR } // quality } // cv diff --git a/modules/quality/include/opencv2/quality/qualityssim.hpp b/modules/quality/include/opencv2/quality/qualityssim.hpp index 9e62266a6..edbd3ae6b 100644 --- a/modules/quality/include/opencv2/quality/qualityssim.hpp +++ b/modules/quality/include/opencv2/quality/qualityssim.hpp @@ -21,38 +21,39 @@ public: /** @brief Computes SSIM - @param cmpImgs Comparison images + @param cmp Comparison image @returns cv::Scalar with per-channel quality values. Values range from 0 (worst) to 1 (best) */ - CV_WRAP cv::Scalar compute(InputArrayOfArrays cmpImgs) CV_OVERRIDE; + CV_WRAP cv::Scalar compute( InputArray cmp ) CV_OVERRIDE; /** @brief Implements Algorithm::empty() */ CV_WRAP bool empty() const CV_OVERRIDE { return _refImgData.empty() && QualityBase::empty(); } /** @brief Implements Algorithm::clear() */ - CV_WRAP void clear() CV_OVERRIDE { _refImgData.clear(); QualityBase::clear(); } + CV_WRAP void clear() CV_OVERRIDE { _refImgData = _mat_data(); QualityBase::clear(); } /** - @brief Create an object which calculates quality via mean square error - @param refImgs input image(s) to use as the source for comparison + @brief Create an object which calculates quality + @param ref input image to use as the reference image for comparison */ - CV_WRAP static Ptr create(InputArrayOfArrays refImgs); + CV_WRAP static Ptr create( InputArray ref ); /** @brief static method for computing quality - @param refImgs reference image(s) - @param cmpImgs comparison image(s) - @param qualityMaps output quality map(s), or cv::noArray() + @param ref reference image + @param cmp comparison image + @param qualityMap output quality map, or cv::noArray() @returns cv::Scalar with per-channel quality values. Values range from 0 (worst) to 1 (best) */ - CV_WRAP static cv::Scalar compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps); + CV_WRAP static cv::Scalar compute( InputArray ref, InputArray cmp, OutputArray qualityMap ); protected: // holds computed values for a mat struct _mat_data { - using mat_type = QualityBase::_quality_map_type; + // internal mat type + using mat_type = QualityBase::_mat_type; mat_type I @@ -62,28 +63,32 @@ protected: , sigma_2 ; + // allow default construction + _mat_data() = default; + + // construct from mat_type _mat_data(const mat_type&); - // construct vector of _mat_data for input mats - static std::vector<_mat_data> create(InputArrayOfArrays arr); + // construct from inputarray + _mat_data(InputArray); + + // return flag if this is empty + bool empty() const { return I.empty() && I_2.empty() && mu.empty() && mu_2.empty() && sigma_2.empty(); } // computes ssim and quality map for single frame static std::pair compute(const _mat_data& lhs, const _mat_data& rhs); - // computes mse and quality maps for multiple frames - static cv::Scalar compute(const std::vector<_mat_data>& lhs, const std::vector<_mat_data>& rhs, OutputArrayOfArrays qualityMaps); - }; // mat_data /** @brief Reference image data */ - std::vector<_mat_data> _refImgData; + _mat_data _refImgData; /** @brief Constructor - @param refImgData vector of reference images, converted to internal type + @param refImgData reference image, converted to internal type */ - QualitySSIM(std::vector<_mat_data> refImgData) - : _refImgData(std::move(refImgData)) + QualitySSIM( _mat_data refImgData ) + : _refImgData( std::move(refImgData) ) {} }; // QualitySSIM diff --git a/modules/quality/samples/brisque_trainer_livedb.cpp b/modules/quality/samples/brisque_trainer_livedb.cpp index 4df44a5ff..71caa554b 100644 --- a/modules/quality/samples/brisque_trainer_livedb.cpp +++ b/modules/quality/samples/brisque_trainer_livedb.cpp @@ -2,6 +2,7 @@ #include #include "opencv2/quality.hpp" +#include "opencv2/quality/quality_utils.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/ml.hpp" diff --git a/modules/quality/src/qualitybrisque.cpp b/modules/quality/src/qualitybrisque.cpp index 256f34e28..4c0895e9f 100644 --- a/modules/quality/src/qualitybrisque.cpp +++ b/modules/quality/src/qualitybrisque.cpp @@ -240,26 +240,6 @@ namespace result[0] = computescore(model, range, img); return result; } - - cv::Scalar compute( const cv::Ptr& model, const cv::Mat& range, std::vector& imgs) - { - CV_DbgAssert(imgs.size() > 0); - - cv::Scalar result = {}; - - const auto sz = imgs.size(); - - for (unsigned i = 0; i < sz; ++i) - { - auto cmp = compute(model, range, imgs[i]); - cv::add(result, cmp, result); - } - - if (sz > 1) - result /= (cv::Scalar::value_type)sz; // average result - - return result; - } } // static @@ -275,52 +255,26 @@ cv::Ptr QualityBRISQUE::create(const cv::Ptr& model } // static -cv::Scalar QualityBRISQUE::compute(InputArrayOfArrays imgs, const cv::String& model_file_path, const cv::String& range_file_path) +cv::Scalar QualityBRISQUE::compute( InputArray img, const cv::String& model_file_path, const cv::String& range_file_path) { - auto obj = create(model_file_path, range_file_path); - return obj->compute(imgs); + return QualityBRISQUE(model_file_path, range_file_path).compute(img); } // QualityBRISQUE() constructor QualityBRISQUE::QualityBRISQUE(const cv::String& model_file_path, const cv::String& range_file_path) + : QualityBRISQUE( + cv::ml::SVM::load(model_file_path) + , cv::FileStorage(range_file_path, cv::FileStorage::READ)["range"].mat() + ) +{} + +cv::Scalar QualityBRISQUE::compute( InputArray img ) { - // construct data file path from OPENCV_DIR env var and quality subdir - const auto get_data_path = [](const cv::String& fname) - { - cv::String path{ std::getenv("OPENCV_DIR") }; - return path.empty() - ? path // empty - : path + "/testdata/contrib/quality/" + fname - ; - }; + auto mat = quality_utils::extract_mat(img); // extract input mats - const auto - modelpath = model_file_path.empty() ? get_data_path("brisque_model_live.yml") : model_file_path - , rangepath = range_file_path.empty() ? get_data_path("brisque_range_live.yml") : range_file_path - ; + mat = mat_convert(mat);// convert to gs, scale to [0,1] - if (modelpath.empty()) - CV_Error(cv::Error::StsObjectNotFound, "BRISQUE model data not found"); - - if (rangepath.empty()) - CV_Error(cv::Error::StsObjectNotFound, "BRISQUE range data not found"); - - *this = QualityBRISQUE( - cv::ml::SVM::load(modelpath) - , cv::FileStorage(rangepath, cv::FileStorage::READ)["range"].mat() - ); -} - -cv::Scalar QualityBRISQUE::compute(InputArrayOfArrays imgs) -{ - auto vec = quality_utils::extract_mats(imgs); // extract input mats - - CV_Assert(!vec.empty()); - - for (auto& mat : vec) - mat = mat_convert(mat); // convert to gs, scale to [0,1] - - return ::compute(this->_model, this->_range, vec); + return ::compute(this->_model, this->_range, mat ); } //static diff --git a/modules/quality/src/qualitygmsd.cpp b/modules/quality/src/qualitygmsd.cpp index 752a541f9..f5a12af7b 100644 --- a/modules/quality/src/qualitygmsd.cpp +++ b/modules/quality/src/qualitygmsd.cpp @@ -123,36 +123,31 @@ QualityGMSD::_mat_data::_mat_data(const QualityGMSD::_mat_data::mat_type& mat) this->gradient_map_squared = this->gradient_map.mul(this->gradient_map); } +QualityGMSD::_mat_data::_mat_data(InputArray arr) + : _mat_data(quality_utils::expand_mat(arr))//delegate +{} + // static -Ptr QualityGMSD::create(InputArrayOfArrays refImgs) +Ptr QualityGMSD::create( InputArray ref ) { - return Ptr(new QualityGMSD( _mat_data::create( refImgs ))); + return Ptr(new QualityGMSD( _mat_data(ref))); } // static -cv::Scalar QualityGMSD::compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps) +cv::Scalar QualityGMSD::compute( InputArray ref, InputArray cmp, OutputArray qualityMap ) { - auto ref = _mat_data::create( refImgs ); - auto cmp = _mat_data::create( cmpImgs ); + auto result = _mat_data::compute( _mat_data(ref), _mat_data(cmp) ); - return _mat_data::compute(ref, cmp, qualityMaps); + if (qualityMap.needed()) + qualityMap.assign(result.second); + return result.first; } -cv::Scalar QualityGMSD::compute(InputArrayOfArrays cmpImgs) +cv::Scalar QualityGMSD::compute( InputArray cmp ) { - auto cmp = _mat_data::create(cmpImgs); - return _mat_data::compute(this->_refImgData, cmp, this->_qualityMaps); -} - -// static, converts mat/umat to vector of mat_data -std::vector QualityGMSD::_mat_data::create(InputArrayOfArrays arr) -{ - std::vector<_mat_data> result = {}; - auto mats = quality_utils::expand_mats<_mat_type>(arr); - result.reserve(mats.size()); - for (auto& mat : mats) - result.emplace_back(mat); - return result; + auto result = _mat_data::compute(this->_refImgData, _mat_data(cmp)); + OutputArray(this->_qualityMap).assign(result.second); + return result.first; } // computes gmsd and quality map for single frame @@ -180,39 +175,4 @@ std::pair QualityGMSD::_mat_data::compute(const Q result.second = std::move(qm); return result; -} // compute - -// static, computes mse and quality maps for multiple frames -cv::Scalar QualityGMSD::_mat_data::compute(const std::vector& lhs, const std::vector& rhs, OutputArrayOfArrays qualityMaps) -{ - CV_Assert(lhs.size() > 0); - CV_Assert(lhs.size() == rhs.size()); - - cv::Scalar result = {}; - std::vector<_quality_map_type> quality_maps = {}; - const auto sz = lhs.size(); - - for (unsigned i = 0; i < sz; ++i) - { - CV_Assert(!lhs.empty() && !rhs.empty()); - - auto cmp = compute(lhs[i], rhs[i]); // differs slightly when using umat vs mat - - cv::add(result, cmp.first, result); // result += cmp.first - - if (qualityMaps.needed()) - quality_maps.emplace_back(std::move(cmp.second)); - } - - if (qualityMaps.needed()) - { - auto qMaps = InputArray(quality_maps); - qualityMaps.create(qMaps.size(), qMaps.type()); - qualityMaps.assign(quality_maps); - } - - if (sz > 1) - result /= (cv::Scalar::value_type)sz; // average result - - return result; -} \ No newline at end of file +} // compute \ No newline at end of file diff --git a/modules/quality/src/qualitymse.cpp b/modules/quality/src/qualitymse.cpp index faa9d2d26..f83679504 100644 --- a/modules/quality/src/qualitymse.cpp +++ b/modules/quality/src/qualitymse.cpp @@ -28,59 +28,32 @@ namespace return result; } - - // computes mse and quality maps for multiple frames - cv::Scalar compute(const std::vector& lhs, const std::vector& rhs, OutputArrayOfArrays qualityMaps ) - { - CV_Assert(lhs.size() > 0); - CV_Assert(lhs.size() == rhs.size()); - - cv::Scalar result = {}; - std::vector<_quality_map_type> quality_maps = {}; - const auto sz = lhs.size(); - - for (unsigned i = 0; i < sz; ++i) - { - CV_Assert(!lhs.empty() && !rhs.empty()); - - auto cmp = compute(lhs[i], rhs[i]); - cv::add(result, cmp.first, result); - - if ( qualityMaps.needed() ) - quality_maps.emplace_back(std::move(cmp.second)); - } - - if (qualityMaps.needed()) - { - auto qMaps = InputArray(quality_maps); - qualityMaps.create(qMaps.size(), qMaps.type()); - qualityMaps.assign(quality_maps); - } - - if (sz > 1) - result /= (cv::Scalar::value_type)sz; // average result - - return result; - } } // static -Ptr QualityMSE::create( InputArrayOfArrays refImgs ) +Ptr QualityMSE::create( InputArray ref ) { - return Ptr(new QualityMSE(quality_utils::expand_mats(refImgs))); + return Ptr(new QualityMSE(quality_utils::expand_mat(ref))); } // static -cv::Scalar QualityMSE::compute( InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps ) +cv::Scalar QualityMSE::compute( InputArray ref_, InputArray cmp_, OutputArray qualityMap ) { - auto ref = quality_utils::expand_mats(refImgs); - auto cmp = quality_utils::expand_mats(cmpImgs); + auto ref = quality_utils::expand_mat(ref_); + auto cmp = quality_utils::expand_mat(cmp_); - return ::compute(ref, cmp, qualityMaps); + auto result = ::compute(ref, cmp); + + if (qualityMap.needed()) + qualityMap.assign(result.second); + + return result.first; } -cv::Scalar QualityMSE::compute( InputArrayOfArrays cmpImgs ) +cv::Scalar QualityMSE::compute( InputArray cmp_ ) { - auto cmp = quality_utils::expand_mats(cmpImgs); - return ::compute( this->_refImgs, cmp, this->_qualityMaps); + auto cmp = quality_utils::expand_mat(cmp_); + auto result = ::compute( this->_ref, cmp ); + OutputArray(this->_qualityMap).assign(result.second); + return result.first; } \ No newline at end of file diff --git a/modules/quality/src/qualityssim.cpp b/modules/quality/src/qualityssim.cpp index 53908780f..b8008b722 100644 --- a/modules/quality/src/qualityssim.cpp +++ b/modules/quality/src/qualityssim.cpp @@ -34,40 +34,40 @@ QualitySSIM::_mat_data::_mat_data( const _mat_type& mat ) cv::subtract(this->sigma_2, this->mu_2, this->sigma_2); } +QualitySSIM::_mat_data::_mat_data(InputArray arr ) + : _mat_data( quality_utils::expand_mat(arr) ) // delegate +{} + // static -Ptr QualitySSIM::create(InputArrayOfArrays refImgs) +Ptr QualitySSIM::create( InputArray ref ) { - return Ptr(new QualitySSIM( _mat_data::create( refImgs ))); + return Ptr(new QualitySSIM( _mat_data( ref ))); } // static -cv::Scalar QualitySSIM::compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps) +cv::Scalar QualitySSIM::compute( InputArray ref, InputArray cmp, OutputArray qualityMap ) { - auto ref = _mat_data::create( refImgs ); - auto cmp = _mat_data::create( cmpImgs ); + auto result = _mat_data::compute( _mat_data(ref), _mat_data(cmp) ); - return _mat_data::compute(ref, cmp, qualityMaps); + if (qualityMap.needed()) + qualityMap.assign(result.second); + + return result.first; } -cv::Scalar QualitySSIM::compute(InputArrayOfArrays cmpImgs) +cv::Scalar QualitySSIM::compute( InputArray cmp ) { - auto cmp = _mat_data::create(cmpImgs); - return _mat_data::compute(this->_refImgData, cmp, this->_qualityMaps); + auto result = _mat_data::compute( + this->_refImgData + , _mat_data(cmp) + ); + + OutputArray(this->_qualityMap).assign(result.second); + return result.first; } -// static. converts mat/umat to vector of mat_data -std::vector QualitySSIM::_mat_data::create(InputArrayOfArrays arr) -{ - std::vector result = {}; - auto mats = quality_utils::expand_mats<_mat_type>(arr); - result.reserve(mats.size()); - for (auto& mat : mats) - result.emplace_back(mat); - return result; -} - -// computes ssim and quality map for single frame - // based on https://docs.opencv.org/2.4/doc/tutorials/highgui/video-input-psnr-ssim/video-input-psnr-ssim.html +// static. computes ssim and quality map for single frame +// based on https://docs.opencv.org/2.4/doc/tutorials/highgui/video-input-psnr-ssim/video-input-psnr-ssim.html std::pair QualitySSIM::_mat_data::compute(const _mat_data& lhs, const _mat_data& rhs) { const double @@ -115,39 +115,4 @@ std::pair QualitySSIM::_mat_data::compute(const _mat_data cv::mean(t3) , std::move(t3) }; -} // compute - -// computes mse and quality maps for multiple frames -cv::Scalar QualitySSIM::_mat_data::compute(const std::vector<_mat_data>& lhs, const std::vector<_mat_data>& rhs, OutputArrayOfArrays qualityMaps) -{ - CV_Assert(lhs.size() > 0); - CV_Assert(lhs.size() == rhs.size()); - - Scalar result = {}; - std::vector quality_maps = {}; - const auto sz = lhs.size(); - - for (unsigned i = 0; i < sz; ++i) - { - CV_Assert(!lhs.empty() && !rhs.empty()); - - auto cmp = compute(lhs[i], rhs[i]); // differs slightly when using umat vs mat - - cv::add(result, cmp.first, result); // result += cmp.first - - if (qualityMaps.needed()) - quality_maps.emplace_back(std::move(cmp.second)); - } - - if (qualityMaps.needed()) - { - auto qMaps = InputArray(quality_maps); - qualityMaps.create(qMaps.size(), qMaps.type()); - qualityMaps.assign(quality_maps); - } - - if (sz > 1) - result /= (cv::Scalar::value_type)sz;// average result - - return result; -} \ No newline at end of file +} // compute \ No newline at end of file diff --git a/modules/quality/test/test_brisque.cpp b/modules/quality/test/test_brisque.cpp index 4c4197f06..466bec39d 100644 --- a/modules/quality/test/test_brisque.cpp +++ b/modules/quality/test/test_brisque.cpp @@ -18,14 +18,13 @@ const cv::Scalar ; // default model and range file names +// opencv tests must be installed (cmake var: INSTALL_TESTS), or BRISQUE tests will be skipped static const char* MODEL_FNAME = "brisque_model_live.yml"; static const char* RANGE_FNAME = "brisque_range_live.yml"; // instantiates a brisque object for testing inline cv::Ptr create_brisque() { - // location of BRISQUE model and range file - // place these files in ${OPENCV_TEST_DATA_PATH}/quality/, or the tests will be skipped const auto model = cvtest::findDataFile(MODEL_FNAME, false); const auto range = cvtest::findDataFile(RANGE_FNAME, false); return quality::QualityBRISQUE::create(model, range); @@ -47,7 +46,7 @@ TEST(TEST_CASE_NAME, static_ ) // single channel, instance method, with and without opencl TEST(TEST_CASE_NAME, single_channel ) { - auto fn = []() { quality_test(create_brisque(), get_testfile_1a(), BRISQUE_EXPECTED_1, 0, true ); }; + auto fn = []() { quality_test(create_brisque(), get_testfile_1a(), BRISQUE_EXPECTED_1, false, true ); }; OCL_OFF( fn() ); OCL_ON( fn() ); } @@ -55,25 +54,14 @@ TEST(TEST_CASE_NAME, single_channel ) // multi-channel TEST(TEST_CASE_NAME, multi_channel) { - quality_test(create_brisque(), get_testfile_2a(), BRISQUE_EXPECTED_2, 0, true); -} - -// multi-frame test -TEST(TEST_CASE_NAME, multi_frame) -{ - // result mse == average of all frames - cv::Scalar expected; - cv::add(BRISQUE_EXPECTED_1, BRISQUE_EXPECTED_2, expected); - expected /= 2.; - - quality_test(create_brisque(), get_testfile_1a2a(), expected, 0, true ); + quality_test(create_brisque(), get_testfile_2a(), BRISQUE_EXPECTED_2, false, true); } // check brisque model/range persistence TEST(TEST_CASE_NAME, model_persistence ) { auto ptr = create_brisque(); - auto fn = [&ptr]() { quality_test(ptr, get_testfile_1a(), BRISQUE_EXPECTED_1, 0, true); }; + auto fn = [&ptr]() { quality_test(ptr, get_testfile_1a(), BRISQUE_EXPECTED_1, false, true); }; fn(); fn(); // model/range should persist with brisque ptr through multiple invocations } diff --git a/modules/quality/test/test_gmsd.cpp b/modules/quality/test/test_gmsd.cpp index b1921a6a2..3853d06b2 100644 --- a/modules/quality/test/test_gmsd.cpp +++ b/modules/quality/test/test_gmsd.cpp @@ -11,7 +11,7 @@ namespace opencv_test namespace quality_test { -// gmsd per channel +// expected gmsd per channel const cv::Scalar GMSD_EXPECTED_1 = { .2393 } , GMSD_EXPECTED_2 = { .0942, .1016, .0995 } @@ -20,9 +20,9 @@ const cv::Scalar // static method TEST(TEST_CASE_NAME, static_) { - std::vector qMats = {}; - quality_expect_near(quality::QualityGMSD::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(0.)); // ref vs ref == 0. - EXPECT_EQ(qMats.size(), 1U ); + cv::Mat qMat = {}; + quality_expect_near(quality::QualityGMSD::compute(get_testfile_1a(), get_testfile_1a(), qMat), cv::Scalar(0.)); // ref vs ref == 0. + check_quality_map(qMat); } // single channel, with and without opencl @@ -39,17 +39,6 @@ TEST(TEST_CASE_NAME, multi_channel) quality_test(quality::QualityGMSD::create(get_testfile_2a()), get_testfile_2b(), GMSD_EXPECTED_2); } -// multi-frame test -TEST(TEST_CASE_NAME, multi_frame) -{ - // result == average of all frames - cv::Scalar expected; - cv::add(GMSD_EXPECTED_1, GMSD_EXPECTED_2, expected); - expected /= 2.; - - quality_test(quality::QualityGMSD::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2); -} - // internal A/B test /* TEST(TEST_CASE_NAME, performance) diff --git a/modules/quality/test/test_main.cpp b/modules/quality/test/test_main.cpp index f9e864fb7..f96908c1a 100644 --- a/modules/quality/test/test_main.cpp +++ b/modules/quality/test/test_main.cpp @@ -4,5 +4,6 @@ #include "test_precomp.hpp" CV_TEST_MAIN("", - cvtest::addDataSearchSubDirectory("quality") + cvtest::addDataSearchSubDirectory("contrib/quality") // for ocv_add_testdata + , cvtest::addDataSearchSubDirectory("quality") // for ${OPENCV_TEST_DATA_PATH} ) \ No newline at end of file diff --git a/modules/quality/test/test_mse.cpp b/modules/quality/test/test_mse.cpp index afe2e5957..e301edffd 100644 --- a/modules/quality/test/test_mse.cpp +++ b/modules/quality/test/test_mse.cpp @@ -14,9 +14,9 @@ namespace quality_test // static method TEST(TEST_CASE_NAME, static_ ) { - std::vector qMats = {}; - quality_expect_near(quality::QualityMSE::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(0.)); // ref vs ref == 0 - EXPECT_EQ(qMats.size(), 1U); + cv::Mat qMat = {}; + quality_expect_near(quality::QualityMSE::compute(get_testfile_1a(), get_testfile_1a(), qMat), cv::Scalar(0.)); // ref vs ref == 0 + check_quality_map(qMat); } // single channel, with and without opencl @@ -33,17 +33,6 @@ TEST(TEST_CASE_NAME, multi_channel) quality_test(quality::QualityMSE::create(get_testfile_2a()), get_testfile_2b(), MSE_EXPECTED_2); } -// multi-frame test -TEST(TEST_CASE_NAME, multi_frame) -{ - // result mse == average mse of all frames - cv::Scalar expected; - cv::add(MSE_EXPECTED_1, MSE_EXPECTED_2, expected); - expected /= 2.; - - quality_test(quality::QualityMSE::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2 ); -} - // internal a/b test /* TEST(TEST_CASE_NAME, performance) diff --git a/modules/quality/test/test_precomp.hpp b/modules/quality/test/test_precomp.hpp index 3d886965b..f8f192a17 100644 --- a/modules/quality/test/test_precomp.hpp +++ b/modules/quality/test/test_precomp.hpp @@ -43,10 +43,8 @@ inline cv::Mat get_testfile_1a() { return get_testfile(testfile1a, IMREAD_GRAYSC inline cv::Mat get_testfile_1b() { return get_testfile(testfile1b, IMREAD_GRAYSCALE); } inline cv::Mat get_testfile_2a() { return get_testfile(testfile2a); } inline cv::Mat get_testfile_2b() { return get_testfile(testfile2b); } -inline std::vector get_testfile_1a2a() { return { get_testfile_1a(), get_testfile_2a() }; } -inline std::vector get_testfile_1b2b() { return { get_testfile_1b(), get_testfile_2b() }; } -const double QUALITY_ERR_TOLERANCE = .001 // allowed margin of error +const double QUALITY_ERR_TOLERANCE = .002 // allowed margin of error ; inline void quality_expect_near( const cv::Scalar& a, const cv::Scalar& b, double err_tolerance = QUALITY_ERR_TOLERANCE) @@ -60,14 +58,29 @@ inline void quality_expect_near( const cv::Scalar& a, const cv::Scalar& b, doubl } } +template +inline void check_quality_map( const TMat& mat, const bool expect_empty = false ) +{ + EXPECT_EQ( mat.empty(), expect_empty ); + if ( !expect_empty ) + { + EXPECT_GT(mat.rows, 0); + EXPECT_GT(mat.cols, 0); + } +} + // execute quality test for a pair of images template -inline void quality_test(cv::Ptr ptr, const TMat& cmp, const Scalar& expected, const std::size_t quality_maps_expected = 1, const bool empty_expected = false ) +inline void quality_test(cv::Ptr ptr, const TMat& cmp, const Scalar& expected, const bool quality_map_expected = true, const bool empty_expected = false ) { - std::vector qMats = {}; - ptr->getQualityMaps(qMats); - EXPECT_TRUE( qMats.empty()); + cv::Mat qMat = {}; + cv::UMat qUMat = {}; + // quality map should return empty in initial state + ptr->getQualityMap(qMat); + EXPECT_TRUE( qMat.empty() ); + + // compute quality, check result quality_expect_near( expected, ptr->compute(cmp)); if (empty_expected) @@ -75,15 +88,15 @@ inline void quality_test(cv::Ptr ptr, const TMat& cmp, con else EXPECT_FALSE(ptr->empty()); - ptr->getQualityMaps(qMats); + // getQualityMap to Mat, UMat + ptr->getQualityMap(qMat); + ptr->getQualityMap(qUMat); - EXPECT_EQ( qMats.size(), quality_maps_expected); - for (auto& qm : qMats) - { - EXPECT_GT(qm.rows, 0); - EXPECT_GT(qm.cols, 0); - } + // check them + check_quality_map(qMat, !quality_map_expected); + check_quality_map(qUMat, !quality_map_expected); + // reset algorithm, should now be empty ptr->clear(); EXPECT_TRUE(ptr->empty()); } diff --git a/modules/quality/test/test_psnr.cpp b/modules/quality/test/test_psnr.cpp index ddd45c38d..0ab3a80df 100644 --- a/modules/quality/test/test_psnr.cpp +++ b/modules/quality/test/test_psnr.cpp @@ -19,9 +19,9 @@ const cv::Scalar // static method TEST(TEST_CASE_NAME, static_) { - std::vector qMats = {}; - quality_expect_near(quality::QualityPSNR::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(INFINITY,INFINITY,INFINITY,INFINITY)); // ref vs ref == inf - EXPECT_EQ(qMats.size(), 1U); + cv::Mat qMat = {}; + quality_expect_near(quality::QualityPSNR::compute(get_testfile_1a(), get_testfile_1a(), qMat), cv::Scalar(INFINITY, INFINITY, INFINITY, INFINITY)); // ref vs ref == inf + check_quality_map(qMat); } // single channel, with/without opencl @@ -38,18 +38,6 @@ TEST(TEST_CASE_NAME, multi_channel) quality_test(quality::QualityPSNR::create(get_testfile_2a()), get_testfile_2b(), PSNR_EXPECTED_2); } -// multi-frame test -TEST(TEST_CASE_NAME, multi_frame) -{ - cv::Scalar expected; - cv::add(MSE_EXPECTED_1, MSE_EXPECTED_2, expected); - expected /= 2.; - - expected = quality::quality_utils::mse_to_psnr(expected, quality::QualityPSNR::MAX_PIXEL_VALUE_DEFAULT ); - - quality_test(quality::QualityPSNR::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2); -} - // internal a/b test /* TEST(TEST_CASE_NAME, performance) diff --git a/modules/quality/test/test_ssim.cpp b/modules/quality/test/test_ssim.cpp index d23650bcf..f02c16752 100644 --- a/modules/quality/test/test_ssim.cpp +++ b/modules/quality/test/test_ssim.cpp @@ -11,7 +11,7 @@ namespace opencv_test namespace quality_test { -// ssim per channel +// expected ssim per channel const cv::Scalar SSIM_EXPECTED_1 = { .1501 } , SSIM_EXPECTED_2 = { .7541, .7742, .8095 } @@ -20,9 +20,9 @@ const cv::Scalar // static method TEST(TEST_CASE_NAME, static_) { - std::vector qMats = {}; - quality_expect_near(quality::QualitySSIM::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(1.)); // ref vs ref == 1. - EXPECT_EQ(qMats.size(), 1U ); + cv::Mat qMat = {}; + quality_expect_near(quality::QualitySSIM::compute(get_testfile_1a(), get_testfile_1a(), qMat), cv::Scalar(1.)); // ref vs ref == 1. + check_quality_map(qMat); } // single channel, with/without opencl @@ -39,17 +39,6 @@ TEST(TEST_CASE_NAME, multi_channel) quality_test(quality::QualitySSIM::create(get_testfile_2a()), get_testfile_2b(), SSIM_EXPECTED_2); } -// multi-frame test -TEST(TEST_CASE_NAME, multi_frame) -{ - // result == average of all frames - cv::Scalar expected; - cv::add(SSIM_EXPECTED_1, SSIM_EXPECTED_2, expected); - expected /= 2.; - - quality_test(quality::QualitySSIM::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2U ); -} - // internal a/b test /* TEST(TEST_CASE_NAME, performance)