├── .gitignore ├── LICENSE ├── README.md ├── SConstruct ├── res └── lbpcascade_frontalface.xml └── src ├── EvmGdownIIR.cpp ├── EvmGdownIIR.hpp ├── Pulse.cpp ├── Pulse.hpp ├── Window.cpp ├── Window.hpp ├── ext ├── opencv.cpp └── opencv.hpp ├── main.cpp └── profiler ├── Profiler.cpp └── Profiler.h /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | pulse 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Pedro Boloto Chambino 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pulse 2 | ===== 3 | 4 | ### Android-based implementation of Eulerian Video Magnification for vital signs monitoring 5 | 6 | http://p.chambino.com/dissertation 7 | 8 | Eulerian Video Magnification is a recently presented method capable of 9 | revealing temporal variations in videos that are impossible to see with 10 | the naked eye. Using this method, it is possible to visualize the flow of 11 | blood as it fills the face. From its result, a person’s heart rate is possible 12 | to be extracted. 13 | 14 | This research work was developed at Fraunhofer Portugal and its goal is to 15 | test the feasibility of the implementation of the Eulerian Video Magnification 16 | method on smartphones by developing an Android application for monitoring 17 | vital signs based on the Eulerian Video Magnification method. 18 | 19 | There has been some successful effort on the assessment of vital signs, 20 | such as, heart rate, and breathing rate, in a contact-free way using a 21 | webcamera and even a smartphone. However, since the Eulerian Video 22 | Magnification method was recently proposed, its implementation has not been 23 | tested in smartphones yet.Thus, the Eulerian Video Magnification method 24 | performance for color amplification was optimized in order to execute on an 25 | Android device at a reasonable speed. 26 | 27 | The Android application implemented includes features, such as, detection 28 | of a person’s cardiac pulse, dealing with artifacts’ motion, and real-time 29 | display of the magnified blood flow. Then, the application measurements were 30 | evaluated through tests with several individuals and compared to the ones 31 | detected by the ViTrox application and to the readings of a sphygmomanometer. 32 | 33 | 34 | Dependencies 35 | ------------ 36 | 37 | * OpenCV 2.4.x 38 | 39 | 40 | Build 41 | ----- 42 | 43 | Using SConstruct: 44 | 45 | scons 46 | 47 | 48 | Run 49 | --- 50 | 51 | ./pulse 52 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import os 3 | 4 | matches = [] 5 | for root, dirnames, filenames in os.walk('src'): 6 | for filename in fnmatch.filter(filenames, '*.cpp'): 7 | matches.append(os.path.join(root, filename)) 8 | 9 | VariantDir('build', 'src') 10 | src = [match.replace('src', 'build', 1) for match in matches] 11 | 12 | env = Environment() 13 | env.ParseConfig('pkg-config --cflags --libs opencv') 14 | env.Program('pulse', src) 15 | 16 | -------------------------------------------------------------------------------- /res/lbpcascade_frontalface.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | BOOST 9 | LBP 10 | 24 11 | 24 12 | 13 | GAB 14 | 0.9950000047683716 15 | 0.5000000000000000 16 | 0.9500000000000000 17 | 1 18 | 100 19 | 20 | 256 21 | 20 22 | 23 | 24 | <_> 25 | 3 26 | -0.7520892024040222 27 | 28 | 29 | <_> 30 | 31 | 0 -1 46 -67130709 -21569 -1426120013 -1275125205 -21585 32 | -16385 587145899 -24005 33 | 34 | -0.6543210148811340 0.8888888955116272 35 | 36 | <_> 37 | 38 | 0 -1 13 -163512766 -769593758 -10027009 -262145 -514457854 39 | -193593353 -524289 -1 40 | 41 | -0.7739216089248657 0.7278633713722229 42 | 43 | <_> 44 | 45 | 0 -1 2 -363936790 -893203669 -1337948010 -136907894 46 | 1088782736 -134217726 -741544961 -1590337 47 | 48 | -0.7068563103675842 0.6761534214019775 49 | 50 | <_> 51 | 4 52 | -0.4872078299522400 53 | 54 | 55 | <_> 56 | 57 | 0 -1 84 2147483647 1946124287 -536870913 2147450879 58 | 738132490 1061101567 243204619 2147446655 59 | 60 | -0.8083735704421997 0.7685696482658386 61 | 62 | <_> 63 | 64 | 0 -1 21 2147483647 263176079 1879048191 254749487 1879048191 65 | -134252545 -268435457 801111999 66 | 67 | -0.7698410153388977 0.6592915654182434 68 | 69 | <_> 70 | 71 | 0 -1 106 -98110272 1610939566 -285484400 -850010381 72 | -189334372 -1671954433 -571026695 -262145 73 | 74 | -0.7506558895111084 0.5444605946540833 75 | 76 | <_> 77 | 78 | 0 -1 48 -798690576 -131075 1095771153 -237144073 -65569 -1 79 | -216727745 -69206049 80 | 81 | -0.7775990366935730 0.5465461611747742 82 | 83 | <_> 84 | 4 85 | -1.1592328548431396 86 | 87 | 88 | <_> 89 | 90 | 0 -1 47 -21585 -20549 -100818262 -738254174 -20561 -36865 91 | -151016790 -134238549 92 | 93 | -0.5601882934570313 0.7743113040924072 94 | 95 | <_> 96 | 97 | 0 -1 12 -286003217 183435247 -268994614 -421330945 98 | -402686081 1090387966 -286785545 -402653185 99 | 100 | -0.6124526262283325 0.6978127956390381 101 | 102 | <_> 103 | 104 | 0 -1 26 -50347012 970882927 -50463492 -1253377 -134218251 105 | -50364513 -33619992 -172490753 106 | 107 | -0.6114496588706970 0.6537628173828125 108 | 109 | <_> 110 | 111 | 0 -1 8 -273 -135266321 1877977738 -2088243418 -134217987 112 | 2146926575 -18910642 1095231247 113 | 114 | -0.6854077577590942 0.5403239130973816 115 | 116 | <_> 117 | 5 118 | -0.7562355995178223 119 | 120 | 121 | <_> 122 | 123 | 0 -1 96 -1273 1870659519 -20971602 -67633153 -134250731 124 | 2004875127 -250 -150995969 125 | 126 | -0.4051094949245453 0.7584033608436585 127 | 128 | <_> 129 | 130 | 0 -1 33 -868162224 -76810262 -4262145 -257 1465211989 131 | -268959873 -2656269 -524289 132 | 133 | -0.7388162612915039 0.5340843200683594 134 | 135 | <_> 136 | 137 | 0 -1 57 -12817 -49 -541103378 -152950 -38993 -20481 -1153876 138 | -72478976 139 | 140 | -0.6582943797111511 0.5339496731758118 141 | 142 | <_> 143 | 144 | 0 -1 125 -269484161 -452984961 -319816180 -1594032130 -2111 145 | -990117891 -488975296 -520947741 146 | 147 | -0.5981323719024658 0.5323504805564880 148 | 149 | <_> 150 | 151 | 0 -1 53 557787431 670265215 -1342193665 -1075892225 152 | 1998528318 1056964607 -33570977 -1 153 | 154 | -0.6498787999153137 0.4913350641727448 155 | 156 | <_> 157 | 5 158 | -0.8085358142852783 159 | 160 | 161 | <_> 162 | 163 | 0 -1 60 -536873708 880195381 -16842788 -20971521 -176687276 164 | -168427659 -16777260 -33554626 165 | 166 | -0.5278195738792419 0.6946372389793396 167 | 168 | <_> 169 | 170 | 0 -1 7 -1 -62981529 -1090591130 805330978 -8388827 -41945787 171 | -39577 -531118985 172 | 173 | -0.5206505060195923 0.6329920291900635 174 | 175 | <_> 176 | 177 | 0 -1 98 -725287348 1347747543 -852489 -16809993 1489881036 178 | -167903241 -1 -1 179 | 180 | -0.7516061067581177 0.4232024252414703 181 | 182 | <_> 183 | 184 | 0 -1 44 -32777 1006582562 -65 935312171 -8388609 -1078198273 185 | -1 733886267 186 | 187 | -0.7639313936233521 0.4123568832874298 188 | 189 | <_> 190 | 191 | 0 -1 24 -85474705 2138828511 -1036436754 817625855 192 | 1123369029 -58796809 -1013468481 -194513409 193 | 194 | -0.5123769044876099 0.5791834592819214 195 | 196 | <_> 197 | 5 198 | -0.5549971461296082 199 | 200 | 201 | <_> 202 | 203 | 0 -1 42 -17409 -20481 -268457797 -134239493 -17473 -1 -21829 204 | -21846 205 | 206 | -0.3763174116611481 0.7298233509063721 207 | 208 | <_> 209 | 210 | 0 -1 6 -805310737 -2098262358 -269504725 682502698 211 | 2147483519 1740574719 -1090519233 -268472385 212 | 213 | -0.5352765917778015 0.5659480094909668 214 | 215 | <_> 216 | 217 | 0 -1 61 -67109678 -6145 -8 -87884584 -20481 -1073762305 218 | -50856216 -16849696 219 | 220 | -0.5678374171257019 0.4961479902267456 221 | 222 | <_> 223 | 224 | 0 -1 123 -138428633 1002418167 -1359008245 -1908670465 225 | -1346685918 910098423 -1359010520 -1346371657 226 | 227 | -0.5706262588500977 0.4572288393974304 228 | 229 | <_> 230 | 231 | 0 -1 9 -89138513 -4196353 1256531674 -1330665426 1216308261 232 | -36190633 33498198 -151796633 233 | 234 | -0.5344601869583130 0.4672054052352905 235 | 236 | <_> 237 | 5 238 | -0.8776460289955139 239 | 240 | 241 | <_> 242 | 243 | 0 -1 105 1073769576 206601725 -34013449 -33554433 -789514004 244 | -101384321 -690225153 -264193 245 | 246 | -0.7700348496437073 0.5943940877914429 247 | 248 | <_> 249 | 250 | 0 -1 30 -1432340997 -823623681 -49153 -34291724 -269484035 251 | -1342767105 -1078198273 -1277955 252 | 253 | -0.5043668746948242 0.6151274442672730 254 | 255 | <_> 256 | 257 | 0 -1 35 -1067385040 -195758209 -436748425 -134217731 258 | -50855988 -129 -1 -1 259 | 260 | -0.6808040738105774 0.4667325913906097 261 | 262 | <_> 263 | 264 | 0 -1 119 832534325 -34111555 -26050561 -423659521 -268468364 265 | 2105014143 -2114244 -17367185 266 | 267 | -0.4927591383457184 0.5401885509490967 268 | 269 | <_> 270 | 271 | 0 -1 82 -1089439888 -1080524865 2143059967 -1114121 272 | -1140949004 -3 -2361356 -739516 273 | 274 | -0.6445107460021973 0.4227822124958038 275 | 276 | <_> 277 | 6 278 | -1.1139287948608398 279 | 280 | 281 | <_> 282 | 283 | 0 -1 52 -1074071553 -1074003969 -1 -1280135430 -5324817 -1 284 | -335548482 582134442 285 | 286 | -0.5307556986808777 0.6258179545402527 287 | 288 | <_> 289 | 290 | 0 -1 99 -706937396 -705364068 -540016724 -570495027 291 | -570630659 -587857963 -33628164 -35848193 292 | 293 | -0.5227634310722351 0.5049746036529541 294 | 295 | <_> 296 | 297 | 0 -1 18 -2035630093 42119158 -268503053 -1671444 261017599 298 | 1325432815 1954394111 -805306449 299 | 300 | -0.4983572661876679 0.5106441378593445 301 | 302 | <_> 303 | 304 | 0 -1 111 -282529488 -1558073088 1426018736 -170526448 305 | -546832487 -5113037 -34243375 -570427929 306 | 307 | -0.4990860521793366 0.5060507059097290 308 | 309 | <_> 310 | 311 | 0 -1 92 1016332500 -606301707 915094269 -1080086049 312 | -1837027144 -1361600280 2147318747 1067975613 313 | 314 | -0.5695009231567383 0.4460467398166657 315 | 316 | <_> 317 | 318 | 0 -1 51 -656420166 -15413034 -141599534 -603435836 319 | 1505950458 -787556946 -79823438 -1326199134 320 | 321 | -0.6590405106544495 0.3616424500942230 322 | 323 | <_> 324 | 7 325 | -0.8243625760078430 326 | 327 | 328 | <_> 329 | 330 | 0 -1 28 -901591776 -201916417 -262 -67371009 -143312112 331 | -524289 -41943178 -1 332 | 333 | -0.4972776770591736 0.6027074456214905 334 | 335 | <_> 336 | 337 | 0 -1 112 -4507851 -411340929 -268437513 -67502145 -17350859 338 | -32901 -71344315 -29377 339 | 340 | -0.4383158981800079 0.5966237187385559 341 | 342 | <_> 343 | 344 | 0 -1 69 -75894785 -117379438 -239063587 -12538500 1485072126 345 | 2076233213 2123118847 801906927 346 | 347 | -0.6386105418205261 0.3977999985218048 348 | 349 | <_> 350 | 351 | 0 -1 19 -823480413 786628589 -16876049 -1364262914 242165211 352 | 1315930109 -696268833 -455082829 353 | 354 | -0.5512794256210327 0.4282079637050629 355 | 356 | <_> 357 | 358 | 0 -1 73 -521411968 6746762 -1396236286 -2038436114 359 | -185612509 57669627 -143132877 -1041235973 360 | 361 | -0.6418755054473877 0.3549866080284119 362 | 363 | <_> 364 | 365 | 0 -1 126 -478153869 1076028979 -1645895615 1365298272 366 | -557859073 -339771473 1442574528 -1058802061 367 | 368 | -0.4841901361942291 0.4668019413948059 369 | 370 | <_> 371 | 372 | 0 -1 45 -246350404 -1650402048 -1610612745 -788400696 373 | 1467604861 -2787397 1476263935 -4481349 374 | 375 | -0.5855734348297119 0.3879135847091675 376 | 377 | <_> 378 | 7 379 | -1.2237116098403931 380 | 381 | 382 | <_> 383 | 384 | 0 -1 114 -24819 1572863935 -16809993 -67108865 2146778388 385 | 1433927541 -268608444 -34865205 386 | 387 | -0.2518476545810700 0.7088654041290283 388 | 389 | <_> 390 | 391 | 0 -1 97 -1841359 -134271049 -32769 -5767369 -1116675 -2185 392 | -8231 -33603327 393 | 394 | -0.4303432404994965 0.5283288359642029 395 | 396 | <_> 397 | 398 | 0 -1 25 -1359507589 -1360593090 -1073778729 -269553812 399 | -809512977 1744707583 -41959433 -134758978 400 | 401 | -0.4259553551673889 0.5440809130668640 402 | 403 | <_> 404 | 405 | 0 -1 34 729753407 -134270989 -1140907329 -235200777 406 | 658456383 2147467263 -1140900929 -16385 407 | 408 | -0.5605589151382446 0.4220733344554901 409 | 410 | <_> 411 | 412 | 0 -1 134 -310380553 -420675595 -193005472 -353568129 413 | 1205338070 -990380036 887604324 -420544526 414 | 415 | -0.5192656517028809 0.4399855434894562 416 | 417 | <_> 418 | 419 | 0 -1 16 -1427119361 1978920959 -287119734 -487068946 420 | 114759245 -540578051 -707510259 -671660453 421 | 422 | -0.5013077259063721 0.4570254683494568 423 | 424 | <_> 425 | 426 | 0 -1 74 -738463762 -889949281 -328301948 -121832450 427 | -1142658284 -1863576559 2146417353 -263185 428 | 429 | -0.4631414115428925 0.4790246188640595 430 | 431 | <_> 432 | 7 433 | -0.5544230937957764 434 | 435 | 436 | <_> 437 | 438 | 0 -1 113 -76228780 -65538 -1 -67174401 -148007 -33 -221796 439 | -272842924 440 | 441 | -0.3949716091156006 0.6082032322883606 442 | 443 | <_> 444 | 445 | 0 -1 110 369147696 -1625232112 2138570036 -1189900 790708019 446 | -1212613127 799948719 -4456483 447 | 448 | -0.4855885505676270 0.4785369932651520 449 | 450 | <_> 451 | 452 | 0 -1 37 784215839 -290015241 536832799 -402984963 453 | -1342414991 -838864897 -176769 -268456129 454 | 455 | -0.4620285332202911 0.4989669024944305 456 | 457 | <_> 458 | 459 | 0 -1 41 -486418688 -171915327 -340294900 -21938 -519766032 460 | -772751172 -73096060 -585322623 461 | 462 | -0.6420643329620361 0.3624351918697357 463 | 464 | <_> 465 | 466 | 0 -1 117 -33554953 -475332625 -1423463824 -2077230421 467 | -4849669 -2080505925 -219032928 -1071915349 468 | 469 | -0.4820112884044647 0.4632140696048737 470 | 471 | <_> 472 | 473 | 0 -1 65 -834130468 -134217476 -1349314083 -1073803559 474 | -619913764 -1449131844 -1386890321 -1979118423 475 | 476 | -0.4465552568435669 0.5061788558959961 477 | 478 | <_> 479 | 480 | 0 -1 56 -285249779 1912569855 -16530 -1731022870 -1161904146 481 | -1342177297 -268439634 -1464078708 482 | 483 | -0.5190586447715759 0.4441480338573456 484 | 485 | <_> 486 | 7 487 | -0.7161560654640198 488 | 489 | 490 | <_> 491 | 492 | 0 -1 20 1246232575 1078001186 -10027057 60102 -277348353 493 | -43646987 -1210581153 1195769615 494 | 495 | -0.4323809444904327 0.5663768053054810 496 | 497 | <_> 498 | 499 | 0 -1 15 -778583572 -612921106 -578775890 -4036478 500 | -1946580497 -1164766570 -1986687009 -12103599 501 | 502 | -0.4588732719421387 0.4547033011913300 503 | 504 | <_> 505 | 506 | 0 -1 129 -1073759445 2013231743 -1363169553 -1082459201 507 | -1414286549 868185983 -1356133589 -1077936257 508 | 509 | -0.5218553543090820 0.4111092388629913 510 | 511 | <_> 512 | 513 | 0 -1 102 -84148365 -2093417722 -1204850272 564290299 514 | -67121221 -1342177350 -1309195902 -776734797 515 | 516 | -0.4920000731945038 0.4326725304126740 517 | 518 | <_> 519 | 520 | 0 -1 88 -25694458 67104495 -290216278 -168563037 2083877442 521 | 1702788383 -144191964 -234882162 522 | 523 | -0.4494568109512329 0.4448510706424713 524 | 525 | <_> 526 | 527 | 0 -1 59 -857980836 904682741 -1612267521 232279415 528 | 1550862252 -574825221 -357380888 -4579409 529 | 530 | -0.5180826783180237 0.3888972699642181 531 | 532 | <_> 533 | 534 | 0 -1 27 -98549440 -137838400 494928389 -246013630 939541351 535 | -1196072350 -620603549 2137216273 536 | 537 | -0.6081240773200989 0.3333222270011902 538 | 539 | <_> 540 | 8 541 | -0.6743940711021423 542 | 543 | 544 | <_> 545 | 546 | 0 -1 29 -150995201 2071191945 -1302151626 536934335 547 | -1059008937 914128709 1147328110 -268369925 548 | 549 | -0.1790193915367127 0.6605972051620483 550 | 551 | <_> 552 | 553 | 0 -1 128 -134509479 1610575703 -1342177289 1861484541 554 | -1107833788 1577058173 -333558568 -136319041 555 | 556 | -0.3681024610996246 0.5139749646186829 557 | 558 | <_> 559 | 560 | 0 -1 70 -1 1060154476 -1090984524 -630918524 -539492875 561 | 779616255 -839568424 -321 562 | 563 | -0.3217232525348663 0.6171553134918213 564 | 565 | <_> 566 | 567 | 0 -1 4 -269562385 -285029906 -791084350 -17923776 235286671 568 | 1275504943 1344390399 -966276889 569 | 570 | -0.4373284578323364 0.4358185231685638 571 | 572 | <_> 573 | 574 | 0 -1 76 17825984 -747628419 595427229 1474759671 575672208 575 | -1684005538 872217086 -1155858277 576 | 577 | -0.4404836893081665 0.4601220190525055 578 | 579 | <_> 580 | 581 | 0 -1 124 -336593039 1873735591 -822231622 -355795238 582 | -470820869 -1997537409 -1057132384 -1015285005 583 | 584 | -0.4294152259826660 0.4452161788940430 585 | 586 | <_> 587 | 588 | 0 -1 54 -834212130 -593694721 -322142257 -364892500 589 | -951029539 -302125121 -1615106053 -79249765 590 | 591 | -0.3973052501678467 0.4854526817798615 592 | 593 | <_> 594 | 595 | 0 -1 95 1342144479 2147431935 -33554561 -47873 -855685912 -1 596 | 1988052447 536827383 597 | 598 | -0.7054683566093445 0.2697997391223908 599 | 600 | <_> 601 | 9 602 | -1.2042298316955566 603 | 604 | 605 | <_> 606 | 607 | 0 -1 39 1431368960 -183437936 -537002499 -137497097 608 | 1560590321 -84611081 -2097193 -513 609 | 610 | -0.5905947685241699 0.5101932883262634 611 | 612 | <_> 613 | 614 | 0 -1 120 -1645259691 2105491231 2130706431 1458995007 615 | -8567536 -42483883 -33780003 -21004417 616 | 617 | -0.4449204802513123 0.4490709304809570 618 | 619 | <_> 620 | 621 | 0 -1 89 -612381022 -505806938 -362027516 -452985106 622 | 275854917 1920431639 -12600561 -134221825 623 | 624 | -0.4693818688392639 0.4061094820499420 625 | 626 | <_> 627 | 628 | 0 -1 14 -805573153 -161 -554172679 -530519488 -16779441 629 | 2000682871 -33604275 -150997129 630 | 631 | -0.3600351214408875 0.5056326985359192 632 | 633 | <_> 634 | 635 | 0 -1 67 6192 435166195 1467449341 2046691505 -1608493775 636 | -4755729 -1083162625 -71365637 637 | 638 | -0.4459891915321350 0.4132415652275085 639 | 640 | <_> 641 | 642 | 0 -1 86 -41689215 -3281034 1853357967 -420712635 -415924289 643 | -270209208 -1088293113 -825311232 644 | 645 | -0.4466069042682648 0.4135067760944367 646 | 647 | <_> 648 | 649 | 0 -1 80 -117391116 -42203396 2080374461 -188709 -542008165 650 | -356831940 -1091125345 -1073796897 651 | 652 | -0.3394956290721893 0.5658645033836365 653 | 654 | <_> 655 | 656 | 0 -1 75 -276830049 1378714472 -1342181951 757272098 657 | 1073740607 -282199241 -415761549 170896931 658 | 659 | -0.5346512198448181 0.3584479391574860 660 | 661 | <_> 662 | 663 | 0 -1 55 -796075825 -123166849 2113667055 -217530421 664 | -1107432194 -16385 -806359809 -391188771 665 | 666 | -0.4379335641860962 0.4123645126819611 667 | 668 | <_> 669 | 10 670 | -0.8402050137519836 671 | 672 | 673 | <_> 674 | 675 | 0 -1 71 -890246622 15525883 -487690486 47116238 -1212319899 676 | -1291847681 -68159890 -469829921 677 | 678 | -0.2670986354351044 0.6014143228530884 679 | 680 | <_> 681 | 682 | 0 -1 31 -1361180685 -1898008841 -1090588811 -285410071 683 | -1074016265 -840443905 2147221487 -262145 684 | 685 | -0.4149844348430634 0.4670888185501099 686 | 687 | <_> 688 | 689 | 0 -1 40 1426190596 1899364271 2142731795 -142607505 690 | -508232452 -21563393 -41960001 -65 691 | 692 | -0.4985891580581665 0.3719584941864014 693 | 694 | <_> 695 | 696 | 0 -1 109 -201337965 10543906 -236498096 -746195597 697 | 1974565825 -15204415 921907633 -190058309 698 | 699 | -0.4568729996681213 0.3965812027454376 700 | 701 | <_> 702 | 703 | 0 -1 130 -595026732 -656401928 -268649235 -571490699 704 | -440600392 -133131 -358810952 -2004088646 705 | 706 | -0.4770836830139160 0.3862601518630981 707 | 708 | <_> 709 | 710 | 0 -1 66 941674740 -1107882114 1332789109 -67691015 711 | -1360463693 -1556612430 -609108546 733546933 712 | 713 | -0.4877715110778809 0.3778986334800720 714 | 715 | <_> 716 | 717 | 0 -1 49 -17114945 -240061474 1552871558 -82775604 -932393844 718 | -1308544889 -532635478 -99042357 719 | 720 | -0.3721654713153839 0.4994400143623352 721 | 722 | <_> 723 | 724 | 0 -1 133 -655906006 1405502603 -939205164 1884929228 725 | -498859222 559417357 -1928559445 -286264385 726 | 727 | -0.3934195041656494 0.4769641458988190 728 | 729 | <_> 730 | 731 | 0 -1 0 -335837777 1860677295 -90 -1946186226 931096183 732 | 251612987 2013265917 -671232197 733 | 734 | -0.4323300719261169 0.4342164099216461 735 | 736 | <_> 737 | 738 | 0 -1 103 37769424 -137772680 374692301 2002666345 -536176194 739 | -1644484728 807009019 1069089930 740 | 741 | -0.4993278682231903 0.3665378093719482 742 | 743 | <_> 744 | 9 745 | -1.1974394321441650 746 | 747 | 748 | <_> 749 | 750 | 0 -1 43 -5505 2147462911 2143265466 -4511070 -16450 -257 751 | -201348440 -71333206 752 | 753 | -0.3310225307941437 0.5624626278877258 754 | 755 | <_> 756 | 757 | 0 -1 90 -136842268 -499330741 2015250980 -87107126 758 | -641665744 -788524639 -1147864792 -134892563 759 | 760 | -0.5266560912132263 0.3704403042793274 761 | 762 | <_> 763 | 764 | 0 -1 104 -146800880 -1780368555 2111170033 -140904684 765 | -16777551 -1946681885 -1646463595 -839131947 766 | 767 | -0.4171888828277588 0.4540435671806335 768 | 769 | <_> 770 | 771 | 0 -1 85 -832054034 -981663763 -301990281 -578814081 772 | -932319000 -1997406723 -33555201 -69206017 773 | 774 | -0.4556705355644226 0.3704262077808380 775 | 776 | <_> 777 | 778 | 0 -1 24 -118492417 -1209026825 1119023838 -1334313353 779 | 1112948738 -297319313 1378887291 -139469193 780 | 781 | -0.4182529747486115 0.4267231225967407 782 | 783 | <_> 784 | 785 | 0 -1 78 -1714382628 -2353704 -112094959 -549613092 786 | -1567058760 -1718550464 -342315012 -1074972227 787 | 788 | -0.3625369668006897 0.4684656262397766 789 | 790 | <_> 791 | 792 | 0 -1 5 -85219702 316836394 -33279 1904970288 2117267315 793 | -260901769 -621461759 -88607770 794 | 795 | -0.4742925167083740 0.3689507246017456 796 | 797 | <_> 798 | 799 | 0 -1 11 -294654041 -353603585 -1641159686 -50331921 800 | -2080899877 1145569279 -143132713 -152044037 801 | 802 | -0.3666271567344666 0.4580127298831940 803 | 804 | <_> 805 | 806 | 0 -1 32 1887453658 -638545712 -1877976819 -34320972 807 | -1071067983 -661345416 -583338277 1060190561 808 | 809 | -0.4567637443542481 0.3894708156585693 810 | 811 | <_> 812 | 9 813 | -0.5733128190040588 814 | 815 | 816 | <_> 817 | 818 | 0 -1 122 -994063296 1088745462 -318837116 -319881377 819 | 1102566613 1165490103 -121679694 -134744129 820 | 821 | -0.4055117964744568 0.5487945079803467 822 | 823 | <_> 824 | 825 | 0 -1 68 -285233233 -538992907 1811935199 -369234005 -529 826 | -20593 -20505 -1561401854 827 | 828 | -0.3787897229194641 0.4532003402709961 829 | 830 | <_> 831 | 832 | 0 -1 58 -1335245632 1968917183 1940861695 536816369 833 | -1226071367 -570908176 457026619 1000020667 834 | 835 | -0.4258328974246979 0.4202791750431061 836 | 837 | <_> 838 | 839 | 0 -1 94 -1360318719 -1979797897 -50435249 -18646473 840 | -608879292 -805306691 -269304244 -17840167 841 | 842 | -0.4561023116111755 0.4002747833728790 843 | 844 | <_> 845 | 846 | 0 -1 87 2062765935 -16449 -1275080721 -16406 45764335 847 | -1090552065 -772846337 -570464322 848 | 849 | -0.4314672648906708 0.4086346626281738 850 | 851 | <_> 852 | 853 | 0 -1 127 -536896021 1080817663 -738234288 -965478709 854 | -2082767969 1290855887 1993822934 -990381609 855 | 856 | -0.4174543321132660 0.4249868988990784 857 | 858 | <_> 859 | 860 | 0 -1 3 -818943025 168730891 -293610428 -79249354 669224671 861 | 621166734 1086506807 1473768907 862 | 863 | -0.4321364760398865 0.4090838730335236 864 | 865 | <_> 866 | 867 | 0 -1 79 -68895696 -67107736 -1414315879 -841676168 868 | -619843344 -1180610531 -1081990469 1043203389 869 | 870 | -0.5018386244773865 0.3702533841133118 871 | 872 | <_> 873 | 874 | 0 -1 116 -54002134 -543485719 -2124882422 -1437445858 875 | -115617074 -1195787391 -1096024366 -2140472445 876 | 877 | -0.5037505626678467 0.3564981222152710 878 | 879 | <_> 880 | 9 881 | -0.4892596900463104 882 | 883 | 884 | <_> 885 | 886 | 0 -1 132 -67113211 2003808111 1862135111 846461923 -2752 887 | 2002237273 -273154752 1937223539 888 | 889 | -0.2448196411132813 0.5689709186553955 890 | 891 | <_> 892 | 893 | 0 -1 62 1179423888 -78064940 -611839555 -539167899 894 | -1289358360 -1650810108 -892540499 -1432827684 895 | 896 | -0.4633283913135529 0.3587929606437683 897 | 898 | <_> 899 | 900 | 0 -1 23 -285212705 -78450761 -656212031 -264050110 -27787425 901 | -1334349961 -547662981 -135796924 902 | 903 | -0.3731099069118500 0.4290455579757690 904 | 905 | <_> 906 | 907 | 0 -1 77 341863476 403702016 -550588417 1600194541 908 | -1080690735 951127993 -1388580949 -1153717473 909 | 910 | -0.3658909499645233 0.4556473195552826 911 | 912 | <_> 913 | 914 | 0 -1 22 -586880702 -204831512 -100644596 -39319550 915 | -1191150794 705692513 457203315 -75806957 916 | 917 | -0.5214384198188782 0.3221037387847900 918 | 919 | <_> 920 | 921 | 0 -1 72 -416546870 545911370 -673716192 -775559454 922 | -264113598 139424 -183369982 -204474641 923 | 924 | -0.4289036989212036 0.4004956185817719 925 | 926 | <_> 927 | 928 | 0 -1 50 -1026505020 -589692154 -1740499937 -1563770497 929 | 1348491006 -60710713 -1109853489 -633909413 930 | 931 | -0.4621542394161224 0.3832748532295227 932 | 933 | <_> 934 | 935 | 0 -1 108 -1448872304 -477895040 -1778390608 -772418127 936 | -1789923416 -1612057181 -805306693 -1415842113 937 | 938 | -0.3711548447608948 0.4612701535224915 939 | 940 | <_> 941 | 942 | 0 -1 92 407905424 -582449988 52654751 -1294472 -285103725 943 | -74633006 1871559083 1057955850 944 | 945 | -0.5180652141571045 0.3205870389938355 946 | 947 | <_> 948 | 10 949 | -0.5911940932273865 950 | 951 | 952 | <_> 953 | 954 | 0 -1 81 4112 -1259563825 -846671428 -100902460 1838164148 955 | -74153752 -90653988 -1074263896 956 | 957 | -0.2592592537403107 0.5873016119003296 958 | 959 | <_> 960 | 961 | 0 -1 1 -285216785 -823206977 -1085589 -1081346 1207959293 962 | 1157103471 2097133565 -2097169 963 | 964 | -0.3801195919513702 0.4718827307224274 965 | 966 | <_> 967 | 968 | 0 -1 121 -12465 -536875169 2147478367 2130706303 -37765492 969 | -866124467 -318782328 -1392509185 970 | 971 | -0.3509117066860199 0.5094807147979736 972 | 973 | <_> 974 | 975 | 0 -1 38 2147449663 -20741 -16794757 1945873146 -16710 -1 976 | -8406341 -67663041 977 | 978 | -0.4068757295608521 0.4130136370658875 979 | 980 | <_> 981 | 982 | 0 -1 17 -155191713 866117231 1651407483 548272812 -479201468 983 | -447742449 1354229504 -261884429 984 | 985 | -0.4557141065597534 0.3539792001247406 986 | 987 | <_> 988 | 989 | 0 -1 100 -225319378 -251682065 -492783986 -792341777 990 | -1287261695 1393643841 -11274182 -213909521 991 | 992 | -0.4117803275585175 0.4118592441082001 993 | 994 | <_> 995 | 996 | 0 -1 63 -382220122 -2002072729 -51404800 -371201558 997 | -923011069 -2135301457 -2066104743 -1042557441 998 | 999 | -0.4008397758007050 0.4034757018089294 1000 | 1001 | <_> 1002 | 1003 | 0 -1 101 -627353764 -48295149 1581203952 -436258614 1004 | -105268268 -1435893445 -638126888 -1061107126 1005 | 1006 | -0.5694189667701721 0.2964762747287750 1007 | 1008 | <_> 1009 | 1010 | 0 -1 118 -8399181 1058107691 -621022752 -251003468 -12582915 1011 | -574619739 -994397789 -1648362021 1012 | 1013 | -0.3195341229438782 0.5294018983840942 1014 | 1015 | <_> 1016 | 1017 | 0 -1 92 -348343812 -1078389516 1717960437 364735981 1018 | -1783841602 -4883137 -457572354 -1076950384 1019 | 1020 | -0.3365339040756226 0.5067458748817444 1021 | 1022 | <_> 1023 | 10 1024 | -0.7612916231155396 1025 | 1026 | 1027 | <_> 1028 | 1029 | 0 -1 10 -1976661318 -287957604 -1659497122 -782068 43591089 1030 | -453637880 1435470000 -1077438561 1031 | 1032 | -0.4204545319080353 0.5165745615959168 1033 | 1034 | <_> 1035 | 1036 | 0 -1 131 -67110925 14874979 -142633168 -1338923040 1037 | 2046713291 -2067933195 1473503712 -789579837 1038 | 1039 | -0.3762553930282593 0.4075302779674530 1040 | 1041 | <_> 1042 | 1043 | 0 -1 83 -272814301 -1577073 -1118685 -305156120 -1052289 1044 | -1073813756 -538971154 -355523038 1045 | 1046 | -0.4253497421741486 0.3728055357933044 1047 | 1048 | <_> 1049 | 1050 | 0 -1 135 -2233 -214486242 -538514758 573747007 -159390971 1051 | 1994225489 -973738098 -203424005 1052 | 1053 | -0.3601998090744019 0.4563256204128265 1054 | 1055 | <_> 1056 | 1057 | 0 -1 115 -261031688 -1330369299 -641860609 1029570301 1058 | -1306461192 -1196149518 -1529767778 683139823 1059 | 1060 | -0.4034293889999390 0.4160816967487335 1061 | 1062 | <_> 1063 | 1064 | 0 -1 64 -572993608 -34042628 -417865 -111109 -1433365268 1065 | -19869715 -1920939864 -1279457063 1066 | 1067 | -0.3620899617671967 0.4594142735004425 1068 | 1069 | <_> 1070 | 1071 | 0 -1 36 -626275097 -615256993 1651946018 805366393 1072 | 2016559730 -430780849 -799868165 -16580645 1073 | 1074 | -0.3903816640377045 0.4381459355354309 1075 | 1076 | <_> 1077 | 1078 | 0 -1 93 1354797300 -1090957603 1976418270 -1342502178 1079 | -1851873892 -1194637077 -1153521668 -1108399474 1080 | 1081 | -0.3591445386409760 0.4624078869819641 1082 | 1083 | <_> 1084 | 1085 | 0 -1 91 68157712 1211368313 -304759523 1063017136 798797750 1086 | -275513546 648167355 -1145357350 1087 | 1088 | -0.4297670423984528 0.4023293554782867 1089 | 1090 | <_> 1091 | 1092 | 0 -1 107 -546318240 -1628569602 -163577944 -537002306 1093 | -545456389 -1325465645 -380446736 -1058473386 1094 | 1095 | -0.5727006793022156 0.2995934784412384 1096 | 1097 | <_> 1098 | 1099 | 0 0 3 5 1100 | <_> 1101 | 1102 | 0 0 4 2 1103 | <_> 1104 | 1105 | 0 0 6 3 1106 | <_> 1107 | 1108 | 0 1 2 3 1109 | <_> 1110 | 1111 | 0 1 3 3 1112 | <_> 1113 | 1114 | 0 1 3 7 1115 | <_> 1116 | 1117 | 0 4 3 3 1118 | <_> 1119 | 1120 | 0 11 3 4 1121 | <_> 1122 | 1123 | 0 12 8 4 1124 | <_> 1125 | 1126 | 0 14 4 3 1127 | <_> 1128 | 1129 | 1 0 5 3 1130 | <_> 1131 | 1132 | 1 1 2 2 1133 | <_> 1134 | 1135 | 1 3 3 1 1136 | <_> 1137 | 1138 | 1 7 4 4 1139 | <_> 1140 | 1141 | 1 12 2 2 1142 | <_> 1143 | 1144 | 1 13 4 1 1145 | <_> 1146 | 1147 | 1 14 4 3 1148 | <_> 1149 | 1150 | 1 17 3 2 1151 | <_> 1152 | 1153 | 2 0 2 3 1154 | <_> 1155 | 1156 | 2 1 2 2 1157 | <_> 1158 | 1159 | 2 2 4 6 1160 | <_> 1161 | 1162 | 2 3 4 4 1163 | <_> 1164 | 1165 | 2 7 2 1 1166 | <_> 1167 | 1168 | 2 11 2 3 1169 | <_> 1170 | 1171 | 2 17 3 2 1172 | <_> 1173 | 1174 | 3 0 2 2 1175 | <_> 1176 | 1177 | 3 1 7 3 1178 | <_> 1179 | 1180 | 3 7 2 1 1181 | <_> 1182 | 1183 | 3 7 2 4 1184 | <_> 1185 | 1186 | 3 18 2 2 1187 | <_> 1188 | 1189 | 4 0 2 3 1190 | <_> 1191 | 1192 | 4 3 2 1 1193 | <_> 1194 | 1195 | 4 6 2 1 1196 | <_> 1197 | 1198 | 4 6 2 5 1199 | <_> 1200 | 1201 | 4 7 5 2 1202 | <_> 1203 | 1204 | 4 8 4 3 1205 | <_> 1206 | 1207 | 4 18 2 2 1208 | <_> 1209 | 1210 | 5 0 2 2 1211 | <_> 1212 | 1213 | 5 3 4 4 1214 | <_> 1215 | 1216 | 5 6 2 5 1217 | <_> 1218 | 1219 | 5 9 2 2 1220 | <_> 1221 | 1222 | 5 10 2 2 1223 | <_> 1224 | 1225 | 6 3 4 4 1226 | <_> 1227 | 1228 | 6 4 4 3 1229 | <_> 1230 | 1231 | 6 5 2 3 1232 | <_> 1233 | 1234 | 6 5 2 5 1235 | <_> 1236 | 1237 | 6 5 4 3 1238 | <_> 1239 | 1240 | 6 6 4 2 1241 | <_> 1242 | 1243 | 6 6 4 4 1244 | <_> 1245 | 1246 | 6 18 1 2 1247 | <_> 1248 | 1249 | 6 21 2 1 1250 | <_> 1251 | 1252 | 7 0 3 7 1253 | <_> 1254 | 1255 | 7 4 2 3 1256 | <_> 1257 | 1258 | 7 9 5 1 1259 | <_> 1260 | 1261 | 7 21 2 1 1262 | <_> 1263 | 1264 | 8 0 1 4 1265 | <_> 1266 | 1267 | 8 5 2 2 1268 | <_> 1269 | 1270 | 8 5 3 2 1271 | <_> 1272 | 1273 | 8 17 3 1 1274 | <_> 1275 | 1276 | 8 18 1 2 1277 | <_> 1278 | 1279 | 9 0 5 3 1280 | <_> 1281 | 1282 | 9 2 2 6 1283 | <_> 1284 | 1285 | 9 5 1 1 1286 | <_> 1287 | 1288 | 9 11 1 1 1289 | <_> 1290 | 1291 | 9 16 1 1 1292 | <_> 1293 | 1294 | 9 16 2 1 1295 | <_> 1296 | 1297 | 9 17 1 1 1298 | <_> 1299 | 1300 | 9 18 1 1 1301 | <_> 1302 | 1303 | 10 5 1 2 1304 | <_> 1305 | 1306 | 10 5 3 3 1307 | <_> 1308 | 1309 | 10 7 1 5 1310 | <_> 1311 | 1312 | 10 8 1 1 1313 | <_> 1314 | 1315 | 10 9 1 1 1316 | <_> 1317 | 1318 | 10 10 1 1 1319 | <_> 1320 | 1321 | 10 10 1 2 1322 | <_> 1323 | 1324 | 10 14 3 3 1325 | <_> 1326 | 1327 | 10 15 1 1 1328 | <_> 1329 | 1330 | 10 15 2 1 1331 | <_> 1332 | 1333 | 10 16 1 1 1334 | <_> 1335 | 1336 | 10 16 2 1 1337 | <_> 1338 | 1339 | 10 17 1 1 1340 | <_> 1341 | 1342 | 10 21 1 1 1343 | <_> 1344 | 1345 | 11 3 2 2 1346 | <_> 1347 | 1348 | 11 5 1 2 1349 | <_> 1350 | 1351 | 11 5 3 3 1352 | <_> 1353 | 1354 | 11 5 4 6 1355 | <_> 1356 | 1357 | 11 6 1 1 1358 | <_> 1359 | 1360 | 11 7 2 2 1361 | <_> 1362 | 1363 | 11 8 1 2 1364 | <_> 1365 | 1366 | 11 10 1 1 1367 | <_> 1368 | 1369 | 11 10 1 2 1370 | <_> 1371 | 1372 | 11 15 1 1 1373 | <_> 1374 | 1375 | 11 17 1 1 1376 | <_> 1377 | 1378 | 11 18 1 1 1379 | <_> 1380 | 1381 | 12 0 2 2 1382 | <_> 1383 | 1384 | 12 1 2 5 1385 | <_> 1386 | 1387 | 12 2 4 1 1388 | <_> 1389 | 1390 | 12 3 1 3 1391 | <_> 1392 | 1393 | 12 7 3 4 1394 | <_> 1395 | 1396 | 12 10 3 2 1397 | <_> 1398 | 1399 | 12 11 1 1 1400 | <_> 1401 | 1402 | 12 12 3 2 1403 | <_> 1404 | 1405 | 12 14 4 3 1406 | <_> 1407 | 1408 | 12 17 1 1 1409 | <_> 1410 | 1411 | 12 21 2 1 1412 | <_> 1413 | 1414 | 13 6 2 5 1415 | <_> 1416 | 1417 | 13 7 3 5 1418 | <_> 1419 | 1420 | 13 11 3 2 1421 | <_> 1422 | 1423 | 13 17 2 2 1424 | <_> 1425 | 1426 | 13 17 3 2 1427 | <_> 1428 | 1429 | 13 18 1 2 1430 | <_> 1431 | 1432 | 13 18 2 2 1433 | <_> 1434 | 1435 | 14 0 2 2 1436 | <_> 1437 | 1438 | 14 1 1 3 1439 | <_> 1440 | 1441 | 14 2 3 2 1442 | <_> 1443 | 1444 | 14 7 2 1 1445 | <_> 1446 | 1447 | 14 13 2 1 1448 | <_> 1449 | 1450 | 14 13 3 3 1451 | <_> 1452 | 1453 | 14 17 2 2 1454 | <_> 1455 | 1456 | 15 0 2 2 1457 | <_> 1458 | 1459 | 15 0 2 3 1460 | <_> 1461 | 1462 | 15 4 3 2 1463 | <_> 1464 | 1465 | 15 4 3 6 1466 | <_> 1467 | 1468 | 15 6 3 2 1469 | <_> 1470 | 1471 | 15 11 3 4 1472 | <_> 1473 | 1474 | 15 13 3 2 1475 | <_> 1476 | 1477 | 15 17 2 2 1478 | <_> 1479 | 1480 | 15 17 3 2 1481 | <_> 1482 | 1483 | 16 1 2 3 1484 | <_> 1485 | 1486 | 16 3 2 4 1487 | <_> 1488 | 1489 | 16 6 1 1 1490 | <_> 1491 | 1492 | 16 16 2 2 1493 | <_> 1494 | 1495 | 17 1 2 2 1496 | <_> 1497 | 1498 | 17 1 2 5 1499 | <_> 1500 | 1501 | 17 12 2 2 1502 | <_> 1503 | 1504 | 18 0 2 2 1505 | 1506 | -------------------------------------------------------------------------------- /src/EvmGdownIIR.cpp: -------------------------------------------------------------------------------- 1 | #include "EvmGdownIIR.hpp" 2 | #include "profiler/Profiler.h" 3 | #include 4 | #include 5 | #include 6 | 7 | using cv::pyrDown; 8 | using cv::pyrUp; 9 | using cv::resize; 10 | 11 | EvmGdownIIR::EvmGdownIIR() { 12 | first = true; 13 | blurredSize = Size(10, 10); 14 | fLow = 70/60./10; 15 | fHigh = 80/60./10; 16 | alpha = 200; 17 | } 18 | 19 | EvmGdownIIR::~EvmGdownIIR() { 20 | } 21 | 22 | void EvmGdownIIR::onFrame(const Mat& src, Mat& out) { 23 | PROFILE_SCOPED(); 24 | 25 | // convert to float 26 | PROFILE_START_DESC("convert to float"); 27 | src.convertTo(srcFloat, CV_32F); 28 | PROFILE_STOP(); 29 | 30 | // apply spatial filter: blur and downsample 31 | PROFILE_START_DESC("pyrDown"); 32 | resize(srcFloat, blurred, blurredSize, 0, 0, CV_INTER_AREA); 33 | PROFILE_STOP(); 34 | 35 | if (first) { 36 | PROFILE_SCOPED_DESC("first"); 37 | first = false; 38 | blurred.copyTo(lowpassHigh); 39 | blurred.copyTo(lowpassLow); 40 | src.copyTo(out); 41 | } else { 42 | // apply temporal filter: subtraction of two IIR lowpass filters 43 | PROFILE_START_DESC("temporal filter"); 44 | lowpassHigh = lowpassHigh * (1-fHigh) + fHigh * blurred; 45 | lowpassLow = lowpassLow * (1-fLow) + fLow * blurred; 46 | blurred = lowpassHigh - lowpassLow; 47 | PROFILE_STOP(); 48 | 49 | // amplify 50 | PROFILE_START_DESC("amplify"); 51 | blurred *= alpha; 52 | PROFILE_STOP(); 53 | 54 | // resize back to original size 55 | PROFILE_START_DESC("pyrUp"); 56 | resize(blurred, outFloat, src.size(), 0, 0, CV_INTER_LINEAR); 57 | PROFILE_STOP(); 58 | 59 | // add back to original frame 60 | PROFILE_START_DESC("add back to original frame"); 61 | outFloat += srcFloat; 62 | PROFILE_STOP(); 63 | 64 | // convert to 8 bit 65 | PROFILE_START_DESC("convert to 8 bit"); 66 | outFloat.convertTo(out, CV_8U); 67 | PROFILE_STOP(); 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/EvmGdownIIR.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EVMGDOWNIIR_HPP 2 | #define EVMGDOWNIIR_HPP 3 | 4 | #include 5 | 6 | using cv::Mat; 7 | using cv::Size; 8 | 9 | class EvmGdownIIR { 10 | public: 11 | EvmGdownIIR(); 12 | virtual ~EvmGdownIIR(); 13 | 14 | void onFrame(const Mat& src, Mat& out); 15 | 16 | bool first; 17 | Size blurredSize; 18 | double fHigh; 19 | double fLow; 20 | int alpha; 21 | 22 | private: 23 | Mat srcFloat; 24 | Mat blurred; 25 | Mat lowpassHigh; 26 | Mat lowpassLow; 27 | Mat outFloat; 28 | 29 | }; 30 | 31 | #endif /* EVMGDOWNIIR_HPP */ 32 | 33 | -------------------------------------------------------------------------------- /src/Pulse.cpp: -------------------------------------------------------------------------------- 1 | #include "Pulse.hpp" 2 | #include 3 | #include 4 | #include 5 | #include "ext/opencv.hpp" 6 | #include "profiler/Profiler.h" 7 | 8 | using std::stringstream; 9 | using namespace cv; 10 | 11 | Pulse::Pulse() { 12 | maxSignalSize = 100; 13 | relativeMinFaceSize = 0.4; 14 | deleteFaceIn = 1; 15 | holdPulseFor = 30; 16 | fps = 0; 17 | faceDetection.enabled = true; 18 | evm.magnify = true; 19 | evm.alpha = 100; 20 | } 21 | 22 | Pulse::~Pulse() { 23 | } 24 | 25 | void Pulse::load(const string& filename) { 26 | classifier.load(filename); 27 | } 28 | 29 | void Pulse::start(int width, int height) { 30 | now = 0; 31 | lastFaceDetectionTimestamp = 0; 32 | lastBpmTimestamp = 0; 33 | minFaceSize = Size(min(width, height) * relativeMinFaceSize, min(width, height) * relativeMinFaceSize); 34 | faces.clear(); 35 | nextFaceId = 1; 36 | } 37 | 38 | void Pulse::onFrame(Mat& frame) { 39 | PROFILE_SCOPED(); 40 | 41 | // count frames 42 | now = getTickCount(); 43 | 44 | if (faceDetection.enabled) { 45 | // detect faces only every second 46 | if ((now - lastFaceDetectionTimestamp) * 1000. / getTickFrequency() >= 1000) { 47 | lastFaceDetectionTimestamp = getTickCount(); 48 | 49 | PROFILE_START_DESC("detect faces"); 50 | // detect faces 51 | cvtColor(frame, gray, CV_RGB2GRAY); 52 | classifier.detectMultiScale(frame, boxes, 1.1, 3, 0, minFaceSize); 53 | PROFILE_STOP(); 54 | 55 | // iterate through faces and boxes 56 | if (faces.size() <= boxes.size()) { 57 | PROFILE_SCOPED_DESC("equal or more boxes than faces"); 58 | // match each face to nearest box 59 | for (size_t i = 0; i < faces.size(); i++) { 60 | Face& face = faces.at(i); 61 | int boxIndex = face.nearestBox(boxes); 62 | face.deleteIn = deleteFaceIn; 63 | face.updateBox(boxes.at(boxIndex)); 64 | onFace(frame, face, boxes.at(boxIndex)); 65 | boxes.erase(boxes.begin() + boxIndex); 66 | } 67 | // remaining boxes are new faces 68 | for (size_t i = 0; i < boxes.size(); i++) { 69 | faces.push_back(Face(nextFaceId++, boxes.at(i), deleteFaceIn)); 70 | onFace(frame, faces.back(), boxes.at(i)); 71 | } 72 | } else { 73 | PROFILE_SCOPED_DESC("more faces than boxes"); 74 | // match each box to nearest face 75 | for (size_t i = 0; i < faces.size(); i++) { 76 | faces.at(i).selected = false; 77 | } 78 | for (size_t i = 0; i < boxes.size(); i++) { 79 | int faceIndex = nearestFace(boxes.at(i)); 80 | Face& face = faces.at(faceIndex); 81 | face.selected = true; 82 | face.deleteIn = deleteFaceIn; 83 | face.updateBox(boxes.at(i)); 84 | onFace(frame, face, boxes.at(i)); 85 | } 86 | // remaining faces are deleted or marked for deletion 87 | for (size_t i = 0; i < faces.size(); i++) { 88 | Face& face = faces.at(i); 89 | if (!face.selected) { 90 | if (face.deleteIn <= 0) { 91 | faces.erase(faces.begin() + i); 92 | i--; 93 | } else { 94 | face.deleteIn--; 95 | onFace(frame, face, face.box); 96 | } 97 | } 98 | } 99 | } 100 | } else { 101 | PROFILE_SCOPED_DESC("previously detected faces"); 102 | // use previously detected faces 103 | for (size_t i = 0; i < faces.size(); i++) { 104 | Face& face = faces.at(i); 105 | onFace(frame, face, face.box); 106 | } 107 | } 108 | } else { 109 | if (faces.size() == 0 || faces.back().id != faceDetection.disabledFaceId) { 110 | faces.clear(); 111 | double r = relativeMinFaceSize; 112 | int w = frame.size().width; 113 | int h = frame.size().height; 114 | Point tl = Point((1 - r) * w / 2, (1 - r) * h / 2); 115 | Point br = Point((1 + r) * w / 2, (1 + r) * h / 2); 116 | Face face = Face(nextFaceId++, Rect(tl, br), deleteFaceIn); 117 | face.evm.box = Rect(tl, br); 118 | faces.push_back(face); 119 | faceDetection.disabledFaceId = face.id; 120 | } 121 | onFace(frame, faces.back(), faces.back().box); 122 | } 123 | } 124 | 125 | int Pulse::nearestFace(const Rect& box) { 126 | PROFILE_SCOPED(); 127 | 128 | int index = -1; 129 | int min = -1; 130 | Point p; 131 | 132 | // search for first unselected face 133 | for (size_t i = 0; i < faces.size(); i++) { 134 | if (!faces.at(i).selected) { 135 | index = i; 136 | p = box.tl() - faces.at(i).box.tl(); 137 | min = p.x * p.x + p.y * p.y; 138 | break; 139 | } 140 | } 141 | 142 | // no unselected face found 143 | if (index == -1) { 144 | return -1; 145 | } 146 | 147 | // compare with remaining unselected faces 148 | for (size_t i = index + 1; i < faces.size(); i++) { 149 | if (!faces.at(i).selected) { 150 | p = box.tl() - faces.at(i).box.tl(); 151 | int d = p.x * p.x + p.y * p.y; 152 | if (d < min) { 153 | min = d; 154 | index = i; 155 | } 156 | } 157 | } 158 | 159 | return index; 160 | } 161 | 162 | void Pulse::onFace(Mat& frame, Face& face, const Rect& box) { 163 | PROFILE_SCOPED(); 164 | 165 | // only show magnified face when there is pulse 166 | Mat roi = !evm.magnify 167 | || (evm.magnify && face.existsPulse) 168 | || (evm.magnify && !faceDetection.enabled) 169 | ? frame(face.evm.box) : face.evm.out; 170 | 171 | // if magnification is on 172 | if (evm.magnify) { 173 | if (face.evm.evm.first || face.evm.evm.alpha != evm.alpha) { 174 | // reset after changing magnify or alpha value 175 | face.reset(); 176 | } 177 | 178 | // update face Eulerian video magnification values 179 | face.evm.evm.alpha = evm.alpha; 180 | 181 | // apply Eulerian video magnification on face box 182 | face.evm.evm.onFrame(frame(face.evm.box), roi); 183 | 184 | } else if (!face.evm.evm.first) { 185 | // reset after changing magnify value 186 | face.reset(); 187 | } 188 | 189 | // capture raw value and timestamp, shifting if necessary 190 | if (face.raw.rows >= maxSignalSize) { 191 | PROFILE_SCOPED_DESC("shift raw and timestamp"); 192 | const int total = face.raw.rows; 193 | face.raw.rowRange(1, total).copyTo(face.raw.rowRange(0, total - 1)); 194 | face.raw.pop_back(); 195 | face.timestamps.rowRange(1, total).copyTo(face.timestamps.rowRange(0, total - 1)); 196 | face.timestamps.pop_back(); 197 | } 198 | PROFILE_START_DESC("push back raw and timestamp"); 199 | face.raw.push_back(mean(roi)(1)); // grab green channel 200 | face.timestamps.push_back(getTickCount()); 201 | PROFILE_STOP(); 202 | 203 | PROFILE_START_DESC("verify if raw signal is stable enough"); 204 | Scalar rawStdDev; 205 | meanStdDev(face.raw, Scalar(), rawStdDev); 206 | const bool stable = rawStdDev(0) <= (evm.magnify ? 1 : 0) * evm.alpha * 0.045 + 1; 207 | PROFILE_STOP(); 208 | 209 | if (stable) { 210 | // calculate current FPS 211 | currentFps = this->fps; 212 | if (currentFps == 0) { 213 | const double diff = (face.timestamps(face.timestamps.rows - 1) - face.timestamps(0)) * 1000. / getTickFrequency(); 214 | currentFps = face.timestamps.rows * 1000 / diff; 215 | } 216 | 217 | // process raw signal 218 | detrend(face.raw, face.pulse, currentFps / 2); 219 | normalization(face.pulse, face.pulse); 220 | meanFilter(face.pulse, face.pulse); 221 | 222 | // detects peaks and validates pulse 223 | peaks(face); 224 | } else { 225 | // too noisy to detect pulse 226 | face.existsPulse = false; 227 | 228 | // reset to improve BPM detection speed 229 | if (faceDetection.enabled) face.reset(); 230 | } 231 | 232 | if (face.existsPulse) { 233 | bpm(face); 234 | } 235 | 236 | if (!face.existsPulse){ 237 | PROFILE_SCOPED_DESC("no pulse"); 238 | 239 | if (face.pulse.rows == face.raw.rows) { 240 | face.pulse = 0; 241 | } else { 242 | face.pulse = Mat1d::zeros(face.raw.rows, 1); 243 | } 244 | face.peaks.clear(); 245 | face.bpms.pop_back(face.bpms.rows); 246 | face.bpm = 0; 247 | } 248 | 249 | #ifndef __ANDROID__ 250 | draw(frame, face, box); 251 | #endif 252 | } 253 | 254 | // Algorithm based on: Pulse onset detection 255 | void Pulse::peaks(Face& face) { 256 | PROFILE_SCOPED(); 257 | 258 | // remove old peaks 259 | face.peaks.clear(); 260 | 261 | int lastIndex = 0; 262 | int lastPeakIndex = 0; 263 | int lastPeakTimestamp = face.timestamps(0); 264 | int lastPeakValue = face.pulse(0); 265 | double peakValueThreshold = 0; 266 | 267 | for (int i = 1; i < face.raw.rows; i++) { 268 | 269 | // if more than X milliseconds passed search for peak in this segment 270 | const double diff = (face.timestamps(i) - face.timestamps(lastIndex)) * 1000. / getTickFrequency(); 271 | if (diff >= 200) { 272 | 273 | // find max in this segment 274 | int relativePeakIndex[2]; 275 | double peakValue; 276 | minMaxIdx(face.pulse.rowRange(lastIndex, i+1), 0, &peakValue, 0, &relativePeakIndex[0]); 277 | const int peakIndex = lastIndex + relativePeakIndex[0]; 278 | 279 | // if max is not at the boundaries and is higher than the threshold 280 | if (peakValue > peakValueThreshold && lastIndex < peakIndex && peakIndex < i) { 281 | const double peakTimestamp = face.timestamps(peakIndex); 282 | const double peakDiff = (peakTimestamp - lastPeakTimestamp) * 1000. / getTickFrequency(); 283 | 284 | // if peak is too close to last peak and has an higher value 285 | if (peakDiff <= 200 && peakValue > lastPeakValue) { 286 | // then pop last peak 287 | face.peaks.pop(); 288 | } 289 | 290 | // if peak is far away enough from last peak or has an higher value 291 | if (peakDiff > 200 || peakValue > lastPeakValue) { 292 | // then store current peak 293 | face.peaks.push(peakIndex, peakTimestamp, peakValue); 294 | 295 | lastPeakIndex = peakIndex; 296 | lastPeakTimestamp = peakTimestamp; 297 | lastPeakValue = peakValue; 298 | 299 | peakValueThreshold = 0.6 * mean(face.peaks.values)(0); 300 | } 301 | } 302 | 303 | lastIndex = i; 304 | } 305 | } 306 | 307 | // verify if peaks describe a valid pulse signal 308 | Scalar peakValuesStdDev; 309 | meanStdDev(face.peaks.values, Scalar(), peakValuesStdDev); 310 | const double diff = (face.timestamps(face.raw.rows - 1) - face.timestamps(0)) / getTickFrequency(); 311 | 312 | Scalar peakTimestampsStdDev; 313 | if (face.peaks.indices.rows >= 3) { 314 | meanStdDev((face.peaks.timestamps.rowRange(1, face.peaks.timestamps.rows) - face.peaks.timestamps.rowRange(0, face.peaks.timestamps.rows - 1)) / getTickFrequency(), Scalar(), peakTimestampsStdDev); 315 | } 316 | 317 | // TODO extract constants to class? 318 | bool validPulse = 319 | 2 <= face.peaks.indices.rows && 320 | 40/60 * diff <= face.peaks.indices.rows && 321 | face.peaks.indices.rows <= 240/60 * diff && 322 | peakValuesStdDev(0) <= 0.5 && 323 | peakTimestampsStdDev(0) <= 0.5; 324 | 325 | if (!face.existsPulse && validPulse) { 326 | // pulse become valid 327 | face.noPulseIn = holdPulseFor; 328 | face.existsPulse = true; 329 | } else if (face.existsPulse && !validPulse) { 330 | // pulse become invalid 331 | if (face.noPulseIn > 0) face.noPulseIn--; // keep pulse for a few frames 332 | else face.existsPulse = false; // pulse has been invalid for too long 333 | } 334 | } 335 | 336 | void Pulse::Face::Peaks::push(int index, double timestamp, double value) { 337 | indices.push_back(index); 338 | timestamps.push_back(timestamp); 339 | values.push_back(value); 340 | } 341 | 342 | void Pulse::Face::Peaks::pop() { 343 | indices.pop_back(min(indices.rows, 1)); 344 | timestamps.pop_back(min(timestamps.rows, 1)); 345 | values.pop_back(min(values.rows, 1)); 346 | } 347 | 348 | void Pulse::Face::Peaks::clear() { 349 | indices.pop_back(indices.rows); 350 | timestamps.pop_back(timestamps.rows); 351 | values.pop_back(values.rows); 352 | } 353 | 354 | void Pulse::bpm(Face& face) { 355 | PROFILE_SCOPED(); 356 | 357 | dft(face.pulse, powerSpectrum); 358 | 359 | const int total = face.raw.rows; 360 | 361 | // band limit 362 | const int low = total * 40./60./currentFps + 1; 363 | const int high = total * 240./60./currentFps + 1; 364 | powerSpectrum.rowRange(0, min((size_t)low, (size_t)total)) = ZERO; 365 | powerSpectrum.pop_back(min((size_t)(total - high), (size_t)total)); 366 | 367 | // power spectrum 368 | pow(powerSpectrum, 2, powerSpectrum); 369 | 370 | if (!powerSpectrum.empty()) { 371 | // grab index of max power spectrum 372 | int idx[2]; 373 | minMaxIdx(powerSpectrum, 0, 0, 0, &idx[0]); 374 | 375 | // calculate BPM 376 | face.bpms.push_back(idx[0] * currentFps * 30. / total); // constant 30 = 60 BPM / 2 377 | } 378 | 379 | // update BPM when none available or after one second 380 | if (face.bpm == 0 || (now - lastBpmTimestamp) * 1000. / getTickFrequency() >= 1000) { 381 | lastBpmTimestamp = getTickCount(); 382 | 383 | // average calculated BPMs since last time 384 | face.bpm = mean(face.bpms)(0); 385 | face.bpms.pop_back(face.bpms.rows); 386 | 387 | // mark as no pulse when BPM is too low 388 | if (face.bpm <= 40) { 389 | face.existsPulse = false; 390 | } 391 | } 392 | } 393 | 394 | void Pulse::draw(Mat& frame, const Face& face, const Rect& box) { 395 | PROFILE_SCOPED(); 396 | 397 | rectangle(frame, box, BLUE); 398 | rectangle(frame, face.box, BLUE, 2); 399 | rectangle(frame, face.evm.box, GREEN); 400 | 401 | // bottom left point of face box 402 | Point bl = face.box.tl() + Point(0, face.box.height); 403 | Point g; 404 | for (int i = 0; i < face.raw.rows; i++) { 405 | g = bl + Point(i, -face.raw(i) + 50); 406 | line(frame, g, g, GREEN); 407 | g = bl + Point(i, -face.pulse(i) * 10 - 50); 408 | line(frame, g, g, RED, face.existsPulse ? 2 : 1); 409 | } 410 | 411 | // peaks 412 | for (int i = 0; i < face.peaks.indices.rows; i++) { 413 | const int index = face.peaks.indices(i); 414 | g = bl + Point(index, -face.pulse(index) * 10 - 50); 415 | circle(frame, g, 1, BLUE, 2); 416 | } 417 | 418 | stringstream ss; 419 | 420 | // id 421 | ss << face.id; 422 | putText(frame, ss.str(), face.box.tl(), FONT_HERSHEY_PLAIN, 2, BLUE, 2); 423 | ss.str(""); 424 | 425 | // bpm 426 | ss.precision(3); 427 | ss << face.bpm; 428 | putText(frame, ss.str(), bl, FONT_HERSHEY_PLAIN, 2, RED, 2); 429 | } 430 | 431 | Pulse::Face::Face(int id, const Rect& box, int deleteIn) { 432 | this->id = id; 433 | this->box = box; 434 | this->deleteIn = deleteIn; 435 | this->updateBox(this->box); 436 | this->existsPulse = false; 437 | this->noPulseIn = 0; 438 | } 439 | 440 | int Pulse::Face::nearestBox(const vector& boxes) { 441 | PROFILE_SCOPED(); 442 | 443 | if (boxes.empty()) { 444 | return -1; 445 | } 446 | int index = 0; 447 | Point p = box.tl() - boxes.at(0).tl(); 448 | int min = p.x * p.x + p.y * p.y; 449 | for (size_t i = 1; i < boxes.size(); i++) { 450 | p = box.tl() - boxes.at(i).tl(); 451 | int d = p.x * p.x + p.y * p.y; 452 | if (d < min) { 453 | min = d; 454 | index = i; 455 | } 456 | } 457 | return index; 458 | } 459 | 460 | void Pulse::Face::updateBox(const Rect& a) { 461 | PROFILE_SCOPED(); 462 | 463 | // update box position and size 464 | Point p = box.tl() - a.tl(); 465 | double d = (p.x * p.x + p.y * p.y) / pow(box.width / 3., 2.); 466 | interpolate(box, a, box, min(1., d)); 467 | 468 | // update EVM box 469 | Point c = box.tl() + Point(box.size().width * .5, box.size().height * .5); 470 | Point r(box.width * .275, box.height * .425); 471 | evm.box = Rect(c - r, c + r); 472 | } 473 | 474 | void Pulse::Face::reset() { 475 | PROFILE_SCOPED(); 476 | 477 | // restarts Eulerian video magnification 478 | evm.evm.first = true; 479 | 480 | // clear raw signal 481 | raw.pop_back(raw.rows); 482 | timestamps.pop_back(timestamps.rows); 483 | } 484 | -------------------------------------------------------------------------------- /src/Pulse.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PULSE_HPP 2 | #define PULSE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include "EvmGdownIIR.hpp" 8 | 9 | using std::string; 10 | using std::vector; 11 | using cv::Mat; 12 | using cv::Mat1d; 13 | using cv::Mat1i; 14 | using cv::Rect; 15 | using cv::Size; 16 | using cv::CascadeClassifier; 17 | 18 | class Pulse { 19 | public: 20 | Pulse(); 21 | virtual ~Pulse(); 22 | 23 | void load(const string& filename); 24 | void start(int width, int height); 25 | void onFrame(Mat& frame); 26 | 27 | int maxSignalSize; 28 | double relativeMinFaceSize; 29 | struct { 30 | bool enabled; 31 | int disabledFaceId; 32 | } faceDetection; 33 | double fps; 34 | struct { 35 | bool magnify; 36 | double alpha; 37 | } evm; 38 | 39 | struct Face { 40 | int id; 41 | int deleteIn; 42 | bool selected; 43 | 44 | Rect box; 45 | Mat1d timestamps; 46 | Mat1d raw; 47 | Mat1d pulse; 48 | int noPulseIn; 49 | bool existsPulse; 50 | 51 | Mat1d bpms; 52 | double bpm; 53 | 54 | struct { 55 | EvmGdownIIR evm; 56 | Mat out; 57 | Rect box; 58 | } evm; 59 | 60 | struct Peaks { 61 | Mat1i indices; 62 | Mat1d timestamps; 63 | Mat1d values; 64 | 65 | void push(int index, double timestamp, double value); 66 | void pop(); 67 | void clear(); 68 | } peaks; 69 | 70 | Face(int id, const Rect& box, int deleteIn); 71 | int nearestBox(const vector& boxes); 72 | void updateBox(const Rect& box); 73 | void reset(); 74 | }; 75 | 76 | vector faces; 77 | 78 | private: 79 | int nearestFace(const Rect& box); 80 | void onFace(Mat& frame, Face& face, const Rect& box); 81 | void peaks(Face& face); 82 | void bpm(Face& face); 83 | void draw(Mat& frame, const Face& face, const Rect& box); 84 | 85 | double now; 86 | double lastFaceDetectionTimestamp; 87 | double lastBpmTimestamp; 88 | Size minFaceSize; 89 | CascadeClassifier classifier; 90 | Mat gray; 91 | vector boxes; 92 | Mat1d powerSpectrum; 93 | int nextFaceId; 94 | int deleteFaceIn; 95 | int holdPulseFor; 96 | double currentFps; 97 | 98 | }; 99 | 100 | #endif /* PULSE_HPP */ 101 | 102 | -------------------------------------------------------------------------------- /src/Window.cpp: -------------------------------------------------------------------------------- 1 | #include "Window.hpp" 2 | #include 3 | #include 4 | #include 5 | #include "ext/opencv.hpp" 6 | #include "Pulse.hpp" 7 | #include "profiler/Profiler.h" 8 | 9 | using std::stringstream; 10 | using namespace cv; 11 | 12 | Window::Window(Pulse& pulse) : 13 | pulse(pulse), 14 | WINDOW_NAME("EVM"), 15 | TRACKBAR_FACE_DETECTION_NAME("Face Detection"), 16 | TRACKBAR_MAGNIFY_NAME ("Magnify "), 17 | TRACKBAR_ALPHA_NAME ("Amplification ") 18 | { 19 | trackbarFaceDetection = pulse.faceDetection.enabled; 20 | trackbarMagnify = pulse.evm.magnify; 21 | trackbarAlpha = pulse.evm.alpha; 22 | 23 | namedWindow(WINDOW_NAME); 24 | createTrackbar(TRACKBAR_FACE_DETECTION_NAME, WINDOW_NAME, &trackbarFaceDetection, 1); 25 | createTrackbar(TRACKBAR_MAGNIFY_NAME, WINDOW_NAME, &trackbarMagnify, 1); 26 | createTrackbar(TRACKBAR_ALPHA_NAME, WINDOW_NAME, &trackbarAlpha, 500); 27 | 28 | fpsPoint = Point(10, 15); 29 | } 30 | 31 | Window::~Window() { 32 | } 33 | 34 | void Window::update(Mat& frame) { 35 | PROFILE_SCOPED(); 36 | 37 | // update pulse values for Eulerian video magnification 38 | pulse.faceDetection.enabled = trackbarFaceDetection == 1; 39 | pulse.evm.magnify = trackbarMagnify == 1; 40 | pulse.evm.alpha = trackbarAlpha; 41 | 42 | PROFILE_START_DESC("bgr2rgb"); 43 | cvtColor(frame, frame, CV_BGR2RGB); 44 | PROFILE_STOP(); 45 | 46 | // process frame 47 | pulse.onFrame(frame); 48 | 49 | drawTrackbarValues(frame); 50 | drawFps(frame); 51 | 52 | PROFILE_START_DESC("rgb2bgr"); 53 | cvtColor(frame, frame, CV_RGB2BGR); 54 | PROFILE_STOP(); 55 | 56 | PROFILE_START_DESC("imshow"); 57 | imshow(WINDOW_NAME, frame); 58 | PROFILE_STOP(); 59 | } 60 | 61 | void Window::drawTrackbarValues(Mat& frame) { 62 | PROFILE_SCOPED(); 63 | 64 | const int namesX = 10; 65 | const int valuesX = 150; 66 | const int spaceY = 15; 67 | 68 | stringstream ss; 69 | 70 | putText(frame, TRACKBAR_FACE_DETECTION_NAME, Point( namesX, spaceY * 2), FONT_HERSHEY_PLAIN, 1, BLUE); 71 | putText(frame, (trackbarFaceDetection == 1 ? "ON" : "OFF"), Point(valuesX, spaceY * 2), FONT_HERSHEY_PLAIN, 1, BLUE); 72 | 73 | putText(frame, TRACKBAR_MAGNIFY_NAME, Point( namesX, spaceY * 3), FONT_HERSHEY_PLAIN, 1, BLUE); 74 | putText(frame, (trackbarMagnify == 1 ? "ON" : "OFF"), Point(valuesX, spaceY * 3), FONT_HERSHEY_PLAIN, 1, BLUE); 75 | 76 | ss.str(""); 77 | ss << trackbarAlpha; 78 | putText(frame, TRACKBAR_ALPHA_NAME, Point( namesX, spaceY * 4), FONT_HERSHEY_PLAIN, 1, BLUE); 79 | putText(frame, ss.str(), Point(valuesX, spaceY * 4), FONT_HERSHEY_PLAIN, 1, BLUE); 80 | } 81 | 82 | void Window::drawFps(Mat& frame) { 83 | PROFILE_SCOPED(); 84 | 85 | if (fps.update()) { 86 | PROFILE_SCOPED_DESC("fps string"); 87 | stringstream ss; 88 | ss.precision(3); 89 | ss << "FPS: " << fps.fps; 90 | fpsString = ss.str(); 91 | } 92 | 93 | PROFILE_START_DESC("fps drawing"); 94 | putText(frame, fpsString, fpsPoint, FONT_HERSHEY_PLAIN, 1, BLUE); 95 | PROFILE_STOP(); 96 | } 97 | 98 | Window::Fps::Fps() { 99 | deltaFrames = 30; 100 | currentFrame = 0; 101 | lastFrame = 0; 102 | lastFpsTime = 0; 103 | fps = 0; 104 | } 105 | 106 | bool Window::Fps::update() { 107 | PROFILE_SCOPED(); 108 | 109 | if (currentFrame == 0) { 110 | lastFpsTime = (double)getTickCount(); 111 | } 112 | 113 | currentFrame++; 114 | 115 | if (currentFrame % 30 == 0) { 116 | PROFILE_SCOPED_DESC("fps tick"); 117 | double now = (double)getTickCount(); 118 | double diff = (now - lastFpsTime) * 1000. / getTickFrequency(); 119 | 120 | if (diff > 0) { 121 | fps = (currentFrame - lastFrame) * 1000 / diff; 122 | lastFrame = currentFrame; 123 | lastFpsTime = now; 124 | return true; 125 | } 126 | } 127 | 128 | return false; 129 | } 130 | -------------------------------------------------------------------------------- /src/Window.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WINDOW_HPP 2 | #define WINDOW_HPP 3 | 4 | #include 5 | #include 6 | #include "Pulse.hpp" 7 | 8 | using std::string; 9 | using cv::Mat; 10 | using cv::Point; 11 | 12 | class Window { 13 | public: 14 | Window(Pulse& pulse); 15 | virtual ~Window(); 16 | 17 | void update(Mat& frame); 18 | 19 | private: 20 | void drawTrackbarValues(Mat& frame); 21 | void drawFps(Mat& frame); 22 | 23 | Pulse& pulse; 24 | 25 | const string WINDOW_NAME; 26 | const string TRACKBAR_FACE_DETECTION_NAME; 27 | const string TRACKBAR_MAGNIFY_NAME; 28 | const string TRACKBAR_ALPHA_NAME; 29 | 30 | int trackbarFaceDetection; 31 | int trackbarMagnify; 32 | int trackbarAlpha; 33 | 34 | class Fps { 35 | public: 36 | int deltaFrames; 37 | uint64 currentFrame; 38 | uint64 lastFrame; 39 | double lastFpsTime; 40 | double fps; 41 | 42 | Fps(); 43 | bool update(); 44 | }; 45 | 46 | Fps fps; 47 | string fpsString; 48 | Point fpsPoint; 49 | 50 | }; 51 | 52 | #endif /* WINDOW_HPP */ 53 | 54 | -------------------------------------------------------------------------------- /src/ext/opencv.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv.hpp" 2 | #include 3 | #include "../profiler/Profiler.h" 4 | 5 | namespace cv { 6 | 7 | void normalization(InputArray _a, OutputArray _b) { 8 | PROFILE_SCOPED(); 9 | 10 | _a.getMat().copyTo(_b); 11 | Mat b = _b.getMat(); 12 | Scalar mean, stdDev; 13 | meanStdDev(b, mean, stdDev); 14 | b = (b - mean[0]) / stdDev[0]; 15 | } 16 | 17 | void meanFilter(InputArray _a, OutputArray _b, size_t n, Size s) { 18 | PROFILE_SCOPED(); 19 | 20 | _a.getMat().copyTo(_b); 21 | Mat b = _b.getMat(); 22 | for (size_t i = 0 ; i < n; i++) { 23 | blur(b, b, s); 24 | } 25 | } 26 | 27 | void interpolate(const Rect& a, const Rect& b, Rect& c, double p) { 28 | PROFILE_SCOPED(); 29 | 30 | double np = 1 - p; 31 | c.x = a.x * np + b.x * p + 0.5; 32 | c.y = a.y * np + b.y * p + 0.5; 33 | c.width = a.width * np + b.width * p + 0.5; 34 | c.height = a.height * np + b.height * p + 0.5; 35 | } 36 | 37 | void printMatInfo(const string& name, InputArray _a) { 38 | Mat a = _a.getMat(); 39 | cout << name << ": " << a.rows << "x" << a.cols 40 | << " channels=" << a.channels() 41 | << " depth=" << a.depth() 42 | << " isContinuous=" << (a.isContinuous() ? "true" : "false") 43 | << " isSubmatrix=" << (a.isSubmatrix() ? "true" : "false") << endl; 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/ext/opencv.hpp: -------------------------------------------------------------------------------- 1 | #ifndef EXT_OPENCV_HPP 2 | #define EXT_OPENCV_HPP 3 | 4 | #include 5 | #include 6 | #include "../profiler/Profiler.h" 7 | 8 | using std::cout; 9 | using std::endl; 10 | 11 | namespace cv { 12 | 13 | const Scalar BLACK ( 0, 0, 0); 14 | const Scalar RED (255, 0, 0); 15 | const Scalar GREEN ( 0, 255, 0); 16 | const Scalar BLUE ( 0, 0, 255); 17 | const Scalar WHITE (255, 255, 255); 18 | 19 | const Scalar ZERO (0); 20 | 21 | void normalization(InputArray _a, OutputArray _b); 22 | 23 | void meanFilter(InputArray _a, OutputArray _b, size_t n = 3, Size s = Size(5, 5)); 24 | 25 | void interpolate(const Rect& a, const Rect& b, Rect& c, double p); 26 | 27 | template 28 | void detrend(InputArray _z, OutputArray _r, int lambda = 10) { 29 | PROFILE_SCOPED(); 30 | 31 | CV_DbgAssert((_z.type() == CV_32F || _z.type() == CV_64F) 32 | && _z.total() == max(_z.size().width, _z.size().height)); 33 | 34 | Mat z = _z.total() == (size_t)_z.size().height ? _z.getMat() : _z.getMat().t(); 35 | if (z.total() < 3) { 36 | z.copyTo(_r); 37 | } else { 38 | int t = z.total(); 39 | Mat i = Mat::eye(t, t, z.type()); 40 | Mat d = Mat(Matx(1, -2, 1)); 41 | Mat d2Aux = Mat::ones(t-2, 1, z.type()) * d; 42 | Mat d2 = Mat::zeros(t-2, t, z.type()); 43 | for (int k = 0; k < 3; k++) { 44 | d2Aux.col(k).copyTo(d2.diag(k)); 45 | } 46 | Mat r = (i - (i + lambda * lambda * d2.t() * d2).inv()) * z; 47 | r.copyTo(_r); 48 | } 49 | } 50 | 51 | template 52 | int countZeros(InputArray _a) { 53 | CV_DbgAssert(_a.channels() == 1 54 | && _a.total() == max(_a.size().width, _a.size().height)); 55 | 56 | int count = 0; 57 | if (_a.total() > 0) { 58 | Mat a = _a.getMat(); 59 | T last = a.at(0); 60 | for (int i = 1; i < a.total(); i++) { 61 | T current = a.at(i); 62 | if ((last < 0 && current >= 0) || (last > 0 && current <= 0)) { 63 | count++; 64 | } 65 | last = current; 66 | } 67 | } 68 | return count; 69 | } 70 | 71 | /** 72 | * Print Mat info such as rows, cols, channels, depth, isContinuous, 73 | * and isSubmatrix. 74 | */ 75 | void printMatInfo(const string& name, InputArray _a); 76 | 77 | /** 78 | * Same as printMatInfo plus the actual values of the Mat. 79 | * @see printMatInfo 80 | */ 81 | template 82 | void printMat(const string& name, InputArray _a, 83 | int rows = -1, 84 | int cols = -1, 85 | int channels = -1) 86 | { 87 | printMatInfo(name, _a); 88 | 89 | Mat a = _a.getMat(); 90 | if (-1 == rows) rows = a.rows; 91 | if (-1 == cols) cols = a.cols; 92 | if (-1 == channels) channels = a.channels(); 93 | 94 | for (int y = 0; y < rows; y++) { 95 | cout << "["; 96 | for (int x = 0; x < cols; x++) { 97 | T* e = &a.at(y, x); 98 | cout << "(" << e[0]; 99 | for (int c = 1; c < channels; c++) { 100 | cout << ", " << e[c]; 101 | } 102 | cout << ")"; 103 | } 104 | cout << "]" << endl; 105 | } 106 | cout << endl; 107 | } 108 | 109 | } 110 | 111 | #endif /* EXT_OPENCV_HPP */ 112 | 113 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "EvmGdownIIR.hpp" 5 | #include "Pulse.hpp" 6 | #include "Window.hpp" 7 | #include "profiler/Profiler.h" 8 | 9 | using std::cout; 10 | using std::endl; 11 | using cv::VideoCapture; 12 | using cv::VideoWriter; 13 | using cv::waitKey; 14 | using cv::flip; 15 | 16 | static void writeVideo(VideoCapture& capture, const Mat& frame); 17 | 18 | int main(int argc, const char** argv) { 19 | const bool shouldWrite = false; 20 | const bool shouldFlip = true; 21 | 22 | VideoCapture capture(0); 23 | 24 | const int WIDTH = capture.get(CV_CAP_PROP_FRAME_WIDTH); 25 | const int HEIGHT = capture.get(CV_CAP_PROP_FRAME_HEIGHT); 26 | const double FPS = capture.get(CV_CAP_PROP_FPS); 27 | cout << "SIZE: " << WIDTH << "x" << HEIGHT << endl; 28 | 29 | Pulse pulse; 30 | if (FPS != 0) { 31 | cout << "FPS: " << FPS << endl; 32 | pulse.fps = FPS; 33 | } 34 | pulse.load("res/lbpcascade_frontalface.xml"); 35 | pulse.start(WIDTH, HEIGHT); 36 | 37 | Window window(pulse); 38 | 39 | Mat frame; 40 | while (true) { 41 | PROFILE_SCOPED_DESC("loop"); 42 | 43 | PROFILE_START_DESC("capture"); 44 | capture >> frame; 45 | PROFILE_STOP(); 46 | 47 | if (frame.empty()) { 48 | PROFILE_PAUSE_SCOPED(); // loop 49 | while (waitKey() != 27) {} 50 | break; 51 | } 52 | 53 | PROFILE_START_DESC("flip"); 54 | if (shouldFlip) 55 | flip(frame, frame, 1); 56 | PROFILE_STOP(); 57 | 58 | window.update(frame); 59 | 60 | if (shouldWrite) { 61 | writeVideo(capture, frame); 62 | } 63 | 64 | PROFILE_START_DESC("wait key"); 65 | if (waitKey(1) == 27) { 66 | PROFILE_STOP(); // wait key 67 | break; 68 | } 69 | PROFILE_STOP(); 70 | } 71 | 72 | Profiler::detect(argc, argv); 73 | Profiler::dumphtml(); 74 | return 0; 75 | } 76 | 77 | static void writeVideo(VideoCapture& capture, const Mat& frame) { 78 | static VideoWriter writer("out.avi", 79 | CV_FOURCC('X', 'V', 'I', 'D'), 80 | capture.get(CV_CAP_PROP_FPS), 81 | Size(capture.get(CV_CAP_PROP_FRAME_WIDTH), 82 | capture.get(CV_CAP_PROP_FRAME_HEIGHT))); 83 | 84 | writer << frame; 85 | } 86 | -------------------------------------------------------------------------------- /src/profiler/Profiler.cpp: -------------------------------------------------------------------------------- 1 | #define __PROFILER_SMP__ 2 | #define __PROFILER_CONSOLIDATE_THREADS__ 3 | 4 | #define css_outline_color "#848484" 5 | #define css_thread_style "background-color:#EEEEEE;margin-top:8px;" 6 | #define css_title_row "FunctionCallsMCyclesAvgSelf MCyclesSelf Avg\n" 7 | #define css_totals_row "FunctionCallsSelf MCyclesSelf Avg\n" 8 | 9 | #if defined(_WIN32) 10 | #define _CRT_SECURE_NO_WARNINGS 11 | #define copystring _strdup 12 | #include 13 | #else 14 | #define copystring strdup 15 | #include 16 | #endif 17 | 18 | #if defined(__PROFILER_ENABLED__) 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #endif 26 | 27 | #include "Profiler.h" 28 | 29 | #if defined(__ICC) || defined(__ICL) 30 | #pragma warning( disable: 1684 ) // (size_t )name >> 5 31 | #pragma warning( disable: 1011 ) // missing return statement at end of non-void function 32 | #endif 33 | 34 | #undef threadlocal 35 | 36 | #if defined(_MSC_VER) 37 | #define YIELD() Sleep(0); 38 | #define PRINTFU64() "%I64u" 39 | #define PATHSLASH() '\\' 40 | #define threadlocal __declspec(thread) 41 | #define snprintf _snprintf 42 | 43 | #undef inline 44 | #define inline __forceinline 45 | #else 46 | #include 47 | #define YIELD() sched_yield(); 48 | #define PRINTFU64() "%llu" 49 | #define PATHSLASH() '/' 50 | #define threadlocal __thread 51 | #endif 52 | 53 | #undef threadlocal 54 | #define threadlocal 55 | 56 | namespace Profiler { 57 | #if defined(__PROFILER_ENABLED__) 58 | 59 | #if defined(__PROFILER_SMP__) 60 | #if defined(_MSC_VER) 61 | 62 | template< class type > 63 | inline bool CAS( volatile type &ptr_, const type old_, const type new_ ) { 64 | __asm { 65 | mov eax, [old_] 66 | mov edx, [new_] 67 | mov ecx, [ptr_] 68 | lock cmpxchg dword ptr [ecx], edx 69 | sete al 70 | } 71 | } 72 | 73 | #elif defined(__GNUC__) || defined(__ICC) 74 | 75 | template< class type > 76 | inline bool CAS( volatile type &ptr_, const type old_, const type new_ ) { 77 | u8 ret; 78 | __asm__ __volatile__ ( 79 | " lock\n" 80 | " cmpxchgl %2,%1\n" 81 | " sete %0\n" 82 | : "=q" (ret), "=m" (ptr_) 83 | : "r" (new_), "m" (ptr_), "a" (old_) 84 | : "memory" 85 | ); 86 | return ret; 87 | } 88 | 89 | #else 90 | #error Define a compare-and-swap / full memory barrier implementation! 91 | #endif 92 | 93 | struct CASLock { 94 | void Acquire() { while ( !CAS( mLock, u32(0), u32(1) ) ) YIELD() } 95 | void Release() { while ( !CAS( mLock, u32(1), u32(0) ) ) YIELD() } 96 | bool TryAcquire() { return CAS( mLock, u32(0), u32(1) ); } 97 | bool TryRelease() { return CAS( mLock, u32(1), u32(0) ); } 98 | u32 Value() const { return mLock; } 99 | //protected: 100 | volatile u32 mLock; 101 | }; 102 | #else 103 | struct CASLock { 104 | void Acquire() {} 105 | void Release() {} 106 | bool TryAcquire() { return false; } 107 | bool TryRelease() { return false; } 108 | u32 Value() const { return 0; } 109 | u32 dummy; 110 | }; 111 | #endif 112 | 113 | u32 nextpow2( u32 x ) { 114 | x |= ( x >> 1 ); 115 | x |= ( x >> 2 ); 116 | x |= ( x >> 4 ); 117 | x |= ( x >> 8 ); 118 | x |= ( x >> 16 ); 119 | return ( x + 1 ); 120 | } 121 | 122 | template< class type > 123 | inline void zeroarray( type *array, size_t count ) { 124 | memset( array, 0, count * sizeof( type ) ); 125 | } 126 | 127 | template< class type > 128 | inline type *makepointer( type *base, size_t byteoffset ) { 129 | return (type *)((const char *)base + byteoffset); 130 | } 131 | 132 | template< class type > 133 | inline void swapitems( type &a, type &b ) { 134 | type tmp = a; 135 | a = b; 136 | b = tmp; 137 | } 138 | 139 | #undef min 140 | #undef max 141 | 142 | template< class type > 143 | inline const type &min( const type &a, const type &b ) { 144 | return ( a < b ) ? a : b; 145 | } 146 | 147 | template< class type > 148 | inline const type &max( const type &a, const type &b ) { 149 | return ( a < b ) ? b : a; 150 | } 151 | 152 | /* 153 | ============= 154 | Buffer - Don't use for anything with a constructor/destructor. Doesn't shrink on popping 155 | ============= 156 | */ 157 | 158 | template< class type > 159 | struct Buffer { 160 | Buffer() : mBuffer(NULL), mAlloc(0), mItems(0) { Resize( 4 ); } 161 | Buffer( u32 size ) : mBuffer(NULL), mAlloc(0), mItems(0) { Resize( size ); } 162 | ~Buffer() { free( mBuffer ); } 163 | 164 | void Clear() { mItems = ( 0 ); } 165 | type *Data() { return ( mBuffer ); } 166 | void EnsureCapacity( u32 capacity ) { if ( capacity >= mAlloc ) Resize( capacity * 2 ); } 167 | type *Last() { return ( &mBuffer[ mItems - 1 ] ); } 168 | void Push( const type &item ) { EnsureCapacity( mItems + 1 ); mBuffer[ mItems++ ] = ( item ); } 169 | type &Pop() { return ( mBuffer[ --mItems ] ); } 170 | 171 | void Resize( u32 newsize ) { 172 | mAlloc = nextpow2( newsize ); 173 | mBuffer = (type *)realloc( mBuffer, mAlloc * sizeof( type ) ); 174 | } 175 | 176 | u32 Size() const { return mItems; } 177 | 178 | template< class Compare > 179 | void Sort( Compare comp ) { 180 | if ( mItems <= 1 ) 181 | return; 182 | 183 | Buffer scratch( mItems ); 184 | 185 | // merge sort with scratch buffer 186 | type *src = Data(), *dst = scratch.Data(); 187 | for( u32 log = 2; log < mItems * 2; log *= 2 ) { 188 | type *out = dst; 189 | for( u32 i = 0; i < mItems; i += log ) { 190 | u32 lo = i, lo2 = min( i + log / 2, mItems ); 191 | u32 hi = lo2, hi2 = min( lo + log, mItems ); 192 | while ( ( lo < lo2 ) && ( hi < hi2 ) ) 193 | *out++ = ( comp( src[lo], src[hi] ) ) ? src[lo++] : src[hi++]; 194 | while ( lo < lo2 ) *out++ = src[lo++]; 195 | while ( hi < hi2 ) *out++ = src[hi++]; 196 | } 197 | 198 | swapitems( src, dst ); 199 | } 200 | 201 | if ( src != mBuffer ) 202 | swapitems( mBuffer, scratch.mBuffer ); 203 | } 204 | 205 | template< class Mapto > 206 | void ForEachByRef( Mapto &mapto, u32 limit ) { 207 | limit = ( limit < mItems ) ? limit : mItems; 208 | u32 last = limit - 1; 209 | for ( u32 i = 0; i < limit; ++i ) 210 | mapto( mBuffer[ i ], i == last ); 211 | } 212 | 213 | template< class Mapto > void ForEach( Mapto mapto, u32 limit ) { ForEachByRef( mapto, limit ); } 214 | template< class Mapto > void ForEach( Mapto mapto ) { ForEachByRef( mapto, mItems ); } 215 | 216 | type &operator[] ( u32 index ) { return ( mBuffer[ index ] ); } 217 | const type &operator[] ( u32 index ) const { return ( mBuffer[ index ] ); } 218 | 219 | protected: 220 | type *mBuffer; 221 | u32 mAlloc, mItems; 222 | }; 223 | 224 | 225 | /* 226 | =================== 227 | ColorRamp for HTML 228 | =================== 229 | */ 230 | 231 | struct ColorF { 232 | ColorF() {} 233 | ColorF( f32 r_, f32 g_, f32 b_ ) : r(r_), g(g_), b(b_) {} 234 | f32 r, g, b; 235 | }; 236 | 237 | struct ColorRamp { 238 | struct Marker { 239 | Marker() {} 240 | Marker( const ColorF &color_, f32 value_ ) : color(color_), value(value_) {} 241 | ColorF color; 242 | f32 value; 243 | }; 244 | 245 | ColorRamp() {} 246 | 247 | void clear() { 248 | mColors.Clear(); 249 | } 250 | 251 | const char *value( f32 pos ) const { 252 | ColorF base(0, 0, 0); 253 | u32 pre = 0, post = 0; 254 | for ( pre = 0; pre < mColors.Size() - 1; pre++ ) 255 | if ( mColors[pre+1].value >= pos ) 256 | break; 257 | post = pre + 1; 258 | if ( ( pre < mColors.Size() ) && ( post < mColors.Size() ) ) { 259 | const Marker &a = mColors[pre], &b = mColors[post]; 260 | f32 dist = ( b.value - a.value ), posw = ( pos - a.value ), bw = ( posw / dist ), aw = 1 - bw; 261 | base = ColorF( a.color.r * aw + b.color.r * bw, a.color.g * aw + b.color.g * bw, a.color.b * aw + b.color.b * bw ); 262 | } 263 | u8 r = u8(base.r * 255.0f), g = u8(base.g * 255.0f), b = u8(base.b * 255.0f); 264 | static threadlocal char buffer[8][32], bufferon = 0; 265 | sprintf( buffer[bufferon&7], "#%02x%02x%02x", r, g, b ); 266 | return buffer[bufferon++&7]; 267 | } 268 | 269 | ColorRamp &push( const ColorF &color, f32 value ) { mColors.Push( Marker( color, value ) ); return *this; } 270 | 271 | Buffer mColors; 272 | }; 273 | 274 | /* 275 | ============= 276 | Caller 277 | ============= 278 | */ 279 | 280 | #pragma pack(push,1) 281 | struct Caller { 282 | struct foreach { 283 | // Adds each Caller to the specified buckets 284 | struct AddToNewBuckets { 285 | AddToNewBuckets( Caller **buckets, u32 bucket_count ) : mBuckets(buckets), mBucketCount(bucket_count) {} 286 | void operator()( Caller *item ) { 287 | FindEmptyChildSlot( mBuckets, mBucketCount, item->mName ) = item; 288 | } 289 | Caller **mBuckets; 290 | u32 mBucketCount; 291 | }; 292 | 293 | 294 | // Destructs a Caller 295 | struct Deleter { 296 | void operator()( Caller *item ) { 297 | delete item; 298 | } 299 | }; 300 | 301 | // Merges a Caller with the root 302 | struct Merger { 303 | Merger( Caller *root ) : mRoot(root) {} 304 | void addFrom( Caller *item ) { (*this)( item ); } 305 | void operator()( Caller *item ) { 306 | Caller *child = mRoot->FindOrCreate( item->GetName() ); 307 | child->GetTimer() += item->GetTimer(); 308 | child->SetParent( item->GetParent() ); 309 | item->ForEachNonEmpty( Merger( child ) ); 310 | } 311 | Caller *mRoot; 312 | }; 313 | 314 | // Prints a Caller 315 | struct Printer { 316 | Printer( u32 indent ) : mIndent(indent) { } 317 | void operator()( Caller *item, bool islast ) const { 318 | item->Print( mIndent, islast ); 319 | } 320 | u32 mIndent; 321 | }; 322 | 323 | struct PrinterHtml { 324 | PrinterHtml( FILE *f, u32 indent ) : mFile(f), mIndent(indent) { } 325 | void operator()( Caller *item, bool islast ) const { 326 | item->PrintHtml( mFile, mIndent, islast ); 327 | } 328 | FILE *mFile; 329 | u32 mIndent; 330 | }; 331 | 332 | struct SoftReset { 333 | void operator()( Caller *item ) { 334 | item->GetTimer().SoftReset(); 335 | item->ForEach( SoftReset() ); 336 | } 337 | }; 338 | 339 | // Sums Caller's ticks 340 | struct SumTicks { 341 | SumTicks() : sum(0) {} 342 | void operator()( Caller *item ) { 343 | sum += ( item->mTimer.ticks ); 344 | } 345 | u64 sum; 346 | }; 347 | 348 | struct UpdateTopMaxStats { 349 | UpdateTopMaxStats() { maxStats.reset(); } 350 | void operator()( Caller *item, bool islast ) { 351 | if ( !item->GetParent() ) 352 | return; 353 | maxStats.check( Max::Calls, item->mTimer.calls ); 354 | maxStats.check( Max::Ms, Timer::ms( item->mTimer.ticks ) ); 355 | maxStats.check( Max::Avg, item->mTimer.avgms() ); 356 | } 357 | }; 358 | }; // foreach 359 | 360 | 361 | struct compare { 362 | struct Ticks { 363 | bool operator()( const Caller *a, const Caller *b ) const { 364 | return ( a->mTimer.ticks > b->mTimer.ticks ); 365 | } 366 | }; 367 | 368 | struct SelfTicks { 369 | bool operator()( const Caller *a, const Caller *b ) const { 370 | return ( ( a->mTimer.ticks - a->mChildTicks ) > ( b->mTimer.ticks - b->mChildTicks ) ); 371 | } 372 | }; 373 | 374 | struct Calls { 375 | bool operator()( const Caller *a, const Caller *b ) const { 376 | return ( a->mTimer.calls > b->mTimer.calls ); 377 | } 378 | }; 379 | }; // sort 380 | 381 | 382 | /* 383 | Since Caller.mTimer.ticks is inclusive of all children, summing the first level 384 | children of a Caller to Caller.mChildTicks is an accurate total of the complete 385 | child tree. 386 | 387 | mTotals is used to keep track of total ticks by Caller excluding children 388 | */ 389 | struct ComputeChildTicks { 390 | ComputeChildTicks( Caller &totals ) : mTotals(totals) { maxStats.reset(); } 391 | void operator()( Caller *item ) { 392 | foreach::SumTicks sumchildren; 393 | item->ForEachByRefNonEmpty( sumchildren ); 394 | item->mChildTicks = ( sumchildren.sum ); 395 | 396 | u64 selfticks = ( item->mTimer.ticks >= item->mChildTicks ) ? ( item->mTimer.ticks - item->mChildTicks ) : 0; 397 | Caller &totalitem = ( *mTotals.FindOrCreate( item->mName ) ); 398 | totalitem.mTimer.ticks += selfticks; 399 | totalitem.mTimer.calls += item->mTimer.calls; 400 | totalitem.SetParent( item->GetParent() ); 401 | 402 | // don't include the root node in the max stats 403 | if ( item->GetParent() ) { 404 | maxStats.check( Max::SelfMs, Timer::ms( selfticks ) ); 405 | maxStats.check( Max::Calls, item->mTimer.calls ); 406 | maxStats.check( Max::Ms, Timer::ms( item->mTimer.ticks ) ); 407 | maxStats.check( Max::Avg, item->mTimer.avgms() ); 408 | maxStats.check( Max::SelfAvg, average( Timer::ms( selfticks ), item->mTimer.calls ) ); 409 | } 410 | 411 | // compute child ticks for all children of children of this caller 412 | item->ForEachByRefNonEmpty( *this ); 413 | } 414 | Caller &mTotals; 415 | }; 416 | 417 | /* 418 | Format a Caller's information. ComputeChildTicks will need to be used on the Root 419 | to generate mChildTicks for all Callers 420 | */ 421 | struct Format { 422 | Format( const char *prefix ) : mPrefix(prefix) {} 423 | void operator()( Caller *item, bool islast ) const { 424 | u64 ticks = item->mTimer.ticks; 425 | f64 ms = Timer::ms( ticks ); 426 | printf( "%s %.2f mcycles, %d calls, %.0f cycles avg, %.2f%%: %s\n", 427 | mPrefix, ms, item->mTimer.calls, item->mTimer.avg(), average( ticks * 100, mGlobalDuration ), item->mName ); 428 | } 429 | const char *mPrefix; 430 | }; 431 | 432 | struct FormatHtml { 433 | FormatHtml( FILE *f, Buffer &prefix ) : mFile(f), mPrefix(prefix) {} 434 | void operator()( Caller *item ) const { 435 | fprintf( mFile, "\t", !item->GetParent() ? "style=\"" css_thread_style "\"": "class=\"h\"" ); 436 | for ( u32 i = 0; i < mPrefix.Size(); i++ ) 437 | fprintf( mFile, "", mPrefix[i] ); 438 | u64 ticks = item->mTimer.ticks; 439 | f64 ms = Timer::ms( ticks ), globalpct = average( ticks * 100, mGlobalDuration ); 440 | f64 childms = Timer::ms( item->mChildTicks ), selfms = ( ms - childms ), avg = item->mTimer.avgms(), selfavg = average( selfms, item->mTimer.calls ); 441 | if ( !item->GetParent() ) 442 | fprintf( mFile, "
%s
%u%0.4f (%3.0f%%)%0.4f%0.4f%0.4f\n", 443 | item->mName, 444 | item->mTimer.calls, 445 | ms, 446 | globalpct, 447 | avg, 448 | selfms, 449 | selfavg 450 | ); 451 | else 452 | fprintf( mFile, "%s%u%0.4f (%3.0f%%)%0.4f%0.4f%0.4f\n", 453 | item->mName, 454 | maxStats.color( Max::Calls, item->mTimer.calls ), item->mTimer.calls, 455 | maxStats.color( Max::Ms, ms ), ms, 456 | globalpct, 457 | maxStats.color( Max::Avg, avg ), avg, 458 | maxStats.color( Max::SelfMs, selfms ), selfms, 459 | maxStats.color( Max::SelfAvg, selfavg ), selfavg 460 | ); 461 | } 462 | FILE *mFile; 463 | Buffer &mPrefix; 464 | }; 465 | 466 | struct FormatHtmlTop { 467 | FormatHtmlTop( FILE *f ) : mFile(f) {} 468 | void operator()( Caller *item, bool islast ) const { 469 | fprintf( mFile, "\t", !item->GetParent() ? "style=\"" css_thread_style "\"": "class=\"h\"" ); 470 | fprintf( mFile, "", islast ? "img/last-empty.gif" : "img/empty.gif" ); 471 | u64 ticks = item->mTimer.ticks; 472 | f64 ms = Timer::ms( ticks ), globalpct = average( ticks * 100, mGlobalDuration ), avg = item->mTimer.avgms(); 473 | if ( !item->GetParent() ) { 474 | fprintf( mFile, "
%s
%u%0.4f (%.0f%%)%0.4f\n", 475 | item->mName, 476 | item->mTimer.calls, 477 | ms, 478 | globalpct, 479 | avg 480 | ); 481 | } else { 482 | fprintf( mFile, "%s%u%0.4f (%.0f%%)%0.4f\n", 483 | item->mName, 484 | maxStats.color( Max::Calls, item->mTimer.calls ), item->mTimer.calls, 485 | maxStats.color( Max::Ms, ms ), ms, 486 | globalpct, 487 | maxStats.color( Max::Avg, avg ), avg 488 | ); 489 | } 490 | } 491 | FILE *mFile; 492 | }; 493 | 494 | /* 495 | Methods 496 | */ 497 | 498 | // we're guaranteed to be null because of calloc. ONLY create Callers with "new"! 499 | Caller( const char *name, Caller *parent = NULL ) { 500 | mName = name; 501 | mParent = parent; 502 | Resize( 2 ); // mBuckets must always exist and mBucketCount >= 2! 503 | } 504 | 505 | ~Caller() { 506 | ForEach( foreach::Deleter() ); 507 | free( mBuckets ); 508 | } 509 | 510 | void CopyToListNonEmpty( Buffer &list ) { 511 | list.Clear(); 512 | 513 | for ( u32 i = 0; i < mBucketCount; ++i ) 514 | if ( mBuckets[ i ] && !mBuckets[ i ]->GetTimer().IsEmpty() ) 515 | list.Push( mBuckets[ i ] ); 516 | } 517 | 518 | inline Caller *FindOrCreate( const char *name ) { 519 | u32 index = ( GetBucket( name, mBucketCount ) ), mask = ( mBucketCount - 1 ); 520 | for ( Caller *caller = mBuckets[index]; caller; caller = mBuckets[index & mask] ) { 521 | if ( caller->mName == name ) 522 | return caller; 523 | 524 | index = ( index + 1 ); 525 | } 526 | 527 | // didn't find the caller, lock this thread and mutate 528 | AcquirePerThreadLock(); 529 | EnsureCapacity( ++mNumChildren ); 530 | Caller *&slot = FindEmptyChildSlot( mBuckets, mBucketCount, name ); 531 | slot = new Caller( name, this ); 532 | ReleasePerThreadLock(); 533 | return slot; 534 | } 535 | 536 | template< class Mapto > 537 | void ForEachByRef( Mapto &mapto ) { 538 | for ( u32 i = 0; i < mBucketCount; ++i ) 539 | if ( mBuckets[ i ] ) 540 | mapto( mBuckets[ i ] ); 541 | } 542 | 543 | template< class Mapto > 544 | void ForEachByRefNonEmpty( Mapto &mapto ) { 545 | for ( u32 i = 0; i < mBucketCount; ++i ) 546 | if ( mBuckets[ i ] && !mBuckets[ i ]->GetTimer().IsEmpty() ) 547 | mapto( mBuckets[ i ] ); 548 | } 549 | 550 | template< class Mapto > 551 | void ForEach( Mapto mapto ) { 552 | ForEachByRef( mapto ); 553 | } 554 | 555 | template< class Mapto > 556 | void ForEachNonEmpty( Mapto mapto ) { 557 | ForEachByRefNonEmpty( mapto ); 558 | } 559 | 560 | inline Caller *GetParent() { 561 | return mParent; 562 | } 563 | 564 | Timer &GetTimer() { 565 | return mTimer; 566 | } 567 | 568 | const char *GetName() const { 569 | return mName; 570 | } 571 | 572 | bool IsActive() const { 573 | return mActive; 574 | } 575 | 576 | void Print( u32 indent = 0, bool islast = false ) { 577 | Buffer children( mNumChildren ); 578 | CopyToListNonEmpty( children ); 579 | 580 | mFormatter.EnsureCapacity( indent + 3 ); 581 | char *fmt = ( &mFormatter[indent] ); 582 | 583 | if ( indent ) { 584 | fmt[-2] = ( islast ) ? ' ' : '|'; 585 | fmt[-1] = ( islast ) ? '\\' : ' '; 586 | } 587 | fmt[0] = ( children.Size() ) ? '+' : '-'; 588 | fmt[1] = ( '-' ); 589 | fmt[2] = ( 0 ); 590 | 591 | Format(mFormatter.Data())( this, islast ); 592 | 593 | if ( indent && islast ) 594 | fmt[-2] = fmt[-1] = ' '; 595 | 596 | if ( children.Size() ) { 597 | children.Sort( compare::Ticks() ); 598 | children.ForEach( foreach::Printer(indent+2) ); 599 | } 600 | } 601 | 602 | void PrintHtml( FILE *f, u32 indent = 0, bool islast = false ) { 603 | Buffer children( mNumChildren ); 604 | CopyToListNonEmpty( children ); 605 | 606 | if ( !indent ) { 607 | mHTMLFormatter.Push( "img/root.gif" ); 608 | } else if ( children.Size() ) { 609 | mHTMLFormatter.Push( ( ( islast ) || ( children.Size() == 1 ) ) ? "img/last-child-open.gif" : "img/open.gif" ); 610 | } else { 611 | mHTMLFormatter.Push( ( islast ) ? "img/last-empty.gif" : "img/empty.gif" ); 612 | } 613 | 614 | FormatHtml(f, mHTMLFormatter)( this ); 615 | 616 | mHTMLFormatter[indent] = ( islast || !indent ) ? "img/blank.gif" : "img/vertical.gif"; 617 | 618 | if ( children.Size() ) { 619 | children.Sort( compare::Ticks() ); 620 | children.ForEach( foreach::PrinterHtml(f,indent+1) ); 621 | } 622 | 623 | mHTMLFormatter.Pop(); 624 | } 625 | 626 | void PrintTopStats( u32 nitems ) { 627 | nitems = ( nitems > mNumChildren ) ? mNumChildren : nitems; 628 | printf( "\ntop %d functions (self time)\n", (u32 )nitems ); 629 | Buffer sorted( mNumChildren ); 630 | CopyToListNonEmpty( sorted ); 631 | sorted.Sort( compare::SelfTicks() ); 632 | sorted.ForEach( Format(">"), nitems ); 633 | } 634 | 635 | void Resize( u32 new_size ) { 636 | new_size = ( new_size < mBucketCount ) ? mBucketCount << 1 : nextpow2( new_size - 1 ); 637 | Caller **new_buckets = (Caller **)calloc( new_size, sizeof( Caller* ) ); 638 | ForEach( foreach::AddToNewBuckets( new_buckets, new_size ) ); 639 | 640 | free( mBuckets ); 641 | mBuckets = ( new_buckets ); 642 | mBucketCount = ( new_size ); 643 | } 644 | 645 | void Reset() { 646 | ForEach( foreach::Deleter() ); 647 | zeroarray( mBuckets, mBucketCount ); 648 | mNumChildren = ( 0 ); 649 | mTimer.Reset(); 650 | } 651 | 652 | void SetActive( bool active ) { 653 | mActive = active; 654 | } 655 | 656 | void SetParent( Caller *parent ) { 657 | mParent = parent; 658 | } 659 | 660 | void SoftReset() { 661 | mTimer.SoftReset(); 662 | ForEach( foreach::SoftReset() ); 663 | } 664 | 665 | void Start() { 666 | mTimer.Start(); 667 | } 668 | 669 | void Stop() { 670 | mTimer.Stop(); 671 | } 672 | 673 | void *operator new ( size_t size ) { 674 | return calloc( size, 1 ); 675 | } 676 | 677 | void operator delete ( void *p ) { 678 | free( p ); 679 | } 680 | 681 | 682 | /* Acquire the caller lock for this thread */ 683 | 684 | inline static void AcquirePerThreadLock() { 685 | #if defined(__PROFILER_SMP__) 686 | if ( thisThread.requireThreadLock ) 687 | thisThread.threadLock.Acquire(); 688 | #endif 689 | } 690 | 691 | inline static void ReleasePerThreadLock() { 692 | #if defined(__PROFILER_SMP__) 693 | if ( thisThread.requireThreadLock ) 694 | thisThread.threadLock.Release(); 695 | #endif 696 | } 697 | 698 | protected: 699 | static inline Caller *&FindEmptyChildSlot( Caller **buckets, u32 bucket_count, const char *name ) { 700 | u32 index = ( GetBucket( name, bucket_count ) ), mask = ( bucket_count - 1 ); 701 | Caller **caller = &buckets[index]; 702 | for ( ; *caller; caller = &buckets[index & mask] ) 703 | index = ( index + 1 ); 704 | return *caller; 705 | } 706 | 707 | inline static u32 GetBucket( const char *name, u32 bucket_count ) { 708 | return u32( ( ( (size_t )name >> 5 ) /* * 2654435761 */ ) & ( bucket_count - 1 ) ); 709 | } 710 | 711 | inline void EnsureCapacity( u32 capacity ) { 712 | if ( capacity < ( mBucketCount / 2 ) ) 713 | return; 714 | Resize( capacity ); 715 | } 716 | 717 | protected: 718 | const char *mName; 719 | Timer mTimer; 720 | u32 mBucketCount, mNumChildren; 721 | Caller **mBuckets, *mParent; 722 | 723 | bool mActive; 724 | u64 mChildTicks; 725 | 726 | public: 727 | // caller 728 | static Buffer mFormatter; 729 | static Buffer mHTMLFormatter; 730 | static ColorRamp mColors; 731 | 732 | // global 733 | static f64 mTimerOverhead, mRdtscOverhead; 734 | static u64 mGlobalDuration; 735 | static struct Max { 736 | enum f64Enum { SelfMs = 0, Ms, Avg, SelfAvg, f64Enums }; 737 | enum u64Enum { Calls = 0, TotalCalls, u64Enums }; 738 | 739 | void reset() { 740 | memset( this, 0, sizeof( *this ) ); 741 | } 742 | 743 | void check( u64Enum e, u64 u ) { if ( u64fields[e] < u ) u64fields[e] = u; if ( e == Calls ) u64fields[TotalCalls] += u; } 744 | void check( f64Enum e, f64 f ) { if ( f64fields[e] < f ) f64fields[e] = f; } 745 | 746 | const char *color( u64Enum e, u64 u ) const { return mColors.value( f32(f64(u)/f64(u64fields[e])) ); } 747 | const char *color( f64Enum e, f64 f ) const { return mColors.value( f32(f/f64fields[e]) ); } 748 | 749 | const u64 &operator() ( u64Enum e ) const { return u64fields[e]; } 750 | const f64 &operator() ( f64Enum e ) const { return f64fields[e]; } 751 | 752 | protected: 753 | u64 u64fields[u64Enums]; 754 | f64 f64fields[f64Enums]; 755 | } maxStats; 756 | 757 | // per thread state 758 | struct ThreadState { 759 | CASLock threadLock; 760 | bool requireThreadLock; 761 | Caller *activeCaller; 762 | }; 763 | 764 | static threadlocal ThreadState thisThread; 765 | }; 766 | #pragma pack(pop) 767 | #endif 768 | 769 | 770 | 771 | 772 | 773 | #if defined(__PROFILER_ENABLED__) 774 | threadlocal Caller::ThreadState Caller::thisThread = { {0}, 0, 0 }; 775 | f64 Caller::mTimerOverhead = 0, Caller::mRdtscOverhead = 0; 776 | u64 Caller::mGlobalDuration = 0; 777 | Caller::Max Caller::maxStats; 778 | Buffer Caller::mFormatter( 64 ); 779 | Buffer Caller::mHTMLFormatter( 64 ); 780 | ColorRamp Caller::mColors; 781 | char *programName = NULL, *commandLine = NULL; 782 | 783 | void detectByArgs( int argc, const char *argv[] ) { 784 | const char *path = argv[0], *finalSlash = path, *iter = path; 785 | for ( ; *iter; ++iter ) 786 | finalSlash = ( *iter == PATHSLASH() ) ? iter + 1 : finalSlash; 787 | if ( !*finalSlash ) 788 | finalSlash = path; 789 | programName = copystring( finalSlash ); 790 | 791 | size_t width = 0; 792 | for ( int i = 1; i < argc; i++ ) { 793 | size_t len = strlen( argv[i] ); 794 | commandLine = (char *)realloc( commandLine, width + len + 1 ); 795 | memcpy( commandLine + width, argv[i], len ); 796 | commandLine[width + len] = ' '; 797 | width += len + 1; 798 | } 799 | if ( width ) 800 | commandLine[width - 1] = '\x0'; 801 | } 802 | 803 | void detectWinMain( const char *cmdLine ) { 804 | #if defined(_MSC_VER) 805 | char path[1024], *finalSlash = path, *iter = path; 806 | GetModuleFileName( NULL, path, 1023 ); 807 | for ( ; *iter; ++iter ) 808 | finalSlash = ( *iter == PATHSLASH() ) ? iter + 1 : finalSlash; 809 | if ( !*finalSlash ) 810 | finalSlash = path; 811 | programName = copystring( finalSlash ); 812 | commandLine = copystring( cmdLine ); 813 | #else 814 | programName = copystring( "only_for_win32" ); 815 | commandLine = copystring( "" ); 816 | #endif 817 | } 818 | 819 | /* 820 | ============ 821 | Root - Holds the root caller and the thread state for a thread 822 | ============ 823 | */ 824 | 825 | struct Root { 826 | Root( Caller *caller, Caller::ThreadState *ts ) : root(caller), threadState(ts) {} 827 | Caller *root; 828 | Caller::ThreadState *threadState; 829 | }; 830 | 831 | struct GlobalThreadList { 832 | ~GlobalThreadList() { 833 | if ( list ) { 834 | Buffer &threadsref = *list; 835 | u32 cnt = threadsref.Size(); 836 | for ( u32 i = 0; i < cnt; i++ ) 837 | delete threadsref[i].root; 838 | } 839 | delete list; 840 | } 841 | 842 | void AcquireGlobalLock() { 843 | threadsLock.Acquire(); 844 | if ( !list ) 845 | list = new Buffer; 846 | } 847 | 848 | void ReleaseGlobalLock() { 849 | threadsLock.Release(); 850 | } 851 | 852 | Buffer *list; 853 | CASLock threadsLock; 854 | }; 855 | 856 | u64 globalStart = Timer::getticks(); 857 | GlobalThreadList threads = { NULL, {0} }; 858 | threadlocal Caller *root = NULL; 859 | 860 | 861 | /* 862 | Thread Dumping 863 | */ 864 | 865 | struct PrintfDumper { 866 | void Init() { 867 | } 868 | 869 | void GlobalInfo( u64 rawCycles ) { 870 | printf( "> Raw run time %.2f mcycles\n", Timer::ms( rawCycles ) ); 871 | } 872 | 873 | void ThreadsInfo( u64 totalCalls, f64 timerOverhead, f64 rdtscOverhead ) { 874 | printf( "> Total calls " PRINTFU64() ", per call overhead %.0f cycles, rdtsc overhead %.0f cycles, estimated overhead %.2f mcycles\n\n", 875 | totalCalls, timerOverhead, rdtscOverhead, Timer::ms( timerOverhead * totalCalls ) ); 876 | } 877 | 878 | void PrintThread( Caller *root ) { 879 | root->Print(); 880 | printf( "\n\n" ); 881 | } 882 | 883 | void PrintAccumulated( Caller *accumulated ) { 884 | accumulated->PrintTopStats( 50 ); 885 | } 886 | 887 | void Finish() { 888 | } 889 | }; 890 | 891 | struct HTMLDumper { 892 | void Init() { 893 | Caller::mColors.clear(); 894 | Caller::mColors.push( ColorF( 255.0f/255.0f, 255.0f/255.0f, 255.0f/255.0f ), 0.00f ); 895 | //Caller::mColors.push( ColorF( 255.0f/255.0f, 212.0f/255.0f, 129.0f/255.0f ), 0.50f ); 896 | Caller::mColors.push( ColorF( 255.0f/255.0f, 203.0f/255.0f, 203.0f/255.0f ), 0.20f ); 897 | Caller::mColors.push( ColorF( 255.0f/255.0f, 128.0f/255.0f, 128.0f/255.0f ), 1.00f ); 898 | 899 | time_t now; 900 | time( &now ); 901 | tm *now_tm = localtime( &now ); 902 | strftime( timeFormat, 255, "%Y%m%d_%H%M%S", now_tm ); 903 | snprintf( fileFormat, 255, "%s-profile-%s.html", programName ? programName : "no-info-given", timeFormat ); 904 | strftime( timeFormat, 255, "%Ec", now_tm ); 905 | f = fopen( fileFormat, "wb+" ); 906 | 907 | Caller::mHTMLFormatter.Clear(); 908 | fputs( 909 | "\n" 910 | "\n" 911 | "\n" 912 | " \n" 930 | "\n" 931 | "\n\n", 932 | f 933 | ); 934 | } 935 | 936 | void GlobalInfo( u64 rawCycles ) { 937 | fputs( "
", f ); 938 | if ( programName ) { 939 | fprintf( f, "", f ); 943 | } 944 | fprintf( f, "\n", 945 | timeFormat, Timer::ms( rawCycles ) ); 946 | } 947 | 948 | void ThreadsInfo( u64 totalCalls, f64 timerOverhead, f64 rdtscOverhead ) { 949 | fprintf( f, "\n", totalCalls ); 950 | fprintf( f, "\n", rdtscOverhead ); 951 | fprintf( f, "\n", timerOverhead ); 952 | fprintf( f, "\n", Timer::ms( totalCalls * timerOverhead ) ); 953 | fprintf( f, "
Command Line: %s", programName ); 940 | if ( commandLine ) 941 | fprintf( f, " %s", commandLine ); 942 | fputs( "
Date: %s
Raw run time: %.2f mcycles
Total calls: " PRINTFU64() "
rdtsc overhead: %.0f cycles
Per call overhead: %.0f cycles
Estimated overhead: %.4f mcycles
\n" ); 954 | } 955 | 956 | void PrintThread( Caller *root ) { 957 | fputs( "
\n", f ); 958 | fputs( css_title_row, f ); 959 | root->PrintHtml( f ); 960 | fputs( "
\n", f ); 961 | } 962 | 963 | void PrintAccumulated( Caller *accumulated ) { 964 | fputs( "
\n", f ); 965 | fputs( css_totals_row, f ); 966 | fputs( "\n", f ); 967 | Buffer sorted; 968 | accumulated->CopyToListNonEmpty( sorted ); 969 | sorted.ForEach( Caller::foreach::UpdateTopMaxStats() ); 970 | sorted.Sort( Caller::compare::SelfTicks() ); 971 | sorted.ForEach( Caller::FormatHtmlTop(f) ); 972 | fputs( "
Functions sorted by self time
\n", f ); 973 | } 974 | 975 | void Finish() { 976 | fputs( "\n", f ); 977 | fputs( "", f ); 978 | fclose( f ); 979 | } 980 | 981 | protected: 982 | FILE *f; 983 | char timeFormat[256], fileFormat[256]; 984 | }; 985 | 986 | template< class Dumper > 987 | void dumpThreads( Dumper dumper ) { 988 | u64 rawDuration = ( Timer::getticks() - globalStart ); 989 | 990 | Caller *accumulate = new Caller( "/Top Callers" ), *packer = new Caller( "/Thread Packer" ); 991 | Buffer packedThreads; 992 | 993 | dumper.Init(); 994 | dumper.GlobalInfo( rawDuration ); 995 | 996 | threads.AcquireGlobalLock(); 997 | 998 | // crawl the list of theads and store their data in to packer 999 | Buffer &threadsref = *threads.list; 1000 | for ( u32 i = 0; i < threadsref.Size(); i++ ) { 1001 | Root &thread = threadsref[i]; 1002 | 1003 | // if the thread is no longer active, the lock won't be valid 1004 | bool active = ( thread.root->IsActive() ); 1005 | if ( active ) { 1006 | thread.threadState->threadLock.Acquire(); 1007 | // disable requiring our local lock in case the caller is in our thread, accumulate will try to set it otherwise 1008 | Caller::thisThread.requireThreadLock = false; 1009 | for ( Caller *walk = thread.threadState->activeCaller; walk; walk = walk->GetParent() ) 1010 | walk->GetTimer().SoftStop(); 1011 | } 1012 | 1013 | #if defined(__PROFILER_CONSOLIDATE_THREADS__) 1014 | // merge the thread into the packer object, will result in 1 caller per thread name, not 1 caller per thread instance 1015 | Caller::foreach::Merger( packer ).addFrom( thread.root ); 1016 | Caller *child = packer->FindOrCreate( thread.root->GetName() ); 1017 | 1018 | // add the child to the list of threads to dump (use the active flag to indicate if it's been added) 1019 | if ( !child->IsActive() ) { 1020 | packedThreads.Push( child ); 1021 | child->SetActive( true ); 1022 | } 1023 | #else 1024 | // create a dummy entry for each thread (fake a name with the address of the thread root) 1025 | Caller *stub = packer->FindOrCreate( (const char *)thread.root ); 1026 | Caller::foreach::Merger( stub ).addFrom( thread.root ); 1027 | Caller *stubroot = stub->FindOrCreate( thread.root->GetName() ); 1028 | stubroot->SetParent( NULL ); // for proper crawling 1029 | packedThreads.Push( stubroot ); 1030 | #endif 1031 | 1032 | if ( active ) { 1033 | Caller::thisThread.requireThreadLock = true; 1034 | thread.threadState->threadLock.Release(); 1035 | } 1036 | } 1037 | 1038 | // working on local data now, don't need the threads lock any more 1039 | threads.ReleaseGlobalLock(); 1040 | 1041 | // do the pre-computations on the gathered threads 1042 | Caller::ComputeChildTicks preprocessor( *accumulate ); 1043 | for ( u32 i = 0; i < packedThreads.Size(); i++ ) 1044 | preprocessor( packedThreads[i] ); 1045 | 1046 | dumper.ThreadsInfo( Caller::maxStats( Caller::Max::TotalCalls ), Caller::mTimerOverhead, Caller::mRdtscOverhead ); 1047 | 1048 | // print the gathered threads 1049 | u64 sumTicks = 0; 1050 | for ( u32 i = 0; i < packedThreads.Size(); i++ ) { 1051 | Caller *root = packedThreads[i]; 1052 | u64 threadTicks = root->GetTimer().ticks; 1053 | sumTicks += threadTicks; 1054 | Caller::mGlobalDuration = threadTicks; 1055 | dumper.PrintThread( root ); 1056 | } 1057 | 1058 | // print the totals, use the summed total of ticks to adjust percentages 1059 | Caller::mGlobalDuration = sumTicks; 1060 | dumper.PrintAccumulated( accumulate ); 1061 | dumper.Finish(); 1062 | 1063 | delete accumulate; 1064 | delete packer; 1065 | } 1066 | 1067 | void resetThreads() { 1068 | globalStart = Timer::getticks(); 1069 | 1070 | #if defined(__PROFILER_SMP__) 1071 | threads.AcquireGlobalLock(); 1072 | 1073 | Buffer &threadsref = *threads.list; 1074 | u32 cnt = threadsref.Size(), last = cnt - 1; 1075 | for ( u32 i = 0; i < cnt; i++ ) { 1076 | Root &thread = threadsref[i]; 1077 | if ( !thread.root->IsActive() ) { 1078 | // thread isn't active, remove it 1079 | delete thread.root; 1080 | Root removed = threadsref.Pop(); 1081 | if ( i != last ) 1082 | thread = removed; 1083 | last--; 1084 | cnt--; 1085 | i--; 1086 | } else { 1087 | thread.threadState->threadLock.Acquire(); 1088 | thread.root->SoftReset(); 1089 | thread.threadState->threadLock.Release(); 1090 | } 1091 | } 1092 | 1093 | threads.ReleaseGlobalLock(); 1094 | #else 1095 | if ( root ) 1096 | root->SoftReset(); 1097 | #endif 1098 | } 1099 | 1100 | void enterThread( const char *name ) { 1101 | Caller *tmp = new Caller( name ); 1102 | 1103 | threads.AcquireGlobalLock(); 1104 | threads.list->Push( Root( tmp, &Caller::thisThread ) ); 1105 | 1106 | Caller::AcquirePerThreadLock(); 1107 | Caller::thisThread.activeCaller = tmp; 1108 | tmp->Start(); 1109 | tmp->SetActive( true ); 1110 | root = tmp; 1111 | Caller::ReleasePerThreadLock(); 1112 | 1113 | threads.ReleaseGlobalLock(); 1114 | } 1115 | 1116 | void exitThread() { 1117 | threads.AcquireGlobalLock(); 1118 | 1119 | Caller::AcquirePerThreadLock(); 1120 | root->Stop(); 1121 | root->SetActive( false ); 1122 | Caller::thisThread.activeCaller = NULL; 1123 | Caller::ReleasePerThreadLock(); 1124 | 1125 | threads.ReleaseGlobalLock(); 1126 | } 1127 | 1128 | inline void fastcall enterCaller( const char *name ) { 1129 | Caller *parent = Caller::thisThread.activeCaller; 1130 | if ( !parent ) 1131 | return; 1132 | 1133 | Caller *active = parent->FindOrCreate( name ); 1134 | active->Start(); 1135 | Caller::thisThread.activeCaller = active; 1136 | } 1137 | 1138 | inline void exitCaller() { 1139 | Caller *active = Caller::thisThread.activeCaller; 1140 | if ( !active ) 1141 | return; 1142 | 1143 | active->Stop(); 1144 | Caller::thisThread.activeCaller = active->GetParent(); 1145 | } 1146 | 1147 | inline void pauseCaller() { 1148 | u64 curticks = Timer::getticks(); 1149 | Caller *iter = Caller::thisThread.activeCaller; 1150 | for ( ; iter; iter = iter->GetParent() ) 1151 | iter->GetTimer().Pause( curticks ); 1152 | } 1153 | 1154 | inline void unpauseCaller() { 1155 | u64 curticks = Timer::getticks(); 1156 | Caller *iter = Caller::thisThread.activeCaller; 1157 | for ( ; iter; iter = iter->GetParent() ) 1158 | iter->GetTimer().Unpause( curticks ); 1159 | } 1160 | 1161 | // enter the main thread automatically 1162 | struct MakeRoot { 1163 | MakeRoot() { 1164 | // get an idea of how long timer calls / rdtsc takes 1165 | const u32 reps = 1000; 1166 | Caller::mTimerOverhead = Caller::mRdtscOverhead = 1000000; 1167 | for ( u32 tries = 0; tries < 20; tries++ ) { 1168 | Timer t, t2; 1169 | t.Start(); 1170 | for ( u32 i = 0; i < reps; i++ ) { 1171 | t2.Start(); 1172 | t2.Stop(); 1173 | } 1174 | t.Stop(); 1175 | f64 avg = f64(t2.ticks)/f64(reps); 1176 | if ( avg < Caller::mRdtscOverhead ) 1177 | Caller::mRdtscOverhead = avg; 1178 | avg = f64(t.ticks)/f64(reps); 1179 | if ( avg < Caller::mTimerOverhead ) 1180 | Caller::mTimerOverhead = avg; 1181 | } 1182 | 1183 | enterThread( "/Main" ); 1184 | } 1185 | 1186 | ~MakeRoot() { 1187 | free( programName ); 1188 | free( commandLine ); 1189 | } 1190 | } makeRoot; 1191 | 1192 | void detect( int argc, const char *argv[] ) { detectByArgs( argc, argv ); } 1193 | void detect( const char *commandLine ) { detectWinMain( commandLine ); } 1194 | void dump() { dumpThreads( PrintfDumper() ); } 1195 | void dumphtml() { dumpThreads( HTMLDumper() ); } 1196 | void fastcall enter( const char *name ) { enterCaller( name ); } 1197 | void fastcall exit() { exitCaller(); } 1198 | void fastcall pause() { pauseCaller(); } 1199 | void fastcall unpause() { unpauseCaller(); } 1200 | void threadenter( const char *name ) { enterThread( name ); } 1201 | void threadexit() { exitThread(); } 1202 | void reset() { resetThreads(); } 1203 | #else 1204 | void detect( int argc, const char *argv[] ) {} 1205 | void detect( const char *commandLine ) {} 1206 | void dump() {} 1207 | void dumphtml() {} 1208 | void fastcall enter( const char *name ) {} 1209 | void fastcall exit() {} 1210 | void fastcall pause() {} 1211 | void fastcall unpause() {} 1212 | void threadenter( const char *name ) {} 1213 | void threadexit() {} 1214 | void reset() {} 1215 | #endif 1216 | 1217 | }; // namespace Profiler 1218 | -------------------------------------------------------------------------------- /src/profiler/Profiler.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROFILER_H__ 2 | #define __PROFILER_H__ 3 | 4 | //#define __PROFILER_ENABLED__ 5 | #define __PROFILER_FULL_TYPE_EXPANSION__ 6 | 7 | #undef noinline 8 | #undef fastcall 9 | 10 | #if defined(_MSC_VER) 11 | #undef __PRETTY_FUNCTION__ 12 | #define __PRETTY_FUNCTION__ __FUNCSIG__ 13 | #define PROFILE_CONCAT( a, b ) a "/" b 14 | 15 | #define noinline __declspec(noinline) 16 | #define fastcall __fastcall 17 | #else 18 | #define PROFILE_CONCAT( a, b ) b 19 | 20 | #define noinline __attribute__ ((noinline)) 21 | #define fastcall 22 | #endif 23 | 24 | #if defined(__PROFILER_FULL_TYPE_EXPANSION__) 25 | #define PROFILE_FUNCTION() __PRETTY_FUNCTION__ 26 | #else 27 | #define PROFILE_FUNCTION() __FUNCTION__ 28 | #endif 29 | 30 | #if defined(__PROFILER_ENABLED__) 31 | // thread 32 | #define PROFILE_THREAD_START_RAW( text ) Profiler::threadenter( text ); 33 | #define PROFILE_THREAD_START() PROFILE_THREAD_START_RAW( PROFILE_FUNCTION() ) 34 | #define PROFILE_THREAD_START_DESC( desc ) PROFILE_THREAD_START_RAW( PROFILE_CONCAT( PROFILE_FUNCTION(), desc ) ) 35 | 36 | #define PROFILE_THREAD_SCOPED_RAW( text ) Profiler::ScopedThread profiler##__LINE__ ( text ); 37 | #define PROFILE_THREAD_SCOPED() PROFILE_THREAD_SCOPED_RAW( PROFILE_FUNCTION() ) 38 | #define PROFILE_THREAD_SCOPED_DESC( desc ) PROFILE_THREAD_SCOPED_RAW( PROFILE_CONCAT( PROFILE_FUNCTION(), desc ) ) 39 | 40 | #define PROFILE_THREAD_STOP() Profiler::threadexit(); 41 | 42 | // function 43 | #define PROFILE_PAUSE() Profiler::pause(); 44 | #define PROFILE_UNPAUSE() Profiler::unpause(); 45 | #define PROFILE_PAUSE_SCOPED() Profiler::ScopedPause profilerpause##__LINE__; 46 | 47 | #define PROFILE_START_RAW( text ) Profiler::enter( text ); 48 | #define PROFILE_START() PROFILE_START_RAW( PROFILE_FUNCTION() ) 49 | #define PROFILE_START_DESC( desc ) PROFILE_START_RAW( PROFILE_CONCAT( PROFILE_FUNCTION(), desc ) ) 50 | 51 | #define PROFILE_SCOPED_RAW( text ) Profiler::Scoped profiler##__LINE__ ( text ); 52 | #define PROFILE_SCOPED() PROFILE_SCOPED_RAW( PROFILE_FUNCTION() ) 53 | #define PROFILE_SCOPED_DESC( desc ) PROFILE_SCOPED_RAW( PROFILE_CONCAT( PROFILE_FUNCTION(), desc ) ) 54 | 55 | #define PROFILE_STOP() Profiler::exit(); 56 | #else 57 | #define PROFILE_THREAD_START_RAW( text ) 58 | #define PROFILE_THREAD_START() 59 | #define PROFILE_THREAD_START_DESC( desc ) 60 | 61 | #define PROFILE_THREAD_SCOPED_RAW( text ) 62 | #define PROFILE_THREAD_SCOPED() 63 | #define PROFILE_THREAD_SCOPED_DESC( desc ) 64 | 65 | #define PROFILE_THREAD_STOP() 66 | 67 | #define PROFILE_PAUSE() 68 | #define PROFILE_UNPAUSE() 69 | #define PROFILE_PAUSE_SCOPED() 70 | 71 | #define PROFILE_START_RAW( text ) 72 | #define PROFILE_START() 73 | #define PROFILE_START_DESC( desc ) 74 | 75 | #define PROFILE_SCOPED_RAW( text ) 76 | #define PROFILE_SCOPED() 77 | #define PROFILE_SCOPED_DESC( desc ) 78 | 79 | #define PROFILE_STOP() 80 | #endif 81 | 82 | namespace Profiler { 83 | /* 84 | ============= 85 | Types that won't conflict with the rest of the system 86 | ============= 87 | */ 88 | typedef float f32; 89 | typedef double f64; 90 | typedef unsigned char u8; 91 | typedef unsigned short u16; 92 | typedef unsigned int u32; 93 | #if defined(_MSC_VER) 94 | typedef unsigned __int64 u64; 95 | #else 96 | typedef unsigned long long u64; 97 | #endif 98 | 99 | template< class type1, class type2 > 100 | f64 average( type1 sum, type2 count ) { 101 | return ( count ) ? f64(sum)/f64(count) : 0; 102 | } 103 | 104 | /* 105 | ============= 106 | Timer 107 | ============= 108 | */ 109 | #pragma pack(push,1) 110 | struct Timer { 111 | Timer() { Reset(); } 112 | 113 | inline bool IsEmpty() const { return ticks == 0; } 114 | inline bool IsPaused() const { return paused; } 115 | inline void Unpause( u64 curticks ) { started = curticks; paused = false; } 116 | inline void Unpause() { Unpause( getticks() ); } 117 | inline void Pause( u64 curticks ) { ticks += ( curticks - started ); paused = true; } 118 | inline void Pause() { Pause( getticks() ); } 119 | inline void Start() { ++calls; started = getticks(); } 120 | inline void Stop() { ticks += ( getticks() - started ); } 121 | inline void Reset() { ticks = started = calls = 0; paused = false; } 122 | inline void SoftStop() { if ( !paused ) { u64 t = getticks(); ticks += ( t - started ); started = t; } } 123 | inline void SoftReset() { ticks = 0; calls = 1; started = getticks(); } 124 | 125 | template< class type > static f64 ms( const type &t ) { return f64( t ) / 1000000.0; } 126 | f64 millicycles() { return ms( ticks ); } 127 | f64 currentmillicycles() { return ms( ticks + ( getticks() - started ) ); } 128 | f64 avg() { return average( ticks, calls ); } 129 | f64 avgms() { return ms( average( ticks, calls ) ); } 130 | 131 | void operator+= ( const Timer &b ) { 132 | ticks += b.ticks; 133 | calls += b.calls; 134 | } 135 | 136 | static inline u64 getticks_serial() { 137 | #if defined(__GNUC__) 138 | asm volatile("cpuid" : : : "%eax", "%ebx", "%ecx", "%edx" ); 139 | #else 140 | __asm cpuid; 141 | #endif 142 | return getticks(); 143 | } 144 | 145 | #if defined(__GNUC__) 146 | static inline u64 getticks() { 147 | u32 __a,__d; 148 | asm volatile("rdtsc" : "=a" (__a), "=d" (__d)); 149 | return ( u64(__a) | u64(__d) << 32 ); 150 | } 151 | #elif defined(__ICC) || defined(__ICL) 152 | static inline u64 getticks() { return _rdtsc(); } 153 | #else 154 | static inline u64 getticks() { __asm { rdtsc }; } 155 | #endif 156 | 157 | u64 ticks, started; 158 | u32 calls; 159 | bool paused; 160 | }; 161 | #pragma pack(pop) 162 | 163 | 164 | /* 165 | ============= 166 | Interface functions 167 | ============= 168 | */ 169 | 170 | void detect( int argc, const char *argv[] ); 171 | void detect( const char *commandLine ); 172 | void dump(); 173 | void dumphtml(); 174 | void fastcall enter( const char *name ); 175 | void fastcall exit(); 176 | void fastcall pause(); 177 | void fastcall unpause(); 178 | void threadenter( const char *name ); 179 | void threadexit(); 180 | void reset(); 181 | 182 | struct Scoped { 183 | Scoped( const char *name ) { PROFILE_START_RAW( name ) } 184 | ~Scoped() { PROFILE_STOP() } 185 | }; 186 | 187 | struct ScopedPause { 188 | ScopedPause() { PROFILE_PAUSE() } 189 | ~ScopedPause() { PROFILE_UNPAUSE() } 190 | }; 191 | 192 | struct ScopedThread { 193 | ScopedThread( const char *name ) { PROFILE_THREAD_START_RAW( name ); } 194 | ~ScopedThread() { PROFILE_THREAD_STOP() } 195 | }; 196 | }; // namespace Profiler 197 | 198 | 199 | struct ScopedTimer { 200 | ScopedTimer( Profiler::Timer &t ) : mTimer(t) { mTimer.Start(); } 201 | ~ScopedTimer() { mTimer.Stop(); } 202 | protected: 203 | Profiler::Timer &mTimer; 204 | }; 205 | 206 | #endif // __PROFILER_H__ 207 | --------------------------------------------------------------------------------