Commit 796853e0 authored by clunietp's avatar clunietp Committed by Alexander Alekhin

Merge pull request #1990 from clunietp:quality-2

Image Quality Analysis (IQA) API (#1990)

* Implement Quality API

* Update README, fixes for gcc/tests

* Add license headers

* Update README

* Update README

* Case fixing, CMakeLists.txt update

* Update CV_EXPORTS_W

* Updates for compiler and doxygen warnings

* Updates for doxygen

* Updates based on feedback from @alalek

* Compliance updates

* case fixing

* case fixing

* OpenCL test fixes

* Case fixing

* Python fixes

* Python fixes

* Compliance updates

* Python binding fix

* Documentation updates

* Update README, fix cv::Scalar ops

* Updated precompiled headers for src cpp

* Removed internal a/b perf tests
parent 301aa7e9
set(the_description "Image Quality Analysis API")
ocv_define_module(quality opencv_core opencv_imgproc WRAP python)
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.
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.
Quick Start/Usage
-----------------------------------------
#include <opencv2/quality.hpp>
cv::Mat img1, img2; /* your cv::Mat images */
std::vector<cv::Mat> quality_maps; /* output quality map(s) (optional) */
/* compute MSE via static method */
cv::Scalar result_static = quality::QualityMSE::compute(img1, img2, quality_maps); /* or cv::noArray() if not interested in output quality maps */
/* 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->getQualityMaps(quality_maps); /* optionally, access output quality maps */
Library Design
-----------------------------------------
Each implemented algorithm shall:
- Inherit from `QualityBase`, and properly implement/override `compute`, `empty` and `clear` instance methods, along with a static `compute` method.
- Accept one or more `cv::Mat` or `cv::UMat` via `InputArrayOfArrays` for computation. Each input `cv::Mat` or `cv::UMat` may contain one or more channels. If the algorithm does not support multiple channels or multiple inputs, it should be documented and an appropriate assertion should be in place.
- Return a `cv::Scalar` with per-channel computed value. If multiple input images are provided, the resulting scalar should return the average result per channel.
- Compute result via a single, static method named `compute` and via an overridden instance method (see `compute` in `qualitybase.hpp`).
- Perform any setup and/or pre-processing of reference images in the constructor, allowing for efficient computation when comparing the reference image(s) versus multiple comparison image(s). No-reference algorithms should accept images for evaluation in the `compute` method.
- Optionally compute resulting quality maps. Instance `compute` method should store them in `QualityBase::_qualityMaps` as the mat type defined by `QualityBase::_quality_map_type`, or override `QualityBase::getQualityMaps`. Static `compute` method should return them in an `OutputArrayOfArrays` parameter.
- Document algorithm in this readme and in its respective header. Documentation should include interpretation for the results of `compute` as well as the format of the output quality maps (if supported), along with any other notable usage information.
- Implement tests of static `compute` method and instance methods using single- and multi-channel images, multi-frame images, and OpenCL enabled and disabled
To Do
-----------------------------------------
- Document the output quality maps for each algorithm
- Implement at least one no-reference IQA algorithm
- Investigate precision loss with cv::Filter2D + UMat + CV_32F + OCL for GMSD
\ No newline at end of file
// 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"
#endif
\ No newline at end of file
// 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 <limits> // numeric_limits
#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;
// expand matrix to target type
template <typename OutT, typename InT>
inline OutT expand_mat(const InT& src, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE)
{
OutT result = {};
// by default, expand to 32F unless we already have >= 32 bits, then go to 64
// if/when we can detect OpenCL CV_16F support, opt for that when input depth == 8
// note that this may impact the precision of the algorithms and would need testing
int type = TYPE_DEFAULT;
switch (src.depth())
{
case CV_32F:
case CV_32S:
case CV_64F:
type = CV_64F;
}; // switch
src.convertTo(result, type);
return result;
}
// convert input array to vector of expanded mat types
template <typename R>
inline std::vector<R> expand_mats(InputArrayOfArrays arr, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE)
{
std::vector<R> result = {};
std::vector<UMat> umats = {};
std::vector<Mat> mats = {};
if (arr.isUMatVector())
arr.getUMatVector(umats);
else if (arr.isUMat())
umats.emplace_back(arr.getUMat());
else if (arr.isMatVector())
arr.getMatVector(mats);
else if (arr.isMat())
mats.emplace_back(arr.getMat());
else
CV_Error(Error::StsNotImplemented, "Unsupported input type");
// convert umats, mats to expanded internal type
for (auto& umat : umats)
result.emplace_back(expand_mat<R>(umat, TYPE_DEFAULT ));
for (auto& mat : mats)
result.emplace_back(expand_mat<R>(mat, TYPE_DEFAULT ));
return result;
}
// convert mse to psnr
inline double mse_to_psnr(double mse, double max_pixel_value)
{
return (mse == 0.)
? std::numeric_limits<double>::infinity()
: 10. * std::log10((max_pixel_value * max_pixel_value) / mse)
;
}
// convert scalar of mses to psnrs
inline cv::Scalar mse_to_psnr(cv::Scalar mse, double max_pixel_value)
{
for (int i = 0; i < mse.rows; ++i)
mse(i) = mse_to_psnr(mse(i), max_pixel_value);
return mse;
}
} // quality_utils
} // quality
} // cv
#endif
\ No newline at end of file
// 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 <vector>
#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 cmpImgs comparison image(s), or image(s) to evalute for no-reference quality algorithms
*/
virtual CV_WRAP cv::Scalar compute( InputArrayOfArrays cmpImgs ) = 0;
/** @brief Returns output quality map images that were generated during computation, if supported by the algorithm */
virtual CV_WRAP void getQualityMaps(OutputArrayOfArrays dst) const
{
if (!dst.needed() || _qualityMaps.empty() )
return;
auto qMaps = InputArray(_qualityMaps);
dst.create(qMaps.size(), qMaps.type());
dst.assign(_qualityMaps);
}
/** @brief Implements Algorithm::clear() */
CV_WRAP void clear() CV_OVERRIDE { _qualityMaps.clear(); Algorithm::clear(); }
/** @brief Implements Algorithm::empty() */
CV_WRAP bool empty() const CV_OVERRIDE { return _qualityMaps.empty(); }
protected:
/** @brief internal quality map type default */
using _quality_map_type = cv::UMat;
/** @brief Output quality maps if generated by algorithm */
std::vector<_quality_map_type> _qualityMaps;
}; // QualityBase
//! @}
} // quality
} // cv
#endif
\ No newline at end of file
// 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 cmpImgs Comparison images
@returns Per-channel GMSD
*/
CV_WRAP cv::Scalar compute(InputArrayOfArrays cmpImgs) CV_OVERRIDE;
/** @brief Implements Algorithm::empty() */
CV_WRAP bool empty() const CV_OVERRIDE { return _refImgData.empty() && QualityBase::empty(); }
/** @brief Implements Algorithm::clear() */
CV_WRAP void clear() CV_OVERRIDE { _refImgData.clear(); QualityBase::clear(); }
/**
@brief Create an object which calculates image quality
@param refImgs input image(s) to use as the source for comparison
*/
CV_WRAP static Ptr<QualityGMSD> create(InputArrayOfArrays refImgs);
/**
@brief static method for computing quality
@param refImgs reference image(s)
@param cmpImgs comparison image(s)
@param qualityMaps output quality map(s), or cv::noArray()
@returns cv::Scalar with per-channel quality value. Values range from 0 (worst) to 1 (best)
*/
CV_WRAP static cv::Scalar compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps);
protected:
// holds computed values for an input mat
struct _mat_data
{
using mat_type = QualityBase::_quality_map_type;
mat_type
gradient_map
, gradient_map_squared
;
_mat_data(const mat_type&);
// converts mat/umat to vector of mat_data
static std::vector<_mat_data> create(InputArrayOfArrays arr);
// compute for a single frame
static std::pair<cv::Scalar, mat_type> compute(const _mat_data& lhs, const _mat_data& rhs);
// compute for vector of inputs
static cv::Scalar compute(const std::vector<_mat_data>& lhs, const std::vector<_mat_data>& rhs, OutputArrayOfArrays qualityMaps);
}; // mat_data
/** @brief Reference image data */
std::vector<_mat_data> _refImgData;
/**
@brief Constructor
@param refImgData vector of reference images, converted to internal type
*/
QualityGMSD(std::vector<_mat_data> refImgData)
: _refImgData(std::move(refImgData))
{}
}; // QualityGMSD
} // quality
} // cv
#endif
\ No newline at end of file
// 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 _refImgs.empty() && QualityBase::empty(); }
/** @brief Implements Algorithm::clear() */
CV_WRAP void clear() CV_OVERRIDE { _refImgs.clear(); QualityBase::clear(); }
/**
@brief Create an object which calculates quality
@param refImgs input image(s) to use as the source for comparison
*/
CV_WRAP static Ptr<QualityMSE> create(InputArrayOfArrays refImgs);
/**
@brief static method for computing quality
@param refImgs reference image(s)
@param cmpImgs comparison image(s)
@param qualityMaps output quality map(s), or cv::noArray()
@returns cv::Scalar with per-channel quality values. Values range from 0 (best) to potentially max float (worst)
*/
CV_WRAP static cv::Scalar compute( InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps );
protected:
/** @brief Reference images, converted to internal mat type */
std::vector<QualityBase::_quality_map_type> _refImgs;
/**
@brief Constructor
@param refImgs vector of reference images, converted to internal type
*/
QualityMSE(std::vector<QualityBase::_quality_map_type> refImgs)
: _refImgs(std::move(refImgs))
{}
}; // QualityMSE
} // quality
} // cv
#endif
\ No newline at end of file
// 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 "qualitybase.hpp"
#include "qualitymse.hpp"
#include "quality_utils.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 */
static CV_CONSTEXPR const double MAX_PIXEL_VALUE_DEFAULT = 255.;
/**
@brief Create an object which calculates quality via mean square error
@param refImgs input image(s) 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(InputArrayOfArrays refImgs, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT )
{
return Ptr<QualityPSNR>(new QualityPSNR(QualityMSE::create(refImgs), maxPixelValue));
}
/**
@brief compute the PSNR
@param cmpImgs Comparison images
@returns Per-channel PSNR value, or std::numeric_limits<double>::infinity() if the MSE between the two images == 0
The PSNR for multi-frame images is computed by calculating the average MSE of all frames and then generating the PSNR from that value
*/
CV_WRAP cv::Scalar compute(InputArrayOfArrays cmpImgs) CV_OVERRIDE
{
auto result = _qualityMSE->compute(cmpImgs);
_qualityMSE->getQualityMaps(_qualityMaps); // copy from internal obj to this obj
return quality_utils::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 refImgs reference image(s)
@param cmpImgs comparison image(s)
@param qualityMaps output quality map(s), 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
The PSNR for multi-frame images is computed by calculating the average MSE of all frames and then generating the PSNR from that value
*/
CV_WRAP static cv::Scalar compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps, double maxPixelValue = QualityPSNR::MAX_PIXEL_VALUE_DEFAULT)
{
return quality_utils::mse_to_psnr(
QualityMSE::compute(refImgs, cmpImgs, qualityMaps)
, 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)
{}
}; // QualityPSNR
} // quality
} // cv
#endif
\ No newline at end of file
// 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 cmpImgs Comparison images
@returns cv::Scalar with per-channel quality values. Values range from 0 (worst) to 1 (best)
*/
CV_WRAP cv::Scalar compute(InputArrayOfArrays cmpImgs) CV_OVERRIDE;
/** @brief Implements Algorithm::empty() */
CV_WRAP bool empty() const CV_OVERRIDE { return _refImgData.empty() && QualityBase::empty(); }
/** @brief Implements Algorithm::clear() */
CV_WRAP void clear() CV_OVERRIDE { _refImgData.clear(); QualityBase::clear(); }
/**
@brief Create an object which calculates quality via mean square error
@param refImgs input image(s) to use as the source for comparison
*/
CV_WRAP static Ptr<QualitySSIM> create(InputArrayOfArrays refImgs);
/**
@brief static method for computing quality
@param refImgs reference image(s)
@param cmpImgs comparison image(s)
@param qualityMaps output quality map(s), or cv::noArray()
@returns cv::Scalar with per-channel quality values. Values range from 0 (worst) to 1 (best)
*/
CV_WRAP static cv::Scalar compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps);
protected:
// holds computed values for a mat
struct _mat_data
{
using mat_type = QualityBase::_quality_map_type;
mat_type
I
, I_2
, mu
, mu_2
, sigma_2
;
_mat_data(const mat_type&);
// construct vector of _mat_data for input mats
static std::vector<_mat_data> create(InputArrayOfArrays arr);
// computes ssim and quality map for single frame
static std::pair<cv::Scalar, mat_type> compute(const _mat_data& lhs, const _mat_data& rhs);
// computes mse and quality maps for multiple frames
static cv::Scalar compute(const std::vector<_mat_data>& lhs, const std::vector<_mat_data>& rhs, OutputArrayOfArrays qualityMaps);
}; // mat_data
/** @brief Reference image data */
std::vector<_mat_data> _refImgData;
/**
@brief Constructor
@param refImgData vector of reference images, converted to internal type
*/
QualitySSIM(std::vector<_mat_data> refImgData)
: _refImgData(std::move(refImgData))
{}
}; // QualitySSIM
} // quality
} // cv
#endif
\ No newline at end of file
// 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
\ No newline at end of file
// 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);
}
// static
Ptr<QualityGMSD> QualityGMSD::create(InputArrayOfArrays refImgs)
{
return Ptr<QualityGMSD>(new QualityGMSD( _mat_data::create( refImgs )));
}
// static
cv::Scalar QualityGMSD::compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps)
{
auto ref = _mat_data::create( refImgs );
auto cmp = _mat_data::create( cmpImgs );
return _mat_data::compute(ref, cmp, qualityMaps);
}
cv::Scalar QualityGMSD::compute(InputArrayOfArrays cmpImgs)
{
auto cmp = _mat_data::create(cmpImgs);
return _mat_data::compute(this->_refImgData, cmp, this->_qualityMaps);
}
// static, converts mat/umat to vector of mat_data
std::vector<QualityGMSD::_mat_data> QualityGMSD::_mat_data::create(InputArrayOfArrays arr)
{
std::vector<_mat_data> result = {};
auto mats = quality_utils::expand_mats<_mat_type>(arr);
result.reserve(mats.size());
for (auto& mat : mats)
result.emplace_back(mat);
return result;
}
// 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
// static, computes mse and quality maps for multiple frames
cv::Scalar QualityGMSD::_mat_data::compute(const std::vector<QualityGMSD::_mat_data>& lhs, const std::vector<QualityGMSD::_mat_data>& rhs, OutputArrayOfArrays qualityMaps)
{
CV_Assert(lhs.size() > 0);
CV_Assert(lhs.size() == rhs.size());
cv::Scalar result = {};
std::vector<_quality_map_type> quality_maps = {};
const auto sz = lhs.size();
for (unsigned i = 0; i < sz; ++i)
{
CV_Assert(!lhs.empty() && !rhs.empty());
auto cmp = compute(lhs[i], rhs[i]); // differs slightly when using umat vs mat
cv::add(result, cmp.first, result); // result += cmp.first
if (qualityMaps.needed())
quality_maps.emplace_back(std::move(cmp.second));
}
if (qualityMaps.needed())
{
auto qMaps = InputArray(quality_maps);
qualityMaps.create(qMaps.size(), qMaps.type());
qualityMaps.assign(quality_maps);
}
if (sz > 1)
result /= (cv::Scalar::value_type)sz; // average result
return result;
}
\ No newline at end of file
// 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;
}
// computes mse and quality maps for multiple frames
cv::Scalar compute(const std::vector<mse_mat_type>& lhs, const std::vector<mse_mat_type>& rhs, OutputArrayOfArrays qualityMaps )
{
CV_Assert(lhs.size() > 0);
CV_Assert(lhs.size() == rhs.size());
cv::Scalar result = {};
std::vector<_quality_map_type> quality_maps = {};
const auto sz = lhs.size();
for (unsigned i = 0; i < sz; ++i)
{
CV_Assert(!lhs.empty() && !rhs.empty());
auto cmp = compute(lhs[i], rhs[i]);
cv::add(result, cmp.first, result);
if ( qualityMaps.needed() )
quality_maps.emplace_back(std::move(cmp.second));
}
if (qualityMaps.needed())
{
auto qMaps = InputArray(quality_maps);
qualityMaps.create(qMaps.size(), qMaps.type());
qualityMaps.assign(quality_maps);
}
if (sz > 1)
result /= (cv::Scalar::value_type)sz; // average result
return result;
}
}
// static
Ptr<QualityMSE> QualityMSE::create( InputArrayOfArrays refImgs )
{
return Ptr<QualityMSE>(new QualityMSE(quality_utils::expand_mats<mse_mat_type>(refImgs)));
}
// static
cv::Scalar QualityMSE::compute( InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps )
{
auto ref = quality_utils::expand_mats<mse_mat_type>(refImgs);
auto cmp = quality_utils::expand_mats<mse_mat_type>(cmpImgs);
return ::compute(ref, cmp, qualityMaps);
}
cv::Scalar QualityMSE::compute( InputArrayOfArrays cmpImgs )
{
auto cmp = quality_utils::expand_mats<mse_mat_type>(cmpImgs);
return ::compute( this->_refImgs, cmp, this->_qualityMaps);
}
\ No newline at end of file
// 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);
}
// static
Ptr<QualitySSIM> QualitySSIM::create(InputArrayOfArrays refImgs)
{
return Ptr<QualitySSIM>(new QualitySSIM( _mat_data::create( refImgs )));
}
// static
cv::Scalar QualitySSIM::compute(InputArrayOfArrays refImgs, InputArrayOfArrays cmpImgs, OutputArrayOfArrays qualityMaps)
{
auto ref = _mat_data::create( refImgs );
auto cmp = _mat_data::create( cmpImgs );
return _mat_data::compute(ref, cmp, qualityMaps);
}
cv::Scalar QualitySSIM::compute(InputArrayOfArrays cmpImgs)
{
auto cmp = _mat_data::create(cmpImgs);
return _mat_data::compute(this->_refImgData, cmp, this->_qualityMaps);
}
// static. converts mat/umat to vector of mat_data
std::vector<QualitySSIM::_mat_data> QualitySSIM::_mat_data::create(InputArrayOfArrays arr)
{
std::vector<QualitySSIM::_mat_data> result = {};
auto mats = quality_utils::expand_mats<_mat_type>(arr);
result.reserve(mats.size());
for (auto& mat : mats)
result.emplace_back(mat);
return result;
}
// 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
// computes mse and quality maps for multiple frames
cv::Scalar QualitySSIM::_mat_data::compute(const std::vector<_mat_data>& lhs, const std::vector<_mat_data>& rhs, OutputArrayOfArrays qualityMaps)
{
CV_Assert(lhs.size() > 0);
CV_Assert(lhs.size() == rhs.size());
Scalar result = {};
std::vector<QualityBase::_quality_map_type> quality_maps = {};
const auto sz = lhs.size();
for (unsigned i = 0; i < sz; ++i)
{
CV_Assert(!lhs.empty() && !rhs.empty());
auto cmp = compute(lhs[i], rhs[i]); // differs slightly when using umat vs mat
cv::add(result, cmp.first, result); // result += cmp.first
if (qualityMaps.needed())
quality_maps.emplace_back(std::move(cmp.second));
}
if (qualityMaps.needed())
{
auto qMaps = InputArray(quality_maps);
qualityMaps.create(qMaps.size(), qMaps.type());
qualityMaps.assign(quality_maps);
}
if (sz > 1)
result /= (cv::Scalar::value_type)sz;// average result
return result;
}
\ No newline at end of file
// 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
{
// gmsd per channel
const cv::Scalar
GMSD_EXPECTED_1 = { .2393 }
, GMSD_EXPECTED_2 = { .0942, .1016, .0995 }
;
// static method
TEST(TEST_CASE_NAME, static_)
{
std::vector<cv::Mat> qMats = {};
quality_expect_near(quality::QualityGMSD::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(0.)); // ref vs ref == 0.
EXPECT_EQ(qMats.size(), 1U );
}
// 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);
}
// multi-frame test
TEST(TEST_CASE_NAME, multi_frame)
{
// result == average of all frames
cv::Scalar expected;
cv::add(GMSD_EXPECTED_1, GMSD_EXPECTED_2, expected);
expected /= 2.;
quality_test(quality::QualityGMSD::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2);
}
// internal A/B test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("GMSD", [&]() { cv::quality::QualityGMSD::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
\ No newline at end of file
// 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("")
\ No newline at end of file
// 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_ )
{
std::vector<cv::Mat> qMats = {};
quality_expect_near(quality::QualityMSE::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(0.)); // ref vs ref == 0
EXPECT_EQ(qMats.size(), 1U);
}
// 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);
}
// multi-frame test
TEST(TEST_CASE_NAME, multi_frame)
{
// result mse == average mse of all frames
cv::Scalar expected;
cv::add(MSE_EXPECTED_1, MSE_EXPECTED_2, expected);
expected /= 2.;
quality_test(quality::QualityMSE::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2 );
}
// internal a/b test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("MSE", [&]() { cv::quality::QualityMSE::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
\ No newline at end of file
// 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); }
inline std::vector<cv::Mat> get_testfile_1a2a() { return { get_testfile_1a(), get_testfile_2a() }; }
inline std::vector<cv::Mat> get_testfile_1b2b() { return { get_testfile_1b(), get_testfile_2b() }; }
const double QUALITY_ERR_TOLERANCE = .001 // 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);
}
}
// 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 std::size_t quality_maps_expected = 1)
{
std::vector<cv::Mat> qMats = {};
ptr->getQualityMaps(qMats);
EXPECT_TRUE( qMats.empty());
quality_expect_near( expected, ptr->compute(cmp));
EXPECT_FALSE(ptr->empty());
ptr->getQualityMaps(qMats);
EXPECT_EQ( qMats.size(), quality_maps_expected);
for (auto& qm : qMats)
{
EXPECT_GT(qm.rows, 0);
EXPECT_GT(qm.cols, 0);
}
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
\ No newline at end of file
// 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_)
{
std::vector<cv::Mat> qMats = {};
quality_expect_near(quality::QualityPSNR::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(INFINITY,INFINITY,INFINITY,INFINITY)); // ref vs ref == inf
EXPECT_EQ(qMats.size(), 1U);
}
// 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);
}
// multi-frame test
TEST(TEST_CASE_NAME, multi_frame)
{
cv::Scalar expected;
cv::add(MSE_EXPECTED_1, MSE_EXPECTED_2, expected);
expected /= 2.;
expected = quality::quality_utils::mse_to_psnr(expected, quality::QualityPSNR::MAX_PIXEL_VALUE_DEFAULT );
quality_test(quality::QualityPSNR::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2);
}
// internal a/b test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("PSNR", [&]() { cv::quality::QualityPSNR::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
\ No newline at end of file
// 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
{
// ssim per channel
const cv::Scalar
SSIM_EXPECTED_1 = { .1501 }
, SSIM_EXPECTED_2 = { .7541, .7742, .8095 }
;
// static method
TEST(TEST_CASE_NAME, static_)
{
std::vector<cv::Mat> qMats = {};
quality_expect_near(quality::QualitySSIM::compute(get_testfile_1a(), get_testfile_1a(), qMats), cv::Scalar(1.)); // ref vs ref == 1.
EXPECT_EQ(qMats.size(), 1U );
}
// 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);
}
// multi-frame test
TEST(TEST_CASE_NAME, multi_frame)
{
// result == average of all frames
cv::Scalar expected;
cv::add(SSIM_EXPECTED_1, SSIM_EXPECTED_2, expected);
expected /= 2.;
quality_test(quality::QualitySSIM::create(get_testfile_1a2a()), get_testfile_1b2b(), expected, 2U );
}
// internal a/b test
/*
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto cmp = get_testfile_1b();
quality_performance_test("SSIM", [&]() { cv::quality::QualitySSIM::compute(ref, cmp, cv::noArray()); });
}
*/
}
} // namespace
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment