«Դեմքի ճանաչման օգտագործումը հավելվածը կառավարելու համար» հոդվածի շարունակությունը

Բոն, սա այս հոդվածի շարունակությունն է, որտեղ ես նկարագրեցի, թե ինչպես եմ ստեղծել հավելված, որը հետևում է իմ հայացքին: Հավելվածը բավականին լավ էր աշխատում, բայց ոչ: Օրենսգիրքը նայելուց և այն բազմիցս վերակառուցելու մասին մտածելուց հետո ես ուզում էի ստեղծել լուծում, որն ինքնին կսովորեր կամ մոտ ինչ-որ բան:

Հիմնական ML

Երբ ես ավելի շատ քննարկեցի մարտահրավերը, մտքովս անցավ, որ սա կարող է հիանալի նախագիծ լինել Core ML-ի համար: Ես կարող էի ստեղծել արժեքների մոդել տարբեր խառնուրդների ձևերի համար և օգտագործել այն որոշելու համար, թե որ կողմն էի փնտրում: Դա լավ հարմար էր մոդելավորման համար, քանի որ ես ունեի մի շարք հստակ արժեքներ և արդյունքներ, և նույնիսկ ավելի լավ, դա իրական աշխարհի խնդիր էր, որի շուրջ ես կարող էի կառուցել մեքենայական ուսուցման մոդել:

Կոդ

Իմ ճանապարհորդության ընթացքում առաջին փոփոխությունը, որ կատարեցի, իմ հավելվածի համար հեռակառավարման վահանակի ներդրումն էր: Ես վստահ չէի, որ դրա կարիքը կունենամ, բայց դա կարող է օգնել ինձ դիտորդի խնդրի հարցում: Դա անելու համար ես օգտագործեցի MultipeerConnectivity շրջանակը՝ այն իրականացնելով այս թղթում նկարագրված նույն սխեմայով։ Հանուն արդարության ձեզ համար, ընթերցող, այստեղ կա մի դյուրանցում ներբեռնելու բազմաստիճան կոդը: Խնդրում ենք պատճենել և տեղադրել այս կոդը իր սեփական Swift ֆայլում: Ես զանգահարեցի իմը MultiPeer:

Ձեր ներբեռնած կոդը կազմաձևված է որպես գովազդատու. դուք պետք է այն ներառեք հիմնական հավելվածում: Դա անելուց հետո ես ստեղծեցի երկրորդ հավելվածը, որը կոչվում է «հեռակառավարում», որը նաև պահանջում էր, որ կոդը չմեկնաբանի բրաուզերի տողերը և մեկնաբանի գովազդատուի տողերը init() մեթոդով հեռավոր կողմի համար: Այստեղ հեռակառավարիչը զննարկիչն է. հիմնական հավելվածը գովազդատուն է:

override init() {
...
    serviceBrowser.delegate = self
    serviceBrowser.startBrowsingForPeers()
}

