Taekwondo CV
Հարվածների հայտնաբերում և միավորների հաշվարկ իրական ժամանակում՝ ռինգի չորս տեսախցիկով
Համակարգչային տեսողության համակարգ, որը միաժամանակ չորս տեսախցիկով հետևում է թեքվոնդոյի մենամարտին, դասակարգում է, թե յուրաքանչյուր մարզիկ ինչ արեց, դա վերածում է միավորների և մրցավարին ցույց տալիս ընթացիկ հաշիվը։ ML մասն արել եմ զրոյից՝ պիտակավորման սխեման և տվյալների բազան, հայտնաբերման կասկադը, երկմակարդակ դեդուպլիկացիան, որը որոշում է՝ ինչն է մեկ հարված, և GPU-ի վրա իրական ժամանակի ինֆերենսը։ Ստորև՝ ինչ եմ փորձել, ինչից եմ հրաժարվել, ինչ շեմերով է ամեն ինչ իրականում աշխատում և ինչու են դրանք ասիմետրիկ։
- ԴերըML/CV-ն միայնակ, նաև ճարտարապետություն և բեքենդ
- ՄուտքըՉորս կենդանի RTSP հոսք, ինֆերենսից առաջ՝ 1280x720
- ՀիմքըԵրկաստիճան YOLO կասկադ, հարվածների 20 դաս
- ԵլքըՄիավորված իրադարձություններ, պիտակավորված RTSP, հաշիվը WebSocket-ով
Սահմանափակումը, որից աճել է մնացած ամեն ինչ
Համակարգը պետք է պատասխաներ մրցավարի հարցին, ոչ թե հայտնաբերիչի հարցին՝ ով, ինչ տեխնիկայով, որ մակարդակում և քանի միավոր։ Եվ պատասխաներ մենամարտի ընթացքում, ոչ թե հետո, չորս հոսքով միաժամանակ, մեկ մեքենայի վրա։
Ոլորտի երկու հատկություն որոշեց հետագա բոլոր քայլերը։ Առաջինը՝ սխալների գինն ասիմետրիկ է. բաց թողնված հարվածը վեճ է ռաունդի վերջում, հորինված հարվածը՝ սխալ հաշիվ տախտակին։ Երկրորդը՝ մեկ հարվածը մեկ իրադարձություն է, բայց հայտնաբերիչն աշխատում է ամեն կադրի վրա, և երեսուն կադր տեսանելի հարվածը չպետք է դառնա երեսուն իրադարձություն։

