Featured image of post Curriculum Vitae in Realtà Aumentata

Curriculum Vitae in Realtà Aumentata

Costruiamo un CV in Realtà Aumentata da sovrapporre al classico CV cartaceo

Come ho creato il mio CV in Realtà Aumentata, partendo dal mio CV Reale.


Introduzione

Le parole Curriculum Vitae (acronimo CV) derivano dal latino currĕre, ovvero “correre” e vitae ovvero “vita” e significano “Corso della vita”. La lingua tedesca chiama il CV Lebenslauf parola composta da Leben “vita” e Lauf “corsa”, enfatizzando l’idea della corsa e della successione di eventi. La lingua tedesca ci offre in questo senso un’immagine ancora più chiara: “Lebenslauf”, il curriculum, è una parola composta da Leben «vita», e Lauf «corsa».

Il curriculum così come lo conosciamo, si diffonde come documento codificato nel secondo dopoguerra, anche se non mancano esempi celebri risalenti a epoche antecedenti.

Il primo curriculum di cui si ha storia è stato quello di Leonardo Da Vinci, in particolare la lettera che Leonardo da Vinci mandò a Ludovico il Moro per convincerlo della bontà dei suoi servigi.

1482 lettera di Leonardo da Vinci a Ludovico Sforza; Un curriculum che delinea le sue capacità in scienza, ingegneria e arte.

Nella lettera di Da Vinci le competenze vengono elencate in maniera chiara, ad esempio:

  1. Sono in grado di creare ponti, robusti ma maneggevoli, sia per attaccare i nemici che per sfuggirgli.
  2. In caso di assedio, so come eliminare l’acqua dei fossati e so creare macchine d’assedio adatte a questo scopo.
  3. Sono in grado di ideare e creare, in modo poco rumoroso, percorsi sotterranei.

La lettera originale e una trascrizione in lingua moderna è disponibile a questo link.

Il CV di Leonardo Da Vinci è contestualizzato e punta all’obiettivo facendo leva sui bisogni di chi dovrebbe ingaggiarlo, oggi si assumono persone senza neanche capire bene quali competenze servano e di conseguenza questo porta a CV sempre più ricchi di conoscenze superficiali.

Oggi il CV deve essere qualcosa che si impone tra una massa di curriculum per cui l’aspetto concreto e creativo diventano fondamentali.

Idea

Trasformare il CV in un’applicazione in Realtà Aumentata che aumenti le informazioni del Curriculum Vitae / Resume in uno strumento di approfondimento interattivo e dinamico. Per fare questo l’idea è di creare un Avatar 3D (o 2D) della propria persona che racconta e spiega le varie parti del CV ad un recruiter o a chiunque voglia approfondire i temi del tuo CV.

Ci sono varie possibilità per creare questo tipo di applicazione:

  1. Usare il CV cartaceo come base di riferimento per il tracking e poi animarlo con elementi virtuali 2D.

Esempio 1. di CV in 2D

  1. Usare il CV cartaceo come riferimento per il tracking e poi creare un ambiente virtuale indipendente.

Esempio 2. di CV ricostruito in AR

  1. Usare il CV cartaceo come base di riferimento per il tracking e poi animarlo con elementi virtuali 3D.

Esempio 3. di CV in 3D

Nei prossimi paragrafi vedremo esattamente come creare il terzo modello proposto, ovvero utilizzare il CV cartaceo o digitale come base per degli elementi 3D virtuali.

Creiamo il nostro CV cartaceo in realtà aumentata

Partendo da un progetto base 3D Unity ci serviranno:

  • Android Build Support per Unity;
  • Licenza Vuforia Free;
  • Ready Player Me Avatar

Per questo progetto è richiesta una minima esperienza con Unity, anche se la guida sarà semplificata al massimo e step-by-step.

Creare un Avatar 3D

Per creare un Avatar che somigli il più possibile alla nostra persona esistono diversi provider che offrono tali funzionalità gratuitamente.

Un Asset che permette di creare avatar 3D estremamente realistici è Avatar Maker Free - 3D avatar from a single selfie, da cui dandogli in input un singolo selfie o scattando una foto via webcam, crea un avatar quasi realistico.

Avatar Maker Free

