1 /*
2 By downloading, copying, installing or using the software you agree to this license.
3 If you do not agree to this license, do not download, install,
4 copy or use the software.
5 
6 
7                           License Agreement
8                For Open Source Computer Vision Library
9                        (3-clause BSD License)
10 
11 Copyright (C) 2000-2016, Intel Corporation, all rights reserved.
12 Copyright (C) 2009-2011, Willow Garage Inc., all rights reserved.
13 Copyright (C) 2009-2016, NVIDIA Corporation, all rights reserved.
14 Copyright (C) 2010-2013, Advanced Micro Devices, Inc., all rights reserved.
15 Copyright (C) 2015-2016, OpenCV Foundation, all rights reserved.
16 Copyright (C) 2015-2016, Itseez Inc., all rights reserved.
17 Third party copyrights are property of their respective owners.
18 
19 Redistribution and use in source and binary forms, with or without modification,
20 are permitted provided that the following conditions are met:
21 
22   * Redistributions of source code must retain the above copyright notice,
23     this list of conditions and the following disclaimer.
24 
25   * Redistributions in binary form must reproduce the above copyright notice,
26     this list of conditions and the following disclaimer in the documentation
27     and/or other materials provided with the distribution.
28 
29   * Neither the names of the copyright holders nor the names of the contributors
30     may be used to endorse or promote products derived from this software
31     without specific prior written permission.
32 
33 This software is provided by the copyright holders and contributors "as is" and
34 any express or implied warranties, including, but not limited to, the implied
35 warranties of merchantability and fitness for a particular purpose are disclaimed.
36 In no event shall copyright holders or contributors be liable for any direct,
37 indirect, incidental, special, exemplary, or consequential damages
38 (including, but not limited to, procurement of substitute goods or services;
39 loss of use, data, or profits; or business interruption) however caused
40 and on any theory of liability, whether in contract, strict liability,
41 or tort (including negligence or otherwise) arising in any way out of
42 the use of this software, even if advised of the possibility of such damage.
43 */
44 
45 /*
46 Contributed by Gregor Kovalcik <gregor dot kovalcik at gmail dot com>
47     based on code provided by Martin Krulis, Jakub Lokoc and Tomas Skopal.
48 
49 References:
50     Martin Krulis, Jakub Lokoc, Tomas Skopal.
51     Efficient Extraction of Clustering-Based Feature Signatures Using GPU Architectures.
52     Multimedia tools and applications, 75(13), pp.: 8071�8103, Springer, ISSN: 1380-7501, 2016
53 
54     Christian Beecks, Merih Seran Uysal, Thomas Seidl.
55     Signature quadratic form distance.
56     In Proceedings of the ACM International Conference on Image and Video Retrieval, pages 438-445.
57     ACM, 2010.
58 */
59 #include "precomp.hpp"
60 
61 #include "pct_signatures/constants.hpp"
62 #include "pct_signatures/pct_sampler.hpp"
63 #include "pct_signatures/pct_clusterizer.hpp"
64 
65 using namespace cv::xfeatures2d::pct_signatures;
66 
67 namespace cv
68 {
69     namespace xfeatures2d
70     {
71         namespace pct_signatures
72         {
73             class PCTSignatures_Impl : public PCTSignatures
74             {
75             public:
PCTSignatures_Impl(const std::vector<Point2f> & initSamplingPoints,int initSeedCount)76                 PCTSignatures_Impl(const std::vector<Point2f>& initSamplingPoints, int initSeedCount)
77                 {
78                     if (initSamplingPoints.size() == 0)
79                     {
80                         CV_Error(Error::StsBadArg, "No sampling points provided!");
81                     }
82                     if (initSeedCount <= 0)
83                     {
84                         CV_Error(Error::StsBadArg, "Not enough initial seeds, at least 1 required.");
85                     }
86 
87                     mSampler = PCTSampler::create(initSamplingPoints);
88 
89                     initSeedCount = std::min(initSeedCount, (int)initSamplingPoints.size());
90                     std::vector<int> initClusterSeedIndexes = pickRandomClusterSeedIndexes(initSeedCount);
91                     mClusterizer = PCTClusterizer::create(initClusterSeedIndexes);
92                 }
93 
PCTSignatures_Impl(const std::vector<Point2f> & initSamplingPoints,const std::vector<int> & initClusterSeedIndexes)94                 PCTSignatures_Impl(
95                     const std::vector<Point2f>& initSamplingPoints,
96                     const std::vector<int>& initClusterSeedIndexes)
97                 {
98                     if (initSamplingPoints.size() == 0)
99                     {
100                         CV_Error(Error::StsBadArg, "No sampling points provided!");
101                     }
102                     if (initClusterSeedIndexes.size() == 0)
103                     {
104                         CV_Error(Error::StsBadArg, "Not enough initial seeds, at least 1 required.");
105                     }
106                     if (initClusterSeedIndexes.size() > initSamplingPoints.size())
107                     {
108                         CV_Error(Error::StsBadArg, "Too much cluster seeds or not enough sampling points.");
109                     }
110                     for (int iCluster = 0; iCluster < (int)(initClusterSeedIndexes.size()); iCluster++)
111                     {
112                         if (initClusterSeedIndexes[iCluster] < 0
113                             || initClusterSeedIndexes[iCluster] >= (int)(initSamplingPoints.size()))
114                         {
115                             CV_Error(Error::StsBadArg,
116                                 "Initial cluster seed indexes contain an index outside the range of the sampling point list.");
117                         }
118                     }
119 
120                     mSampler = PCTSampler::create(initSamplingPoints);
121                     mClusterizer = PCTClusterizer::create(initClusterSeedIndexes);
122                 }
123 
124                 void computeSignature(InputArray image, OutputArray signature) const CV_OVERRIDE;
125 
126                 void computeSignatures(const std::vector<Mat>& images, std::vector<Mat>& signatures) const CV_OVERRIDE;
127 
128                 void getGrayscaleBitmap(OutputArray _grayscaleBitmap, bool normalize) const;
129 
130                 /**** sampler ****/
131 
getSampleCount() const132                 int getSampleCount() const CV_OVERRIDE                      { return mSampler->getGrayscaleBits(); }
getGrayscaleBits() const133                 int getGrayscaleBits() const CV_OVERRIDE                    { return mSampler->getGrayscaleBits(); }
getWindowRadius() const134                 int getWindowRadius() const CV_OVERRIDE                     { return mSampler->getWindowRadius(); }
getWeightX() const135                 float getWeightX() const CV_OVERRIDE                        { return mSampler->getWeightX(); }
getWeightY() const136                 float getWeightY() const CV_OVERRIDE                        { return mSampler->getWeightY(); }
getWeightL() const137                 float getWeightL() const CV_OVERRIDE                        { return mSampler->getWeightL(); }
getWeightA() const138                 float getWeightA() const CV_OVERRIDE                        { return mSampler->getWeightA(); }
getWeightB() const139                 float getWeightB() const CV_OVERRIDE                        { return mSampler->getWeightB(); }
getWeightContrast() const140                 float getWeightContrast() const CV_OVERRIDE                 { return mSampler->getWeightContrast(); }
getWeightEntropy() const141                 float getWeightEntropy() const CV_OVERRIDE                  { return mSampler->getWeightEntropy(); }
142 
getSamplingPoints() const143                 std::vector<Point2f> getSamplingPoints() const CV_OVERRIDE  { return mSampler->getSamplingPoints(); }
144 
145 
setGrayscaleBits(int grayscaleBits)146                 void setGrayscaleBits(int grayscaleBits) CV_OVERRIDE        { mSampler->setGrayscaleBits(grayscaleBits); }
setWindowRadius(int windowRadius)147                 void setWindowRadius(int windowRadius) CV_OVERRIDE          { mSampler->setWindowRadius(windowRadius); }
setWeightX(float weight)148                 void setWeightX(float weight) CV_OVERRIDE                   { mSampler->setWeightX(weight); }
setWeightY(float weight)149                 void setWeightY(float weight) CV_OVERRIDE                   { mSampler->setWeightY(weight); }
setWeightL(float weight)150                 void setWeightL(float weight) CV_OVERRIDE                   { mSampler->setWeightL(weight); }
setWeightA(float weight)151                 void setWeightA(float weight) CV_OVERRIDE                   { mSampler->setWeightA(weight); }
setWeightB(float weight)152                 void setWeightB(float weight) CV_OVERRIDE                   { mSampler->setWeightB(weight); }
setWeightContrast(float weight)153                 void setWeightContrast(float weight) CV_OVERRIDE            { mSampler->setWeightContrast(weight); }
setWeightEntropy(float weight)154                 void setWeightEntropy(float weight) CV_OVERRIDE             { mSampler->setWeightEntropy(weight); }
155 
setWeight(int idx,float value)156                 void setWeight(int idx, float value) CV_OVERRIDE                           { mSampler->setWeight(idx, value); }
setWeights(const std::vector<float> & weights)157                 void setWeights(const std::vector<float>& weights) CV_OVERRIDE             { mSampler->setWeights(weights); }
setTranslation(int idx,float value)158                 void setTranslation(int idx, float value) CV_OVERRIDE                      { mSampler->setTranslation(idx, value); }
setTranslations(const std::vector<float> & translations)159                 void setTranslations(const std::vector<float>& translations) CV_OVERRIDE   { mSampler->setTranslations(translations); }
160 
setSamplingPoints(std::vector<Point2f> samplingPoints)161                 void setSamplingPoints(std::vector<Point2f> samplingPoints) CV_OVERRIDE    { mSampler->setSamplingPoints(samplingPoints); }
162 
163 
164                 /**** clusterizer ****/
165 
getIterationCount() const166                 int getIterationCount() const CV_OVERRIDE                       { return mClusterizer->getIterationCount(); }
getInitSeedIndexes() const167                 std::vector<int> getInitSeedIndexes() const CV_OVERRIDE         { return mClusterizer->getInitSeedIndexes(); }
getInitSeedCount() const168                 int getInitSeedCount() const CV_OVERRIDE                        { return (int)mClusterizer->getInitSeedIndexes().size(); }
getMaxClustersCount() const169                 int getMaxClustersCount() const CV_OVERRIDE                     { return mClusterizer->getMaxClustersCount(); }
getClusterMinSize() const170                 int getClusterMinSize() const CV_OVERRIDE                       { return mClusterizer->getClusterMinSize(); }
getJoiningDistance() const171                 float getJoiningDistance() const CV_OVERRIDE                    { return mClusterizer->getJoiningDistance(); }
getDropThreshold() const172                 float getDropThreshold() const CV_OVERRIDE                      { return mClusterizer->getDropThreshold(); }
getDistanceFunction() const173                 int getDistanceFunction() const CV_OVERRIDE
174                                                                     { return mClusterizer->getDistanceFunction(); }
175 
setIterationCount(int iterations)176                 void setIterationCount(int iterations) CV_OVERRIDE              { mClusterizer->setIterationCount(iterations); }
setInitSeedIndexes(std::vector<int> initSeedIndexes)177                 void setInitSeedIndexes(std::vector<int> initSeedIndexes) CV_OVERRIDE
178                                                                     { mClusterizer->setInitSeedIndexes(initSeedIndexes); }
setMaxClustersCount(int maxClusters)179                 void setMaxClustersCount(int maxClusters) CV_OVERRIDE           { mClusterizer->setMaxClustersCount(maxClusters); }
setClusterMinSize(int clusterMinSize)180                 void setClusterMinSize(int clusterMinSize) CV_OVERRIDE          { mClusterizer->setClusterMinSize(clusterMinSize); }
setJoiningDistance(float joiningDistance)181                 void setJoiningDistance(float joiningDistance) CV_OVERRIDE      { mClusterizer->setJoiningDistance(joiningDistance); }
setDropThreshold(float dropThreshold)182                 void setDropThreshold(float dropThreshold) CV_OVERRIDE          { mClusterizer->setDropThreshold(dropThreshold); }
setDistanceFunction(int distanceFunction)183                 void setDistanceFunction(int distanceFunction) CV_OVERRIDE
184                                                                     { mClusterizer->setDistanceFunction(distanceFunction); }
185 
186             private:
187                 /**
188                 * @brief Samples used for sampling the input image and producing list of samples.
189                 */
190                 Ptr<PCTSampler> mSampler;
191 
192                 /**
193                 * @brief Clusterizer using k-means algorithm to produce list of centroids from sampled points - the image signature.
194                 */
195                 Ptr<PCTClusterizer> mClusterizer;
196 
197                 /**
198                 * @brief Creates vector of random indexes of sampling points
199                 *       which will be used as initial centroids for k-means clusterization.
200                 * @param initSeedCount Number of indexes of initial centroids to be produced.
201                 * @return The generated vector of random indexes.
202                 */
pickRandomClusterSeedIndexes(int initSeedCount)203                 static std::vector<int> pickRandomClusterSeedIndexes(int initSeedCount)
204                 {
205                     std::vector<int> seedIndexes;
206                     for (int iSeed = 0; iSeed < initSeedCount; iSeed++)
207                     {
208                         seedIndexes.push_back(iSeed);
209                     }
210 
211                     randShuffle(seedIndexes);
212                     return seedIndexes;
213                 }
214             };
215 
216 
217             /**
218             * @brief Class implementing parallel computing of signatures for multiple images.
219             */
220             class Parallel_computeSignatures : public ParallelLoopBody
221             {
222             private:
223                 const PCTSignatures* mPctSignaturesAlgorithm;
224                 const std::vector<Mat>* mImages;
225                 std::vector<Mat>* mSignatures;
226 
227             public:
Parallel_computeSignatures(const PCTSignatures * pctSignaturesAlgorithm,const std::vector<Mat> * images,std::vector<Mat> * signatures)228                 Parallel_computeSignatures(
229                     const PCTSignatures* pctSignaturesAlgorithm,
230                     const std::vector<Mat>* images,
231                     std::vector<Mat>* signatures)
232                     : mPctSignaturesAlgorithm(pctSignaturesAlgorithm),
233                     mImages(images),
234                     mSignatures(signatures)
235                 {
236                     mSignatures->resize(images->size());
237                 }
238 
operator ()(const Range & range) const239                 void operator()(const Range& range) const CV_OVERRIDE
240                 {
241                     for (int i = range.start; i < range.end; i++)
242                     {
243                         mPctSignaturesAlgorithm->computeSignature((*mImages)[i], (*mSignatures)[i]);
244                     }
245                 }
246             };
247 
248 
249             /**
250             * @brief Computes signature for one image.
251             */
computeSignature(InputArray _image,OutputArray _signature) const252             void PCTSignatures_Impl::computeSignature(InputArray _image, OutputArray _signature) const
253             {
254                 if (_image.empty())
255                 {
256                     _signature.create(_image.size(), CV_32FC1);
257                     return;
258                 }
259 
260                 Mat image = _image.getMat();
261                 CV_Assert(image.depth() == CV_8U);
262 
263                 // TODO: OpenCL
264                 //if (ocl::useOpenCL())
265                 //{
266                 //}
267 
268                 // sample features
269                 Mat samples;
270                 mSampler->sample(image, samples);               // HOT PATH: 40%
271 
272                 // kmeans clusterize, use feature samples, produce signature clusters
273                 Mat signature;
274                 mClusterizer->clusterize(samples, signature);   // HOT PATH: 60%
275 
276 
277                 // set result
278                 _signature.create(signature.size(), signature.type());
279                 Mat result = _signature.getMat();
280                 signature.copyTo(result);
281             }
282 
283             /**
284             * @brief Parallel function computing signatures for multiple images.
285             */
computeSignatures(const std::vector<Mat> & images,std::vector<Mat> & signatures) const286             void PCTSignatures_Impl::computeSignatures(const std::vector<Mat>& images, std::vector<Mat>& signatures) const
287             {
288                 parallel_for_(Range(0, (int)images.size()), Parallel_computeSignatures(this, &images, &signatures));
289             }
290 
291         } // end of namespace pct_signatures
292 
293 
294 
create(const int initSampleCount,const int initSeedCount,const int pointDistribution)295         Ptr<PCTSignatures> PCTSignatures::create(
296             const int initSampleCount,
297             const int initSeedCount,
298             const int pointDistribution)
299         {
300             std::vector<Point2f> initPoints;
301             generateInitPoints(initPoints, initSampleCount, pointDistribution);
302             return create(initPoints, initSeedCount);
303         }
304 
create(const std::vector<Point2f> & initPoints,const int initSeedCount)305         Ptr<PCTSignatures> PCTSignatures::create(
306             const std::vector<Point2f>& initPoints,
307             const int initSeedCount)
308         {
309             return makePtr<PCTSignatures_Impl>(initPoints, initSeedCount);
310         }
311 
create(const std::vector<Point2f> & initPoints,const std::vector<int> & initClusterSeedIndexes)312         Ptr<PCTSignatures> PCTSignatures::create(
313             const std::vector<Point2f>& initPoints,
314             const std::vector<int>& initClusterSeedIndexes)
315         {
316             return makePtr<PCTSignatures_Impl>(initPoints, initClusterSeedIndexes);
317         }
318 
319 
drawSignature(InputArray _source,InputArray _signature,OutputArray _result,float radiusToShorterSideRatio,int borderThickness)320         void PCTSignatures::drawSignature(
321             InputArray _source,
322             InputArray _signature,
323             OutputArray _result,
324             float radiusToShorterSideRatio,
325             int borderThickness)
326         {
327             // check source
328             if (_source.empty())
329             {
330                 return;
331             }
332             Mat source = _source.getMat();
333 
334             // create result
335             _result.create(source.size(), source.type());
336             Mat result = _result.getMat();
337             source.copyTo(result);
338 
339             // check signature
340             if (_signature.empty())
341             {
342                 return;
343             }
344             Mat signature = _signature.getMat();
345             if (signature.type() != CV_32F || signature.cols != SIGNATURE_DIMENSION)
346             {
347                 CV_Error_(Error::StsBadArg, ("Invalid signature format. Type must be CV_32F and signature.cols must be %d.", SIGNATURE_DIMENSION));
348             }
349 
350             // compute max radius using given ratio of shorter image side
351             float maxRadius = ((source.rows < source.cols) ? source.rows : source.cols) * radiusToShorterSideRatio;
352 
353             // draw signature
354             for (int i = 0; i < signature.rows; i++)
355             {
356                 Vec3f labColor(
357                     signature.at<float>(i, L_IDX) * L_COLOR_RANGE,      // convert Lab pixel to BGR
358                     signature.at<float>(i, A_IDX) * A_COLOR_RANGE,
359                     signature.at<float>(i, B_IDX) * B_COLOR_RANGE);
360                 Mat labPixel(1, 1, CV_32FC3);
361                 labPixel.at<Vec3f>(0, 0) = labColor;
362                 Mat rgbPixel;
363                 cvtColor(labPixel, rgbPixel, COLOR_Lab2BGR);
364                 rgbPixel.convertTo(rgbPixel, CV_8UC3, 255);
365                 Vec3b rgbColor = rgbPixel.at<Vec3b>(0, 0);
366 
367                 // precompute variables
368                 Point center((int)(signature.at<float>(i, X_IDX) * source.cols), (int)(signature.at<float>(i, Y_IDX) * source.rows));
369                 int radius = (int)(maxRadius * signature.at<float>(i, WEIGHT_IDX));
370                 Vec3b borderColor(0, 0, 0);
371 
372                 // draw filled circle
373                 circle(result, center, radius, rgbColor, -1);
374                 // draw circle outline
375                 circle(result, center, radius, borderColor, borderThickness);
376             }
377         }
378 
379 
generateInitPoints(std::vector<Point2f> & initPoints,const int count,const int pointDistribution)380         void PCTSignatures::generateInitPoints(
381             std::vector<Point2f>& initPoints,
382             const int count,
383             const int pointDistribution)
384         {
385             RNG random;
386             random.state = getTickCount();
387             initPoints.resize(count);
388 
389             switch (pointDistribution)
390             {
391             case UNIFORM:
392                 for (int i = 0; i < count; i++)
393                 {
394                     // returns uniformly distributed float random number from [0, 1) range
395                     initPoints[i] = (Point2f(random.uniform((float)0.0, (float)1.0), random.uniform((float)0.0, (float)1.0)));
396                 }
397                 break;
398             case REGULAR:
399             {
400                 int gridSize = (int)ceil(sqrt((float)count));
401                 const float step = 1.0f / gridSize;
402                 const float halfStep = step / 2;
403                 float x = halfStep;
404                 float y = halfStep;
405                 for (int i = 0; i < count; i++)
406                 {
407                     // returns regular grid
408                     initPoints[i] = Point2f(x, y);
409                     if ((i + 1) % gridSize == 0)
410                     {
411                         x = halfStep;
412                         y += step;
413                     }
414                     else
415                     {
416                         x += step;
417                     }
418                 }
419                 break;
420             }
421             case NORMAL:
422                 for (int i = 0; i < count; i++)
423                 {
424                     // returns normally distributed float random number from (0, 1) range with mean 0.5
425                     float sigma = 0.2f;
426                     float x = (float)random.gaussian(sigma);
427                     float y = (float)random.gaussian(sigma);
428                     while (x <= -0.5f || x >= 0.5f)
429                         x = (float)random.gaussian(sigma);
430                     while (y <= -0.5f || y >= 0.5f)
431                         y = (float)random.gaussian(sigma);
432                     initPoints[i] = Point2f(x, y) + Point2f(0.5, 0.5);
433                 }
434                 break;
435             default:
436                 CV_Error(Error::StsNotImplemented, "Generation of this init point distribution is not implemented!");
437                 break;
438             }
439         }
440 
441 
442     } // end of namespace xfeatures2d
443 } // end of namespace cv
444