Ես կառուցեցի երկրորդ հավելվածի էկրանը, որպեսզի պարունակի երկու կոճակ՝ դադարեցման կոճակ՝ իրերը դադարեցնելու համար և գնալ կոճակ՝ դրանք ազատելու համար: Հեռակառավարման վահանակի նպատակն է վերանայել հայացքի արժեքները սարքի էկրանին` չփոխելով նշված արժեքները, երբ ես նայում եմ դրան, դասական դիտորդի խնդիր:

Դուք կգտնեք իմ հեռակառավարման ContentView-ը՝ այս հիմնական հղումով: Օգտագործեք այս երկու աղբյուր ֆայլերը՝ ստեղծելու ձեր սեփական հեռահար հավելվածը և մի մոռացեք կատարել անհրաժեշտ փոփոխությունները տեղեկատվական հատկություններում:

Ինձ անհրաժեշտ էր ավելացնել ֆունկցիոնալությունը մյուս հավելվածին, այնպես որ ես ավելացրի օրինակ և հղումներ նշված օբյեկտին:

var connectSession = MultipeerSession.shared

Այնուհետև ես թարմացրի նստաշրջանի կոդը, որպեսզի փոխեմ դադարի փոփոխականը ճիշտ կամ կեղծ՝ կախված հղման միջոցով ուղարկված տվյալներից:

Նշում. փոփոխությունն արդեն առկա է ձեր ներբեռնած բազմակի կոդում:

Դուք պետք է մեկնաբանեք այս կոդը «հեռավոր» տարբերակում:

let foo = String(decoding: data, as: UTF8.self)
DispatchQueue.main.async {
    if foo == "free" {
        looked.paused = false
    } else {
        looked.paused = true
    }
}

Դիտորդների մարտահրավեր

Երկրորդը՝ անկասկած, ավելի կարևոր փոփոխությունը, որը ես արել եմ հավելվածում, խոսքի դասի ներառումն էր: Սա կարևոր էր, քանի որ ես պետք է շարունակեի էկրանից հեռու նայել՝ տարբեր հայացքները տեսնելու համար: Լավ, ես ստեղծել էի հեռակառավարման վահանակ՝ այն սառեցնելու համար, բայց եթե այն կարդա ուղղությունները, ևս մեկ լար կավելացներ իմ աղեղին: Ահա խոսքի կոդի աղբյուրը. այն կարող եք ներբեռնել այստեղ:

Ենթադրելով, որ դուք հայտարարել եք դասի օրինակ որպես talk, այն գործելու համար ձեզ անհրաժեշտ է ընդամենը մի տող, ինչպիսին է.

talk.speaker(words: ["Minority Report"])

Ստեղծեք ML և Core ML

Լավ, գնացեք հիմնական ճաշատեսակ: Նախորդ տարբերակի հիմնական մարտահրավերը իմ հայացքի մատնանշած բոլոր ութ վայրերը ճանաչելն էր: Ես նախկինում կոշտ կոդավորված էի հավելվածում թվացող ողջամիտ արժեքները, ուստի կոդը հետևյալն էր.

if eyesLookUp > 0.2 && eyesLookSide < 0.3 && eyesLookDown < 0.1 {
  sphereNode.simdPosition = SIMD3(x: 0, y: height + 1, z: -6)
  sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.red.withAlphaComponent(1)
}

Բայց դա չաշխատեց այնպես, ինչպես ես ակնկալում էի: Ես կարող էի ստիպել, որ անկյունները լավ աշխատեն, բայց պայքարում էի կողմերի հետ և հակառակը: Ես ստեղծեցի մի պարզ աղյուսակ՝ տարբեր հայացքների արժեքները ուրվագծելու համար:

Դա կատարյալ համընկնում էր մոդելավորման համար, քանի որ յուրաքանչյուր ճանապարհ ուներ վերջնական արժեքներ: Այնուհետև ես սա արտահանեցի որպես «CSV» ֆայլ և գործարկեցի Ստեղծել ML գործիքը Xcode-ի տակ:

Նշում. ես պետք է խմբագրեի «CSV» ֆայլը, նախքան այն ներմուծելը, քանի որ թվերն իմ ֆայլում ստորակետների շարք ավելացրին:

Դա անելու համար ես օգտագործեցի այս գերազանց տեսանյութը, որպեսզի ինքս ինձ առաջնորդեմ մոդելային ֆայլ ստեղծելու համար անհրաժեշտ առաջադրանքները: Եթե ​​ժամանակ չունեք դա դիտելու, ահա արագ ակնարկ:

Ինձ անհրաժեշտ էր ընտրել մեկ թիրախային փոփոխական՝ ուղղությունը: Իմ աղյուսակի մյուս տասը փոփոխականները/սյունակները անհրաժեշտ էին արժեքը որոշելու համար: Երբ ես ստեղծեցի մոդելը, ես օգտագործեցի այս երկրորդ տեսանյութը, որպեսզի հիշեցնեմ, թե ինչպես այն ներառել իմ նախագծում: Ինձ համար վերջնական կոդը այսպիսի տեսք ուներ.

Նշում. Ես սահմանել եմ eyeLook փոփոխականները՝ օգտագործելով ARFaceAnchor արժեքները:

let eyeLookIn_L = faceAnchor?.blendShapes[.eyeLookInLeft]?.doubleValue
let eyeLookIn_R = faceAnchor?.blendShapes[.eyeLookInRight]?.doubleValue
let eyeLookOut_R = faceAnchor?.blendShapes[.eyeLookOutRight]?.doubleValue
let eyeLookOut_L = faceAnchor?.blendShapes[.eyeLookOutLeft]?.doubleValue
let eyeLookDown_L = faceAnchor?.blendShapes[.eyeLookDownLeft]?.doubleValue
let eyeLookUp_L = faceAnchor?.blendShapes[.eyeLookUpLeft]?.doubleValue
let eyeLookDown_R = faceAnchor?.blendShapes[.eyeLookDownRight]?.doubleValue
let eyeLookUp_R = faceAnchor?.blendShapes[.eyeLookUpRight]?.doubleValue

Այժմ, օգտագործելով այս պարզ մոդելը, ես կարող էի տարբերել բոլոր ուղղությունները, բացառությամբ առանց ուղղության հայացքի, մի անտեսում, որը ես ի սկզբանե ֆիքսել եմ կոդերի այս տողով.

if eyesLookLeft < 0.1 && eyesLookRight < 0.1 && eyesLookDown < 0.1 && eyesLookUp < 0.1 {
    sphereNode.simdPosition = SIMD3(x: 0, y: height, z: -6)
    sphereNode.geometry?.firstMaterial?.diffuse.contents = UIColor.green.withAlphaComponent(0.8)
    looked.gazeX = ""
    return
}

Մոդելի կանխատեսումը գործադրելու համար ես միավորեցի այս անջատիչի հայտարարությունը «եթե» կետի հետ՝ օգնելու ինձ պահել իմ գլուխը տեսախցիկի նկատմամբ:

Ես նորից փորձարկեցի այն անիմացիոն GIF-ով, որն այստեղ ցույց է տալիս արդյունքը.

Ես գոհ էի նոր կառուցվածքից; այն ուներ ավելի նրբագեղություն և, ի տարբերություն կոշտ կոդավորված տարբերակի, հեշտ էր կսմթել: Կարգավորումների թեմայի վերաբերյալ ես փորձեցի մոդելի յոթ տարբեր տարբերակներ [փոխելով շեմային արժեքները], նախքան հայտնվեցի մեկը, թեև պետք է խոստովանեմ, որ պետք էր շատ անշարժ մնալ բոլոր ութ դիրքերը կազմելու համար: Ես փորձեցի նաև մի քանի այլ ալգորիթմներ. նրանք ոչ մի տարբերություն չեն ունեցել:

Հաջորդ շարժումները

Մտքիս վերջում ես ուզում էի գրանցել իրական արժեքները, պահել դրանք CSV-ում և օգտագործել դրանք մոդելը վերակառուցելու համար: Ես պետք է ավելի լավ հասկանայի, թե ինչպես է գլխիս կողմնորոշումը փոխում հայացքի արժեքները: Դա հասկանալը թույլ կտա ինձ այն ներդնել մոդելի մեջ, ինչը տեսականորեն կարող է նշանակել, որ ես կարող եմ մի փոքր ավելի շարժել գլուխս:

Ես ստեղծեցի մեթոդ՝ զանգվածում տեսած բոլոր արժեքները պահելու համար: Ահա դրա կոդը.

func logEyes(look:String) {
    let eyeLookIn_L = faceAnchor?.blendShapes[.eyeLookInLeft]?.doubleValue
    let eyeLookIn_R = faceAnchor?.blendShapes[.eyeLookInRight]?.doubleValue
    let eyeLookOut_R = faceAnchor?.blendShapes[.eyeLookOutRight]?.doubleValue
    let eyeLookOut_L = faceAnchor?.blendShapes[.eyeLookOutLeft]?.doubleValue
    
    let eyeLookDown_L = faceAnchor?.blendShapes[.eyeLookDownLeft]?.doubleValue
    let eyeLookUp_L = faceAnchor?.blendShapes[.eyeLookUpLeft]?.doubleValue
    let eyeLookDown_R = faceAnchor?.blendShapes[.eyeLookDownRight]?.doubleValue
    let eyeLookUp_R = faceAnchor?.blendShapes[.eyeLookUpRight]?.doubleValue
    
    let angleX = rad2deg(Double(self.cubeNode.eulerAngles.x))
    let angleY = rad2deg(Double(self.cubeNode.eulerAngles.y))
    
    let rec = Regard(direct: look, eyeLookIn_L: eyeLookIn_L, eyeLookOut_R: eyeLookOut_R, eyeLookIn_R: eyeLookIn_R, eyeLookOut_L: eyeLookOut_L, eyeLookDown_L: eyeLookDown_L, eyeLookDown_R: eyeLookDown_R, eyeLookUp_L: eyeLookUp_L, eyeLookUp_R: eyeLookUp_R, angleX: angleX, angleY: angleY)
    log.append(rec)
}

Այնուհետև ես հավաքեցի կոդը, որն անհրաժեշտ էր ինձ տարբեր հայացքների համար մի քանի նվազագույն և առավելագույն արժեքներ տալու համար: Դա կարծես սա է.

if eyesTrue {
  let foo = log.filter({$0.direct == "eyesLeft"})
  let foo2 = foo.filter({$0.eyeLookIn_L > 0})
  let minValue = foo2.min { $0.eyeLookIn_L < $1.eyeLookIn_L }?.eyeLookIn_L
  let maxValue = foo2.max { $0.eyeLookIn_L < $1.eyeLookIn_L }?.eyeLookIn_L
  let sortedValues = foo2.sorted(by: { $0.eyeLookIn_L > $1.eyeLookIn_L} )
  
  for foos in foo2 {
      print("\(foos.eyeLookIn_L!)")
  }
  print("lookLeft \(minValue) \(maxValue)")
}

Ես փորձեցի գործարկել նշված կոդը իմ հեռակառավարման վահանակով, բայց որոշեցի, որ ավելի հեշտ կլինի օգտագործել այլ ժեստ՝ այն գործարկելու համար: Ահա դա անելու կոդը.

let eyesTrue = (faceAnchor?.blendShapes[.browOuterUpLeft]!.doubleValue)! > 0.1

Ես չափեցի արժեքները, գործարկեցի հավելվածը և հավաքեցի նմուշներ: Ահա այն արժեքները, որոնք ես վերադարձա.

lookUp min 0.010353747755289078 max 0.5442964434623718 eyeLookUp_L
lookLeftUp min 0.10131984949111938 max 0.42720866203308105) eyeLookUp_L
lookLeft min 0.054532475769519806 max 0.6156175136566162 eyeLookOut_R
lookLeft min 0.4086191952228546) max 0.7494118809700012 eyeLookIn_L

Ես փոխեցի մոդելի արժեքները ձախ և աջ հայացքների համար և ավելացրի eyescenter արժեքներ մոդելին: Ավելի վաղ նշված խնդիրը շտկելուց հետո նման տեսք ուներ.

Եվ դա բավականին լավ աշխատեց: Կարծում եմ, որ iPhone-ի ետևում կաղապար էր պետք՝ աչքերս ճիշտ տեղում կենտրոնացնելու համար: Դրանից ավելին անցնելու համար ինձ պետք էր տեսախցիկի վրա տեղադրված սաղավարտ, ինչպես այն, ինչ նրանք օգտագործում են Ավատար 2-ի հատուկ էֆեկտների համար: Եթե ցանկանում եք փորձել այն, ահա աղբյուրային կոդը: