├── .gitignore ├── LICENSE ├── Maskdata ├── ImageSets │ └── Main │ │ ├── train.txt │ │ ├── trainval.txt │ │ └── val.txt └── README.md ├── README.md ├── assets ├── 1_Handshaking_Handshaking_1_71.jpg ├── out_1_Handshaking_Handshaking_1_71.jpg ├── out_test_00002330.jpg └── test_00002330.jpg ├── checkpoints └── weights_epoch_100.h5 ├── components ├── __init__.py ├── config.py ├── kmeans.py ├── lr_scheduler.py ├── prior_box.py └── utils.py ├── dataset ├── check_dataset.py ├── tf_dataset_preprocess.py └── voc_to_tfrecord.py ├── inference.py ├── mAP ├── README.md ├── __init__.py ├── compute_mAP.py └── detect.py ├── network ├── __init__.py ├── losses.py └── network.py ├── requirements.txt └── train.py /.gitignore: -------------------------------------------------------------------------------- 1 | #package file 2 | *.war 3 | *.ear 4 | *.zip 5 | *.tar.gz 6 | *.rar 7 | .idea 8 | __pycache__ 9 | 10 | log*/ 11 | *h5 12 | !/checkpoints/weights_epoch_100.h5 13 | *.txt 14 | 15 | /Maskdata/Annotations/*.xml 16 | /Maskdata/JPEGImages/*.jpg 17 | 18 | !/Maskdata/**/*.txt 19 | mAP/**/ 20 | numpy*.py 21 | dataset/*.tfrecord 22 | dataset/*.txt 23 | dataset/split*.py 24 | dataset/xml*.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 PureHing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Maskdata/ImageSets/Main/val.txt: -------------------------------------------------------------------------------- 1 | test_00003299 2 | test_00001620 3 | test_00001630 4 | test_00002655 5 | 20_Family_Group_Family_Group_20_1026 6 | 40_Gymnastics_Gymnastics_40_47 7 | 13_Interview_Interview_2_People_Visible_13_241 8 | test_00003741 9 | 42_Car_Racing_Nascar_42_482 10 | test_00004602 11 | test_00002096 12 | test_00001314 13 | test_00001185 14 | test_00002392 15 | test_00001356 16 | test_00003748 17 | 52_Photographers_photographertakingphoto_52_743 18 | test_00003391 19 | test_00000708 20 | 4_Dancing_Dancing_4_983 21 | 19_Couple_Couple_19_873 22 | 38_Tennis_Tennis_38_485 23 | 52_Photographers_photographertakingphoto_52_84 24 | test_00003591 25 | test_00002840 26 | 39_Ice_Skating_Ice_Skating_39_486 27 | 38_Tennis_Tennis_38_371 28 | test_00003722 29 | 16_Award_Ceremony_Awards_Ceremony_16_311 30 | 4_Dancing_Dancing_4_960 31 | test_00003684 32 | test_00004361 33 | 13_Interview_Interview_On_Location_13_238 34 | test_00001293 35 | 47_Matador_Bullfighter_Matador_Bullfighter_47_567 36 | test_00000347 37 | 16_Award_Ceremony_Awards_Ceremony_16_474 38 | test_00003592 39 | 56_Voter_peoplevoting_56_873 40 | test_00000693 41 | test_00000131 42 | 30_Surgeons_Surgeons_30_840 43 | test_00000769 44 | test_00004920 45 | test_00004007 46 | test_00004340 47 | test_00004790 48 | test_00004421 49 | test_00001968 50 | test_00003626 51 | 13_Interview_Interview_On_Location_13_491 52 | test_00001311 53 | 38_Tennis_Tennis_38_332 54 | test_00000024 55 | 40_Gymnastics_Gymnastics_40_420 56 | test_00004854 57 | test_00004106 58 | 56_Voter_peoplevoting_56_887 59 | test_00004266 60 | test_00003567 61 | test_00000339 62 | test_00003635 63 | 13_Interview_Interview_Sequences_13_237 64 | 19_Couple_Couple_19_88 65 | 9_Press_Conference_Press_Conference_9_297 66 | 39_Ice_Skating_Ice_Skating_39_458 67 | 4_Dancing_Dancing_4_514 68 | 13_Interview_Interview_On_Location_13_3 69 | test_00003828 70 | test_00002712 71 | test_00003915 72 | 41_Swimming_Swimmer_41_885 73 | 31_Waiter_Waitress_Waiter_Waitress_31_215 74 | test_00004091 75 | test_00004683 76 | test_00001972 77 | test_00002532 78 | test_00001476 79 | test_00000115 80 | 41_Swimming_Swimmer_41_275 81 | test_00001788 82 | 51_Dresses_wearingdress_51_789 83 | test_00000524 84 | 38_Tennis_Tennis_38_497 85 | 41_Swimming_Swimmer_41_772 86 | 13_Interview_Interview_On_Location_13_610 87 | test_00004289 88 | test_00002734 89 | test_00001103 90 | 51_Dresses_wearingdress_51_549 91 | 8_Election_Campain_Election_Campaign_8_173 92 | 13_Interview_Interview_2_People_Visible_13_420 93 | test_00003553 94 | 13_Interview_Interview_2_People_Visible_13_237 95 | test_00002898 96 | test_00003341 97 | 51_Dresses_wearingdress_51_580 98 | test_00000337 99 | test_00002360 100 | 38_Tennis_Tennis_38_240 101 | 41_Swimming_Swimmer_41_55 102 | 16_Award_Ceremony_Awards_Ceremony_16_546 103 | 9_Press_Conference_Press_Conference_9_883 104 | 9_Press_Conference_Press_Conference_9_636 105 | test_00001879 106 | 56_Voter_peoplevoting_56_346 107 | 51_Dresses_wearingdress_51_763 108 | test_00000757 109 | test_00001588 110 | test_00000760 111 | test_00000784 112 | 13_Interview_Interview_On_Location_13_852 113 | test_00002120 114 | test_00004520 115 | 39_Ice_Skating_Ice_Skating_39_616 116 | 32_Worker_Laborer_Worker_Laborer_32_262 117 | test_00003114 118 | test_00002942 119 | test_00001533 120 | 51_Dresses_wearingdress_51_161 121 | test_00003800 122 | test_00000819 123 | test_00001366 124 | test_00003844 125 | 30_Surgeons_Surgeons_30_746 126 | 51_Dresses_wearingdress_51_874 127 | test_00002965 128 | test_00001919 129 | test_00003690 130 | 47_Matador_Bullfighter_matadorbullfighting_47_845 131 | test_00004071 132 | 28_Sports_Fan_Sports_Fan_28_198 133 | test_00000112 134 | 41_Swimming_Swimmer_41_232 135 | 9_Press_Conference_Press_Conference_9_552 136 | 13_Interview_Interview_On_Location_13_334 137 | 41_Swimming_Swimmer_41_35 138 | 13_Interview_Interview_2_People_Visible_13_425 139 | test_00003426 140 | test_00003958 141 | 4_Dancing_Dancing_4_1036 142 | test_00002310 143 | 59_peopledrivingcar_peopledrivingcar_59_200 144 | test_00004459 145 | test_00002801 146 | test_00000884 147 | 20_Family_Group_Family_Group_20_702 148 | 19_Couple_Couple_19_24 149 | test_00000616 150 | test_00000955 151 | test_00001698 152 | test_00002487 153 | 39_Ice_Skating_Ice_Skating_39_344 154 | 4_Dancing_Dancing_4_718 155 | test_00004486 156 | 13_Interview_Interview_Sequences_13_268 157 | 51_Dresses_wearingdress_51_599 158 | test_00003549 159 | 4_Dancing_Dancing_4_1026 160 | test_00001004 161 | test_00003366 162 | test_00003923 163 | test_00000730 164 | 32_Worker_Laborer_Worker_Laborer_32_443 165 | test_00004551 166 | test_00004157 167 | 19_Couple_Couple_19_349 168 | test_00001613 169 | test_00003514 170 | test_00001483 171 | test_00001231 172 | 38_Tennis_Tennis_38_717 173 | test_00002207 174 | 13_Interview_Interview_Sequences_13_813 175 | 32_Worker_Laborer_Worker_Laborer_32_357 176 | 39_Ice_Skating_Ice_Skating_39_611 177 | test_00000432 178 | test_00000292 179 | test_00002286 180 | test_00001371 181 | test_00002114 182 | test_00001363 183 | test_00000365 184 | test_00001182 185 | 51_Dresses_wearingdress_51_748 186 | 52_Photographers_photographertakingphoto_52_776 187 | test_00004494 188 | test_00002162 189 | test_00003030 190 | 27_Spa_Spa_27_486 191 | test_00003547 192 | test_00001070 193 | test_00002175 194 | test_00002814 195 | 38_Tennis_Tennis_38_323 196 | test_00000987 197 | test_00001211 198 | 19_Couple_Couple_19_86 199 | 13_Interview_Interview_Sequences_13_347 200 | test_00001963 201 | 4_Dancing_Dancing_4_228 202 | test_00003082 203 | test_00001097 204 | test_00004738 205 | test_00002448 206 | 41_Swimming_Swimmer_41_659 207 | test_00001990 208 | 41_Swimming_Swimmer_41_1002 209 | 40_Gymnastics_Gymnastics_40_740 210 | test_00002463 211 | 17_Ceremony_Ceremony_17_782 212 | 32_Worker_Laborer_Worker_Laborer_32_135 213 | 13_Interview_Interview_On_Location_13_539 214 | test_00004496 215 | test_00000095 216 | test_00002428 217 | test_00002475 218 | test_00000670 219 | test_00001872 220 | test_00000324 221 | test_00004606 222 | test_00002539 223 | test_00001764 224 | test_00001745 225 | test_00000639 226 | test_00004874 227 | test_00000126 228 | test_00002726 229 | 52_Photographers_photographertakingphoto_52_316 230 | test_00000198 231 | 51_Dresses_wearingdress_51_445 232 | test_00002896 233 | test_00003791 234 | 39_Ice_Skating_Ice_Skating_39_119 235 | test_00003967 236 | test_00004931 237 | 32_Worker_Laborer_Worker_Laborer_32_134 238 | test_00004387 239 | test_00000596 240 | test_00002451 241 | 51_Dresses_wearingdress_51_837 242 | test_00002245 243 | test_00003060 244 | test_00003603 245 | test_00003104 246 | 47_Matador_Bullfighter_Matador_Bullfighter_47_338 247 | test_00000067 248 | test_00001975 249 | test_00004324 250 | test_00001263 251 | 40_Gymnastics_Gymnastics_40_771 252 | test_00003378 253 | 32_Worker_Laborer_Worker_Laborer_32_944 254 | 13_Interview_Interview_Sequences_13_807 255 | 30_Surgeons_Surgeons_30_979 256 | 39_Ice_Skating_Ice_Skating_39_487 257 | test_00000258 258 | test_00000975 259 | 39_Ice_Skating_Ice_Skating_39_270 260 | test_00002009 261 | test_00004029 262 | 13_Interview_Interview_On_Location_13_933 263 | 41_Swimming_Swimmer_41_718 264 | 30_Surgeons_Surgeons_30_861 265 | 16_Award_Ceremony_Awards_Ceremony_16_239 266 | 45_Balloonist_Balloonist_45_733 267 | 40_Gymnastics_Gymnastics_40_460 268 | test_00002571 269 | 30_Surgeons_Surgeons_30_932 270 | test_00003038 271 | test_00003308 272 | test_00001888 273 | test_00002729 274 | 38_Tennis_Tennis_38_128 275 | test_00001802 276 | test_00001691 277 | test_00004629 278 | test_00002653 279 | 28_Sports_Fan_Sports_Fan_28_265 280 | test_00002129 281 | test_00000186 282 | 9_Press_Conference_Press_Conference_9_571 283 | test_00004689 284 | test_00001287 285 | test_00002785 286 | 13_Interview_Interview_2_People_Visible_13_239 287 | 13_Interview_Interview_Sequences_13_11 288 | test_00004043 289 | test_00001762 290 | test_00004856 291 | test_00003036 292 | 25_Soldier_Patrol_Soldier_Patrol_25_513 293 | test_00003213 294 | test_00000790 295 | 30_Surgeons_Surgeons_30_914 296 | 4_Dancing_Dancing_4_813 297 | test_00001102 298 | 28_Sports_Fan_Sports_Fan_28_130 299 | test_00003309 300 | 9_Press_Conference_Press_Conference_9_933 301 | test_00002113 302 | test_00004427 303 | 13_Interview_Interview_On_Location_13_861 304 | 31_Waiter_Waitress_Waiter_Waitress_31_932 305 | 17_Ceremony_Ceremony_17_972 306 | test_00004122 307 | test_00002937 308 | 20_Family_Group_Family_Group_20_599 309 | test_00000119 310 | 13_Interview_Interview_Sequences_13_373 311 | 52_Photographers_photographertakingphoto_52_809 312 | 30_Surgeons_Surgeons_30_8 313 | 41_Swimming_Swimming_41_714 314 | 49_Greeting_peoplegreeting_49_153 315 | 51_Dresses_wearingdress_51_340 316 | 59_peopledrivingcar_peopledrivingcar_59_64 317 | 39_Ice_Skating_iceskiing_39_249 318 | test_00002908 319 | 13_Interview_Interview_On_Location_13_208 320 | test_00003172 321 | test_00002875 322 | 1_Handshaking_Handshaking_1_134 323 | test_00003059 324 | test_00001136 325 | 1_Handshaking_Handshaking_1_357 326 | test_00004637 327 | test_00004781 328 | test_00002227 329 | 51_Dresses_wearingdress_51_815 330 | test_00002454 331 | 4_Dancing_Dancing_4_240 332 | 41_Swimming_Swimmer_41_538 333 | 52_Photographers_photographertakingphoto_52_759 334 | 32_Worker_Laborer_Worker_Laborer_32_400 335 | test_00001944 336 | 13_Interview_Interview_On_Location_13_554 337 | 41_Swimming_Swimmer_41_19 338 | test_00001167 339 | test_00003648 340 | test_00002333 341 | test_00003287 342 | test_00003180 343 | test_00004890 344 | test_00001171 345 | test_00004160 346 | 40_Gymnastics_Gymnastics_40_138 347 | test_00001238 348 | test_00001397 349 | 19_Couple_Couple_19_139 350 | test_00003911 351 | test_00004708 352 | test_00000732 353 | 16_Award_Ceremony_Awards_Ceremony_16_566 354 | test_00002707 355 | test_00000625 356 | 16_Award_Ceremony_Awards_Ceremony_16_124 357 | 16_Award_Ceremony_Awards_Ceremony_16_422 358 | test_00000272 359 | 32_Worker_Laborer_Worker_Laborer_32_566 360 | 31_Waiter_Waitress_Waiter_Waitress_31_339 361 | test_00004648 362 | test_00001770 363 | 1_Handshaking_Handshaking_1_275 364 | test_00001771 365 | 41_Swimming_Swimmer_41_113 366 | test_00000492 367 | test_00001777 368 | 19_Couple_Couple_19_90 369 | test_00000990 370 | 31_Waiter_Waitress_Waiter_Waitress_31_722 371 | test_00001843 372 | test_00000803 373 | 31_Waiter_Waitress_Waiter_Waitress_31_484 374 | 39_Ice_Skating_Ice_Skating_39_495 375 | test_00001438 376 | test_00003316 377 | test_00001340 378 | 59_peopledrivingcar_peopledrivingcar_59_117 379 | 27_Spa_Spa_27_728 380 | 27_Spa_Spa_27_393 381 | 45_Balloonist_Balloonist_45_615 382 | test_00002945 383 | 1_Handshaking_Handshaking_1_209 384 | 52_Photographers_taketouristphotos_52_97 385 | 1_Handshaking_Handshaking_1_567 386 | 19_Couple_Couple_19_106 387 | 19_Couple_Couple_19_881 388 | 28_Sports_Fan_Sports_Fan_28_448 389 | test_00003928 390 | 4_Dancing_Dancing_4_194 391 | 41_Swimming_Swimmer_41_701 392 | 13_Interview_Interview_On_Location_13_849 393 | 27_Spa_Spa_27_360 394 | 9_Press_Conference_Press_Conference_9_607 395 | test_00001442 396 | test_00000886 397 | test_00004364 398 | test_00002638 399 | test_00003952 400 | test_00002344 401 | test_00004360 402 | 59_peopledrivingcar_peopledrivingcar_59_34 403 | test_00001688 404 | 25_Soldier_Patrol_Soldier_Patrol_25_419 405 | test_00002303 406 | 31_Waiter_Waitress_Waiter_Waitress_31_726 407 | 13_Interview_Interview_On_Location_13_246 408 | 52_Photographers_photographertakingphoto_52_76 409 | 45_Balloonist_Balloonist_45_217 410 | test_00002336 411 | 28_Sports_Fan_Sports_Fan_28_282 412 | 38_Tennis_Tennis_38_592 413 | test_00000804 414 | test_00003229 415 | test_00003685 416 | 31_Waiter_Waitress_Waiter_Waitress_31_200 417 | test_00003683 418 | test_00001497 419 | test_00004202 420 | 13_Interview_Interview_Sequences_13_718 421 | test_00000608 422 | 13_Interview_Interview_2_People_Visible_13_107 423 | test_00003559 424 | 51_Dresses_wearingdress_51_150 425 | 39_Ice_Skating_Ice_Skating_39_825 426 | test_00002512 427 | test_00002064 428 | 45_Balloonist_Balloonist_45_936 429 | 41_Swimming_Swimming_41_641 430 | test_00004300 431 | test_00001447 432 | 42_Car_Racing_Car_Racing_42_1045 433 | test_00003337 434 | test_00004892 435 | test_00002149 436 | test_00000728 437 | 28_Sports_Fan_Sports_Fan_28_880 438 | test_00003199 439 | test_00000901 440 | test_00003569 441 | 20_Family_Group_Family_Group_20_760 442 | 13_Interview_Interview_On_Location_13_301 443 | 13_Interview_Interview_2_People_Visible_13_475 444 | test_00004303 445 | test_00001240 446 | test_00002790 447 | test_00004495 448 | test_00001081 449 | 13_Interview_Interview_Sequences_13_15 450 | 1_Handshaking_Handshaking_1_827 451 | test_00000564 452 | test_00002168 453 | test_00004428 454 | 40_Gymnastics_Gymnastics_40_401 455 | test_00001015 456 | test_00004260 457 | test_00001938 458 | test_00004634 459 | 1_Handshaking_Handshaking_1_801 460 | test_00001572 461 | 20_Family_Group_Family_Group_20_87 462 | test_00001928 463 | test_00000863 464 | test_00000660 465 | test_00001717 466 | test_00000749 467 | 52_Photographers_photographertakingphoto_52_61 468 | 59_peopledrivingcar_peopledrivingcar_59_789 469 | 39_Ice_Skating_iceskiing_39_276 470 | test_00001564 471 | 13_Interview_Interview_2_People_Visible_13_327 472 | test_00004895 473 | test_00004922 474 | 9_Press_Conference_Press_Conference_9_595 475 | 40_Gymnastics_Gymnastics_40_389 476 | test_00004016 477 | 31_Waiter_Waitress_Waiter_Waitress_31_847 478 | 16_Award_Ceremony_Awards_Ceremony_16_512 479 | 39_Ice_Skating_Ice_Skating_39_44 480 | test_00004323 481 | 38_Tennis_Tennis_38_452 482 | test_00004015 483 | 39_Ice_Skating_Ice_Skating_39_382 484 | test_00000184 485 | test_00004304 486 | test_00001566 487 | 59_peopledrivingcar_peopledrivingcar_59_95 488 | test_00003421 489 | 51_Dresses_wearingdress_51_388 490 | test_00004550 491 | 13_Interview_Interview_On_Location_13_33 492 | test_00000910 493 | 51_Dresses_wearingdress_51_226 494 | test_00001714 495 | test_00001455 496 | test_00004378 497 | 20_Family_Group_Family_Group_20_1015 498 | test_00002941 499 | 13_Interview_Interview_Sequences_13_103 500 | test_00004582 501 | 31_Waiter_Waitress_Waiter_Waitress_31_742 502 | test_00004050 503 | test_00000926 504 | 31_Waiter_Waitress_Waiter_Waitress_31_667 505 | test_00002723 506 | test_00002260 507 | test_00000715 508 | 28_Sports_Fan_Sports_Fan_28_357 509 | 59_peopledrivingcar_peopledrivingcar_59_690 510 | test_00002635 511 | 27_Spa_Spa_27_512 512 | 25_Soldier_Patrol_Soldier_Patrol_25_1045 513 | test_00004334 514 | test_00002499 515 | 32_Worker_Laborer_Worker_Laborer_32_170 516 | test_00001773 517 | test_00000680 518 | 52_Photographers_photographertakingphoto_52_755 519 | test_00002654 520 | test_00002306 521 | test_00002325 522 | 13_Interview_Interview_On_Location_13_433 523 | 4_Dancing_Dancing_4_253 524 | test_00001429 525 | test_00001957 526 | 40_Gymnastics_Gymnastics_40_945 527 | test_00001863 528 | test_00002134 529 | test_00003972 530 | 4_Dancing_Dancing_4_922 531 | 16_Award_Ceremony_Awards_Ceremony_16_589 532 | test_00000799 533 | test_00002035 534 | test_00004741 535 | test_00004034 536 | test_00001160 537 | test_00004735 538 | 17_Ceremony_Ceremony_17_1037 539 | test_00004633 540 | test_00002932 541 | 28_Sports_Fan_Sports_Fan_28_165 542 | 17_Ceremony_Ceremony_17_1007 543 | test_00002930 544 | test_00002349 545 | 13_Interview_Interview_On_Location_13_736 546 | 19_Couple_Couple_19_847 547 | 41_Swimming_Swimming_41_106 548 | 31_Waiter_Waitress_Waiter_Waitress_31_111 549 | test_00002580 550 | 28_Sports_Fan_Sports_Fan_28_683 551 | 49_Greeting_peoplegreeting_49_302 552 | 41_Swimming_Swimming_41_271 553 | test_00003129 554 | test_00002138 555 | test_00000194 556 | 31_Waiter_Waitress_Waiter_Waitress_31_613 557 | test_00001029 558 | 45_Balloonist_Balloonist_45_939 559 | 4_Dancing_Dancing_4_1028 560 | test_00004566 561 | 44_Aerobics_Aerobics_44_936 562 | 40_Gymnastics_Gymnastics_40_1022 563 | 49_Greeting_peoplegreeting_49_73 564 | test_00002904 565 | 51_Dresses_wearingdress_51_869 566 | test_00003065 567 | test_00002871 568 | test_00003999 569 | 20_Family_Group_Family_Group_20_33 570 | test_00000866 571 | test_00001138 572 | test_00002136 573 | test_00000915 574 | 1_Handshaking_Handshaking_1_94 575 | 39_Ice_Skating_Ice_Skating_39_283 576 | 32_Worker_Laborer_Worker_Laborer_32_512 577 | test_00004736 578 | test_00001329 579 | test_00000323 580 | test_00003932 581 | test_00000518 582 | 59_peopledrivingcar_peopledrivingcar_59_978 583 | 13_Interview_Interview_On_Location_13_773 584 | test_00001495 585 | 28_Sports_Fan_Sports_Fan_28_1018 586 | 9_Press_Conference_Press_Conference_9_757 587 | 32_Worker_Laborer_Worker_Laborer_32_434 588 | test_00003873 589 | 13_Interview_Interview_On_Location_13_247 590 | 51_Dresses_wearingdress_51_348 591 | test_00001134 592 | 20_Family_Group_Family_Group_20_387 593 | test_00002373 594 | test_00000335 595 | 38_Tennis_Tennis_38_531 596 | 1_Handshaking_Handshaking_1_465 597 | test_00002434 598 | 31_Waiter_Waitress_Waiter_Waitress_31_230 599 | test_00004688 600 | 52_Photographers_photographertakingphoto_52_456 601 | test_00003737 602 | 51_Dresses_wearingdress_51_465 603 | test_00001552 604 | 51_Dresses_wearingdress_51_183 605 | 20_Family_Group_Family_Group_20_544 606 | test_00004840 607 | test_00000621 608 | 31_Waiter_Waitress_Waiter_Waitress_31_327 609 | 51_Dresses_wearingdress_51_17 610 | 39_Ice_Skating_Ice_Skating_39_682 611 | test_00002481 612 | test_00003551 613 | test_00002992 614 | test_00002721 615 | 49_Greeting_peoplegreeting_49_124 616 | test_00002961 617 | test_00001883 618 | test_00002891 619 | 20_Family_Group_Family_Group_20_255 620 | test_00003078 621 | 20_Family_Group_Family_Group_20_579 622 | test_00002458 623 | 4_Dancing_Dancing_4_1000 624 | test_00004647 625 | 13_Interview_Interview_On_Location_13_129 626 | test_00000273 627 | test_00001344 628 | 4_Dancing_Dancing_4_517 629 | 41_Swimming_Swimmer_41_262 630 | test_00000097 631 | test_00002489 632 | test_00004614 633 | 13_Interview_Interview_Sequences_13_513 634 | 31_Waiter_Waitress_Waiter_Waitress_31_162 635 | 32_Worker_Laborer_Worker_Laborer_32_462 636 | test_00004061 637 | test_00001586 638 | test_00002377 639 | test_00000079 640 | test_00003181 641 | 13_Interview_Interview_Sequences_13_973 642 | test_00002997 643 | 9_Press_Conference_Press_Conference_9_924 644 | 45_Balloonist_Balloonist_45_974 645 | 59_peopledrivingcar_peopledrivingcar_59_704 646 | 41_Swimming_Swimmer_41_376 647 | test_00002810 648 | 4_Dancing_Dancing_4_162 649 | 31_Waiter_Waitress_Waiter_Waitress_31_34 650 | test_00003857 651 | 16_Award_Ceremony_Awards_Ceremony_16_490 652 | 38_Tennis_Tennis_38_40 653 | test_00004232 654 | 31_Waiter_Waitress_Waiter_Waitress_31_373 655 | 51_Dresses_wearingdress_51_306 656 | test_00003840 657 | 40_Gymnastics_Gymnastics_40_57 658 | 40_Gymnastics_Gymnastics_40_845 659 | 41_Swimming_Swimming_41_161 660 | test_00000649 661 | 16_Award_Ceremony_Awards_Ceremony_16_467 662 | test_00000009 663 | test_00004306 664 | test_00002872 665 | test_00002408 666 | test_00001303 667 | 30_Surgeons_Surgeons_30_696 668 | 59_peopledrivingcar_peopledrivingcar_59_85 669 | test_00003983 670 | 31_Waiter_Waitress_Waiter_Waitress_31_915 671 | test_00004423 672 | 1_Handshaking_Handshaking_1_762 673 | 13_Interview_Interview_2_People_Visible_13_204 674 | test_00001342 675 | test_00001059 676 | test_00003680 677 | 13_Interview_Interview_On_Location_13_512 678 | test_00002763 679 | 4_Dancing_Dancing_4_124 680 | test_00004286 681 | test_00003301 682 | test_00001299 683 | test_00000829 684 | 16_Award_Ceremony_Awards_Ceremony_16_309 685 | test_00002356 686 | test_00000173 687 | test_00004776 688 | test_00004605 689 | test_00000776 690 | test_00003083 691 | test_00001759 692 | test_00000102 693 | 49_Greeting_peoplegreeting_49_783 694 | 13_Interview_Interview_On_Location_13_865 695 | 38_Tennis_Tennis_38_230 696 | test_00001723 697 | 13_Interview_Interview_On_Location_13_542 698 | 41_Swimming_Swimmer_41_26 699 | 52_Photographers_photographertakingphoto_52_303 700 | test_00004885 701 | test_00004818 702 | test_00004759 703 | test_00002089 704 | test_00004108 705 | 13_Interview_Interview_On_Location_13_166 706 | 9_Press_Conference_Press_Conference_9_43 707 | test_00001404 708 | test_00002352 709 | 27_Spa_Spa_27_157 710 | 41_Swimming_Swimmer_41_401 711 | test_00000879 712 | test_00003232 713 | 52_Photographers_photographertakingphoto_52_807 714 | test_00003251 715 | 45_Balloonist_Balloonist_45_142 716 | test_00000201 717 | test_00003541 718 | 51_Dresses_wearingdress_51_327 719 | test_00004782 720 | 19_Couple_Couple_19_832 721 | test_00002395 722 | test_00001276 723 | test_00000583 724 | test_00000124 725 | test_00003772 726 | 16_Award_Ceremony_Awards_Ceremony_16_482 727 | 51_Dresses_wearingdress_51_633 728 | 51_Dresses_wearingdress_51_492 729 | 9_Press_Conference_Press_Conference_9_648 730 | test_00004288 731 | test_00000966 732 | test_00002936 733 | test_00000066 734 | 59_peopledrivingcar_peopledrivingcar_59_1038 735 | test_00001772 736 | 40_Gymnastics_Gymnastics_40_887 737 | 41_Swimming_Swimmer_41_483 738 | 9_Press_Conference_Press_Conference_9_613 739 | test_00001913 740 | test_00000648 741 | 20_Family_Group_Family_Group_20_427 742 | test_00004642 743 | 13_Interview_Interview_Sequences_13_135 744 | test_00002684 745 | 32_Worker_Laborer_Worker_Laborer_32_169 746 | 51_Dresses_wearingdress_51_386 747 | test_00000641 748 | 20_Family_Group_Family_Group_20_556 749 | 13_Interview_Interview_2_People_Visible_13_406 750 | test_00002939 751 | 9_Press_Conference_Press_Conference_9_828 752 | 32_Worker_Laborer_Worker_Laborer_32_812 753 | test_00001749 754 | test_00004531 755 | test_00003641 756 | test_00004560 757 | 41_Swimming_Swimming_41_240 758 | test_00000208 759 | test_00003832 760 | test_00001436 761 | test_00002100 762 | 19_Couple_Couple_19_317 763 | 13_Interview_Interview_2_People_Visible_13_381 764 | test_00003786 765 | test_00001730 766 | 20_Family_Group_Family_Group_20_72 767 | 40_Gymnastics_Gymnastics_40_894 768 | test_00004368 769 | test_00003765 770 | test_00002682 771 | 8_Election_Campain_Election_Campaign_8_553 772 | test_00004398 773 | test_00001962 774 | test_00001481 775 | test_00001799 776 | test_00003576 777 | test_00000929 778 | test_00004706 779 | test_00004200 780 | 20_Family_Group_Family_Group_20_272 781 | 19_Couple_Couple_19_1014 782 | test_00000237 783 | 9_Press_Conference_Press_Conference_9_196 784 | 30_Surgeons_Surgeons_30_486 785 | 16_Award_Ceremony_Awards_Ceremony_16_524 786 | test_00000801 787 | 38_Tennis_Tennis_38_131 788 | test_00002268 789 | 31_Waiter_Waitress_Waiter_Waitress_31_304 790 | 17_Ceremony_Ceremony_17_818 791 | test_00001976 792 | test_00001700 793 | 4_Dancing_Dancing_4_224 794 | 1_Handshaking_Handshaking_1_314 795 | test_00001748 796 | test_00001839 797 | 19_Couple_Couple_19_325 798 | test_00000494 799 | test_00000954 800 | test_00001277 801 | test_00002592 802 | 42_Car_Racing_Nascar_42_468 803 | 40_Gymnastics_Gymnastics_40_521 804 | 31_Waiter_Waitress_Waiter_Waitress_31_720 805 | test_00000872 806 | test_00000413 807 | 39_Ice_Skating_Ice_Skating_39_203 808 | test_00004725 809 | 19_Couple_Couple_19_548 810 | 56_Voter_peoplevoting_56_842 811 | test_00001186 812 | test_00002437 813 | 52_Photographers_taketouristphotos_52_86 814 | 47_Matador_Bullfighter_Matador_Bullfighter_47_385 815 | 52_Photographers_photographertakingphoto_52_30 816 | test_00000180 817 | 52_Photographers_taketouristphotos_52_331 818 | 13_Interview_Interview_Sequences_13_37 819 | 41_Swimming_Swimming_41_472 820 | test_00003812 821 | test_00003814 822 | test_00000219 823 | 13_Interview_Interview_On_Location_13_394 824 | test_00004078 825 | test_00000452 826 | 1_Handshaking_Handshaking_1_733 827 | test_00000555 828 | test_00001454 829 | 52_Photographers_taketouristphotos_52_661 830 | 59_peopledrivingcar_peopledrivingcar_59_229 831 | test_00002636 832 | test_00000865 833 | 4_Dancing_Dancing_4_384 834 | test_00000743 835 | 52_Photographers_photographertakingphoto_52_416 836 | 8_Election_Campain_Election_Campaign_8_300 837 | test_00001288 838 | test_00003288 839 | 13_Interview_Interview_Sequences_13_586 840 | 31_Waiter_Waitress_Waiter_Waitress_31_176 841 | test_00002552 842 | test_00002989 843 | test_00002261 844 | test_00003343 845 | 39_Ice_Skating_Ice_Skating_39_275 846 | 56_Voter_peoplevoting_56_103 847 | test_00003254 848 | test_00004194 849 | 28_Sports_Fan_Sports_Fan_28_656 850 | 38_Tennis_Tennis_38_683 851 | test_00003929 852 | 13_Interview_Interview_Sequences_13_779 853 | 41_Swimming_Swimming_41_74 854 | test_00000543 855 | test_00002795 856 | 52_Photographers_photographertakingphoto_52_358 857 | 32_Worker_Laborer_Worker_Laborer_32_494 858 | 13_Interview_Interview_Sequences_13_778 859 | test_00003194 860 | test_00004141 861 | 13_Interview_Interview_Sequences_13_3 862 | 51_Dresses_wearingdress_51_691 863 | test_00004072 864 | test_00004293 865 | test_00004916 866 | 13_Interview_Interview_On_Location_13_940 867 | test_00001124 868 | test_00000517 869 | 16_Award_Ceremony_Awards_Ceremony_16_569 870 | test_00002678 871 | test_00001289 872 | test_00000199 873 | test_00000247 874 | test_00004399 875 | test_00001027 876 | 59_peopledrivingcar_peopledrivingcar_59_401 877 | 30_Surgeons_Surgeons_30_264 878 | test_00002694 879 | 9_Press_Conference_Press_Conference_9_45 880 | 41_Swimming_Swimmer_41_399 881 | 27_Spa_Spa_27_322 882 | test_00002304 883 | test_00001656 884 | test_00003839 885 | 38_Tennis_Tennis_38_300 886 | 25_Soldier_Patrol_Soldier_Patrol_25_986 887 | 40_Gymnastics_Gymnastics_40_609 888 | 13_Interview_Interview_Sequences_13_929 889 | 30_Surgeons_Surgeons_30_862 890 | test_00001457 891 | 13_Interview_Interview_Sequences_13_937 892 | test_00000353 893 | 20_Family_Group_Family_Group_20_483 894 | 8_Election_Campain_Election_Campaign_8_69 895 | 39_Ice_Skating_iceskiing_39_121 896 | 32_Worker_Laborer_Worker_Laborer_32_204 897 | test_00001811 898 | test_00000297 899 | 52_Photographers_taketouristphotos_52_123 900 | 20_Family_Group_Family_Group_20_696 901 | 8_Election_Campain_Election_Campaign_8_113 902 | test_00000972 903 | 13_Interview_Interview_Sequences_13_477 904 | 51_Dresses_wearingdress_51_178 905 | test_00004650 906 | 31_Waiter_Waitress_Waiter_Waitress_31_517 907 | test_00001187 908 | 9_Press_Conference_Press_Conference_9_257 909 | test_00002854 910 | test_00001227 911 | 4_Dancing_Dancing_4_378 912 | test_00004845 913 | 13_Interview_Interview_2_People_Visible_13_155 914 | 32_Worker_Laborer_Worker_Laborer_32_738 915 | test_00004335 916 | test_00003141 917 | test_00002756 918 | 40_Gymnastics_Gymnastics_40_171 919 | test_00002011 920 | 9_Press_Conference_Press_Conference_9_161 921 | 56_Voter_peoplevoting_56_953 922 | test_00003382 923 | test_00003253 924 | test_00001175 925 | test_00000473 926 | test_00003784 927 | test_00002671 928 | test_00004451 929 | test_00003325 930 | test_00003542 931 | test_00000454 932 | 31_Waiter_Waitress_Waiter_Waitress_31_858 933 | test_00002599 934 | test_00002619 935 | test_00001704 936 | test_00000378 937 | test_00003225 938 | 40_Gymnastics_Gymnastics_40_488 939 | 52_Photographers_photographertakingphoto_52_359 940 | 32_Worker_Laborer_Worker_Laborer_32_786 941 | test_00001513 942 | test_00004761 943 | test_00003456 944 | test_00004217 945 | test_00000764 946 | 13_Interview_Interview_Sequences_13_609 947 | 13_Interview_Interview_Sequences_13_33 948 | test_00004460 949 | 28_Sports_Fan_Sports_Fan_28_862 950 | 52_Photographers_photographertakingphoto_52_310 951 | test_00002346 952 | 4_Dancing_Dancing_4_854 953 | test_00000281 954 | test_00002590 955 | 32_Worker_Laborer_Worker_Laborer_32_529 956 | 41_Swimming_Swimmer_41_358 957 | test_00002545 958 | test_00000313 959 | 17_Ceremony_Ceremony_17_735 960 | test_00003321 961 | test_00001743 962 | test_00001947 963 | test_00000440 964 | test_00002687 965 | 59_peopledrivingcar_peopledrivingcar_59_928 966 | test_00003217 967 | test_00000088 968 | test_00001241 969 | test_00003880 970 | 41_Swimming_Swimmer_41_170 971 | 32_Worker_Laborer_Worker_Laborer_32_624 972 | 30_Surgeons_Surgeons_30_343 973 | test_00002881 974 | test_00002150 975 | 28_Sports_Fan_Sports_Fan_28_770 976 | 28_Sports_Fan_Sports_Fan_28_663 977 | 41_Swimming_Swimmer_41_792 978 | test_00003120 979 | 9_Press_Conference_Press_Conference_9_658 980 | 38_Tennis_Tennis_38_754 981 | 28_Sports_Fan_Sports_Fan_28_557 982 | 13_Interview_Interview_2_People_Visible_13_217 983 | 41_Swimming_Swimmer_41_976 984 | test_00003045 985 | test_00002812 986 | 19_Couple_Couple_19_936 987 | test_00000679 988 | 41_Swimming_Swimmer_41_688 989 | 52_Photographers_taketouristphotos_52_328 990 | test_00004359 991 | 40_Gymnastics_Gymnastics_40_612 992 | 31_Waiter_Waitress_Waiter_Waitress_31_43 993 | 20_Family_Group_Family_Group_20_108 994 | test_00004437 995 | 51_Dresses_wearingdress_51_736 996 | 13_Interview_Interview_On_Location_13_282 997 | test_00003174 998 | 51_Dresses_wearingdress_51_1041 999 | test_00002265 1000 | test_00004883 1001 | 39_Ice_Skating_iceskiing_39_351 1002 | 20_Family_Group_Family_Group_20_109 1003 | 38_Tennis_Tennis_38_94 1004 | test_00004252 1005 | test_00002827 1006 | test_00003653 1007 | 13_Interview_Interview_On_Location_13_636 1008 | test_00004802 1009 | test_00001577 1010 | 13_Interview_Interview_On_Location_13_187 1011 | 40_Gymnastics_Gymnastics_40_805 1012 | test_00000721 1013 | test_00004863 1014 | test_00000227 1015 | 19_Couple_Couple_19_514 1016 | 44_Aerobics_Aerobics_44_1032 1017 | test_00000531 1018 | 9_Press_Conference_Press_Conference_9_252 1019 | test_00004567 1020 | 44_Aerobics_Aerobics_44_937 1021 | test_00000145 1022 | test_00001791 1023 | 38_Tennis_Tennis_38_142 1024 | test_00000113 1025 | test_00002007 1026 | 56_Voter_peoplevoting_56_323 1027 | test_00002850 1028 | test_00000609 1029 | test_00000570 1030 | test_00004752 1031 | 52_Photographers_photographertakingphoto_52_653 1032 | 52_Photographers_photographertakingphoto_52_666 1033 | test_00001177 1034 | 16_Award_Ceremony_Awards_Ceremony_16_56 1035 | 13_Interview_Interview_On_Location_13_605 1036 | test_00000738 1037 | test_00002124 1038 | test_00003452 1039 | test_00001107 1040 | test_00000537 1041 | test_00000020 1042 | 20_Family_Group_Family_Group_20_1037 1043 | test_00002737 1044 | test_00002130 1045 | 13_Interview_Interview_2_People_Visible_13_189 1046 | test_00003238 1047 | test_00003387 1048 | test_00001923 1049 | test_00001767 1050 | 30_Surgeons_Surgeons_30_988 1051 | test_00002132 1052 | test_00001248 1053 | test_00000493 1054 | 13_Interview_Interview_Sequences_13_31 1055 | test_00001936 1056 | test_00004779 1057 | 20_Family_Group_Family_Group_20_453 1058 | 20_Family_Group_Family_Group_20_1003 1059 | test_00000513 1060 | test_00004710 1061 | test_00002996 1062 | 16_Award_Ceremony_Awards_Ceremony_16_338 1063 | test_00000659 1064 | test_00002674 1065 | test_00002453 1066 | test_00001233 1067 | 31_Waiter_Waitress_Waiter_Waitress_31_276 1068 | 16_Award_Ceremony_Awards_Ceremony_16_591 1069 | 51_Dresses_wearingdress_51_512 1070 | test_00003656 1071 | 51_Dresses_wearingdress_51_113 1072 | test_00003142 1073 | 32_Worker_Laborer_Worker_Laborer_32_42 1074 | test_00002092 1075 | 28_Sports_Fan_Sports_Fan_28_868 1076 | test_00001003 1077 | test_00001086 1078 | test_00003601 1079 | test_00000030 1080 | 20_Family_Group_Family_Group_20_100 1081 | 17_Ceremony_Ceremony_17_1009 1082 | test_00004877 1083 | test_00002637 1084 | 39_Ice_Skating_Ice_Skating_39_793 1085 | test_00000826 1086 | test_00001661 1087 | test_00004036 1088 | 20_Family_Group_Family_Group_20_750 1089 | 13_Interview_Interview_Sequences_13_152 1090 | test_00004193 1091 | 52_Photographers_photographertakingphoto_52_815 1092 | 40_Gymnastics_Gymnastics_40_869 1093 | test_00002112 1094 | 31_Waiter_Waitress_Waiter_Waitress_31_225 1095 | test_00003890 1096 | 19_Couple_Couple_19_688 1097 | test_00002148 1098 | test_00004218 1099 | test_00001028 1100 | test_00002555 1101 | 1_Handshaking_Handshaking_1_158 1102 | test_00004256 1103 | test_00000711 1104 | 9_Press_Conference_Press_Conference_9_710 1105 | test_00001599 1106 | 51_Dresses_wearingdress_51_106 1107 | test_00003861 1108 | 13_Interview_Interview_On_Location_13_186 1109 | test_00004847 1110 | test_00000389 1111 | test_00004532 1112 | test_00002308 1113 | 49_Greeting_peoplegreeting_49_10 1114 | test_00004447 1115 | 1_Handshaking_Handshaking_1_343 1116 | test_00003450 1117 | 41_Swimming_Swimmer_41_471 1118 | test_00002404 1119 | test_00001547 1120 | test_00003507 1121 | 19_Couple_Couple_19_667 1122 | 41_Swimming_Swimmer_41_773 1123 | test_00001789 1124 | test_00003807 1125 | 28_Sports_Fan_Sports_Fan_28_989 1126 | test_00004395 1127 | 41_Swimming_Swimmer_41_831 1128 | test_00001360 1129 | test_00000604 1130 | 9_Press_Conference_Press_Conference_9_907 1131 | 51_Dresses_wearingdress_51_139 1132 | 39_Ice_Skating_iceskiing_39_541 1133 | 28_Sports_Fan_Sports_Fan_28_959 1134 | test_00000188 1135 | 9_Press_Conference_Press_Conference_9_114 1136 | test_00002815 1137 | test_00002415 1138 | test_00000183 1139 | test_00002846 1140 | test_00004557 1141 | test_00003124 1142 | test_00004772 1143 | 8_Election_Campain_Election_Campaign_8_451 1144 | test_00004675 1145 | test_00002276 1146 | 52_Photographers_photographertakingphoto_52_219 1147 | test_00000309 1148 | test_00003333 1149 | test_00003475 1150 | test_00000476 1151 | 8_Election_Campain_Election_Campaign_8_120 1152 | 1_Handshaking_Handshaking_1_766 1153 | 39_Ice_Skating_Ice_Skating_39_362 1154 | test_00004523 1155 | 51_Dresses_wearingdress_51_96 1156 | test_00003705 1157 | 20_Family_Group_Family_Group_20_663 1158 | test_00000976 1159 | 38_Tennis_Tennis_38_182 1160 | test_00003177 1161 | test_00001176 1162 | 51_Dresses_wearingdress_51_685 1163 | 41_Swimming_Swimmer_41_943 1164 | test_00004555 1165 | 20_Family_Group_Family_Group_20_730 1166 | 31_Waiter_Waitress_Waiter_Waitress_31_227 1167 | 38_Tennis_Tennis_38_81 1168 | test_00002564 1169 | 40_Gymnastics_Gymnastics_40_776 1170 | 27_Spa_Spa_27_656 1171 | test_00001575 1172 | 31_Waiter_Waitress_Waiter_Waitress_31_188 1173 | test_00001708 1174 | test_00003588 1175 | test_00003546 1176 | test_00004639 1177 | 4_Dancing_Dancing_4_189 1178 | test_00000528 1179 | test_00000308 1180 | test_00003277 1181 | 13_Interview_Interview_Sequences_13_884 1182 | test_00003087 1183 | test_00004935 1184 | 31_Waiter_Waitress_Waiter_Waitress_31_572 1185 | 52_Photographers_photographertakingphoto_52_428 1186 | test_00002357 1187 | test_00003574 1188 | 47_Matador_Bullfighter_matadorbullfighting_47_837 1189 | test_00004021 1190 | 1_Handshaking_Handshaking_1_380 1191 | 13_Interview_Interview_Sequences_13_636 1192 | 41_Swimming_Swimmer_41_440 1193 | 9_Press_Conference_Press_Conference_9_767 1194 | 1_Handshaking_Handshaking_1_453 1195 | test_00003423 1196 | 13_Interview_Interview_2_People_Visible_13_245 1197 | test_00004008 1198 | 20_Family_Group_Family_Group_20_849 1199 | test_00003721 1200 | test_00001576 1201 | 52_Photographers_photographertakingphoto_52_113 1202 | test_00003099 1203 | 13_Interview_Interview_On_Location_13_791 1204 | 13_Interview_Interview_On_Location_13_225 1205 | 59_peopledrivingcar_peopledrivingcar_59_27 1206 | test_00000689 1207 | test_00000857 1208 | 40_Gymnastics_Gymnastics_40_566 1209 | test_00001423 1210 | test_00001279 1211 | test_00003436 1212 | 30_Surgeons_Surgeons_30_490 1213 | test_00000974 1214 | 31_Waiter_Waitress_Waiter_Waitress_31_740 1215 | test_00003437 1216 | test_00002228 1217 | test_00004541 1218 | test_00001851 1219 | 1_Handshaking_Handshaking_1_107 1220 | test_00001067 1221 | test_00003759 1222 | 27_Spa_Spa_27_168 1223 | test_00002165 1224 | 9_Press_Conference_Press_Conference_9_105 1225 | test_00000751 1226 | test_00000490 1227 | 16_Award_Ceremony_Awards_Ceremony_16_361 1228 | test_00003817 1229 | test_00004238 1230 | test_00003449 1231 | test_00003247 1232 | test_00002101 1233 | test_00000266 1234 | test_00001810 1235 | 16_Award_Ceremony_Awards_Ceremony_16_134 1236 | test_00002869 1237 | 19_Couple_Couple_19_254 1238 | 20_Family_Group_Family_Group_20_447 1239 | 51_Dresses_wearingdress_51_612 1240 | 32_Worker_Laborer_Worker_Laborer_32_594 1241 | test_00003949 1242 | test_00001579 1243 | test_00002181 1244 | test_00002443 1245 | test_00002195 1246 | test_00003868 1247 | test_00004230 1248 | test_00000457 1249 | 20_Family_Group_Family_Group_20_62 1250 | test_00000548 1251 | test_00001672 1252 | test_00000938 1253 | test_00000179 1254 | test_00001611 1255 | test_00000189 1256 | 4_Dancing_Dancing_4_878 1257 | test_00001503 1258 | test_00001025 1259 | 31_Waiter_Waitress_Waiter_Waitress_31_818 1260 | 28_Sports_Fan_Sports_Fan_28_826 1261 | 32_Worker_Laborer_Worker_Laborer_32_44 1262 | test_00001740 1263 | 19_Couple_Couple_19_770 1264 | 52_Photographers_photographertakingphoto_52_780 1265 | test_00004000 1266 | test_00003897 1267 | test_00002217 1268 | test_00004136 1269 | test_00003922 1270 | 51_Dresses_wearingdress_51_670 1271 | test_00004320 1272 | 39_Ice_Skating_iceskiing_39_416 1273 | 39_Ice_Skating_iceskiing_39_272 1274 | 20_Family_Group_Family_Group_20_843 1275 | test_00003187 1276 | test_00001469 1277 | 51_Dresses_wearingdress_51_94 1278 | 27_Spa_Spa_27_38 1279 | 31_Waiter_Waitress_Waiter_Waitress_31_888 1280 | test_00001072 1281 | test_00001069 1282 | test_00004797 1283 | test_00002731 1284 | test_00004789 1285 | 19_Couple_Couple_19_509 1286 | 30_Surgeons_Surgeons_30_525 1287 | test_00004243 1288 | 27_Spa_Spa_27_329 1289 | test_00004821 1290 | test_00002927 1291 | 13_Interview_Interview_Sequences_13_456 1292 | 13_Interview_Interview_Sequences_13_1032 1293 | 16_Award_Ceremony_Awards_Ceremony_16_94 1294 | test_00000090 1295 | test_00003372 1296 | 16_Award_Ceremony_Awards_Ceremony_16_143 1297 | test_00004636 1298 | 28_Sports_Fan_Sports_Fan_28_835 1299 | test_00001224 1300 | test_00004155 1301 | 13_Interview_Interview_On_Location_13_74 1302 | test_00001638 1303 | test_00003397 1304 | 9_Press_Conference_Press_Conference_9_183 1305 | 20_Family_Group_Family_Group_20_411 1306 | test_00004147 1307 | test_00003347 1308 | 13_Interview_Interview_2_People_Visible_13_374 1309 | test_00000622 1310 | 40_Gymnastics_Gymnastics_40_762 1311 | test_00001075 1312 | test_00000081 1313 | test_00003753 1314 | test_00004667 1315 | test_00000054 1316 | test_00004037 1317 | 16_Award_Ceremony_Awards_Ceremony_16_447 1318 | 52_Photographers_taketouristphotos_52_3 1319 | 13_Interview_Interview_2_People_Visible_13_260 1320 | 32_Worker_Laborer_Worker_Laborer_32_860 1321 | test_00004824 1322 | test_00004577 1323 | 31_Waiter_Waitress_Waiter_Waitress_31_685 1324 | test_00002547 1325 | test_00002147 1326 | test_00004561 1327 | 13_Interview_Interview_On_Location_13_505 1328 | test_00003777 1329 | test_00000094 1330 | 16_Award_Ceremony_Awards_Ceremony_16_135 1331 | test_00003053 1332 | test_00004066 1333 | test_00000978 1334 | 28_Sports_Fan_Sports_Fan_28_124 1335 | 28_Sports_Fan_Sports_Fan_28_535 1336 | test_00001146 1337 | test_00001236 1338 | 41_Swimming_Swimming_41_275 1339 | test_00000129 1340 | 8_Election_Campain_Election_Campaign_8_269 1341 | 52_Photographers_photographertakingphoto_52_721 1342 | test_00003270 1343 | test_00001355 1344 | 13_Interview_Interview_Sequences_13_764 1345 | 56_Voter_peoplevoting_56_378 1346 | test_00002059 1347 | test_00000482 1348 | test_00002553 1349 | test_00004199 1350 | 39_Ice_Skating_Ice_Skating_39_156 1351 | 9_Press_Conference_Press_Conference_9_594 1352 | 13_Interview_Interview_On_Location_13_513 1353 | test_00001095 1354 | 13_Interview_Interview_Sequences_13_189 1355 | 19_Couple_Couple_19_125 1356 | 13_Interview_Interview_On_Location_13_190 1357 | test_00004679 1358 | 45_Balloonist_Balloonist_45_508 1359 | test_00002126 1360 | test_00001608 1361 | test_00000408 1362 | test_00001042 1363 | 13_Interview_Interview_Sequences_13_557 1364 | test_00001370 1365 | test_00004022 1366 | 9_Press_Conference_Press_Conference_9_182 1367 | 30_Surgeons_Surgeons_30_555 1368 | test_00001432 1369 | test_00002340 1370 | test_00004846 1371 | test_00002269 1372 | test_00002851 1373 | 4_Dancing_Dancing_4_21 1374 | 31_Waiter_Waitress_Waiter_Waitress_31_465 1375 | test_00003660 1376 | 41_Swimming_Swimmer_41_927 1377 | 28_Sports_Fan_Sports_Fan_28_723 1378 | 56_Voter_peoplevoting_56_796 1379 | 16_Award_Ceremony_Awards_Ceremony_16_637 1380 | 13_Interview_Interview_On_Location_13_284 1381 | test_00003865 1382 | 13_Interview_Interview_Sequences_13_121 1383 | test_00001673 1384 | test_00002797 1385 | test_00002713 1386 | test_00003769 1387 | test_00000304 1388 | 9_Press_Conference_Press_Conference_9_784 1389 | 19_Couple_Couple_19_50 1390 | test_00002673 1391 | test_00004628 1392 | test_00002253 1393 | test_00003725 1394 | 8_Election_Campain_Election_Campaign_8_25 1395 | test_00003518 1396 | 47_Matador_Bullfighter_Matador_Bullfighter_47_703 1397 | 52_Photographers_taketouristphotos_52_536 1398 | 51_Dresses_wearingdress_51_451 1399 | test_00003346 1400 | 17_Ceremony_Ceremony_17_668 1401 | test_00001113 1402 | test_00002656 1403 | test_00003069 1404 | test_00003652 1405 | 13_Interview_Interview_On_Location_13_510 1406 | test_00003899 1407 | test_00000483 1408 | test_00001125 1409 | test_00002658 1410 | test_00004322 1411 | 13_Interview_Interview_On_Location_13_559 1412 | test_00000654 1413 | test_00002037 1414 | test_00000027 1415 | test_00002068 1416 | 9_Press_Conference_Press_Conference_9_141 1417 | 51_Dresses_wearingdress_51_1031 1418 | test_00000158 1419 | test_00002663 1420 | 27_Spa_Spa_27_212 1421 | 20_Family_Group_Family_Group_20_27 1422 | test_00002024 1423 | 38_Tennis_Tennis_38_420 1424 | test_00002025 1425 | 16_Award_Ceremony_Awards_Ceremony_16_305 1426 | test_00004814 1427 | test_00004545 1428 | test_00003328 1429 | 13_Interview_Interview_On_Location_13_287 1430 | 59_peopledrivingcar_peopledrivingcar_59_1020 1431 | 13_Interview_Interview_On_Location_13_56 1432 | test_00002760 1433 | test_00002394 1434 | 38_Tennis_Tennis_38_666 1435 | test_00003028 1436 | test_00002830 1437 | 41_Swimming_Swimmer_41_610 1438 | test_00001556 1439 | test_00002355 1440 | test_00003555 1441 | test_00003599 1442 | test_00001220 1443 | 59_peopledrivingcar_peopledrivingcar_59_725 1444 | test_00002867 1445 | test_00001393 1446 | test_00000881 1447 | 30_Surgeons_Surgeons_30_482 1448 | 39_Ice_Skating_Ice_Skating_39_87 1449 | test_00000021 1450 | test_00004671 1451 | test_00000059 1452 | 9_Press_Conference_Press_Conference_9_945 1453 | 51_Dresses_wearingdress_51_536 1454 | test_00004098 1455 | 13_Interview_Interview_2_People_Visible_13_285 1456 | test_00002486 1457 | 52_Photographers_photographertakingphoto_52_568 1458 | 9_Press_Conference_Press_Conference_9_521 1459 | test_00001275 1460 | test_00004868 1461 | test_00000356 1462 | 52_Photographers_photographertakingphoto_52_228 1463 | 9_Press_Conference_Press_Conference_9_748 1464 | 19_Couple_Couple_19_631 1465 | 4_Dancing_Dancing_4_53 1466 | 32_Worker_Laborer_Worker_Laborer_32_788 1467 | test_00001297 1468 | test_00001815 1469 | test_00004345 1470 | test_00004113 1471 | test_00004760 1472 | test_00004768 1473 | test_00000653 1474 | test_00003528 1475 | 40_Gymnastics_Gymnastics_40_596 1476 | 13_Interview_Interview_Sequences_13_40 1477 | test_00002620 1478 | 28_Sports_Fan_Sports_Fan_28_877 1479 | test_00003070 1480 | 1_Handshaking_Handshaking_1_411 1481 | 28_Sports_Fan_Sports_Fan_28_144 1482 | test_00003162 1483 | test_00003461 1484 | 19_Couple_Couple_19_110 1485 | test_00004178 1486 | test_00004305 1487 | test_00002719 1488 | test_00003875 1489 | test_00001509 1490 | 16_Award_Ceremony_Awards_Ceremony_16_64 1491 | test_00003169 1492 | test_00004799 1493 | test_00004231 1494 | 31_Waiter_Waitress_Waiter_Waitress_31_220 1495 | test_00004704 1496 | test_00004383 1497 | test_00003577 1498 | 32_Worker_Laborer_Worker_Laborer_32_530 1499 | test_00001587 1500 | test_00003664 1501 | 38_Tennis_Tennis_38_18 1502 | test_00002043 1503 | test_00004899 1504 | test_00004615 1505 | 28_Sports_Fan_Sports_Fan_28_792 1506 | test_00003210 1507 | 52_Photographers_photographertakingphoto_52_701 1508 | test_00002077 1509 | 41_Swimming_Swimmer_41_931 1510 | test_00000295 1511 | test_00003477 1512 | test_00002469 1513 | 31_Waiter_Waitress_Waiter_Waitress_31_93 1514 | 41_Swimming_Swimmer_41_488 1515 | test_00004493 1516 | test_00001859 1517 | test_00001655 1518 | test_00001654 1519 | test_00000302 1520 | test_00000580 1521 | test_00004751 1522 | test_00003872 1523 | test_00000853 1524 | 51_Dresses_wearingdress_51_672 1525 | test_00000755 1526 | 1_Handshaking_Handshaking_1_781 1527 | test_00004544 1528 | test_00003135 1529 | 49_Greeting_peoplegreeting_49_589 1530 | test_00001036 1531 | test_00003285 1532 | 19_Couple_Couple_19_835 1533 | 13_Interview_Interview_On_Location_13_179 1534 | test_00001262 1535 | 30_Surgeons_Surgeons_30_722 1536 | 51_Dresses_wearingdress_51_377 1537 | test_00001829 1538 | test_00002156 1539 | 1_Handshaking_Handshaking_1_362 1540 | test_00004841 1541 | test_00001895 1542 | 9_Press_Conference_Press_Conference_9_693 1543 | 27_Spa_Spa_27_768 1544 | 42_Car_Racing_Nascar_42_900 1545 | test_00003874 1546 | 41_Swimming_Swimming_41_380 1547 | test_00000985 1548 | test_00004082 1549 | test_00004392 1550 | 9_Press_Conference_Press_Conference_9_849 1551 | 27_Spa_Spa_27_691 1552 | test_00003891 1553 | 51_Dresses_wearingdress_51_398 1554 | 47_Matador_Bullfighter_Matador_Bullfighter_47_617 1555 | 31_Waiter_Waitress_Waiter_Waitress_31_21 1556 | test_00002277 1557 | test_00004844 1558 | test_00001458 1559 | test_00001245 1560 | test_00001795 1561 | 51_Dresses_wearingdress_51_105 1562 | test_00000099 1563 | test_00002028 1564 | test_00002584 1565 | test_00002419 1566 | test_00001373 1567 | 13_Interview_Interview_Sequences_13_108 1568 | 38_Tennis_Tennis_38_604 1569 | 41_Swimming_Swimmer_41_507 1570 | 39_Ice_Skating_Ice_Skating_39_440 1571 | test_00000807 1572 | 41_Swimming_Swimmer_41_935 1573 | 28_Sports_Fan_Sports_Fan_28_866 1574 | 31_Waiter_Waitress_Waiter_Waitress_31_195 1575 | test_00003831 1576 | test_00002203 1577 | 51_Dresses_wearingdress_51_233 1578 | 9_Press_Conference_Press_Conference_9_258 1579 | test_00004511 1580 | 1_Handshaking_Handshaking_1_35 1581 | test_00003642 1582 | test_00002991 1583 | test_00001403 1584 | test_00002478 1585 | 13_Interview_Interview_On_Location_13_569 1586 | 49_Greeting_peoplegreeting_49_140 1587 | 45_Balloonist_Balloonist_45_211 1588 | 31_Waiter_Waitress_Waiter_Waitress_31_118 1589 | test_00002062 1590 | test_00001931 1591 | 59_peopledrivingcar_peopledrivingcar_59_201 1592 | test_00002633 1593 | 32_Worker_Laborer_Worker_Laborer_32_468 1594 | test_00002885 1595 | test_00002435 1596 | test_00001468 1597 | test_00001119 1598 | test_00002506 1599 | 39_Ice_Skating_Ice_Skating_39_200 1600 | test_00000870 1601 | 16_Award_Ceremony_Awards_Ceremony_16_59 1602 | test_00001553 1603 | 47_Matador_Bullfighter_matadorbullfighting_47_641 1604 | 41_Swimming_Swimmer_41_1028 1605 | test_00003596 1606 | test_00004255 1607 | test_00004656 1608 | 27_Spa_Spa_27_782 1609 | 59_peopledrivingcar_peopledrivingcar_59_763 1610 | 41_Swimming_Swimmer_41_369 1611 | test_00002569 1612 | test_00001274 1613 | test_00001326 1614 | test_00004129 1615 | test_00004849 1616 | test_00004229 1617 | 51_Dresses_wearingdress_51_883 1618 | 32_Worker_Laborer_Worker_Laborer_32_90 1619 | 16_Award_Ceremony_Awards_Ceremony_16_392 1620 | test_00002396 1621 | test_00001710 1622 | test_00000646 1623 | test_00003578 1624 | test_00001590 1625 | test_00001049 1626 | 38_Tennis_Tennis_38_758 1627 | 4_Dancing_Dancing_4_375 1628 | test_00002209 1629 | 28_Sports_Fan_Sports_Fan_28_22 1630 | 9_Press_Conference_Press_Conference_9_872 1631 | 4_Dancing_Dancing_4_1029 1632 | test_00001846 1633 | 40_Gymnastics_Gymnastics_40_950 1634 | test_00001945 1635 | test_00002143 1636 | test_00001626 1637 | 32_Worker_Laborer_Worker_Laborer_32_101 1638 | 16_Award_Ceremony_Awards_Ceremony_16_270 1639 | 40_Gymnastics_Gymnastics_40_484 1640 | test_00004347 1641 | test_00001073 1642 | test_00001544 1643 | test_00000882 1644 | test_00003770 1645 | 41_Swimming_Swimming_41_283 1646 | 39_Ice_Skating_Ice_Skating_39_163 1647 | 51_Dresses_wearingdress_51_13 1648 | 20_Family_Group_Family_Group_20_22 1649 | test_00004033 1650 | test_00001641 1651 | test_00001157 1652 | test_00001663 1653 | 28_Sports_Fan_Sports_Fan_28_507 1654 | test_00003927 1655 | 9_Press_Conference_Press_Conference_9_34 1656 | test_00002560 1657 | 16_Award_Ceremony_Awards_Ceremony_16_750 1658 | test_00003084 1659 | test_00001817 1660 | test_00002409 1661 | test_00001489 1662 | test_00001528 1663 | 38_Tennis_Tennis_38_558 1664 | test_00001215 1665 | test_00000213 1666 | 9_Press_Conference_Press_Conference_9_278 1667 | 9_Press_Conference_Press_Conference_9_563 1668 | test_00000499 1669 | 47_Matador_Bullfighter_Matador_Bullfighter_47_583 1670 | test_00000827 1671 | test_00004440 1672 | test_00004384 1673 | 13_Interview_Interview_Sequences_13_270 1674 | 25_Soldier_Patrol_Soldier_Patrol_25_683 1675 | 16_Award_Ceremony_Awards_Ceremony_16_346 1676 | 41_Swimming_Swimming_41_466 1677 | test_00000965 1678 | test_00004397 1679 | test_00002944 1680 | test_00000779 1681 | 32_Worker_Laborer_Worker_Laborer_32_209 1682 | test_00002169 1683 | 13_Interview_Interview_Sequences_13_759 1684 | 51_Dresses_wearingdress_51_689 1685 | test_00002743 1686 | test_00004851 1687 | test_00003489 1688 | 49_Greeting_peoplegreeting_49_203 1689 | 49_Greeting_peoplegreeting_49_59 1690 | 51_Dresses_wearingdress_51_692 1691 | 16_Award_Ceremony_Awards_Ceremony_16_495 1692 | test_00002755 1693 | 32_Worker_Laborer_Worker_Laborer_32_408 1694 | test_00000441 1695 | test_00000892 1696 | 32_Worker_Laborer_Worker_Laborer_32_1038 1697 | test_00000614 1698 | 49_Greeting_peoplegreeting_49_98 1699 | 38_Tennis_Tennis_38_319 1700 | 13_Interview_Interview_2_People_Visible_13_36 1701 | test_00004670 1702 | test_00000299 1703 | test_00001035 1704 | 4_Dancing_Dancing_4_494 1705 | test_00000551 1706 | test_00003097 1707 | 39_Ice_Skating_iceskiing_39_777 1708 | test_00004314 1709 | 13_Interview_Interview_On_Location_13_847 1710 | 32_Worker_Laborer_Worker_Laborer_32_1039 1711 | 31_Waiter_Waitress_Waiter_Waitress_31_927 1712 | 1_Handshaking_Handshaking_1_522 1713 | test_00000868 1714 | 19_Couple_Couple_19_301 1715 | 19_Couple_Couple_19_822 1716 | 31_Waiter_Waitress_Waiter_Waitress_31_769 1717 | test_00000154 1718 | 13_Interview_Interview_On_Location_13_478 1719 | test_00004542 1720 | test_00000327 1721 | test_00000306 1722 | test_00001798 1723 | test_00000261 1724 | test_00001842 1725 | 51_Dresses_wearingdress_51_221 1726 | 20_Family_Group_Family_Group_20_360 1727 | test_00002634 1728 | test_00003355 1729 | 59_peopledrivingcar_peopledrivingcar_59_906 1730 | test_00003689 1731 | 41_Swimming_Swimming_41_535 1732 | test_00002924 1733 | test_00002055 1734 | test_00002643 1735 | test_00004720 1736 | test_00000404 1737 | test_00003604 1738 | 9_Press_Conference_Press_Conference_9_632 1739 | 20_Family_Group_Family_Group_20_648 1740 | 20_Family_Group_Family_Group_20_101 1741 | test_00001076 1742 | 4_Dancing_Dancing_4_915 1743 | test_00001776 1744 | test_00001804 1745 | 20_Family_Group_Family_Group_20_227 1746 | test_00003435 1747 | 51_Dresses_wearingdress_51_741 1748 | test_00002574 1749 | 59_peopledrivingcar_peopledrivingcar_59_202 1750 | 20_Family_Group_Family_Group_20_193 1751 | test_00003233 1752 | 13_Interview_Interview_Sequences_13_691 1753 | test_00001415 1754 | 13_Interview_Interview_Sequences_13_134 1755 | test_00002219 1756 | test_00004353 1757 | 1_Handshaking_Handshaking_1_457 1758 | 30_Surgeons_Surgeons_30_819 1759 | test_00001144 1760 | test_00000883 1761 | test_00003593 1762 | 31_Waiter_Waitress_Waiter_Waitress_31_267 1763 | test_00004889 1764 | 40_Gymnastics_Gymnastics_40_197 1765 | 1_Handshaking_Handshaking_1_313 1766 | test_00004145 1767 | test_00004282 1768 | 9_Press_Conference_Press_Conference_9_930 1769 | test_00000817 1770 | 13_Interview_Interview_On_Location_13_728 1771 | test_00003882 1772 | test_00000714 1773 | test_00002642 1774 | test_00001135 1775 | 8_Election_Campain_Election_Campaign_8_529 1776 | test_00001286 1777 | test_00003013 1778 | 28_Sports_Fan_Sports_Fan_28_782 1779 | test_00000647 1780 | 40_Gymnastics_Gymnastics_40_1044 1781 | 20_Family_Group_Family_Group_20_318 1782 | 52_Photographers_photographertakingphoto_52_479 1783 | test_00003047 1784 | test_00000891 1785 | 32_Worker_Laborer_Worker_Laborer_32_870 1786 | test_00001150 1787 | test_00004391 1788 | 4_Dancing_Dancing_4_885 1789 | test_00002766 1790 | 30_Surgeons_Surgeons_30_160 1791 | test_00000927 1792 | 51_Dresses_wearingdress_51_588 1793 | 51_Dresses_wearingdress_51_610 1794 | 41_Swimming_Swimmer_41_711 1795 | 52_Photographers_taketouristphotos_52_15 1796 | test_00004225 1797 | test_00000221 1798 | 9_Press_Conference_Press_Conference_9_615 1799 | test_00002999 1800 | test_00002899 1801 | test_00002874 1802 | test_00001571 1803 | 31_Waiter_Waitress_Waiter_Waitress_31_842 1804 | 31_Waiter_Waitress_Waiter_Waitress_31_214 1805 | 25_Soldier_Patrol_Soldier_Patrol_25_640 1806 | test_00001887 1807 | 20_Family_Group_Family_Group_20_739 1808 | 42_Car_Racing_Nascar_42_911 1809 | test_00000246 1810 | test_00001402 1811 | 40_Gymnastics_Gymnastics_40_783 1812 | test_00002122 1813 | test_00000636 1814 | test_00003344 1815 | test_00004812 1816 | test_00002953 1817 | test_00000206 1818 | test_00000077 1819 | test_00000196 1820 | test_00001951 1821 | test_00001145 1822 | test_00001460 1823 | 39_Ice_Skating_iceskiing_39_817 1824 | test_00004312 1825 | 8_Election_Campain_Election_Campaign_8_266 1826 | test_00003647 1827 | test_00004517 1828 | test_00003662 1829 | 9_Press_Conference_Press_Conference_9_129 1830 | test_00002461 1831 | 13_Interview_Interview_On_Location_13_912 1832 | 41_Swimming_Swimming_41_580 1833 | test_00004866 1834 | 31_Waiter_Waitress_Waiter_Waitress_31_410 1835 | 16_Award_Ceremony_Awards_Ceremony_16_752 1836 | 13_Interview_Interview_2_People_Visible_13_409 1837 | 8_Election_Campain_Election_Campaign_8_620 1838 | test_00000089 1839 | test_00004244 1840 | -------------------------------------------------------------------------------- /Maskdata/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/fd66b0f1a5a71ee3265c3347740d6fbf3ed7000a/Maskdata/README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Face mask detection 2 | 3 | 4 | This model is a lightweight face mask detection model. Based on ssd,the backbone is Mobilenet and RFB. 5 | 6 | 7 | ## Key Features 8 | 9 | - [x] Tensorflow 2.1 10 | - [x] Trainging and Inference 11 | - [x] Precision with mAP 12 | - [x] Eager mode training with `tf.GradientTape` 13 | - [x] Network function with `tf.keras` 14 | - [x] Dataset prepocessing with `tf.data.TFRecordDataset` 15 | 16 | ```bash 17 | ├── assets 18 | │ ├── 1_Handshaking_Handshaking_1_71.jpg 19 | │ ├── out_1_Handshaking_Handshaking_1_71.jpg 20 | │ ├── out_test_00002330.jpg 21 | │ └── test_00002330.jpg 22 | ├── checkpoints 23 | │ └── weights_epoch_100.h5 24 | ├── components 25 | │ ├── config.py 26 | │ ├── __init__.py 27 | │ ├── kmeans.py 28 | │ ├── prior_box.py 29 | │ └── utils.py 30 | ├── dataset 31 | │ ├── check_dataset.py 32 | │ ├── tf_dataset_preprocess.py 33 | │ ├── train_mask.tfrecord 34 | │ ├── trainval_mask.tfrecord 35 | │ ├── val_mask.tfrecord 36 | │ ├── voc_to_tfrecord.py 37 | ├── inference.py 38 | ├── logs 39 | │ └── train 40 | ├── mAP 41 | │ ├── compute_mAP.py 42 | │ ├── detection-results 43 | │ ├── detect.py 44 | │ ├── ground-truth 45 | │ ├── __init__.py 46 | │ ├── map-results 47 | │ └── README.md 48 | ├── Maskdata 49 | │ ├── Annotations 50 | │ ├── ImageSets 51 | │   └── Main 52 | │ │   ├── train.txt 53 | │ │   ├── trainval.txt 54 | │ │   └── val.txt 55 | │ └── JPEGImages 56 | ├── network 57 | │ ├── __init__.py 58 | │ ├── losses.py 59 | │ ├── model.py 60 | │ ├── net.py 61 | │ ├── network.py 62 | ├── README.md 63 | └── train.py 64 | └── requirements.txt 65 | ``` 66 | 67 | ## Usage 68 | 69 | ### Installation 70 | 71 | Create a new python virtual environment by [Anaconda](https://www.anaconda.com/) ,`pip install -r requirements.txt` 72 | 73 | ### Data Preparing 74 | 75 | 1. Face Mask Data 76 | 77 | Source data from [**AIZOOTech**](https://github.com/AIZOOTech/FaceMaskDetection) , which is a great job. 78 | 79 | I checked and corrected some error to apply my own training network according to the voc dataset format. 80 | You can download it here: 81 | + [Baidu](https://pan.baidu.com/s/1nc-cBNAqPIYYAmtGSFUSmg) code:44pl 82 | + [GoogleDrive](https://drive.google.com/open?id=1KVoipQ-VsmF3FDil2QiZgJD1_Nnf02XW) 83 | 84 | 2. Data Processing 85 | 86 | + Download the mask data images 87 | 88 | + Convert the training images and annotations to tfrecord file with the the script bellow. 89 | 90 | ```bash 91 | python dataset/voc_to_tfrecord.py --dataset_path Maskdata/ --output_file dataset/train_mask.tfrecord --split train 92 | ``` 93 | 94 | you can change the --split parameters to 'val' to get the validation tfrecord, Please modify the inside setting `voc_to_tfrecord.py` for different situations. 95 | 96 | 3. Check tfrecord dataloader by run `python dataset/check_dataset.py` . 97 | 98 | ### Training 99 | 100 | 1. Modify your configuration in `components/config.py`. 101 | 102 | You can get the anchors by run `python components/kmeans.py` 103 | 104 | 2. Train the model by run `python train.py` . 105 | 106 | ### Inference 107 | 108 | + Run on video 109 | 110 | ```bash 111 | python inference.py --model_path checkpoints/ --camera True 112 | or 113 | python inference.py --model_path checkpoints/*.h5 --camera True 114 | ``` 115 | 116 | + Detect on Image 117 | 118 | ```bash 119 | python inference.py --model_path checkpoints/ --img_path assert/1_Handshaking_Handshaking_1_71.jpg 120 | ``` 121 | 122 | ![](https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/master/assets/out_test_00002330.jpg) 123 | 124 | ![](https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/master/assets/out_1_Handshaking_Handshaking_1_71.jpg) 125 | 126 | 127 | ### mAP 128 | 129 | + Convert xml to txt file on `mAP/ground truth`, predicting the bbox and class on `mAP/detection-results`. 130 | 131 | ```bash 132 | python mAP/detect.py --model_path checkpoints/ --dataset_path Maskdata/ --split val 133 | 134 | python mAP/compute_mAP.py 135 | ``` 136 | # For K210 project: 137 | something refer to [k210-camera-project](https://github.com/PureHing/k210-camera-project). 138 | # Reference 139 | + mAP code: [https://github.com/Cartucho/mAP](https://github.com/Cartucho/mAP) 140 | + SSD-Tensorflow: [https://github.com/balancap/SSD-Tensorflow](https://github.com/balancap/SSD-Tensorflow/) 141 | + ssd-tf2: [https://github.com/ChunML/ssd-tf2](https://github.com/ChunML/ssd-tf2) 142 | -------------------------------------------------------------------------------- /assets/1_Handshaking_Handshaking_1_71.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/fd66b0f1a5a71ee3265c3347740d6fbf3ed7000a/assets/1_Handshaking_Handshaking_1_71.jpg -------------------------------------------------------------------------------- /assets/out_1_Handshaking_Handshaking_1_71.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/fd66b0f1a5a71ee3265c3347740d6fbf3ed7000a/assets/out_1_Handshaking_Handshaking_1_71.jpg -------------------------------------------------------------------------------- /assets/out_test_00002330.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/fd66b0f1a5a71ee3265c3347740d6fbf3ed7000a/assets/out_test_00002330.jpg -------------------------------------------------------------------------------- /assets/test_00002330.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/fd66b0f1a5a71ee3265c3347740d6fbf3ed7000a/assets/test_00002330.jpg -------------------------------------------------------------------------------- /checkpoints/weights_epoch_100.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PureHing/face-mask-detection-tf2/fd66b0f1a5a71ee3265c3347740d6fbf3ed7000a/checkpoints/weights_epoch_100.h5 -------------------------------------------------------------------------------- /components/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/24 3 | # @File : __init__.py.py 4 | # @Software: PyCharm -------------------------------------------------------------------------------- /components/config.py: -------------------------------------------------------------------------------- 1 | cfg = { 2 | # general setting 3 | "batch_size": 32, 4 | "input_size": (240, 320), # (h,w) 5 | 6 | # training dataset 7 | "dataset_path": 'dataset/train_mask.tfrecord', # 'dataset/trainval_mask.tfrecord' 8 | "val_path": 'dataset/val_mask.tfrecord', # 9 | "dataset_len": 6115, # train 6115 , trainval 7954, number of training samples 10 | "val_len": 1839, 11 | "using_crop": True, 12 | "using_bin": True, 13 | "using_flip": True, 14 | "using_distort": True, 15 | "using_normalizing": True, 16 | "labels_list": ['background', 'mask', 'unmask'], # xml annotation 17 | 18 | # anchor setting 19 | # "min_sizes": [[(9, 7), (24, 20), (39, 35)], [(54, 41), (65, 61), (81, 66)], 20 | # [(94, 86), (113, 95), (131, 122)], [(137, 128), (172, 162), (176, 210)]], 21 | "min_sizes":[[10, 16, 24], [32, 48], [64, 96], [128, 192, 256]], 22 | "steps": [8, 16, 32, 64], 23 | "match_thresh": 0.45, 24 | "variances": [0.1, 0.2], 25 | "clip": False, 26 | 27 | # network 28 | "base_channel": 16, 29 | 30 | # training setting 31 | "resume": False, # if False,training from scratch 32 | "epoch": 100, 33 | "init_lr": 1e-2, 34 | "lr_decay_epoch": [50, 70], 35 | "lr_rate": 0.1, 36 | "warmup_epoch": 5, 37 | "min_lr": 1e-4, 38 | 39 | "weights_decay": 5e-4, 40 | "momentum": 0.9, 41 | "save_freq": 1, #frequency of save model weights 42 | 43 | # inference 44 | "score_threshold": 0.5, 45 | "nms_threshold": 0.4, 46 | "max_number_keep": 200 47 | } 48 | -------------------------------------------------------------------------------- /components/kmeans.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | 4 | from xml.etree import ElementTree 5 | 6 | from lxml import etree 7 | import numpy as np 8 | import os 9 | import sys 10 | import matplotlib.pyplot as plt 11 | from sklearn.cluster import KMeans 12 | 13 | # rootPath = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0] 14 | # sys.path.append(rootPath) 15 | # print(rootPath) 16 | XML_EXT = '.xml' 17 | ENCODE_METHOD = 'utf-8' 18 | 19 | 20 | # pascalVocReader readers the voc xml files parse it 21 | class PascalVocReader: 22 | """ 23 | this class will be used to get transfered width and height from voc xml files 24 | """ 25 | 26 | def __init__(self, filepath, width, height): 27 | # shapes type: 28 | # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] 29 | self.shapes = [] 30 | self.filepath = filepath 31 | self.verified = False 32 | self.width = width 33 | self.height = height 34 | 35 | try: 36 | self.parseXML() 37 | except: 38 | pass 39 | 40 | def getShapes(self): 41 | return self.shapes 42 | 43 | def addShape(self, bndbox, width, height): 44 | xmin = int(bndbox.find('xmin').text) 45 | ymin = int(bndbox.find('ymin').text) 46 | xmax = int(bndbox.find('xmax').text) 47 | ymax = int(bndbox.find('ymax').text) 48 | width_trans = (xmax - xmin) / width *self.width 49 | height_trans = (ymax - ymin) / height*self.height 50 | points = [width_trans, height_trans] 51 | self.shapes.append((points)) 52 | 53 | def parseXML(self): 54 | assert self.filepath.endswith(XML_EXT), "Unsupport file format" 55 | parser = etree.XMLParser(encoding=ENCODE_METHOD) 56 | xmltree = ElementTree.parse(self.filepath, parser=parser).getroot() 57 | pic_size = xmltree.find('size') 58 | size = (int(pic_size.find('width').text), int(pic_size.find('height').text)) 59 | for object_iter in xmltree.findall('object'): 60 | bndbox = object_iter.find("bndbox") 61 | self.addShape(bndbox, *size) 62 | return True 63 | 64 | 65 | class create_w_h_txt: 66 | def __init__(self, vocxml_path, txt_path,IMAGE_W,IMAGE_H): 67 | 68 | self.voc_path = vocxml_path 69 | self.txt_path = txt_path 70 | self.image_w=IMAGE_W 71 | self.image_h=IMAGE_H 72 | # print(self.voc_path,self.txt_path) 73 | 74 | def _gether_w_h(self): 75 | pass 76 | 77 | def _write_to_txt(self): 78 | pass 79 | 80 | def process_file(self): 81 | file_w = open(self.txt_path, 'a') 82 | # print (self.txt_path) 83 | for file in os.listdir(self.voc_path): 84 | file_path = os.path.join(self.voc_path, file) 85 | xml_parse = PascalVocReader(file_path, self.image_w,self.image_h) 86 | data = xml_parse.getShapes() 87 | for w, h in data: 88 | txtstr = str(w) + ' ' + str(h) + '\n' 89 | # print (txtstr) 90 | file_w.write(txtstr) 91 | file_w.close() 92 | 93 | 94 | class kMean_parse: 95 | def __init__(self, path_txt, k): 96 | self.path = path_txt 97 | self.km = KMeans(n_clusters=k, init="k-means++", n_init=10, max_iter=3000000, tol=1e-5, random_state=0) 98 | self._load_data() 99 | 100 | def _load_data(self): 101 | self.data = np.loadtxt(self.path) 102 | 103 | def _iou(self, box, clusters): 104 | """ 105 | Calculates the Intersection over Union (IoU) between a box and k clusters. 106 | :param box: tuple or array, shifted to the origin (i. e. width and height) 107 | :param clusters: numpy array of shape (k, 2) where k is the number of clusters 108 | :return: numpy array of shape (k, 0) where k is the number of clusters 109 | """ 110 | x = np.minimum(clusters[:, 0], box[0]) 111 | y = np.minimum(clusters[:, 1], box[1]) 112 | if np.count_nonzero(x == 0) > 0 or np.count_nonzero(y == 0) > 0: 113 | raise ValueError("Box has no area") 114 | 115 | intersection = x * y 116 | box_area = box[0] * box[1] 117 | cluster_area = clusters[:, 0] * clusters[:, 1] 118 | 119 | iou_ = intersection / (box_area + cluster_area - intersection) 120 | 121 | return iou_ 122 | 123 | def _avg_iou(self, boxes, clusters): 124 | """ 125 | Calculates the average Intersection over Union (IoU) between a numpy array of boxes and k clusters. 126 | :param boxes: numpy array of shape (r, 2), where r is the number of rows 127 | :param clusters: numpy array of shape (k, 2) where k is the number of clusters 128 | :return: average IoU as a single float 129 | """ 130 | return np.mean([np.max(self._iou(boxes[i], clusters)) for i in range(boxes.shape[0])]) 131 | 132 | def parse_data(self): 133 | self.y_k = self.km.fit_predict(self.data) 134 | sort_arr = np.sort(self.km.cluster_centers_, axis=0) 135 | acc = self._avg_iou(self.data, sort_arr) * 100 136 | sort_arr[:, [0, 1]] = sort_arr[:, [1, 0]] 137 | filename = 'priors_h_w_anchors.txt' 138 | np.savetxt(filename, sort_arr, fmt='%d', delimiter=',', newline=' , ') 139 | print(f'Accuracy: {acc:.2f}% , Anchors file save into {os.getcwd()+"/"+filename} ') 140 | 141 | def plot_data(self): 142 | plt.scatter(self.data[self.y_k == 0, 0], self.data[self.y_k == 0, 1], s=50, c="orange", marker="o", 143 | label="cluster 1") 144 | plt.scatter(self.data[self.y_k == 1, 0], self.data[self.y_k == 1, 1], s=50, c="green", marker="s", 145 | label="cluster 2") 146 | plt.scatter(self.data[self.y_k == 2, 0], self.data[self.y_k == 2, 1], s=50, c="blue", marker="^", 147 | label="cluster 3") 148 | plt.scatter(self.data[self.y_k == 3, 0], self.data[self.y_k == 3, 1], s=50, c="gray", marker="*", 149 | label="cluster 4") 150 | plt.scatter(self.data[self.y_k == 4, 0], self.data[self.y_k == 4, 1], s=50, c="yellow", marker="d", 151 | label="cluster 5") 152 | # draw the centers 153 | plt.scatter(self.km.cluster_centers_[:, 0], self.km.cluster_centers_[:, 1], s=250, marker="*", c="red", 154 | label="cluster center") 155 | plt.legend() 156 | plt.grid() 157 | plt.show() 158 | 159 | 160 | if __name__ == '__main__': 161 | 162 | xml_path = "Maskdata/Annotations" 163 | 164 | whtxt = create_w_h_txt(xml_path, 'data.txt',320,240) 165 | whtxt.process_file() 166 | kmean_parse = kMean_parse('data.txt', k=16) 167 | kmean_parse.parse_data() 168 | kmean_parse.plot_data() 169 | -------------------------------------------------------------------------------- /components/lr_scheduler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/4/20 3 | # @File : lr_scheduler.py 4 | # @Software: PyCharm 5 | import tensorflow as tf 6 | 7 | 8 | 9 | def MultiStepLR(initial_learning_rate, lr_steps, lr_rate, name='MultiStepLR'): 10 | """Multi-steps learning rate scheduler.""" 11 | lr_steps_value = [initial_learning_rate] 12 | for _ in range(len(lr_steps)): 13 | lr_steps_value.append(lr_steps_value[-1] * lr_rate) 14 | return tf.keras.optimizers.schedules.PiecewiseConstantDecay( 15 | boundaries=lr_steps, values=lr_steps_value) 16 | 17 | 18 | def MultiStepWarmUpLR(initial_learning_rate, lr_steps, lr_rate, 19 | warmup_steps=0., min_lr=0., 20 | name='MultiStepWarmUpLR'): 21 | """Multi-steps warm up learning rate scheduler.""" 22 | assert warmup_steps <= lr_steps[0] 23 | assert min_lr <= initial_learning_rate 24 | lr_steps_value = [initial_learning_rate] 25 | for _ in range(len(lr_steps)): 26 | lr_steps_value.append(lr_steps_value[-1] * lr_rate) 27 | return PiecewiseConstantWarmUpDecay( 28 | boundaries=lr_steps, values=lr_steps_value, warmup_steps=warmup_steps, 29 | min_lr=min_lr) 30 | 31 | 32 | def CosineAnnealingLR_Restart(initial_learning_rate, t_period, lr_min): 33 | """Cosine annealing learning rate scheduler with restart.""" 34 | return tf.keras.experimental.CosineDecayRestarts( 35 | initial_learning_rate=initial_learning_rate, 36 | first_decay_steps=t_period, t_mul=1.0, m_mul=1.0, 37 | alpha=lr_min / initial_learning_rate) 38 | 39 | 40 | class PiecewiseConstantWarmUpDecay( 41 | tf.keras.optimizers.schedules.LearningRateSchedule): 42 | """A LearningRateSchedule wiht warm up schedule. 43 | Modified from tf.keras.optimizers.schedules.PiecewiseConstantDecay""" 44 | 45 | def __init__(self, boundaries, values, warmup_steps, min_lr, 46 | name=None): 47 | super(PiecewiseConstantWarmUpDecay, self).__init__() 48 | 49 | if len(boundaries) != len(values) - 1: 50 | raise ValueError( 51 | "The length of boundaries should be 1 less than the" 52 | "length of values") 53 | 54 | self.boundaries = boundaries 55 | self.values = values 56 | self.name = name 57 | self.warmup_steps = warmup_steps 58 | self.min_lr = min_lr 59 | 60 | def __call__(self, step): 61 | with tf.name_scope(self.name or "PiecewiseConstantWarmUp"): 62 | step = tf.cast(tf.convert_to_tensor(step), tf.float32) 63 | pred_fn_pairs = [] 64 | warmup_steps = self.warmup_steps 65 | boundaries = self.boundaries 66 | values = self.values 67 | min_lr = self.min_lr 68 | 69 | pred_fn_pairs.append( 70 | (step <= warmup_steps, 71 | lambda: min_lr + step * (values[0] - min_lr) / warmup_steps)) 72 | pred_fn_pairs.append( 73 | (tf.logical_and(step <= boundaries[0], 74 | step > warmup_steps), 75 | lambda: tf.constant(values[0]))) 76 | pred_fn_pairs.append( 77 | (step > boundaries[-1], lambda: tf.constant(values[-1]))) 78 | 79 | for low, high, v in zip(boundaries[:-1], boundaries[1:], 80 | values[1:-1]): 81 | # Need to bind v here; can do this with lambda v=v: ... 82 | pred = (step > low) & (step <= high) 83 | pred_fn_pairs.append((pred, lambda: tf.constant(v))) 84 | 85 | # The default isn't needed here because our conditions are mutually 86 | # exclusive and exhaustive, but tf.case requires it. 87 | return tf.case(pred_fn_pairs, lambda: tf.constant(values[0]), 88 | exclusive=True) 89 | 90 | def get_config(self): 91 | return { 92 | "boundaries": self.boundaries, 93 | "values": self.values, 94 | "warmup_steps": self.warmup_steps, 95 | "min_lr": self.min_lr, 96 | "name": self.name 97 | } -------------------------------------------------------------------------------- /components/prior_box.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import math,sys 5 | import numpy as np 6 | from itertools import product as product 7 | 8 | 9 | def priors_box(cfg,image_sizes=None): 10 | """prior box""" 11 | if image_sizes is None: 12 | image_sizes = cfg['input_size'] 13 | min_sizes=cfg["min_sizes"] 14 | steps=cfg["steps"] 15 | clip=cfg["clip"] 16 | 17 | if isinstance(image_sizes, int): 18 | image_sizes = (image_sizes, image_sizes) 19 | elif isinstance(image_sizes, tuple): 20 | image_sizes = image_sizes 21 | else: 22 | raise Exception('Type error of input image size format,tuple or int. ') 23 | 24 | for m in range(4): 25 | if (steps[m] != pow(2, (m + 3))): 26 | print("steps must be [8,16,32,64]") 27 | sys.exit() 28 | 29 | assert len(min_sizes) == len(steps), "anchors number didn't match the feature map layer." 30 | 31 | feature_maps = [ 32 | [math.ceil(image_sizes[0] / step), math.ceil(image_sizes[1] / step)] 33 | for step in steps] 34 | 35 | anchors = [] 36 | num_box_fm_cell=[] 37 | for k, f in enumerate(feature_maps): 38 | num_box_fm_cell.append(len(min_sizes[k])) 39 | for i, j in product(range(f[0]), range(f[1])): 40 | for min_size in min_sizes[k]: 41 | if isinstance(min_size, int): 42 | min_size = (min_size, min_size) 43 | elif isinstance(min_size, tuple): 44 | min_size=min_size 45 | else: 46 | raise Exception('Type error of min_sizes elements format,tuple or int. ') 47 | s_kx = min_size[1] / image_sizes[1] 48 | s_ky = min_size[0] / image_sizes[0] 49 | cx = (j + 0.5) * steps[k] / image_sizes[1] 50 | cy = (i + 0.5) * steps[k] / image_sizes[0] 51 | anchors += [cx, cy, s_kx, s_ky] 52 | 53 | output = np.asarray(anchors).reshape([-1, 4]) 54 | # print("prios:",output.shape,len(output)) 55 | # print("num box for fm cell:",num_box_fm_cell) 56 | if clip: 57 | output = np.clip(output, 0, 1) 58 | return output,num_box_fm_cell 59 | 60 | 61 | 62 | if __name__ == '__main__': 63 | import config 64 | priors_box(config.cfg) -------------------------------------------------------------------------------- /components/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/20 3 | # @File : utils.py 4 | # @Software: PyCharm 5 | import cv2 6 | import tensorflow as tf 7 | from absl import logging 8 | 9 | import numpy as np 10 | 11 | 12 | def set_memory_growth(): 13 | gpus = tf.config.experimental.list_physical_devices('GPU') 14 | if gpus: 15 | try: 16 | for gpu in gpus: 17 | tf.config.experimental.set_memory_growth(gpu, True) 18 | logical_gpus = tf.config.experimental.list_logical_devices('GPU') 19 | logging.info("Detect {} Physical GPUs, {} Logical GPUs.".format(len(gpus), len(logical_gpus))) 20 | except RuntimeError as e: 21 | # Memory growth must be set before GPUs have been initialized 22 | logging.info(e) 23 | 24 | 25 | def _transform_center_to_corner(boxes): 26 | """ Convert prior_boxes to (xmin, ymin, xmax, ymax) 27 | representation for comparison to point form ground truth data. 28 | Args: 29 | boxes: (tensor) center-size default boxes from priorbox layers. 30 | Return: 31 | boxes: (tensor) Converted xmin, ymin, xmax, ymax form of boxes. 32 | """ 33 | return tf.concat((boxes[:, :2] - boxes[:, 2:] / 2, 34 | boxes[:, :2] + boxes[:, 2:] / 2), axis=1) 35 | 36 | 37 | def _intersect(box_a, box_b): 38 | """ We resize both tensors to [A,B,2]: 39 | [A,2] -> [A,1,2] -> [A,B,2] 40 | [B,2] -> [1,B,2] -> [A,B,2] 41 | Then we compute the area of intersect between box_a and box_b. 42 | Args: 43 | box_a: (tensor) bounding boxes, Shape: [A,4]. 44 | box_b: (tensor) bounding boxes, Shape: [B,4]. 45 | Return: 46 | (tensor) intersection area, Shape: [A,B]. 47 | """ 48 | A = tf.shape(box_a)[0] 49 | B = tf.shape(box_b)[0] 50 | max_xy = tf.minimum( 51 | tf.broadcast_to(tf.expand_dims(box_a[:, 2:], 1), [A, B, 2]), 52 | tf.broadcast_to(tf.expand_dims(box_b[:, 2:], 0), [A, B, 2])) 53 | min_xy = tf.maximum( 54 | tf.broadcast_to(tf.expand_dims(box_a[:, :2], 1), [A, B, 2]), 55 | tf.broadcast_to(tf.expand_dims(box_b[:, :2], 0), [A, B, 2])) 56 | # inter = tf.maximum((max_xy - min_xy), tf.zeros_like(max_xy - min_xy)) 57 | inter = tf.clip_by_value(max_xy - min_xy, 0.0, 512.0) 58 | return inter[:, :, 0] * inter[:, :, 1] 59 | 60 | 61 | def _jaccard(box_a, box_b): 62 | """Compute the jaccard overlap of two sets of boxes. The jaccard overlap 63 | is simply the intersection over union of two boxes. Here we operate on 64 | ground truth boxes and default boxes. 65 | E.g.: 66 | A ∩ B / A ∪ B = A ∩ B / (area(A) + area(B) - A ∩ B) 67 | Args: 68 | box_a: (tensor) Ground truth bounding boxes, Shape: [num_objects,4] 69 | box_b: (tensor) Prior boxes from priorbox layers, Shape: [num_priors,4] 70 | Return: 71 | jaccard overlap: (tensor) Shape: [box_a.size(0), box_b.size(0)] 72 | """ 73 | inter = _intersect(box_a, box_b) 74 | area_a = tf.broadcast_to( 75 | tf.expand_dims( 76 | (box_a[:, 2] - box_a[:, 0]) * (box_a[:, 3] - box_a[:, 1]), 1), 77 | tf.shape(inter)) # [A,B] 78 | area_b = tf.broadcast_to( 79 | tf.expand_dims( 80 | (box_b[:, 2] - box_b[:, 0]) * (box_b[:, 3] - box_b[:, 1]), 0), 81 | tf.shape(inter)) # [A,B] 82 | union = area_a + area_b - inter 83 | return inter / union # [A,B] 84 | 85 | 86 | def _encode_bbox(matched, priors, variances): 87 | """Encode the variances from the priorbox layers into the ground truth 88 | boxes we have matched (based on jaccard overlap) with the prior boxes. 89 | Args: 90 | matched: (tensor) Coords of ground truth for each prior in point-form 91 | Shape: [num_priors, 4]. 92 | priors: (tensor) Prior boxes in center-offset form 93 | Shape: [num_priors,4]. 94 | variances: (list[float]) Variances of prior boxes 95 | Return: 96 | encoded boxes (tensor), Shape: [num_priors, 4] 97 | """ 98 | 99 | # dist b/t match center and prior's center 100 | g_cxcy = (matched[:, :2] + matched[:, 2:]) / 2 - priors[:, :2] 101 | # encode variance 102 | g_cxcy /= (variances[0] * priors[:, 2:]) 103 | # match wh / prior wh 104 | g_wh = (matched[:, 2:] - matched[:, :2]) / priors[:, 2:] 105 | g_wh = tf.math.log(g_wh) / variances[1] 106 | # return target for smooth_l1_loss 107 | return tf.concat([g_cxcy, g_wh], 1) # [num_priors,4] 108 | 109 | 110 | def encode_tf(labels, priors, match_thresh, variances=None): 111 | """tensorflow encoding""" 112 | if variances is None: 113 | variances = [0.1, 0.2] 114 | 115 | priors = tf.cast(priors, tf.float32) 116 | bbox = labels[:, :4] 117 | conf = labels[:, -1] 118 | 119 | # jaccard index 120 | overlaps = _jaccard(bbox, _transform_center_to_corner(priors)) 121 | best_prior_overlap = tf.reduce_max(overlaps, 1) 122 | best_prior_idx = tf.argmax(overlaps, 1, tf.int32) 123 | 124 | best_truth_overlap = tf.reduce_max(overlaps, 0) 125 | best_truth_idx = tf.argmax(overlaps, 0, tf.int32) 126 | 127 | best_truth_overlap = tf.tensor_scatter_nd_update( 128 | best_truth_overlap, tf.expand_dims(best_prior_idx, 1), 129 | tf.ones_like(best_prior_idx, tf.float32) * 2.) 130 | best_truth_idx = tf.tensor_scatter_nd_update( 131 | best_truth_idx, tf.expand_dims(best_prior_idx, 1), 132 | tf.range(tf.size(best_prior_idx), dtype=tf.int32)) 133 | 134 | matches_bbox = tf.gather(bbox, best_truth_idx) # [num_priors, 4] 135 | loc_t = _encode_bbox(matches_bbox, priors, variances) 136 | 137 | conf_t = tf.gather(conf, best_truth_idx) # [num_priors] 138 | conf_t = tf.where(tf.less(best_truth_overlap, match_thresh), tf.zeros_like(conf_t), conf_t) 139 | 140 | return tf.concat([loc_t, conf_t[..., tf.newaxis]], axis=1) 141 | 142 | 143 | ###########For Check dataset############### 144 | def decode_tf(labels, priors, variances=None): 145 | """tensorflow decoding""" 146 | if variances is None: 147 | variances = [0.1, 0.2] 148 | bbox = decode_bbox_tf(labels[:, :4], priors, variances) 149 | conf = labels[:, 4:] 150 | 151 | return tf.concat([bbox, conf], axis=1) 152 | 153 | ###########Test and Inference############### 154 | def decode_bbox(bbox, priors, variances): 155 | """Decode locations from predictions using anchors to undo 156 | the encoding we did for offset regression at train time. 157 | """ 158 | if variances is None: 159 | variances = [0.1, 0.2] 160 | boxes = np.concatenate( 161 | (priors[:, :2] + bbox[:, :2] * variances[0] * priors[:, 2:], 162 | priors[:, 2:] * np.exp(bbox[:, 2:] * variances[1])), 1) 163 | boxes[:, :2] -= boxes[:, 2:] / 2 164 | boxes[:, 2:] += boxes[:, :2] 165 | return boxes 166 | 167 | 168 | def decode_bbox_tf(pre, priors, variances=None): 169 | """Decode locations from predictions using priors to undo 170 | the encoding we did for offset regression at train time. 171 | Args: 172 | pre (tensor): location predictions for loc layers, 173 | Shape: [num_priors,4] 174 | priors (tensor): Prior boxes in center-offset form. 175 | Shape: [num_priors,4]. 176 | variances: (list[float]) Variances of prior boxes 177 | Return: 178 | decoded bounding box predictions xmin, ymin, xmax, ymax 179 | """ 180 | if variances is None: 181 | variances = [0.1, 0.2] 182 | centers = priors[:, :2] + pre[:, :2] * variances[0] * priors[:, 2:] 183 | sides = priors[:, 2:] * tf.math.exp(pre[:, 2:] * variances[1]) 184 | 185 | return tf.concat([centers - sides / 2, centers + sides / 2], axis=1) 186 | 187 | 188 | def draw_anchor(img, prior, img_height, img_width): 189 | """draw anchors""" 190 | x1 = int(prior[0] * img_width - prior[2] * img_width / 2) 191 | y1 = int(prior[1] * img_height - prior[3] * img_height / 2) 192 | x2 = int(prior[0] * img_width + prior[2] * img_width / 2) 193 | y2 = int(prior[1] * img_height + prior[3] * img_height / 2) 194 | cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 0), 1) 195 | 196 | 197 | def pad_input_image(img, max_steps): 198 | """pad image to suitable shape""" 199 | img_h, img_w, _ = img.shape 200 | 201 | img_pad_h = 0 202 | if img_h % max_steps > 0: 203 | img_pad_h = max_steps - img_h % max_steps 204 | 205 | img_pad_w = 0 206 | if img_w % max_steps > 0: 207 | img_pad_w = max_steps - img_w % max_steps 208 | 209 | padd_val = np.mean(img, axis=(0, 1)).astype(np.uint8) 210 | img = cv2.copyMakeBorder(img, 0, img_pad_h, 0, img_pad_w, 211 | cv2.BORDER_CONSTANT, value=padd_val.tolist()) 212 | pad_params = (img_h, img_w, img_pad_h, img_pad_w) 213 | 214 | return img, pad_params 215 | 216 | 217 | def recover_pad_output(outputs, pad_params): 218 | """ 219 | recover the padded output effect 220 | 221 | """ 222 | img_h, img_w, img_pad_h, img_pad_w = pad_params 223 | 224 | recover_xy = np.reshape(outputs[0], [-1, 2, 2]) * \ 225 | [(img_pad_w + img_w) / img_w, (img_pad_h + img_h) / img_h] 226 | outputs[0] = np.reshape(recover_xy, [-1, 4]) 227 | 228 | return outputs 229 | 230 | 231 | def show_image(img, boxes, classes, scores, img_height, img_width, prior_index, class_list): 232 | """ 233 | draw bboxes and labels 234 | out:boxes,classes,scores 235 | """ 236 | # bbox 237 | 238 | x1, y1, x2, y2 = int(boxes[prior_index][0] * img_width), int(boxes[prior_index][1] * img_height), \ 239 | int(boxes[prior_index][2] * img_width), int(boxes[prior_index][3] * img_height) 240 | if classes[prior_index] == 1: 241 | color = (0, 255, 0) 242 | else: 243 | color = (0, 0, 255) 244 | cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) 245 | # confidence 246 | 247 | score = "{:.4f}".format(scores[prior_index]) 248 | class_name = class_list[classes[prior_index]] 249 | 250 | cv2.putText(img, '{} {}'.format(class_name, score), 251 | (int(boxes[prior_index][0] * img_width), int(boxes[prior_index][1] * img_height) - 4), 252 | cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255)) 253 | 254 | 255 | 256 | def compute_nms(boxes, scores, nms_threshold=0.5, limit=200): 257 | """ Perform Non Maximum Suppression algorithm 258 | to eliminate boxes with high overlap 259 | Args: 260 | boxes: tensor (num_boxes, 4) 261 | of format (xmin, ymin, xmax, ymax) 262 | scores: tensor (num_boxes,) 263 | nms_threshold: NMS threshold 264 | limit: maximum number of boxes to keep 265 | Returns: 266 | idx: indices of kept boxes 267 | """ 268 | if boxes.shape[0] == 0: 269 | return tf.constant([], dtype=tf.int32) 270 | selected = [0] 271 | idx = tf.argsort(scores, direction='DESCENDING') 272 | idx = idx[:limit] 273 | boxes = tf.gather(boxes, idx) 274 | 275 | iou = _jaccard(boxes, boxes) 276 | 277 | while True: 278 | row = iou[selected[-1]] 279 | next_indices = row <= nms_threshold 280 | 281 | # iou[:, ~next_indices] = 1.0 282 | iou = tf.where( 283 | tf.expand_dims(tf.math.logical_not(next_indices), 0), 284 | tf.ones_like(iou, dtype=tf.float32), 285 | iou) 286 | 287 | if not tf.math.reduce_any(next_indices): 288 | break 289 | 290 | selected.append(tf.argsort( 291 | tf.dtypes.cast(next_indices, tf.int32), direction='DESCENDING')[0].numpy()) 292 | 293 | return tf.gather(idx, selected) 294 | -------------------------------------------------------------------------------- /dataset/check_dataset.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/20 3 | # @File : check_dataset.py 4 | # @Software: PyCharm 5 | import cv2, time, os 6 | import numpy as np 7 | import sys 8 | import os 9 | rootPath = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0] 10 | sys.path.append(rootPath) 11 | 12 | from components import config 13 | from components.utils import draw_anchor, decode_tf 14 | from components.prior_box import priors_box 15 | from dataset.tf_dataset_preprocess import load_tfrecord_dataset 16 | 17 | 18 | 19 | 20 | def draw(img, ann, img_height, img_width, class_list): 21 | """draw bboxes and labels""" 22 | # bbox 23 | 24 | x1, y1, x2, y2 = int(ann[0] * img_width), int(ann[1] * img_height), int(ann[2] * img_width), int(ann[3] * img_height) 25 | cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) 26 | 27 | # confidence 28 | 29 | class_name = class_list[int(ann[-1])] 30 | cv2.putText(img, '{}'.format(class_name), (int(ann[0] * img_width), int(ann[1] * img_height) - 4), 31 | cv2.FONT_HERSHEY_SIMPLEX, 0.3, (255, 255, 255)) 32 | 33 | 34 | def data_visulization(): 35 | dataset = load_tfrecord_dataset(tfrecord_name=tfrecord_name, batch_size=batch_size, img_dim=cfg['input_size'], 36 | using_flip=True, using_distort=True, using_encoding=using_encoding, 37 | using_normalizing=using_normalizing, priors=priors, match_thresh=match_thresh, 38 | variances=variances, shuffle=False,repeat=False) 39 | 40 | start_time = time.time() 41 | #check dataset 42 | for idx, (inputs, labels) in enumerate(dataset.take(num_samples)): 43 | print("{} inputs:".format(idx), inputs.shape, "labels:", labels.shape) 44 | if not visualization: 45 | continue 46 | #img:-0.5~0.5 >> 0~255 47 | img = np.clip((inputs.numpy()[0]+0.5) * 255.0, 0, 255).astype(np.uint8) 48 | 49 | if not using_encoding: 50 | # labels includes loc,conf 51 | 52 | targets = labels.numpy()[0] 53 | 54 | for target in targets: 55 | 56 | draw(img, target, cfg['input_size'][0], cfg['input_size'][1], class_list) 57 | else: 58 | # labels includes loc, conf. 59 | targets = decode_tf(labels[0], priors, variances=variances).numpy() 60 | 61 | for prior_index in range(len(targets)): 62 | 63 | if targets[prior_index][4] > 0: 64 | # print(f"class id {targets[prior_index][4]}") 65 | draw(img, targets[prior_index], cfg['input_size'][0], cfg['input_size'][1], class_list) 66 | draw_anchor(img, priors[prior_index], cfg['input_size'][0], cfg['input_size'][1]) 67 | 68 | cv2.imshow('img', cv2.cvtColor(img, cv2.COLOR_RGB2BGR)) 69 | if cv2.waitKey(0) == ord('q') : 70 | exit() 71 | 72 | print("data fps: {:.2f}".format(num_samples / (time.time() - start_time))) 73 | 74 | 75 | if __name__ == '__main__': 76 | 77 | #for test dataset 78 | cfg = config.cfg 79 | class_list = cfg['labels_list'] 80 | print(f"class:{class_list}") 81 | os.environ["TF_CPP_MIN_LOG_LEVEL"] = '2' 82 | 83 | batch_size = 1 84 | priors, num_cell = priors_box(cfg) 85 | visualization = True # False for time cost estimattion 86 | using_encoding = True # batch size should be 1 when False 87 | using_normalizing = True # image:-0.5~0.5 88 | variances = [0.1, 0.2] 89 | match_thresh = 0.45 90 | ignore_thresh = 0.3 91 | 92 | num_samples = cfg['dataset_len'] 93 | tfrecord_name = rootPath+'/dataset/train_mask.tfrecord' 94 | 95 | # num_samples = cfg['val_len'] 96 | # tfrecord_name = rootPath+'/dataset/trainval_mask.tfrecord' 97 | 98 | data_visulization() 99 | exit() 100 | -------------------------------------------------------------------------------- /dataset/tf_dataset_preprocess.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/20 3 | # @File : tf_dataset_preprocess.py 4 | # @Software: PyCharm 5 | 6 | import tensorflow as tf 7 | from components.utils import encode_tf 8 | from absl import logging 9 | 10 | def _parse_tfrecord(img_dim,using_crop, using_flip, using_distort, 11 | using_encoding, using_normalizing,priors, match_thresh, variances): 12 | def parse_tfrecord(tfrecord): 13 | features = { 14 | 'filename': tf.io.FixedLenFeature([], tf.string), 15 | 'height': tf.io.FixedLenFeature([], tf.int64), 16 | 'width': tf.io.FixedLenFeature([], tf.int64), 17 | 'classes': tf.io.VarLenFeature(tf.int64), 18 | 'x_mins': tf.io.VarLenFeature(tf.float32), 19 | 'y_mins': tf.io.VarLenFeature(tf.float32), 20 | 'x_maxes': tf.io.VarLenFeature(tf.float32), 21 | 'y_maxes': tf.io.VarLenFeature(tf.float32), 22 | 'difficult':tf.io.VarLenFeature(tf.int64), 23 | 'image_raw': tf.io.FixedLenFeature([], tf.string), 24 | } 25 | 26 | parsed_example = tf.io.parse_single_example(tfrecord, features) 27 | img = tf.image.decode_jpeg(parsed_example['image_raw'], channels=3) 28 | 29 | width = tf.cast(parsed_example['width'], tf.float32) 30 | height = tf.cast(parsed_example['height'], tf.float32) 31 | 32 | labels = tf.sparse.to_dense(parsed_example['classes']) 33 | labels = tf.cast(labels, tf.float32) 34 | 35 | labels = tf.stack( 36 | [tf.sparse.to_dense(parsed_example['x_mins']), 37 | tf.sparse.to_dense(parsed_example['y_mins']), 38 | tf.sparse.to_dense(parsed_example['x_maxes']), 39 | tf.sparse.to_dense(parsed_example['y_maxes']),labels], axis=1) 40 | 41 | img, labels = _transform_data( 42 | img_dim, using_crop,using_flip, using_distort, using_encoding, using_normalizing,priors, 43 | match_thresh, variances)(img, labels) 44 | 45 | return img, labels 46 | return parse_tfrecord 47 | 48 | 49 | def _transform_data(img_dim, using_crop,using_flip, using_distort, using_encoding,using_normalizing, priors, 50 | match_thresh, variances): 51 | def transform_data(img, labels): 52 | img = tf.cast(img, tf.float32) 53 | if using_crop: 54 | # randomly crop 55 | img, labels = _crop(img, labels) 56 | 57 | # padding to square 58 | img = _pad_to_square(img) 59 | 60 | # resize and boxes coordinate to percent 61 | img, labels = _resize(img, labels, img_dim) 62 | 63 | # randomly left-right flip 64 | if using_flip: 65 | img, labels = _flip(img, labels) 66 | 67 | # distort 68 | if using_distort: 69 | img = _distort(img) 70 | 71 | # encode labels to feature targets 72 | if using_encoding: 73 | labels = encode_tf(labels=labels, priors=priors, match_thresh=match_thresh, variances=variances) 74 | if using_normalizing: 75 | img=(img/255.0-0.5)/1.0 76 | 77 | return img, labels 78 | return transform_data 79 | 80 | 81 | def load_tfrecord_dataset(tfrecord_name, batch_size, img_dim, 82 | using_crop=True,using_flip=True, using_distort=True, 83 | using_encoding=True, using_normalizing=True, 84 | priors=None, match_thresh=0.45,variances=None, 85 | shuffle=True, repeat=True,buffer_size=10240): 86 | 87 | if variances is None: 88 | variances = [0.1, 0.2] 89 | 90 | """load dataset from tfrecord""" 91 | if not using_encoding: 92 | assert batch_size == 1 93 | else: 94 | assert priors is not None 95 | 96 | raw_dataset = tf.data.TFRecordDataset(tfrecord_name) 97 | raw_dataset = raw_dataset.cache() 98 | if repeat: 99 | raw_dataset = raw_dataset.repeat() 100 | if shuffle: 101 | raw_dataset = raw_dataset.shuffle(buffer_size=buffer_size) 102 | 103 | 104 | dataset = raw_dataset.map( 105 | _parse_tfrecord(img_dim, using_crop, using_flip, using_distort, 106 | using_encoding, using_normalizing,priors, match_thresh, variances), 107 | num_parallel_calls=tf.data.experimental.AUTOTUNE) 108 | dataset = dataset.batch(batch_size, drop_remainder=True) 109 | dataset = dataset.prefetch( 110 | buffer_size=tf.data.experimental.AUTOTUNE) 111 | 112 | return dataset 113 | 114 | 115 | 116 | ############################################################################### 117 | # Data Augmentation # 118 | ############################################################################### 119 | 120 | def _crop(img, labels, max_loop=250): 121 | shape = tf.shape(img) 122 | 123 | def matrix_iof(a, b): 124 | """ 125 | return iof of a and b, numpy version for data augenmentation 126 | """ 127 | lt = tf.math.maximum(a[:, tf.newaxis, :2], b[:, :2]) 128 | rb = tf.math.minimum(a[:, tf.newaxis, 2:], b[:, 2:]) 129 | 130 | area_i = tf.math.reduce_prod(rb - lt, axis=2) * \ 131 | tf.cast(tf.reduce_all(lt < rb, axis=2), tf.float32) 132 | area_a = tf.math.reduce_prod(a[:, 2:] - a[:, :2], axis=1) 133 | return area_i / tf.math.maximum(area_a[:, tf.newaxis], 1) 134 | 135 | def crop_loop_body(i, img, labels): 136 | valid_crop = tf.constant(1, tf.int32) 137 | 138 | pre_scale = tf.constant([0.3, 0.45, 0.6, 0.8, 1.0], dtype=tf.float32) 139 | scale = pre_scale[tf.random.uniform([], 0, 5, dtype=tf.int32)] 140 | short_side = tf.cast(tf.minimum(shape[0], shape[1]), tf.float32) 141 | h = w = tf.cast(scale * short_side, tf.int32) 142 | h_offset = tf.random.uniform([], 0, shape[0] - h + 1, dtype=tf.int32) 143 | w_offset = tf.random.uniform([], 0, shape[1] - w + 1, dtype=tf.int32) 144 | roi = tf.stack([w_offset, h_offset, w_offset + w, h_offset + h]) 145 | roi = tf.cast(roi, tf.float32) 146 | 147 | 148 | value = matrix_iof(labels[:, :4], roi[tf.newaxis]) 149 | valid_crop = tf.cond(tf.math.reduce_any(value >= 1), 150 | lambda: valid_crop, lambda: 0) 151 | 152 | centers = (labels[:, :2] + labels[:, 2:4]) / 2 153 | mask_a = tf.reduce_all( 154 | tf.math.logical_and(roi[:2] < centers, centers < roi[2:]), 155 | axis=1) 156 | labels_t = tf.boolean_mask(labels, mask_a) 157 | valid_crop = tf.cond(tf.reduce_any(mask_a), 158 | lambda: valid_crop, lambda: 0) 159 | 160 | img_t = img[h_offset:h_offset + h, w_offset:w_offset + w, :] 161 | h_offset = tf.cast(h_offset, tf.float32) 162 | w_offset = tf.cast(w_offset, tf.float32) 163 | labels_t = tf.stack( 164 | [labels_t[:, 0] - w_offset, labels_t[:, 1] - h_offset, 165 | labels_t[:, 2] - w_offset, labels_t[:, 3] - h_offset, 166 | labels_t[:, 4]], axis=1) 167 | 168 | return tf.cond(valid_crop == 1, 169 | lambda: (max_loop, img_t, labels_t), 170 | lambda: (i + 1, img, labels)) 171 | 172 | _, img, labels = tf.while_loop( 173 | lambda i, img, labels: tf.less(i, max_loop), 174 | crop_loop_body, 175 | [tf.constant(-1), img, labels], 176 | shape_invariants=[tf.TensorShape([]), 177 | tf.TensorShape([None, None, 3]), 178 | tf.TensorShape([None, 5])]) 179 | 180 | return img, labels 181 | 182 | 183 | def _pad_to_square(img): 184 | height = tf.shape(img)[0] 185 | width = tf.shape(img)[1] 186 | 187 | def pad_h(): 188 | img_pad_h = tf.ones([width - height, width, 3]) * tf.reduce_mean(img, axis=[0, 1], keepdims=True) 189 | return tf.concat([img, img_pad_h], axis=0) 190 | 191 | def pad_w(): 192 | img_pad_w = tf.ones([height, height - width, 3]) * tf.reduce_mean(img, axis=[0, 1], keepdims=True) 193 | return tf.concat([img, img_pad_w], axis=1) 194 | 195 | img = tf.case([(tf.greater(height, width), pad_w), 196 | (tf.less(height, width), pad_h)], default=lambda: img) 197 | 198 | return img 199 | 200 | 201 | 202 | def _resize(img, labels, img_dim): 203 | ''' # resize and boxes coordinate to percent''' 204 | w_f = tf.cast(tf.shape(img)[1], tf.float32) 205 | h_f = tf.cast(tf.shape(img)[0], tf.float32) 206 | locs = tf.stack([labels[:, 0] / w_f, labels[:, 1] / h_f, 207 | labels[:, 2] / w_f, labels[:, 3] / h_f] ,axis=1) 208 | locs = tf.clip_by_value(locs, 0, 1.0) 209 | labels = tf.concat([locs, labels[:, 4][:, tf.newaxis]], axis=1) 210 | 211 | resize_case = tf.random.uniform([], 0, 5, dtype=tf.int32) 212 | if isinstance(img_dim, int): 213 | img_dim = (img_dim, img_dim) 214 | elif isinstance(img_dim,tuple): 215 | img_dim = img_dim 216 | else: 217 | raise Exception('Type error of input image size format,tuple or int. ') 218 | 219 | def resize(method): 220 | def _resize(): 221 | # size h,w 222 | return tf.image.resize(img, [img_dim[0], img_dim[1]], method=method, antialias=True) 223 | return _resize 224 | 225 | img = tf.case([(tf.equal(resize_case, 0), resize('bicubic')), 226 | (tf.equal(resize_case, 1), resize('area')), 227 | (tf.equal(resize_case, 2), resize('nearest')), 228 | (tf.equal(resize_case, 3), resize('lanczos3'))], 229 | default=resize('bilinear')) 230 | 231 | return img, labels 232 | 233 | def _flip(img, labels): 234 | flip_case = tf.random.uniform([], 0, 2, dtype=tf.int32) 235 | 236 | def flip_func(): 237 | flip_img = tf.image.flip_left_right(img) 238 | flip_labels = tf.stack([1 - labels[:, 2], labels[:, 1], 239 | 1 - labels[:, 0], labels[:, 3], 240 | labels[:, 4]], axis=1) 241 | 242 | return flip_img, flip_labels 243 | 244 | img, labels = tf.case([(tf.equal(flip_case, 0), flip_func)],default=lambda: (img, labels)) 245 | 246 | return img, labels 247 | 248 | def _distort(img): 249 | img = tf.image.random_brightness(img, 0.4) 250 | img = tf.image.random_contrast(img, 0.5, 1.5) 251 | img = tf.image.random_saturation(img, 0.5, 1.5) 252 | img = tf.image.random_hue(img, 0.1) 253 | 254 | return img 255 | 256 | 257 | def load_dataset(cfg, priors, shuffle=True, buffer_size=10240,train=True): 258 | """load dataset""" 259 | global dataset 260 | if train: 261 | logging.info("load train dataset from {}".format(cfg['dataset_path'])) 262 | dataset = load_tfrecord_dataset( 263 | tfrecord_name=cfg['dataset_path'], 264 | batch_size=cfg['batch_size'], 265 | img_dim=cfg['input_size'], 266 | using_crop=cfg['using_crop'], 267 | using_flip=cfg['using_flip'], 268 | using_distort=cfg['using_distort'], 269 | using_encoding=True, 270 | using_normalizing=cfg['using_normalizing'], 271 | priors=priors, 272 | match_thresh=cfg['match_thresh'], 273 | variances=cfg['variances'], 274 | shuffle=shuffle, 275 | repeat=True, 276 | buffer_size=buffer_size) 277 | else: 278 | dataset = load_tfrecord_dataset( 279 | tfrecord_name=cfg['val_path'], 280 | batch_size=cfg['batch_size'], 281 | img_dim=cfg['input_size'], 282 | using_crop=False, 283 | using_flip=False, 284 | using_distort=False, 285 | using_encoding=True, 286 | using_normalizing=True, 287 | priors=priors, 288 | match_thresh=cfg['match_thresh'], 289 | variances=cfg['variances'], 290 | shuffle=shuffle, 291 | repeat=False, 292 | buffer_size=buffer_size) 293 | logging.info("load validation dataset from {}".format(cfg['val_path'])) 294 | 295 | return dataset -------------------------------------------------------------------------------- /dataset/voc_to_tfrecord.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/20 3 | # @File : voc_to_tfrecord.py 4 | # @Software: PyCharm 5 | 6 | import os,tqdm,sys 7 | import tensorflow as tf 8 | from absl import app, flags, logging 9 | from absl.flags import FLAGS 10 | import xml.etree.ElementTree as ET 11 | rootPath = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0] 12 | sys.path.append(rootPath) 13 | 14 | from components import config 15 | 16 | flags.DEFINE_string('dataset_path', 'Maskdata', 'VOC format dataset') 17 | flags.DEFINE_string('output_file', rootPath+'/dataset/train_mask.tfrecord', 'TFRecord file:output dataset') 18 | flags.DEFINE_enum('split', 'train', ['train', 'val', 'trainval'], 'train or val dataset') 19 | 20 | 21 | 22 | def process_image(image_file): 23 | # image_string = open(image_file,'rb').read() 24 | image_string = tf.io.read_file(image_file) 25 | try: 26 | image_data = tf.image.decode_jpeg(image_string, channels=3) 27 | return 0, image_string, image_data 28 | except tf.errors.InvalidArgumentError: 29 | logging.info('{}: Invalid JPEG data or crop window'.format(image_file)) 30 | return 1, image_string, None 31 | 32 | 33 | def parse_annot(annot_file, CLASSES): 34 | """Parse Pascal VOC annotations.""" 35 | tree = ET.parse(annot_file) 36 | root = tree.getroot() 37 | 38 | image_info = {} 39 | image_info_list = [] 40 | 41 | file_name = root.find('filename').text 42 | 43 | size = root.find('size') 44 | width = int(size.find('width').text) 45 | height = int(size.find('height').text) 46 | depth = int(size.find('depth').text) 47 | 48 | xmin, ymin, xmax, ymax = [], [], [], [] 49 | classes = [] 50 | difficult = [] 51 | 52 | for obj in root.iter('object'): 53 | label = obj.find('name').text 54 | 55 | if len(CLASSES) > 0 and label not in CLASSES: 56 | continue 57 | else: 58 | classes.append(CLASSES.index(label)) 59 | 60 | if obj.find('difficult'): 61 | difficult.append(int(obj.find('difficult').text)) 62 | else: 63 | difficult.append(0) 64 | 65 | for box in obj.findall('bndbox'): 66 | xmin.append(float(box.find('xmin').text)) 67 | ymin.append(float(box.find('ymin').text)) 68 | xmax.append(float(box.find('xmax').text)) 69 | ymax.append(float(box.find('ymax').text)) 70 | # xmin.append(float(box.find('xmin').text) / width) 71 | # ymin.append(float(box.find('ymin').text) / height) 72 | # xmax.append(float(box.find('xmax').text) / width) 73 | # ymax.append(float(box.find('ymax').text) / height) 74 | 75 | image_info['filename'] = file_name 76 | image_info['width'] = width 77 | image_info['height'] = height 78 | image_info['depth'] = depth 79 | image_info['class'] = classes 80 | image_info['xmin'] = xmin 81 | image_info['ymin'] = ymin 82 | image_info['xmax'] = xmax 83 | image_info['ymax'] = ymax 84 | image_info['difficult'] = difficult 85 | 86 | image_info_list.append(image_info) 87 | 88 | return image_info_list 89 | 90 | 91 | 92 | 93 | def make_example(image_string, image_info_list): 94 | 95 | for info in image_info_list: 96 | filename = info['filename'] 97 | width = info['width'] 98 | height = info['height'] 99 | depth = info['depth'] 100 | classes = info['class'] 101 | xmin = info['xmin'] 102 | ymin = info['ymin'] 103 | xmax = info['xmax'] 104 | ymax = info['ymax'] 105 | # difficult = info['difficult'] 106 | 107 | if isinstance(image_string, type(tf.constant(0))): 108 | encoded_image = [image_string.numpy()] 109 | else: 110 | encoded_image = [image_string] 111 | 112 | base_name = [tf.compat.as_bytes(os.path.basename(filename))] 113 | 114 | example = tf.train.Example(features=tf.train.Features(feature={ 115 | 'filename':tf.train.Feature(bytes_list=tf.train.BytesList(value=base_name)), 116 | 'height':tf.train.Feature(int64_list=tf.train.Int64List(value=[height])), 117 | 'width':tf.train.Feature(int64_list=tf.train.Int64List(value=[width])), 118 | 'classes':tf.train.Feature(int64_list=tf.train.Int64List(value=classes)), 119 | 'x_mins':tf.train.Feature(float_list=tf.train.FloatList(value=xmin)), 120 | 'y_mins':tf.train.Feature(float_list=tf.train.FloatList(value=ymin)), 121 | 'x_maxes':tf.train.Feature(float_list=tf.train.FloatList(value=xmax)), 122 | 'y_maxes':tf.train.Feature(float_list=tf.train.FloatList(value=ymax)), 123 | 'image_raw':tf.train.Feature(bytes_list=tf.train.BytesList(value=encoded_image)) 124 | })) 125 | return example 126 | 127 | 128 | def main(argv): 129 | dataset_path = FLAGS.dataset_path 130 | 131 | if not os.path.isdir(dataset_path): 132 | logging.info('Please define valid dataset path.') 133 | else: 134 | logging.info('Loading {}'.format(dataset_path)) 135 | 136 | logging.info('Reading configuration...') 137 | 138 | 139 | 140 | class_list = config.cfg['labels_list'] 141 | 142 | logging.info("Class dictionary loaded: %s", class_list) 143 | 144 | if os.path.exists(FLAGS.output_file): 145 | logging.info('{:s} already exists. Exit...'.format( 146 | FLAGS.output_file)) 147 | exit() 148 | 149 | with tf.io.TFRecordWriter(FLAGS.output_file) as writer: 150 | img_list = open( 151 | os.path.join(FLAGS.dataset_path, 'ImageSets', 'Main', '%s.txt' % FLAGS.split)).read().splitlines() 152 | logging.info("Image list loaded: %d", len(img_list)) 153 | counter = 0 154 | skipped = 0 155 | for image in tqdm.tqdm(img_list): 156 | image_file = os.path.join(FLAGS.dataset_path, 'JPEGImages', '%s.jpg' % image) 157 | annot_file = os.path.join(FLAGS.dataset_path, 'Annotations', '%s.xml' % image) 158 | 159 | # processes the image and parse the annotation 160 | error, image_string, image_data = process_image(image_file) 161 | image_info_list = parse_annot(annot_file, class_list) 162 | if not error: 163 | tf_example = make_example(image_string, image_info_list) 164 | 165 | writer.write(tf_example.SerializeToString()) 166 | counter += 1 167 | 168 | else: 169 | skipped += 1 170 | logging.info('Skipped {:d} of {:d} images.'.format(skipped, len(img_list))) 171 | 172 | logging.info('Wrote {} images to {}'.format(counter, FLAGS.output_file)) 173 | 174 | if __name__ == '__main__': 175 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' 176 | 177 | try: 178 | app.run(main) 179 | except SystemExit: 180 | pass 181 | -------------------------------------------------------------------------------- /inference.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/21 3 | # @File : inference.py 4 | # @Software: PyCharm 5 | import cv2 6 | import os 7 | import time 8 | 9 | import numpy as np 10 | import tensorflow as tf 11 | from absl import flags, app 12 | from absl.flags import FLAGS 13 | 14 | from components import config 15 | from components.prior_box import priors_box 16 | from components.utils import decode_bbox_tf, compute_nms, pad_input_image, recover_pad_output, show_image 17 | from network.network import SlimModel # defined by tf.keras 18 | 19 | flags.DEFINE_string('model_path', 'checkpoints/', 'config file path') 20 | flags.DEFINE_string('img_path', 'assets/1_Handshaking_Handshaking_1_71.jpg', 'path to input image') 21 | flags.DEFINE_boolean('camera', True, 'get image source from webcam or not') 22 | 23 | 24 | def parse_predict(predictions, priors, cfg): 25 | label_classes = cfg['labels_list'] 26 | 27 | bbox_regressions, confs = tf.split(predictions[0], [4, -1], axis=-1) 28 | boxes = decode_bbox_tf(bbox_regressions, priors, cfg['variances']) 29 | 30 | confs = tf.math.softmax(confs, axis=-1) 31 | 32 | out_boxes = [] 33 | out_labels = [] 34 | out_scores = [] 35 | 36 | for c in range(1, len(label_classes)): 37 | cls_scores = confs[:, c] 38 | 39 | score_idx = cls_scores > cfg['score_threshold'] 40 | 41 | cls_boxes = boxes[score_idx] 42 | cls_scores = cls_scores[score_idx] 43 | 44 | nms_idx = compute_nms(cls_boxes, cls_scores, cfg['nms_threshold'], cfg['max_number_keep']) 45 | 46 | cls_boxes = tf.gather(cls_boxes, nms_idx) 47 | cls_scores = tf.gather(cls_scores, nms_idx) 48 | 49 | cls_labels = [c] * cls_boxes.shape[0] 50 | 51 | out_boxes.append(cls_boxes) 52 | out_labels.extend(cls_labels) 53 | out_scores.append(cls_scores) 54 | 55 | out_boxes = tf.concat(out_boxes, axis=0) 56 | out_scores = tf.concat(out_scores, axis=0) 57 | 58 | boxes = tf.clip_by_value(out_boxes, 0.0, 1.0).numpy() 59 | classes = np.array(out_labels) 60 | scores = out_scores.numpy() 61 | 62 | return boxes, classes, scores 63 | 64 | 65 | def main(_): 66 | global model 67 | cfg = config.cfg 68 | min_sizes = cfg['min_sizes'] 69 | num_cell = [len(min_sizes[k]) for k in range(len(cfg['steps']))] 70 | 71 | try: 72 | model = SlimModel(cfg=cfg, num_cell=num_cell, training=False) 73 | 74 | paths = [os.path.join(FLAGS.model_path, path) 75 | for path in os.listdir(FLAGS.model_path)] 76 | latest = sorted(paths, key=os.path.getmtime)[-1] 77 | model.load_weights(latest) 78 | print(f"model path : {latest}") 79 | model.save('final.h5') #if want to convert to tflite by model.save,it should be set input image size. 80 | # model.summary() 81 | except AttributeError as e: 82 | print('Please make sure there is at least one weights at {}'.format(FLAGS.model_path)) 83 | 84 | if not FLAGS.camera: 85 | if not os.path.exists(FLAGS.img_path): 86 | print(f"Cannot find image path from {FLAGS.img_path}") 87 | exit() 88 | print("[*] Predict {} image.. ".format(FLAGS.img_path)) 89 | img_raw = cv2.imread(FLAGS.img_path) 90 | img_height_raw, img_width_raw, _ = img_raw.shape 91 | img = np.float32(img_raw.copy()) 92 | 93 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 94 | # pad input image to avoid unmatched shape problem 95 | img, pad_params = pad_input_image(img, max_steps=max(cfg['steps'])) 96 | img = img / 255.0 - 0.5 97 | 98 | priors, _ = priors_box(cfg, image_sizes=(img.shape[0], img.shape[1])) 99 | priors = tf.cast(priors, tf.float32) 100 | 101 | predictions = model.predict(img[np.newaxis, ...]) 102 | 103 | boxes, classes, scores = parse_predict(predictions, priors, cfg) 104 | 105 | print(f"scores:{scores}") 106 | # recover padding effect 107 | boxes = recover_pad_output(boxes, pad_params) 108 | 109 | # draw and save results 110 | save_img_path = os.path.join('assets/out_' + os.path.basename(FLAGS.img_path)) 111 | 112 | for prior_index in range(len(boxes)): 113 | show_image(img_raw, boxes, classes, scores, img_height_raw, img_width_raw, prior_index, cfg['labels_list']) 114 | 115 | cv2.imwrite(save_img_path, img_raw) 116 | cv2.imshow('results', img_raw) 117 | if cv2.waitKey(0) == ord('q'): 118 | exit(0) 119 | 120 | else: 121 | capture = cv2.VideoCapture(0) 122 | capture.set(cv2.CAP_PROP_FRAME_WIDTH, 320) 123 | capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 240) 124 | 125 | priors, _ = priors_box(cfg, image_sizes=(240, 320)) 126 | priors = tf.cast(priors, tf.float32) 127 | start = time.time() 128 | while True: 129 | _, frame = capture.read() 130 | if frame is None: 131 | print('No camera found') 132 | 133 | h, w, _ = frame.shape 134 | img = np.float32(frame.copy()) 135 | 136 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 137 | 138 | img = img / 255.0 - 0.5 139 | 140 | predictions = model(img[np.newaxis, ...]) 141 | boxes, classes, scores = parse_predict(predictions, priors, cfg) 142 | 143 | for prior_index in range(len(classes)): 144 | show_image(frame, boxes, classes, scores, h, w, prior_index, cfg['labels_list']) 145 | # calculate fps 146 | fps_str = "FPS: %.2f" % (1 / (time.time() - start)) 147 | start = time.time() 148 | cv2.putText(frame, fps_str, (25, 25), cv2.FONT_HERSHEY_DUPLEX, 0.75, (0, 255, 0), 2) 149 | 150 | # show frame 151 | cv2.imshow('frame', frame) 152 | if cv2.waitKey(1) == ord('q'): 153 | exit() 154 | 155 | 156 | if __name__ == '__main__': 157 | # os.environ['CUDA_VISIBLE_DEVICES'] = '-1' 158 | try: 159 | app.run(main) 160 | except Exception as e: 161 | print(e) 162 | exit() 163 | -------------------------------------------------------------------------------- /mAP/README.md: -------------------------------------------------------------------------------- 1 | fixed input image size model to detect the face mask. 2 | 3 | Get gt boxes and labels,convert xml to txt file on `mAP/ground truth`, predicting the bbox and class on `mAP/detection-results`. 4 | -------------------------------------------------------------------------------- /mAP/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/29 3 | # @File : __init__.py.py 4 | # @Software: PyCharm -------------------------------------------------------------------------------- /mAP/compute_mAP.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import json 3 | import os 4 | import shutil 5 | import operator 6 | import sys 7 | import argparse 8 | import math 9 | 10 | import numpy as np 11 | 12 | MINOVERLAP = 0.5 # default value (defined in the PASCAL VOC2012 challenge) 13 | 14 | parser = argparse.ArgumentParser() 15 | parser.add_argument('-na', '--no-animation', help="no animation is shown.", action="store_true") 16 | parser.add_argument('-np', '--no-plot', help="no plot is shown.", action="store_true") 17 | parser.add_argument('-q', '--quiet', help="minimalistic console output.", action="store_true") 18 | # argparse receiving list of classes to be ignored 19 | parser.add_argument('-i', '--ignore', nargs='+', type=str, help="ignore a list of classes.") 20 | # argparse receiving list of classes with specific IoU (e.g., python compute_mAP.py --set-class-iou person 0.7) 21 | parser.add_argument('--set-class-iou', nargs='+', type=str, help="set IoU for a specific class.") 22 | args = parser.parse_args() 23 | 24 | ''' 25 | 0,0 ------> x (width) 26 | | 27 | | (Left,Top) 28 | | *_________ 29 | | | | 30 | | | 31 | y |_________| 32 | (height) * 33 | (Right,Bottom) 34 | ''' 35 | 36 | # if there are no classes to ignore then replace None by empty list 37 | if args.ignore is None: 38 | args.ignore = [] 39 | 40 | specific_iou_flagged = False 41 | if args.set_class_iou is not None: 42 | specific_iou_flagged = True 43 | 44 | # make sure that the cwd() is the location of the python script (so that every path makes sense) 45 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 46 | 47 | GT_PATH = os.path.join(os.getcwd(), 'ground-truth') 48 | DR_PATH = os.path.join(os.getcwd(), 'detection-results') 49 | # if there are no images then no animation can be shown 50 | IMG_PATH = os.path.join(os.getcwd(), 'input', 'images-optional') 51 | if os.path.exists(IMG_PATH): 52 | for dirpath, dirnames, files in os.walk(IMG_PATH): 53 | if not files: 54 | # no image files found 55 | args.no_animation = True 56 | else: 57 | args.no_animation = True 58 | 59 | # try to import OpenCV if the user didn't choose the option --no-animation 60 | show_animation = False 61 | if not args.no_animation: 62 | try: 63 | import cv2 64 | show_animation = True 65 | except ImportError: 66 | print("\"opencv-python\" not found, please install to visualize the results.") 67 | args.no_animation = True 68 | 69 | # try to import Matplotlib if the user didn't choose the option --no-plot 70 | draw_plot = False 71 | if not args.no_plot: 72 | try: 73 | import matplotlib.pyplot as plt 74 | draw_plot = True 75 | except ImportError: 76 | print("\"matplotlib\" not found, please install it to get the resulting plots.") 77 | args.no_plot = True 78 | 79 | 80 | def log_average_miss_rate(precision, fp_cumsum, num_images): 81 | """ 82 | log-average miss rate: 83 | Calculated by averaging miss rates at 9 evenly spaced FPPI points 84 | between 10e-2 and 10e0, in log-space. 85 | 86 | output: 87 | lamr | log-average miss rate 88 | mr | miss rate 89 | fppi | false positives per image 90 | 91 | references: 92 | [1] Dollar, Piotr, et al. "Pedestrian Detection: An Evaluation of the 93 | State of the Art." Pattern Analysis and Machine Intelligence, IEEE 94 | Transactions on 34.4 (2012): 743 - 761. 95 | """ 96 | 97 | # if there were no detections of that class 98 | if precision.size == 0: 99 | lamr = 0 100 | mr = 1 101 | fppi = 0 102 | return lamr, mr, fppi 103 | 104 | fppi = fp_cumsum / float(num_images) 105 | mr = (1 - precision) 106 | 107 | fppi_tmp = np.insert(fppi, 0, -1.0) 108 | mr_tmp = np.insert(mr, 0, 1.0) 109 | 110 | # Use 9 evenly spaced reference points in log-space 111 | ref = np.logspace(-2.0, 0.0, num = 9) 112 | for i, ref_i in enumerate(ref): 113 | # np.where() will always find at least 1 index, since min(ref) = 0.01 and min(fppi_tmp) = -1.0 114 | j = np.where(fppi_tmp <= ref_i)[-1][-1] 115 | ref[i] = mr_tmp[j] 116 | 117 | # log(0) is undefined, so we use the np.maximum(1e-10, ref) 118 | lamr = math.exp(np.mean(np.log(np.maximum(1e-10, ref)))) 119 | 120 | return lamr, mr, fppi 121 | 122 | """ 123 | throw error and exit 124 | """ 125 | def error(msg): 126 | print(msg) 127 | sys.exit(0) 128 | 129 | """ 130 | check if the number is a float between 0.0 and 1.0 131 | """ 132 | def is_float_between_0_and_1(value): 133 | try: 134 | val = float(value) 135 | if val > 0.0 and val < 1.0: 136 | return True 137 | else: 138 | return False 139 | except ValueError: 140 | return False 141 | 142 | """ 143 | Calculate the AP given the recall and precision array 144 | 1st) We compute a version of the measured precision/recall curve with 145 | precision monotonically decreasing 146 | 2nd) We compute the AP as the area under this curve by numerical integration. 147 | """ 148 | def voc_ap(rec, prec): 149 | """ 150 | --- Official matlab code VOC2012--- 151 | mrec=[0 ; rec ; 1]; 152 | mpre=[0 ; prec ; 0]; 153 | for i=numel(mpre)-1:-1:1 154 | mpre(i)=max(mpre(i),mpre(i+1)); 155 | end 156 | i=find(mrec(2:end)~=mrec(1:end-1))+1; 157 | ap=sum((mrec(i)-mrec(i-1)).*mpre(i)); 158 | """ 159 | rec.insert(0, 0.0) # insert 0.0 at begining of list 160 | rec.append(1.0) # insert 1.0 at end of list 161 | mrec = rec[:] 162 | prec.insert(0, 0.0) # insert 0.0 at begining of list 163 | prec.append(0.0) # insert 0.0 at end of list 164 | mpre = prec[:] 165 | """ 166 | This part makes the precision monotonically decreasing 167 | (goes from the end to the beginning) 168 | matlab: for i=numel(mpre)-1:-1:1 169 | mpre(i)=max(mpre(i),mpre(i+1)); 170 | """ 171 | # matlab indexes start in 1 but python in 0, so I have to do: 172 | # range(start=(len(mpre) - 2), end=0, step=-1) 173 | # also the python function range excludes the end, resulting in: 174 | # range(start=(len(mpre) - 2), end=-1, step=-1) 175 | for i in range(len(mpre)-2, -1, -1): 176 | mpre[i] = max(mpre[i], mpre[i+1]) 177 | """ 178 | This part creates a list of indexes where the recall changes 179 | matlab: i=find(mrec(2:end)~=mrec(1:end-1))+1; 180 | """ 181 | i_list = [] 182 | for i in range(1, len(mrec)): 183 | if mrec[i] != mrec[i-1]: 184 | i_list.append(i) # if it was matlab would be i + 1 185 | """ 186 | The Average Precision (AP) is the area under the curve 187 | (numerical integration) 188 | matlab: ap=sum((mrec(i)-mrec(i-1)).*mpre(i)); 189 | """ 190 | ap = 0.0 191 | for i in i_list: 192 | ap += ((mrec[i]-mrec[i-1])*mpre[i]) 193 | return ap, mrec, mpre 194 | 195 | 196 | """ 197 | Convert the lines of a file to a list 198 | """ 199 | def file_lines_to_list(path): 200 | # open txt file lines to a list 201 | with open(path) as f: 202 | content = f.readlines() 203 | # remove whitespace characters like `\n` at the end of each line 204 | content = [x.strip() for x in content] 205 | return content 206 | 207 | """ 208 | Draws text in image 209 | """ 210 | def draw_text_in_image(img, text, pos, color, line_width): 211 | font = cv2.FONT_HERSHEY_PLAIN 212 | fontScale = 1 213 | lineType = 1 214 | bottomLeftCornerOfText = pos 215 | cv2.putText(img, text, 216 | bottomLeftCornerOfText, 217 | font, 218 | fontScale, 219 | color, 220 | lineType) 221 | text_width, _ = cv2.getTextSize(text, font, fontScale, lineType)[0] 222 | return img, (line_width + text_width) 223 | 224 | """ 225 | Plot - adjust axes 226 | """ 227 | def adjust_axes(r, t, fig, axes): 228 | # get text width for re-scaling 229 | bb = t.get_window_extent(renderer=r) 230 | text_width_inches = bb.width / fig.dpi 231 | # get axis width in inches 232 | current_fig_width = fig.get_figwidth() 233 | new_fig_width = current_fig_width + text_width_inches 234 | propotion = new_fig_width / current_fig_width 235 | # get axis limit 236 | x_lim = axes.get_xlim() 237 | axes.set_xlim([x_lim[0], x_lim[1]*propotion]) 238 | 239 | """ 240 | Draw plot using Matplotlib 241 | """ 242 | def draw_plot_func(dictionary, n_classes, window_title, plot_title, x_label, output_path, to_show, plot_color, true_p_bar): 243 | # sort the dictionary by decreasing value, into a list of tuples 244 | sorted_dic_by_value = sorted(dictionary.items(), key=operator.itemgetter(1)) 245 | # unpacking the list of tuples into two lists 246 | sorted_keys, sorted_values = zip(*sorted_dic_by_value) 247 | # 248 | if true_p_bar != "": 249 | """ 250 | Special case to draw in: 251 | - green -> TP: True Positives (object detected and matches ground-truth) 252 | - red -> FP: False Positives (object detected but does not match ground-truth) 253 | - orange -> FN: False Negatives (object not detected but present in the ground-truth) 254 | """ 255 | fp_sorted = [] 256 | tp_sorted = [] 257 | for key in sorted_keys: 258 | fp_sorted.append(dictionary[key] - true_p_bar[key]) 259 | tp_sorted.append(true_p_bar[key]) 260 | plt.barh(range(n_classes), fp_sorted, align='center', color='crimson', label='False Positive') 261 | plt.barh(range(n_classes), tp_sorted, align='center', color='forestgreen', label='True Positive', left=fp_sorted) 262 | # add legend 263 | plt.legend(loc='lower right') 264 | """ 265 | Write number on side of bar 266 | """ 267 | fig = plt.gcf() # gcf - get current figure 268 | axes = plt.gca() 269 | r = fig.canvas.get_renderer() 270 | for i, val in enumerate(sorted_values): 271 | fp_val = fp_sorted[i] 272 | tp_val = tp_sorted[i] 273 | fp_str_val = " " + str(fp_val) 274 | tp_str_val = fp_str_val + " " + str(tp_val) 275 | # trick to paint multicolor with offset: 276 | # first paint everything and then repaint the first number 277 | t = plt.text(val, i, tp_str_val, color='forestgreen', va='center', fontweight='bold') 278 | plt.text(val, i, fp_str_val, color='crimson', va='center', fontweight='bold') 279 | if i == (len(sorted_values)-1): # largest bar 280 | adjust_axes(r, t, fig, axes) 281 | else: 282 | plt.barh(range(n_classes), sorted_values, color=plot_color) 283 | """ 284 | Write number on side of bar 285 | """ 286 | fig = plt.gcf() # gcf - get current figure 287 | axes = plt.gca() 288 | r = fig.canvas.get_renderer() 289 | for i, val in enumerate(sorted_values): 290 | str_val = " " + str(val) # add a space before 291 | if val < 1.0: 292 | str_val = " {0:.2f}".format(val) 293 | t = plt.text(val, i, str_val, color=plot_color, va='center', fontweight='bold') 294 | # re-set axes to show number inside the figure 295 | if i == (len(sorted_values)-1): # largest bar 296 | adjust_axes(r, t, fig, axes) 297 | # set window title 298 | fig.canvas.set_window_title(window_title) 299 | # write classes in y axis 300 | tick_font_size = 12 301 | plt.yticks(range(n_classes), sorted_keys, fontsize=tick_font_size) 302 | """ 303 | Re-scale height accordingly 304 | """ 305 | init_height = fig.get_figheight() 306 | # comput the matrix height in points and inches 307 | dpi = fig.dpi 308 | height_pt = n_classes * (tick_font_size * 1.4) # 1.4 (some spacing) 309 | height_in = height_pt / dpi 310 | # compute the required figure height 311 | top_margin = 0.15 # in percentage of the figure height 312 | bottom_margin = 0.05 # in percentage of the figure height 313 | figure_height = height_in / (1 - top_margin - bottom_margin) 314 | # set new height 315 | if figure_height > init_height: 316 | fig.set_figheight(figure_height) 317 | 318 | # set plot title 319 | plt.title(plot_title, fontsize=14) 320 | # set axis titles 321 | # plt.xlabel('classes') 322 | plt.xlabel(x_label, fontsize='large') 323 | # adjust size of window 324 | fig.tight_layout() 325 | # save the plot 326 | fig.savefig(output_path) 327 | # show image 328 | if to_show: 329 | plt.show() 330 | # close the plot 331 | plt.close() 332 | 333 | """ 334 | Create a ".temp_files/" and "results/" directory 335 | """ 336 | TEMP_FILES_PATH = ".temp_files" 337 | if not os.path.exists(TEMP_FILES_PATH): # if it doesn't exist already 338 | os.makedirs(TEMP_FILES_PATH) 339 | results_files_path = "map-results" 340 | if os.path.exists(results_files_path): # if it exist already 341 | # reset the results directory 342 | shutil.rmtree(results_files_path) 343 | 344 | os.makedirs(results_files_path) 345 | if draw_plot: 346 | os.makedirs(os.path.join(results_files_path, "classes")) 347 | if show_animation: 348 | os.makedirs(os.path.join(results_files_path, "images", "detections_one_by_one")) 349 | 350 | """ 351 | ground-truth 352 | Load each of the ground-truth files into a temporary ".json" file. 353 | Create a list of all the class names present in the ground-truth (gt_classes). 354 | """ 355 | # get a list with the ground-truth files 356 | ground_truth_files_list = glob.glob(GT_PATH + '/*.txt') 357 | if len(ground_truth_files_list) == 0: 358 | error("Error: No ground-truth files found!") 359 | ground_truth_files_list.sort() 360 | # dictionary with counter per class 361 | gt_counter_per_class = {} 362 | counter_images_per_class = {} 363 | 364 | for txt_file in ground_truth_files_list: 365 | #print(txt_file) 366 | file_id = txt_file.split(".txt", 1)[0] 367 | file_id = os.path.basename(os.path.normpath(file_id)) 368 | # check if there is a correspondent detection-results file 369 | temp_path = os.path.join(DR_PATH, (file_id + ".txt")) 370 | if not os.path.exists(temp_path): 371 | error_msg = "Error. File not found: {}\n".format(temp_path) 372 | error_msg += "(You can avoid this error message by running extra/intersect-gt-and-dr.py)" 373 | error(error_msg) 374 | lines_list = file_lines_to_list(txt_file) 375 | # create ground-truth dictionary 376 | bounding_boxes = [] 377 | is_difficult = False 378 | already_seen_classes = [] 379 | for line in lines_list: 380 | try: 381 | if "difficult" in line: 382 | class_name, left, top, right, bottom, _difficult = line.split() 383 | is_difficult = True 384 | else: 385 | class_name, left, top, right, bottom = line.split() 386 | except ValueError: 387 | error_msg = "Error: File " + txt_file + " in the wrong format.\n" 388 | error_msg += " Expected: ['difficult']\n" 389 | error_msg += " Received: " + line 390 | error_msg += "\n\nIf you have a with spaces between words you should remove them\n" 391 | error_msg += "by running the script \"remove_space.py\" or \"rename_class.py\" in the \"extra/\" folder." 392 | error(error_msg) 393 | # check if class is in the ignore list, if yes skip 394 | if class_name in args.ignore: 395 | continue 396 | bbox = left + " " + top + " " + right + " " +bottom 397 | if is_difficult: 398 | bounding_boxes.append({"class_name":class_name, "bbox":bbox, "used":False, "difficult":True}) 399 | is_difficult = False 400 | else: 401 | bounding_boxes.append({"class_name":class_name, "bbox":bbox, "used":False}) 402 | # count that object 403 | if class_name in gt_counter_per_class: 404 | gt_counter_per_class[class_name] += 1 405 | else: 406 | # if class didn't exist yet 407 | gt_counter_per_class[class_name] = 1 408 | 409 | if class_name not in already_seen_classes: 410 | if class_name in counter_images_per_class: 411 | counter_images_per_class[class_name] += 1 412 | else: 413 | # if class didn't exist yet 414 | counter_images_per_class[class_name] = 1 415 | already_seen_classes.append(class_name) 416 | 417 | 418 | # dump bounding_boxes into a ".json" file 419 | with open(TEMP_FILES_PATH + "/" + file_id + "_ground_truth.json", 'w') as outfile: 420 | json.dump(bounding_boxes, outfile) 421 | 422 | gt_classes = list(gt_counter_per_class.keys()) 423 | # let's sort the classes alphabetically 424 | gt_classes = sorted(gt_classes) 425 | n_classes = len(gt_classes) 426 | #print(gt_classes) 427 | #print(gt_counter_per_class) 428 | 429 | """ 430 | Check format of the flag --set-class-iou (if used) 431 | e.g. check if class exists 432 | """ 433 | if specific_iou_flagged: 434 | n_args = len(args.set_class_iou) 435 | error_msg = \ 436 | '\n --set-class-iou [class_1] [IoU_1] [class_2] [IoU_2] [...]' 437 | if n_args % 2 != 0: 438 | error('Error, missing arguments. Flag usage:' + error_msg) 439 | # [class_1] [IoU_1] [class_2] [IoU_2] 440 | # specific_iou_classes = ['class_1', 'class_2'] 441 | specific_iou_classes = args.set_class_iou[::2] # even 442 | # iou_list = ['IoU_1', 'IoU_2'] 443 | iou_list = args.set_class_iou[1::2] # odd 444 | if len(specific_iou_classes) != len(iou_list): 445 | error('Error, missing arguments. Flag usage:' + error_msg) 446 | for tmp_class in specific_iou_classes: 447 | if tmp_class not in gt_classes: 448 | error('Error, unknown class \"' + tmp_class + '\". Flag usage:' + error_msg) 449 | for num in iou_list: 450 | if not is_float_between_0_and_1(num): 451 | error('Error, IoU must be between 0.0 and 1.0. Flag usage:' + error_msg) 452 | 453 | """ 454 | detection-results 455 | Load each of the detection-results files into a temporary ".json" file. 456 | """ 457 | # get a list with the detection-results files 458 | dr_files_list = glob.glob(DR_PATH + '/*.txt') 459 | dr_files_list.sort() 460 | 461 | for class_index, class_name in enumerate(gt_classes): 462 | bounding_boxes = [] 463 | for txt_file in dr_files_list: 464 | #print(txt_file) 465 | # the first time it checks if all the corresponding ground-truth files exist 466 | file_id = txt_file.split(".txt",1)[0] 467 | file_id = os.path.basename(os.path.normpath(file_id)) 468 | temp_path = os.path.join(GT_PATH, (file_id + ".txt")) 469 | if class_index == 0: 470 | if not os.path.exists(temp_path): 471 | error_msg = "Error. File not found: {}\n".format(temp_path) 472 | error_msg += "(You can avoid this error message by running extra/intersect-gt-and-dr.py)" 473 | error(error_msg) 474 | lines = file_lines_to_list(txt_file) 475 | for line in lines: 476 | try: 477 | tmp_class_name, confidence, left, top, right, bottom = line.split() 478 | except ValueError: 479 | error_msg = "Error: File " + txt_file + " in the wrong format.\n" 480 | error_msg += " Expected: \n" 481 | error_msg += " Received: " + line 482 | error(error_msg) 483 | if tmp_class_name == class_name: 484 | #print("match") 485 | bbox = left + " " + top + " " + right + " " +bottom 486 | bounding_boxes.append({"confidence":confidence, "file_id":file_id, "bbox":bbox}) 487 | #print(bounding_boxes) 488 | # sort detection-results by decreasing confidence 489 | bounding_boxes.sort(key=lambda x:float(x['confidence']), reverse=True) 490 | with open(TEMP_FILES_PATH + "/" + class_name + "_dr.json", 'w') as outfile: 491 | json.dump(bounding_boxes, outfile) 492 | 493 | """ 494 | Calculate the AP for each class 495 | """ 496 | sum_AP = 0.0 497 | ap_dictionary = {} 498 | lamr_dictionary = {} 499 | # open file to store the results 500 | with open(results_files_path + "/results.txt", 'w') as results_file: 501 | results_file.write("# AP and precision/recall per class\n") 502 | count_true_positives = {} 503 | for class_index, class_name in enumerate(gt_classes): 504 | count_true_positives[class_name] = 0 505 | """ 506 | Load detection-results of that class 507 | """ 508 | dr_file = TEMP_FILES_PATH + "/" + class_name + "_dr.json" 509 | dr_data = json.load(open(dr_file)) 510 | 511 | """ 512 | Assign detection-results to ground-truth objects 513 | """ 514 | nd = len(dr_data) 515 | tp = [0] * nd # creates an array of zeros of size nd 516 | fp = [0] * nd 517 | for idx, detection in enumerate(dr_data): 518 | file_id = detection["file_id"] 519 | if show_animation: 520 | # find ground truth image 521 | ground_truth_img = glob.glob1(IMG_PATH, file_id + ".*") 522 | #tifCounter = len(glob.glob1(myPath,"*.tif")) 523 | if len(ground_truth_img) == 0: 524 | error("Error. Image not found with id: " + file_id) 525 | elif len(ground_truth_img) > 1: 526 | error("Error. Multiple image with id: " + file_id) 527 | else: # found image 528 | #print(IMG_PATH + "/" + ground_truth_img[0]) 529 | # Load image 530 | img = cv2.imread(IMG_PATH + "/" + ground_truth_img[0]) 531 | # load image with draws of multiple detections 532 | img_cumulative_path = results_files_path + "/images/" + ground_truth_img[0] 533 | if os.path.isfile(img_cumulative_path): 534 | img_cumulative = cv2.imread(img_cumulative_path) 535 | else: 536 | img_cumulative = img.copy() 537 | # Add bottom border to image 538 | bottom_border = 60 539 | BLACK = [0, 0, 0] 540 | img = cv2.copyMakeBorder(img, 0, bottom_border, 0, 0, cv2.BORDER_CONSTANT, value=BLACK) 541 | # assign detection-results to ground truth object if any 542 | # open ground-truth with that file_id 543 | gt_file = TEMP_FILES_PATH + "/" + file_id + "_ground_truth.json" 544 | ground_truth_data = json.load(open(gt_file)) 545 | ovmax = -1 546 | gt_match = -1 547 | # load detected object bounding-box 548 | bb = [ float(x) for x in detection["bbox"].split() ] 549 | for obj in ground_truth_data: 550 | # look for a class_name match 551 | if obj["class_name"] == class_name: 552 | bbgt = [ float(x) for x in obj["bbox"].split() ] 553 | bi = [max(bb[0],bbgt[0]), max(bb[1],bbgt[1]), min(bb[2],bbgt[2]), min(bb[3],bbgt[3])] 554 | iw = bi[2] - bi[0] + 1 555 | ih = bi[3] - bi[1] + 1 556 | if iw > 0 and ih > 0: 557 | # compute overlap (IoU) = area of intersection / area of union 558 | ua = (bb[2] - bb[0] + 1) * (bb[3] - bb[1] + 1) + (bbgt[2] - bbgt[0] 559 | + 1) * (bbgt[3] - bbgt[1] + 1) - iw * ih 560 | ov = iw * ih / ua 561 | if ov > ovmax: 562 | ovmax = ov 563 | gt_match = obj 564 | 565 | # assign detection as true positive/don't care/false positive 566 | if show_animation: 567 | status = "NO MATCH FOUND!" # status is only used in the animation 568 | # set minimum overlap 569 | min_overlap = MINOVERLAP 570 | if specific_iou_flagged: 571 | if class_name in specific_iou_classes: 572 | index = specific_iou_classes.index(class_name) 573 | min_overlap = float(iou_list[index]) 574 | if ovmax >= min_overlap: 575 | if "difficult" not in gt_match: 576 | if not bool(gt_match["used"]): 577 | # true positive 578 | tp[idx] = 1 579 | gt_match["used"] = True 580 | count_true_positives[class_name] += 1 581 | # update the ".json" file 582 | with open(gt_file, 'w') as f: 583 | f.write(json.dumps(ground_truth_data)) 584 | if show_animation: 585 | status = "MATCH!" 586 | else: 587 | # false positive (multiple detection) 588 | fp[idx] = 1 589 | if show_animation: 590 | status = "REPEATED MATCH!" 591 | else: 592 | # false positive 593 | fp[idx] = 1 594 | if ovmax > 0: 595 | status = "INSUFFICIENT OVERLAP" 596 | 597 | """ 598 | Draw image to show animation 599 | """ 600 | if show_animation: 601 | height, widht = img.shape[:2] 602 | # colors (OpenCV works with BGR) 603 | white = (255,255,255) 604 | light_blue = (255,200,100) 605 | green = (0,255,0) 606 | light_red = (30,30,255) 607 | # 1st line 608 | margin = 10 609 | v_pos = int(height - margin - (bottom_border / 2.0)) 610 | text = "Image: " + ground_truth_img[0] + " " 611 | img, line_width = draw_text_in_image(img, text, (margin, v_pos), white, 0) 612 | text = "Class [" + str(class_index) + "/" + str(n_classes) + "]: " + class_name + " " 613 | img, line_width = draw_text_in_image(img, text, (margin + line_width, v_pos), light_blue, line_width) 614 | if ovmax != -1: 615 | color = light_red 616 | if status == "INSUFFICIENT OVERLAP": 617 | text = "IoU: {0:.2f}% ".format(ovmax*100) + "< {0:.2f}% ".format(min_overlap*100) 618 | else: 619 | text = "IoU: {0:.2f}% ".format(ovmax*100) + ">= {0:.2f}% ".format(min_overlap*100) 620 | color = green 621 | img, _ = draw_text_in_image(img, text, (margin + line_width, v_pos), color, line_width) 622 | # 2nd line 623 | v_pos += int(bottom_border / 2.0) 624 | rank_pos = str(idx+1) # rank position (idx starts at 0) 625 | text = "Detection #rank: " + rank_pos + " confidence: {0:.2f}% ".format(float(detection["confidence"])*100) 626 | img, line_width = draw_text_in_image(img, text, (margin, v_pos), white, 0) 627 | color = light_red 628 | if status == "MATCH!": 629 | color = green 630 | text = "Result: " + status + " " 631 | img, line_width = draw_text_in_image(img, text, (margin + line_width, v_pos), color, line_width) 632 | 633 | font = cv2.FONT_HERSHEY_SIMPLEX 634 | if ovmax > 0: # if there is intersections between the bounding-boxes 635 | bbgt = [ int(round(float(x))) for x in gt_match["bbox"].split() ] 636 | cv2.rectangle(img,(bbgt[0],bbgt[1]),(bbgt[2],bbgt[3]),light_blue,2) 637 | cv2.rectangle(img_cumulative,(bbgt[0],bbgt[1]),(bbgt[2],bbgt[3]),light_blue,2) 638 | cv2.putText(img_cumulative, class_name, (bbgt[0],bbgt[1] - 5), font, 0.6, light_blue, 1, cv2.LINE_AA) 639 | bb = [int(i) for i in bb] 640 | cv2.rectangle(img,(bb[0],bb[1]),(bb[2],bb[3]),color,2) 641 | cv2.rectangle(img_cumulative,(bb[0],bb[1]),(bb[2],bb[3]),color,2) 642 | cv2.putText(img_cumulative, class_name, (bb[0],bb[1] - 5), font, 0.6, color, 1, cv2.LINE_AA) 643 | # show image 644 | cv2.imshow("Animation", img) 645 | cv2.waitKey(20) # show for 20 ms 646 | # save image to results 647 | output_img_path = results_files_path + "/images/detections_one_by_one/" + class_name + "_detection" + str(idx) + ".jpg" 648 | cv2.imwrite(output_img_path, img) 649 | # save the image with all the objects drawn to it 650 | cv2.imwrite(img_cumulative_path, img_cumulative) 651 | 652 | #print(tp) 653 | # compute precision/recall 654 | cumsum = 0 655 | for idx, val in enumerate(fp): 656 | fp[idx] += cumsum 657 | cumsum += val 658 | cumsum = 0 659 | for idx, val in enumerate(tp): 660 | tp[idx] += cumsum 661 | cumsum += val 662 | #print(tp) 663 | rec = tp[:] 664 | for idx, val in enumerate(tp): 665 | rec[idx] = float(tp[idx]) / gt_counter_per_class[class_name] 666 | #print(rec) 667 | prec = tp[:] 668 | for idx, val in enumerate(tp): 669 | prec[idx] = float(tp[idx]) / (fp[idx] + tp[idx]) 670 | #print(prec) 671 | 672 | ap, mrec, mprec = voc_ap(rec[:], prec[:]) 673 | sum_AP += ap 674 | text = "{0:.2f}%".format(ap*100) + " = " + class_name + " AP " #class_name + " AP = {0:.2f}%".format(ap*100) 675 | """ 676 | Write to results.txt 677 | """ 678 | rounded_prec = [ '%.2f' % elem for elem in prec ] 679 | rounded_rec = [ '%.2f' % elem for elem in rec ] 680 | results_file.write(text + "\n Precision: " + str(rounded_prec) + "\n Recall :" + str(rounded_rec) + "\n\n") 681 | if not args.quiet: 682 | print(text) 683 | ap_dictionary[class_name] = ap 684 | 685 | n_images = counter_images_per_class[class_name] 686 | lamr, mr, fppi = log_average_miss_rate(np.array(rec), np.array(fp), n_images) 687 | lamr_dictionary[class_name] = lamr 688 | 689 | """ 690 | Draw plot 691 | """ 692 | if draw_plot: 693 | plt.plot(rec, prec, '-o') 694 | # add a new penultimate point to the list (mrec[-2], 0.0) 695 | # since the last line segment (and respective area) do not affect the AP value 696 | area_under_curve_x = mrec[:-1] + [mrec[-2]] + [mrec[-1]] 697 | area_under_curve_y = mprec[:-1] + [0.0] + [mprec[-1]] 698 | plt.fill_between(area_under_curve_x, 0, area_under_curve_y, alpha=0.2, edgecolor='r') 699 | # set window title 700 | fig = plt.gcf() # gcf - get current figure 701 | fig.canvas.set_window_title('AP ' + class_name) 702 | # set plot title 703 | plt.title('class: ' + text) 704 | #plt.suptitle('This is a somewhat long figure title', fontsize=16) 705 | # set axis titles 706 | plt.xlabel('Recall') 707 | plt.ylabel('Precision') 708 | # optional - set axes 709 | axes = plt.gca() # gca - get current axes 710 | axes.set_xlim([0.0,1.0]) 711 | axes.set_ylim([0.0,1.05]) # .05 to give some extra space 712 | # Alternative option -> wait for button to be pressed 713 | #while not plt.waitforbuttonpress(): pass # wait for key display 714 | # Alternative option -> normal display 715 | #plt.show() 716 | # save the plot 717 | fig.savefig(results_files_path + "/classes/" + class_name + ".png") 718 | plt.cla() # clear axes for next plot 719 | 720 | if show_animation: 721 | cv2.destroyAllWindows() 722 | 723 | results_file.write("\n# mAP of all classes\n") 724 | mAP = sum_AP / n_classes 725 | text = "mAP = {0:.2f}%".format(mAP*100) 726 | results_file.write(text + "\n") 727 | print(text) 728 | 729 | # remove the temp_files directory 730 | shutil.rmtree(TEMP_FILES_PATH) 731 | 732 | """ 733 | Count total of detection-results 734 | """ 735 | # iterate through all the files 736 | det_counter_per_class = {} 737 | for txt_file in dr_files_list: 738 | # get lines to list 739 | lines_list = file_lines_to_list(txt_file) 740 | for line in lines_list: 741 | class_name = line.split()[0] 742 | # check if class is in the ignore list, if yes skip 743 | if class_name in args.ignore: 744 | continue 745 | # count that object 746 | if class_name in det_counter_per_class: 747 | det_counter_per_class[class_name] += 1 748 | else: 749 | # if class didn't exist yet 750 | det_counter_per_class[class_name] = 1 751 | #print(det_counter_per_class) 752 | dr_classes = list(det_counter_per_class.keys()) 753 | 754 | 755 | """ 756 | Plot the total number of occurences of each class in the ground-truth 757 | """ 758 | if draw_plot: 759 | window_title = "ground-truth-info" 760 | plot_title = "ground-truth\n" 761 | plot_title += "(" + str(len(ground_truth_files_list)) + " files and " + str(n_classes) + " classes)" 762 | x_label = "Number of objects per class" 763 | output_path = results_files_path + "/ground-truth-info.png" 764 | to_show = False 765 | plot_color = 'forestgreen' 766 | draw_plot_func( 767 | gt_counter_per_class, 768 | n_classes, 769 | window_title, 770 | plot_title, 771 | x_label, 772 | output_path, 773 | to_show, 774 | plot_color, 775 | '', 776 | ) 777 | 778 | """ 779 | Write number of ground-truth objects per class to results.txt 780 | """ 781 | with open(results_files_path + "/results.txt", 'a') as results_file: 782 | results_file.write("\n# Number of ground-truth objects per class\n") 783 | for class_name in sorted(gt_counter_per_class): 784 | results_file.write(class_name + ": " + str(gt_counter_per_class[class_name]) + "\n") 785 | 786 | """ 787 | Finish counting true positives 788 | """ 789 | for class_name in dr_classes: 790 | # if class exists in detection-detection-results but not in ground-truth then there are no true positives in that class 791 | if class_name not in gt_classes: 792 | count_true_positives[class_name] = 0 793 | #print(count_true_positives) 794 | 795 | """ 796 | Plot the total number of occurences of each class in the "detection-results" folder 797 | """ 798 | if draw_plot: 799 | window_title = "detection-results-info" 800 | # Plot title 801 | plot_title = "detection-results\n" 802 | plot_title += "(" + str(len(dr_files_list)) + " files and " 803 | count_non_zero_values_in_dictionary = sum(int(x) > 0 for x in list(det_counter_per_class.values())) 804 | plot_title += str(count_non_zero_values_in_dictionary) + " detected classes)" 805 | # end Plot title 806 | x_label = "Number of objects per class" 807 | output_path = results_files_path + "/detection-results-info.png" 808 | to_show = False 809 | plot_color = 'forestgreen' 810 | true_p_bar = count_true_positives 811 | draw_plot_func( 812 | det_counter_per_class, 813 | len(det_counter_per_class), 814 | window_title, 815 | plot_title, 816 | x_label, 817 | output_path, 818 | to_show, 819 | plot_color, 820 | true_p_bar 821 | ) 822 | 823 | """ 824 | Write number of detected objects per class to results.txt 825 | """ 826 | with open(results_files_path + "/results.txt", 'a') as results_file: 827 | results_file.write("\n# Number of detected objects per class\n") 828 | for class_name in sorted(dr_classes): 829 | n_det = det_counter_per_class[class_name] 830 | text = class_name + ": " + str(n_det) 831 | text += " (tp:" + str(count_true_positives[class_name]) + "" 832 | text += ", fp:" + str(n_det - count_true_positives[class_name]) + ")\n" 833 | results_file.write(text) 834 | 835 | """ 836 | Draw log-average miss rate plot (Show lamr of all classes in decreasing order) 837 | """ 838 | if draw_plot: 839 | window_title = "lamr" 840 | plot_title = "log-average miss rate" 841 | x_label = "log-average miss rate" 842 | output_path = results_files_path + "/lamr.png" 843 | to_show = False 844 | plot_color = 'royalblue' 845 | draw_plot_func( 846 | lamr_dictionary, 847 | n_classes, 848 | window_title, 849 | plot_title, 850 | x_label, 851 | output_path, 852 | to_show, 853 | plot_color, 854 | "" 855 | ) 856 | 857 | """ 858 | Draw mAP plot (Show AP's of all classes in decreasing order) 859 | """ 860 | if draw_plot: 861 | window_title = "mAP" 862 | plot_title = "mAP = {0:.2f}%".format(mAP*100) 863 | x_label = "Average Precision" 864 | output_path = results_files_path + "/mAP.png" 865 | to_show = True 866 | plot_color = 'royalblue' 867 | draw_plot_func( 868 | ap_dictionary, 869 | n_classes, 870 | window_title, 871 | plot_title, 872 | x_label, 873 | output_path, 874 | to_show, 875 | plot_color, 876 | "" 877 | ) 878 | -------------------------------------------------------------------------------- /mAP/detect.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/29 3 | # @File : detect.py 4 | # @Software: PyCharm 5 | 6 | import tensorflow as tf 7 | import numpy as np 8 | import os, cv2, tqdm, sys 9 | from absl import app, flags, logging 10 | from absl.flags import FLAGS 11 | from tensorflow.keras.models import load_model 12 | import xml.etree.ElementTree as ET 13 | 14 | rootPath = os.path.split(os.path.abspath(os.path.dirname(__file__)))[0] 15 | sys.path.append(rootPath) 16 | 17 | from components import config 18 | 19 | from network.network import SlimModel 20 | from components.prior_box import priors_box 21 | from components.utils import decode_bbox_tf, compute_nms 22 | 23 | flags.DEFINE_string('model_path', 'checkpoints/', 'VOC format dataset') 24 | flags.DEFINE_string('dataset_path', 'Maskdata', 'VOC format dataset') 25 | flags.DEFINE_enum('split', 'val', ['val', 'trainval'], 'val or test dataset') 26 | flags.DEFINE_list('image_size', [240, 320], 'single scale for model test') 27 | 28 | 29 | def parse_predict(predictions, priors, cfg): 30 | label_classes = cfg['labels_list'] 31 | 32 | bbox_regressions, confs = tf.split(predictions[0], [4, -1], axis=-1) 33 | boxes = decode_bbox_tf(bbox_regressions, priors, cfg['variances']) 34 | ##classifications shape :(num_priors,num_classes) 35 | 36 | confs = tf.math.softmax(confs, axis=-1) 37 | 38 | out_boxes = [] 39 | out_labels = [] 40 | out_scores = [] 41 | 42 | for c in range(1, len(label_classes)): 43 | cls_scores = confs[:, c] 44 | 45 | score_idx = cls_scores > 0.02 # cfg['score_threshold'] 46 | 47 | cls_boxes = boxes[score_idx] 48 | cls_scores = cls_scores[score_idx] 49 | 50 | nms_idx = compute_nms(cls_boxes, cls_scores, cfg['nms_threshold'], cfg['max_number_keep']) 51 | 52 | cls_boxes = tf.gather(cls_boxes, nms_idx) 53 | cls_scores = tf.gather(cls_scores, nms_idx) 54 | 55 | cls_labels = [c] * cls_boxes.shape[0] 56 | 57 | out_boxes.append(cls_boxes) 58 | out_labels.extend(cls_labels) 59 | out_scores.append(cls_scores) 60 | 61 | out_boxes = tf.concat(out_boxes, axis=0) 62 | out_scores = tf.concat(out_scores, axis=0) 63 | 64 | boxes = tf.clip_by_value(out_boxes, 0.0, 1.0).numpy() 65 | classes = np.array(out_labels) 66 | scores = out_scores.numpy() 67 | 68 | return boxes, classes, scores 69 | 70 | 71 | def main(_): 72 | dataset_path = FLAGS.dataset_path 73 | 74 | if not os.path.isdir(dataset_path): 75 | logging.info('Please define valid dataset path.') 76 | else: 77 | logging.info('Loading {}'.format(dataset_path)) 78 | 79 | detect_reslut_dir = 'mAP/detection-results/' 80 | if not os.path.exists(detect_reslut_dir): 81 | os.makedirs(detect_reslut_dir) 82 | 83 | for file in os.listdir(detect_reslut_dir): 84 | path_file = os.path.join(detect_reslut_dir + file) 85 | if os.path.isfile(path_file): 86 | os.remove(path_file) 87 | 88 | ground_thuth_dir = 'mAP/ground-truth/' 89 | if not os.path.exists(ground_thuth_dir): 90 | os.makedirs(ground_thuth_dir) 91 | 92 | for file in os.listdir(ground_thuth_dir): 93 | path_file = os.path.join(ground_thuth_dir + file) 94 | if os.path.isfile(path_file): 95 | os.remove(path_file) 96 | 97 | logging.info('Reading configuration...') 98 | cfg = config.cfg 99 | class_list = cfg['labels_list'] 100 | 101 | image_size = tuple(FLAGS.image_size) 102 | 103 | logging.info("Class dictionary loaded: %s", class_list) 104 | 105 | priors, num_cell = priors_box(cfg, image_size) 106 | priors = tf.cast(priors, tf.float32) 107 | 108 | try: 109 | model = load_model(FLAGS.model_path) 110 | except: 111 | model = SlimModel(cfg=cfg, num_cell=num_cell, training=False) 112 | paths = [os.path.join(FLAGS.model_path, path) 113 | for path in os.listdir(FLAGS.model_path)] 114 | latest = sorted(paths, key=os.path.getmtime)[-1] 115 | model.load_weights(latest) 116 | print(f"model path : {latest}") 117 | 118 | img_list = open( 119 | os.path.join(FLAGS.dataset_path, 'ImageSets', 'Main', '%s.txt' % FLAGS.split)).read().splitlines() 120 | logging.info("Image list loaded: %d", len(img_list)) 121 | 122 | for image in tqdm.tqdm(img_list): 123 | 124 | image_file = os.path.join(FLAGS.dataset_path, 'JPEGImages', '%s.jpg' % image) 125 | annot_file = os.path.join(FLAGS.dataset_path, 'Annotations', '%s.xml' % image) 126 | 127 | # detect image 128 | img_raw = cv2.imread(image_file) 129 | img_height_raw, img_width_raw, _ = img_raw.shape 130 | img = np.float32(img_raw.copy()) 131 | img = cv2.resize(img, (image_size[1], image_size[0])) # cv2.resize 132 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 133 | img = (img / 255.0 - 0.5) / 1.0 134 | predictions = model.predict(img[np.newaxis, ...]) 135 | 136 | boxes, classes, scores = parse_predict(predictions, priors, cfg) 137 | with open(detect_reslut_dir + f'{image}.txt', "a") as new_f: 138 | for prior_index in range(len(boxes)): 139 | x1, y1, x2, y2 = (boxes[prior_index][0] * img_width_raw), (boxes[prior_index][1] * img_height_raw), \ 140 | (boxes[prior_index][2] * img_width_raw), (boxes[prior_index][3] * img_height_raw) 141 | 142 | top = max(0, np.floor(y1 + 0.5).astype('int32')) 143 | left = max(0, np.floor(x1 + 0.5).astype('int32')) 144 | bottom = min(img_width_raw, np.floor(y2 + 0.5).astype('int32')) 145 | right = min(img_height_raw, np.floor(x2 + 0.5).astype('int32')) 146 | 147 | class_name = class_list[classes[prior_index]] 148 | score = "{:.2f}".format(scores[prior_index]) 149 | label = '{} {}'.format(class_name, score) 150 | new_f.write("%s %s %s %s %s\n" % (label, left, top, right, bottom)) 151 | 152 | # ground truth 153 | with open(ground_thuth_dir + f'{image}.txt', 'a') as gt_f: 154 | tree = ET.parse(annot_file) 155 | root = tree.getroot() 156 | 157 | for obj in root.iter('object'): 158 | difficult = obj.find('difficult') 159 | if not difficult: 160 | difficult = '0' 161 | else: 162 | difficult = difficult.text 163 | cls = obj.find('name').text 164 | 165 | xmlbox = obj.find('bndbox') 166 | bbox = (int(xmlbox.find('xmin').text), int(xmlbox.find('ymin').text), 167 | int(xmlbox.find('xmax').text), int(xmlbox.find('ymax').text)) 168 | gt_f.write(cls + ' ' + " ".join([str(a) for a in bbox]) + '\n') 169 | 170 | 171 | if __name__ == '__main__': 172 | # os.environ['CUDA_VISIBLE_DEVICES'] = '-1' 173 | try: 174 | app.run(main) 175 | except Exception as e: 176 | print(e) 177 | exit() 178 | -------------------------------------------------------------------------------- /network/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/21 3 | # @File : __init__.py.py 4 | # @Software: PyCharm -------------------------------------------------------------------------------- /network/losses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/19 3 | # @File : losses.py 4 | # @Software: PyCharm 5 | 6 | import tensorflow as tf 7 | 8 | 9 | def _smooth_l1_loss(y_true, y_pred): 10 | t = tf.abs(y_pred - y_true) 11 | return tf.where(t < 1, 0.5 * t ** 2, t - 0.5) 12 | 13 | 14 | def sigmoid_focal_loss(y_true, y_pred, alpha=0.25, gamma=2.0): 15 | pass 16 | 17 | 18 | def hard_negative_mining(loss, class_truth, neg_ratio): 19 | """ Hard negative mining algorithm 20 | to pick up negative examples for back-propagation 21 | base on classification loss values 22 | Args: 23 | loss: list of classification losses of all default boxes (B, num_default) 24 | class_truth: classification targets (B, num_default) 25 | neg_ratio: negative / positive ratio 26 | Returns: 27 | class_loss: classification loss 28 | loc_loss: regression loss 29 | """ 30 | # loss: B x N 31 | # class_truth: B x N 32 | pos_idx = class_truth > 0 33 | num_pos = tf.reduce_sum(tf.dtypes.cast(pos_idx, tf.int32), axis=1) 34 | num_neg = num_pos * neg_ratio 35 | 36 | rank = tf.argsort(loss, axis=1, direction='DESCENDING') 37 | rank = tf.argsort(rank, axis=1) 38 | neg_idx = rank < tf.expand_dims(num_neg, 1) 39 | 40 | return pos_idx, neg_idx 41 | 42 | 43 | def MultiBoxLoss(num_class=3, neg_pos_ratio=3.0): 44 | def multi_loss(y_true, y_pred): 45 | """ Compute losses for SSD 46 | regression loss: smooth L1 47 | classification loss: cross entropy 48 | Args: 49 | y_true: [B,N,5] 50 | y_pred: [B,N,num_class] 51 | class_pred: outputs of classification heads (B,N, num_classes) 52 | loc_pred: outputs of regression heads (B,N, 4) 53 | class_truth: classification targets (B,N) 54 | loc_truth: regression targets (B,N, 4) 55 | Returns: 56 | class_loss: classification loss 57 | loc_loss: regression loss 58 | """ 59 | num_batch = tf.shape(y_true)[0] 60 | num_prior = tf.shape(y_true)[1] 61 | loc_pred, class_pred = y_pred[..., :4], y_pred[..., 4:] 62 | loc_truth, class_truth = y_true[..., :4], tf.squeeze(y_true[..., 4:]) 63 | # print(f"loc_pred{y_pred}") 64 | cross_entropy = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none') 65 | 66 | # compute classification losses without reduction 67 | temp_loss = cross_entropy(class_truth, class_pred) 68 | # 2. hard negative mining 69 | pos_idx, neg_idx = hard_negative_mining(temp_loss, class_truth, neg_pos_ratio) 70 | 71 | # classification loss will consist of positive and negative examples 72 | cross_entropy = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='sum') 73 | 74 | smooth_l1_loss = tf.keras.losses.Huber(reduction='sum') 75 | 76 | loss_class = cross_entropy( 77 | class_truth[tf.math.logical_or(pos_idx, neg_idx)], 78 | class_pred[tf.math.logical_or(pos_idx, neg_idx)]) 79 | 80 | # localization loss only consist of positive examples (smooth L1) 81 | loss_loc = smooth_l1_loss(loc_truth[pos_idx],loc_pred[pos_idx]) 82 | 83 | num_pos = tf.reduce_sum(tf.dtypes.cast(pos_idx, tf.float32)) 84 | 85 | loss_class = loss_class / num_pos 86 | loss_loc = loss_loc / num_pos 87 | return loss_loc, loss_class 88 | 89 | return multi_loss 90 | -------------------------------------------------------------------------------- /network/network.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/4/20 3 | # @File : network.py 4 | # @Software: PyCharm 5 | 6 | 7 | import tensorflow as tf 8 | 9 | ###RFB 10 | def _BasicConv(x, out_planes, kernel_size, stride=1, padding='same', dilation=1, activation=1, use_l2=False, 11 | use_bn=True, use_bias=False): 12 | ''' 13 | Basic Convolution Layer 14 | ''' 15 | l2_reg = tf.keras.regularizers.l2(0.005) if use_l2 else None 16 | 17 | if use_bn: 18 | 19 | x = tf.keras.layers.Conv2D(out_planes, kernel_size=kernel_size, strides=stride, padding=padding, 20 | dilation_rate=dilation, use_bias=use_bias, 21 | kernel_regularizer=l2_reg, 22 | data_format='channels_last')(x) 23 | 24 | x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1e-5, momentum=0.99)(x) 25 | 26 | x = tf.keras.layers.ReLU()(x) if activation else x 27 | else: 28 | x = tf.keras.layers.Conv2D(out_planes, kernel_size=kernel_size, strides=stride, padding=padding, 29 | dilation_rate=dilation, use_bias=True, 30 | kernel_regularizer=l2_reg, 31 | data_format='channels_last')(x) 32 | x = tf.keras.layers.ReLU()(x) if activation else x 33 | 34 | return x 35 | 36 | 37 | def _BasicSepConv(x, out_planes, kernel_size, stride=1, padding='same', dilation=1, activation=1, use_l2=False, 38 | use_bn=True, use_bias=False): 39 | ''' 40 | Seperable Convolution Layer 41 | ''' 42 | l2_reg = tf.keras.regularizers.l2(0.005) if use_l2 else None 43 | 44 | if use_bn: 45 | x = tf.keras.layers.SeparableConv2D(out_planes, kernel_size=kernel_size, strides=stride, padding=padding, 46 | kernel_regularizer=l2_reg, 47 | dilation_rate=dilation, 48 | use_bias=use_bias, data_format='channels_last')(x) 49 | x = tf.keras.layers.BatchNormalization(axis=-1, epsilon=1e-5, momentum=0.01)(x) 50 | x = tf.keras.layers.ReLU()(x) if activation else x 51 | 52 | else: 53 | x = tf.keras.layers.SeparableConv2D(out_planes, kernel_size=kernel_size, strides=stride, padding=padding, 54 | kernel_regularizer=l2_reg, 55 | dilation_rate=dilation, 56 | use_bias=True, data_format='channels_last')(x) 57 | x = tf.keras.layers.ReLU()(x) if activation else x 58 | 59 | return x 60 | 61 | 62 | def BasicRFB(x, out_planes, stride=1, scale=0.1): 63 | ''' 64 | Basic RFB module 65 | Modified: 66 | 1. All padding used same 67 | 2. Add pooling to shortcut to match stride 68 | ''' 69 | # scale = (scale,scale,scale,scales 70 | in_planes = x.get_shape().as_list()[3] 71 | inter_planes = in_planes // 8 72 | 73 | # original branch 0' 74 | x0 = _BasicConv(x, 2 * inter_planes, kernel_size=1, stride=stride) 75 | x0 = _BasicConv(x0, 2 * inter_planes, kernel_size=3, stride=1, padding='same', dilation=1, activation=0) 76 | 77 | # original branch 1' 78 | x1 = _BasicConv(x, inter_planes, kernel_size=1, stride=1) 79 | x1 = _BasicConv(x1, (inter_planes // 2) * 3, kernel_size=(1, 3), stride=1, padding='same') 80 | x1 = _BasicConv(x1, (inter_planes // 2) * 3, kernel_size=(3, 1), stride=stride, padding='same') 81 | x1 = _BasicSepConv(x1, (inter_planes // 2) * 3, kernel_size=3, stride=1, padding='same', dilation=3, activation=0) 82 | 83 | # original branch 2 84 | x2 = _BasicConv(x, inter_planes, kernel_size=1, stride=1) 85 | x2 = _BasicConv(x2, (inter_planes // 2) * 3, kernel_size=3, stride=1, padding='same') 86 | x2 = _BasicConv(x2, (inter_planes // 2) * 3, kernel_size=3, stride=stride, padding='same') 87 | x2 = _BasicSepConv(x2, (inter_planes // 2) * 3, kernel_size=3, stride=1, dilation=5, activation=0) 88 | 89 | out = tf.keras.layers.Concatenate(axis=-1)([x0, x1, x2]) 90 | 91 | # Original Conv Linear 92 | out = _BasicConv(out, out_planes, kernel_size=1, stride=1, activation=0) 93 | out = tf.keras.layers.Lambda(lambda x: x * scale)(out) 94 | if in_planes != out_planes: 95 | x = _BasicConv(x, out_planes, kernel_size=1, stride=1, activation=0, use_bn=False) 96 | if stride != 1: 97 | x = tf.keras.layers.MaxPooling2D(stride, padding='same')(x) 98 | out = tf.keras.layers.Add()([out, x]) 99 | out = tf.keras.layers.Activation('relu')(out) 100 | return out 101 | 102 | 103 | def _conv_block(inputs, filters, kernel=(3, 3), strides=(1, 1), use_bn=True, padding=None, block_id=None): 104 | """Adds an initial convolution layer (with batch normalization and relu). 105 | # Returns 106 | Output tensor of block. 107 | """ 108 | if block_id is None: 109 | block_id = (tf.keras.backend.get_uid()) 110 | 111 | if strides == (2, 2): 112 | x = tf.keras.layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name='conv_pad_%d' % block_id)(inputs) 113 | x = tf.keras.layers.Conv2D(filters, kernel, 114 | padding='valid', 115 | use_bias=False if use_bn else True, 116 | strides=strides, 117 | name='conv_%d' % block_id)(x) 118 | else: 119 | x = tf.keras.layers.Conv2D(filters, kernel, 120 | padding='same', 121 | use_bias=False if use_bn else True, 122 | strides=strides, 123 | name='conv_%d' % block_id)(inputs) 124 | if use_bn: 125 | x = tf.keras.layers.BatchNormalization(name='conv_bn_%d' % block_id)(x) 126 | return tf.keras.layers.ReLU(name='conv_relu_%d' % block_id)(x) 127 | 128 | 129 | def _depthwise_conv_block(inputs, pointwise_conv_filters, 130 | depth_multiplier=1, strides=(1, 1), use_bn=True, block_id=None): 131 | """Adds a depthwise convolution block. 132 | # Returns 133 | Output tensor of block. 134 | """ 135 | if block_id is None: 136 | block_id = (tf.keras.backend.get_uid()) 137 | 138 | if strides == (1, 1): 139 | x = inputs 140 | else: 141 | x = tf.keras.layers.ZeroPadding2D(((1, 1), (1, 1)), name='conv_pad_%d' % block_id)(inputs) 142 | 143 | x = tf.keras.layers.DepthwiseConv2D((3, 3), 144 | padding='same' if strides == (1, 1) else 'valid', 145 | depth_multiplier=depth_multiplier, 146 | strides=strides, 147 | use_bias=False if use_bn else True, 148 | name='conv_dw_%d' % block_id)(x) 149 | if use_bn: 150 | x = tf.keras.layers.BatchNormalization(name='conv_dw_%d_bn' % block_id)(x) 151 | x = tf.keras.layers.ReLU(name='conv_dw_%d_relu' % block_id)(x) 152 | 153 | x = tf.keras.layers.Conv2D(pointwise_conv_filters, (1, 1), 154 | padding='same', 155 | use_bias=False if use_bn else True, 156 | strides=(1, 1), 157 | name='conv_pw_%d' % block_id)(x) 158 | if use_bn: 159 | x = tf.keras.layers.BatchNormalization(name='conv_pw_%d_bn' % block_id)(x) 160 | return tf.keras.layers.ReLU(name='conv_pw_%d_relu' % block_id)(x) 161 | 162 | 163 | def _create_head_block(inputs, filters, strides=(1, 1), block_id=None): 164 | 165 | x = tf.keras.layers.Conv2D(filters, kernel_size=(3, 3), strides=strides, padding='same')(inputs) 166 | 167 | return x 168 | 169 | 170 | def _branch_block(input, filters): 171 | 172 | x = tf.keras.layers.Conv2D(filters, kernel_size=(3, 3), padding='same')(input) 173 | x = tf.keras.layers.LeakyReLU()(x) 174 | x = tf.keras.layers.Conv2D(filters, kernel_size=(3, 3), padding='same')(x) 175 | 176 | x1 = tf.keras.layers.Conv2D(filters * 2, kernel_size=(3, 3), padding='same')(input) 177 | 178 | x = tf.keras.layers.Concatenate(axis=-1)([x, x1]) 179 | 180 | return tf.keras.layers.ReLU()(x) 181 | 182 | 183 | def _compute_heads(x, idx, num_class, num_cell): 184 | """ Compute outputs of classification and regression heads 185 | Args: 186 | x: the input feature map 187 | idx: index of the head layer 188 | Returns: 189 | conf: output of the idx-th classification head 190 | loc: output of the idx-th regression head 191 | """ 192 | conf = _create_head_block(inputs=x, filters=num_cell[idx] * num_class) 193 | conf = tf.keras.layers.Reshape((-1, num_class))(conf) 194 | loc = _create_head_block(inputs=x, filters=num_cell[idx] * 4) 195 | loc = tf.keras.layers.Reshape((-1, 4))(loc) 196 | 197 | return conf, loc 198 | 199 | 200 | def SlimModel(cfg, num_cell, training=False, name='slim_model'): 201 | image_sizes = cfg['input_size'] if training else None 202 | if isinstance(image_sizes, int): 203 | image_sizes = (image_sizes, image_sizes) 204 | elif isinstance(image_sizes, tuple): 205 | image_sizes = image_sizes 206 | elif image_sizes == None: 207 | image_sizes = (None, None) 208 | else: 209 | raise Exception('Type error of input image size format,tuple or int. ') 210 | 211 | base_channel = cfg["base_channel"] 212 | num_class = len(cfg['labels_list']) 213 | 214 | x = inputs = tf.keras.layers.Input(shape=[image_sizes[0], image_sizes[1], 3], name='input_image') 215 | 216 | x = _conv_block(x, base_channel, strides=(2, 2)) # 120*160*16 217 | x = _conv_block(x, base_channel * 2, strides=(1, 1)) 218 | x = _conv_block(x, base_channel * 2, strides=(2, 2)) # 60*80 219 | x = _conv_block(x, base_channel * 2, strides=(1, 1)) 220 | x = _conv_block(x, base_channel * 4, strides=(2, 2)) # 30*40 221 | x = _conv_block(x, base_channel * 4, strides=(1, 1)) 222 | x = _conv_block(x, base_channel * 4, strides=(1, 1)) 223 | x = _conv_block(x, base_channel * 4, strides=(1, 1)) 224 | # x = BasicRFB(x, base_channel * 4, stride=1, scale=1.0) 225 | x1 = _branch_block(x, base_channel) 226 | 227 | x = _conv_block(x, base_channel * 8, strides=(2, 2)) # 15*20 228 | x = _conv_block(x, base_channel * 8, strides=(1, 1)) 229 | x = _conv_block(x, base_channel * 8, strides=(1, 1)) 230 | x2 = _branch_block(x, base_channel) 231 | 232 | x = _depthwise_conv_block(x, base_channel * 16, strides=(2, 2)) # 8*10 233 | x = _depthwise_conv_block(x, base_channel * 16, strides=(1, 1)) 234 | x3 = _branch_block(x, base_channel) 235 | 236 | x = _depthwise_conv_block(x, base_channel * 16, strides=(2, 2)) # 4*5 237 | x4 = _branch_block(x, base_channel) 238 | 239 | extra_layers = [x1, x2, x3,x4] 240 | 241 | confs = [] 242 | locs = [] 243 | 244 | head_idx = 0 245 | assert len(extra_layers) == len(num_cell) 246 | for layer in extra_layers: 247 | conf, loc = _compute_heads(layer, head_idx, num_class, num_cell) 248 | confs.append(conf) 249 | locs.append(loc) 250 | 251 | head_idx += 1 252 | 253 | confs = tf.keras.layers.Concatenate(axis=1, name="face_classes")(confs) 254 | locs = tf.keras.layers.Concatenate(axis=1, name="face_boxes")(locs) 255 | 256 | predictions = tf.keras.layers.Concatenate(axis=2, name='predictions')([locs, confs]) 257 | 258 | 259 | model = tf.keras.Model(inputs=inputs, outputs=predictions, name=name) 260 | return model 261 | 262 | 263 | if __name__ == '__main__': 264 | from components import config 265 | import os 266 | 267 | cfg = config.cfg 268 | os.environ['CUDA_VISIBLE_DEVICES'] = '-1' 269 | model = SlimModel(cfg, num_cell=[3, 2, 2,3],training=False) 270 | print(len(model.layers)) 271 | model.summary() 272 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv_contrib_python==4.2.0.32 2 | tqdm==4.43.0 3 | numpy==1.18.1 4 | lxml==4.5.0 5 | absl_py==0.9.0 6 | tensorflow_gpu==2.1.2 7 | matplotlib==3.1.3 8 | absl==0.0 9 | scikit_learn==0.22.2.post1 10 | tensorflow==2.1.2 11 | -------------------------------------------------------------------------------- /train.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # @Time : 2020/3/24 3 | # @File : train.py 4 | # @Software: PyCharm 5 | import os 6 | import sys 7 | import time 8 | 9 | import tensorflow as tf 10 | from absl import flags, logging, app 11 | from absl.flags import FLAGS 12 | 13 | from components import config 14 | from components.lr_scheduler import MultiStepWarmUpLR 15 | from components.prior_box import priors_box 16 | from components.utils import set_memory_growth 17 | from dataset.tf_dataset_preprocess import load_dataset 18 | from network.losses import MultiBoxLoss 19 | from network.network import SlimModel 20 | 21 | flags.DEFINE_string('gpu', '0', 'which gpu to use') 22 | 23 | 24 | def main(_): 25 | global load_t1 26 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' 27 | os.environ['CUDA_VISIBLE_DEVICES'] = FLAGS.gpu # CPU:'-1' 28 | logger = tf.get_logger() 29 | logger.disabled = True 30 | logger.setLevel(logging.FATAL) 31 | set_memory_growth() 32 | 33 | weights_dir = 'checkpoints/' 34 | if not os.path.exists(weights_dir): 35 | os.mkdir(weights_dir) 36 | # if os.path.exists('logs'): 37 | # shutil.rmtree('logs') 38 | 39 | logging.info("Load configuration...") 40 | cfg = config.cfg 41 | label_classes = cfg['labels_list'] 42 | logging.info(f"Total image sample:{cfg['dataset_len']},Total classes number:" 43 | f"{len(label_classes)},classes list:{label_classes}") 44 | 45 | logging.info("Compute priors boxes...") 46 | priors, num_cell = priors_box(cfg) 47 | logging.info(f"Prior boxes number:{len(priors)},default anchor box number per feature map cell:{num_cell}") 48 | 49 | logging.info("Loading dataset...") 50 | train_dataset = load_dataset(cfg, priors, shuffle=True, train=True) 51 | # val_dataset = load_dataset(cfg, priors, shuffle=False, train=False) 52 | 53 | logging.info("Create Model...") 54 | try: 55 | model = SlimModel(cfg=cfg, num_cell=num_cell, training=True) 56 | model.summary() 57 | tf.keras.utils.plot_model(model, to_file=os.path.join(os.getcwd(), 'model.png'), 58 | show_shapes=True, show_layer_names=True) 59 | except Exception as e: 60 | logging.error(e) 61 | logging.info("Create network failed.") 62 | sys.exit() 63 | 64 | if cfg['resume']: 65 | # Training from latest weights 66 | paths = [os.path.join(weights_dir, path) 67 | for path in os.listdir(weights_dir)] 68 | latest = sorted(paths, key=os.path.getmtime)[-1] 69 | model.load_weights(latest) 70 | init_epoch = int(os.path.splitext(latest)[0][-3:]) 71 | 72 | else: 73 | # Training from scratch 74 | init_epoch = -1 75 | 76 | steps_per_epoch = cfg['dataset_len'] // cfg['batch_size'] 77 | # val_steps_per_epoch = cfg['val_len'] // cfg['batch_size'] 78 | 79 | logging.info(f"steps_per_epoch:{steps_per_epoch}") 80 | 81 | logging.info("Define optimizer and loss computation and so on...") 82 | 83 | # learning_rate =tf.keras.optimizers.schedules.ExponentialDecay(initial_learning_rate=1e-3, 84 | # decay_steps=20000, 85 | # decay_rate=0.96) 86 | # optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate) 87 | learning_rate = MultiStepWarmUpLR( 88 | initial_learning_rate=cfg['init_lr'], 89 | lr_steps=[e * steps_per_epoch for e in cfg['lr_decay_epoch']], 90 | lr_rate=cfg['lr_rate'], 91 | warmup_steps=cfg['warmup_epoch'] * steps_per_epoch, 92 | min_lr=cfg['min_lr']) 93 | 94 | optimizer = tf.keras.optimizers.SGD(learning_rate=learning_rate, momentum=cfg['momentum'], nesterov=True) 95 | 96 | multi_loss = MultiBoxLoss(num_class=len(label_classes), neg_pos_ratio=3) 97 | 98 | train_log_dir = 'logs/train' 99 | train_summary_writer = tf.summary.create_file_writer(train_log_dir) 100 | 101 | @tf.function 102 | def train_step(inputs, labels): 103 | with tf.GradientTape() as tape: 104 | predictions = model(inputs, training=True) 105 | losses = {} 106 | losses['reg'] = tf.reduce_sum(model.losses) # unused. Init for redefine network 107 | losses['loc'], losses['class'] = multi_loss(labels, predictions) 108 | total_loss = tf.add_n([l for l in losses.values()]) 109 | 110 | grads = tape.gradient(total_loss, model.trainable_variables) 111 | optimizer.apply_gradients(zip(grads, model.trainable_variables)) 112 | 113 | return total_loss, losses 114 | 115 | for epoch in range(init_epoch + 1, cfg['epoch']): 116 | try: 117 | start = time.time() 118 | avg_loss = 0.0 119 | for step, (inputs, labels) in enumerate(train_dataset.take(steps_per_epoch)): 120 | 121 | load_t0 = time.time() 122 | total_loss, losses = train_step(inputs, labels) 123 | avg_loss = (avg_loss * step + total_loss.numpy()) / (step + 1) 124 | load_t1 = time.time() 125 | batch_time = load_t1 - load_t0 126 | 127 | steps = steps_per_epoch * epoch + step 128 | with train_summary_writer.as_default(): 129 | tf.summary.scalar('loss/total_loss', total_loss, step=steps) 130 | for k, l in losses.items(): 131 | tf.summary.scalar('loss/{}'.format(k), l, step=steps) 132 | tf.summary.scalar('learning_rate', optimizer.lr(steps), step=steps) 133 | 134 | print( 135 | f"\rEpoch: {epoch + 1}/{cfg['epoch']} | Batch {step + 1}/{steps_per_epoch} | Batch time {batch_time:.3f} || Loss: {total_loss:.6f} | loc loss:{losses['loc']:.6f} | class loss:{losses['class']:.6f} ", 136 | end='', flush=True) 137 | 138 | print( 139 | f"\nEpoch: {epoch + 1}/{cfg['epoch']} | Epoch time {(load_t1 - start):.3f} || Average Loss: {avg_loss:.6f}") 140 | 141 | with train_summary_writer.as_default(): 142 | tf.summary.scalar('loss/avg_loss', avg_loss, step=epoch) 143 | 144 | if (epoch + 1) % cfg['save_freq'] == 0: 145 | filepath = os.path.join(weights_dir, f'weights_epoch_{(epoch + 1):03d}.h5') 146 | model.save_weights(filepath) 147 | if os.path.exists(filepath): 148 | print(f">>>>>>>>>>Save weights file at {filepath}<<<<<<<<<<") 149 | 150 | except KeyboardInterrupt: 151 | print('interrupted') 152 | # filepath = os.path.join(weights_dir, 'weights_last.h5') 153 | # model.save_weights(filepath) 154 | # print(f'model saved into: {filepath}') 155 | exit(0) 156 | 157 | 158 | if __name__ == '__main__': 159 | 160 | try: 161 | app.run(main) 162 | except SystemExit: 163 | pass 164 | --------------------------------------------------------------------------------