Un altro tool per creare avatar 3D è (Ready Player Me)[https://readyplayer.me/it], un servizio gratuito per creare online avatar a partire da una singola foto e personalizzabili in un secondo modello.

Ready Player Me Avatar

Per comodità useremo in questo progetto l’avatar 3D creato da Ready Player Me, modificabile direttamente da interfaccia browser sul loro sito ufficiale, inoltre è consigliabile registrarsi come sviluppatori sul loro sito https://readyplayer.me/it/developers.

Dopo aver creato l’avatar 3D salviamo il link che ci servirà per importarlo nel progetto Unity.

Creare il progetto Unity con Vuforia

La libreria per la realtà aumentata scelta per questo progetto è Vuforia (se ti interessa approfondire Vuforia puoi leggere alcuni miei articoli qui).

Prima di procedere è necessario creare una licenza free di Vuforia, https://library.vuforia.com/faqs/pricing-and-licensing-options.

A questo punto, è possibile creare il progetto Unity e importare le librerie di Vuforia come segue:

  1. Scaricare il package Vuforia Engine dall’Asset Store;
  2. Importarlo nel progetto Unity da Windows -> Asset;
  3. Assicurarsi che il progetto sia settato su Build->Android.
  4. Aggiungere XR Plug-in Management al progetto assicurandosi che la dicitura Initialize XR on Startup sia spuntata (come da immagine sottostante).

Configurazione XR Plug-in Management

Configurare Vuforia per l’Image Tracking

Dal sito di Vufuria assicurarsi di avere attiva una licenza attiva da importare nel progetto, per crearla basta collegarsi al sito https://developer.vuforia.com/vui/develop/ e creare una licenza free cliccando Get Basics

Cliccando sulal licenza è possibile ottenere la licence key da copiare, ci servirà per il nostro progetto Unity.

Vuforia LicenseVuforia License - Copy into your app

A questo punto è necessario creare il Target Manager ossia il database necessario per il target dell’immagine del CV. Cliccate su Target Manager o collegatevi al sito https://developer.vuforia.com/vui/develop/databases e cliccate su Add Database, scegliete un Database Name e su Type selezionate Device.

A questo punto è necessario scaricare il database cliccando **Download

Vuforia Add DatabaseVuforia License - Copy into your app

Import Avatar 3D

Dal sito Ready Player Me (acronimo RPM), scaricare la SDK per Unity disponibile al sito https://docs.readyplayer.me/ready-player-me/integration-guides/unity-sdk/unity-sdk-download e importarla in Unity seguendo i passi:

  1. Scarica la SDK Unity al seguente link https://docs.readyplayer.me/ready-player-me/integration-guides/unity-sdk/unity-sdk-download
  2. Importa il file .unitypackage nel tuo progetto, o clicca su Assets > Import Package e selezioni il file .unitypackage di Ready Player Me.
  3. Se l’import è avvenuto correttamente dovresti vedere il menù selezionabile Ready Player Me.

A questo punto è necessario scaricare l’avatar 3D precedentemente creato su Ready Player Me, click su Ready Player Me -> Avatar Loader, inserire nella riga Avatar URL, l’url dell’avatar creato online.

Dovremmo vedere all’interno del nostro progetto l’avatar 3D appena importato. Se dovesse capitare il seguente errore: , fai riferimento al paragrafo 1 del Troubleshooting.

All’interno del nostro progetto abbiamo l’avatar 3D RPM.

Costruiamo la scene

Per fare questo progetto possiamo dividerlo in tre fasi principali:

  1. Introduzione;
  2. Avatar/Player si muove all’interno del CV;
  3. Una volta arrivato al punto d’interesse il Player parla.

Per creare la fase introduttiva ho utilizzato un script temporizzato con l’animazione d’entrata che ho creato:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Intro : MonoBehaviour
{
    //GameObject "Player"
    public GameObject Avatar;
    //GameObject "Rings"
    public GameObject TeleportRings;
    //GameObject "Start CV"
    public GameObject LetsGo;

    public void StartAnim()
    {
        //Start the coroutine we define below named StartAnimation.
        StartCoroutine(StartAnimation());
    }

    /*
     * Start the scene intro. 
     * Initialize the transport rings and player;
     * rings & "Start CV" disappear at the end of the timed animation.
     */
    public IEnumerator StartAnimation()
    {
        if (Avatar.activeInHierarchy == false)
        {
            Debug.Log("Start Time : " + Time.time);
            yield return new WaitForSeconds(2);
            Debug.Log("Delete Image: " + Time.time);
            TeleportRings.SetActive(true);
            Debug.Log("Before Timer : " + Time.time);
            yield return new WaitForSeconds(3.4f);
            Debug.Log("Start Avatar : " + Time.time);
            Avatar.SetActive(true);
            yield return new WaitForSeconds(3.7f);
            Debug.Log("Delete Rings: " + Time.time);
            TeleportRings.SetActive(false);
            LetsGo.SetActive(false);
        }
    }
}

In questo script ho temporizzato l’entrata degli anelli di trasporto, che danno il via alla scena, in particolare questo script fa partire gli anelli al click sul bottone Start CV, carica il Player dopo un tempo X sicronizzato con il bagliore dell’animazione e la rimozione del bottone Start CV alla fine del video.

Per la logica vera e propria e i movimenti del Player ho scritto questo script che è il core dell’intero progetto:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using ReadyPlayerMe;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MainController : MonoBehaviour
{
    public GameObject Player;
    public bool PlayerStop = true;
    public bool PlayerTalk = false;
    private Transform target;
    public GameObject objectTarget;

    [SerializeField]
    private AudioClip PlayerAudioClip;
    private AudioSource AudioSource;
    public VoiceHandler voiceHandler;


    private void Start()
    {
        //PlayerAudioClip = Resources.Load("introduzione") as AudioClip;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Reference");
        AudioSource = GetComponent<AudioSource>();
        StartCoroutine(WaitForTalk());
    }

    public IEnumerator WaitForTalk()
    {
        yield return new WaitForSeconds(4.5f);
        PlayerTalk = true;
        Idle();
    }


    void FixedUpdate()
    {
        if (PlayerStop == false)
        { 
            //find the vector pointing from our position to the target
            Vector3 direction = (target.position - transform.position).normalized;
            //create the rotation we need to be in to look at the target
            Quaternion toRotation = Quaternion.LookRotation(direction);
            //rotate us over time according to speed until we are in the required rotation
            transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, 0.2f);
            //Debug.Log("Player rotation: " + transform.rotation + "  Target Rotation: " + lookRotation);
            Player.GetComponent<Animator>().Play("Walk");
        }
        else
        {
            objectTarget = GameObject.Find("/ImageTarget/Positions/Reference");
            target = objectTarget.transform;
            Vector3 direction = (target.position - transform.position).normalized;
            Quaternion toRotation = Quaternion.LookRotation(direction);
            transform.rotation = Quaternion.Slerp(transform.rotation, toRotation, 0.05f);
            if (PlayerTalk == true)
            {
                Player.GetComponent<Animator>().Play("Talk");
                voiceHandler.PlayAudioClip(PlayerAudioClip);
                PlayerTalk = false;
            }
            if (!AudioSource.isPlaying)
            {
                Player.GetComponent<Animator>().Play("Idle Calm");
            }
        }
    }


    void OnCollisionStay(Collision collision)
    {
        if(collision.collider.name == objectTarget.name)
        {
            PlayerStop = true;
            Player.GetComponent<Animator>().enabled = false;
            Idle();
        }
    }
        public void Idle()
    {
        Player.GetComponent<Animator>().enabled = true;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Reference");
        target = objectTarget.transform;
        Player.GetComponent<Animator>().Play("Idle");
    }

    public void About()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("about") as AudioClip;
        PlayerStop = false;
        PlayerTalk = true;
        objectTarget = GameObject.Find("/ImageTarget/Positions/About");
        target = objectTarget.transform;
    }

    public void Review()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("review") as AudioClip;
        PlayerStop = false;
        PlayerTalk = true;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Review");
        target = objectTarget.transform;
    }

    public void Experience()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("experience") as AudioClip;
        PlayerStop = false;
        PlayerTalk = true;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Experience");
        target = objectTarget.transform;
    }

    public void Projects()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("projects") as AudioClip;
        PlayerStop = false;
        PlayerTalk = true;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Projects");
        target = objectTarget.transform;
        Player.GetComponent<Animator>().Play("Talk");
    }

    public void Education()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("education") as AudioClip;
        PlayerTalk = true;
        PlayerStop = false;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Education");
        target = objectTarget.transform;
        Player.GetComponent<Animator>().Play("Talk");
    }

    public void CertSkills()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("certifications") as AudioClip;
        PlayerTalk = true;
        PlayerStop = false;
        objectTarget = GameObject.Find("/ImageTarget/Positions/CertSkills");
        target = objectTarget.transform;
        Player.GetComponent<Animator>().Play("Talk");
    }



    public void Skills()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("skills") as AudioClip;
        PlayerStop = false;
        PlayerTalk = true; 
        objectTarget = GameObject.Find("/ImageTarget/Positions/Skills");
        target = objectTarget.transform;
        Player.GetComponent<Animator>().Play("Talk");
    }

    public void SoftSkills()
    {
        voiceHandler.AudioSource.Stop();
        PlayerAudioClip = Resources.Load("softskills") as AudioClip;
        PlayerTalk = true;
        PlayerStop = false;
        objectTarget = GameObject.Find("/ImageTarget/Positions/Softskills");
        target = objectTarget.transform;
        Player.GetComponent<Animator>().Play("Talk");
    }
}

