// 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_, 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_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 = 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<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::