diff --git a/modules/img_hash/CMakeLists.txt b/modules/img_hash/CMakeLists.txt new file mode 100644 index 000000000..3e2effab8 --- /dev/null +++ b/modules/img_hash/CMakeLists.txt @@ -0,0 +1,3 @@ +set(the_description "Image hash algorithms") +set(OPENCV_MODULE_IS_PART_OF_WORLD OFF) +ocv_define_module(img_hash opencv_imgproc opencv_core WRAP java python) diff --git a/modules/img_hash/README.md b/modules/img_hash/README.md new file mode 100644 index 000000000..1ee2b7333 --- /dev/null +++ b/modules/img_hash/README.md @@ -0,0 +1,5 @@ +Image Hashing algorithms +======================== + +This module is intended to port the algorithms from PHash library and implement other image hash +algorithm do not exist in PHash library yet. diff --git a/modules/img_hash/doc/attack_performance.JPG b/modules/img_hash/doc/attack_performance.JPG new file mode 100644 index 000000000..a2429dd7d Binary files /dev/null and b/modules/img_hash/doc/attack_performance.JPG differ diff --git a/modules/img_hash/doc/hash_comparison_chart.JPG b/modules/img_hash/doc/hash_comparison_chart.JPG new file mode 100644 index 000000000..1cd99d1e0 Binary files /dev/null and b/modules/img_hash/doc/hash_comparison_chart.JPG differ diff --git a/modules/img_hash/doc/hash_computation_chart.JPG b/modules/img_hash/doc/hash_computation_chart.JPG new file mode 100644 index 000000000..5945c22e0 Binary files /dev/null and b/modules/img_hash/doc/hash_computation_chart.JPG differ diff --git a/modules/img_hash/doc/img_hash.bib b/modules/img_hash/doc/img_hash.bib new file mode 100644 index 000000000..34cb0c5b3 --- /dev/null +++ b/modules/img_hash/doc/img_hash.bib @@ -0,0 +1,23 @@ +@misc{lookslikeit, + author={Krawetz, Neal}, + title={Looks Like It}, + url={http://www.hackerfactor.com/blog/?/archives/432-Looks-Like-It.html} +} + +@article{tang2012perceptual, + title={Perceptual hashing for color images using invariant moments}, + author={Tang, Zhenjun and Dai, Yumin and Zhang, Xianquan}, + journal={Appl. Math}, + volume={6}, + number={2S}, + pages={643S--650S}, + year={2012}, + url={http://www.phash.org/docs/pubs/thesis_zauner.pdf} +} + +@article{zauner2010implementation, + title={Implementation and benchmarking of perceptual image hash functions}, + author={Zauner, Christoph}, + year={2010}, + publisher={na} +} diff --git a/modules/img_hash/include/opencv2/img_hash.hpp b/modules/img_hash/include/opencv2/img_hash.hpp new file mode 100644 index 000000000..5e7a92846 --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash.hpp @@ -0,0 +1,78 @@ +// 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_IMG_HASH_H +#define OPENCV_IMG_HASH_H + +#include "opencv2/img_hash/average_hash.hpp" +#include "opencv2/img_hash/block_mean_hash.hpp" +#include "opencv2/img_hash/color_moment_hash.hpp" +#include "opencv2/img_hash/marr_hildreth_hash.hpp" +#include "opencv2/img_hash/phash.hpp" +#include "opencv2/img_hash/radial_variance_hash.hpp" + +/** +@defgroup img_hash The module brings implementations of different image hashing algorithms. + +Provide algorithms to extract the hash of images and fast way to figure out most similar images in +huge data set. + +Namespace for all functions is cv::img_hash. + +### Supported Algorithms + +- Average hash (also called Different hash) +- PHash (also called Perceptual hash) +- Marr Hildreth Hash +- Radial Variance Hash +- Block Mean Hash (modes 0 and 1) +- Color Moment Hash (this is the one and only hash algorithm resist to rotation attack(-90~90 degree)) + +You can study more about image hashing from following paper and websites: + +- "Implementation and benchmarking of perceptual image hash functions" @cite zauner2010implementation +- "Looks Like It" @cite lookslikeit + +### Code Example + +@include samples/hash_samples.cpp + +### Performance under different attacks + +![Performance chart](img_hash/doc/attack_performance.JPG) + +### Speed comparison with PHash library (100 images from ukbench) + +![Hash Computation chart](img_hash/doc/hash_computation_chart.JPG) +![Hash comparison chart](img_hash/doc/hash_comparison_chart.JPG) + +As you can see, hash computation speed of img_hash module outperform [PHash library](http://www.phash.org/) a lot. + +PS : I do not list out the comparison of Average hash, PHash and Color Moment hash, because I cannot +find them in PHash. + +### Motivation + +Collects useful image hash algorithms into opencv, so we do not need to rewrite them by ourselves +again and again or rely on another 3rd party library(ex : PHash library). BOVW or correlation +matching are good and robust, but they are very slow compare with image hash, if you need to deal +with large scale CBIR(content based image retrieval) problem, image hash is a more reasonable +solution. + +### More info + +You can learn more about img_hash modules from following links, these links show you how to find +similar image from ukbench dataset, provide thorough benchmark of different attacks(contrast, blur, +noise(gaussion,pepper and salt), jpeg compression, watermark, resize). + +* [Introduction to image hash module of opencv](http://qtandopencv.blogspot.my/2016/06/introduction-to-image-hash-module-of.html) +* [Speed up image hashing of opencv(img_hash) and introduce color moment hash](http://qtandopencv.blogspot.my/2016/06/speed-up-image-hashing-of-opencvimghash.html) + +### Contributors + +Tham Ngap Wei, thamngapwei@gmail.com + +*/ + +#endif // OPENCV_IMG_HASH_H diff --git a/modules/img_hash/include/opencv2/img_hash/average_hash.hpp b/modules/img_hash/include/opencv2/img_hash/average_hash.hpp new file mode 100644 index 000000000..1204441c3 --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/average_hash.hpp @@ -0,0 +1,39 @@ +// 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_AVERAGE_HASH_HPP +#define OPENCV_AVERAGE_HASH_HPP + +#include "img_hash_base.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + +/** @brief Computes average hash value of the input image + +This is a fast image hashing algorithm, but only work on simple case. For more details, please +refer to @cite lookslikeit +*/ +class CV_EXPORTS_W AverageHash : public ImgHashBase +{ +public: + CV_WRAP static Ptr create(); +protected: + AverageHash() {} +}; + +/** @brief Calculates img_hash::AverageHash in one call +@param inputArr input image want to compute hash value, type should be CV_8UC4, CV_8UC3 or CV_8UC1. +@param outputArr Hash value of input, it will contain 16 hex decimal number, return type is CV_8U +*/ +CV_EXPORTS_W void averageHash(cv::InputArray inputArr, cv::OutputArray outputArr); + +//! @} + +}} // cv::img_hash:: + +#endif // OPENCV_AVERAGE_HASH_HPP diff --git a/modules/img_hash/include/opencv2/img_hash/block_mean_hash.hpp b/modules/img_hash/include/opencv2/img_hash/block_mean_hash.hpp new file mode 100644 index 000000000..65d0a037b --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/block_mean_hash.hpp @@ -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. + +#ifndef OPENCV_BLOCK_MEAN_HASH_HPP +#define OPENCV_BLOCK_MEAN_HASH_HPP + +#include "img_hash_base.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + +enum BlockMeanHashMode +{ + BLOCK_MEAN_HASH_MODE_0 = 0, //!< use fewer block and generate 16*16/8 uchar hash value + BLOCK_MEAN_HASH_MODE_1 = 1, //!< use block blocks(step sizes/2), generate 31*31/8 + 1 uchar hash value +}; + +/** @brief Image hash based on block mean. + +See @cite zauner2010implementation for details. +*/ +class CV_EXPORTS_W BlockMeanHash : public ImgHashBase +{ +public: + /** @brief Create BlockMeanHash object + @param mode + */ + CV_WRAP void setMode(int mode); + CV_WRAP std::vector getMean() const; + CV_WRAP static Ptr create(int mode = BLOCK_MEAN_HASH_MODE_0); +protected: + BlockMeanHash() {} +}; + +/** @brief Computes block mean hash of the input image + @param inputArr input image want to compute hash value, type should be CV_8UC4, CV_8UC3 or CV_8UC1. + @param outputArr Hash value of input, it will contain 16 hex decimal number, return type is CV_8U + @param mode +*/ +CV_EXPORTS_W void blockMeanHash(cv::InputArray inputArr, + cv::OutputArray outputArr, + int mode = BLOCK_MEAN_HASH_MODE_0); + +//! @} + +}} // cv::img_hash:: + +#endif // OPENCV_BLOCK_MEAN_HASH_HPP diff --git a/modules/img_hash/include/opencv2/img_hash/color_moment_hash.hpp b/modules/img_hash/include/opencv2/img_hash/color_moment_hash.hpp new file mode 100644 index 000000000..d0a820bd9 --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/color_moment_hash.hpp @@ -0,0 +1,41 @@ +// 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_COLOR_MOMENT_HASH_HPP +#define OPENCV_COLOR_MOMENT_HASH_HPP + +#include "img_hash_base.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + +/** @brief Image hash based on color moments. + +See @cite tang2012perceptual for details. +*/ +class CV_EXPORTS_W ColorMomentHash : public ImgHashBase +{ +public: + CV_WRAP static Ptr create(); +protected: + ColorMomentHash() {} +}; + +/** @brief Computes color moment hash of the input, the algorithm + is come from the paper "Perceptual Hashing for Color Images + Using Invariant Moments" + @param inputArr input image want to compute hash value, + type should be CV_8UC4, CV_8UC3 or CV_8UC1. + @param outputArr 42 hash values with type CV_64F(double) + */ +CV_EXPORTS_W void colorMomentHash(cv::InputArray inputArr, cv::OutputArray outputArr); + +//! @} + +}} // cv::img_hash:: + +#endif // OPENCV_COLOR_MOMENT_HASH_HPP diff --git a/modules/img_hash/include/opencv2/img_hash/img_hash_base.hpp b/modules/img_hash/include/opencv2/img_hash/img_hash_base.hpp new file mode 100644 index 000000000..f0cc451ff --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/img_hash_base.hpp @@ -0,0 +1,46 @@ +// 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_IMG_HASH_BASE_HPP +#define OPENCV_IMG_HASH_BASE_HPP + +#include "opencv2/core.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + +/** @brief The base class for image hash algorithms + */ +class CV_EXPORTS_W ImgHashBase : public Algorithm +{ +public: + class ImgHashImpl; + + ~ImgHashBase(); + /** @brief Computes hash of the input image + @param inputArr input image want to compute hash value + @param outputArr hash of the image + */ + CV_WRAP void compute(cv::InputArray inputArr, cv::OutputArray outputArr); + /** @brief Compare the hash value between inOne and inTwo + @param hashOne Hash value one + @param hashTwo Hash value two + @return value indicate similarity between inOne and inTwo, the meaning + of the value vary from algorithms to algorithms + */ + CV_WRAP double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const; +protected: + ImgHashBase(); +protected: + Ptr pImpl; +}; + +//! @} + +} } // cv::img_hash:: + +#endif // OPENCV_IMG_HASH_BASE_HPP diff --git a/modules/img_hash/include/opencv2/img_hash/marr_hildreth_hash.hpp b/modules/img_hash/include/opencv2/img_hash/marr_hildreth_hash.hpp new file mode 100644 index 000000000..a9b04f9f9 --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/marr_hildreth_hash.hpp @@ -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_MARR_HILDRETH_HASH_HPP +#define OPENCV_MARR_HILDRETH_HASH_HPP + +#include "img_hash_base.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + +/** @brief Marr-Hildreth Operator Based Hash, slowest but more discriminative. + +See @cite zauner2010implementation for details. +*/ +class CV_EXPORTS_W MarrHildrethHash : public ImgHashBase +{ +public: + /** + * @brief self explain + */ + CV_WRAP float getAlpha() const; + + /** + * @brief self explain + */ + CV_WRAP float getScale() const; + + /** @brief Set Mh kernel parameters + @param alpha int scale factor for marr wavelet (default=2). + @param scale int level of scale factor (default = 1) + */ + CV_WRAP void setKernelParam(float alpha, float scale); + + /** + @param alpha int scale factor for marr wavelet (default=2). + @param scale int level of scale factor (default = 1) + */ + CV_WRAP static Ptr create(float alpha = 2.0f, float scale = 1.0f); +protected: + MarrHildrethHash() {} +}; + +/** @brief Computes average hash value of the input image + @param inputArr input image want to compute hash value, + type should be CV_8UC4, CV_8UC3, CV_8UC1. + @param outputArr Hash value of input, it will contain 16 hex + decimal number, return type is CV_8U + @param alpha int scale factor for marr wavelet (default=2). + @param scale int level of scale factor (default = 1) +*/ +CV_EXPORTS_W void marrHildrethHash(cv::InputArray inputArr, + cv::OutputArray outputArr, + float alpha = 2.0f, float scale = 1.0f); + +//! @} + +}} // cv::img_hash:: + +#endif // OPENCV_MARR_HILDRETH_HASH_HPP diff --git a/modules/img_hash/include/opencv2/img_hash/phash.hpp b/modules/img_hash/include/opencv2/img_hash/phash.hpp new file mode 100644 index 000000000..d57cd6f3a --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/phash.hpp @@ -0,0 +1,41 @@ +// 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_PHASH_HPP +#define OPENCV_PHASH_HPP + +#include "img_hash_base.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + +/** @brief pHash + +Slower than average_hash, but tolerant of minor modifications + +This algorithm can combat more variation than averageHash, for more details please refer to @cite lookslikeit +*/ +class CV_EXPORTS_W PHash : public ImgHashBase +{ +public: + CV_WRAP static Ptr create(); +protected: + PHash() {} +}; + +/** @brief Computes pHash value of the input image + @param inputArr input image want to compute hash value, + type should be CV_8UC4, CV_8UC3, CV_8UC1. + @param outputArr Hash value of input, it will contain 8 uchar value +*/ +CV_EXPORTS_W void pHash(cv::InputArray inputArr, cv::OutputArray outputArr); + +//! @} + +} } // cv::img_hash:: + +#endif // OPENCV_PHASH_HPP diff --git a/modules/img_hash/include/opencv2/img_hash/radial_variance_hash.hpp b/modules/img_hash/include/opencv2/img_hash/radial_variance_hash.hpp new file mode 100644 index 000000000..455f2850e --- /dev/null +++ b/modules/img_hash/include/opencv2/img_hash/radial_variance_hash.hpp @@ -0,0 +1,58 @@ +// 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_RADIAL_VARIANCE_HASH_HPP +#define OPENCV_RADIAL_VARIANCE_HASH_HPP + +#include "img_hash_base.hpp" + +namespace cv { +namespace img_hash { + +//! @addtogroup img_hash +//! @{ + + +/** @brief Image hash based on Radon transform. + +See @cite tang2012perceptual for details. +*/ +class CV_EXPORTS_W RadialVarianceHash : public ImgHashBase +{ +public: + CV_WRAP static Ptr create(double sigma = 1, int numOfAngleLine = 180); + + CV_WRAP int getNumOfAngleLine() const; + CV_WRAP double getSigma() const; + + CV_WRAP void setNumOfAngleLine(int value); + CV_WRAP void setSigma(double value); + + // internals + std::vector getFeatures(); + cv::Mat getHash(); + Mat getPixPerLine(Mat const &input); + Mat getProjection(); +protected: + RadialVarianceHash() {} +}; + +/** @brief Computes radial variance hash of the input image + @param inputArr input image want to compute hash value, + type should be CV_8UC4, CV_8UC3, CV_8UC1. + @param outputArr Hash value of input + @param sigma Gaussian kernel standard deviation + @param numOfAngleLine The number of angles to consider + */ +CV_EXPORTS_W void radialVarianceHash(cv::InputArray inputArr, + cv::OutputArray outputArr, + double sigma = 1, + int numOfAngleLine = 180); + + +//! @} + +}} // cv::img_hash:: + +#endif // OPENCV_RADIAL_VARIANCE_HASH_HPP diff --git a/modules/img_hash/samples/hash_samples.cpp b/modules/img_hash/samples/hash_samples.cpp new file mode 100644 index 000000000..561f1ed75 --- /dev/null +++ b/modules/img_hash/samples/hash_samples.cpp @@ -0,0 +1,53 @@ +#include "opencv2/core.hpp" +#include "opencv2/core/ocl.hpp" +#include "opencv2/highgui.hpp" +#include "opencv2/img_hash.hpp" + +#include + +using namespace cv; +using namespace cv::img_hash; +using namespace std; + +template +inline void test_one(const std::string &title, const Mat &a, const Mat &b) +{ + cout << "=== " << title << " ===" << endl; + TickMeter tick; + Mat hashA, hashB; + Ptr func; + func = T::create(); + + tick.reset(); tick.start(); + func->compute(a, hashA); + tick.stop(); + cout << "compute1: " << tick.getTimeMilli() << " ms" << endl; + + tick.reset(); tick.start(); + func->compute(b, hashB); + tick.stop(); + cout << "compute2: " << tick.getTimeMilli() << " ms" << endl; + + cout << "compare: " << func->compare(hashA, hashB) << endl << endl;; +} + +int main(int argc, char **argv) +{ + if (argc != 3) + { + cerr << "must input the path of input image and target image. ex : hash_samples lena.jpg lena2.jpg" << endl; + return -1; + } + ocl::setUseOpenCL(false); + + Mat input = imread(argv[1]); + Mat target = imread(argv[2]); + + test_one("AverageHash", input, target); + test_one("PHash", input, target); + test_one("MarrHildrethHash", input, target); + test_one("RadialVarianceHash", input, target); + test_one("BlockMeanHash", input, target); + + return 0; +} diff --git a/modules/img_hash/src/average_hash.cpp b/modules/img_hash/src/average_hash.cpp new file mode 100644 index 000000000..142b089bf --- /dev/null +++ b/modules/img_hash/src/average_hash.cpp @@ -0,0 +1,86 @@ +// 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" + +using namespace cv; +using namespace std; +using namespace img_hash; + +namespace { + +class AverageHashImpl : public ImgHashBase::ImgHashImpl +{ +private: + cv::Mat bitsImg; + cv::Mat grayImg; + cv::Mat resizeImg; + +public: + + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) + { + cv::Mat const input = inputArr.getMat(); + CV_Assert(input.type() == CV_8UC4 || + input.type() == CV_8UC3 || + input.type() == CV_8U); + + cv::resize(input, resizeImg, cv::Size(8,8)); + if(input.type() == CV_8UC3) + { + cv::cvtColor(resizeImg, grayImg, CV_BGR2GRAY); + } + else if(input.type() == CV_8UC4) + { + cv::cvtColor(resizeImg, grayImg, CV_BGRA2GRAY); + } + else + { + grayImg = resizeImg; + } + + uchar const imgMean = static_cast(cvRound(cv::mean(grayImg)[0])); + cv::compare(grayImg, imgMean, bitsImg, CMP_GT); + bitsImg /= 255; + outputArr.create(1, 8, CV_8U); + cv::Mat hash = outputArr.getMat(); + uchar *hash_ptr = hash.ptr(0); + uchar const *bits_ptr = bitsImg.ptr(0); + std::bitset<8> bits; + for(size_t i = 0, j = 0; i != bitsImg.total(); ++j) + { + for(size_t k = 0; k != 8; ++k) + { + //avoid warning C4800, casting do not work + bits[k] = bits_ptr[i++] != 0; + } + hash_ptr[j] = static_cast(bits.to_ulong()); + } + } + + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const + { + return norm(hashOne, hashTwo, NORM_HAMMING); + } +}; + +} // namespace:: + +//================================================================================================== + +namespace cv { namespace img_hash { + +Ptr AverageHash::create() +{ + Ptr res(new AverageHash()); + res->pImpl = makePtr(); + return res; +} + +void averageHash(cv::InputArray inputArr, cv::OutputArray outputArr) +{ + AverageHashImpl().compute(inputArr, outputArr); +} + +}} // cv::img_hash:: diff --git a/modules/img_hash/src/block_mean_hash.cpp b/modules/img_hash/src/block_mean_hash.cpp new file mode 100644 index 000000000..e60b1e625 --- /dev/null +++ b/modules/img_hash/src/block_mean_hash.cpp @@ -0,0 +1,167 @@ +// 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" + +using namespace cv; +using namespace cv::img_hash; +using namespace std; + +namespace { + +enum +{ + imgWidth = 256, + imgHeight = 256, + blockWidth = 16, + blockHeigth = 16, + blockPerCol = imgHeight / blockHeigth, + blockPerRow = imgWidth / blockWidth, + rowSize = imgHeight - blockHeigth, + colSize = imgWidth - blockWidth +}; + +class BlockMeanHashImpl : public ImgHashBase::ImgHashImpl +{ +public: + BlockMeanHashImpl(int mode) + { + setMode(mode); + } + + ~BlockMeanHashImpl() {} + + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) + { + cv::Mat const input = inputArr.getMat(); + CV_Assert(input.type() == CV_8UC4 || + input.type() == CV_8UC3 || + input.type() == CV_8U); + + cv::resize(input, resizeImg_, cv::Size(imgWidth,imgHeight)); + if(input.type() == CV_8UC3) + { + cv::cvtColor(resizeImg_, grayImg_, CV_BGR2GRAY); + } + else if(input.type() == CV_8UC4) + { + cv::cvtColor(resizeImg_, grayImg_, CV_BGRA2GRAY); + } + else + { + grayImg_ = resizeImg_; + } + + int pixColStep = blockWidth; + int pixRowStep = blockHeigth; + int numOfBlocks = 0; + switch(mode_) + { + case BLOCK_MEAN_HASH_MODE_0: + { + numOfBlocks = blockPerCol * blockPerRow; + break; + } + case BLOCK_MEAN_HASH_MODE_1: + { + pixColStep /= 2; + pixRowStep /= 2; + numOfBlocks = (blockPerCol*2-1) * (blockPerRow*2-1); + break; + } + default: + break; + } + + mean_.resize(numOfBlocks); + findMean(pixRowStep, pixColStep); + outputArr.create(1, numOfBlocks/8 + numOfBlocks % 8, CV_8U); + cv::Mat hash = outputArr.getMat(); + createHash(hash); + } + + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const + { + return norm(hashOne, hashTwo, NORM_HAMMING); + } + + void setMode(int mode) + { + CV_Assert(mode == BLOCK_MEAN_HASH_MODE_0 || mode == BLOCK_MEAN_HASH_MODE_1); + mode_ = mode; + } + + void createHash(cv::Mat &hash) + { + double const median = cv::mean(grayImg_)[0]; + uchar *hashPtr = hash.ptr(0); + std::bitset<8> bits = 0; + for(size_t i = 0; i < mean_.size(); ++i) + { + size_t const residual = i%8; + bits[residual] = mean_[i] < median ? 0 : 1; + if(residual == 7) + { + *hashPtr = static_cast(bits.to_ulong()); + ++hashPtr; + }else if(i == mean_.size() - 1) + { + *hashPtr = bits[residual]; + } + } + } + void findMean(int pixRowStep, int pixColStep) + { + size_t blockIdx = 0; + for(int row = 0; row <= rowSize; row += pixRowStep) + { + for(int col = 0; col <= colSize; col += pixColStep) + { + mean_[blockIdx++] = cv::mean(grayImg_(cv::Rect(col, row, blockWidth, blockHeigth)))[0]; + } + } + } + + cv::Mat grayImg_; + std::vector mean_; + int mode_; + cv::Mat resizeImg_; +}; + +inline BlockMeanHashImpl *getLocalImpl(ImgHashBase::ImgHashImpl *ptr) +{ + BlockMeanHashImpl * impl = static_cast(ptr); + CV_Assert(impl); + return impl; +} + +} + +//================================================================================================== + +namespace cv { namespace img_hash { + +Ptr BlockMeanHash::create(int mode) +{ + Ptr res(new BlockMeanHash); + res->pImpl = makePtr(mode); + return res; +} + +void BlockMeanHash::setMode(int mode) +{ + getLocalImpl(pImpl)->setMode(mode); +} + +std::vector BlockMeanHash::getMean() const +{ + return getLocalImpl(pImpl)->mean_; +} + +void blockMeanHash(cv::InputArray inputArr, cv::OutputArray outputArr, int mode) +{ + BlockMeanHashImpl(mode).compute(inputArr, outputArr); +} + +}} // cv::img_hash:: diff --git a/modules/img_hash/src/color_moment_hash.cpp b/modules/img_hash/src/color_moment_hash.cpp new file mode 100644 index 000000000..006f40efd --- /dev/null +++ b/modules/img_hash/src/color_moment_hash.cpp @@ -0,0 +1,95 @@ +// 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" + +using namespace cv; +using namespace cv::img_hash; +using namespace std; + +namespace { + +class ColorMomentHashImpl : public ImgHashBase::ImgHashImpl +{ +public: + ~ColorMomentHashImpl() {} + + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) + { + cv::Mat const input = inputArr.getMat(); + CV_Assert(input.type() == CV_8UC4 || + input.type() == CV_8UC3 || + input.type() == CV_8U); + + if(input.type() == CV_8UC3) + { + colorImg_ = input; + } + else if(input.type() == CV_8UC4) + { + cv::cvtColor(input, colorImg_, CV_BGRA2BGR); + } + else + { + cv::cvtColor(input, colorImg_, CV_GRAY2BGR); + } + + cv::resize(colorImg_, resizeImg_, cv::Size(512,512), 0, 0, + INTER_CUBIC); + cv::GaussianBlur(resizeImg_, blurImg_, cv::Size(3,3), 0, 0); + + cv::cvtColor(blurImg_, colorSpace_, CV_BGR2HSV); + cv::split(colorSpace_, channels_); + outputArr.create(1, 42, CV_64F); + cv::Mat hash = outputArr.getMat(); + hash.setTo(0); + computeMoments(hash.ptr(0)); + + cv::cvtColor(blurImg_, colorSpace_, CV_BGR2YCrCb); + cv::split(colorSpace_, channels_); + computeMoments(hash.ptr(0) + 21); + } + + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const + { + return norm(hashOne, hashTwo, NORM_L2) * 10000; + } + +private: + void computeMoments(double *inout) + { + for(size_t i = 0; i != channels_.size(); ++i) + { + cv::HuMoments(cv::moments(channels_[i]), inout); + inout += 7; + } + } + +private: + cv::Mat blurImg_; + cv::Mat colorImg_; + std::vector channels_; + cv::Mat colorSpace_; + cv::Mat resizeImg_; +}; + +} + +//================================================================================================== + +namespace cv { namespace img_hash { + +Ptr ColorMomentHash::create() +{ + Ptr res(new ColorMomentHash); + res->pImpl = makePtr(); + return res; +} + +void colorMomentHash(cv::InputArray inputArr, cv::OutputArray outputArr) +{ + ColorMomentHashImpl().compute(inputArr, outputArr); +} + +} } // cv::img_hash:: diff --git a/modules/img_hash/src/img_hash_base.cpp b/modules/img_hash/src/img_hash_base.cpp new file mode 100644 index 000000000..2a5b633b8 --- /dev/null +++ b/modules/img_hash/src/img_hash_base.cpp @@ -0,0 +1,28 @@ +// 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" + +namespace cv { +namespace img_hash{ + +ImgHashBase::ImgHashBase() +{ +} + +ImgHashBase::~ImgHashBase() +{ +} + +void ImgHashBase::compute(cv::InputArray inputArr, cv::OutputArray outputArr) +{ + pImpl->compute(inputArr, outputArr); +} + +double ImgHashBase::compare(cv::InputArray hashOne, cv::InputArray hashTwo) const +{ + return pImpl->compare(hashOne, hashTwo); +} + +} } // cv::img_hash:: diff --git a/modules/img_hash/src/marr_hildreth_hash.cpp b/modules/img_hash/src/marr_hildreth_hash.cpp new file mode 100644 index 000000000..54a6e85be --- /dev/null +++ b/modules/img_hash/src/marr_hildreth_hash.cpp @@ -0,0 +1,212 @@ +// 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" + +using namespace cv; +using namespace cv::img_hash; +using namespace std; + +namespace { + +void getMHKernel(float alpha, float level, cv::Mat &kernel) +{ + int const sigma = static_cast(4*std::pow(alpha,level)); + + float const ratio = std::pow(alpha, -level); + kernel.create(2*sigma+1, 2*sigma+1, CV_32F); + for(int row = 0; row != kernel.rows; ++row) + { + float const ydiff = static_cast(row - sigma); + float const ypos = ratio * ydiff; + float const yposPow2 = ypos * ypos; + float *kPtr = kernel.ptr(row); + for(int col = 0; col != kernel.cols; ++col) + { + float const xpos = ratio * static_cast((col - sigma)); + float const a = xpos * xpos + yposPow2; + kPtr[col] = (2-a)*std::exp(a/2); + } + } +} + +void fillBlocks(cv::Mat const &freImg, cv::Mat &blocks) +{ + //TODO : use forEach may provide better speed, however, + //it is quite tedious to apply without lambda + blocks.setTo(0); + for(int row = 0; row != blocks.rows; ++row) + { + float *bptr = blocks.ptr(row); + int const rOffset = row*16; + for(int col = 0; col != blocks.cols; ++col) + { + cv::Rect const roi(rOffset,col*16,16,16); + bptr[col] = + static_cast(cv::sum(freImg(roi))[0]); + } + } +} + +void createHash(cv::Mat const &blocks, cv::Mat &hash) +{ + int hash_index = 0; + int bit_index = 0; + uchar hashbyte = 0; + uchar *hashPtr = hash.ptr(0); + for (int row=0; row < 29; row += 4) + { + for (int col=0; col < 29; col += 4) + { + cv::Rect const roi(col,row,3,3); + cv::Mat const blockROI = blocks(roi); + float const avg = + static_cast(cv::sum(blockROI)[0]/9.0); + for(int i = 0; i != blockROI.rows; ++i) + { + float const *bptr = blockROI.ptr(i); + for(int j = 0; j != blockROI.cols; ++j) + { + hashbyte <<= 1; + if (bptr[j] > avg) + { + hashbyte |= 0x01; + } + ++bit_index; + if ((bit_index%8) == 0) + { + hash_index = (bit_index/8) - 1; + hashPtr[hash_index] = hashbyte; + hashbyte = 0x00; + } + } + } + } + } +} + +class MarrHildrethHashImpl : public ImgHashBase::ImgHashImpl +{ +public: + + MarrHildrethHashImpl(float alpha = 2.0f, float scale = 1.0f) : alphaVal(alpha), scaleVal(scale) + { + getMHKernel(alphaVal, scaleVal, mhKernel); + blocks.create(31,31, CV_32F); + } + + ~MarrHildrethHashImpl() { } + + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) + { + cv::Mat const input = inputArr.getMat(); + CV_Assert(input.type() == CV_8UC4 || + input.type() == CV_8UC3 || + input.type() == CV_8U); + + if(input.type() == CV_8UC3) + { + cv::cvtColor(input, grayImg, CV_BGR2GRAY); + } + else if(input.type() == CV_8UC4) + { + cv::cvtColor(input, grayImg, CV_BGRA2GRAY); + } + else + { + grayImg = input; + } + //pHash use Canny-deritch filter to blur the image + cv::GaussianBlur(grayImg, blurImg, cv::Size(7, 7), 0); + cv::resize(blurImg, resizeImg, cv::Size(512, 512), 0, 0, INTER_CUBIC); + cv::equalizeHist(resizeImg, equalizeImg); + + //extract frequency info by mh kernel + cv::filter2D(equalizeImg, freImg, CV_32F, mhKernel); + fillBlocks(freImg, blocks); + + outputArr.create(1, 72, CV_8U); + cv::Mat hash = outputArr.getMat(); + createHash(blocks, hash); + } + + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const + { + return norm(hashOne, hashTwo, NORM_HAMMING); + } + + float getAlpha() const + { + return alphaVal; + } + + float getScale() const + { + return scaleVal; + } + + void setKernelParam(float alpha, float scale) + { + alphaVal = alpha; + scaleVal = scale; + getMHKernel(alphaVal, scaleVal, mhKernel); + } + +friend class MarrHildrethHash; + +private: + float alphaVal; + cv::Mat blocks; + cv::Mat blurImg; + cv::Mat equalizeImg; + cv::Mat freImg; //frequency response image + cv::Mat grayImg; + cv::Mat mhKernel; + cv::Mat resizeImg; + float scaleVal; +}; + +inline MarrHildrethHashImpl *getLocalImpl(ImgHashBase::ImgHashImpl *ptr) +{ + MarrHildrethHashImpl * impl = static_cast(ptr); + CV_Assert(impl); + return impl; +} + +} + +//================================================================================================== + +namespace cv { namespace img_hash { + +float MarrHildrethHash::getAlpha() const +{ + return getLocalImpl(pImpl)->getAlpha(); +} + +float MarrHildrethHash::getScale() const +{ + return getLocalImpl(pImpl)->getScale(); +} + +void MarrHildrethHash::setKernelParam(float alpha, float scale) +{ + getLocalImpl(pImpl)->setKernelParam(alpha, scale); +} + +Ptr MarrHildrethHash::create(float alpha, float scale) +{ + Ptr res(new MarrHildrethHash); + res->pImpl = makePtr(alpha, scale); + return res; +} + +void marrHildrethHash(cv::InputArray inputArr, + cv::OutputArray outputArr, + float alpha, float scale) +{ + MarrHildrethHashImpl(alpha, scale).compute(inputArr, outputArr); +} + +} } // cv::img_hash:: diff --git a/modules/img_hash/src/phash.cpp b/modules/img_hash/src/phash.cpp new file mode 100644 index 000000000..6ee866210 --- /dev/null +++ b/modules/img_hash/src/phash.cpp @@ -0,0 +1,93 @@ +// 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" + +using namespace cv; +using namespace cv::img_hash; +using namespace std; + +namespace { + +class PHashImpl : public ImgHashBase::ImgHashImpl +{ +public: + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) + { + cv::Mat const input = inputArr.getMat(); + CV_Assert(input.type() == CV_8UC4 || + input.type() == CV_8UC3 || + input.type() == CV_8U); + + cv::resize(input, resizeImg, cv::Size(32,32)); + if(input.type() == CV_8UC3) + { + cv::cvtColor(resizeImg, grayImg, CV_BGR2GRAY); + } + else if(input.type() == CV_8UC4) + { + cv::cvtColor(resizeImg, grayImg, CV_BGRA2GRAY); + } + else + { + grayImg = resizeImg; + } + + grayImg.convertTo(grayFImg, CV_32F); + cv::dct(grayFImg, dctImg); + dctImg(cv::Rect(0, 0, 8, 8)).copyTo(topLeftDCT); + topLeftDCT.at(0, 0) = 0; + float const imgMean = static_cast(cv::mean(topLeftDCT)[0]); + + cv::compare(topLeftDCT, imgMean, bitsImg, CMP_GT); + bitsImg /= 255; + outputArr.create(1, 8, CV_8U); + cv::Mat hash = outputArr.getMat(); + uchar *hash_ptr = hash.ptr(0); + uchar const *bits_ptr = bitsImg.ptr(0); + std::bitset<8> bits; + for(size_t i = 0, j = 0; i != bitsImg.total(); ++j) + { + for(size_t k = 0; k != 8; ++k) + { + //avoid warning C4800, casting do not work + bits[k] = bits_ptr[i++] != 0; + } + hash_ptr[j] = static_cast(bits.to_ulong()); + } + } + + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const + { + return norm(hashOne, hashTwo, NORM_HAMMING); + } + +private: + cv::Mat bitsImg; + cv::Mat dctImg; + cv::Mat grayFImg; + cv::Mat grayImg; + cv::Mat resizeImg; + cv::Mat topLeftDCT; +}; + +} // namespace:: + +//================================================================================================== + +namespace cv { namespace img_hash { + +Ptr PHash::create() +{ + Ptr res(new PHash); + res->pImpl = makePtr(); + return res; +} + +void pHash(cv::InputArray inputArr, cv::OutputArray outputArr) +{ + PHashImpl().compute(inputArr, outputArr); +} + +} } // cv::img_hash:: diff --git a/modules/img_hash/src/precomp.hpp b/modules/img_hash/src/precomp.hpp new file mode 100644 index 000000000..24bb8168a --- /dev/null +++ b/modules/img_hash/src/precomp.hpp @@ -0,0 +1,29 @@ +// 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_IMG_HASH_PRECOMP_H +#define OPENCV_IMG_HASH_PRECOMP_H + +#include "opencv2/core.hpp" +#include "opencv2/core/base.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/imgproc/types_c.h" +#include "opencv2/img_hash.hpp" + +#include +#include + +namespace cv{ namespace img_hash { + +class ImgHashBase::ImgHashImpl +{ +public: + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) = 0; + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const = 0; + virtual ~ImgHashImpl() {} +}; + +}} // cv::img_hash:: + +#endif // OPENCV_IMG_HASH_PRECOMP_H diff --git a/modules/img_hash/src/radial_variance_hash.cpp b/modules/img_hash/src/radial_variance_hash.cpp new file mode 100644 index 000000000..1265abc31 --- /dev/null +++ b/modules/img_hash/src/radial_variance_hash.cpp @@ -0,0 +1,361 @@ +// 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" + +using namespace cv; +using namespace cv::img_hash; +using namespace std; + +namespace { + +enum +{ + hashSize = 40, +}; + +inline float roundingFactor(float val) +{ + return val >= 0 ? 0.5f : -0.5f; +} + +inline int createOffSet(int length) +{ + float const center = static_cast(length/2); + return static_cast(std::floor(center + roundingFactor(center))); +} + +class RadialVarianceHashImpl : public ImgHashBase::ImgHashImpl +{ +public: + cv::Mat blurImg_; + std::vector features_; + cv::Mat grayImg_; + int numOfAngelLine_; + cv::Mat pixPerLine_; + cv::Mat projections_; + double sigma_; + + RadialVarianceHashImpl(double sigma, int numOfAngleLine) + : numOfAngelLine_(numOfAngleLine), sigma_(sigma) + { + } + + ~RadialVarianceHashImpl() {} + + virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) + { + cv::Mat const input = inputArr.getMat(); + CV_Assert(input.type() == CV_8UC4 || + input.type() == CV_8UC3 || + input.type() == CV_8U); + + if(input.type() == CV_8UC3) + { + cv::cvtColor(input, grayImg_, CV_BGR2GRAY); + } + else if(input.type() == CV_8UC4) + { + cv::cvtColor(input, grayImg_, CV_BGRA2GRAY); + } + else + { + grayImg_ = input; + } + + cv::GaussianBlur(grayImg_, blurImg_, cv::Size(0,0), sigma_, sigma_); + radialProjections(blurImg_); + findFeatureVector(); + outputArr.create(1, hashSize, CV_8U); + cv::Mat hash = outputArr.getMat(); + hashCalculate(hash); + } + + virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const + { + cv::Mat const hashOneF = hashOne.getMat(); + cv::Mat const hashTwoF = hashTwo.getMat(); + CV_Assert(hashOneF.cols == hashSize && hashOneF.cols == hashTwoF.cols); + + float bufferOne[hashSize]; + cv::Mat hashFloatOne(1, hashSize, CV_32F, bufferOne); + hashOneF.convertTo(hashFloatOne, CV_32F); + + float bufferTwo[hashSize]; + cv::Mat hashFloatTwo(1, hashSize, CV_32F, bufferTwo); + hashTwoF.convertTo(hashFloatTwo, CV_32F); + + int const pixNum = hashFloatOne.rows * hashFloatOne.cols; + cv::Scalar hOneMean, hOneStd, hTwoMean, hTwoStd; + cv::meanStdDev(hashFloatOne, hOneMean, hOneStd); + cv::meanStdDev(hashFloatTwo, hTwoMean, hTwoStd); + + // Compute covariance and correlation coefficient + hashFloatOne -= hOneMean; + hashFloatTwo -= hTwoMean; + double max = std::numeric_limits::min(); + for(int i = 0; i != hashSize; ++i) + { + double const covar = (hashFloatOne).dot(hashFloatTwo) / pixNum; + double const corre = covar / (hOneStd[0] * hTwoStd[0] + 1e-20); + max = std::max(corre, max); + //move last value to first position, first value to second position, + //second value to third position and so on + float const preValue = bufferTwo[hashSize-1]; + std::copy_backward(bufferTwo, bufferTwo + hashSize - 1, bufferTwo + hashSize); + bufferTwo[0] = preValue; + } + + //return peak correlation coefficient + return max; + } + + int getNumOfAngleLine() const + { + return numOfAngelLine_; + } + double getSigma() const + { + return sigma_; + } + + void setNumOfAngleLine(int value) + { + CV_Assert(value > 0); + numOfAngelLine_ = value; + } + void setSigma(double value) + { + CV_Assert(value >= 1.0); + sigma_ = value; + } + + void afterHalfProjections(cv::Mat const &input, int D, int xOff, int yOff) + { + int *pplPtr = pixPerLine_.ptr(0); + int const init = 3*numOfAngelLine_/4; + for(int k = init, j = 0; k < numOfAngelLine_; ++k, j += 2) + { + float const theta = k*3.14159f/numOfAngelLine_; + float const alpha = std::tan(theta); + uchar *projDown = projections_.ptr(k); + uchar *projUp = projections_.ptr(k-j); + for(int x = 0; x < D; ++x) + { + float const y = alpha*(x-xOff); + int const yd = static_cast(std::floor(y + roundingFactor(y))); + if((yd + yOff >= 0)&&(yd + yOff < input.rows) && (x < input.cols)) + { + projDown[x] = input.at(yd+yOff, x); + pplPtr[k] += 1; + } + if ((yOff - yd >= 0)&&(yOff - yd < input.cols)&& + (2*yOff - x >= 0)&&(2*yOff- x < input.rows)&& + (k != init)) + { + projUp[x] = + input.at(-(x-yOff)+yOff, -yd+yOff); + pplPtr[k-j] += 1; + } + } + } + } + + void findFeatureVector() + { + features_.resize(numOfAngelLine_); + double sum = 0.0; + double sumSqd = 0.0; + int const *pplPtr = pixPerLine_.ptr(0); + for(int k=0; k < numOfAngelLine_; ++k) + { + double lineSum = 0.0; + double lineSumSqd = 0.0; + //original implementation of pHash may generate zero pixNum, this + //will cause NaN value and make the features become less discriminative + //to avoid this problem, I add a small value--0.00001 + double const pixNum = pplPtr[k] + 0.00001; + double const pixNumPow2 = pixNum * pixNum; + uchar const *projPtr = projections_.ptr(k); + for(int i = 0; i < projections_.cols; ++i) + { + double const value = projPtr[i]; + lineSum += value; + lineSumSqd += value * value; + } + features_[k] = (lineSumSqd/pixNum) - + (lineSum*lineSum)/(pixNumPow2); + sum += features_[k]; + sumSqd += features_[k]*features_[k]; + } + double const numOfALPow2 = numOfAngelLine_ * numOfAngelLine_; + double const mean = sum/numOfAngelLine_; + double const var = std::sqrt((sumSqd/numOfAngelLine_) - (sum*sum)/(numOfALPow2)); + for(int i = 0; i < numOfAngelLine_; ++i) + { + features_[i] = (features_[i] - mean)/var; + } + } + + void firstHalfProjections(cv::Mat const &input, int D, int xOff, int yOff) + { + int *pplPtr = pixPerLine_.ptr(0); + for(int k = 0; k < numOfAngelLine_/4+1; ++k) + { + float const theta = k*3.14159f/numOfAngelLine_; + float const alpha = std::tan(theta); + uchar *projOne = projections_.ptr(k); + uchar *projTwo = projections_.ptr(numOfAngelLine_/2-k); + for(int x = 0; x < D; ++x) + { + float const y = alpha*(x-xOff); + int const yd = static_cast(std::floor(y + roundingFactor(y))); + if((yd + yOff >= 0)&&(yd + yOff < input.rows) && (x < input.cols)) + { + projOne[x] = input.at(yd+yOff, x); + pplPtr[k] += 1; + } + if((yd + xOff >= 0) && (yd + xOff < input.cols) && + (k != numOfAngelLine_/4) && (x < input.rows)) + { + projTwo[x] = + input.at(x, yd+xOff); + pplPtr[numOfAngelLine_/2-k] += 1; + } + } + } + } + + void hashCalculate(cv::Mat &hash) + { + double temp[hashSize]; + double max = 0; + double min = 0; + size_t const featureSize = features_.size(); + //constexpr is a better choice + double const sqrtTwo = 1.4142135623730950488016887242097; + for(int k = 0; k < hash.cols; ++k) + { + double sum = 0; + for(size_t n = 0; n < featureSize; ++n) + { + sum += features_[n]*std::cos((3.14159*(2*n+1)*k)/(2*featureSize)); + } + temp[k] = k == 0 ? sum/std::sqrt(featureSize) : + sum*sqrtTwo/std::sqrt(featureSize); + if(temp[k] > max) + { + max = temp[k]; + } + else if(temp[k] < min) + { + min = temp[k]; + } + } + + double const range = max - min; + if(range != 0) + { + //std::transform is a better choice if lambda supported + uchar *hashPtr = hash.ptr(0); + for(int i = 0; i < hash.cols; ++i) + { + hashPtr[i] = static_cast((255*(temp[i] - min)/range)); + } + } + else + { + hash = 0; + } + } + + void radialProjections(cv::Mat const &input) + { + int const D = (input.cols > input.rows) ? input.cols : input.rows; + //Different with PHash, this part reverse the row size and col size, + //because cv::Mat is row major but not column major + projections_.create(numOfAngelLine_, D, CV_8U); + projections_ = 0; + pixPerLine_.create(1, numOfAngelLine_, CV_32S); + pixPerLine_ = 0; + int const xOff = createOffSet(input.cols); + int const yOff = createOffSet(input.rows); + + firstHalfProjections(input, D, xOff, yOff); + afterHalfProjections(input, D, xOff, yOff); + } +}; + +inline RadialVarianceHashImpl *getLocalImpl(ImgHashBase::ImgHashImpl *ptr) +{ + RadialVarianceHashImpl * impl = static_cast(ptr); + CV_Assert(impl); + return impl; +} + +} // namespace:: + +//================================================================================================== + +namespace cv { namespace img_hash { + +Ptr RadialVarianceHash::create(double sigma, int numOfAngleLine) +{ + Ptr res(new RadialVarianceHash); + res->pImpl = makePtr(sigma, numOfAngleLine); + return res; +} + +int RadialVarianceHash::getNumOfAngleLine() const +{ + return getLocalImpl(pImpl)->getNumOfAngleLine(); +} + +double RadialVarianceHash::getSigma() const +{ + return getLocalImpl(pImpl)->getSigma(); +} + +void RadialVarianceHash::setNumOfAngleLine(int value) +{ + getLocalImpl(pImpl)->setNumOfAngleLine(value); +} + +void RadialVarianceHash::setSigma(double value) +{ + getLocalImpl(pImpl)->setSigma(value); +} + +std::vector RadialVarianceHash::getFeatures() +{ + getLocalImpl(pImpl)->findFeatureVector(); + return getLocalImpl(pImpl)->features_; +} + +cv::Mat RadialVarianceHash::getHash() +{ + cv::Mat hash; + getLocalImpl(pImpl)->hashCalculate(hash); + return hash; +} + +Mat RadialVarianceHash::getPixPerLine(Mat const &input) +{ + getLocalImpl(pImpl)->radialProjections(input); + return getLocalImpl(pImpl)->pixPerLine_; +} + +Mat RadialVarianceHash::getProjection() +{ + return getLocalImpl(pImpl)->projections_; +} + +void radialVarianceHash(cv::InputArray inputArr, + cv::OutputArray outputArr, + double sigma, int numOfAngleLine) +{ + RadialVarianceHashImpl(sigma, numOfAngleLine).compute(inputArr, outputArr); +} + +}} // cv::img_hash:: diff --git a/modules/img_hash/test/test_average_hash.cpp b/modules/img_hash/test/test_average_hash.cpp new file mode 100644 index 000000000..146dfb9fd --- /dev/null +++ b/modules/img_hash/test/test_average_hash.cpp @@ -0,0 +1,58 @@ +// 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" + +#include + +using namespace cv; + +class CV_AverageHashTest : public cvtest::BaseTest +{ +public: + CV_AverageHashTest(); + ~CV_AverageHashTest(); +protected: + void run(int /* idx */); +}; + +CV_AverageHashTest::CV_AverageHashTest(){} +CV_AverageHashTest::~CV_AverageHashTest(){} + +void CV_AverageHashTest::run(int ) +{ + cv::Mat const input = (cv::Mat_(8, 8) << + 1, 5, 4, 6, 3, 2, 7, 8, + 2, 4, 8, 9, 2, 1, 4, 3, + 3, 4, 5, 7, 9, 8, 7, 6, + 1, 2, 3, 4, 5, 6, 7, 8, + 8, 7, 2, 3, 6, 4, 5, 1, + 3, 4, 1, 2, 9, 8, 4, 2, + 6, 7, 8, 9, 7, 4, 3, 2, + 8, 7, 6, 5, 4, 3, 2, 1); + cv::Mat hash; + cv::img_hash::averageHash(input, hash); + bool const expectResult[] = + { + 0,0,0,1,0,0,1,1, + 0,0,1,1,0,0,0,0, + 0,0,0,1,1,1,1,1, + 0,0,0,0,0,1,1,1, + 1,1,0,0,1,0,0,0, + 0,0,0,0,1,1,0,0, + 1,1,1,1,1,0,0,0, + 1,1,1,0,0,0,0,0 + }; + uchar const *hashPtr = hash.ptr(0); + for(int i = 0; i != hash.cols; ++i) + { + std::bitset<8> const bits = hashPtr[i]; + for(int j = 0; j != 8; ++j) + { + EXPECT_EQ(bits[j], expectResult[i*8+j]); + } + } +} + +TEST(average_hash_test, accuracy) { CV_AverageHashTest test; test.safe_run(); } diff --git a/modules/img_hash/test/test_block_mean_hash.cpp b/modules/img_hash/test/test_block_mean_hash.cpp new file mode 100644 index 000000000..c23872a82 --- /dev/null +++ b/modules/img_hash/test/test_block_mean_hash.cpp @@ -0,0 +1,212 @@ +// 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" + +#include + +using namespace cv; +using namespace cv::img_hash; + +/** + *The expected results of this test case are come from the Phash library, + *I use it as golden model + */ +class CV_BlockMeanHashTest : public cvtest::BaseTest +{ +public: + CV_BlockMeanHashTest(); +protected: + void run(int /* idx */); + + void testMeanMode0(); + void testMeanMode1(); + void testHashMode0(); + void testHashMode1(); + + cv::Mat input; + cv::Mat hash; + Ptr bmh; +}; + +CV_BlockMeanHashTest::CV_BlockMeanHashTest() +{ + input.create(256, 256, CV_8U); + for(int row = 0; row != input.rows; ++row) + { + uchar value = static_cast(row); + for(int col = 0; col != input.cols; ++col) + { + input.at(row, col) = value++; + } + } + bmh = BlockMeanHash::create(BLOCK_MEAN_HASH_MODE_0); +} + +void CV_BlockMeanHashTest::testMeanMode0() +{ + std::vector const &features = bmh->getMean(); + double const expectResult[] = + {15,31,47,63,79,95,111,127,143,159,175,191,207,223,239,135, + 31,47,63,79,95,111,127,143,159,175,191,207,223,239,135,15, + 47,63,79,95,111,127,143,159,175,191,207,223,239,135,15,31, + 63,79,95,111,127,143,159,175,191,207,223,239,135,15,31,47, + 79,95,111,127,143,159,175,191,207,223,239,135,15,31,47,63, + 95,111,127,143,159,175,191,207,223,239,135,15,31,47,63,79, + 111,127,143,159,175,191,207,223,239,135,15,31,47,63,79,95, + 127,143,159,175,191,207,223,239,135,15,31,47,63,79,95,111, + 143,159,175,191,207,223,239,135,15,31,47,63,79,95,111,127, + 159,175,191,207,223,239,135,15,31,47,63,79,95,111,127,143, + 175,191,207,223,239,135,15,31,47,63,79,95,111,127,143,159, + 191,207,223,239,135,15,31,47,63,79,95,111,127,143,159,175, + 207,223,239,135,15,31,47,63,79,95,111,127,143,159,175,191, + 223,239,135,15,31,47,63,79,95,111,127,143,159,175,191,207, + 239,135,15,31,47,63,79,95,111,127,143,159,175,191,207,223, + 135,15,31,47,63,79,95,111,127,143,159,175,191,207,223,239,}; + for(size_t i = 0; i != features.size(); ++i) + { + ASSERT_NEAR(features[i], expectResult[i], 0.0001); + } +} + +void CV_BlockMeanHashTest::testMeanMode1() +{ + std::vector const &features = bmh->getMean(); + double const expectResult[] = + {15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135, + 23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43, + 31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15, + 39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23, + 47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31, + 55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39, + 63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47, + 71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55, + 79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63, + 87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71, + 95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79, + 103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87, + 111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95, + 119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103, + 127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111, + 135,143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119, + 143,151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127, + 151,159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135, + 159,167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143, + 167,175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151, + 175,183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159, + 183,191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167, + 191,199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175, + 199,207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183, + 207,215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191, + 215,223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199, + 223,231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207, + 231,239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215, + 239,219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223, + 219,135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231, + 135,43,15,23,31,39,47,55,63,71,79,87,95,103,111,119,127,135,143,151,159,167,175,183,191,199,207,215,223,231,239,}; + for(size_t i = 0; i != features.size(); ++i) + { + ASSERT_NEAR(features[i], expectResult[i], 0.0001); + } +} + +void CV_BlockMeanHashTest::testHashMode0() +{ + bool const expectResult[] = + {0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, + 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0, + 0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0, + 0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0, + 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, + 0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0, + 0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0, + 0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1, + 1,1,1,1,1,1,0,0,0,0,0,0,0,0,1,1, + 1,1,1,1,1,0,0,0,0,0,0,0,0,1,1,1, + 1,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1, + 1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1, + 1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1, + 1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1, + }; + + for(int i = 0; i != hash.cols; ++i) + { + std::bitset<8> const bits = hash.at(0, i); + for(size_t j = 0; j != bits.size(); ++j) + { + EXPECT_EQ(expectResult[i*8+j], bits[j]); + } + } +} + +void CV_BlockMeanHashTest::testHashMode1() +{ + bool const expectResult[] = + {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + }; + + for(int i = 0; i != hash.cols; ++i) + { + std::bitset<8> const bits = hash.at(0, i); + if(i != hash.cols-1) + { + for(size_t j = 0; j != bits.size(); ++j) + { + EXPECT_EQ(expectResult[i*8+j], bits[j]); + } + } + else + { + //when mode == 1, there will be 961 block mean + //that is why we only check one bit at here + EXPECT_EQ(expectResult[i*8], bits[0]); + } + } +} + +void CV_BlockMeanHashTest::run(int) +{ + bmh->compute(input, hash); + testMeanMode0(); + testHashMode0(); + + bmh->setMode(BLOCK_MEAN_HASH_MODE_1); + bmh->compute(input, hash); + testMeanMode1(); + testHashMode1(); +} + +TEST(block_mean_hash_test, accuracy) { CV_BlockMeanHashTest test; test.safe_run(); } diff --git a/modules/img_hash/test/test_main.cpp b/modules/img_hash/test/test_main.cpp new file mode 100644 index 000000000..556b026ac --- /dev/null +++ b/modules/img_hash/test/test_main.cpp @@ -0,0 +1,7 @@ +// 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("cv") diff --git a/modules/img_hash/test/test_marr_hildreth_hash.cpp b/modules/img_hash/test/test_marr_hildreth_hash.cpp new file mode 100644 index 000000000..f20e795d7 --- /dev/null +++ b/modules/img_hash/test/test_marr_hildreth_hash.cpp @@ -0,0 +1,60 @@ +// 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" + +using namespace cv; + +class CV_MarrHildrethTest : public cvtest::BaseTest +{ +public: + CV_MarrHildrethTest(); + ~CV_MarrHildrethTest(); +protected: + void run(int /* idx */); +}; + +CV_MarrHildrethTest::CV_MarrHildrethTest(){} +CV_MarrHildrethTest::~CV_MarrHildrethTest(){} + +void CV_MarrHildrethTest::run(int ) +{ + cv::Mat_ input(512,512); + int val = 0; + for(int row = 0; row != input.rows; ++row) + { + for(int col = 0; col != input.cols; ++col) + { + input.at(row, col) = val % 256; + ++val; + } + } + + cv::Mat hash; + cv::img_hash::marrHildrethHash(input, hash); + uchar const expectResult[] = + { + 252, 126, 63, 31, 143, 199, 227, 241, + 248, 252, 126, 63, 31, 143, 199, 227, + 241, 248, 252, 126, 63, 31, 143, 199, + 227, 241, 248, 252, 126, 63, 31, 143, + 199, 227, 241, 248, 31, 143, 199, 227, + 241, 248, 252, 126, 63, 252, 126, 63, + 31, 143, 199, 227, 241, 248, 252, 126, + 63, 31, 143, 199, 227, 241, 248, 252, + 126, 63, 31, 143, 199, 227, 241, 248 + }; + uchar const *hashPtr = hash.ptr(0); + for(int i = 0; i != 72; ++i) + { + if(hashPtr[i] != expectResult[i]) + { + ts->printf(cvtest::TS::LOG, "Wrong hash value \n"); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + return; + } + } +} + +TEST(marr_hildreth_test, accuracy) { CV_MarrHildrethTest test; test.safe_run(); } diff --git a/modules/img_hash/test/test_phash.cpp b/modules/img_hash/test/test_phash.cpp new file mode 100644 index 000000000..43ed96228 --- /dev/null +++ b/modules/img_hash/test/test_phash.cpp @@ -0,0 +1,58 @@ +// 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" + +#include + +using namespace cv; + +class CV_PHashTest : public cvtest::BaseTest +{ +public: + CV_PHashTest(); + ~CV_PHashTest(); +protected: + void run(int /* idx */); +}; + +CV_PHashTest::CV_PHashTest(){} +CV_PHashTest::~CV_PHashTest(){} + +void CV_PHashTest::run(int ) +{ + cv::Mat input(32, 32, CV_8U); + cv::Mat hash; + + uchar value = 0; + uchar *inPtr = input.ptr(0); + for(size_t i = 0; i != 32*32; ++i) + { + inPtr[i] = value++; + } + + cv::img_hash::pHash(input, hash); + bool const expectResult[] = + { + 1,0,1,1,1,1,1,1, + 0,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, + 0,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, + 0,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1, + 0,1,1,1,1,1,1,1, + }; + uchar const *hashPtr = hash.ptr(0); + for(int i = 0; i != hash.cols; ++i) + { + std::bitset<8> const bits = hashPtr[i]; + for(int j = 0; j != 8; ++j) + { + EXPECT_EQ(bits[j], expectResult[i*8+j]); + } + } +} + +TEST(average_phash_test, accuracy) { CV_PHashTest test; test.safe_run(); } diff --git a/modules/img_hash/test/test_precomp.hpp b/modules/img_hash/test/test_precomp.hpp new file mode 100644 index 000000000..c4ea21785 --- /dev/null +++ b/modules/img_hash/test/test_precomp.hpp @@ -0,0 +1,20 @@ +#ifdef __GNUC__ +# pragma GCC diagnostic ignored "-Wmissing-declarations" +# if defined __clang__ || defined __APPLE__ +# pragma GCC diagnostic ignored "-Wmissing-prototypes" +# pragma GCC diagnostic ignored "-Wextra" +# endif +#endif + +#ifndef __OPENCV_TEST_PRECOMP_HPP__ +#define __OPENCV_TEST_PRECOMP_HPP__ + +#include +#include "opencv2/ts.hpp" +#include "opencv2/img_hash.hpp" +#include "opencv2/imgproc.hpp" +#include "opencv2/core/cvdef.h" +#include "opencv2/core.hpp" +#include "opencv2/highgui.hpp" + +#endif diff --git a/modules/img_hash/test/test_radial_variance_hash.cpp b/modules/img_hash/test/test_radial_variance_hash.cpp new file mode 100644 index 000000000..d4b5d7b1e --- /dev/null +++ b/modules/img_hash/test/test_radial_variance_hash.cpp @@ -0,0 +1,150 @@ +// 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" + +using namespace cv; +using namespace cv::img_hash; + + +/** + *The expected results of this test case are come from the phash library, + *I use it as golden model + */ +class CV_RadialVarianceHashTest : public cvtest::BaseTest +{ +public: + CV_RadialVarianceHashTest(); +protected: + void run(int /* idx */); + + //this test case do not use the original "golden data" + //of pHash library, I add a small value to nb_pixels in + //the function "ph_feature_vector" to avoid NaN value + void testComputeHash(); + void testFeatures(); + void testHash(); + void testPixPerLine(); + void testProjection(); + + cv::Mat input; + Ptr rvh; +}; + +CV_RadialVarianceHashTest::CV_RadialVarianceHashTest() +{ + input.create(8, 8, CV_8U); + uchar *inPtr = input.ptr(0); + for(size_t i = 0; i != input.total(); ++i) + { + inPtr[i] = static_cast(i); + } + rvh = RadialVarianceHash::create(1, 10); +} + +void CV_RadialVarianceHashTest::testComputeHash() +{ + cv::Mat hashOne(1, 40, CV_8U); + uchar buffer[] = + { + 52, 41, 49, 64, 40, 67, 76, 71, 69, + 55, 58, 68, 72, 78, 63, 73, 66, 77, + 60, 57, 48, 59, 62, 74, 70, 47, 46, + 51, 45, 44, 42, 61, 54, 75, 50, 79, + 65, 43, 53, 56 + }; + cv::Mat hashTwo(1, 40, CV_8U, buffer); + for(uchar i = 0; i != 40; ++i) + { + hashOne.at(0, i) = i; + } + + double const actual = rvh->compare(hashOne, hashTwo); + ASSERT_NEAR(0.481051, actual, 0.0001); +} + +void CV_RadialVarianceHashTest::testFeatures() +{ + std::vector const &features = rvh->getFeatures(); + double const expectResult[] = + {-1.35784,-0.42703,0.908487,-1.39327,1.17313, + 1.47515,-0.0156121,0.774335,-0.116755,-1.02059}; + for(size_t i = 0; i != features.size(); ++i) + { + ASSERT_NEAR(features[i], expectResult[i], 0.0001); + } +} + +void CV_RadialVarianceHashTest::testHash() +{ + cv::Mat const hash = rvh->getHash(); + uchar const expectResult[] = + { + 127, 92, 0, 158, 101, + 88, 14, 136, 227, 160, + 127, 94, 27, 118, 240, + 166, 153, 96, 254, 162, + 127, 162, 255, 96, 153, + 166, 240, 118, 27, 94, + 127, 160, 227, 136, 14, + 88, 101, 158, 0, 92 + }; + for(int i = 0; i != hash.cols; ++i) + { + EXPECT_EQ(hash.at(0, i), expectResult[i]); + } +} + +void CV_RadialVarianceHashTest::testPixPerLine() +{ + cv::Mat const pixPerLine = rvh->getPixPerLine(input); + uchar const expectResult[] = + { + 8,8,8,0,8,15,7,5,8,8, + }; + bool const equal = + std::equal(expectResult, expectResult + pixPerLine.total(), + pixPerLine.ptr(0)); + if(equal == false) + { + ts->printf(cvtest::TS::LOG, "Wrong pixel per line value \n"); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + } +} + +void CV_RadialVarianceHashTest::testProjection() +{ + cv::Mat const proj = rvh->getProjection(); + uchar const expectResult[] = + { + 32, 33, 34, 35, 36, 37, 38, 39, + 16, 17, 18, 27, 36, 37, 46, 47, + 0, 9, 18, 19, 36, 45, 46, 55, + 0, 0, 0, 0, 0, 0, 0, 0, + 2, 10, 18, 27, 36, 44, 53, 61, + 4, 59, 51, 44, 36, 29, 22, 14, + 0, 58, 51, 43, 36, 30, 22, 15, + 0, 0, 58, 43, 36, 21, 6, 0, + 56, 49, 42, 43, 36, 21, 22, 15, + 40, 41, 42, 35, 36, 29, 22, 23 + }; + bool const equal = + std::equal(expectResult, expectResult + proj.total(), + proj.ptr(0)); + if(equal == false) + { + ts->printf(cvtest::TS::LOG, "Wrong projection value \n"); + ts->set_failed_test_info(cvtest::TS::FAIL_INVALID_TEST_DATA); + } +} + +void CV_RadialVarianceHashTest::run(int) +{ + testPixPerLine(); + testProjection(); + testFeatures(); + testComputeHash(); +} + +TEST(radial_variance_hash_test, accuracy) { CV_RadialVarianceHashTest test; test.safe_run(); }