Commit 0239196ada49e19f60023aecd32b1ff6607233ea
1 parent
a301132a
removed cascade classifier
Showing
1 changed file
with
0 additions
and
327 deletions
openbr/plugins/classification/cascade_classifier.cpp deleted
| 1 | -#include <opencv2/imgproc/imgproc.hpp> | |
| 2 | -#include <opencv2/highgui/highgui.hpp> | |
| 3 | - | |
| 4 | -#include <openbr/plugins/openbr_internal.h> | |
| 5 | -#include <openbr/core/common.h> | |
| 6 | -#include "openbr/core/opencvutils.h" | |
| 7 | - | |
| 8 | -#include <QtConcurrent> | |
| 9 | - | |
| 10 | -using namespace cv; | |
| 11 | - | |
| 12 | -namespace br | |
| 13 | -{ | |
| 14 | - | |
| 15 | -struct Miner | |
| 16 | -{ | |
| 17 | - Template src; | |
| 18 | - Template scaledSrc; | |
| 19 | - Size windowSize; | |
| 20 | - Point offset, point; | |
| 21 | - float scale, scaleFactor, stepFactor; | |
| 22 | - | |
| 23 | - Miner(const Template &t, const Size &windowSize, const Point &offset) : | |
| 24 | - src(t), | |
| 25 | - windowSize(windowSize), | |
| 26 | - offset(offset), | |
| 27 | - point(offset) | |
| 28 | - { | |
| 29 | - scale = 1.0F; | |
| 30 | - scaleFactor = 1.4142135623730950488016887242097F; | |
| 31 | - stepFactor = 0.5F; | |
| 32 | - | |
| 33 | - scale = max(((float)windowSize.width + point.x) / ((float)src.m().cols), | |
| 34 | - ((float)windowSize.height + point.y) / ((float)src.m().rows)); | |
| 35 | - Size size((int)(scale*src.m().cols + 0.5F), (int)(scale*src.m().rows + 0.5F)); | |
| 36 | - scaledSrc = resize(src, size); | |
| 37 | - } | |
| 38 | - | |
| 39 | - Template resize(const Template &src, const Size &size) | |
| 40 | - { | |
| 41 | - Template dst(src.file); | |
| 42 | - for (int i=0; i<src.size(); i++) { | |
| 43 | - Mat buffer; | |
| 44 | - cv::resize(src[i], buffer, size); | |
| 45 | - dst.append(buffer); | |
| 46 | - } | |
| 47 | - return dst; | |
| 48 | - } | |
| 49 | - | |
| 50 | - Template mine(bool *newImg) | |
| 51 | - { | |
| 52 | - Template dst(src.file); | |
| 53 | - // Copy region of winSize region of img into m | |
| 54 | - for (int i=0; i<scaledSrc.size(); i++) { | |
| 55 | - Mat window(windowSize.height, windowSize.width, CV_8U, | |
| 56 | - (void*)(scaledSrc[i].data + point.y * scaledSrc[i].step + point.x * scaledSrc[i].elemSize()), | |
| 57 | - scaledSrc[i].step); | |
| 58 | - Mat sample; | |
| 59 | - window.copyTo(sample); | |
| 60 | - dst.append(sample); | |
| 61 | - } | |
| 62 | - | |
| 63 | - if ((int)(point.x + (1.0F + stepFactor) * windowSize.width) < scaledSrc.m().cols) | |
| 64 | - point.x += (int)(stepFactor * windowSize.width); | |
| 65 | - else { | |
| 66 | - point.x = offset.x; | |
| 67 | - if ((int)(point.y + (1.0F + stepFactor) * windowSize.height) < scaledSrc.m().rows) | |
| 68 | - point.y += (int)(stepFactor * windowSize.height); | |
| 69 | - else { | |
| 70 | - point.y = offset.y; | |
| 71 | - scale *= scaleFactor; | |
| 72 | - if (scale <= 1.0F) { | |
| 73 | - Size size((int)(scale*src.m().cols), (int)(scale*src.m().rows)); | |
| 74 | - scaledSrc = resize(src, size); | |
| 75 | - } else { | |
| 76 | - *newImg = true; | |
| 77 | - return dst; | |
| 78 | - } | |
| 79 | - } | |
| 80 | - } | |
| 81 | - | |
| 82 | - *newImg = false; | |
| 83 | - return dst; | |
| 84 | - } | |
| 85 | -}; | |
| 86 | - | |
| 87 | -/*! | |
| 88 | - * \brief A meta Classifier that creates a cascade of another Classifier. The cascade is a series of stages, each with its own instance of a given classifier. A sample can only reach the next stage if it is classified as positive by the previous stage. | |
| 89 | - * \author Jordan Cheney \cite jcheney | |
| 90 | - * \author Scott Klum \cite sklum | |
| 91 | - * \br_property int numStages The number of stages in the cascade | |
| 92 | - * \br_property int numPos The number of positives to feed each stage during training | |
| 93 | - * \br_property int numNegs The number of negatives to feed each stage during training. A negative sample must have been classified by the previous stages in the cascade as positive to be fed to the next stage during training. | |
| 94 | - * \br_property float maxFAR A termination parameter. Calculated as (number of passed negatives) / (total number of checked negatives) for a given stage during training. If that number is below the given maxFAR cascade training is terminated early. This can help prevent overfitting. | |
| 95 | - * \br_property bool requireAllStages If true, the cascade will train until it has the number of stages specified by numStages even if the FAR at a given is lower than maxFAR. | |
| 96 | - * \br_property int maxStage Parameter to limit the stages used at test time. If -1 (default), all numStages stages will be used. | |
| 97 | - * \br_paper Paul Viola, Michael Jones | |
| 98 | - * Rapid Object Detection using a Boosted Cascade of Simple Features | |
| 99 | - * CVPR, 2001 | |
| 100 | - * \br_link Rapid Object Detection using a Boosted Cascade of Simple Features https://www.cs.cmu.edu/~efros/courses/LBMV07/Papers/viola-cvpr-01.pdf | |
| 101 | - */ | |
| 102 | -class CascadeClassifier : public Classifier | |
| 103 | -{ | |
| 104 | - Q_OBJECT | |
| 105 | - | |
| 106 | - Q_PROPERTY(QString stageDescription READ get_stageDescription WRITE set_stageDescription RESET reset_stageDescription STORED false) | |
| 107 | - Q_PROPERTY(int numStages READ get_numStages WRITE set_numStages RESET reset_numStages STORED false) | |
| 108 | - Q_PROPERTY(int numPos READ get_numPos WRITE set_numPos RESET reset_numPos STORED false) | |
| 109 | - Q_PROPERTY(int numNegs READ get_numNegs WRITE set_numNegs RESET reset_numNegs STORED false) | |
| 110 | - Q_PROPERTY(float maxFAR READ get_maxFAR WRITE set_maxFAR RESET reset_maxFAR STORED false) | |
| 111 | - Q_PROPERTY(bool requireAllStages READ get_requireAllStages WRITE set_requireAllStages RESET reset_requireAllStages STORED false) | |
| 112 | - Q_PROPERTY(int maxStage READ get_maxStage WRITE set_maxStage RESET reset_maxStage STORED false) | |
| 113 | - Q_PROPERTY(QList<br::Classifier*> stages READ get_stages WRITE set_stages RESET reset_stages STORED false) | |
| 114 | - | |
| 115 | - BR_PROPERTY(QString, stageDescription, "") | |
| 116 | - BR_PROPERTY(int, numStages, 20) | |
| 117 | - BR_PROPERTY(int, numPos, 1000) | |
| 118 | - BR_PROPERTY(int, numNegs, 1000) | |
| 119 | - BR_PROPERTY(float, maxFAR, pow(0.5, numStages)) | |
| 120 | - BR_PROPERTY(bool, requireAllStages, false) | |
| 121 | - BR_PROPERTY(int, maxStage, -1) | |
| 122 | - BR_PROPERTY(QList<br::Classifier*>, stages, QList<br::Classifier*>()) | |
| 123 | - | |
| 124 | - TemplateList posImages, negImages; | |
| 125 | - TemplateList posSamples, negSamples; | |
| 126 | - | |
| 127 | - QList<int> negIndices, posIndices; | |
| 128 | - int negIndex, posIndex, samplingRound; | |
| 129 | - | |
| 130 | - QMutex samplingMutex, miningMutex; | |
| 131 | - | |
| 132 | - void init() | |
| 133 | - { | |
| 134 | - negIndex = posIndex = samplingRound = 0; | |
| 135 | - } | |
| 136 | - | |
| 137 | - bool getPositive(Template &img) | |
| 138 | - { | |
| 139 | - if (posIndex >= posImages.size()) | |
| 140 | - return false; | |
| 141 | - img = posImages[posIndices[posIndex++]]; | |
| 142 | - return true; | |
| 143 | - } | |
| 144 | - | |
| 145 | - Template getNegative(Point &offset) | |
| 146 | - { | |
| 147 | - Template negative; | |
| 148 | - | |
| 149 | - const Size size = windowSize(); | |
| 150 | - // Grab negative from list | |
| 151 | - int count = negImages.size(); | |
| 152 | - for (int i = 0; i < count; i++) { | |
| 153 | - negative = negImages[negIndices[negIndex++]]; | |
| 154 | - | |
| 155 | - samplingRound += negIndex / count; | |
| 156 | - samplingRound = samplingRound % (size.width * size.height); | |
| 157 | - negIndex %= count; | |
| 158 | - | |
| 159 | - offset.x = qMin( (int)samplingRound % size.width, negative.m().cols - size.width); | |
| 160 | - offset.y = qMin( (int)samplingRound / size.width, negative.m().rows - size.height); | |
| 161 | - if (!negative.m().empty() && negative.m().type() == CV_8U | |
| 162 | - && offset.x >= 0 && offset.y >= 0) | |
| 163 | - break; | |
| 164 | - } | |
| 165 | - | |
| 166 | - return negative; | |
| 167 | - } | |
| 168 | - | |
| 169 | - uint64 mine() | |
| 170 | - { | |
| 171 | - uint64 passedNegatives = 0; | |
| 172 | - forever { | |
| 173 | - Template negative; | |
| 174 | - Point offset; | |
| 175 | - QMutexLocker samplingLocker(&samplingMutex); | |
| 176 | - negative = getNegative(offset); | |
| 177 | - samplingLocker.unlock(); | |
| 178 | - | |
| 179 | - Miner miner(negative, windowSize(), offset); | |
| 180 | - forever { | |
| 181 | - bool newImg; | |
| 182 | - Template sample = miner.mine(&newImg); | |
| 183 | - if (!newImg) { | |
| 184 | - if (negSamples.size() >= numNegs) | |
| 185 | - return passedNegatives; | |
| 186 | - | |
| 187 | - float confidence; | |
| 188 | - if (classify(sample, true, &confidence) != 0) { | |
| 189 | - QMutexLocker miningLocker(&miningMutex); | |
| 190 | - if (negSamples.size() >= numNegs) | |
| 191 | - return passedNegatives; | |
| 192 | - negSamples.append(sample); | |
| 193 | - printf("Negative samples: %d\r", negSamples.size()); | |
| 194 | - } | |
| 195 | - | |
| 196 | - passedNegatives++; | |
| 197 | - } else | |
| 198 | - break; | |
| 199 | - } | |
| 200 | - } | |
| 201 | - } | |
| 202 | - | |
| 203 | - void train(const TemplateList &data) | |
| 204 | - { | |
| 205 | - foreach (const Template &t, data) | |
| 206 | - t.file.get<float>("Label") == 1.0f ? posImages.append(t) : negImages.append(t); | |
| 207 | - | |
| 208 | - qDebug() << "Total images:" << data.size() | |
| 209 | - << "\nTotal positive images:" << posImages.size() | |
| 210 | - << "\nTotal negative images:" << negImages.size(); | |
| 211 | - | |
| 212 | - posIndices = Common::RandSample(posImages.size(), posImages.size(), true); | |
| 213 | - negIndices = Common::RandSample(negImages.size(), negImages.size(), true); | |
| 214 | - | |
| 215 | - stages.reserve(numStages); | |
| 216 | - for (int i = 0; i < numStages; i++) { | |
| 217 | - qDebug() << "===== TRAINING" << i << "stage ====="; | |
| 218 | - qDebug() << "<BEGIN"; | |
| 219 | - | |
| 220 | - Classifier *next_stage = Classifier::make(stageDescription, NULL); | |
| 221 | - stages.append(next_stage); | |
| 222 | - | |
| 223 | - float currFAR = getSamples(); | |
| 224 | - | |
| 225 | - if (currFAR < maxFAR && !requireAllStages) { | |
| 226 | - qDebug() << "FAR is below required level! Terminating early"; | |
| 227 | - return; | |
| 228 | - } | |
| 229 | - | |
| 230 | - stages.last()->train(posSamples + negSamples); | |
| 231 | - | |
| 232 | - qDebug() << "END>"; | |
| 233 | - } | |
| 234 | - } | |
| 235 | - | |
| 236 | - float classify(const Template &src, bool process, float *confidence) const | |
| 237 | - { | |
| 238 | - float stageConf = 0.0f; | |
| 239 | - const int stopStage = maxStage == -1 ? numStages : maxStage; | |
| 240 | - int stageIndex = 0; | |
| 241 | - foreach (const Classifier *stage, stages) { | |
| 242 | - if (stageIndex++ == stopStage) | |
| 243 | - break; | |
| 244 | - float result = stage->classify(src, process, &stageConf); | |
| 245 | - if (confidence) | |
| 246 | - *confidence += stageConf; | |
| 247 | - if (result == 0.0f) | |
| 248 | - return 0.0f; | |
| 249 | - } | |
| 250 | - return 1.0f; | |
| 251 | - } | |
| 252 | - | |
| 253 | - int numFeatures() const | |
| 254 | - { | |
| 255 | - return stages.first()->numFeatures(); | |
| 256 | - } | |
| 257 | - | |
| 258 | - Template preprocess(const Template &src) const | |
| 259 | - { | |
| 260 | - return stages.first()->preprocess(src); | |
| 261 | - } | |
| 262 | - | |
| 263 | - Size windowSize(int *dx = NULL, int *dy = NULL) const | |
| 264 | - { | |
| 265 | - return stages.first()->windowSize(dx, dy); | |
| 266 | - } | |
| 267 | - | |
| 268 | - void load(QDataStream &stream) | |
| 269 | - { | |
| 270 | - int numStages; stream >> numStages; | |
| 271 | - for (int i = 0; i < numStages; i++) { | |
| 272 | - Classifier *nextStage = Classifier::make(stageDescription, NULL); | |
| 273 | - nextStage->load(stream); | |
| 274 | - stages.append(nextStage); | |
| 275 | - } | |
| 276 | - } | |
| 277 | - | |
| 278 | - void store(QDataStream &stream) const | |
| 279 | - { | |
| 280 | - stream << stages.size(); | |
| 281 | - foreach (const Classifier *stage, stages) | |
| 282 | - stage->store(stream); | |
| 283 | - } | |
| 284 | - | |
| 285 | -private: | |
| 286 | - float getSamples() | |
| 287 | - { | |
| 288 | - posSamples.clear(); posSamples.reserve(numPos); | |
| 289 | - negSamples.clear(); negSamples.reserve(numNegs); | |
| 290 | - posIndex = 0; | |
| 291 | - | |
| 292 | - float confidence; | |
| 293 | - while (posSamples.size() < numPos) { | |
| 294 | - Template pos; | |
| 295 | - if (!getPositive(pos)) | |
| 296 | - qFatal("Cannot get another positive sample!"); | |
| 297 | - | |
| 298 | - if (classify(pos, true, &confidence) > 0.0f) { | |
| 299 | - printf("POS current samples: %d\r", posSamples.size()); | |
| 300 | - posSamples.append(pos); | |
| 301 | - } | |
| 302 | - } | |
| 303 | - | |
| 304 | - qDebug() << "POS count : consumed " << posSamples.size() << ":" << posIndex; | |
| 305 | - | |
| 306 | - QFutureSynchronizer<uint64> futures; | |
| 307 | - for (int i=0; i<QThread::idealThreadCount(); i++) | |
| 308 | - futures.addFuture(QtConcurrent::run(this, &CascadeClassifier::mine)); | |
| 309 | - futures.waitForFinished(); | |
| 310 | - | |
| 311 | - uint64 passedNegs = 0; | |
| 312 | - QList<QFuture<uint64> > results = futures.futures(); | |
| 313 | - for (int i=0; i<results.size(); i++) | |
| 314 | - passedNegs += results[i].result(); | |
| 315 | - | |
| 316 | - double acceptanceRatio = negSamples.size() / (double)passedNegs; | |
| 317 | - qDebug() << "NEG count : acceptanceRatio " << negSamples.size() << ":" << acceptanceRatio; | |
| 318 | - | |
| 319 | - return acceptanceRatio; | |
| 320 | - } | |
| 321 | -}; | |
| 322 | - | |
| 323 | -BR_REGISTER(Classifier, CascadeClassifier) | |
| 324 | - | |
| 325 | -} // namespace br | |
| 326 | - | |
| 327 | -#include "classification/cascade_classifier.moc" |