1 /*M/////////////////////////////////////////////////////////////////////////////////////// 2 // 3 // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. 4 // 5 // By downloading, copying, installing or using the software you agree to this license. 6 // If you do not agree to this license, do not download, install, 7 // copy or use the software. 8 // 9 // 10 // License Agreement 11 // For Open Source Computer Vision Library 12 // 13 // Copyright (C) 2000-2008, Intel Corporation, all rights reserved. 14 // Copyright (C) 2009, Willow Garage Inc., all rights reserved. 15 // Third party copyrights are property of their respective owners. 16 // 17 // Redistribution and use in source and binary forms, with or without modification, 18 // are permitted provided that the following conditions are met: 19 // 20 // * Redistribution's of source code must retain the above copyright notice, 21 // this list of conditions and the following disclaimer. 22 // 23 // * Redistribution's in binary form must reproduce the above copyright notice, 24 // this list of conditions and the following disclaimer in the documentation 25 // and/or other materials provided with the distribution. 26 // 27 // * The name of the copyright holders may not be used to endorse or promote products 28 // derived from this software without specific prior written permission. 29 // 30 // This software is provided by the copyright holders and contributors "as is" and 31 // any express or implied warranties, including, but not limited to, the implied 32 // warranties of merchantability and fitness for a particular purpose are disclaimed. 33 // In no event shall the Intel Corporation or contributors be liable for any direct, 34 // indirect, incidental, special, exemplary, or consequential damages 35 // (including, but not limited to, procurement of substitute goods or services; 36 // loss of use, data, or profits; or business interruption) however caused 37 // and on any theory of liability, whether in contract, strict liability, 38 // or tort (including negligence or otherwise) arising in any way out of 39 // the use of this software, even if advised of the possibility of such damage. 40 // 41 //M*/ 42 43 #include "precomp.hpp" 44 #include <iterator> 45 #include <limits> 46 47 //#define DEBUG_BLOB_DETECTOR 48 49 #ifdef DEBUG_BLOB_DETECTOR 50 # include "opencv2/opencv_modules.hpp" 51 # ifdef HAVE_OPENCV_HIGHGUI 52 # include "opencv2/highgui.hpp" 53 # else 54 # undef DEBUG_BLOB_DETECTOR 55 # endif 56 #endif 57 58 namespace cv 59 { 60 61 class CV_EXPORTS_W SimpleBlobDetectorImpl : public SimpleBlobDetector 62 { 63 public: 64 65 explicit SimpleBlobDetectorImpl(const SimpleBlobDetector::Params ¶meters = SimpleBlobDetector::Params()); 66 67 virtual void read( const FileNode& fn ); 68 virtual void write( FileStorage& fs ) const; 69 70 protected: 71 struct CV_EXPORTS Center 72 { 73 Point2d location; 74 double radius; 75 double confidence; 76 }; 77 78 virtual void detect( InputArray image, std::vector<KeyPoint>& keypoints, InputArray mask=noArray() ); 79 virtual void findBlobs(InputArray image, InputArray binaryImage, std::vector<Center> ¢ers) const; 80 81 Params params; 82 }; 83 84 /* 85 * SimpleBlobDetector 86 */ 87 SimpleBlobDetector::Params::Params() 88 { 89 thresholdStep = 10; 90 minThreshold = 50; 91 maxThreshold = 220; 92 minRepeatability = 2; 93 minDistBetweenBlobs = 10; 94 95 filterByColor = true; 96 blobColor = 0; 97 98 filterByArea = true; 99 minArea = 25; 100 maxArea = 5000; 101 102 filterByCircularity = false; 103 minCircularity = 0.8f; 104 maxCircularity = std::numeric_limits<float>::max(); 105 106 filterByInertia = true; 107 //minInertiaRatio = 0.6; 108 minInertiaRatio = 0.1f; 109 maxInertiaRatio = std::numeric_limits<float>::max(); 110 111 filterByConvexity = true; 112 //minConvexity = 0.8; 113 minConvexity = 0.95f; 114 maxConvexity = std::numeric_limits<float>::max(); 115 } 116 117 void SimpleBlobDetector::Params::read(const cv::FileNode& fn ) 118 { 119 thresholdStep = fn["thresholdStep"]; 120 minThreshold = fn["minThreshold"]; 121 maxThreshold = fn["maxThreshold"]; 122 123 minRepeatability = (size_t)(int)fn["minRepeatability"]; 124 minDistBetweenBlobs = fn["minDistBetweenBlobs"]; 125 126 filterByColor = (int)fn["filterByColor"] != 0 ? true : false; 127 blobColor = (uchar)(int)fn["blobColor"]; 128 129 filterByArea = (int)fn["filterByArea"] != 0 ? true : false; 130 minArea = fn["minArea"]; 131 maxArea = fn["maxArea"]; 132 133 filterByCircularity = (int)fn["filterByCircularity"] != 0 ? true : false; 134 minCircularity = fn["minCircularity"]; 135 maxCircularity = fn["maxCircularity"]; 136 137 filterByInertia = (int)fn["filterByInertia"] != 0 ? true : false; 138 minInertiaRatio = fn["minInertiaRatio"]; 139 maxInertiaRatio = fn["maxInertiaRatio"]; 140 141 filterByConvexity = (int)fn["filterByConvexity"] != 0 ? true : false; 142 minConvexity = fn["minConvexity"]; 143 maxConvexity = fn["maxConvexity"]; 144 } 145 146 void SimpleBlobDetector::Params::write(cv::FileStorage& fs) const 147 { 148 fs << "thresholdStep" << thresholdStep; 149 fs << "minThreshold" << minThreshold; 150 fs << "maxThreshold" << maxThreshold; 151 152 fs << "minRepeatability" << (int)minRepeatability; 153 fs << "minDistBetweenBlobs" << minDistBetweenBlobs; 154 155 fs << "filterByColor" << (int)filterByColor; 156 fs << "blobColor" << (int)blobColor; 157 158 fs << "filterByArea" << (int)filterByArea; 159 fs << "minArea" << minArea; 160 fs << "maxArea" << maxArea; 161 162 fs << "filterByCircularity" << (int)filterByCircularity; 163 fs << "minCircularity" << minCircularity; 164 fs << "maxCircularity" << maxCircularity; 165 166 fs << "filterByInertia" << (int)filterByInertia; 167 fs << "minInertiaRatio" << minInertiaRatio; 168 fs << "maxInertiaRatio" << maxInertiaRatio; 169 170 fs << "filterByConvexity" << (int)filterByConvexity; 171 fs << "minConvexity" << minConvexity; 172 fs << "maxConvexity" << maxConvexity; 173 } 174 175 SimpleBlobDetectorImpl::SimpleBlobDetectorImpl(const SimpleBlobDetector::Params ¶meters) : 176 params(parameters) 177 { 178 } 179 180 void SimpleBlobDetectorImpl::read( const cv::FileNode& fn ) 181 { 182 params.read(fn); 183 } 184 185 void SimpleBlobDetectorImpl::write( cv::FileStorage& fs ) const 186 { 187 params.write(fs); 188 } 189 190 void SimpleBlobDetectorImpl::findBlobs(InputArray _image, InputArray _binaryImage, std::vector<Center> ¢ers) const 191 { 192 Mat image = _image.getMat(), binaryImage = _binaryImage.getMat(); 193 (void)image; 194 centers.clear(); 195 196 std::vector < std::vector<Point> > contours; 197 Mat tmpBinaryImage = binaryImage.clone(); 198 findContours(tmpBinaryImage, contours, RETR_LIST, CHAIN_APPROX_NONE); 199 200 #ifdef DEBUG_BLOB_DETECTOR 201 // Mat keypointsImage; 202 // cvtColor( binaryImage, keypointsImage, CV_GRAY2RGB ); 203 // 204 // Mat contoursImage; 205 // cvtColor( binaryImage, contoursImage, CV_GRAY2RGB ); 206 // drawContours( contoursImage, contours, -1, Scalar(0,255,0) ); 207 // imshow("contours", contoursImage ); 208 #endif 209 210 for (size_t contourIdx = 0; contourIdx < contours.size(); contourIdx++) 211 { 212 Center center; 213 center.confidence = 1; 214 Moments moms = moments(Mat(contours[contourIdx])); 215 if (params.filterByArea) 216 { 217 double area = moms.m00; 218 if (area < params.minArea || area >= params.maxArea) 219 continue; 220 } 221 222 if (params.filterByCircularity) 223 { 224 double area = moms.m00; 225 double perimeter = arcLength(Mat(contours[contourIdx]), true); 226 double ratio = 4 * CV_PI * area / (perimeter * perimeter); 227 if (ratio < params.minCircularity || ratio >= params.maxCircularity) 228 continue; 229 } 230 231 if (params.filterByInertia) 232 { 233 double denominator = std::sqrt(std::pow(2 * moms.mu11, 2) + std::pow(moms.mu20 - moms.mu02, 2)); 234 const double eps = 1e-2; 235 double ratio; 236 if (denominator > eps) 237 { 238 double cosmin = (moms.mu20 - moms.mu02) / denominator; 239 double sinmin = 2 * moms.mu11 / denominator; 240 double cosmax = -cosmin; 241 double sinmax = -sinmin; 242 243 double imin = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmin - moms.mu11 * sinmin; 244 double imax = 0.5 * (moms.mu20 + moms.mu02) - 0.5 * (moms.mu20 - moms.mu02) * cosmax - moms.mu11 * sinmax; 245 ratio = imin / imax; 246 } 247 else 248 { 249 ratio = 1; 250 } 251 252 if (ratio < params.minInertiaRatio || ratio >= params.maxInertiaRatio) 253 continue; 254 255 center.confidence = ratio * ratio; 256 } 257 258 if (params.filterByConvexity) 259 { 260 std::vector < Point > hull; 261 convexHull(Mat(contours[contourIdx]), hull); 262 double area = contourArea(Mat(contours[contourIdx])); 263 double hullArea = contourArea(Mat(hull)); 264 double ratio = area / hullArea; 265 if (ratio < params.minConvexity || ratio >= params.maxConvexity) 266 continue; 267 } 268 269 if(moms.m00 == 0.0) 270 continue; 271 center.location = Point2d(moms.m10 / moms.m00, moms.m01 / moms.m00); 272 273 if (params.filterByColor) 274 { 275 if (binaryImage.at<uchar> (cvRound(center.location.y), cvRound(center.location.x)) != params.blobColor) 276 continue; 277 } 278 279 //compute blob radius 280 { 281 std::vector<double> dists; 282 for (size_t pointIdx = 0; pointIdx < contours[contourIdx].size(); pointIdx++) 283 { 284 Point2d pt = contours[contourIdx][pointIdx]; 285 dists.push_back(norm(center.location - pt)); 286 } 287 std::sort(dists.begin(), dists.end()); 288 center.radius = (dists[(dists.size() - 1) / 2] + dists[dists.size() / 2]) / 2.; 289 } 290 291 centers.push_back(center); 292 293 294 #ifdef DEBUG_BLOB_DETECTOR 295 // circle( keypointsImage, center.location, 1, Scalar(0,0,255), 1 ); 296 #endif 297 } 298 #ifdef DEBUG_BLOB_DETECTOR 299 // imshow("bk", keypointsImage ); 300 // waitKey(); 301 #endif 302 } 303 304 void SimpleBlobDetectorImpl::detect(InputArray image, std::vector<cv::KeyPoint>& keypoints, InputArray) 305 { 306 //TODO: support mask 307 keypoints.clear(); 308 Mat grayscaleImage; 309 if (image.channels() == 3) 310 cvtColor(image, grayscaleImage, COLOR_BGR2GRAY); 311 else 312 grayscaleImage = image.getMat(); 313 314 std::vector < std::vector<Center> > centers; 315 for (double thresh = params.minThreshold; thresh < params.maxThreshold; thresh += params.thresholdStep) 316 { 317 Mat binarizedImage; 318 threshold(grayscaleImage, binarizedImage, thresh, 255, THRESH_BINARY); 319 320 std::vector < Center > curCenters; 321 findBlobs(grayscaleImage, binarizedImage, curCenters); 322 std::vector < std::vector<Center> > newCenters; 323 for (size_t i = 0; i < curCenters.size(); i++) 324 { 325 bool isNew = true; 326 for (size_t j = 0; j < centers.size(); j++) 327 { 328 double dist = norm(centers[j][ centers[j].size() / 2 ].location - curCenters[i].location); 329 isNew = dist >= params.minDistBetweenBlobs && dist >= centers[j][ centers[j].size() / 2 ].radius && dist >= curCenters[i].radius; 330 if (!isNew) 331 { 332 centers[j].push_back(curCenters[i]); 333 334 size_t k = centers[j].size() - 1; 335 while( k > 0 && centers[j][k].radius < centers[j][k-1].radius ) 336 { 337 centers[j][k] = centers[j][k-1]; 338 k--; 339 } 340 centers[j][k] = curCenters[i]; 341 342 break; 343 } 344 } 345 if (isNew) 346 newCenters.push_back(std::vector<Center> (1, curCenters[i])); 347 } 348 std::copy(newCenters.begin(), newCenters.end(), std::back_inserter(centers)); 349 } 350 351 for (size_t i = 0; i < centers.size(); i++) 352 { 353 if (centers[i].size() < params.minRepeatability) 354 continue; 355 Point2d sumPoint(0, 0); 356 double normalizer = 0; 357 for (size_t j = 0; j < centers[i].size(); j++) 358 { 359 sumPoint += centers[i][j].confidence * centers[i][j].location; 360 normalizer += centers[i][j].confidence; 361 } 362 sumPoint *= (1. / normalizer); 363 KeyPoint kpt(sumPoint, (float)(centers[i][centers[i].size() / 2].radius) * 2.0f); 364 keypoints.push_back(kpt); 365 } 366 } 367 368 Ptr<SimpleBlobDetector> SimpleBlobDetector::create(const SimpleBlobDetector::Params& params) 369 { 370 return makePtr<SimpleBlobDetectorImpl>(params); 371 } 372 373 } 374