Commit fd2ca919 authored by Alexander Alekhin's avatar Alexander Alekhin

Merge pull request #2015 from krshrimali:krshrimali-patch-brisque

parents 7753b593 ebad13e0
set(the_description "Image Quality Analysis API")
ocv_define_module(quality opencv_core opencv_imgproc WRAP python)
ocv_define_module(quality opencv_core opencv_imgproc opencv_ml WRAP python)
ocv_add_testdata(samples/ contrib/quality
FILES_MATCHING PATTERN "*.yml"
)
\ No newline at end of file
//! @addtogroup quality
//! @{
Quality API, Image Quality Analysis
=======================================
......@@ -16,6 +19,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 +36,74 @@ 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 = 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_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 +120,6 @@ 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
- 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},
}
@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},
}
\ No newline at end of file
......@@ -10,5 +10,6 @@
#include "quality/qualitypsnr.hpp"
#include "quality/qualityssim.hpp"
#include "quality/qualitygmsd.hpp"
#include "quality/qualitybrisque.hpp"
#endif
\ No newline at end of file
......@@ -18,6 +18,41 @@ namespace quality_utils
// default type of matrix to expand to
static CV_CONSTEXPR const int EXPANDED_MAT_DEFAULT_TYPE = CV_32F;
// convert input array to vector of specified mat types. set type == -1 to preserve existing type
template <typename R>
inline std::vector<R> extract_mats( InputArrayOfArrays arr, const int type = -1 )
{
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 desired type
for (auto& umat : umats)
{
result.emplace_back(R{});
umat.convertTo(result.back(), ( type != -1 ) ? type : umat.type() );
}
for (auto& mat : mats)
{
result.emplace_back(R{});
mat.convertTo(result.back(), (type != -1) ? type : mat.type() );
}
return result;
}
// expand matrix to target type
template <typename OutT, typename InT>
inline OutT expand_mat(const InT& src, int TYPE_DEFAULT = EXPANDED_MAT_DEFAULT_TYPE)
......@@ -46,26 +81,10 @@ 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 ));
auto mats = extract_mats<R>(arr, -1);
for (auto& mat : mats)
result.emplace_back(expand_mat<R>(mat, TYPE_DEFAULT ));
result.emplace_back(expand_mat<R>(mat, TYPE_DEFAULT));
return result;
}
......@@ -87,6 +106,54 @@ inline cv::Scalar mse_to_psnr(cv::Scalar mse, double max_pixel_value)
return mse;
}
// 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
......
// 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 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 worst 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_model_live.yml
@param range_file_path cv::String which contains a path to the BRISQUE range data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_range_live.yml
*/
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 imgs image(s) for which to compute quality (passed as Mat or 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_model_live.yml
@param range_file_path cv::String which contains a path to the BRISQUE range data. If empty, attempts to load from ${OPENCV_DIR}/testdata/contrib/quality/brisque_range_live.yml
@returns cv::Scalar result of format {std::double score, 0., 0., 0.}. Score ranges from 0 to 100 (100 means worst and 0 means best)
*/
CV_WRAP static cv::Scalar compute( InputArrayOfArrays imgs, 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
#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;
}
This diff is collapsed.
%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 ]
#include <sstream>
#include <iostream>
#include "opencv2/quality.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;
}
This diff is collapsed.
// 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
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()
{
// location of BRISQUE model and range file
// place these files in ${OPENCV_TEST_DATA_PATH}/quality/, or the tests will be skipped
const auto model = cvtest::findDataFile(MODEL_FNAME, false);
const auto range = cvtest::findDataFile(RANGE_FNAME, false);
return quality::QualityBRISQUE::create(model, range);
}
// 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, 0, 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, 0, true);
}
// multi-frame test
TEST(TEST_CASE_NAME, multi_frame)
{
// result mse == average of all frames
cv::Scalar expected;
cv::add(BRISQUE_EXPECTED_1, BRISQUE_EXPECTED_2, expected);
expected /= 2.;
quality_test(create_brisque(), get_testfile_1a2a(), expected, 0, true );
}
// 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
}
// 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
\ No newline at end of file
......@@ -3,4 +3,6 @@
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
CV_TEST_MAIN("")
\ No newline at end of file
CV_TEST_MAIN("",
cvtest::addDataSearchSubDirectory("quality")
)
\ No newline at end of file
......@@ -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)
{
......@@ -106,6 +111,7 @@ inline void quality_performance_test( const char* name, Fn&& op )
#endif
}
*/
}
}
......
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