diff --git a/ElevenLabs.mp3 b/ElevenLabs.mp3 new file mode 100644 index 0000000..9f67682 Binary files /dev/null and b/ElevenLabs.mp3 differ diff --git a/ElevenLabs1.mp3 b/ElevenLabs1.mp3 new file mode 100644 index 0000000..50546c1 Binary files /dev/null and b/ElevenLabs1.mp3 differ diff --git a/bien.mp3 b/bien.mp3 new file mode 100644 index 0000000..acd5e7e Binary files /dev/null and b/bien.mp3 differ diff --git a/cache_nombres/nombre_Brayan.mp3 b/cache_nombres/nombre_Brayan.mp3 new file mode 100644 index 0000000..df84592 Binary files /dev/null and b/cache_nombres/nombre_Brayan.mp3 differ diff --git a/cache_nombres/nombre_Cafeteria.mp3 b/cache_nombres/nombre_Cafeteria.mp3 new file mode 100644 index 0000000..81726ff Binary files /dev/null and b/cache_nombres/nombre_Cafeteria.mp3 differ diff --git a/cache_nombres/nombre_Diana Laura Tecpa.mp3 b/cache_nombres/nombre_Diana Laura Tecpa.mp3 new file mode 100644 index 0000000..0560b47 Binary files /dev/null and b/cache_nombres/nombre_Diana Laura Tecpa.mp3 differ diff --git a/cache_nombres/nombre_Diana Laura.mp3 b/cache_nombres/nombre_Diana Laura.mp3 new file mode 100644 index 0000000..af12e47 Binary files /dev/null and b/cache_nombres/nombre_Diana Laura.mp3 differ diff --git a/cache_nombres/nombre_Emanuel Flores.mp3 b/cache_nombres/nombre_Emanuel Flores.mp3 new file mode 100644 index 0000000..9f34443 Binary files /dev/null and b/cache_nombres/nombre_Emanuel Flores.mp3 differ diff --git a/cache_nombres/nombre_Haaland_1.mp3 b/cache_nombres/nombre_Haaland_1.mp3 new file mode 100644 index 0000000..a3815ef Binary files /dev/null and b/cache_nombres/nombre_Haaland_1.mp3 differ diff --git a/cache_nombres/nombre_Jose Maria.mp3 b/cache_nombres/nombre_Jose Maria.mp3 new file mode 100644 index 0000000..e69de29 diff --git a/cache_nombres/nombre_Messi.mp3 b/cache_nombres/nombre_Messi.mp3 new file mode 100644 index 0000000..b7dcf53 Binary files /dev/null and b/cache_nombres/nombre_Messi.mp3 differ diff --git a/cache_nombres/nombre_Oscar Atriano Ponce_1.mp3 b/cache_nombres/nombre_Oscar Atriano Ponce_1.mp3 new file mode 100644 index 0000000..227a055 Binary files /dev/null and b/cache_nombres/nombre_Oscar Atriano Ponce_1.mp3 differ diff --git a/cache_nombres/nombre_Rodrigo C.mp3 b/cache_nombres/nombre_Rodrigo C.mp3 new file mode 100644 index 0000000..996d16d Binary files /dev/null and b/cache_nombres/nombre_Rodrigo C.mp3 differ diff --git a/cache_nombres/nombre_Rodrigo Cahuantzi C.mp3 b/cache_nombres/nombre_Rodrigo Cahuantzi C.mp3 new file mode 100644 index 0000000..9761203 Binary files /dev/null and b/cache_nombres/nombre_Rodrigo Cahuantzi C.mp3 differ diff --git a/cache_nombres/nombre_Rodrigo Cahuantzi.mp3 b/cache_nombres/nombre_Rodrigo Cahuantzi.mp3 new file mode 100644 index 0000000..a179d2e Binary files /dev/null and b/cache_nombres/nombre_Rodrigo Cahuantzi.mp3 differ diff --git a/cache_nombres/nombre_Rodrigo Cahuantzi_1.mp3 b/cache_nombres/nombre_Rodrigo Cahuantzi_1.mp3 new file mode 100644 index 0000000..1270d53 Binary files /dev/null and b/cache_nombres/nombre_Rodrigo Cahuantzi_1.mp3 differ diff --git a/cache_nombres/nombre_Rodrigo Cahuantzi_2.mp3 b/cache_nombres/nombre_Rodrigo Cahuantzi_2.mp3 new file mode 100644 index 0000000..9af89cd Binary files /dev/null and b/cache_nombres/nombre_Rodrigo Cahuantzi_2.mp3 differ diff --git a/cache_nombres/nombre_Rodrigo.mp3 b/cache_nombres/nombre_Rodrigo.mp3 new file mode 100644 index 0000000..25f3b77 Binary files /dev/null and b/cache_nombres/nombre_Rodrigo.mp3 differ diff --git a/cache_nombres/nombre_Rosa Maria.mp3 b/cache_nombres/nombre_Rosa Maria.mp3 new file mode 100644 index 0000000..8386987 Binary files /dev/null and b/cache_nombres/nombre_Rosa Maria.mp3 differ diff --git a/cache_nombres/nombre_Rubisela Barrientos.mp3 b/cache_nombres/nombre_Rubisela Barrientos.mp3 new file mode 100644 index 0000000..b24428c Binary files /dev/null and b/cache_nombres/nombre_Rubisela Barrientos.mp3 differ diff --git a/cache_nombres/nombre_Victor Manuel Ocampo Mendez.mp3 b/cache_nombres/nombre_Victor Manuel Ocampo Mendez.mp3 new file mode 100644 index 0000000..95dbe93 Binary files /dev/null and b/cache_nombres/nombre_Victor Manuel Ocampo Mendez.mp3 differ diff --git a/cache_nombres/nombre_Victor Manuel.mp3 b/cache_nombres/nombre_Victor Manuel.mp3 new file mode 100644 index 0000000..f85ac84 Binary files /dev/null and b/cache_nombres/nombre_Victor Manuel.mp3 differ diff --git a/cache_nombres/nombre_Xayli Ximena.mp3 b/cache_nombres/nombre_Xayli Ximena.mp3 new file mode 100644 index 0000000..ddf9063 Binary files /dev/null and b/cache_nombres/nombre_Xayli Ximena.mp3 differ diff --git a/cache_nombres/nombre_Ximena.mp3 b/cache_nombres/nombre_Ximena.mp3 new file mode 100644 index 0000000..8ad15b0 Binary files /dev/null and b/cache_nombres/nombre_Ximena.mp3 differ diff --git a/cache_nombres/nombre_ian axel.mp3 b/cache_nombres/nombre_ian axel.mp3 new file mode 100644 index 0000000..e664600 Binary files /dev/null and b/cache_nombres/nombre_ian axel.mp3 differ diff --git a/cache_nombres/nombre_jose Maria.mp3 b/cache_nombres/nombre_jose Maria.mp3 new file mode 100644 index 0000000..1bf61bb Binary files /dev/null and b/cache_nombres/nombre_jose Maria.mp3 differ diff --git a/cache_nombres/nombre_qRodrigo C.mp3 b/cache_nombres/nombre_qRodrigo C.mp3 new file mode 100644 index 0000000..73aa82e Binary files /dev/null and b/cache_nombres/nombre_qRodrigo C.mp3 differ diff --git a/db_institucion/Adriana Lopez.jpg b/db_institucion/Adriana Lopez.jpg new file mode 100644 index 0000000..91e9370 Binary files /dev/null and b/db_institucion/Adriana Lopez.jpg differ diff --git a/db_institucion/Ana Karen Guerrero.jpg b/db_institucion/Ana Karen Guerrero.jpg new file mode 100644 index 0000000..ada30bc Binary files /dev/null and b/db_institucion/Ana Karen Guerrero.jpg differ diff --git a/db_institucion/Aridai Montiel.jpg b/db_institucion/Aridai Montiel.jpg new file mode 100644 index 0000000..76b7844 Binary files /dev/null and b/db_institucion/Aridai Montiel.jpg differ diff --git a/db_institucion/Aridai montiel.jpg b/db_institucion/Aridai montiel.jpg new file mode 100644 index 0000000..3e2b00d Binary files /dev/null and b/db_institucion/Aridai montiel.jpg differ diff --git a/db_institucion/Carlos Eduardo Cuamatzi.jpg b/db_institucion/Carlos Eduardo Cuamatzi.jpg new file mode 100644 index 0000000..e9ab70c Binary files /dev/null and b/db_institucion/Carlos Eduardo Cuamatzi.jpg differ diff --git a/db_institucion/Cristian Hernandez Suarez.jpg b/db_institucion/Cristian Hernandez Suarez.jpg new file mode 100644 index 0000000..1ca481b Binary files /dev/null and b/db_institucion/Cristian Hernandez Suarez.jpg differ diff --git a/db_institucion/Diana Laura Tecpa.jpg b/db_institucion/Diana Laura Tecpa.jpg new file mode 100644 index 0000000..a7f70e4 Binary files /dev/null and b/db_institucion/Diana Laura Tecpa.jpg differ diff --git a/db_institucion/Diana Laura.jpg b/db_institucion/Diana Laura.jpg new file mode 100644 index 0000000..2e8318c Binary files /dev/null and b/db_institucion/Diana Laura.jpg differ diff --git a/db_institucion/Diana laura.jpg b/db_institucion/Diana laura.jpg new file mode 100644 index 0000000..100562a Binary files /dev/null and b/db_institucion/Diana laura.jpg differ diff --git a/db_institucion/Emanuel Flores.jpg b/db_institucion/Emanuel Flores.jpg new file mode 100644 index 0000000..ce282e7 Binary files /dev/null and b/db_institucion/Emanuel Flores.jpg differ diff --git a/db_institucion/Ian Axel.jpg b/db_institucion/Ian Axel.jpg new file mode 100644 index 0000000..25a9030 Binary files /dev/null and b/db_institucion/Ian Axel.jpg differ diff --git a/db_institucion/Oscar Atriano Ponce.jpg b/db_institucion/Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..5f05c42 Binary files /dev/null and b/db_institucion/Oscar Atriano Ponce.jpg differ diff --git a/db_institucion/Oscar Atriano Ponce_1.jpg b/db_institucion/Oscar Atriano Ponce_1.jpg new file mode 100644 index 0000000..4a71dee Binary files /dev/null and b/db_institucion/Oscar Atriano Ponce_1.jpg differ diff --git a/db_institucion/Rafael.jpg b/db_institucion/Rafael.jpg new file mode 100644 index 0000000..5745ad4 Binary files /dev/null and b/db_institucion/Rafael.jpg differ diff --git a/db_institucion/Rodrigo C.jpg b/db_institucion/Rodrigo C.jpg new file mode 100644 index 0000000..da37fac Binary files /dev/null and b/db_institucion/Rodrigo C.jpg differ diff --git a/db_institucion/Rodrigo Cahuantzi_1.jpg b/db_institucion/Rodrigo Cahuantzi_1.jpg new file mode 100644 index 0000000..e35066d Binary files /dev/null and b/db_institucion/Rodrigo Cahuantzi_1.jpg differ diff --git a/db_institucion/Rosa Maria.jpg b/db_institucion/Rosa Maria.jpg new file mode 100644 index 0000000..0adb5a9 Binary files /dev/null and b/db_institucion/Rosa Maria.jpg differ diff --git a/db_institucion/Rosa Maria_2.jpg b/db_institucion/Rosa Maria_2.jpg new file mode 100644 index 0000000..3e81394 Binary files /dev/null and b/db_institucion/Rosa Maria_2.jpg differ diff --git a/db_institucion/Rosa maria.jpg b/db_institucion/Rosa maria.jpg new file mode 100644 index 0000000..61be68d Binary files /dev/null and b/db_institucion/Rosa maria.jpg differ diff --git a/db_institucion/Rubisela Barrientos.jpg b/db_institucion/Rubisela Barrientos.jpg new file mode 100644 index 0000000..40dc211 Binary files /dev/null and b/db_institucion/Rubisela Barrientos.jpg differ diff --git a/db_institucion/Victor Manuel Ocampo Mendez.jpg b/db_institucion/Victor Manuel Ocampo Mendez.jpg new file mode 100644 index 0000000..25af5b9 Binary files /dev/null and b/db_institucion/Victor Manuel Ocampo Mendez.jpg differ diff --git a/db_institucion/Vikicar Aldana.jpg b/db_institucion/Vikicar Aldana.jpg new file mode 100644 index 0000000..0f0cf32 Binary files /dev/null and b/db_institucion/Vikicar Aldana.jpg differ diff --git a/db_institucion/Vikicar.jpg b/db_institucion/Vikicar.jpg new file mode 100644 index 0000000..e7f1def Binary files /dev/null and b/db_institucion/Vikicar.jpg differ diff --git a/db_institucion/Xayli Ximena.jpg b/db_institucion/Xayli Ximena.jpg new file mode 100644 index 0000000..c3b0902 Binary files /dev/null and b/db_institucion/Xayli Ximena.jpg differ diff --git a/db_institucion/Ximena.jpg b/db_institucion/Ximena.jpg new file mode 100644 index 0000000..3cce987 Binary files /dev/null and b/db_institucion/Ximena.jpg differ diff --git a/db_institucion/aridai montiel zistecatl.jpg b/db_institucion/aridai montiel zistecatl.jpg new file mode 100644 index 0000000..70b44c7 Binary files /dev/null and b/db_institucion/aridai montiel zistecatl.jpg differ diff --git a/db_institucion/ian axel.jpg b/db_institucion/ian axel.jpg new file mode 100644 index 0000000..d72d3e9 Binary files /dev/null and b/db_institucion/ian axel.jpg differ diff --git a/desconocidos/20260303_114448_sim081_cerca-de-Rodrigo.jpg b/desconocidos/20260303_114448_sim081_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..66ccf3a Binary files /dev/null and b/desconocidos/20260303_114448_sim081_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260303_114453_sim082_cerca-de-Victor Manuel.jpg b/desconocidos/20260303_114453_sim082_cerca-de-Victor Manuel.jpg new file mode 100644 index 0000000..4f9a970 Binary files /dev/null and b/desconocidos/20260303_114453_sim082_cerca-de-Victor Manuel.jpg differ diff --git a/desconocidos/20260303_114459_sim083_cerca-de-Victor Manuel.jpg b/desconocidos/20260303_114459_sim083_cerca-de-Victor Manuel.jpg new file mode 100644 index 0000000..050bf9a Binary files /dev/null and b/desconocidos/20260303_114459_sim083_cerca-de-Victor Manuel.jpg differ diff --git a/desconocidos/20260304_090037_sim068_cerca-de-Cafeteria.jpg b/desconocidos/20260304_090037_sim068_cerca-de-Cafeteria.jpg new file mode 100644 index 0000000..2dcf8a6 Binary files /dev/null and b/desconocidos/20260304_090037_sim068_cerca-de-Cafeteria.jpg differ diff --git a/desconocidos/20260304_092722_sim054_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092722_sim054_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..a100bc9 Binary files /dev/null and b/desconocidos/20260304_092722_sim054_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092723_sim046_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092723_sim046_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..d8e8e6d Binary files /dev/null and b/desconocidos/20260304_092723_sim046_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092724_sim049_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092724_sim049_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..1c6362e Binary files /dev/null and b/desconocidos/20260304_092724_sim049_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092725_sim051_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092725_sim051_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..c41b346 Binary files /dev/null and b/desconocidos/20260304_092725_sim051_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092725_sim061_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092725_sim061_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..9129282 Binary files /dev/null and b/desconocidos/20260304_092725_sim061_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092726_sim058_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092726_sim058_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..2fda930 Binary files /dev/null and b/desconocidos/20260304_092726_sim058_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092726_sim059_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092726_sim059_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..717fc75 Binary files /dev/null and b/desconocidos/20260304_092726_sim059_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092727_sim050_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092727_sim050_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..68d908f Binary files /dev/null and b/desconocidos/20260304_092727_sim050_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092727_sim054_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092727_sim054_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..ed9e617 Binary files /dev/null and b/desconocidos/20260304_092727_sim054_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092728_sim055_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092728_sim055_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..81d2ac0 Binary files /dev/null and b/desconocidos/20260304_092728_sim055_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092728_sim059_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092728_sim059_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..180a120 Binary files /dev/null and b/desconocidos/20260304_092728_sim059_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092728_sim061_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092728_sim061_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..700af60 Binary files /dev/null and b/desconocidos/20260304_092728_sim061_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092729_sim049_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092729_sim049_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..a037032 Binary files /dev/null and b/desconocidos/20260304_092729_sim049_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092729_sim052_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092729_sim052_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..1d2f6fb Binary files /dev/null and b/desconocidos/20260304_092729_sim052_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092730_sim049_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092730_sim049_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..ca03689 Binary files /dev/null and b/desconocidos/20260304_092730_sim049_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092827_sim057_cerca-de-Vikicar.jpg b/desconocidos/20260304_092827_sim057_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..e70dbce Binary files /dev/null and b/desconocidos/20260304_092827_sim057_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092827_sim068_cerca-de-Vikicar.jpg b/desconocidos/20260304_092827_sim068_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..2babb30 Binary files /dev/null and b/desconocidos/20260304_092827_sim068_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092828_sim059_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092828_sim059_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..01a59cf Binary files /dev/null and b/desconocidos/20260304_092828_sim059_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092829_sim050_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092829_sim050_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..5489252 Binary files /dev/null and b/desconocidos/20260304_092829_sim050_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092829_sim064_cerca-de-Vikicar.jpg b/desconocidos/20260304_092829_sim064_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..e2a9d48 Binary files /dev/null and b/desconocidos/20260304_092829_sim064_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092830_sim059_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_092830_sim059_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..214cffa Binary files /dev/null and b/desconocidos/20260304_092830_sim059_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_092833_sim065_cerca-de-Vikicar.jpg b/desconocidos/20260304_092833_sim065_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..f5da62b Binary files /dev/null and b/desconocidos/20260304_092833_sim065_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092833_sim066_cerca-de-Vikicar.jpg b/desconocidos/20260304_092833_sim066_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..15656e4 Binary files /dev/null and b/desconocidos/20260304_092833_sim066_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092834_sim068_cerca-de-Vikicar.jpg b/desconocidos/20260304_092834_sim068_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..a27c351 Binary files /dev/null and b/desconocidos/20260304_092834_sim068_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092834_sim072_cerca-de-Vikicar.jpg b/desconocidos/20260304_092834_sim072_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..609a3ce Binary files /dev/null and b/desconocidos/20260304_092834_sim072_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_092932_sim045_cerca-de-Rosa Maria.jpg b/desconocidos/20260304_092932_sim045_cerca-de-Rosa Maria.jpg new file mode 100644 index 0000000..51287da Binary files /dev/null and b/desconocidos/20260304_092932_sim045_cerca-de-Rosa Maria.jpg differ diff --git a/desconocidos/20260304_093117_sim048_cerca-de-Rosa Maria.jpg b/desconocidos/20260304_093117_sim048_cerca-de-Rosa Maria.jpg new file mode 100644 index 0000000..12b0177 Binary files /dev/null and b/desconocidos/20260304_093117_sim048_cerca-de-Rosa Maria.jpg differ diff --git a/desconocidos/20260304_093118_sim045_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093118_sim045_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..1ab5e25 Binary files /dev/null and b/desconocidos/20260304_093118_sim045_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093118_sim052_cerca-de-Rosa Maria.jpg b/desconocidos/20260304_093118_sim052_cerca-de-Rosa Maria.jpg new file mode 100644 index 0000000..ca112be Binary files /dev/null and b/desconocidos/20260304_093118_sim052_cerca-de-Rosa Maria.jpg differ diff --git a/desconocidos/20260304_093131_sim046_cerca-de-Rosa Maria.jpg b/desconocidos/20260304_093131_sim046_cerca-de-Rosa Maria.jpg new file mode 100644 index 0000000..8d6677c Binary files /dev/null and b/desconocidos/20260304_093131_sim046_cerca-de-Rosa Maria.jpg differ diff --git a/desconocidos/20260304_093201_sim057_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093201_sim057_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..4a33b5b Binary files /dev/null and b/desconocidos/20260304_093201_sim057_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093202_sim046_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093202_sim046_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..3098521 Binary files /dev/null and b/desconocidos/20260304_093202_sim046_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093202_sim050_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093202_sim050_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..92c0dfe Binary files /dev/null and b/desconocidos/20260304_093202_sim050_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093203_sim046_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093203_sim046_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..cde16a7 Binary files /dev/null and b/desconocidos/20260304_093203_sim046_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093203_sim051_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093203_sim051_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..02683ad Binary files /dev/null and b/desconocidos/20260304_093203_sim051_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093204_sim045_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093204_sim045_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..6c114da Binary files /dev/null and b/desconocidos/20260304_093204_sim045_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093204_sim047_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093204_sim047_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..8742255 Binary files /dev/null and b/desconocidos/20260304_093204_sim047_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093205_sim053_cerca-de-Rosa Maria.jpg b/desconocidos/20260304_093205_sim053_cerca-de-Rosa Maria.jpg new file mode 100644 index 0000000..88cd856 Binary files /dev/null and b/desconocidos/20260304_093205_sim053_cerca-de-Rosa Maria.jpg differ diff --git a/desconocidos/20260304_093205_sim054_cerca-de-Adriana Lopez.jpg b/desconocidos/20260304_093205_sim054_cerca-de-Adriana Lopez.jpg new file mode 100644 index 0000000..9d63a1c Binary files /dev/null and b/desconocidos/20260304_093205_sim054_cerca-de-Adriana Lopez.jpg differ diff --git a/desconocidos/20260304_093206_sim057_cerca-de-Adriana Lopez.jpg b/desconocidos/20260304_093206_sim057_cerca-de-Adriana Lopez.jpg new file mode 100644 index 0000000..2322234 Binary files /dev/null and b/desconocidos/20260304_093206_sim057_cerca-de-Adriana Lopez.jpg differ diff --git a/desconocidos/20260304_093207_sim048_cerca-de-Vikicar.jpg b/desconocidos/20260304_093207_sim048_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..0b2bcb2 Binary files /dev/null and b/desconocidos/20260304_093207_sim048_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093220_sim052_cerca-de-Adriana Lopez.jpg b/desconocidos/20260304_093220_sim052_cerca-de-Adriana Lopez.jpg new file mode 100644 index 0000000..5982f56 Binary files /dev/null and b/desconocidos/20260304_093220_sim052_cerca-de-Adriana Lopez.jpg differ diff --git a/desconocidos/20260304_093221_sim050_cerca-de-Adriana Lopez.jpg b/desconocidos/20260304_093221_sim050_cerca-de-Adriana Lopez.jpg new file mode 100644 index 0000000..a7b82b7 Binary files /dev/null and b/desconocidos/20260304_093221_sim050_cerca-de-Adriana Lopez.jpg differ diff --git a/desconocidos/20260304_093221_sim052_cerca-de-Adriana Lopez.jpg b/desconocidos/20260304_093221_sim052_cerca-de-Adriana Lopez.jpg new file mode 100644 index 0000000..f28f6db Binary files /dev/null and b/desconocidos/20260304_093221_sim052_cerca-de-Adriana Lopez.jpg differ diff --git a/desconocidos/20260304_093249_sim052_cerca-de-Vikicar.jpg b/desconocidos/20260304_093249_sim052_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..57c15f4 Binary files /dev/null and b/desconocidos/20260304_093249_sim052_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093250_sim053_cerca-de-Vikicar.jpg b/desconocidos/20260304_093250_sim053_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..77fb035 Binary files /dev/null and b/desconocidos/20260304_093250_sim053_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093250_sim055_cerca-de-Vikicar.jpg b/desconocidos/20260304_093250_sim055_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..c50b085 Binary files /dev/null and b/desconocidos/20260304_093250_sim055_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093251_sim057_cerca-de-Vikicar.jpg b/desconocidos/20260304_093251_sim057_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..7b4f5fa Binary files /dev/null and b/desconocidos/20260304_093251_sim057_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093257_sim056_cerca-de-Vikicar.jpg b/desconocidos/20260304_093257_sim056_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..dbfeee0 Binary files /dev/null and b/desconocidos/20260304_093257_sim056_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093257_sim058_cerca-de-Vikicar.jpg b/desconocidos/20260304_093257_sim058_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..72d0f5b Binary files /dev/null and b/desconocidos/20260304_093257_sim058_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093258_sim047_cerca-de-Vikicar.jpg b/desconocidos/20260304_093258_sim047_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..0071ede Binary files /dev/null and b/desconocidos/20260304_093258_sim047_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093259_sim060_cerca-de-Vikicar.jpg b/desconocidos/20260304_093259_sim060_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..12bd6c5 Binary files /dev/null and b/desconocidos/20260304_093259_sim060_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093300_sim056_cerca-de-Vikicar.jpg b/desconocidos/20260304_093300_sim056_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..aff9e30 Binary files /dev/null and b/desconocidos/20260304_093300_sim056_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093301_sim057_cerca-de-Vikicar.jpg b/desconocidos/20260304_093301_sim057_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..063695d Binary files /dev/null and b/desconocidos/20260304_093301_sim057_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093301_sim060_cerca-de-Vikicar.jpg b/desconocidos/20260304_093301_sim060_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..0934407 Binary files /dev/null and b/desconocidos/20260304_093301_sim060_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093302_sim046_cerca-de-Vikicar.jpg b/desconocidos/20260304_093302_sim046_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..e753d90 Binary files /dev/null and b/desconocidos/20260304_093302_sim046_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093302_sim051_cerca-de-Vikicar.jpg b/desconocidos/20260304_093302_sim051_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..2300d7e Binary files /dev/null and b/desconocidos/20260304_093302_sim051_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093303_sim055_cerca-de-Vikicar.jpg b/desconocidos/20260304_093303_sim055_cerca-de-Vikicar.jpg new file mode 100644 index 0000000..f998a58 Binary files /dev/null and b/desconocidos/20260304_093303_sim055_cerca-de-Vikicar.jpg differ diff --git a/desconocidos/20260304_093332_sim047_cerca-de-ian axel.jpg b/desconocidos/20260304_093332_sim047_cerca-de-ian axel.jpg new file mode 100644 index 0000000..6ab5cd3 Binary files /dev/null and b/desconocidos/20260304_093332_sim047_cerca-de-ian axel.jpg differ diff --git a/desconocidos/20260304_093333_sim045_cerca-de-Carlos Eduardo Cuamatzi.jpg b/desconocidos/20260304_093333_sim045_cerca-de-Carlos Eduardo Cuamatzi.jpg new file mode 100644 index 0000000..4c7cbb4 Binary files /dev/null and b/desconocidos/20260304_093333_sim045_cerca-de-Carlos Eduardo Cuamatzi.jpg differ diff --git a/desconocidos/20260304_093337_sim046_cerca-de-Carlos Eduardo Cuamatzi.jpg b/desconocidos/20260304_093337_sim046_cerca-de-Carlos Eduardo Cuamatzi.jpg new file mode 100644 index 0000000..fb67064 Binary files /dev/null and b/desconocidos/20260304_093337_sim046_cerca-de-Carlos Eduardo Cuamatzi.jpg differ diff --git a/desconocidos/20260304_093341_sim046_cerca-de-Carlos Eduardo Cuamatzi.jpg b/desconocidos/20260304_093341_sim046_cerca-de-Carlos Eduardo Cuamatzi.jpg new file mode 100644 index 0000000..9f2a4e5 Binary files /dev/null and b/desconocidos/20260304_093341_sim046_cerca-de-Carlos Eduardo Cuamatzi.jpg differ diff --git a/desconocidos/20260304_093418_sim045_cerca-de-Ximena.jpg b/desconocidos/20260304_093418_sim045_cerca-de-Ximena.jpg new file mode 100644 index 0000000..22139cb Binary files /dev/null and b/desconocidos/20260304_093418_sim045_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093419_sim050_cerca-de-Ximena.jpg b/desconocidos/20260304_093419_sim050_cerca-de-Ximena.jpg new file mode 100644 index 0000000..9daeffd Binary files /dev/null and b/desconocidos/20260304_093419_sim050_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093419_sim053_cerca-de-Ximena.jpg b/desconocidos/20260304_093419_sim053_cerca-de-Ximena.jpg new file mode 100644 index 0000000..f7771dd Binary files /dev/null and b/desconocidos/20260304_093419_sim053_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093420_sim046_cerca-de-Ximena.jpg b/desconocidos/20260304_093420_sim046_cerca-de-Ximena.jpg new file mode 100644 index 0000000..2664bc1 Binary files /dev/null and b/desconocidos/20260304_093420_sim046_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093421_sim050_cerca-de-Ximena.jpg b/desconocidos/20260304_093421_sim050_cerca-de-Ximena.jpg new file mode 100644 index 0000000..c57e02d Binary files /dev/null and b/desconocidos/20260304_093421_sim050_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093421_sim057_cerca-de-Ximena.jpg b/desconocidos/20260304_093421_sim057_cerca-de-Ximena.jpg new file mode 100644 index 0000000..27ab040 Binary files /dev/null and b/desconocidos/20260304_093421_sim057_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093422_sim049_cerca-de-Ximena.jpg b/desconocidos/20260304_093422_sim049_cerca-de-Ximena.jpg new file mode 100644 index 0000000..da95c46 Binary files /dev/null and b/desconocidos/20260304_093422_sim049_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093422_sim051_cerca-de-Ximena.jpg b/desconocidos/20260304_093422_sim051_cerca-de-Ximena.jpg new file mode 100644 index 0000000..26a0978 Binary files /dev/null and b/desconocidos/20260304_093422_sim051_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093423_sim055_cerca-de-Ximena.jpg b/desconocidos/20260304_093423_sim055_cerca-de-Ximena.jpg new file mode 100644 index 0000000..ae2df0e Binary files /dev/null and b/desconocidos/20260304_093423_sim055_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093424_sim049_cerca-de-Ximena.jpg b/desconocidos/20260304_093424_sim049_cerca-de-Ximena.jpg new file mode 100644 index 0000000..b1fa0be Binary files /dev/null and b/desconocidos/20260304_093424_sim049_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093424_sim051_cerca-de-Ximena.jpg b/desconocidos/20260304_093424_sim051_cerca-de-Ximena.jpg new file mode 100644 index 0000000..78dd784 Binary files /dev/null and b/desconocidos/20260304_093424_sim051_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093427_sim054_cerca-de-Ximena.jpg b/desconocidos/20260304_093427_sim054_cerca-de-Ximena.jpg new file mode 100644 index 0000000..26b5017 Binary files /dev/null and b/desconocidos/20260304_093427_sim054_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093431_sim057_cerca-de-Ximena.jpg b/desconocidos/20260304_093431_sim057_cerca-de-Ximena.jpg new file mode 100644 index 0000000..7991694 Binary files /dev/null and b/desconocidos/20260304_093431_sim057_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093432_sim049_cerca-de-Ximena.jpg b/desconocidos/20260304_093432_sim049_cerca-de-Ximena.jpg new file mode 100644 index 0000000..db69675 Binary files /dev/null and b/desconocidos/20260304_093432_sim049_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093432_sim051_cerca-de-Ximena.jpg b/desconocidos/20260304_093432_sim051_cerca-de-Ximena.jpg new file mode 100644 index 0000000..2737cd8 Binary files /dev/null and b/desconocidos/20260304_093432_sim051_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093433_sim050_cerca-de-Ximena.jpg b/desconocidos/20260304_093433_sim050_cerca-de-Ximena.jpg new file mode 100644 index 0000000..0dfef8f Binary files /dev/null and b/desconocidos/20260304_093433_sim050_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093433_sim053_cerca-de-Ximena.jpg b/desconocidos/20260304_093433_sim053_cerca-de-Ximena.jpg new file mode 100644 index 0000000..db330c9 Binary files /dev/null and b/desconocidos/20260304_093433_sim053_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093433_sim055_cerca-de-Ximena.jpg b/desconocidos/20260304_093433_sim055_cerca-de-Ximena.jpg new file mode 100644 index 0000000..da278c1 Binary files /dev/null and b/desconocidos/20260304_093433_sim055_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093434_sim061_cerca-de-Ximena.jpg b/desconocidos/20260304_093434_sim061_cerca-de-Ximena.jpg new file mode 100644 index 0000000..483f017 Binary files /dev/null and b/desconocidos/20260304_093434_sim061_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093508_sim045_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093508_sim045_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..f042b14 Binary files /dev/null and b/desconocidos/20260304_093508_sim045_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093514_sim046_cerca-de-Diana Laura.jpg b/desconocidos/20260304_093514_sim046_cerca-de-Diana Laura.jpg new file mode 100644 index 0000000..cfba58c Binary files /dev/null and b/desconocidos/20260304_093514_sim046_cerca-de-Diana Laura.jpg differ diff --git a/desconocidos/20260304_093515_sim046_cerca-de-Vikicar Aldana.jpg b/desconocidos/20260304_093515_sim046_cerca-de-Vikicar Aldana.jpg new file mode 100644 index 0000000..9cecdd6 Binary files /dev/null and b/desconocidos/20260304_093515_sim046_cerca-de-Vikicar Aldana.jpg differ diff --git a/desconocidos/20260304_093547_sim047_cerca-de-Ximena.jpg b/desconocidos/20260304_093547_sim047_cerca-de-Ximena.jpg new file mode 100644 index 0000000..8b553fc Binary files /dev/null and b/desconocidos/20260304_093547_sim047_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093551_sim047_cerca-de-Ximena.jpg b/desconocidos/20260304_093551_sim047_cerca-de-Ximena.jpg new file mode 100644 index 0000000..76c520e Binary files /dev/null and b/desconocidos/20260304_093551_sim047_cerca-de-Ximena.jpg differ diff --git a/desconocidos/20260304_093710_sim045_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093710_sim045_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..c13cd4f Binary files /dev/null and b/desconocidos/20260304_093710_sim045_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093711_sim048_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093711_sim048_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..0878fff Binary files /dev/null and b/desconocidos/20260304_093711_sim048_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093711_sim049_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093711_sim049_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..d2bea5c Binary files /dev/null and b/desconocidos/20260304_093711_sim049_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093713_sim049_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093713_sim049_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..3daa276 Binary files /dev/null and b/desconocidos/20260304_093713_sim049_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093713_sim051_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093713_sim051_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..ffaa23b Binary files /dev/null and b/desconocidos/20260304_093713_sim051_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093714_sim045_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093714_sim045_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..c8a46f1 Binary files /dev/null and b/desconocidos/20260304_093714_sim045_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093714_sim047_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093714_sim047_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..ebe9dd5 Binary files /dev/null and b/desconocidos/20260304_093714_sim047_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093715_sim047_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093715_sim047_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..998765f Binary files /dev/null and b/desconocidos/20260304_093715_sim047_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093715_sim050_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093715_sim050_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..1f4fa50 Binary files /dev/null and b/desconocidos/20260304_093715_sim050_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093716_sim046_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093716_sim046_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..7207303 Binary files /dev/null and b/desconocidos/20260304_093716_sim046_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093716_sim047_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093716_sim047_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..63aee25 Binary files /dev/null and b/desconocidos/20260304_093716_sim047_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093717_sim050_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093717_sim050_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..0e300ae Binary files /dev/null and b/desconocidos/20260304_093717_sim050_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093717_sim051_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093717_sim051_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..2d6a48b Binary files /dev/null and b/desconocidos/20260304_093717_sim051_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093718_sim048_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093718_sim048_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..0b5d2bb Binary files /dev/null and b/desconocidos/20260304_093718_sim048_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093718_sim049_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093718_sim049_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..d6b0b76 Binary files /dev/null and b/desconocidos/20260304_093718_sim049_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093719_sim049_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093719_sim049_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..7042e79 Binary files /dev/null and b/desconocidos/20260304_093719_sim049_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093720_sim047_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093720_sim047_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..8aabfc9 Binary files /dev/null and b/desconocidos/20260304_093720_sim047_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093720_sim050_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093720_sim050_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..f7db068 Binary files /dev/null and b/desconocidos/20260304_093720_sim050_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093720_sim051_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093720_sim051_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..2be36df Binary files /dev/null and b/desconocidos/20260304_093720_sim051_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093721_sim046_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093721_sim046_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..bebe469 Binary files /dev/null and b/desconocidos/20260304_093721_sim046_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093721_sim048_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093721_sim048_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..adbc0ca Binary files /dev/null and b/desconocidos/20260304_093721_sim048_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093722_sim048_cerca-de-Oscar Atriano Ponce.jpg b/desconocidos/20260304_093722_sim048_cerca-de-Oscar Atriano Ponce.jpg new file mode 100644 index 0000000..9d515c7 Binary files /dev/null and b/desconocidos/20260304_093722_sim048_cerca-de-Oscar Atriano Ponce.jpg differ diff --git a/desconocidos/20260304_093722_sim048_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093722_sim048_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..9045197 Binary files /dev/null and b/desconocidos/20260304_093722_sim048_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260304_093723_sim045_cerca-de-Rodrigo.jpg b/desconocidos/20260304_093723_sim045_cerca-de-Rodrigo.jpg new file mode 100644 index 0000000..92994b1 Binary files /dev/null and b/desconocidos/20260304_093723_sim045_cerca-de-Rodrigo.jpg differ diff --git a/desconocidos/20260313_102233_sim057_cerca-de-Rodrigo Cahuantzi.jpg b/desconocidos/20260313_102233_sim057_cerca-de-Rodrigo Cahuantzi.jpg new file mode 100644 index 0000000..e949768 Binary files /dev/null and b/desconocidos/20260313_102233_sim057_cerca-de-Rodrigo Cahuantzi.jpg differ diff --git a/desconocidos/20260313_102356_sim050_cerca-de-Rodrigo Cahuantzi.jpg b/desconocidos/20260313_102356_sim050_cerca-de-Rodrigo Cahuantzi.jpg new file mode 100644 index 0000000..4cc837c Binary files /dev/null and b/desconocidos/20260313_102356_sim050_cerca-de-Rodrigo Cahuantzi.jpg differ diff --git a/desconocidos/20260313_112201_sim047_cerca-de-Rodrigo C.jpg b/desconocidos/20260313_112201_sim047_cerca-de-Rodrigo C.jpg new file mode 100644 index 0000000..1d63ef5 Binary files /dev/null and b/desconocidos/20260313_112201_sim047_cerca-de-Rodrigo C.jpg differ diff --git a/dias.mp3 b/dias.mp3 new file mode 100644 index 0000000..1539ca1 Binary files /dev/null and b/dias.mp3 differ diff --git a/face_detection_yunet_2023mar.onnx b/face_detection_yunet_2023mar.onnx new file mode 100644 index 0000000..f9beb30 Binary files /dev/null and b/face_detection_yunet_2023mar.onnx differ diff --git a/fin_dia_h.mp3 b/fin_dia_h.mp3 new file mode 100644 index 0000000..fa1cdfa Binary files /dev/null and b/fin_dia_h.mp3 differ diff --git a/fin_dia_m.mp3 b/fin_dia_m.mp3 new file mode 100644 index 0000000..867e828 Binary files /dev/null and b/fin_dia_m.mp3 differ diff --git a/fin_noche_h.mp3 b/fin_noche_h.mp3 new file mode 100644 index 0000000..cb3594b Binary files /dev/null and b/fin_noche_h.mp3 differ diff --git a/fin_noche_m.mp3 b/fin_noche_m.mp3 new file mode 100644 index 0000000..7cdffda Binary files /dev/null and b/fin_noche_m.mp3 differ diff --git a/fusion.py b/fusion.py new file mode 100644 index 0000000..7552b2a --- /dev/null +++ b/fusion.py @@ -0,0 +1,279 @@ +import os +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' +os.environ['CUDA_VISIBLE_DEVICES'] = '-1' + +import cv2 +import numpy as np +import time +import threading +from queue import Queue +from deepface import DeepFace +from ultralytics import YOLO +import warnings + +warnings.filterwarnings("ignore") + +# ────────────────────────────────────────────────────────────────────────────── +# 1. IMPORTAMOS NUESTROS MÓDULOS +# ────────────────────────────────────────────────────────────────────────────── +# Del motor matemático y tracking +from seguimiento2 import GlobalMemory, CamManager, SECUENCIA, URLS, FUENTE + +# Del motor de reconocimiento facial y audio +from reconocimiento2 import ( + gestionar_vectores, + detectar_rostros_yunet, + buscar_mejor_match, + hilo_bienvenida, + UMBRAL_SIM, + COOLDOWN_TIME +) + +# ────────────────────────────────────────────────────────────────────────────── +# 2. PROTECCIONES MULTIHILO E INICIALIZACIÓN +# ────────────────────────────────────────────────────────────────────────────── +COLA_ROSTROS = Queue(maxsize=4) +YUNET_LOCK = threading.Lock() +IA_LOCK = threading.Lock() + +# Inicializamos la base de datos usando tu función importada +print("\nIniciando carga de base de datos...") +BASE_DATOS_ROSTROS = gestionar_vectores(actualizar=True) + +# ────────────────────────────────────────────────────────────────────────────── +# 3. MOTOR ASÍNCRONO +# ────────────────────────────────────────────────────────────────────────────── +def procesar_rostro_async(frame, box, gid, cam_id, global_mem, trk): + """ Toma el recorte del tracker, usa YuNet importado, y hace la Fusión Mágica """ + try: + if not BASE_DATOS_ROSTROS: return + + h_real, w_real = frame.shape[:2] + escala_x = w_real / 480.0 + escala_y = h_real / 270.0 + + x_min, y_min, x_max, y_max = box + h_box = y_max - y_min + + y_min_expandido = max(0, y_min - (h_box * 0.15)) + y_max_cabeza = min(y_max, y_min + (h_box * 0.40)) + + x1 = int(max(0, x_min) * escala_x) + y1 = int(y_min_expandido * escala_y) + x2 = int(min(480, x_max) * escala_x) + y2 = int(y_max_cabeza * escala_y) + + roi_cabeza = frame[y1:y2, x1:x2] + + if roi_cabeza.size == 0 or roi_cabeza.shape[0] < 20 or roi_cabeza.shape[1] < 20: + return + + h_roi, w_roi = roi_cabeza.shape[:2] + + # Usamos la función de YuNet importada, pasándole nuestro candado + faces = detectar_rostros_yunet(roi_cabeza, lock=YUNET_LOCK) + + # OJO: Tu detectar_rostros_yunet devuelve 5 valores (x, y, w, h, score) + for (rx, ry, rw, rh, score) in faces: + rx, ry = max(0, rx), max(0, ry) + rw, rh = min(w_roi - rx, rw), min(h_roi - ry, rh) + + area_rostro_actual = rw * rh + + with global_mem.lock: + data = global_mem.db.get(gid, {}) + nombre_actual = data.get('nombre') + area_ref = data.get('area_rostro_ref', 0) + + necesita_saludo = False + if str(cam_id) == "7": + if not hasattr(global_mem, 'ultimos_saludos'): + global_mem.ultimos_saludos = {} + ultimo = global_mem.ultimos_saludos.get(nombre_actual if nombre_actual else "", 0) + if (time.time() - ultimo) > COOLDOWN_TIME: + necesita_saludo = True + + if nombre_actual is None or area_rostro_actual >= (area_ref * 1.5) or necesita_saludo: + m_x = int(rw * 0.15) + m_y = int(rh * 0.15) + + roi_rostro = roi_cabeza[max(0, ry-m_y):min(h_roi, ry+rh+m_y), + max(0, rx-m_x):min(w_roi, rx+rw+m_x)] + + if roi_rostro.size == 0 or roi_rostro.shape[0] < 40 or roi_rostro.shape[1] < 40: + continue + + # ── Filtro de nitidez ── + gray_roi = cv2.cvtColor(roi_rostro, cv2.COLOR_BGR2GRAY) + nitidez = cv2.Laplacian(gray_roi, cv2.CV_64F).var() + if nitidez < 50.0: + continue + + # ── ArcFace (Protegido con IA_LOCK) ── + with IA_LOCK: + try: + res = DeepFace.represent(img_path=roi_rostro, model_name="ArcFace", enforce_detection=False) + emb = np.array(res[0]["embedding"], dtype=np.float32) + + # Usamos tu función importada (ya con producto punto) + mejor_match, max_sim = buscar_mejor_match(emb, BASE_DATOS_ROSTROS) + except Exception: + continue + + print(f"[DEBUG CAM {cam_id}] ArcFace: {mejor_match} al {max_sim:.2f} (Umbral: {UMBRAL_SIM})") + + if max_sim > UMBRAL_SIM and mejor_match: + nombre_limpio = mejor_match.split('_')[0] + + with global_mem.lock: + gid_original = None + for otro_gid, datos_otro in global_mem.db.items(): + if datos_otro.get('nombre') == nombre_limpio and otro_gid != gid: + gid_original = otro_gid + break + + if gid_original is not None: + print(f"\n[FUSIÓN MÁGICA] Uniendo el ID {gid} al original {gid_original} ({nombre_limpio})") + if gid in global_mem.db: + del global_mem.db[gid] + + global_mem.db[gid_original]['ts'] = time.time() + global_mem.db[gid_original]['last_cam'] = cam_id + trk.gid = gid_original + else: + global_mem.db[gid]['nombre'] = nombre_limpio + global_mem.db[gid]['area_rostro_ref'] = area_rostro_actual + + if str(cam_id) == "7" and necesita_saludo: + global_mem.ultimos_saludos[nombre_limpio] = time.time() + + try: + with IA_LOCK: + analisis = DeepFace.analyze(roi_rostro, actions=['gender'], enforce_detection=False)[0] + genero = analisis.get('dominant_gender', 'Man') + except Exception: + genero = "Man" + + # Usamos la función importada para el audio + threading.Thread( + target=hilo_bienvenida, + args=(nombre_limpio, genero), + daemon=True + ).start() + break + except Exception as e: + pass + finally: + trk.procesando_rostro = False + +def worker_rostros(global_mem): + """ Consumidor de la cola multihilo """ + while True: + frame, box, gid, cam_id, trk = COLA_ROSTROS.get() + procesar_rostro_async(frame, box, gid, cam_id, global_mem, trk) + COLA_ROSTROS.task_done() + +# ────────────────────────────────────────────────────────────────────────────── +# 4. LOOP PRINCIPAL DE FUSIÓN +# ────────────────────────────────────────────────────────────────────────────── +class CamStream: + def __init__(self, url): + self.url = url + self.cap = cv2.VideoCapture(url) + self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + self.frame = None + threading.Thread(target=self._run, daemon=True).start() + + def _run(self): + while True: + ret, f = self.cap.read() + if ret: + self.frame = f + time.sleep(0.01) + else: + time.sleep(2) + self.cap.open(self.url) + +def dibujar_track_fusion(frame_show, trk, global_mem): + try: x1, y1, x2, y2 = map(int, trk.box) + except Exception: return + + nombre_str = "" + if trk.gid is not None: + with global_mem.lock: + nombre = global_mem.db.get(trk.gid, {}).get('nombre') + if nombre: nombre_str = f" [{nombre}]" + + if trk.gid is None: color, label = (150, 150, 150), f"?{trk.local_id}" + elif nombre_str: color, label = (255, 0, 255), f"ID:{trk.gid}{nombre_str}" + elif trk.en_grupo: color, label = (0, 0, 255), f"ID:{trk.gid} [grp]" + elif trk.aprendiendo: color, label = (255, 255, 0), f"ID:{trk.gid} [++]" + elif trk.origen_global: color, label = (0, 165, 255), f"ID:{trk.gid} [re-id]" + else: color, label = (0, 255, 0), f"ID:{trk.gid}" + + cv2.rectangle(frame_show, (x1, y1), (x2, y2), color, 2) + (tw, th), _ = cv2.getTextSize(label, FUENTE, 0.55, 1) + cv2.rectangle(frame_show, (x1, y1-th-6), (x1+tw+2, y1), color, -1) + cv2.putText(frame_show, label, (x1+1, y1-4), FUENTE, 0.55, (0,0,0), 1) + +def main(): + print("\nIniciando Sistema") + model = YOLO("yolov8n.pt") + global_mem = GlobalMemory() + managers = {str(c): CamManager(c, global_mem) for c in SECUENCIA} + cams = [CamStream(u) for u in URLS] + + for _ in range(2): + threading.Thread(target=worker_rostros, args=(global_mem,), daemon=True).start() + + cv2.namedWindow("SmartSoft", cv2.WINDOW_AUTOSIZE) + idx = 0 + + while True: + now = time.time() + tiles = [] + cam_ia = idx % len(cams) + + for i, cam_obj in enumerate(cams): + frame = cam_obj.frame; cid = str(SECUENCIA[i]) + if frame is None: + tiles.append(np.zeros((270, 480, 3), np.uint8)) + continue + + frame_show = cv2.resize(frame.copy(), (480, 270)) + boxes = [] + turno_activo = (i == cam_ia) + + if turno_activo: + res = model.predict(frame_show, conf=0.40, iou=0.50, classes=[0], verbose=False, imgsz=480) + if res[0].boxes: + boxes = res[0].boxes.xyxy.cpu().numpy().tolist() + + tracks = managers[cid].update(boxes, frame_show, now, turno_activo) + + for trk in tracks: + if trk.time_since_update <= 1: + dibujar_track_fusion(frame_show, trk, global_mem) + + if turno_activo and trk.gid is not None and not getattr(trk, 'procesando_rostro', False): + if not COLA_ROSTROS.full(): + trk.procesando_rostro = True + COLA_ROSTROS.put((frame.copy(), trk.box, trk.gid, cid, trk)) + + if turno_activo: cv2.circle(frame_show, (460, 20), 6, (0, 0, 255), -1) + + con_id = sum(1 for t in tracks if t.gid and t.time_since_update==0) + cv2.putText(frame_show, f"CAM {cid} [{con_id} ID]", (10, 28), FUENTE, 0.7, (255, 255, 255), 2) + tiles.append(frame_show) + + if len(tiles) == 6: + cv2.imshow("SmartSoft Fusion", np.vstack([np.hstack(tiles[0:3]), np.hstack(tiles[3:6])])) + + idx += 1 + if cv2.waitKey(1) == ord('q'): + break + + cv2.destroyAllWindows() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/noche.mp3 b/noche.mp3 new file mode 100644 index 0000000..79e0b2a Binary files /dev/null and b/noche.mp3 differ diff --git a/osnet_x0_25_msmt17.onnx b/osnet_x0_25_msmt17.onnx new file mode 100644 index 0000000..917d3e8 Binary files /dev/null and b/osnet_x0_25_msmt17.onnx differ diff --git a/reconocimiento2.py b/reconocimiento2.py new file mode 100644 index 0000000..df4a647 --- /dev/null +++ b/reconocimiento2.py @@ -0,0 +1,444 @@ +import os +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' +os.environ['CUDA_VISIBLE_DEVICES'] = '-1' + +import cv2 +import numpy as np +from deepface import DeepFace +import pickle +import time +import threading +import asyncio +import edge_tts +import subprocess +from datetime import datetime +import warnings +import urllib.request + +warnings.filterwarnings("ignore") + +# ────────────────────────────────────────────────────────────────────────────── +# CONFIGURACIÓN +# ────────────────────────────────────────────────────────────────────────────── +DB_PATH = "db_institucion" +CACHE_PATH = "cache_nombres" +VECTORS_FILE = "representaciones.pkl" +TIMESTAMPS_FILE = "representaciones_timestamps.pkl" +UMBRAL_SIM = 0.50 # Por encima → identificado. Por debajo → desconocido. +COOLDOWN_TIME = 15 # Segundos entre saludos + +USUARIO, PASSWORD, IP_DVR = "admin", "TCA200503", "192.168.1.65" +RTSP_URL = f"rtsp://{USUARIO}:{PASSWORD}@{IP_DVR}:554/Streaming/Channels/702" + +for path in [DB_PATH, CACHE_PATH]: + os.makedirs(path, exist_ok=True) + +# ────────────────────────────────────────────────────────────────────────────── +# YUNET — Detector facial rápido en CPU +# ────────────────────────────────────────────────────────────────────────────── +YUNET_MODEL_PATH = "face_detection_yunet_2023mar.onnx" + +if not os.path.exists(YUNET_MODEL_PATH): + print(f"Descargando YuNet ({YUNET_MODEL_PATH})...") + url = ("https://github.com/opencv/opencv_zoo/raw/main/models/" + "face_detection_yunet/face_detection_yunet_2023mar.onnx") + urllib.request.urlretrieve(url, YUNET_MODEL_PATH) + print("YuNet descargado.") + +# Detector estricto para ROIs grandes (persona cerca) +# score_threshold alto → menos falsos positivos en fondos +detector_yunet = cv2.FaceDetectorYN.create( + model=YUNET_MODEL_PATH, config="", + input_size=(320, 320), + score_threshold=0.70, + nms_threshold=0.3, + top_k=5000 +) + +# Detector permisivo para ROIs pequeños (persona lejos) +# score_threshold bajo → no perdemos caras pequeñas o a contraluz +detector_yunet_lejano = cv2.FaceDetectorYN.create( + model=YUNET_MODEL_PATH, config="", + input_size=(320, 320), + score_threshold=0.45, + nms_threshold=0.3, + top_k=5000 +) + +def detectar_rostros_yunet(roi, lock=None): + """ + Elige automáticamente el detector según el tamaño del ROI. + ROI grande → detector estricto (evita falsos positivos). + ROI pequeño → detector permisivo (no pierde caras lejanas). + Devuelve lista de (x, y, w, h) o lista vacía. + """ + h_roi, w_roi = roi.shape[:2] + area = w_roi * h_roi + det = detector_yunet if area > 8000 else detector_yunet_lejano + + try: + if lock: + with lock: + det.setInputSize((w_roi, h_roi)) + _, faces = det.detect(roi) + else: + det.setInputSize((w_roi, h_roi)) + _, faces = det.detect(roi) + except Exception: + return [] + + if faces is None: + return [] + + resultado = [] + for face in faces: + try: + fx, fy, fw, fh = map(int, face[:4]) + score = float(face[14]) if len(face) > 14 else 1.0 + resultado.append((fx, fy, fw, fh, score)) + except (ValueError, OverflowError, TypeError): + continue + return resultado + + +# ────────────────────────────────────────────────────────────────────────────── +# SISTEMA DE AUDIO +# ────────────────────────────────────────────────────────────────────────────── +def obtener_audios_humanos(genero): + hora = datetime.now().hour + es_mujer = genero.lower() == 'woman' + suffix = "_m.mp3" if es_mujer else "_h.mp3" + if 5 <= hora < 12: + intro = "dias.mp3" + elif 12 <= hora < 19: + intro = "tarde.mp3" + else: + intro = "noches.mp3" + cierre = ("fin_noche" if (hora >= 19 or hora < 5) else "fin_dia") + suffix + return intro, cierre + + +async def sintetizar_nombre(nombre, ruta): + """Genera el audio del nombre con edge-tts (solo si no existe en caché).""" + nombre_limpio = nombre.replace('_', ' ') + try: + comunicador = edge_tts.Communicate(nombre_limpio, "es-MX-DaliaNeural", rate="+10%") + await comunicador.save(ruta) + except Exception: + pass + + +def reproducir(archivo): + """Lanza mpv en segundo plano sin bloquear.""" + if os.path.exists(archivo): + subprocess.Popen( + ["mpv", "--no-video", "--volume=100", archivo], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + + +# mpv encadena los 3 archivos en una sola llamada +def hilo_bienvenida(nombre, genero): + """ + Reproduce intro + nombre + cierre sin time.sleep(). + mpv los reproduce en orden nativamente → el hilo queda libre de inmediato. + """ + archivo_nombre = os.path.join(CACHE_PATH, f"nombre_{nombre}.mp3") + + # Sintetizar solo si no estaba en caché + if not os.path.exists(archivo_nombre): + try: + asyncio.run(sintetizar_nombre(nombre, archivo_nombre)) + except Exception: + pass + + intro, cierre = obtener_audios_humanos(genero) + + # Una sola llamada a mpv con los 3 archivos en secuencia + archivos = [f for f in [intro, archivo_nombre, cierre] if os.path.exists(f)] + if archivos: + subprocess.Popen( + ["mpv", "--no-video", "--volume=100"] + archivos, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + + +# ────────────────────────────────────────────────────────────────────────────── +# GESTIÓN DE BASE DE DATOS +# Incremental: solo procesa fotos nuevas o modificadas +# Vectores normalizados al guardar → comparación 3x más rápida +# ────────────────────────────────────────────────────────────────────────────── +def gestionar_vectores(actualizar=False): + """ + Carga o actualiza el diccionario {nombre: vector_normalizado}. + + - Si actualizar=False y el .pkl existe → carga directamente (instantáneo). + - Si actualizar=True → solo reprocesa fotos nuevas o modificadas (incremental). + - Los vectores se guardan NORMALIZADOS → la comparación es un simple np.dot(). + """ + vectores_actuales = {} + + # Intentar cargar lo que ya teníamos + if os.path.exists(VECTORS_FILE): + try: + with open(VECTORS_FILE, 'rb') as f: + vectores_actuales = pickle.load(f) + except Exception: + vectores_actuales = {} + + if not actualizar: + return vectores_actuales + + # ── Cargar timestamps para detectar cambios ─────────────────────────────── + timestamps = {} + if os.path.exists(TIMESTAMPS_FILE): + try: + with open(TIMESTAMPS_FILE, 'rb') as f: + timestamps = pickle.load(f) + except Exception: + timestamps = {} + + print("\nACTUALIZANDO BASE DE DATOS (solo fotos nuevas o modificadas)...") + imagenes = [f for f in os.listdir(DB_PATH) if f.lower().endswith(('.jpg', '.png'))] + nombres_en_disco = set() + hubo_cambios = False + + for archivo in imagenes: + nombre_archivo = os.path.splitext(archivo)[0] + ruta_img = os.path.join(DB_PATH, archivo) + nombres_en_disco.add(nombre_archivo) + + ts_actual = os.path.getmtime(ruta_img) + ts_guardado = timestamps.get(nombre_archivo, 0) + + # Si ya la teníamos y no cambió → saltar (no llamar a ArcFace) + if nombre_archivo in vectores_actuales and ts_actual == ts_guardado: + continue + + try: + res = DeepFace.represent( + img_path=ruta_img, model_name="ArcFace", enforce_detection=True + ) + emb = np.array(res[0]["embedding"], dtype=np.float32) + + # MEJORA 2: Normalizar al guardar (una sola vez para siempre) + norma = np.linalg.norm(emb) + if norma > 0: + emb = emb / norma + + vectores_actuales[nombre_archivo] = emb + timestamps[nombre_archivo] = ts_actual + hubo_cambios = True + print(f" Procesado: {nombre_archivo}") + + except Exception: + print(f" Rostro no válido en '{archivo}', omitido.") + + # Eliminar personas cuya foto fue borrada del disco + for nombre in list(vectores_actuales.keys()): + if nombre not in nombres_en_disco: + del vectores_actuales[nombre] + timestamps.pop(nombre, None) + hubo_cambios = True + print(f" Eliminado (sin foto): {nombre}") + + if hubo_cambios: + with open(VECTORS_FILE, 'wb') as f: + pickle.dump(vectores_actuales, f) + with open(TIMESTAMPS_FILE, 'wb') as f: + pickle.dump(timestamps, f) + print(" Sincronización terminada.\n") + else: + print(" Sin cambios. Base de datos al día.\n") + + return vectores_actuales + + +def buscar_mejor_match(emb_consulta, base_datos): + """ + Compara un embedding (ya normalizado) contra la base de datos. + MEJORA 2: Solo producto punto → no hay divisiones por norma en el loop. + Devuelve (mejor_nombre, similitud_maxima). + """ + # Normalizar el embedding de consulta + norma = np.linalg.norm(emb_consulta) + if norma > 0: + emb_consulta = emb_consulta / norma + + mejor_match, max_sim = None, -1.0 + for nombre, vec in base_datos.items(): + # vec ya está normalizado → sim coseno = producto punto puro + sim = float(np.dot(emb_consulta, vec)) + if sim > max_sim: + max_sim, mejor_match = sim, nombre + + return mejor_match, max_sim + + +# ────────────────────────────────────────────────────────────────────────────── +# LOOP DE PRUEBA Y REGISTRO +# MEJORA 5 — Reemplaza face_cascade por YuNet (consistente con principal2.py) +# ────────────────────────────────────────────────────────────────────────────── +def sistema_interactivo(): + """ + Ventana de depuración y registro de nuevas personas. + Muestra barra de similitud en consola y en pantalla. + Controles: [R] Registrar | [Q] Salir + """ + base_datos = gestionar_vectores(actualizar=False) + cap = cv2.VideoCapture(RTSP_URL) + ultimo_saludo = 0 + persona_actual = None + confirmaciones = 0 + + print("\n" + "=" * 50) + print(" MÓDULO DE REGISTRO Y DEPURACIÓN") + print(" [R] Registrar nuevo rostro | [Q] Salir") + print("=" * 50 + "\n") + + faces_ultimo_frame = [] # Para que [R] pueda usarlas aunque no haya detección nueva + + while True: + ret, frame = cap.read() + if not ret: + time.sleep(2) + cap.open(RTSP_URL) + continue + + h, w = frame.shape[:2] + display_frame = frame.copy() + tiempo_actual = time.time() + + # ── Detección con YuNet sobre el frame completo ─────────────────────── + faces_raw = detectar_rostros_yunet(frame) + faces_ultimo_frame = faces_raw # Guardamos para usar con [R] + + for (fx, fy, fw, fh, score_yunet) in faces_raw: + # Clampear dentro de la imagen + fx = max(0, fx); fy = max(0, fy) + fw = min(w - fx, fw); fh = min(h - fy, fh) + if fw <= 0 or fh <= 0: + continue + + # Rectángulo base (amarillo) + cv2.rectangle(display_frame, (fx, fy), (fx+fw, fy+fh), (255, 200, 0), 2) + cv2.putText(display_frame, f"YN:{score_yunet:.2f}", + (fx, fy - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 200, 0), 1) + + if (tiempo_actual - ultimo_saludo) <= COOLDOWN_TIME: + continue # En cooldown, no procesamos + + # ── Recorte del rostro con margen ───────────────────────────────── + m = int(fw * 0.15) + roi = frame[max(0, fy-m): min(h, fy+fh+m), + max(0, fx-m): min(w, fx+fw+m)] + + if roi.size == 0 or roi.shape[0] < 40 or roi.shape[1] < 40: + cv2.putText(display_frame, "muy pequeño", + (fx, fy-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 100, 255), 1) + continue + + # ── Filtro de nitidez (descarta caras movidas antes de ArcFace) ─── + gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) + nitidez = cv2.Laplacian(gray_roi, cv2.CV_64F).var() + if nitidez < 50.0: + cv2.putText(display_frame, f"blur({nitidez:.0f})", + (fx, fy-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 165, 255), 1) + continue + + # ── ArcFace ─────────────────────────────────────────────────────── + try: + res = DeepFace.represent( + img_path=roi, model_name="ArcFace", enforce_detection=False + ) + emb = np.array(res[0]["embedding"], dtype=np.float32) + mejor_match, max_sim = buscar_mejor_match(emb, base_datos) + + except Exception as e: + print(f"[ERROR ArcFace]: {e}") + continue + + # ── Barra de similitud en consola ───────────────────────────────── + estado = " IDENTIFICADO" if max_sim > UMBRAL_SIM else "DESCONOCIDO" + nombre_d = mejor_match.split('_')[0] if mejor_match else "nadie" + n_bloques = int(max_sim * 20) + barra = "█" * n_bloques + "░" * (20 - n_bloques) + print(f"[REGISTRO] {estado} | {nombre_d:<14} | {barra} | " + f"{max_sim*100:.1f}% (umbral: {UMBRAL_SIM*100:.0f}%)") + + # ── Resultado en pantalla ───────────────────────────────────────── + if max_sim > UMBRAL_SIM and mejor_match: + color = (0, 255, 0) + texto = f"{mejor_match.split('_')[0]} ({max_sim:.2f})" + + if mejor_match == persona_actual: + confirmaciones += 1 + else: + persona_actual, confirmaciones = mejor_match, 1 + + if confirmaciones >= 2: + cv2.rectangle(display_frame, (fx, fy), (fx+fw, fy+fh), (0, 255, 0), 3) + try: + analisis = DeepFace.analyze( + roi, actions=['gender'], enforce_detection=False + )[0] + genero = analisis['dominant_gender'] + except Exception: + genero = "Man" + + threading.Thread( + target=hilo_bienvenida, + args=(mejor_match, genero), + daemon=True + ).start() + ultimo_saludo = tiempo_actual + confirmaciones = 0 + + else: + color = (0, 0, 255) + texto = f"? ({max_sim:.2f})" + confirmaciones = max(0, confirmaciones - 1) + + cv2.putText(display_frame, texto, + (fx, fy - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) + + # ── Mostrar frame ───────────────────────────────────────────────────── + cv2.imshow("Módulo de Registro", display_frame) + key = cv2.waitKey(1) & 0xFF + + if key == ord('q'): + break + + #R: Registrar el rostro más grande del frame ───────────────── + elif key == ord('r'): + if faces_ultimo_frame: + # Elegimos la cara más grande (más cercana) + areas = [fw * fh for (fx, fy, fw, fh, _) in faces_ultimo_frame] + fx, fy, fw, fh, _ = faces_ultimo_frame[np.argmax(areas)] + m = 25 + face_roi = frame[max(0, fy-m): min(h, fy+fh+m), + max(0, fx-m): min(w, fx+fw+m)] + + if face_roi.size > 0: + nom = input("\nNombre de la persona: ").strip() + if nom: + foto_path = os.path.join(DB_PATH, f"{nom}.jpg") + cv2.imwrite(foto_path, face_roi) + print(f"[OK] Rostro de '{nom}' guardado. Sincronizando...") + base_datos = gestionar_vectores(actualizar=True) + else: + print("[!] Registro cancelado.") + else: + print("[!] Recorte vacío. Intenta de nuevo.") + else: + print("\n[!] No se detectó rostro. Acércate más o mira a la lente.") + + cap.release() + cv2.destroyAllWindows() + + +# ────────────────────────────────────────────────────────────────────────────── +if __name__ == "__main__": + sistema_interactivo() \ No newline at end of file diff --git a/representaciones.pkl b/representaciones.pkl new file mode 100644 index 0000000..d2cff45 Binary files /dev/null and b/representaciones.pkl differ diff --git a/representaciones_timestamps.pkl b/representaciones_timestamps.pkl new file mode 100644 index 0000000..8cc84a1 Binary files /dev/null and b/representaciones_timestamps.pkl differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1096595 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,96 @@ +absl-py==2.4.0 +aiohappyeyeballs==2.6.1 +aiohttp==3.13.3 +aiosignal==1.4.0 +astunparse==1.6.3 +attrs==25.4.0 +beautifulsoup4==4.14.3 +blinker==1.9.0 +certifi==2026.1.4 +charset-normalizer==3.4.4 +click==8.3.1 +contourpy==1.3.3 +cycler==0.12.1 +deepface==0.0.98 +edge-tts==7.2.7 +filelock==3.20.0 +fire==0.7.1 +Flask==3.1.2 +flask-cors==6.0.2 +flatbuffers==25.12.19 +fonttools==4.61.1 +frozenlist==1.8.0 +fsspec==2025.12.0 +gast==0.7.0 +gdown==5.2.1 +google-pasta==0.2.0 +grpcio==1.78.0 +gunicorn==25.0.3 +h5py==3.15.1 +idna==3.11 +itsdangerous==2.2.0 +Jinja2==3.1.6 +joblib==1.5.3 +keras==3.13.2 +kiwisolver==1.4.9 +lap==0.5.12 +libclang==18.1.1 +lightdsa==0.0.3 +lightecc==0.0.4 +lightphe==0.0.20 +lz4==4.4.5 +Markdown==3.10.2 +markdown-it-py==4.0.0 +MarkupSafe==2.1.5 +matplotlib==3.10.8 +mdurl==0.1.2 +ml_dtypes==0.5.4 +mpmath==1.3.0 +mtcnn==1.0.0 +multidict==6.7.1 +namex==0.1.0 +networkx==3.6.1 +numpy==1.26.4 +onnxruntime==1.24.2 +opencv-python==4.11.0.86 +opt_einsum==3.4.0 +optree==0.18.0 +packaging==26.0 +pandas==3.0.0 +pillow==12.0.0 +polars==1.38.1 +polars-runtime-32==1.38.1 +propcache==0.4.1 +protobuf==6.33.5 +psutil==7.2.2 +Pygments==2.19.2 +pyparsing==3.3.2 +PySocks==1.7.1 +python-dateutil==2.9.0.post0 +python-dotenv==1.2.1 +PyYAML==6.0.3 +requests==2.32.5 +retina-face==0.0.17 +rich==14.3.2 +scipy==1.17.0 +six==1.17.0 +soupsieve==2.8.3 +sympy==1.14.0 +tabulate==0.9.0 +tensorboard==2.20.0 +tensorboard-data-server==0.7.2 +tensorflow==2.20.0 +tensorflow-io-gcs-filesystem==0.37.1 +termcolor==3.3.0 +tf_keras==2.20.1 +torch==2.10.0+cpu +torchreid==0.2.5 +torchvision==0.25.0+cpu +tqdm==4.67.3 +typing_extensions==4.15.0 +ultralytics==8.4.14 +ultralytics-thop==2.0.18 +urllib3==2.6.3 +Werkzeug==3.1.5 +wrapt==2.1.1 +yarl==1.22.0 diff --git a/seguimiento2.py b/seguimiento2.py new file mode 100644 index 0000000..7a8163f --- /dev/null +++ b/seguimiento2.py @@ -0,0 +1,679 @@ +import cv2 +import numpy as np +import time +import threading +from scipy.optimize import linear_sum_assignment +from scipy.spatial.distance import cosine +from ultralytics import YOLO +import onnxruntime as ort +import os + +# ────────────────────────────────────────────────────────────────────────────── +# CONFIGURACIÓN DEL SISTEMA +# ────────────────────────────────────────────────────────────────────────────── +USUARIO, PASSWORD, IP_DVR = "admin", "TCA200503", "192.168.1.65" +SECUENCIA = [1, 7, 5, 8, 3, 6] +os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;tcp" +URLS = [f"rtsp://{USUARIO}:{PASSWORD}@{IP_DVR}:554/Streaming/Channels/{i}02" for i in SECUENCIA] +ONNX_MODEL_PATH = "osnet_x0_25_msmt17.onnx" + +VECINOS = { + "1": ["7"], "7": ["1", "5"], "5": ["7", "8"], + "8": ["5", "3"], "3": ["8", "6"], "6": ["3"] +} + +# ─── PARÁMETROS TÉCNICOS +ASPECT_RATIO_MIN = 0.5 +ASPECT_RATIO_MAX = 4.0 +AREA_MIN_CALIDAD = 1200 +FRAMES_CALIDAD = 2 +TIEMPO_MAX_AUSENCIA = 800.0 + +# ─── TIEMPOS DE VIDA DE TRACKERS +TIEMPO_PACIENCIA_BORDE_CON_ID = 5.0 # Segundos antes de matar un ID conocido en borde +TIEMPO_PACIENCIA_BORDE_SIN_ID = 1.0 # Segundos antes de matar un candidato en borde +TIEMPO_PACIENCIA_INTERIOR = 8.0 # Vida para ID conocido en el interior +TIEMPO_PACIENCIA_INTERIOR_NUEVO = 1.5 # Vida para candidato sin ID en interior + +# ─── RE-ADQUISICIÓN RÁPIDA (persona que sale y entra por la misma puerta) +RADIO_REENTRADA = 80 # Píxeles: si reaparece a menos de esta distancia, re-adquirir +TIEMPO_REENTRADA = 15.0 # Segundos máximos para considerar una reentrada + +# ─── UMBRALES DE RE-ID +# ⚡ UMBRAL_REID_MISMA_CAM ajustado a 0.65 para recuperar estabilidad en sombras y giros +UMBRAL_REID_MISMA_CAM = 0.65 +UMBRAL_REID_VECINO = 0.62 +UMBRAL_REID_NO_VECINO = 0.80 +MARGEN_MINIMO_REID = 0.07 +MAX_FIRMAS_MEMORIA = 15 + +# ─── TIEMPO ENTRE TURNOS ACTIVOS (para el anti-fantasma dinámico) +TIEMPO_TURNO_ROTATIVO = len(SECUENCIA) * 0.035 + +# ─── COLORES Y FUENTE +C_CANDIDATO = (150, 150, 150) +C_LOCAL = (0, 255, 0) +C_GLOBAL = (0, 165, 255) +C_GRUPO = (0, 0, 255) +C_APRENDIZAJE = (255, 255, 0) +FUENTE = cv2.FONT_HERSHEY_SIMPLEX + +# ────────────────────────────────────────────────────────────────────────────── +# INICIALIZACIÓN DEL MOTOR DEEP LEARNING (ONNX) +# ────────────────────────────────────────────────────────────────────────────── +print("Cargando motor de Re-Identificación (OSNet ONNX en CPU)...") +try: + ort_session = ort.InferenceSession(ONNX_MODEL_PATH, providers=['CPUExecutionProvider']) + input_name = ort_session.get_inputs()[0].name + print("✅ OSNet cargado.") +except Exception as e: + print(f"ERROR FATAL: No se pudo cargar {ONNX_MODEL_PATH}. {e}") + exit() + +MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape(1, 3, 1, 1) +STD = np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape(1, 3, 1, 1) + +# ────────────────────────────────────────────────────────────────────────────── +# 1. MOTOR HÍBRIDO DE FIRMA +# ────────────────────────────────────────────────────────────────────────────── +def analizar_calidad(box): + x1, y1, x2, y2 = box + w, h = x2 - x1, y2 - y1 + if w <= 0 or h <= 0: + return False + return (ASPECT_RATIO_MIN < (h / w) < ASPECT_RATIO_MAX) and ((w * h) > AREA_MIN_CALIDAD) + +def preprocess_onnx(roi): + img = cv2.resize(roi, (128, 256)) + img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + img = img.transpose(2, 0, 1).astype(np.float32) / 255.0 + img = np.expand_dims(img, axis=0) + img = (img - MEAN) / STD + return img + +def extraer_color_zonas(roi): + h_roi = roi.shape[0] + t1 = int(h_roi * 0.15) + t2 = int(h_roi * 0.55) + zonas = [roi[:t1, :], roi[t1:t2, :], roi[t2:, :]] + + def hist_zona(z): + if z.size == 0: + return np.zeros(16 * 8) + hsv = cv2.cvtColor(z, cv2.COLOR_BGR2HSV) + hist = cv2.calcHist([hsv], [0, 1], None, [16, 8], [0, 180, 0, 256]) + cv2.normalize(hist, hist) + return hist.flatten() + + return np.concatenate([hist_zona(z) for z in zonas]) + +def extraer_firma_hibrida(frame, box): + try: + x1, y1, x2, y2 = map(int, box) + fh, fw = frame.shape[:2] + x1_c = max(0, x1); y1_c = max(0, y1) + x2_c = min(fw, x2); y2_c = min(fh, y2) + roi = frame[y1_c:y2_c, x1_c:x2_c] + + if roi.size == 0 or roi.shape[0] < 20 or roi.shape[1] < 10: + return None + + calidad_area = (x2_c - x1_c) * (y2_c - y1_c) + + # ⚡ ROI CRUDA: Pasamos la imagen tal cual, sin alterar su contraste artificialmente + blob = preprocess_onnx(roi) + blob_16 = np.zeros((16, 3, 256, 128), dtype=np.float32) + blob_16[0] = blob[0] + deep_feat = ort_session.run(None, {input_name: blob_16})[0][0].flatten() + norma = np.linalg.norm(deep_feat) + if norma > 0: + deep_feat = deep_feat / norma + + color_feat = extraer_color_zonas(roi) + + return {'deep': deep_feat, 'color': color_feat, 'calidad': calidad_area} + + except Exception: + return None + +def similitud_hibrida(f1, f2, cross_cam=False): + if f1 is None or f2 is None: + return 0.0 + + sim_deep = max(0.0, 1.0 - cosine(f1['deep'], f2['deep'])) + + if f1['color'].shape == f2['color'].shape and f1['color'].size > 1: + L = len(f1['color']) // 3 + sim_head = max(0.0, float(cv2.compareHist( + f1['color'][:L].astype(np.float32), + f2['color'][:L].astype(np.float32), cv2.HISTCMP_CORREL))) + sim_torso = max(0.0, float(cv2.compareHist( + f1['color'][L:2*L].astype(np.float32), + f2['color'][L:2*L].astype(np.float32), cv2.HISTCMP_CORREL))) + sim_legs = max(0.0, float(cv2.compareHist( + f1['color'][2*L:].astype(np.float32), + f2['color'][2*L:].astype(np.float32), cv2.HISTCMP_CORREL))) + sim_color = (0.10 * sim_head) + (0.60 * sim_torso) + (0.30 * sim_legs) + else: + sim_color = 0.0 + + if cross_cam: + return (sim_deep * 0.75) + (sim_color * 0.25) + else: + return (sim_deep * 0.85) + (sim_color * 0.15) + +# ────────────────────────────────────────────────────────────────────────────── +# 2. KALMAN TRACKER +# ────────────────────────────────────────────────────────────────────────────── +class KalmanTrack: + _count = 0 + + def __init__(self, box, now): + self.kf = cv2.KalmanFilter(7, 4) + self.kf.measurementMatrix = np.array([ + [1,0,0,0,0,0,0], [0,1,0,0,0,0,0], + [0,0,1,0,0,0,0], [0,0,0,1,0,0,0]], np.float32) + self.kf.transitionMatrix = np.eye(7, dtype=np.float32) + self.kf.transitionMatrix[0,4] = 1 + self.kf.transitionMatrix[1,5] = 1 + self.kf.transitionMatrix[2,6] = 1 + self.kf.processNoiseCov *= 0.03 + self.kf.statePost = np.zeros((7, 1), np.float32) + self.kf.statePost[:4] = self._convert_bbox_to_z(box) + + self.local_id = KalmanTrack._count + KalmanTrack._count += 1 + + self.gid = None + self.origen_global = False + self.aprendiendo = False + self.box = list(box) + self.ts_creacion = now + self.ts_ultima_deteccion = now + self.time_since_update = 0 + self.en_grupo = False + self.frames_buena_calidad = 0 + self.listo_para_id = False + self.area_referencia = 0.0 + self.ultimo_aprendizaje = 0.0 + + def _convert_bbox_to_z(self, bbox): + w = bbox[2] - bbox[0]; h = bbox[3] - bbox[1] + x = bbox[0] + w / 2.; y = bbox[1] + h / 2. + return np.array([[x],[y],[w*h],[w/float(h+1e-6)]]).astype(np.float32) + + def _convert_x_to_bbox(self, x): + cx = float(x[0].item()); cy = float(x[1].item()) + s = float(x[2].item()); r = float(x[3].item()) + w = np.sqrt(s * r); h = s / (w + 1e-6) + return [cx-w/2., cy-h/2., cx+w/2., cy+h/2.] + + def predict(self, turno_activo=True): + if (self.kf.statePost[6] + self.kf.statePost[2]) <= 0: + self.kf.statePost[6] *= 0.0 + self.kf.predict() + if turno_activo: + self.time_since_update += 1 + self.aprendiendo = False + self.box = self._convert_x_to_bbox(self.kf.statePre) + return self.box + + def update(self, box, en_grupo, now): + self.ts_ultima_deteccion = now + self.time_since_update = 0 + self.box = list(box) + self.en_grupo = en_grupo + self.kf.correct(self._convert_bbox_to_z(box)) + + if analizar_calidad(box) and not en_grupo: + self.frames_buena_calidad += 1 + if self.frames_buena_calidad >= FRAMES_CALIDAD: + self.listo_para_id = True + elif self.gid is None: + self.frames_buena_calidad = max(0, self.frames_buena_calidad - 1) + +# ────────────────────────────────────────────────────────────────────────────── +# 3. MEMORIA GLOBAL +# ────────────────────────────────────────────────────────────────────────────── +class GlobalMemory: + def __init__(self): + self.db = {} + self.next_gid = 100 + self.lock = threading.Lock() + + def _es_transito_posible(self, data, cam_destino, now): + ultima_cam = str(data['last_cam']) + cam_destino = str(cam_destino) + dt = now - data['ts'] + if ultima_cam == cam_destino: + return True + vecinos = VECINOS.get(ultima_cam, []) + if cam_destino in vecinos: + return dt >= -0.5 + return dt >= 4.0 + + def _sim_robusta(self, firma_nueva, firmas_guardadas, cross_cam=False): + if not firmas_guardadas: + return 0.0 + sims = sorted( + [similitud_hibrida(firma_nueva, f, cross_cam=cross_cam) + for f in firmas_guardadas], + reverse=True + ) + if len(sims) == 1: + return sims[0] + elif len(sims) <= 3: + return (sims[0] * 0.60) + (sims[1] * 0.40) + else: + return (sims[0] * 0.50) + (sims[1] * 0.30) + (sims[2] * 0.20) + + def identificar_candidato(self, firma_hibrida, cam_id, now, active_gids): + with self.lock: + candidatos = [] + vecinos = VECINOS.get(str(cam_id), []) + + for gid, data in self.db.items(): + if gid in active_gids: + continue + dt = now - data['ts'] + if dt > TIEMPO_MAX_AUSENCIA: + continue + if not self._es_transito_posible(data, cam_id, now): + continue + if not data['firmas']: + continue + + cross_cam = str(data['last_cam']) != str(cam_id) + sim = self._sim_robusta(firma_hibrida, data['firmas'], cross_cam=cross_cam) + + misma_cam = str(data['last_cam']) == str(cam_id) + es_vecino = str(data['last_cam']) in vecinos + + if misma_cam: + umbral = UMBRAL_REID_MISMA_CAM + elif es_vecino: + umbral = UMBRAL_REID_VECINO + else: + umbral = UMBRAL_REID_NO_VECINO + + if sim > umbral: + candidatos.append((sim, gid)) + + if not candidatos: + nid = self.next_gid + self.next_gid += 1 + self._actualizar_sin_lock(nid, firma_hibrida, cam_id, now) + return nid, False + + candidatos.sort(reverse=True) + sim_1, best_gid = candidatos[0] + + if len(candidatos) >= 2: + sim_2 = candidatos[1][0] + margen = sim_1 - sim_2 + if margen < MARGEN_MINIMO_REID: + nid = self.next_gid + self.next_gid += 1 + self._actualizar_sin_lock(nid, firma_hibrida, cam_id, now) + return nid, False + + self._actualizar_sin_lock(best_gid, firma_hibrida, cam_id, now) + return best_gid, True + + def _actualizar_sin_lock(self, gid, firma_dict, cam_id, now): + if gid not in self.db: + self.db[gid] = {'firmas': [], 'last_cam': cam_id, 'ts': now} + + if firma_dict is not None: + firmas_list = self.db[gid]['firmas'] + + if not firmas_list: + firmas_list.append(firma_dict) + else: + if firma_dict['calidad'] > (firmas_list[0]['calidad'] * 1.50): + vieja_ancla = firmas_list[0] + firmas_list[0] = firma_dict + firma_dict = vieja_ancla + + if len(firmas_list) >= MAX_FIRMAS_MEMORIA: + max_sim_interna = -1.0 + idx_redundante = 1 + for i in range(1, len(firmas_list)): + sims_con_otras = [ + similitud_hibrida(firmas_list[i], firmas_list[j]) + for j in range(1, len(firmas_list)) if j != i + ] + sim_promedio = float(np.mean(sims_con_otras)) if sims_con_otras else 0.0 + if sim_promedio > max_sim_interna: + max_sim_interna = sim_promedio + idx_redundante = i + firmas_list[idx_redundante] = firma_dict + else: + firmas_list.append(firma_dict) + + self.db[gid]['last_cam'] = cam_id + self.db[gid]['ts'] = now + + def actualizar(self, gid, firma, cam_id, now): + with self.lock: + self._actualizar_sin_lock(gid, firma, cam_id, now) + +# ────────────────────────────────────────────────────────────────────────────── +# 4. GESTOR LOCAL +# ────────────────────────────────────────────────────────────────────────────── +def iou_overlap(boxA, boxB): + xA = max(boxA[0], boxB[0]); yA = max(boxA[1], boxB[1]) + xB = min(boxA[2], boxB[2]); yB = min(boxA[3], boxB[3]) + inter = max(0, xB-xA) * max(0, yB-yA) + areaA = (boxA[2]-boxA[0]) * (boxA[3]-boxA[1]) + areaB = (boxB[2]-boxB[0]) * (boxB[3]-boxB[1]) + return inter / (areaA + areaB - inter + 1e-6) + +class CamManager: + def __init__(self, cam_id, global_mem): + self.cam_id = cam_id + self.global_mem = global_mem + self.trackers = [] + + def update(self, boxes, frame, now, turno_activo): + for trk in self.trackers: + trk.predict(turno_activo=turno_activo) + if not turno_activo: + return self.trackers + + matched, unmatched_dets, _ = self._asignar(boxes, now) + fh, fw = frame.shape[:2] + + for t_idx, d_idx in matched: + trk = self.trackers[t_idx] + box = boxes[d_idx] + + en_grupo = any( + other is not trk and iou_overlap(box, other.box) > 0.10 + for other in self.trackers + ) + trk.update(box, en_grupo, now) + + active_gids_local = {t.gid for t in self.trackers if t.gid is not None} + area_actual = (box[2] - box[0]) * (box[3] - box[1]) + + if trk.gid is None and trk.listo_para_id: + firma = extraer_firma_hibrida(frame, box) + if firma is not None: + gid, es_reid = self.global_mem.identificar_candidato( + firma, self.cam_id, now, active_gids_local + ) + trk.gid = gid + trk.origen_global = es_reid + trk.area_referencia = area_actual + + elif trk.gid is None and not trk.listo_para_id: + firma_rapida = extraer_firma_hibrida(frame, box) + if firma_rapida is not None: + cx_nuevo = (box[0] + box[2]) / 2 + cy_nuevo = (box[1] + box[3]) / 2 + + with self.global_mem.lock: + for gid_cand, data_cand in self.global_mem.db.items(): + if gid_cand in active_gids_local: + continue + if str(data_cand.get('last_cam', '')) != str(self.cam_id): + continue + if (now - data_cand['ts']) > TIEMPO_REENTRADA: + continue + if not data_cand['firmas']: + continue + + ultima_box = data_cand.get('last_box') + if ultima_box is None: + continue + + cx_ult = (ultima_box[0] + ultima_box[2]) / 2 + cy_ult = (ultima_box[1] + ultima_box[3]) / 2 + distancia = np.sqrt((cx_nuevo-cx_ult)**2 + (cy_nuevo-cy_ult)**2) + + if distancia < RADIO_REENTRADA: + sim = similitud_hibrida(firma_rapida, data_cand['firmas'][0]) + if sim > UMBRAL_REID_MISMA_CAM: + trk.gid = gid_cand + trk.origen_global = True + trk.area_referencia = area_actual + trk.listo_para_id = True + self.global_mem._actualizar_sin_lock( + gid_cand, firma_rapida, self.cam_id, now + ) + break + + elif trk.gid is not None and not trk.en_grupo: + tiempo_ultima_firma = trk.ultimo_aprendizaje + if (now - tiempo_ultima_firma) > 1.5 and analizar_calidad(box): + x1b, y1b, x2b, y2b = map(int, box) + en_borde = (x1b < 15 or y1b < 15 or x2b > fw-15 or y2b > fh-15) + + if not en_borde: + firma_nueva = extraer_firma_hibrida(frame, box) + if firma_nueva is not None: + with self.global_mem.lock: + if (trk.gid in self.global_mem.db + and self.global_mem.db[trk.gid]['firmas']): + + firma_ancla = self.global_mem.db[trk.gid]['firmas'][0] + sim_coherencia = similitud_hibrida(firma_nueva, firma_ancla) + + if sim_coherencia > 0.55: + es_coherente = True + for otro_gid, otro_data in self.global_mem.db.items(): + if otro_gid == trk.gid or not otro_data['firmas']: + continue + sim_intruso = similitud_hibrida( + firma_nueva, otro_data['firmas'][0] + ) + if sim_intruso > 0.55: + es_coherente = False + break + + if es_coherente: + self.global_mem._actualizar_sin_lock( + trk.gid, firma_nueva, self.cam_id, now + ) + trk.ultimo_aprendizaje = now + trk.aprendiendo = True + + for trk in self.trackers: + if trk.time_since_update == 0 and trk.gid is not None: + with self.global_mem.lock: + if trk.gid in self.global_mem.db: + self.global_mem.db[trk.gid]['last_box'] = list(trk.box) + + for d_idx in unmatched_dets: + self.trackers.append(KalmanTrack(boxes[d_idx], now)) + + vivos = [] + for t in self.trackers: + x1, y1, x2, y2 = t.box + toca_borde = (x1 < 5 or y1 < 5 or x2 > fw-5 or y2 > fh-5) + tiempo_oculto = now - t.ts_ultima_deteccion + + if toca_borde: + limite = TIEMPO_PACIENCIA_BORDE_CON_ID if t.gid else TIEMPO_PACIENCIA_BORDE_SIN_ID + else: + limite = TIEMPO_PACIENCIA_INTERIOR if t.gid else TIEMPO_PACIENCIA_INTERIOR_NUEVO + + if tiempo_oculto < limite: + vivos.append(t) + + self.trackers = vivos + return self.trackers + + def _asignar(self, boxes, now): + n_trk = len(self.trackers) + n_det = len(boxes) + + if n_trk == 0: return [], list(range(n_det)), [] + if n_det == 0: return [], [], list(range(n_trk)) + + cost_mat = np.zeros((n_trk, n_det), dtype=np.float32) + + for t, trk in enumerate(self.trackers): + for d, det in enumerate(boxes): + iou = iou_overlap(trk.box, det) + cx_t = (trk.box[0]+trk.box[2]) / 2 + cy_t = (trk.box[1]+trk.box[3]) / 2 + cx_d = (det[0]+det[2]) / 2 + cy_d = (det[1]+det[3]) / 2 + dist_norm = np.sqrt((cx_t-cx_d)**2 + (cy_t-cy_d)**2) / 550.0 + + area_trk = (trk.box[2]-trk.box[0]) * (trk.box[3]-trk.box[1]) + area_det = (det[2]-det[0]) * (det[3]-det[1]) + ratio_area = max(area_trk, area_det) / (min(area_trk, area_det) + 1e-6) + castigo_tam = (ratio_area - 1.0) * 0.4 + + tiempo_oculto = now - trk.ts_ultima_deteccion + if tiempo_oculto > (TIEMPO_TURNO_ROTATIVO * 2) and iou < 0.10: + fantasma_penalty = 5.0 + else: + fantasma_penalty = 0.0 + + if iou >= 0.05 or dist_norm < 0.50: + cost_mat[t, d] = ((1.0 - iou) + + (dist_norm * 2.0) + + fantasma_penalty + + castigo_tam) + else: + cost_mat[t, d] = 100.0 + + row_ind, col_ind = linear_sum_assignment(cost_mat) + matched, unmatched_dets, unmatched_trks = [], [], [] + + for r, c in zip(row_ind, col_ind): + if cost_mat[r, c] > 4.0: + unmatched_trks.append(r) + unmatched_dets.append(c) + else: + matched.append((r, c)) + + matched_t = {m[0] for m in matched} + matched_d = {m[1] for m in matched} + for t in range(n_trk): + if t not in matched_t: unmatched_trks.append(t) + for d in range(n_det): + if d not in matched_d: unmatched_dets.append(d) + + return matched, unmatched_dets, unmatched_trks + +# ────────────────────────────────────────────────────────────────────────────── +# 5. STREAM DE CÁMARA +# ────────────────────────────────────────────────────────────────────────────── +class CamStream: + def __init__(self, url): + self.url = url + self.cap = cv2.VideoCapture(url) + self.frame = None + self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + threading.Thread(target=self._run, daemon=True).start() + + def _run(self): + while True: + ret, f = self.cap.read() + if ret: + self.frame = f + time.sleep(0.01) + else: + time.sleep(2) + self.cap.open(self.url) + +# ────────────────────────────────────────────────────────────────────────────── +# 6. DIBUJADO +# ────────────────────────────────────────────────────────────────────────────── +def dibujar_track(frame_show, trk): + try: + x1, y1, x2, y2 = map(int, trk.box) + except Exception: + return + + if trk.gid is None: + color, label = C_CANDIDATO, f"?{trk.local_id}" + elif trk.en_grupo: + color, label = C_GRUPO, f"ID:{trk.gid} [grp]" + elif trk.aprendiendo: + color, label = C_APRENDIZAJE, f"ID:{trk.gid} [++]" + elif trk.origen_global: + color, label = C_GLOBAL, f"ID:{trk.gid} [re-id]" + else: + color, label = C_LOCAL, f"ID:{trk.gid}" + + cv2.rectangle(frame_show, (x1, y1), (x2, y2), color, 2) + (tw, th), _ = cv2.getTextSize(label, FUENTE, 0.55, 1) + cv2.rectangle(frame_show, (x1, y1-th-6), (x1+tw+2, y1), color, -1) + cv2.putText(frame_show, label, (x1+1, y1-4), FUENTE, 0.55, (0, 0, 0), 1) + + if trk.gid is None: + pct = min(trk.frames_buena_calidad / FRAMES_CALIDAD, 1.0) + bw = x2 - x1 + cv2.rectangle(frame_show, (x1, y2+2), (x2, y2+7), (50, 50, 50), -1) + cv2.rectangle(frame_show, (x1, y2+2), (x1+int(bw*pct), y2+7), (0, 220, 220), -1) + +# ────────────────────────────────────────────────────────────────────────────── +# 7. MAIN LOOP (para pruebas standalone) +# ────────────────────────────────────────────────────────────────────────────── +def main(): + print("SmartSoft — seguimiento2.py standalone") + model = YOLO("yolov8n.pt") + global_mem = GlobalMemory() + managers = {str(c): CamManager(c, global_mem) for c in SECUENCIA} + cams = [CamStream(u) for u in URLS] + cv2.namedWindow("SmartSoft", cv2.WINDOW_AUTOSIZE) + + idx = 0 + while True: + now, tiles = time.time(), [] + cam_ia = idx % len(cams) + + for i, cam_obj in enumerate(cams): + frame = cam_obj.frame + cid = str(SECUENCIA[i]) + + if frame is None: + tiles.append(np.zeros((270, 480, 3), np.uint8)) + continue + + frame_show = cv2.resize(frame.copy(), (480, 270)) + boxes = [] + turno_activo = (i == cam_ia) + + if turno_activo: + res = model.predict( + frame_show, conf=0.40, iou=0.50, + classes=[0], verbose=False, imgsz=480 + ) + if res[0].boxes: + boxes = res[0].boxes.xyxy.cpu().numpy().tolist() + + tracks = managers[cid].update(boxes, frame_show, now, turno_activo) + + for trk in tracks: + if trk.time_since_update == 0: + dibujar_track(frame_show, trk) + + if turno_activo: + cv2.circle(frame_show, (460, 20), 6, (0, 0, 255), -1) + + con_id = sum(1 for t in tracks if t.gid and t.time_since_update == 0) + cv2.putText(frame_show, f"CAM {cid} [{con_id} ID]", + (10, 28), FUENTE, 0.7, (255, 255, 255), 2) + tiles.append(frame_show) + + if len(tiles) == 6: + cv2.imshow("SmartSoft", np.vstack([ + np.hstack(tiles[0:3]), + np.hstack(tiles[3:6]) + ])) + + idx += 1 + if cv2.waitKey(1) == ord('q'): + break + + cv2.destroyAllWindows() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tarde.mp3 b/tarde.mp3 new file mode 100644 index 0000000..dc0f9a5 Binary files /dev/null and b/tarde.mp3 differ diff --git a/yolov8n.pt b/yolov8n.pt new file mode 100644 index 0000000..0db4ca4 Binary files /dev/null and b/yolov8n.pt differ