vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
set(the_description "Image Quality Analysis API")
ocv_define_module(quality opencv_core opencv_imgproc opencv_ml WRAP python)
# 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)
+123
View File
@@ -0,0 +1,123 @@
//! @addtogroup quality
//! @{
Quality API, Image Quality Analysis
=======================================
Implementation of various image quality analysis (IQA) algorithms
- **Mean squared error (MSE)**
https://en.wikipedia.org/wiki/Mean_squared_error
- **Peak signal-to-noise ratio (PSNR)**
https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
- **Structural similarity (SSIM)**
https://en.wikipedia.org/wiki/Structural_similarity
- **Gradient Magnitude Similarity Deviation (GMSD)**
http://www4.comp.polyu.edu.hk/~cslzhang/IQA/GMSD/GMSD.htm
In general, the GMSD algorithm should yield the best result for full-reference IQA.
- **Blind/Referenceless Image Spatial Quality Evaluation (BRISQUE)**
http://live.ece.utexas.edu/research/Quality/nrqa.htm
Interface/Usage
-----------------------------------------
All algorithms can be accessed through the simpler static `compute` methods,
or be accessed by instance created via the static `create` methods.
Instance methods are designed to be more performant when comparing one source
file against multiple comparison files, as the algorithm-specific preprocessing on the
source file need not be repeated with each call.
For performance reaasons, it is recommended, but not required, for users of this module
to convert input images to grayscale images prior to processing.
SSIM and GMSD were originally tested by their respective researchers on grayscale uint8 images,
but this implementation will compute the values for each channel if the user desires to do so.
BRISQUE is a NR-IQA algorithm (No-Reference) which doesn't require a reference image.
Quick Start/Usage
-----------------------------------------
**C++ Implementations**
**For Full Reference IQA Algorithms (MSE, PSNR, SSIM, GMSD)**
```cpp
#include <opencv2/quality.hpp>
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_map); /* or cv::noArray() if not interested in output quality maps */
/* alternatively, compute MSE via instance */
cv::Ptr<quality::QualityBase> ptr = quality::QualityMSE::create(img1);
cv::Scalar result = ptr->compute( img2 ); /* compute MSE, compare img1 vs img2 */
ptr->getQualityMap(quality_map); /* optionally, access output quality maps */
```
**For No Reference IQA Algorithm (BRISQUE)**
```cpp
#include <opencv2/quality.hpp>
cv::Mat img = cv::imread("/path/to/my_image.bmp"); // path to the image to evaluate
cv::String model_path = "path/to/brisque_model_live.yml"; // path to the trained model
cv::String range_path = "path/to/brisque_range_live.yml"; // path to range file
/* compute BRISQUE quality score via static method */
cv::Scalar result_static = quality::QualityBRISQUE::compute(img,
model_path, range_path);
/* alternatively, compute BRISQUE via instance */
cv::Ptr<quality::QualityBase> ptr = quality::QualityBRISQUE::create(model_path, range_path);
cv::Scalar result = ptr->compute(img); /* computes BRISQUE score for img */
```
**Python Implementations**
**For Full Reference IQA Algorithms (MSE, PSNR, SSIM, GSMD)**
```python
import cv2
# read images
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_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_map = obj.getQualityMap()
```
**For No Reference IQA Algorithm (BRISQUE)**
```python
import cv2
# read image
img = cv2.imread(img_path, 1) # mention img_path
# compute brisque quality score via static method
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)
```
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 `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 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
-----------------------------------------
- Document the output quality maps for each algorithm
- Investigate precision loss with cv::Filter2D + UMat + CV_32F + OCL for GMSD
//! @}
+34
View File
@@ -0,0 +1,34 @@
@article{Mittal2,
title={No-Reference Image Quality Assessment in the Spatial Domain},
author={A. {Mittal} and A. K. {Moorthy} and A. C. {Bovik}},
journal={IEEE Transactions on Image Processing},
volume={21},
number={12},
pages={4695-4708},
year={2012},
ISSN={1057-7149},
doi={10.1109/TIP.2012.2214050},
}
@misc{Mittal2_software,
title={BRISQUE Software Release},
author={A. {Mittal} and A. K. {Moorthy} and A. C. {Bovik}},
howpublished={\url{http://live.ece.utexas.edu/research/quality/BRISQUE_release.zip}},
year={2011},
}
@article{Ponomarenko,
title={TID2008 - A Database for Evaluation of Full-Reference Visual Quality Assessment Metrics},
author={N. {Ponomarenko}, V. {Lukin}, A. {Zelensky}, K. {Egiazarian}, M. {Carli}, F. {Battisti}},
journal={Advances of Modern Radioelectronics},
volume={10},
pages={30-45},
year={2009},
}
@misc{Sheikh,
title={LIVE Image Quality Assessment Database Release 2},
author={H.R. {Sheikh}, Z. {Wang}, L. {Cormack} and A.C. {Bovik}},
howpublished={\url{http://live.ece.utexas.edu/research/quality}},
year={2005},
}
@@ -0,0 +1,15 @@
// 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_HPP
#define OPENCV_QUALITY_HPP
#include "quality/qualitybase.hpp"
#include "quality/qualitymse.hpp"
#include "quality/qualitypsnr.hpp"
#include "quality/qualityssim.hpp"
#include "quality/qualitygmsd.hpp"
#include "quality/qualitybrisque.hpp"
#endif
@@ -0,0 +1,109 @@
// 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_QUALITY_UTILS_HPP
#define OPENCV_QUALITY_QUALITY_UTILS_HPP
#include "qualitybase.hpp"
namespace cv
{
namespace quality
{
namespace quality_utils
{
// default type of matrix to expand to
static CV_CONSTEXPR const int EXPANDED_MAT_DEFAULT_TYPE = CV_32F;
// convert inputarray to specified mat type. set type == -1 to preserve existing type
template <typename R>
inline R extract_mat(InputArray in, const int type = -1)
{
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");
return result;
}
// extract and expand matrix to target type
template <typename R>
inline R expand_mat( InputArray src, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE)
{
auto result = extract_mat<R>(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 (result.depth())
{
case CV_32F:
case CV_32S:
case CV_64F:
type = CV_64F;
}; // switch
result.convertTo(result, type);
return result;
}
// return mat of observed min/max pair per column
// row 0: min per column
// row 1: max per column
// template <typename T>
inline cv::Mat get_column_range( const cv::Mat& data )
{
CV_Assert(data.channels() == 1);
CV_Assert(data.rows > 0);
cv::Mat result( cv::Size( data.cols, 2 ), data.type() );
auto
row_min = result.row(0)
, row_max = result.row(1)
;
// set initial min/max
data.row(0).copyTo(row_min);
data.row(0).copyTo(row_max);
for (int y = 1; y < data.rows; ++y)
{
auto row = data.row(y);
cv::min(row,row_min, row_min);
cv::max(row, row_max, row_max);
}
return result;
} // get_column_range
// linear scale of each column from min to max
// range is column-wise pair of observed min/max. See get_column_range
template <typename T>
inline void scale( cv::Mat& mat, const cv::Mat& range, const T min, const T max )
{
// value = lower + (upper - lower) * (value - feature_min[index]) / (feature_max[index] - feature_min[index]);
// where [lower] = lower bound, [upper] = upper bound
for (int y = 0; y < mat.rows; ++y)
{
auto row = mat.row(y);
auto row_min = range.row(0);
auto row_max = range.row(1);
for (int x = 0; x < mat.cols; ++x)
row.at<T>(x) = min + (max - min) * (row.at<T>(x) - row_min.at<T>(x) ) / (row_max.at<T>(x) - row_min.at<T>(x));
}
}
} // quality_utils
} // quality
} // cv
#endif
@@ -0,0 +1,63 @@
// 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_QUALITYBASE_HPP
#define OPENCV_QUALITYBASE_HPP
#include <opencv2/core.hpp>
/**
@defgroup quality Image Quality Analysis (IQA) API
*/
namespace cv
{
namespace quality
{
//! @addtogroup quality
//! @{
/************************************ Quality Base Class ************************************/
class CV_EXPORTS_W QualityBase
: public virtual Algorithm
{
public:
/** @brief Destructor */
virtual ~QualityBase() = default;
/**
@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 img comparison image, or image to evalute for no-reference quality algorithms
*/
virtual CV_WRAP cv::Scalar compute( InputArray img ) = 0;
/** @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() || _qualityMap.empty() )
return;
dst.assign(_qualityMap);
}
/** @brief Implements 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 _qualityMap.empty(); }
protected:
/** @brief internal mat type default */
using _mat_type = cv::UMat;
/** @brief Output quality maps if generated by algorithm */
_mat_type _qualityMap;
}; // QualityBase
//! @}
} // quality
} // cv
#endif
@@ -0,0 +1,82 @@
// 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_QUALITYBRISQUE_HPP
#define OPENCV_QUALITY_QUALITYBRISQUE_HPP
#include "qualitybase.hpp"
#include "opencv2/ml.hpp"
namespace cv
{
namespace quality
{
/**
@brief BRISQUE (Blind/Referenceless Image Spatial Quality Evaluator) is a No Reference Image Quality Assessment (NR-IQA) algorithm.
BRISQUE computes a score based on extracting Natural Scene Statistics (https://en.wikipedia.org/wiki/Scene_statistics)
and calculating feature vectors. See Mittal et al. @cite Mittal2 for original paper and original implementation @cite Mittal2_software .
A trained model is provided in the /samples/ directory and is trained on the LIVE-R2 database @cite Sheikh as in the original implementation.
When evaluated against the TID2008 database @cite Ponomarenko , the SROCC is -0.8424 versus the SROCC of -0.8354 in the original implementation.
C++ code for the BRISQUE LIVE-R2 trainer and TID2008 evaluator are also provided in the /samples/ directory.
*/
class CV_EXPORTS_W QualityBRISQUE : public QualityBase {
public:
/** @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( 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, 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<QualityBRISQUE> create( const cv::String& model_file_path, const cv::String& range_file_path );
/**
@brief Create an object which calculates quality
@param model cv::Ptr<cv::ml::SVM> which contains a loaded BRISQUE model
@param range cv::Mat which contains BRISQUE range data
*/
CV_WRAP static Ptr<QualityBRISQUE> create( const cv::Ptr<cv::ml::SVM>& model, const cv::Mat& range );
/**
@brief static method for computing quality
@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( 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
@param img image (BGR(A) or grayscale) for which to compute features
@param features output row vector of features to cv::Mat or cv::UMat
*/
CV_WRAP static void computeFeatures(InputArray img, OutputArray features);
protected:
cv::Ptr<cv::ml::SVM> _model = nullptr;
cv::Mat _range;
/** @brief Internal constructor */
QualityBRISQUE( const cv::String& model_file_path, const cv::String& range_file_path );
/** @brief Internal constructor */
QualityBRISQUE(const cv::Ptr<cv::ml::SVM>& model, const cv::Mat& range )
: _model{ model }
, _range{ range }
{}
}; // QualityBRISQUE
} // quality
} // cv
#endif
@@ -0,0 +1,92 @@
// 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_QUALITYGMSD_HPP
#define OPENCV_QUALITY_QUALITYGMSD_HPP
#include "qualitybase.hpp"
namespace cv
{
namespace quality
{
/**
@brief Full reference GMSD algorithm
http://www4.comp.polyu.edu.hk/~cslzhang/IQA/GMSD/GMSD.htm
*/
class CV_EXPORTS_W QualityGMSD
: public QualityBase {
public:
/**
@brief Compute 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( 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 = _mat_data(); QualityBase::clear(); }
/**
@brief Create an object which calculates image quality
@param ref reference image
*/
CV_WRAP static Ptr<QualityGMSD> create( InputArray ref );
/**
@brief static method for computing quality
@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( InputArray ref, InputArray cmp, OutputArray qualityMap );
protected:
// holds computed values for a mat
struct _mat_data
{
// 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&);
// 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<cv::Scalar, mat_type> compute(const _mat_data& lhs, const _mat_data& rhs);
}; // mat_data
/** @brief Reference image data */
_mat_data _refImgData;
// internal constructor
QualityGMSD(_mat_data refImgData)
: _refImgData(std::move(refImgData))
{}
}; // QualityGMSD
} // quality
} // cv
#endif
@@ -0,0 +1,64 @@
// 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_QUALITYMSE_HPP
#define OPENCV_QUALITY_QUALITYMSE_HPP
#include "qualitybase.hpp"
namespace cv
{
namespace quality
{
/**
@brief Full reference mean square error algorithm https://en.wikipedia.org/wiki/Mean_squared_error
*/
class CV_EXPORTS_W QualityMSE : public QualityBase {
public:
/** @brief Computes MSE 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 cv::Scalar compute( InputArrayOfArrays 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
*/
CV_WRAP static Ptr<QualityMSE> create(InputArray ref);
/**
@brief static method for computing quality
@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( InputArray ref, InputArray cmp, OutputArray qualityMap );
protected:
/** @brief Reference image, converted to internal mat type */
QualityBase::_mat_type _ref;
/**
@brief Constructor
@param ref reference image, converted to internal type
*/
QualityMSE(QualityBase::_mat_type ref)
: _ref(std::move(ref))
{}
}; // QualityMSE
} // quality
} // cv
#endif
@@ -0,0 +1,120 @@
// 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_QUALITYPSNR_HPP
#define OPENCV_QUALITY_QUALITYPSNR_HPP
#include <limits> // numeric_limits
#include "qualitybase.hpp"
#include "qualitymse.hpp"
namespace cv
{
namespace quality
{
/**
@brief Full reference peak signal to noise ratio (PSNR) algorithm https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
*/
class CV_EXPORTS_W QualityPSNR
: public QualityBase {
public:
/** @brief Default maximum pixel value */
#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900/*MSVS 2015*/)
static constexpr double MAX_PIXEL_VALUE_DEFAULT = 255.;
#else
// support MSVS 2013
static const int MAX_PIXEL_VALUE_DEFAULT = 255;
#endif
/**
@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<QualityPSNR> create( InputArray ref, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT )
{
return Ptr<QualityPSNR>(new QualityPSNR(QualityMSE::create(ref), maxPixelValue));
}
/**
@brief Compute the PSNR
@param cmp Comparison image
@returns Per-channel PSNR value, or std::numeric_limits<double>::infinity() if the MSE between the two images == 0
*/
CV_WRAP cv::Scalar compute( InputArray cmp ) CV_OVERRIDE
{
auto result = _qualityMSE->compute( cmp );
_qualityMSE->getQualityMap(_qualityMap); // copy from internal obj to this obj
return _mse_to_psnr(
result
, _maxPixelValue
);
}
/** @brief Implements Algorithm::empty() */
CV_WRAP bool empty() const CV_OVERRIDE { return _qualityMSE->empty() && QualityBase::empty(); }
/** @brief Implements Algorithm::clear() */
CV_WRAP void clear() CV_OVERRIDE { _qualityMSE->clear(); QualityBase::clear(); }
/**
@brief static method for computing quality
@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<double>::infinity() if the MSE between the two images == 0
*/
CV_WRAP static cv::Scalar compute( InputArray ref, InputArray cmp, OutputArray qualityMap, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT)
{
return _mse_to_psnr(
QualityMSE::compute(ref, cmp, qualityMap)
, maxPixelValue
);
}
/** @brief return the maximum pixel value used for PSNR computation */
CV_WRAP double getMaxPixelValue() const { return _maxPixelValue; }
/**
@brief sets the maximum pixel value used for PSNR computation
@param val Maximum pixel value
*/
CV_WRAP void setMaxPixelValue(double val) { this->_maxPixelValue = val; }
protected:
Ptr<QualityMSE> _qualityMSE;
double _maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT;
/** @brief Constructor */
QualityPSNR( Ptr<QualityMSE> qualityMSE, double maxPixelValue )
: _qualityMSE(std::move(qualityMSE))
, _maxPixelValue(maxPixelValue)
{}
// convert mse to psnr
static double _mse_to_psnr(double mse, double max_pixel_value)
{
return (mse == 0.)
? std::numeric_limits<double>::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
#endif
@@ -0,0 +1,97 @@
// 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_QUALITYSSIM_HPP
#define OPENCV_QUALITY_QUALITYSSIM_HPP
#include "qualitybase.hpp"
namespace cv
{
namespace quality
{
/**
@brief Full reference structural similarity algorithm https://en.wikipedia.org/wiki/Structural_similarity
*/
class CV_EXPORTS_W QualitySSIM
: public QualityBase {
public:
/**
@brief Computes SSIM
@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( 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 = _mat_data(); QualityBase::clear(); }
/**
@brief Create an object which calculates quality
@param ref input image to use as the reference image for comparison
*/
CV_WRAP static Ptr<QualitySSIM> create( InputArray ref );
/**
@brief static method for computing quality
@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( InputArray ref, InputArray cmp, OutputArray qualityMap );
protected:
// holds computed values for a mat
struct _mat_data
{
// internal mat type
using mat_type = QualityBase::_mat_type;
mat_type
I
, I_2
, mu
, mu_2
, sigma_2
;
// allow default construction
_mat_data() = default;
// construct from mat_type
_mat_data(const mat_type&);
// 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<cv::Scalar, mat_type> compute(const _mat_data& lhs, const _mat_data& rhs);
}; // mat_data
/** @brief Reference image data */
_mat_data _refImgData;
/**
@brief Constructor
@param refImgData reference image, converted to internal type
*/
QualitySSIM( _mat_data refImgData )
: _refImgData( std::move(refImgData) )
{}
}; // QualitySSIM
} // quality
} // cv
#endif
@@ -0,0 +1,243 @@
#include <fstream>
#include "opencv2/quality.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/ml.hpp"
/*
BRISQUE evaluator using TID2008
TID2008:
http://www.ponomarenko.info/tid2008.htm
[1] N. Ponomarenko, V. Lukin, A. Zelensky, K. Egiazarian, M. Carli,
F. Battisti, "TID2008 - A Database for Evaluation of Full-Reference
Visual Quality Assessment Metrics", Advances of Modern
Radioelectronics, Vol. 10, pp. 30-45, 2009.
[2] N. Ponomarenko, F. Battisti, K. Egiazarian, J. Astola, V. Lukin
"Metrics performance comparison for color image database", Fourth
international workshop on video processing and quality metrics
for consumer electronics, Scottsdale, Arizona, USA. Jan. 14-16, 2009, 6 p.
*/
namespace {
// get ordinal ranks of data, fractional ranks assigned for ties. O(n^2) time complexity
// optional binary predicate used for rank ordering of data elements, equality evaluation
template <typename T, typename PrEqual = std::equal_to<T>, typename PrLess = std::less<T>>
std::vector<float> rank_ordinal(const T* data, std::size_t sz, PrEqual&& eq = {}, PrLess&& lt = {})
{
std::vector<float> result{};
result.resize(sz, -1);// set all ranks to -1, indicating not yet done
int rank = 0;
while (rank < (int)sz)
{
std::vector<int> els = {};
for (int i = 0; i < (int)sz; ++i)
{
if (result[i] < 0)//not yet done
{
if (!els.empty())// already found something
{
if (lt(data[i], data[els[0]]))//found a smaller item, replace existing
{
els.clear();
els.emplace_back(i);
}
else if (eq(data[i], data[els[0]]))// found a tie, add to vector
els.emplace_back(i);
}
else//els.empty==no current item, add it
els.emplace_back(i);
}
}
CV_Assert(!els.empty());
// compute, assign arithmetic mean
const auto assigned_rank = (double)rank + (double)(els.size() - 1) / 2.;
for (auto el : els)
result[el] = (float)assigned_rank;
rank += (int)els.size();
}
return result;
}
template <typename T>
double pearson(const T* x, const T* y, std::size_t sz)
{
// based on https://www.geeksforgeeks.org/program-spearmans-rank-correlation/
double sigma_x = {}, sigma_y = {}, sigma_xy = {}, sigma_xsq = {}, sigma_ysq = {};
for (unsigned i = 0; i < sz; ++i)
{
sigma_x += x[i];
sigma_y += y[i];
sigma_xy += x[i] * y[i];
sigma_xsq += x[i] * x[i];
sigma_ysq += y[i] * y[i];
}
const double
num = (sz * sigma_xy - sigma_x * sigma_y)
, den = std::sqrt(((double)sz*sigma_xsq - sigma_x * sigma_x) * ((double)sz*sigma_ysq - sigma_y * sigma_y))
;
return num / den;
}
// https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient
template <typename T>
double spearman(const T* x, const T* y, std::size_t sz)
{
// convert x, y to ranked integral vectors
const auto
x_rank = rank_ordinal(x, sz)
, y_rank = rank_ordinal(y, sz)
;
return pearson(x_rank.data(), y_rank.data(), sz);
}
// returns cv::Mat of columns: { Distortion Type ID, MOS_Score, Brisque_Score }
cv::Mat tid2008_eval(const std::string& root, cv::quality::QualityBRISQUE& alg)
{
const std::string
mos_with_names_path = root + "mos_with_names.txt"
, dist_imgs_root = root + "distorted_images/"
;
cv::Mat result(0, 3, CV_32FC1);
// distortion types we care about
static const std::vector<int> distortion_types = {
10 // jpeg compression
, 11 // jp2k compression
, 1 // additive gaussian noise
, 8 // gaussian blur
};
static const int
num_images = 25 // [I01_ - I25_], file names
, num_distortions = 4 // num distortions per image
;
// load mos_with_names. format: { mos, fname }
std::vector<std::pair<float, std::string>> mos_with_names = {};
std::ifstream mos_file(mos_with_names_path, std::ios::in);
while (true)
{
std::string line;
std::getline(mos_file, line);
if (!line.empty())
{
const auto space_pos = line.find(' ');
CV_Assert(space_pos != line.npos);
mos_with_names.emplace_back(std::make_pair(
(float)std::atof(line.substr(0, space_pos).c_str())
, line.substr(space_pos + 1)
));
}
if (mos_file.peek() == EOF)
break;
};
// foreach image
// foreach distortion type
// foreach distortion level
// distortion type id, mos value, brisque value
for (int i = 0; i < num_images; ++i)
{
for (int ty = 0; ty < (int)distortion_types.size(); ++ty)
{
for (int dist = 1; dist <= num_distortions; ++dist)
{
float mos_val = 0.f;
const std::string img_name = std::string("i")
+ (((i + 1) < 10) ? "0" : "")
+ std::to_string(i + 1)
+ "_"
+ ((distortion_types[ty] < 10) ? "0" : "")
+ std::to_string(distortion_types[ty])
+ "_"
+ std::to_string(dist)
+ ".bmp";
// find mos
bool found = false;
for (const auto& val : mos_with_names)
{
if (val.second == img_name)
{
found = true;
mos_val = val.first;
break;
}
}
CV_Assert(found);
// do brisque
auto img = cv::imread(dist_imgs_root + img_name);
// typeid, mos, brisque
cv::Mat row(1, 3, CV_32FC1);
row.at<float>(0) = (float)distortion_types[ty];
row.at<float>(1) = mos_val;
row.at<float>(2) = (float)alg.compute(img)[0];
result.push_back(row);
}// dist
}//ty
}//i
return result;
}
}
inline void printHelp()
{
using namespace std;
cout << " Demo of comparing BRISQUE quality assessment model against TID2008 database." << endl;
cout << " A. Mittal, A. K. Moorthy and A. C. Bovik, 'No Reference Image Quality Assessment in the Spatial Domain'" << std::endl << std::endl;
cout << " Usage: program <tid2008_path> <brisque_model_path> <brisque_range_path>" << endl << endl;
}
int main(int argc, const char * argv[])
{
using namespace cv::ml;
if (argc != 4)
{
printHelp();
exit(1);
}
std::cout << "Evaluating database at " << argv[1] << "..." << std::endl;
const auto ptr = cv::quality::QualityBRISQUE::create(argv[2], argv[3]);
const auto data = tid2008_eval( std::string( argv[1] ) + "/", *ptr );
// create contiguous mats
const auto mos = data.col(1).clone();
const auto brisque = data.col(2).clone();
// calc srocc
const auto cc = spearman((const float*)mos.data, (const float*)brisque.data, data.rows);
std::cout << "SROCC: " << cc << std::endl;
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
%YAML:1.0
---
range: !!opencv-matrix
rows: 2
cols: 36
dt: f
data: [ 3.44000012e-01, 1.92631185e-02, 2.31999993e-01,
-1.25608176e-01, 1.54766443e-04, 5.36677078e-04, 2.47999996e-01,
-1.25662684e-01, 1.56631286e-04, 5.32896898e-04, 2.64999986e-01,
-1.37013525e-01, 1.69135848e-04, 3.88529879e-04, 2.68999994e-01,
-1.45002097e-01, 1.74277433e-04, 4.11326590e-04, 4.09000009e-01,
1.65343825e-02, 2.17999995e-01, -2.00738415e-01, 1.03299266e-04,
8.17875145e-04, 2.28000000e-01, -1.98958635e-01, 1.15834941e-04,
8.49922828e-04, 2.46000007e-01, -1.55001476e-01, 1.20401361e-04,
3.38587241e-04, 2.47999996e-01, -1.48134664e-01, 1.16321200e-04,
3.34327371e-04, 10., 8.07274520e-01, 1.64100003e+00,
2.02751741e-01, 7.14265108e-01, 4.68011886e-01, 1.63699996e+00,
1.79955900e-01, 7.12509930e-01, 4.68246639e-01, 1.54499996e+00,
1.01060480e-01, 6.86503410e-01, 5.31757474e-01, 1.54900002e+00,
1.00678936e-01, 6.87403798e-01, 5.33775926e-01, 3.73600006e+00,
8.01105976e-01, 1.10699999e+00, 1.75127238e-01, 7.52403796e-01,
4.00098890e-01, 1.09300005e+00, 1.56139076e-01, 7.52328634e-01,
4.06460851e-01, 1.04900002e+00, 9.35277343e-02, 6.23002231e-01,
5.31899512e-01, 1.05200005e+00, 9.37106311e-02, 6.25087202e-01,
5.38609207e-01 ]
@@ -0,0 +1,176 @@
#include <sstream>
#include <iostream>
#include "opencv2/quality.hpp"
#include "opencv2/quality/quality_utils.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/ml.hpp"
/*
BRISQUE Trainer using LIVE DB R2
http://live.ece.utexas.edu/research/Quality/subjective.htm
H.R. Sheikh, Z.Wang, L. Cormack and A.C. Bovik, "LIVE Image Quality Assessment Database Release 2", http://live.ece.utexas.edu/research/quality .
H.R. Sheikh, M.F. Sabir and A.C. Bovik, "A statistical evaluation of recent full reference image quality assessment algorithms", IEEE Transactions on Image Processing, vol. 15, no. 11, pp. 3440-3451, Nov. 2006.
Z. Wang, A.C. Bovik, H.R. Sheikh and E.P. Simoncelli, "Image quality assessment: from error visibility to structural similarity," IEEE Transactions on Image Processing , vol.13, no.4, pp. 600- 612, April 2004.
*/
/*
Copyright (c) 2011 The University of Texas at Austin
All rights reserved.
Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy,
modify, and distribute this code (the source files) and its documentation for
any purpose, provided that the copyright notice in its entirety appear in all copies of this code, and the
original source of this code, Laboratory for Image and Video Engineering (LIVE, http://live.ece.utexas.edu)
and Center for Perceptual Systems (CPS, http://www.cps.utexas.edu) at the University of Texas at Austin (UT Austin,
http://www.utexas.edu), is acknowledged in any publication that reports research using this code. The research
is to be cited in the bibliography as:
1) A. Mittal, A. K. Moorthy and A. C. Bovik, "BRISQUE Software Release",
URL: http://live.ece.utexas.edu/research/quality/BRISQUE_release.zip, 2011
2) A. Mittal, A. K. Moorthy and A. C. Bovik, "No Reference Image Quality Assessment in the Spatial Domain"
submitted
IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT AUSTIN BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS DATABASE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF TEXAS
AT AUSTIN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF TEXAS AT AUSTIN SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE DATABASE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THE UNIVERSITY OF TEXAS AT AUSTIN HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/
/* Original Paper: @cite Mittal2 and Original Implementation: @cite Mittal2_software */
namespace {
#define CATEGORIES 5
#define IMAGENUM 982
#define JP2KNUM 227
#define JPEGNUM 233
#define WNNUM 174
#define GBLURNUM 174
#define FFNUM 174
// collects training data from LIVE R2 database
// returns {features, responses}, 1 row per image
std::pair<cv::Mat, cv::Mat> collect_data_live_r2(const std::string& foldername)
{
FILE* fid = nullptr;
//----------------------------------------------------
// class is the distortion category, there are 982 images in LIVE database
std::vector<std::string> distortionlabels;
distortionlabels.push_back("jp2k");
distortionlabels.push_back("jpeg");
distortionlabels.push_back("wn");
distortionlabels.push_back("gblur");
distortionlabels.push_back("fastfading");
int imnumber[5] = { 0,227,460,634,808 };
std::vector<int>categorylabels;
categorylabels.insert(categorylabels.end(), JP2KNUM, 0);
categorylabels.insert(categorylabels.end(), JPEGNUM, 1);
categorylabels.insert(categorylabels.end(), WNNUM, 2);
categorylabels.insert(categorylabels.end(), GBLURNUM, 3);
categorylabels.insert(categorylabels.end(), FFNUM, 4);
int iforg[IMAGENUM];
fid = fopen((foldername + "orgs.txt").c_str(), "r");
for (int itr = 0; itr < IMAGENUM; itr++)
CV_Assert( fscanf(fid, "%d", iforg + itr) > 0);
fclose(fid);
float dmosscores[IMAGENUM];
fid = fopen((foldername + "dmos.txt").c_str(), "r");
for (int itr = 0; itr < IMAGENUM; itr++)
CV_Assert( fscanf(fid, "%f", dmosscores + itr) > 0 );
fclose(fid);
// features vector, 1 row per image
cv::Mat features(0, 0, CV_32FC1);
// response vector, 1 row per image
cv::Mat responses(0, 1, CV_32FC1);
for (int itr = 0; itr < IMAGENUM; itr++)
{
//Dont compute features for original images
if (iforg[itr])
continue;
// append dmos score
float score = dmosscores[itr];
responses.push_back(cv::Mat(1, 1, CV_32FC1, (void*)&score));
// load image, calc features
std::string imname = "";
imname.append(foldername);
imname.append("/");
imname.append(distortionlabels[categorylabels[itr]].c_str());
imname.append("/img");
imname += std::to_string((itr - imnumber[categorylabels[itr]] + 1));
imname.append(".bmp");
cv::Mat im_features;
cv::quality::QualityBRISQUE::computeFeatures(cv::imread(imname), im_features); // outputs a row vector
features.push_back(im_features.row(0)); // append row vector
}
return std::make_pair(std::move(features), std::move(responses));
} // collect_data_live_r2
}
inline void printHelp()
{
using namespace std;
cout << " Demo of training BRISQUE quality assessment model using LIVE R2 database." << endl;
cout << " A. Mittal, A. K. Moorthy and A. C. Bovik, 'No Reference Image Quality Assessment in the Spatial Domain'" << std::endl << std::endl;
cout << " Usage: program <live_r2_db_path> <output_model_path> <output_range_path>" << endl << endl;
}
int main(int argc, const char * argv[])
{
using namespace cv::ml;
if (argc != 4)
{
printHelp();
exit(1);
}
std::cout << "Training BRISQUE on database at " << argv[1] << "..." << std::endl;
// collect data from the data set
auto data = collect_data_live_r2( std::string( argv[1] ) + "/" );
// extract column ranges for features
const auto range = cv::quality::quality_utils::get_column_range(data.first);
// scale all features from -1 to 1
cv::quality::quality_utils::scale<float>(data.first, range, -1.f, 1.f);
// do training, output train file
// libsvm call from original BRISQUE impl: svm-train -s 3 -g 0.05 -c 1024 -b 1 -q train_scale allmodel
auto svm = SVM::create();
svm->setType(SVM::Types::EPS_SVR);
svm->setKernel(SVM::KernelTypes::RBF);
svm->setGamma(0.05);
svm->setC(1024.);
svm->setTermCriteria(cv::TermCriteria(cv::TermCriteria::Type::EPS, 1000, 0.001));
svm->setP(.1);// default p (epsilon) from libsvm
svm->train(data.first, cv::ml::ROW_SAMPLE, data.second);
svm->save( argv[2] ); // save to location specified in argv[2]
// output scale file to argv[3]
cv::Mat range_mat(range);
cv::FileStorage fs(argv[3], cv::FileStorage::WRITE );
fs << "range" << range_mat;
return 0;
}
+8
View File
@@ -0,0 +1,8 @@
// 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_PRECOMP_HPP
#define OPENCV_QUALITY_PRECOMP_HPP
#include <opencv2/core.hpp>
#include "opencv2/quality/qualitybase.hpp"
#endif
+302
View File
@@ -0,0 +1,302 @@
// 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.
/*
Copyright (c) 2011 The University of Texas at Austin
All rights reserved.
Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy,
modify, and distribute this code (the source files) and its documentation for
any purpose, provided that the copyright notice in its entirety appear in all copies of this code, and the
original source of this code, Laboratory for Image and Video Engineering (LIVE, http://live.ece.utexas.edu)
and Center for Perceptual Systems (CPS, http://www.cps.utexas.edu) at the University of Texas at Austin (UT Austin,
http://www.utexas.edu), is acknowledged in any publication that reports research using this code. The research
is to be cited in the bibliography as:
1) A. Mittal, A. K. Moorthy and A. C. Bovik, "BRISQUE Software Release",
URL: http://live.ece.utexas.edu/research/quality/BRISQUE_release.zip, 2011
2) A. Mittal, A. K. Moorthy and A. C. Bovik, "No Reference Image Quality Assessment in the Spatial Domain"
submitted
IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT AUSTIN BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS DATABASE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF TEXAS
AT AUSTIN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF TEXAS AT AUSTIN SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE DATABASE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THE UNIVERSITY OF TEXAS AT AUSTIN HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/
/* Original Paper: @cite Mittal2 and Original Implementation: @cite Mittal2_software */
#include "precomp.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/quality/qualitybrisque.hpp"
#include "opencv2/quality/quality_utils.hpp"
namespace
{
using namespace cv;
using namespace cv::quality;
// type of mat we're working with internally
// Win32+UMat: performance is 15-20X worse than Mat
// Win32+UMat+OCL: performance is 200-300X worse than Mat, plus accuracy errors
// Linux+UMat: 15X worse performance than Linux+Mat
using brisque_mat_type = cv::Mat;
// brisque intermediate calculation type
// Linux+Mat: CV_64F is 3X slower than CV_32F
// Win32+Mat: CV_64F is 2X slower than CV_32F
static constexpr const int BRISQUE_CALC_MAT_TYPE = CV_32F;
// brisque intermediate matrix element type. float if BRISQUE_CALC_MAT_TYPE == CV_32F, double if BRISQUE_CALC_MAT_TYPE == CV_64F
using brisque_calc_element_type = float;
// convert mat to grayscale, range [0-1]
brisque_mat_type mat_convert( const brisque_mat_type& mat )
{
brisque_mat_type result = mat;
switch (mat.channels())
{
case 1:
break;
case 3:
cv::cvtColor(result, result, cv::COLOR_BGR2GRAY, 1);
break;
case 4:
cv::cvtColor(result, result, cv::COLOR_BGRA2GRAY, 1);
break;
default:
CV_Error(cv::Error::StsNotImplemented, "Unknown/unsupported channel count");
};//switch
// scale to 0-1 range
result.convertTo(result, BRISQUE_CALC_MAT_TYPE, 1. / 255.);
return result;
}
// function to compute best fit parameters from AGGDfit
void AGGDfit(const brisque_mat_type& structdis, double& lsigma_best, double& rsigma_best, double& gamma_best)
{
long int poscount = 0, negcount = 0;
double possqsum = 0, negsqsum = 0, abssum = 0;
for (int i = 0; i < structdis.rows; i++)
{
for (int j = 0; j < structdis.cols; j++)
{
double pt = structdis.at<brisque_calc_element_type>(i, j);
if (pt > 0)
{
poscount++;
possqsum += pt * pt;
abssum += pt;
}
else if (pt < 0)
{
negcount++;
negsqsum += pt * pt;
abssum -= pt;
}
}
}
lsigma_best = cv::pow(negsqsum / negcount, 0.5);
rsigma_best = cv::pow(possqsum / poscount, 0.5);
double gammahat = lsigma_best / rsigma_best;
long int totalcount = (structdis.cols)*(structdis.rows);
double rhat = cv::pow(abssum / totalcount, static_cast<double>(2)) / ((negsqsum + possqsum) / totalcount);
double rhatnorm = rhat * (cv::pow(gammahat, 3) + 1)*(gammahat + 1) / pow(pow(gammahat, 2) + 1, 2);
double prevgamma = 0;
double prevdiff = 1e10;
double sampling = 0.001;
for (double gam = 0.2; gam < 10; gam += sampling) //possible to coarsen sampling to quicken the code, with some loss of accuracy
{
double r_gam = tgamma(2 / gam)*tgamma(2 / gam) / (tgamma(1 / gam)*tgamma(3 / gam));
double diff = abs(r_gam - rhatnorm);
if (diff > prevdiff) break;
prevdiff = diff;
prevgamma = gam;
}
gamma_best = prevgamma;
// return structdis.clone();
}
std::vector<brisque_calc_element_type> ComputeBrisqueFeature( const brisque_mat_type& orig )
{
CV_DbgAssert(orig.channels() == 1);
std::vector<brisque_calc_element_type> featurevector;
auto orig_bw = orig;
// orig_bw now contains the grayscale image normalized to the range 0,1
int scalenum = 2; // number of times to scale the image
for (int itr_scale = 1; itr_scale <= scalenum; itr_scale++)
{
// resize image
cv::Size dst_size( int( orig_bw.cols / cv::pow((double)2, itr_scale - 1) ), int( orig_bw.rows / pow((double)2, itr_scale - 1)));
brisque_mat_type imdist_scaled;
cv::resize(orig_bw, imdist_scaled, dst_size, 0, 0, cv::INTER_CUBIC); // INTER_CUBIC
// calculating MSCN coefficients
// compute mu (local mean)
brisque_mat_type mu;// (imdist_scaled.size(), CV_64FC1, 1);
cv::GaussianBlur(imdist_scaled, mu, cv::Size(7, 7), 7. / 6., 0., cv::BORDER_REPLICATE );
brisque_mat_type mu_sq;
cv::pow(mu, double(2.0), mu_sq);
//compute sigma (local sigma)
brisque_mat_type sigma;// (imdist_scaled.size(), CV_64FC1, 1);
cv::multiply(imdist_scaled, imdist_scaled, sigma);
cv::GaussianBlur(sigma, sigma, cv::Size(7, 7), 7./6., 0., cv::BORDER_REPLICATE );
cv::subtract(sigma, mu_sq, sigma);
cv::pow(sigma, double(0.5), sigma);
cv::add(sigma, Scalar(1.0 / 255), sigma); // to avoid DivideByZero Error
brisque_mat_type structdis;// (imdist_scaled.size(), CV_64FC1, 1);
cv::subtract(imdist_scaled, mu, structdis);
cv::divide(structdis, sigma, structdis); // structdis is MSCN image
// Compute AGGD fit to MSCN image
double lsigma_best, rsigma_best, gamma_best;
//structdis = AGGDfit(structdis, lsigma_best, rsigma_best, gamma_best);
AGGDfit(structdis, lsigma_best, rsigma_best, gamma_best);
featurevector.push_back( (brisque_calc_element_type) gamma_best);
featurevector.push_back(( (brisque_calc_element_type)( lsigma_best*lsigma_best + rsigma_best * rsigma_best) / 2 ));
// Compute paired product images
// indices for orientations (H, V, D1, D2)
int shifts[4][2] = { {0,1},{1,0},{1,1},{-1,1} };
for (int itr_shift = 1; itr_shift <= 4; itr_shift++)
{
// select the shifting index from the 2D array
int* reqshift = shifts[itr_shift - 1];
// declare, create shifted_structdis as pairwise image
brisque_mat_type shifted_structdis(imdist_scaled.size(), BRISQUE_CALC_MAT_TYPE); //(imdist_scaled.size(), CV_64FC1, 1);
// create pair-wise product for the given orientation (reqshift)
for (int i = 0; i < structdis.rows; i++)
{
for (int j = 0; j < structdis.cols; j++)
{
if (i + reqshift[0] >= 0 && i + reqshift[0] < structdis.rows && j + reqshift[1] >= 0 && j + reqshift[1] < structdis.cols)
{
shifted_structdis.at<brisque_calc_element_type>(i,j) = structdis.at<brisque_calc_element_type>(i + reqshift[0], j + reqshift[1]);
}
else
{
shifted_structdis.at<brisque_calc_element_type>(i, j) = (brisque_calc_element_type) 0;
}
}
}
// calculate the products of the pairs
cv::multiply(structdis, shifted_structdis, shifted_structdis);
// fit the pairwise product to AGGD
// shifted_structdis = AGGDfit(shifted_structdis, lsigma_best, rsigma_best, gamma_best);
AGGDfit(shifted_structdis, lsigma_best, rsigma_best, gamma_best);
double constant = sqrt(tgamma(1 / gamma_best)) / sqrt(tgamma(3 / gamma_best));
double meanparam = (rsigma_best - lsigma_best)*(tgamma(2 / gamma_best) / tgamma(1 / gamma_best))*constant;
// push the calculated parameters from AGGD fit to pair-wise products
featurevector.push_back((brisque_calc_element_type)gamma_best);
featurevector.push_back((brisque_calc_element_type)meanparam);
featurevector.push_back( (brisque_calc_element_type) cv::pow(lsigma_best, 2));
featurevector.push_back( (brisque_calc_element_type) cv::pow(rsigma_best, 2));
}
}
return featurevector;
}
brisque_calc_element_type computescore(const cv::Ptr<cv::ml::SVM>& model, const cv::Mat& range, const brisque_mat_type& img ) {
const auto brisqueFeatures = ComputeBrisqueFeature( img ); // compute brisque features
cv::Mat feat_mat( 1,(int)brisqueFeatures.size(), CV_32FC1, (void*)brisqueFeatures.data() ); // load to mat
quality_utils::scale(feat_mat, range, -1.f, 1.f);// scale to range [-1,1]
cv::Mat result;
model->predict(feat_mat, result);
return std::min( std::max( result.at<float>(0), 0.f ), 100.f ); // clamp to [0-100]
}
// computes score for a single frame
cv::Scalar compute(const cv::Ptr<cv::ml::SVM>& model, const cv::Mat& range, const brisque_mat_type& img)
{
auto result = cv::Scalar{ 0. };
result[0] = computescore(model, range, img);
return result;
}
}
// static
cv::Ptr<QualityBRISQUE> QualityBRISQUE::create(const cv::String& model_file_path, const cv::String& range_file_path)
{
return cv::Ptr<QualityBRISQUE>(new QualityBRISQUE(model_file_path, range_file_path));
}
// static
cv::Ptr<QualityBRISQUE> QualityBRISQUE::create(const cv::Ptr<cv::ml::SVM>& model, const cv::Mat& range)
{
return cv::Ptr<QualityBRISQUE>(new QualityBRISQUE(model, range));
}
// static
cv::Scalar QualityBRISQUE::compute( InputArray img, const cv::String& model_file_path, const cv::String& range_file_path)
{
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 )
{
auto mat = quality_utils::extract_mat<brisque_mat_type>(img); // extract input mats
mat = mat_convert(mat);// convert to gs, scale to [0,1]
return ::compute(this->_model, this->_range, mat );
}
//static
void QualityBRISQUE::computeFeatures(InputArray img, OutputArray features)
{
CV_Assert(features.needed());
CV_Assert(img.isMat());
CV_Assert(!img.getMat().empty());
auto mat = mat_convert(img.getMat());
const auto vals = ComputeBrisqueFeature(mat);
cv::Mat valmat( cv::Size( (int)vals.size(), 1 ), CV_32FC1, (void*)vals.data()); // create row vector, type depends on brisque_calc_element_type
if (features.isUMat())
valmat.copyTo(features.getUMatRef());
else if (features.isMat())
// how to move data instead?
// if calling this:
// features.getMatRef() = valmat;
// then shared data is erased when valmat is released, corrupting the data in the outputarray for the caller
valmat.copyTo(features.getMatRef());
else
CV_Error(cv::Error::StsNotImplemented, "Unsupported output type");
}
+178
View File
@@ -0,0 +1,178 @@
// 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/qualitygmsd.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/imgproc.hpp" // blur, resize
#include "opencv2/quality/quality_utils.hpp"
namespace
{
using namespace cv;
using namespace cv::quality;
using _mat_type = cv::UMat;// match QualityGMSD::_mat_data::mat_type
using _quality_map_type = _mat_type;
template <typename SrcMat, typename DstMat>
void filter_2D(const SrcMat& src, DstMat& dst, cv::InputArray kernel, cv::Point anchor, double delta, int border_type )
{
cv::filter2D(src, dst, src.depth(), kernel, anchor, delta, border_type);
}
// At the time of this writing (OpenCV 4.0.1) cv::Filter2D with OpenCL+UMat/32F suffers from precision loss large enough
// to warrant conversion prior to application of Filter2D
template <typename DstMat>
void filter_2D( const UMat& src, DstMat& dst, cv::InputArray kernel, cv::Point anchor, double delta, int border_type )
{
if ( !cv::ocl::useOpenCL() || src.depth() == CV_64F) // nothing more to do
return filter_2D<UMat, DstMat>(src, dst, kernel, anchor, delta, border_type);
auto dst_type = dst.type() == 0 ? src.type() : dst.type();
// UMat conversion to 64F
UMat src_converted = {};
src.convertTo(src_converted, CV_64F);
dst.convertTo(dst, CV_64F);
filter_2D<UMat, DstMat>(src_converted, dst, kernel, anchor, delta, border_type);
dst.convertTo(dst, dst_type);
}
// conv2, based on https://stackoverflow.com/a/12540358
enum ConvolutionType {
/* Return the full convolution, including border */
CONVOLUTION_FULL,
/* Return only the part that corresponds to the original image */
CONVOLUTION_SAME,
/* Return only the submatrix containing elements that were not influenced by the border */
CONVOLUTION_VALID
};
template <typename MatSrc, typename MatDst, typename TKernel>
void conv2(const MatSrc& img, MatDst& dest, const TKernel& kernel, ConvolutionType type ) {
auto source = img;
TKernel kernel_flipped = {};
cv::flip(kernel, kernel_flipped, -1);
if (CONVOLUTION_FULL == type) {
source = MatSrc();
const int additionalRows = kernel.rows - 1, additionalCols = kernel.cols - 1;
cv::copyMakeBorder(img, source, (additionalRows + 1) / 2, additionalRows / 2,
(additionalCols + 1) / 2, additionalCols / 2, BORDER_CONSTANT, Scalar(0));
}
cv::Point anchor(kernel.cols - kernel.cols / 2 - 1, kernel.rows - kernel.rows / 2 - 1);
// cv::filter2D(source, dest, img.depth(), kernel_flipped, anchor, 0, BORDER_CONSTANT );
filter_2D(source, dest, kernel_flipped, anchor, 0, BORDER_CONSTANT);
if (CONVOLUTION_VALID == type) {
dest = dest.colRange((kernel.cols - 1) / 2, dest.cols - kernel.cols / 2)
.rowRange((kernel.rows - 1) / 2, dest.rows - kernel.rows / 2);
}
}
} // ns
// construct mat_data from _mat_type
QualityGMSD::_mat_data::_mat_data(const QualityGMSD::_mat_data::mat_type& mat)
{
CV_Assert(!mat.empty());
// 2x2 avg kernel
_mat_type
tmp1 = {}
, tmp = {}
;
cv::blur(mat, tmp1, cv::Size(2, 2), cv::Point(0, 0), BORDER_CONSTANT);
// 2x2 downsample
// bug/hack:
// modules\core\src\matrix.cpp:169: error: (-215:Assertion failed) u->refcount == 0 in function 'cv::StdMatAllocator::deallocate'
// when src==dst and using UMat, useOpenCL=false
// workaround: use 2 temp vars instead of 1 so that src != dst
// todo: fix after https://github.com/opencv/opencv/issues/13577 solved
cv::resize(tmp1, tmp, cv::Size(), .5, .5, INTER_NEAREST);
// prewitt conv2
static const cv::Matx33d
prewitt_y = { 1. / 3., 1. / 3., 1. / 3., 0., 0., 0., -1. / 3., -1. / 3., -1. / 3. }
, prewitt_x = { 1. / 3., 0., -1. / 3., 1. / 3., 0., -1. / 3.,1. / 3., 0., -1. / 3. }
;
// prewitt y on tmp ==> this->gradient_map
::conv2(tmp, this->gradient_map, prewitt_y, ::ConvolutionType::CONVOLUTION_SAME);
// prewitt x on tmp ==> tmp
::conv2(tmp, tmp, prewitt_x, ::ConvolutionType::CONVOLUTION_SAME);
// calc gradient map, sqrt( px ^ 2 + py ^ 2 )
cv::multiply(this->gradient_map, this->gradient_map, this->gradient_map); // square gradient map
cv::multiply(tmp, tmp, tmp); // square temp
cv::add(this->gradient_map, tmp, this->gradient_map); // add together
cv::sqrt(this->gradient_map, this->gradient_map);// get sqrt
// calc gradient map squared
this->gradient_map_squared = this->gradient_map.mul(this->gradient_map);
}
QualityGMSD::_mat_data::_mat_data(InputArray arr)
: _mat_data(quality_utils::expand_mat<mat_type>(arr))//delegate
{}
// static
Ptr<QualityGMSD> QualityGMSD::create( InputArray ref )
{
return Ptr<QualityGMSD>(new QualityGMSD( _mat_data(ref)));
}
// static
cv::Scalar QualityGMSD::compute( InputArray ref, InputArray cmp, OutputArray qualityMap )
{
auto result = _mat_data::compute( _mat_data(ref), _mat_data(cmp) );
if (qualityMap.needed())
qualityMap.assign(result.second);
return result.first;
}
cv::Scalar QualityGMSD::compute( InputArray cmp )
{
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
std::pair<cv::Scalar, _quality_map_type> QualityGMSD::_mat_data::compute(const QualityGMSD::_mat_data& lhs, const QualityGMSD::_mat_data& rhs)
{
static const double T = 170.;
std::pair<cv::Scalar, _quality_map_type> result;
// compute quality_map = (2 * gm1 .* gm2 + T) ./ (gm1 .^2 + gm2 .^2 + T);
_mat_type num
, denom
, qm
;
cv::multiply(lhs.gradient_map, rhs.gradient_map, num);
cv::multiply(num, 2., num);
cv::add(num, T, num);
cv::add(lhs.gradient_map_squared, rhs.gradient_map_squared, denom);
cv::add(denom, T, denom);
cv::divide(num, denom, qm);
cv::meanStdDev(qm, cv::noArray(), result.first);
result.second = std::move(qm);
return result;
} // compute
+59
View File
@@ -0,0 +1,59 @@
// 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/qualitymse.hpp"
#include "opencv2/quality/quality_utils.hpp"
namespace
{
using namespace cv;
using namespace cv::quality;
using mse_mat_type = UMat;
using _quality_map_type = mse_mat_type;
// computes mse and quality map for single frame
std::pair<cv::Scalar, _quality_map_type> compute(const mse_mat_type& lhs, const mse_mat_type& rhs)
{
std::pair<cv::Scalar, _quality_map_type> result;
cv::subtract( lhs, rhs, result.second );
// cv::pow(diff, 2., diff);
cv::multiply(result.second, result.second, result.second); // slightly faster than pow2
result.first = cv::mean(result.second);
return result;
}
}
// static
Ptr<QualityMSE> QualityMSE::create( InputArray ref )
{
return Ptr<QualityMSE>(new QualityMSE(quality_utils::expand_mat<mse_mat_type>(ref)));
}
// static
cv::Scalar QualityMSE::compute( InputArray ref_, InputArray cmp_, OutputArray qualityMap )
{
auto ref = quality_utils::expand_mat<mse_mat_type>(ref_);
auto cmp = quality_utils::expand_mat<mse_mat_type>(cmp_);
auto result = ::compute(ref, cmp);
if (qualityMap.needed())
qualityMap.assign(result.second);
return result.first;
}
cv::Scalar QualityMSE::compute( InputArray cmp_ )
{
auto cmp = quality_utils::expand_mat<mse_mat_type>(cmp_);
auto result = ::compute( this->_ref, cmp );
OutputArray(this->_qualityMap).assign(result.second);
return result.first;
}
+118
View File
@@ -0,0 +1,118 @@
// 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/qualityssim.hpp"
#include "opencv2/imgproc.hpp" // GaussianBlur
#include "opencv2/quality/quality_utils.hpp"
namespace
{
using namespace cv;
using namespace cv::quality;
using _mat_type = UMat;
using _quality_map_type = _mat_type;
// SSIM blur function
_mat_type blur(const _mat_type& mat)
{
_mat_type result = {};
cv::GaussianBlur( mat, result, cv::Size(11, 11), 1.5 );
return result;
}
} // ns
QualitySSIM::_mat_data::_mat_data( const _mat_type& mat )
{
this->I = mat;
cv::multiply(this->I, this->I, this->I_2);
this->mu = ::blur(this->I);
cv::multiply(this->mu, this->mu, this->mu_2);
this->sigma_2 = ::blur(this->I_2); // blur the squared img, subtract blurred_squared
cv::subtract(this->sigma_2, this->mu_2, this->sigma_2);
}
QualitySSIM::_mat_data::_mat_data(InputArray arr )
: _mat_data( quality_utils::expand_mat<mat_type>(arr) ) // delegate
{}
// static
Ptr<QualitySSIM> QualitySSIM::create( InputArray ref )
{
return Ptr<QualitySSIM>(new QualitySSIM( _mat_data( ref )));
}
// static
cv::Scalar QualitySSIM::compute( InputArray ref, InputArray cmp, OutputArray qualityMap )
{
auto result = _mat_data::compute( _mat_data(ref), _mat_data(cmp) );
if (qualityMap.needed())
qualityMap.assign(result.second);
return result.first;
}
cv::Scalar QualitySSIM::compute( InputArray cmp )
{
auto result = _mat_data::compute(
this->_refImgData
, _mat_data(cmp)
);
OutputArray(this->_qualityMap).assign(result.second);
return result.first;
}
// 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<cv::Scalar, _mat_type> QualitySSIM::_mat_data::compute(const _mat_data& lhs, const _mat_data& rhs)
{
const double
C1 = 6.5025
, C2 = 58.5225
;
mat_type
I1_I2
, mu1_mu2
, t1
, t2
, t3
, sigma12
;
cv::multiply(lhs.I, rhs.I, I1_I2);
cv::multiply(lhs.mu, rhs.mu, mu1_mu2);
cv::subtract(::blur(I1_I2), mu1_mu2, sigma12);
// t3 = ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
cv::multiply(mu1_mu2, 2., t1);
cv::add(t1, C1, t1);// t1 += C1
cv::multiply(sigma12, 2., t2);
cv::add(t2, C2, t2);// t2 += C2
// t3 = t1 * t2
cv::multiply(t1, t2, t3);
// t1 =((mu1_2 + mu2_2 + C1).*(sigma1_2 + sigma2_2 + C2))
cv::add(lhs.mu_2, rhs.mu_2, t1);
cv::add(t1, C1, t1);
cv::add(lhs.sigma_2, rhs.sigma_2, t2);
cv::add(t2, C2, t2);
// t1 *= t2
cv::multiply(t1, t2, t1);
// quality map: t3 /= t1
cv::divide(t3, t1, t3);
return {
cv::mean(t3)
, std::move(t3)
};
} // compute
+91
View File
@@ -0,0 +1,91 @@
// 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_BRISQUE
namespace opencv_test
{
namespace quality_test
{
// brisque per channel
const cv::Scalar
BRISQUE_EXPECTED_1 = { 31.866388320922852 } // testfile_1a
, BRISQUE_EXPECTED_2 = { 9.7544803619384766 } // testfile 2a
;
// 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<quality::QualityBRISQUE> create_brisque()
{
const auto model = cvtest::findDataFile(MODEL_FNAME, false);
const auto range = cvtest::findDataFile(RANGE_FNAME, false);
return quality::QualityBRISQUE::create(model, range);
}
// static method
TEST(TEST_CASE_NAME, static_ )
{
quality_expect_near(
quality::QualityBRISQUE::compute(
get_testfile_1a()
, cvtest::findDataFile(MODEL_FNAME, false)
, cvtest::findDataFile(RANGE_FNAME, false)
)
, BRISQUE_EXPECTED_1
);
}
// 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, false, true ); };
OCL_OFF( fn() );
OCL_ON( fn() );
}
// multi-channel
TEST(TEST_CASE_NAME, multi_channel)
{
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, false, true); };
fn();
fn(); // model/range should persist with brisque ptr through multiple invocations
}
// check compute features interface method
TEST(TEST_CASE_NAME, compute_features)
{
auto ptr = create_brisque();
cv::Mat features;
ptr->computeFeatures(get_testfile_1a(), features);
EXPECT_EQ(features.rows, 1);
EXPECT_EQ(features.cols, 36);
}
/*
// internal a/b test
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto alg = create_brisque();
quality_performance_test("BRISQUE", [&]() { alg->compute(ref); });
}
*/
}
} // namespace
+53
View File
@@ -0,0 +1,53 @@
// 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_GMSD
namespace opencv_test
{
namespace quality_test
{
// expected gmsd per channel
const cv::Scalar
GMSD_EXPECTED_1 = { .2393 }
, GMSD_EXPECTED_2 = { .0942, .1016, .0995 }
;
// static method
TEST(TEST_CASE_NAME, static_)
{
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
TEST(TEST_CASE_NAME, single_channel)
{
auto fn = []() { quality_test(quality::QualityGMSD::create(get_testfile_1a()), get_testfile_1b(), GMSD_EXPECTED_1); };
OCL_OFF(fn());
OCL_ON(fn());
}
// multi-channel
TEST(TEST_CASE_NAME, multi_channel)
{
quality_test(quality::QualityGMSD::create(get_testfile_2a()), get_testfile_2b(), GMSD_EXPECTED_2);
}
// internal A/B test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("GMSD", [&]() { cv::quality::QualityGMSD::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
+9
View File
@@ -0,0 +1,9 @@
// 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"
CV_TEST_MAIN("",
cvtest::addDataSearchSubDirectory("contrib/quality") // for ocv_add_testdata
, cvtest::addDataSearchSubDirectory("quality") // for ${OPENCV_TEST_DATA_PATH}
)
+47
View File
@@ -0,0 +1,47 @@
// 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_MSE
namespace opencv_test
{
namespace quality_test
{
// static method
TEST(TEST_CASE_NAME, static_ )
{
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
TEST(TEST_CASE_NAME, single_channel )
{
auto fn = []() { quality_test(quality::QualityMSE::create(get_testfile_1a()), get_testfile_1b(), MSE_EXPECTED_1); };
OCL_OFF( fn() );
OCL_ON( fn() );
}
// multi-channel
TEST(TEST_CASE_NAME, multi_channel)
{
quality_test(quality::QualityMSE::create(get_testfile_2a()), get_testfile_2b(), MSE_EXPECTED_2);
}
// internal a/b test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("MSE", [&]() { cv::quality::QualityMSE::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
+131
View File
@@ -0,0 +1,131 @@
// 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_TEST_PRECOMP_HPP
#define OPENCV_TEST_PRECOMP_HPP
#include <chrono>
#include <opencv2/core.hpp>
#include <opencv2/ts.hpp>
#include <opencv2/ts/ocl_test.hpp> // OCL_ON, OCL_OFF
#include <opencv2/imgcodecs.hpp>
#include <opencv2/quality.hpp>
#include <opencv2/quality/quality_utils.hpp>
namespace opencv_test
{
namespace quality_test
{
const cv::String
dataDir = "cv/optflow/"
, testfile1a = dataDir + "rock_1.bmp"
, testfile1b = dataDir + "rock_2.bmp"
, testfile2a = dataDir + "RubberWhale1.png"
, testfile2b = dataDir + "RubberWhale2.png"
;
const cv::Scalar
MSE_EXPECTED_1 = { 2136.0525 } // matlab: immse('rock_1.bmp', 'rock_2.bmp') == 2.136052552083333e+03
, MSE_EXPECTED_2 = { 92.8235, 109.4104, 121.4 } // matlab: immse('rubberwhale1.png', 'rubberwhale2.png') == {92.8235, 109.4104, 121.4}
;
inline cv::Mat get_testfile(const cv::String& path, int flags = IMREAD_UNCHANGED )
{
auto full_path = TS::ptr()->get_data_path() + path;
auto result = cv::imread( full_path, flags );
if (result.empty())
CV_Error(cv::Error::StsObjectNotFound, "Cannot find file: " + full_path );
return result;
}
inline cv::Mat get_testfile_1a() { return get_testfile(testfile1a, IMREAD_GRAYSCALE); }
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); }
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)
{
for (int i = 0; i < a.rows; ++i)
{
if (std::isinf(a(i)))
EXPECT_EQ(a(i), b(i));
else
EXPECT_NEAR(a(i), b(i), err_tolerance);
}
}
template <typename TMat>
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 <typename TMat>
inline void quality_test(cv::Ptr<quality::QualityBase> ptr, const TMat& cmp, const Scalar& expected, const bool quality_map_expected = true, const bool empty_expected = false )
{
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)
EXPECT_TRUE(ptr->empty());
else
EXPECT_FALSE(ptr->empty());
// getQualityMap to Mat, UMat
ptr->getQualityMap(qMat);
ptr->getQualityMap(qUMat);
// 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());
}
/* A/B test benchmarking for development purposes */
/*
template <typename Fn>
inline void quality_performance_test( const char* name, Fn&& op )
{
const auto exec_test = [&]()
{
const int NRUNS = 100;
const auto start_t = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NRUNS; ++i)
op();
const auto end_t = std::chrono::high_resolution_clock::now();
std::cout << name << " performance (OCL=" << cv::ocl::useOpenCL() << "): " << (double)(std::chrono::duration_cast<std::chrono::milliseconds>(end_t - start_t).count()) / (double)NRUNS << "ms\n";
};
// only run tests in NDEBUG mode
#ifdef NDEBUG
OCL_OFF(exec_test());
OCL_ON(exec_test());
#endif
}
*/
}
}
#endif
+51
View File
@@ -0,0 +1,51 @@
// 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_PSNR
namespace opencv_test
{
namespace quality_test
{
const cv::Scalar
PSNR_EXPECTED_1 = { 14.8347, INFINITY, INFINITY, INFINITY } // matlab: psnr('rock_1.bmp', 'rock_2.bmp') == 14.8347
, PSNR_EXPECTED_2 = { 28.4542, 27.7402, 27.2886, INFINITY } // matlab: psnr('rubberwhale1.png', 'rubberwhale2.png') == BGR: 28.4542, 27.7402, 27.2886, avg 27.8015
;
// static method
TEST(TEST_CASE_NAME, static_)
{
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
TEST(TEST_CASE_NAME, single_channel)
{
auto fn = []() { quality_test(quality::QualityPSNR::create(get_testfile_1a()), get_testfile_1b(), PSNR_EXPECTED_1); };
OCL_OFF( fn() );
OCL_ON( fn() );
}
// multi-channel
TEST(TEST_CASE_NAME, multi_channel)
{
quality_test(quality::QualityPSNR::create(get_testfile_2a()), get_testfile_2b(), PSNR_EXPECTED_2);
}
// internal a/b test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("PSNR", [&]() { cv::quality::QualityPSNR::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
+52
View File
@@ -0,0 +1,52 @@
// 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_SSIM
namespace opencv_test
{
namespace quality_test
{
// expected ssim per channel
const cv::Scalar
SSIM_EXPECTED_1 = { .1501 }
, SSIM_EXPECTED_2 = { .7541, .7742, .8095 }
;
// static method
TEST(TEST_CASE_NAME, static_)
{
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
TEST(TEST_CASE_NAME, single_channel)
{
auto fn = []() { quality_test(quality::QualitySSIM::create(get_testfile_1a()), get_testfile_1b(), SSIM_EXPECTED_1); };
OCL_OFF(fn());
OCL_ON(fn());
}
// multi-channel
TEST(TEST_CASE_NAME, multi_channel)
{
quality_test(quality::QualitySSIM::create(get_testfile_2a()), get_testfile_2b(), SSIM_EXPECTED_2);
}
// internal a/b test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("SSIM", [&]() { cv::quality::QualitySSIM::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace