BRISQUE commit

Minor edits

minor edits

double* to vector<data>

Updates: BRISQUE and Python

Added BRISQUE and Python Samples

Minor edits

Final edits: BRISQUE

Final edits: BRISQUE

Copyright notice added

deleted .vscode

Warnings rectified. Added CV_Assert

Final Commit: BRISQUE

Removed whitespaces. Corrected array initializations and override warnings

Removed whitespaces

Resolved array initialization

Override warning

Mac build resolved

Added bibliography and corrected Mac/Android/iOS warnings

Rollback changes

Added BRISQUE bib

Solves Mac/iOS/Android warnings

Removed trailing whitespaces

Updated sample code for BRISQUE C++

Fixes for Win32, Win64

Warnings removed

trailing whitespaces

Trailing whitespace removed
parent 476167ed
......@@ -16,6 +16,8 @@ Implementation of various image quality analysis (IQA) algorithms
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
-----------------------------------------
......@@ -31,23 +33,75 @@ 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**
#include <opencv2/quality.hpp>
**For Full Reference IQA Algorithms (MSE, PSNR, SSIM, GMSD)**
```cpp
#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 */
/* 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 */
```
**For No Reference IQA Algorithm (BRISQUE)**
```cpp
#include <opencv2/quality.hpp>
cv::Mat img;
// check testdata subdirectory
cv::String model_path = "testdata/brisque_allmodel.dat"; // path to the trained model
cv::String range_path = "testdata/brisque_allrange.dat"; // 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_maps = cv2.quality.QualityMSE_compute(img1, img2)
# compute MSE score and quality maps via Instance
obj = cv2.quality.QualityMSE_create(img1)
result = obj.compute(img2)
quality_maps = obj.getQualityMaps()
```
**For No Reference IQA Algorithm (BRISQUE)**
```python
import cv2
# read image
img = cv2.imread(img_path, 1) # mention img_path
# make a list of image to be passed
img_list = [img]
# compute brisque quality score via static method
score = cv2.quality.QualityBRISQUE_compute(img_list, model_path,
range_path) # specify model_path and range_path
# compute brisque quality score via instance
# specify model_path and range_path
obj = cv2.quality.QualityBRISQUE_create(model_path, range_path)
score = obj.compute(img_list)
```
Library Design
-----------------------------------------
......@@ -64,5 +118,4 @@ Each implemented algorithm shall:
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
@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},
}
\ No newline at end of file
......@@ -12,59 +12,65 @@ namespace cv
namespace quality
{
/** @brief Custom deleter for QualityBRISQUE internal data */
struct _QualityBRISQUEDeleter
{
void operator()(void*) const;
};
/**
@brief TODO: Brief description and reference to original BRISQUE paper/implementation
@brief BRISQUE (Blind/Referenceless Image Spatial Quality Evaluator) is a type of No Reference
Image Quality Assessment. It measures score based on extracting Natural Scene Satistics (https://en.wikipedia.org/wiki/Scene_statistics)
and calculating feature vectors. The current implementation uses trained model on TID 2008 Database (http://www.ponomarenko.info/tid2008.htm).
@cite Mittal2 for original paper and @cite Mittal2_software for original implementation
*/
class CV_EXPORTS_W QualityBRISQUE : public QualityBase {
public:
/** @brief Computes XXX for reference images supplied in class constructor and provided comparison images
@param imgs Images for which to compute quality
@returns TODO: describe the resulting cv::Scalar
/** @brief Computes BRISQUE quality score for input images
@param imgs Images for which to compute quality (should be passed as a vector<Mat> in C++ and list of images in Python)
@returns Score (averaged over individual scores of all images) ranging from 0 to 100
(0 denotes the best quality and 100 denotes the poorest quality). The format of the score is: {score, 0., 0., 0.}
*/
CV_WRAP cv::Scalar compute( InputArrayOfArrays imgs ) CV_OVERRIDE;
/**
@brief Create an object which calculates quality
@param model_file_path cv::String which contains a path to the BRISQUE model data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_allmodel.dat
@param range_file_path cv::String which contains a path to the BRISQUE range data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_allrange.dat
*/
CV_WRAP static Ptr<QualityBRISQUE> create(cv::String model, cv::String range);
CV_WRAP static Ptr<QualityBRISQUE> create( const cv::String& model_file_path = "", const cv::String& range_file_path = "" );
// CV_WRAP static cv::Scalar compute_single( const Mat& cmpImg, const cv::String& model_file_path, const cv::String& range_file_path );
/**
@brief static method for computing quality
@param model cv::String containing BRISQUE calculation model
@param range cv::String containing BRISQUE calculation range
@param imgs image(s) for which to compute quality
@param qualityMaps output quality map(s), or cv::noArray() TODO: remove this parameter if algorithm doesn't generate output quality maps
@returns TODO: describe the resulting cv::Scalar
@param imgs image(s) for which to compute quality (passed as vector<Mat> in C++ and as list of images in Python)
@param model_file_path cv::String which contains a path to the BRISQUE model data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_allmodel.dat
@param range_file_path cv::String which contains a path to the BRISQUE range data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_allrange.dat
@returns cv::Scalar result of format {std::double score, 0., 0., 0.}. Score ranges from 0 to 100 (100 means worst and 0 means best)
*/
CV_WRAP static cv::Scalar compute( const cv::String& model, const cv::String& range, InputArrayOfArrays imgs, OutputArrayOfArrays qualityMaps );
/** @brief return the model used for computation */
CV_WRAP const cv::String& getModel() const { return _model; }
/** @brief sets the model used for computation */
CV_WRAP void setModel( cv::String val ) { this->_model = std::move(val); }
CV_WRAP static cv::Scalar compute( InputArrayOfArrays imgs, const cv::String& model_file_path, const cv::String& range_file_path );
/** @brief return the range used for computation */
CV_WRAP const cv::String& getRange() const { return _range; }
/** @brief sets the range used for computation */
CV_WRAP void setRange(cv::String val) { this->_range = std::move(val); }
/* brief destructor */
~QualityBRISQUE() CV_OVERRIDE;
protected:
/**
@brief Constructor
*/
QualityBRISQUE( cv::String model, cv::String range );
/** @brief Internal constructor */
QualityBRISQUE( const cv::String& model_file_path, const cv::String& range_file_path );
// type-erased svmmodel
void* _svm_model = nullptr;
/** @brief BRISQUE model string */
cv::String _model;
static constexpr const std::size_t _SVM_RANGE_SIZE = 36U;
using _svm_range_type = float;
/** @brief BRISQUE range string */
cv::String _range;
std::array<_svm_range_type, _SVM_RANGE_SIZE>
_svm_range_min = {}
, _svm_range_max = {}
;
}; // QualityBRISQUE
} // quality
} // cv
#endif
\ No newline at end of file
#endif
This diff is collapsed.
......@@ -12,49 +12,40 @@ namespace quality_test
{
// brisque per channel
// computed test file values via original brisque impl, libsvm 318, opencv 2.x
// computed test file values via original brisque c++ impl, libsvm 318, opencv 2.x
const cv::Scalar
BRISQUE_EXPECTED_1 = { 31.155 } // testfile_1a
, BRISQUE_EXPECTED_2 = { 15.4114 } // testfile_2a
BRISQUE_EXPECTED_1 = { 31.154966299963547 } // testfile_1a
// , BRISQUE_EXPECTED_2 = { 15.411353283158718 } // testfile_2a with original c++ impl
, BRISQUE_EXPECTED_2 = { 15.600739064304520 } // testfile 2a; delta is due to differences between opencv 2.x and opencv 4.x cvtColor, RGB2GRAY
;
inline cv::String readfile(const cv::String& path)
{
std::ifstream is{ path };
std::stringstream buffer;
buffer << is.rdbuf();
return buffer.str();
}
// location of BRISQUE model and range file
// place these files in ${OPENCV_TEST_DATA_PATH}/quality/, or the tests will be skipped
inline cv::String readbrisquemodel() { return readfile(cvtest::findDataFile("brisque_allmodel.dat", false)); }
inline cv::String readbrisquerange() { return readfile(cvtest::findDataFile("brisque_allrange.dat", false)); }
// instantiates a brisque object for testing
inline cv::Ptr<quality::QualityBRISQUE> create_brisque()
{
return quality::QualityBRISQUE::create(
readbrisquemodel()
, readbrisquerange()
);
// location of BRISQUE model and range file
// place these files in ${OPENCV_TEST_DATA_PATH}/quality/, or the tests will be skipped
const auto model = cvtest::findDataFile("brisque_allmodel.dat", false);
const auto range = cvtest::findDataFile("brisque_allrange.dat", false);
return quality::QualityBRISQUE::create(model, range);
}
// static method
TEST(TEST_CASE_NAME, static_ )
{
std::vector<cv::Mat> qMats = {};
quality_expect_near(
quality::QualityBRISQUE::compute( readbrisquemodel(), readbrisquerange(), get_testfile_1a(), qMats), BRISQUE_EXPECTED_1
quality::QualityBRISQUE::compute(
get_testfile_1a()
, cvtest::findDataFile("brisque_allmodel.dat", false)
, cvtest::findDataFile("brisque_allrange.dat", false)
)
, BRISQUE_EXPECTED_1
);
EXPECT_EQ(qMats.size(), 1U);
}
// 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); };
auto fn = []() { quality_test(create_brisque(), get_testfile_1a(), BRISQUE_EXPECTED_1, 0, true ); };
OCL_OFF( fn() );
OCL_ON( fn() );
}
......@@ -62,7 +53,7 @@ TEST(TEST_CASE_NAME, single_channel )
// multi-channel
TEST(TEST_CASE_NAME, multi_channel)
{
quality_test(create_brisque(), get_testfile_2a(), BRISQUE_EXPECTED_2);
quality_test(create_brisque(), get_testfile_2a(), BRISQUE_EXPECTED_2, 0, true);
}
// multi-frame test
......@@ -73,7 +64,16 @@ TEST(TEST_CASE_NAME, multi_frame)
cv::add(BRISQUE_EXPECTED_1, BRISQUE_EXPECTED_2, expected);
expected /= 2.;
quality_test(create_brisque(), get_testfile_1a2a(), expected, 2 );
quality_test(create_brisque(), get_testfile_1a2a(), expected, 0, true );
}
// check brisque model/range persistence
TEST(TEST_CASE_NAME, model_persistence )
{
auto ptr = create_brisque();
auto fn = [&ptr]() { quality_test(ptr, get_testfile_1a(), BRISQUE_EXPECTED_1, 0, true); };
fn();
fn(); // model/range should persist with brisque ptr through multiple invocations
}
// internal a/b test
......@@ -81,8 +81,9 @@ TEST(TEST_CASE_NAME, multi_frame)
TEST(TEST_CASE_NAME, performance)
{
auto ref = get_testfile_1a();
auto alg = create_brisque();
quality_performance_test("BRISQUE", [&]() { cv::quality::QualityBRISQUE::compute(ref, cv::noArray()); });
quality_performance_test("BRISQUE", [&]() { alg->compute(ref); });
}
*/
}
......
......@@ -62,7 +62,7 @@ inline void quality_expect_near( const cv::Scalar& a, const cv::Scalar& b, doubl
// 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)
inline void quality_test(cv::Ptr<quality::QualityBase> ptr, const TMat& cmp, const Scalar& expected, const std::size_t quality_maps_expected = 1, const bool empty_expected = false )
{
std::vector<cv::Mat> qMats = {};
ptr->getQualityMaps(qMats);
......@@ -70,8 +70,13 @@ inline void quality_test(cv::Ptr<quality::QualityBase> ptr, const TMat& cmp, con
quality_expect_near( expected, ptr->compute(cmp));
EXPECT_FALSE(ptr->empty());
if (empty_expected)
EXPECT_TRUE(ptr->empty());
else
EXPECT_FALSE(ptr->empty());
ptr->getQualityMaps(qMats);
EXPECT_EQ( qMats.size(), quality_maps_expected);
for (auto& qm : qMats)
{
......
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