Փայփլայնն ամբողջությամբ
- Գրավում. մեկ RTSP հոսք յուրաքանչյուր տեսախցիկի համար, ապակոդավորումն իր հոսքում, ինֆերենսից առաջ չափափոխում 1280x720։
- Առաջին փուլ. ամբողջ կադրի հայտնաբերիչը գտնում է մարզիկներին և վերադարձնում նրանց շրջանակները։
- Կտրվածք. յուրաքանչյուր շրջանակ ընդլայնվում է 20 px-ով բոլոր կողմերից և սահմանափակվում կադրով։
- Երկրորդ փուլ. 20 դասի դասակարգիչն աշխատում է այդ կտրվածքի վրա 640 px-ով և վերադարձնում տեխնիկան, մակարդակը և մարզիկի գույնը։
- Համապատասխանեցում. դասը վերածվում է իրադարձության կոդի, իսկ միավորները վերցվում են բազայից քեշավորված աղյուսակից, ոչ թե ամրակցված են մոդելում։
- Դեդուպլիկացիա. ինֆերենսի կողմում՝ cooldown ըստ դասի, ապա իրադարձությունների կողմում՝ peak window։
- Պահպանում. իրադարձությունը, նրա վստահությունը և սքրինշոթը օբյեկտային պահեստում. պիտակավորված կադրը վերադառնում է RTSP-ով, հաշիվը՝ WebSocket-ով։
Առաջին ճյուղ. մեկ սեգմենտացիայի մոդել
Առաջին աշխատող տարբերակը մեկ սեգմենտացիայի մոդել էր ամբողջ կադրի վրա՝ որակյալ մասկերով և ռենդերերով, որը դրանք հարթեցնում էր ժամանակի մեջ՝ էքսպոնենցիալ խառնում նախորդ մասկի հետ, եզրերի փափկեցում և մարում, երբ հայտնաբերումն անհետանում էր, որպեսզի օվերլեյը չթարթի։
- Օվերլեյն իսկապես լավ էր երևում, ինչը կարևոր է, երբ պատկերը գնում է հեռարձակման։
- Մասկը հետևում է մարմնին, ուստի հակառակորդի ֆոնին վերջույթը մնում է ընթեռնելի։
- Մեկ մոդել, մեկ շեմերի հավաքածու, համաձայնեցնելու բան չկա։
- Մասկերն արժենում են ժամանակ ամեն կադրի վրա և ոչինչ չեն տալիս որոշմանը, որն ըստ էության դաս է, ոչ թե ուրվագիծ։
- Ռինգի հեռավոր եզրին ձեռքի և ոտքի հարվածը տեսողականորեն միանում են, երբ մարզիկը զբաղեցնում է մի քանի հարյուր պիքսել, իսկ մեկ մոդելը պետք է միաժամանակ տեղորոշեր և դասակարգեր։ Վատացավ հենց դասակարգումը։
- Հարթեցումը, որը օվերլեյը հաճելի էր դարձնում, միաժամանակ ուշացնում էր այն պահը, երբ հայտնաբերումը համարվում է անհետացած։
Որոշում Մնացել է կոդում որպես մեկ մոդելի legacy ռեժիմ՝ դրոշի տակ. դա դեռ ամենաարագ ձևն է նոր տվյալների բազան տեսողականորեն վրիպազերծելու։ Բայց ոչ արտադրական ուղին։
Երկրորդ ճյուղ. կեցվածքի գնահատում
Վարկածն այն էր, որ հոդերի երկրաչափությունը տեխնիկաներն ավելի լավ է բաժանում, քան արտաքին տեսքը. գլխին և իրանին ոտքի հարվածները տարբերվում են նրանով, թե ոտնաթաթն ուր է հայտնվում կոնքի և ուսերի նկատմամբ։ Սովորեցրել եմ pose-մոդել՝ 35 keypoint-ի իմ կմախքով և երկու դասով՝ կարմիր և կապույտ, սեփական տվյալների բազայի վրա, և արտահանել եմ OpenVINO։
- Keypoint-երը կոմպակտ են և մեկնաբանելի, իսկ դրանց վրա կառուցված կանոնը հեշտ է բացատրել մարզչին։
- Կեցվածքը շատ ավելի քիչ է կախված համազգեստի գույնից և դահլիճի լուսավորությունից, քան հում պատկերը։
- Նույն հարվածի կատարման տարբերակները չափազանց շատ են։ Դասի ներսի ցրվածությունը կուլ տվեց դասերի միջև տարբերությունը։
- Կլինչում երկու մարզիկի keypoint-երը խառնվում են, և կմախքը նստում է սխալ մարմնի վրա։
- Սա շղթայում ավելորդ մոդել է, որի սխալներն ավելի վատ են երևում, քան վատ շրջանակը։
Որոշում Հրաժարվեցի։ Վերլուծության մեջ այն դիտավորյալ է. արտադրություն սովորաբար գնում է այն ճարտարապետությունը, որը վերապրել է մի քանի նման ստուգում։
Երրորդ ճյուղ. կասկադը, որը գնաց արտադրություն
Տեղորոշման և դասակարգման բաժանումը բուժեց հեռավոր պլանի շփոթը։ Առաջին փուլին պետք է միայն գտնել մարդկանց, ինչը խնդրի հեշտ կեսն է և կայուն է մասշտաբի նկատմամբ։ Երկրորդ փուլը տեսնում է կտրվածք, որտեղ մարզիկը զբաղեցնում է ամբողջ մուտքը, իսկ դա հենց այն պայմանն է, որի դեպքում հարվածի դասը բաժանելի է։
- Յուրաքանչյուր փուլ ունի իր շեմը, և դրանք կարելի է պտտել հակառակ ուղղություններով՝ այդ մասին ստորև։
- Բարդ խնդիրը փոքրանում է. դասակարգումն աշխատում է նորմալացված կտրվածքի, ոչ թե 1280 px կադրի վրա։
- Առաջին փուլի շրջանակներն օգտակար են ինքնին, ուստի օվերլեյը միշտ ցույց է տալիս մարզիկներին, նույնիսկ երբ ոչ մի հարված չի հաշվվում։
- Արժեքը դադարում է հաստատուն լինել մեկ կադրի համար. երկրորդ փուլը գործարկվում է ամեն գտնված մարզիկի վրա, ուստի խիտ կադրն ավելի դանդաղ է։
- Առաջին փուլի բացթողումն անուղղելի է. երկրորդը երբեք չի տեսնի այն, ինչ չի կտրվել։
- Երկու մոդելը երկու կշիռների հավաքածու է, երկու արտահանում և երկու բան՝ վերսիավորելու։
Որոշում Սա արտադրական ուղին է։

Շեմերը և ինչու են դրանք նայում տարբեր կողմեր
Փուլերը դիտավորյալ կարգավորված են միմյանց դեմ։ Առաջինն աշխատում է մեղմ, քանի որ չգտնված մարզիկն արժենում է ամբողջ իրադարձությունը, և երկրորդ հնարավորություն չկա։ Երկրորդն աշխատում է խիստ, քանի որ նրա հորինած իրադարձությունն ընկնում է տախտակին, իսկ դա այս ոլորտում թանկ սխալ է։ Ըստ էության առաջին փուլը հավաքում է լիարժեքությունը, իսկ երկրորդը ծախսում է այն ճշգրտության վրա։
Սրանք լռելյայն արժեքներն են, որոնցով ծառայությունը գնում է։ Ամեն դահլիճի և տեսախցիկների դասավորության համար դրանք վերաստուգվում են գրառումների վրա մրցաշարից առաջ, ուստի յուրաքանչյուրը միջավայրի փոփոխական է, ոչ թե ամրակցված հաստատուն։
- STAGE1_CONF
- 0.20
- Մեղմ՝ դիտավորյալ. չգտնված մարզիկը կորցրած իրադարձություն է։
- STAGE2_CONF
- 0.68
- Խիստ. կեղծ դասը դառնում է սխալ հաշիվ, ուստի թույլ վկայությունները մերժվում են։
- IOU_THRESHOLD
- 0.45
- NMS-ի համընկնումը, ընդհանուր երկու փուլի համար։
- BBOX_PADDING_PX
- 20
- Կտրվածքի պաշար, որ շրջանակից դուրս եկող վերջույթը դարձյալ ներս ընկնի։
- 2-րդ փուլի imgsz
- 640
- Կտրվածքն արդեն խիտ է, ավելի մեծ մուտքը տալիս է միայն ուշացում։
- RESIZE
- 1280x720
- Կիրառվում է ինֆերենսից առաջ, սա թողունակության ամենաուժեղ լծակն է։
- cooldown
- 0.7 վրկ
- Կրկնությունների ճնշում կադրերի մակարդակում, բանալին ըստ դասի։
- peak window
- 2.0 վրկ
- Պատուհան իրադարձությունների մակարդակում՝ ըստ մարզիկի, տեսախցիկի և ռաունդի։
- Առաջին փուլի ցածր շեմը բազմապատկում է երկրորդի կանչերը, ուստի ուշացումն աճում է շրջանակների թվով, ոչ թե մնում հաստատուն մեկ կադրի համար։
- Երկրորդ փուլի խիստ շեմը լուռ դեն է նետում իրական, բայց մասամբ փակված հարվածները։ Սա գիտակցված փոխզիջում է, ոչ թե անվճար շահում։

Դեդուպլիկացիա երկու մակարդակում
Առաջին մակարդակն ապրում է ինֆերենսում. cooldown ըստ հարվածի դասի, որպեսզի նույն մարզիկի նույն տեխնիկան պատուհանի ներսում երկու անգամ չաշխատի։ Դասն արդեն պարունակում է գույնը, ուստի կարմիրն ու կապույտը երբեք չեն ճնշում միմյանց։
Երկրորդ մակարդակն ապրում է իրադարձությունների շերտում, և դա ճնշում չէ, այլ արբիտրաժ։ Մարզիկի, տեսախցիկի և ռաունդի պատուհանի ներսում համակարգը թողնում է ամենաարժեքավոր հարվածը և հեռացնում արդեն գրանցված ավելի թույլը. իրանին ոտքի հարվածը, ապա գլխին, ապա դարձյալ իրանին լուծվում է հօգուտ գլխին հարվածի։ Սա կարևոր է, քանի որ մոդելը հաճախ տեսնում է նույն գործողության նախապատրաստումն ու գագաթը որպես երկու հայտնաբերում, իսկ մրցավարը պետք է տեսնի գագաթը։
- cooldown-ի բանալի
- հարվածի դաս
- Դասը կրում է մարզիկի գույնը, ուստի երկուսն էլ հաշվվում են անկախ։
- peak window-ի բանալի
- ռաունդ + մարզիկ + տեսախցիկ
- Արբիտրաժը գնում է ըստ մարզիկի, ոչ թե ըստ կադրի։
- պատուհանի կանոն
- հաղթում է ավելի թանկը
- Ավելի արժեքավոր հարվածը փոխարինում է արդեն պահված իրադարձությանը։
- Իսկապես արագ երկրորդ հարվածը պատուհանի ներսում կլանվում է առաջինի կողմից։ Պատուհանի երկարությունը հենց այն բռնակն է, որը փոխանակում է կրկնօրինակները բաց թողնված շարունակությունների հետ, և այն կոնֆիգում է, ոչ թե հաստատունում։
- Արբիտրաժը գնում է ըստ տեսախցիկի, ուստի երկու տեսախցիկով նկարված մեկ հարվածը դեռ տալիս է երկու իրադարձություն։ Խաչաձև ֆուզիան ակնհայտ հաջորդ աշխատանքն է։

Սխալ, որն արժե պահել վերլուծության մեջ
Իրադարձությունների սքրինշոթներն ունեին իրենց cooldown-ը՝ բանալիով ըստ տեսախցիկի և իրադարձության կոդի։ Երբ կարմիրն ու կապույտը վայրկյանի ընթացքում կատարում էին նույն տիպի հարվածը, սքրինշոթն արվում էր միայն մեկի համար, իսկ երկրորդ իրադարձությունը պահվում էր դատարկ URL-ով։ Իրադարձությունները ստեղծվում են ըստ մարզիկի, իսկ սքրինշոթի բանալին դա հաշվի չէր առնում։ Բանալուն մարզիկին ավելացնելը լուծեց խնդիրը։
Ընդհանուր եզրակացությունն այստեղ ավելի թանկ է, քան ուղղումը. դեդուպլիկացիայի ցանկացած բանալի պարտավոր է համընկնել այն էության հատիկայնությանը, որը պաշտպանում է, և ամենահեշտ ձևը սխալվելու՝ բանալին պատճենելն է հարևան կոդից։
Էժան ճշգրտություն. ռինգի բազմանկյունը
Դահլիճի ամեն մարդ մենամարտի մասնակից չէ։ Հանդիսատեսը, մարզիչները և ֆոնին տաքացող մարզիկները տալիս են միանգամայն վավեր հայտնաբերումներ։ Համակարգը յուրաքանչյուր տեսախցիկի համար բեռնում է ռինգի բազմանկյունը և անտեսում դրանից դուրս ամեն ինչ, իսկ բազմանկյունը կառուցվում է փոքր ինտերակտիվ գործիքով. սեղմում ես անկյունների վրա կենդանի կադրում, այն պահում է JSON։
- Հեռացրեց կեղծ աշխատանքների մեծ դաս՝ գրեթե առանց հաշվողական ծախսի։
- Սա կանոն է, ոչ թե մոդել. բացատրելի է, ուղղվում է ակնթարթորեն, վերասովորեցում պետք չէ։
- Ձեռքով կարգավորում ամեն տեսախցիկի համար, և այն լուռ խափանվում է, եթե տեսախցիկը սեսիաների միջև տեղաշարժել են։
- Գորգի հենց եզրին գտնվող մարզիկը կարող է դուրս մնալ չափազանց խիտ բազմանկյունից։
Ինչպես է չափվել արագությունը
Ուշացման մասին հայտարարությունները ոչինչ չարժեն առանց մեթոդի, ուստի գրել եմ harness, որը թեկնածու մոդելները գործարկում է նույն մենամարտի գրառման վրա և տպում ժամանակները ըստ փուլերի, ոչ թե մեկ թիվ։ Այն դեն է նետում տաքացման կադրերը, համաժամացնում GPU-ն ամեն չափման շուրջ, որպեսզի չափի աշխատանքը, ոչ թե հերթը, և տպում միջինը, մեդիանը, p95-ը և p99-ը յուրաքանչյուր փուլի համար՝ գումարած փայփլայնի FPS-ը և հարաբերությունը սկզբնաղբյուրի կադրերի հաճախությանը, այսինքն՝ պատասխանը միակ կարևոր հարցին. հասցնո՞ւմ է, թե ոչ։
- Նույն տեսանյութը, նույն շեմերը, մեկ անգամում փոխվում է մեկ մոդել։
- Տաքացման կադրերը բացառված են, GPU-ն համաժամացվում է ամեն չափման շուրջ։
- Ամեն փուլի համար avg, p50, p95 և p99. կադրերը գցում է ուշացման պոչը, ոչ թե միջինը։
- Փայփլայնի FPS-ն ընդդեմ աղբյուրի FPS-ի, տպվում է որպես իրական ժամանակի գործակից։
- Պիտակավորված տեսանյութը նույնպես գրվում է, ուստի արագության և որակի հետընթացը երևում են նույն արտեֆակտում։
Որոշում Բացարձակ թվերը պատկանում են այն վիդեոքարտին, որի վրա չափվել են, ուստի նախագծից նախագիծ ես տանում եմ harness-ը, ոչ թե թվերը։
Ինֆերենս և շահագործում
- TensorRT engine-ը կառուցվում է կշիռներից առաջին գործարկման ժամանակ և քեշավորվում կոնկրետ GPU-ի համար, ուստի մեքենայի փոփոխությունը բերում է վերակառուցման, ոչ թե անհամատեղելի engine-ի վրա լուռ աշխատանքի։
- Կիսատ ճշտություն լռելյայն, engine-ի մուտքը 1280 px, ավտոմատ fallback սովորական կշիռների վրա, եթե GPU-ն կամ TensorRT-ն հասանելի չեն։
- CPU կամ GPU ընտրվում է մեկ միջավայրի փոփոխականով, ուստի նույն compose-ն աշխատում է և նոութբուքի, և դահլիճի սերվերի վրա։
- Չորս տեսախցիկի աշխատող զուգահեռ, յուրաքանչյուրն իր գրավման և ինֆերենսի ցիկլով։
- Պիտակավորված տեսանյութը հրապարակվում է հետ՝ RTSP-ով, հաշիվը և մենամարտի կառավարումը՝ WebSocket-ով։
- Իրադարձությունների սքրինշոթները՝ օբյեկտային պահեստ, մետրիկաները՝ Prometheus և Grafana։
Տվյալներ, պիտակավորում և բաժանում
Տվյալների բազան շատ մենամարտերից հավաքված կադրեր են, ոչ թե մի քանիսի հաջորդական կադրերը։ Պիտակավորման սխեման դասից բացի կրում է հատկանիշներ. մարզիկի գույնը, շփման կադրի նշանը, տեսախցիկը և որակի դրոշը երեք արժեքով՝ clear, partial, doubtful։
Որակի դրոշն այն է, ինչ նորից առաջինը կանեի։ Այն թույլ է տալիս վիճելի կադրերին մնալ բազայում որպես տվյալ վիճելիության մասին, փոխանակ կամ թունավորեն ուսուցման ընտրանքը, կամ լուռ ջնջվեն։
Բաժանումն ըստ մենամարտերի է և երբեք ըստ կադրերի։ Մեկ մենամարտի կադրերը կիսում են լույսը, գորգը, մարզիկներին և ռակուրսը, ուստի կադրային բաժանումը թեստը քաշում է ուսուցման մեջ և վերադարձնում մետրիկաներ, որոնք հիանալի են երևում և ոչինչ չեն կանխատեսում։
Մրցավարը ցիկլի ներսում
Ամեն ավտոմատ իրադարձություն պահվում է իր վստահության հետ։ Մրցավարը կարող է ավելացնել բաց թողնված իրադարձությունը և հեռացնել կեղծը, և այս երկու գործողությունը պահվում են բացահայտ դրոշներով՝ փափուկ ջնջմամբ, ոչ թե լուռ վերագրանցմամբ։
Ուստի նույն աղյուսակը և՛ մենամարտի արձանագրությունն է, և՛ մոդելի false negative-ների ու false positive-ների պիտակավորված պատմությունը՝ դրանք ծնած վստահության հետ միասին։ Սա այն մասն է, որը տեղափոխվում է մարդ ունեցող ցանկացած ցիկլի վրա. ուղղումները դառնում են ուսուցման տվյալ միայն այն դեպքում, երբ պահվել են կառուցվածքային ձևով հենց կատարման պահին։
Ազնիվ սահմանափակումներ
- Անձանց հետագծում դեռ չկա։ Մարզիկի ինքնությունը վերցվում է դասից, իսկ պահեստային էվրիստիկան կադրը կիսում է երկու մասի, ինչը փխրուն է հենց այն վայրկյանին, երբ մարզիկները փոխել են կողմերը։ Հետագծումն առաջին բանն է, որ կավելացնեի։
- Խաչաձև ֆուզիա չկա. մեկ հարված տեսած չորս տեսախցիկը տալիս է չորս իրադարձություն, և դրանք զսպում է միայն ամեն տեսախցիկի ներսի peak window-ը։
- Շեմերը ձեռքով վերաստուգվում են ամեն դահլիճի համար, փոքր պիտակավորված ընտրանքով ավտոմատ չափորոշում չկա։
- Տվյալների բազան փոքր է CV-ի չափանիշներով, ուստի հազվադեպ տեխնիկաների մետրիկաներն ունեն լայն վստահության միջակայքեր, և ես դրանք այդպես էլ ներկայացնում եմ։

Ինչ եմ վերցնում նախագծից
Հետաքրքիր ինժեներիան մոդելը չէր։ Հետաքրքիրն էր իրադարձության սահմանումը, դեդուպլիկացիայի բանալիները, ասիմետրիկ շեմերը, ըստ մենամարտերի բաժանումը, չափումների harness-ը և ամեն ուղղում պահելու որոշումը։ Հայտնաբերիչը փոխարինելի է, իսկ հենց այս շրջանակն է, որ հաջորդ հայտնաբերիչը չափելիորեն ավելի լավն է դարձնում նախորդից։
Տեսանյութ
Համակարգի աշխատանքի տեսահոլովակն այստեղ կավելացվի։
Տեխնոլոգիաներ
- Python
- PyTorch
- YOLO / Ultralytics
- OpenCV
- TensorRT
- OpenVINO
- FastAPI
- PostgreSQL
- Redis
- Celery
- WebSocket
- MediaMTX / RTSP
- MinIO
- Docker
- Prometheus
- Grafana