vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
+3
View File
@@ -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_geometry opencv_core WRAP python java objc)
+5
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+23
View File
@@ -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}
}
@@ -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
@@ -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<AverageHash> 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
@@ -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 the mode
*/
CV_WRAP void setMode(int mode);
CV_WRAP std::vector<double> getMean() const;
CV_WRAP static Ptr<BlockMeanHash> 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 the 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
@@ -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<ColorMomentHash> 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
@@ -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<ImgHashImpl> pImpl;
};
//! @}
} } // cv::img_hash::
#endif // OPENCV_IMG_HASH_BASE_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<MarrHildrethHash> 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
@@ -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<PHash> 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
@@ -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<RadialVarianceHash> 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<double> 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
+11
View File
@@ -0,0 +1,11 @@
{
"AdditionalImports" : {
"*" : [ "\"img_hash.hpp\"" ]
},
"func_arg_fix" : {
"BlockMeanHash" : {
"create" : { "mode" : {"ctype" : "BlockMeanHashMode"} },
"setMode" : { "mode" : {"ctype" : "BlockMeanHashMode"} }
}
}
}
+53
View File
@@ -0,0 +1,53 @@
#include "opencv2/core.hpp"
#include "opencv2/core/ocl.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/img_hash.hpp"
#include <iostream>
using namespace cv;
using namespace cv::img_hash;
using namespace std;
template <typename T>
inline void test_one(const std::string &title, const Mat &a, const Mat &b)
{
cout << "=== " << title << " ===" << endl;
TickMeter tick;
Mat hashA, hashB;
Ptr<ImgHashBase> 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>("AverageHash", input, target);
test_one<PHash>("PHash", input, target);
test_one<MarrHildrethHash>("MarrHildrethHash", input, target);
test_one<RadialVarianceHash>("RadialVarianceHash", input, target);
test_one<BlockMeanHash>("BlockMeanHash", input, target);
return 0;
}
+78
View File
@@ -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.
#include "precomp.hpp"
using namespace cv;
using namespace std;
using namespace img_hash;
namespace {
class AverageHashImpl CV_FINAL : public ImgHashBase::ImgHashImpl
{
private:
cv::Mat bitsImg;
cv::Mat grayImg;
cv::Mat resizeImg;
public:
virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) CV_OVERRIDE
{
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), 0, 0, INTER_LINEAR_EXACT);
if(input.channels() > 1)
cv::cvtColor(resizeImg, grayImg, COLOR_BGR2GRAY);
else
grayImg = resizeImg;
uchar const imgMean = static_cast<uchar>(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<uchar>(0);
uchar const *bits_ptr = bitsImg.ptr<uchar>(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<uchar>(bits.to_ulong());
}
}
virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const CV_OVERRIDE
{
return norm(hashOne, hashTwo, NORM_HAMMING);
}
};
} // namespace::
//==================================================================================================
namespace cv { namespace img_hash {
Ptr<AverageHash> AverageHash::create()
{
Ptr<AverageHash> res(new AverageHash());
res->pImpl = makePtr<AverageHashImpl>();
return res;
}
void averageHash(cv::InputArray inputArr, cv::OutputArray outputArr)
{
AverageHashImpl().compute(inputArr, outputArr);
}
}} // cv::img_hash::
+159
View File
@@ -0,0 +1,159 @@
// 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 CV_FINAL : public ImgHashBase::ImgHashImpl
{
public:
BlockMeanHashImpl(int mode)
{
setMode(mode);
}
~BlockMeanHashImpl() CV_OVERRIDE {}
virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) CV_OVERRIDE
{
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), 0, 0, INTER_LINEAR_EXACT);
if(input.channels() > 1)
cv::cvtColor(resizeImg_, grayImg_, COLOR_BGR2GRAY);
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 CV_OVERRIDE
{
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<uchar>(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<uchar>(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<double> mean_;
int mode_;
cv::Mat resizeImg_;
};
inline BlockMeanHashImpl *getLocalImpl(ImgHashBase::ImgHashImpl *ptr)
{
BlockMeanHashImpl * impl = static_cast<BlockMeanHashImpl*>(ptr);
CV_Assert(impl);
return impl;
}
}
//==================================================================================================
namespace cv { namespace img_hash {
Ptr<BlockMeanHash> BlockMeanHash::create(int mode)
{
Ptr<BlockMeanHash> res(new BlockMeanHash);
res->pImpl = makePtr<BlockMeanHashImpl>(mode);
return res;
}
void BlockMeanHash::setMode(int mode)
{
getLocalImpl(pImpl)->setMode(mode);
}
std::vector<double> 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::
@@ -0,0 +1,94 @@
// 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 CV_FINAL : public ImgHashBase::ImgHashImpl
{
public:
~ColorMomentHashImpl() CV_OVERRIDE {}
virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) CV_OVERRIDE
{
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_, COLOR_BGRA2BGR);
}
else
{
cv::cvtColor(input, colorImg_, COLOR_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_, COLOR_BGR2HSV);
cv::split(colorSpace_, channels_);
outputArr.create(1, 42, CV_64F);
cv::Mat hash = outputArr.getMat();
hash.setTo(0);
computeMoments(hash.ptr<double>(0));
cv::cvtColor(blurImg_, colorSpace_, COLOR_BGR2YCrCb);
cv::split(colorSpace_, channels_);
computeMoments(hash.ptr<double>(0) + 21);
}
virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const CV_OVERRIDE
{
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<cv::Mat> channels_;
cv::Mat colorSpace_;
cv::Mat resizeImg_;
};
}
//==================================================================================================
namespace cv { namespace img_hash {
Ptr<ColorMomentHash> ColorMomentHash::create()
{
Ptr<ColorMomentHash> res(new ColorMomentHash);
res->pImpl = makePtr<ColorMomentHashImpl>();
return res;
}
void colorMomentHash(cv::InputArray inputArr, cv::OutputArray outputArr)
{
ColorMomentHashImpl().compute(inputArr, outputArr);
}
} } // cv::img_hash::
+28
View File
@@ -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::
+205
View File
@@ -0,0 +1,205 @@
// 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<int>(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<float>(row - sigma);
float const ypos = ratio * ydiff;
float const yposPow2 = ypos * ypos;
float *kPtr = kernel.ptr<float>(row);
for(int col = 0; col != kernel.cols; ++col)
{
float const xpos = ratio * static_cast<float>((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<float>(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<float>(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<uchar>(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<float>(cv::sum(blockROI)[0]/9.0);
for(int i = 0; i != blockROI.rows; ++i)
{
float const *bptr = blockROI.ptr<float>(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 CV_FINAL : 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() CV_OVERRIDE { }
virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) CV_OVERRIDE
{
cv::Mat const input = inputArr.getMat();
CV_Assert(input.type() == CV_8UC4 ||
input.type() == CV_8UC3 ||
input.type() == CV_8U);
if(input.channels() > 1)
cv::cvtColor(input, grayImg, COLOR_BGR2GRAY);
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 CV_OVERRIDE
{
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<MarrHildrethHashImpl*>(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> MarrHildrethHash::create(float alpha, float scale)
{
Ptr<MarrHildrethHash> res(new MarrHildrethHash);
res->pImpl = makePtr<MarrHildrethHashImpl>(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::
+85
View File
@@ -0,0 +1,85 @@
// 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 CV_FINAL : public ImgHashBase::ImgHashImpl
{
public:
virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) CV_OVERRIDE
{
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), 0, 0, INTER_LINEAR_EXACT);
if(input.channels() > 1)
cv::cvtColor(resizeImg, grayImg, COLOR_BGR2GRAY);
else
grayImg = resizeImg;
grayImg.convertTo(grayFImg, CV_32F);
cv::dct(grayFImg, dctImg);
dctImg(cv::Rect(0, 0, 8, 8)).copyTo(topLeftDCT);
topLeftDCT.at<float>(0, 0) = 0;
float const imgMean = static_cast<float>(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<uchar>(0);
uchar const *bits_ptr = bitsImg.ptr<uchar>(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<uchar>(bits.to_ulong());
}
}
virtual double compare(cv::InputArray hashOne, cv::InputArray hashTwo) const CV_OVERRIDE
{
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> PHash::create()
{
Ptr<PHash> res(new PHash);
res->pImpl = makePtr<PHashImpl>();
return res;
}
void pHash(cv::InputArray inputArr, cv::OutputArray outputArr)
{
PHashImpl().compute(inputArr, outputArr);
}
} } // cv::img_hash::
+29
View File
@@ -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/geometry.hpp"
#include "opencv2/img_hash.hpp"
#include <bitset>
#include <iostream>
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
@@ -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<float>(length/2);
return static_cast<int>(std::floor(center + roundingFactor(center)));
}
class RadialVarianceHashImpl CV_FINAL : public ImgHashBase::ImgHashImpl
{
public:
cv::Mat blurImg_;
std::vector<double> features_;
cv::Mat grayImg_;
int numOfAngelLine_;
cv::Mat pixPerLine_;
cv::Mat projections_;
double sigma_;
RadialVarianceHashImpl(double sigma, int numOfAngleLine)
: numOfAngelLine_(numOfAngleLine), sigma_(sigma)
{
}
~RadialVarianceHashImpl() CV_OVERRIDE {}
virtual void compute(cv::InputArray inputArr, cv::OutputArray outputArr) CV_OVERRIDE
{
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_, COLOR_BGR2GRAY);
}
else if(input.type() == CV_8UC4)
{
cv::cvtColor(input, grayImg_, COLOR_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_OVERRIDE
{
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<double>::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<int>(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<uchar>(k);
uchar *projUp = projections_.ptr<uchar>(k-j);
for(int x = 0; x < D; ++x)
{
float const y = alpha*(x-xOff);
int const yd = static_cast<int>(std::floor(y + roundingFactor(y)));
if((yd + yOff >= 0)&&(yd + yOff < input.rows) && (x < input.cols))
{
projDown[x] = input.at<uchar>(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<uchar>(-(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<int>(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<uchar>(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<int>(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<uchar>(k);
uchar *projTwo = projections_.ptr<uchar>(numOfAngelLine_/2-k);
for(int x = 0; x < D; ++x)
{
float const y = alpha*(x-xOff);
int const yd = static_cast<int>(std::floor(y + roundingFactor(y)));
if((yd + yOff >= 0)&&(yd + yOff < input.rows) && (x < input.cols))
{
projOne[x] = input.at<uchar>(yd+yOff, x);
pplPtr[k] += 1;
}
if((yd + xOff >= 0) && (yd + xOff < input.cols) &&
(k != numOfAngelLine_/4) && (x < input.rows))
{
projTwo[x] =
input.at<uchar>(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<uchar>(0);
for(int i = 0; i < hash.cols; ++i)
{
hashPtr[i] = static_cast<uchar>((255*(temp[i] - min)/range));
}
}
else
{
hash.setTo(cv::Scalar::all(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_.setTo(cv::Scalar::all(0));
pixPerLine_.create(1, numOfAngelLine_, CV_32S);
pixPerLine_.setTo(cv::Scalar::all(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<RadialVarianceHashImpl*>(ptr);
CV_Assert(impl);
return impl;
}
} // namespace::
//==================================================================================================
namespace cv { namespace img_hash {
Ptr<RadialVarianceHash> RadialVarianceHash::create(double sigma, int numOfAngleLine)
{
Ptr<RadialVarianceHash> res(new RadialVarianceHash);
res->pImpl = makePtr<RadialVarianceHashImpl>(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<double> 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::
@@ -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"
#include <bitset>
namespace opencv_test { namespace {
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_<uchar>(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<uchar>(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(); }
}} // namespace
@@ -0,0 +1,214 @@
// 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 <bitset>
namespace opencv_test { namespace {
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<cv::img_hash::BlockMeanHash> bmh;
};
CV_BlockMeanHashTest::CV_BlockMeanHashTest()
{
input.create(256, 256, CV_8U);
for(int row = 0; row != input.rows; ++row)
{
uchar value = static_cast<uchar>(row);
for(int col = 0; col != input.cols; ++col)
{
input.at<uchar>(row, col) = value++;
}
}
bmh = BlockMeanHash::create(BLOCK_MEAN_HASH_MODE_0);
}
void CV_BlockMeanHashTest::testMeanMode0()
{
std::vector<double> 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<double> 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<uchar>(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<uchar>(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(); }
}} // namespace
+7
View File
@@ -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")
@@ -0,0 +1,62 @@
// 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"
namespace opencv_test { namespace {
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_<uchar> input(512,512);
int val = 0;
for(int row = 0; row != input.rows; ++row)
{
for(int col = 0; col != input.cols; ++col)
{
input.at<uchar>(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<uchar>(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(); }
}} // namespace
+60
View File
@@ -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"
#include <bitset>
namespace opencv_test { namespace {
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<uchar>(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<uchar>(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(); }
}} // namespace
+10
View File
@@ -0,0 +1,10 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef __OPENCV_TEST_PRECOMP_HPP__
#define __OPENCV_TEST_PRECOMP_HPP__
#include "opencv2/ts.hpp"
#include "opencv2/img_hash.hpp"
#endif
@@ -0,0 +1,154 @@
// 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"
namespace opencv_test { namespace {
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(); // TODO unused
void testPixPerLine();
void testProjection();
cv::Mat input;
Ptr<cv::img_hash::RadialVarianceHash> rvh;
};
CV_RadialVarianceHashTest::CV_RadialVarianceHashTest()
{
input.create(8, 8, CV_8U);
uchar *inPtr = input.ptr<uchar>(0);
for(size_t i = 0; i != input.total(); ++i)
{
inPtr[i] = static_cast<uchar>(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<uchar>(0, i) = i;
}
double const actual = rvh->compare(hashOne, hashTwo);
ASSERT_NEAR(0.481051, actual, 0.0001);
}
void CV_RadialVarianceHashTest::testFeatures()
{
std::vector<double> 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);
}
}
#if 0 // unused
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<uchar>(0, i), expectResult[i]);
}
}
#endif
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<int>(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<uchar>(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(); }
}} // namespace