I metodi vengono chiamati da una serie di bottoni creati in serie in scena, a cui l’utente può cliccare per avviare il metodo.

Button Unity

Ogni bottone chiama un determinato metodo mediante l’On Click().

On Click() Button

Per gestire le posizioni in cui il Player può muversi ho creato dei GameObject da usare come riferimento per il Player più un GameObject di riferimento fisso in modo che il player si rivolga sempre allo stesso punto quando è in idle o sta parlando.

On Click() Button

Per il Player ho creato una serie di animazioni che vengono chiamate direttamente dallo script Main Controller.

Per rendere il nostro avatar ancor più realistico ho creato un effetto passi semplicemente aggiungendo un rumore di passo quando l’animazione viene chiamata. Per fare questo è necessario modificare l’animazione mediante il comando Edit e inserire, quando la suola dell’animazione tocca il pavimento, una funzione (nel mio caso l’ho chiamata step). A questo punto dobbiamo aggiungere questo script al Player in modo che quando la suola tocca il pavimento viene triggerata la funzione step che chiama il metodo Step() presente nello script sottostante:

Unity Event Animation Walk

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FootSteps : MonoBehaviour
{

    [SerializeField]
    private AudioClip[] audioSteps;
    private AudioSource audioWalk;

    private void Awake()
    {
        audioWalk = GetComponent<AudioSource>();
    }

    // Update is called once per frame

    private void Step()
    {
        AudioClip clip = GetRandomSteps();
        audioWalk.PlayOneShot(clip);
    }

    private AudioClip GetRandomSteps()
    {
        return audioSteps[UnityEngine.Random.Range(0, audioSteps.Length)];
    }
}

