vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e
This commit is contained in:
@@ -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)
|
||||
@@ -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
|
||||
|
||||
//! @}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user