A questo punto non ci resta che provare l’applicazione.

Demo

A breve sarà disponibile la demo dell’applicazione… :)

Conclusioni e Sviluppi Futuri

L’applicazione finale si presta bene per aumentare le informazioni di un CV cartaceo ma cosa succede se il CV si presenta su uno schermo digitale?

Uno sviluppo futuro sarà quello di distinguere il CV posto su un piano orizzonatale rispetto ad un piano verticale in questo caso ci sono due soluzioni implementabili.

  1. Far scegliere all’utente il tipo di CV da tracciare e di conseguenza creare due scene diverse riducendo al minimo gli sforzi di modifica dell’applicazione; in questo modo basterà preparare due scene oppure tramite codice modificare il comportamento dell’avatar in base alla scelta dell’utente.
  2. Creare un tracking dinamico che si adatti in base al puntamento dello smartphone, quindi se lo smartphone punta su una superficie piana orizzontale l’avatar si muoverà su una superficie piana (ad esempio un tavolo, una scrivania, ecc.), se invece il puntamento dello smartphone avviene su una superficie verticale allora l’avatar si muoverà in maniera diversa tramite dei riferimenti (ad esempio scale, animazioni sui passi, ecc.).
  3. Un’altra alternativa è sostituire l’avatar alla foto presente su CV e diventare un avatar 2D (questo comporterebbe uno stravogimento eccessivo ed inefficiente della scena unity).

Troubleshooting

Errore Timeout import avatar RPM

Il problema è dovuto ad un timeout che scatta quando l’avatar non viene scaricato entro un certo tempo t. Per risolvere l’errore aprire il file di progetto Assets\Plugins\Ready Player Me\Runtime\Operations\AvatarDownloader.cs e inserire al posto della varibile timeout, un valore intero relativamente alto (ad esempio: 60000).

Video Youtube

Questo articolo è anche disponibile come Tutorial Youtube:

Codice Sorgente

Al momento non è ancora disponibile, appena pronto inserirò qui il link.

Fonti

Guide Utili

*Augmented Reality for Everyone - Full Course *Unity AR Object Tracking e Realtà Aumentata con Vuforia

Grazie ☺

Condividi:
Views
Create with Hugo, Theme Stack;
Sito creato e gestito da Francesco Garofalo;