

# Utilizzo della sostituzione dello sfondo con l'SDK di trasmissione IVS
<a name="broadcast-3p-camera-filters-background-replacement"></a>

La sostituzione dello sfondo è un tipo di filtro della fotocamera che consente ai creatori di streaming live di modificare lo sfondo. Come illustrato nel seguente diagramma, la sostituzione dello sfondo comporta:

1. L'ottenimento di un'immagine della fotocamera dal feed live della fotocamera.

1. La segmentazione in componenti di primo piano e di secondo piano utilizzando Google ML Kit.

1. La combinazione della maschera di segmentazione risultante con un'immagine di sfondo personalizzata.

1. L'invio a una sorgente di immagini personalizzate per la trasmissione.

![Il flusso di lavoro per l'implementazione della sostituzione dello sfondo.](http://docs.aws.amazon.com/it_it/ivs/latest/RealTimeUserGuide/images/3P_Camera_Filters_Background_Replacement.png)


## App
<a name="background-replacement-web"></a>

Questa sezione presuppone che tu abbia già dimestichezza con la [pubblicazione e la sottoscrizione di video utilizzando l'SDK di trasmissione Web](https://docs.aws.amazon.com//ivs/latest/RealTimeUserGuide/getting-started-pub-sub-web.html).

Per sostituire lo sfondo di uno streaming live con un'immagine personalizzata, utilizza il [modello di segmentazione dei selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model) con [MediaPipe Image Segmenter](https://developers.google.com/mediapipe/solutions/vision/image_segmenter). Si tratta di un modello di machine learning che identifica quali pixel del fotogramma video sono in primo piano o sullo sfondo. Puoi quindi utilizzare i risultati del modello per sostituire lo sfondo di uno streaming live, copiando i pixel in primo piano dal feed video in un'immagine personalizzata che rappresenta il nuovo sfondo.

Per integrare la sostituzione dello sfondo con l'SDK di trasmissione Web per lo streaming in tempo reale IVS, è necessario:

1. Installare MediaPipe e Webpack. (Il nostro esempio utilizza Webpack come bundler, ma puoi utilizzare qualsiasi bundler di tua scelta.)

1. Crea `index.html`.

1. Aggiungere elementi multimediali.

1. Aggiungere un tag di script.

1. Crea `app.js`.

1. Caricare un'immagine di sfondo personalizzata.

1. Creare un'istanza di `ImageSegmenter`.

1. Eseguire il rendering del feed video su un'area di lavoro.

1. Creare la una logica di sostituzione dello sfondo.

1. Creare un file di configurazione di Webpack.

1. Raggruppare il file JavaScript.

### Installazione di MediaPipe e Webpack
<a name="background-replacement-web-install-mediapipe-webpack"></a>

Per iniziare, installa i pacchetti npm `@mediapipe/tasks-vision` e `webpack`. L'esempio seguente utilizza Webpack come bundler JavaScript; se preferisci, puoi usare un bundler diverso.

#### JavaScript
<a name="background-replacement-web-install-mediapipe-webpack-code"></a>

```
npm i @mediapipe/tasks-vision webpack webpack-cli
```

Assicurati di aggiornare anche il tuo `package.json` per specificare `webpack` come script di compilazione:

#### JavaScript
<a name="background-replacement-web-update-package-json-code"></a>

```
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
```

### Creazione di index.html
<a name="background-replacement-web-create-index"></a>

Quindi, create il boilerplate HTML e importa l'SDK di trasmissione Web come tag di script. Nel codice seguente, assicurati di sostituire `<SDK version>` con la versione dell'SDK di trasmissione che stai utilizzando.

#### JavaScript
<a name="background-replacement-web-create-index-code"></a>

```
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <!-- Import the SDK -->
  <script src="https://web-broadcast.live-video.net/<SDK version>/amazon-ivs-web-broadcast.js"></script>
</head>

<body>

</body>
</html>
```

### Aggiunta di elementi multimediali
<a name="background-replacement-web-add-media-elements"></a>

Quindi, aggiungi un elemento video e due elementi dell'area di lavoro all'interno del tag body. L'elemento video conterrà il feed live della fotocamera e sarà utilizzato come input per MediaPipe Image Segmenter. Il primo elemento dell'area di lavoro verrà utilizzato per renderizzare un'anteprima del feed che verrà trasmesso. Il secondo elemento dell'area di lavoro verrà utilizzato per renderizzare l'immagine personalizzata che verrà utilizzata come sfondo. Poiché la seconda area di lavoro con l'immagine personalizzata viene utilizzata solo come sorgente per copiare a livello di codice i pixel da essa all'area di lavoro finale, è nascosta alla vista.

#### JavaScript
<a name="background-replacement-web-add-media-elements-code"></a>

```
<div class="row local-container">
      <video id="webcam" autoplay style="display: none"></video>
    </div>
    <div class="row local-container">
      <canvas id="canvas" width="640px" height="480px"></canvas>

      <div class="column" id="local-media"></div>
      <div class="static-controls hidden" id="local-controls">
        <button class="button" id="mic-control">Mute Mic</button>
        <button class="button" id="camera-control">Mute Camera</button>
      </div>
    </div>
    <div class="row local-container">
      <canvas id="background" width="640px" height="480px" style="display: none"></canvas>
    </div>
```

### Aggiunta di un tag di script
<a name="background-replacement-web-add-script-tag"></a>

Aggiungi un tag di script per caricare un file JavaScript in bundle che conterrà il codice per eseguire la sostituzione dello sfondo e pubblicarlo su una fase:

```
<script src="./dist/bundle.js"></script>
```

### Crea app.js
<a name="background-replacement-web-create-appjs"></a>

Quindi, crea un file JavaScript per ottenere gli oggetti elemento per gli elementi video e dell'area di lavoro creati nella pagina HTML. Importa i moduli `ImageSegmenter` e `FilesetResolver`. Il modulo `ImageSegmenter` verrà utilizzato per eseguire l'attività di segmentazione.

#### JavaScript
<a name="create-appjs-import-imagesegmenter-fileresolver-code"></a>

```
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";
```

Quindi, crea una funzione chiamata `init()` a recuperare il MediaStream dalla fotocamera dell'utente e richiama una funzione di callback ogni volta che un frame della fotocamera termina il caricamento. Aggiungi i listener di eventi per i pulsanti per entrare e uscire da una fase.

Tieni presente che quando si entra in una fase, viene passata una variabile denominata `segmentationStream`. Si tratta di un flusso video catturato da un elemento dell'area di lavoro contenente un'immagine in primo piano sovrapposta all'immagine personalizzata che rappresenta lo sfondo. Successivamente, questo flusso personalizzato verrà utilizzato per creare un'istanza di un `LocalStageStream`, che può essere pubblicata in una fase.

#### JavaScript
<a name="create-appjs-create-init-code"></a>

```
const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });
};
```

### Caricamento di un'immagine di sfondo personalizzata
<a name="background-replacement-web-background-image"></a>

Nella parte inferiore della funzione `init`, aggiungi il codice per chiamare una funzione denominata `initBackgroundCanvas`, che carica un'immagine personalizzata da un file locale e la rende su un'area di lavoro. Definiremo questa funzione nel passaggio successivo. Assegna il file `MediaStream` recuperato dalla fotocamera dell'utente all'oggetto video. Successivamente, questo oggetto video verrà passato a Image Segmenter. Inoltre, imposta una funzione denominata `renderVideoToCanvas` come funzione di callback da richiamare ogni volta che un fotogramma video ha terminato il caricamento. Definiremo questa funzione in un passaggio successivo.

#### JavaScript
<a name="background-replacement-web-load-background-image-code"></a>

```
initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
```

Implementiamo la funzione `initBackgroundCanvas`, che carica un'immagine da un file locale. In questo esempio, viene utilizzata l'immagine di una spiaggia come sfondo personalizzato. L'area di lavoro contenente l'immagine personalizzata verrà nascosta alla visualizzazione, poiché la unirai ai pixel di primo piano dell'elemento dell'area di lavoro contenente il feed della fotocamera.

#### JavaScript
<a name="background-replacement-web-implement-initBackgroundCanvas-code"></a>

```
const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};
```

### Creazione di un'istanza di ImageSegmenter
<a name="background-replacement-web-imagesegmenter"></a>

Quindi, crea un'istanza di `ImageSegmenter`, che segmenterà l'immagine e restituirà il risultato come maschera. Quando crei un'istanza di un `ImageSegmenter`, utilizzerai il [modello di segmentazione dei selfie](https://developers.google.com/mediapipe/solutions/vision/image_segmenter#selfie-model).

#### JavaScript
<a name="background-replacement-web-imagesegmenter-code"></a>

```
const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};
```

### Rendering del feed video su un'area di lavoro
<a name="background-replacement-web-render-video-to-canvas"></a>

Quindi, crea la funzione che esegue il rendering del feed video sull'altro elemento dell'area di lavoro. È necessario renderizzare il feed video su un'area di lavoro in modo da poter estrarre i pixel di primo piano da esso utilizzando l'API Canvas 2D. Durante questa operazione, passeremo anche un fotogramma video alla nostra istanza di `ImageSegmenter`, utilizzando il metodo [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) per segmentare il primo piano dallo sfondo nel fotogramma video. Quando il metodo [segmentforVideo](https://developers.google.com/mediapipe/api/solutions/js/tasks-vision.imagesegmenter#imagesegmentersegmentforvideo) viene completato, richiama la nostra funzione di callback personalizzata, `replaceBackground`, per eseguire la sostituzione dello sfondo.

#### JavaScript
<a name="background-replacement-web-render-video-to-canvas-code"></a>

```
const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};
```

### Creazione di una logica di sostituzione dello sfondo
<a name="background-replacement-web-logic"></a>

Crea la funzione `replaceBackground`, che unisce l'immagine di sfondo personalizzata con il primo piano dal feed della fotocamera per sostituire lo sfondo. La funzione recupera innanzitutto i dati dei pixel sottostanti dell'immagine di sfondo personalizzata e del feed video dai due elementi dell'area di lavoro creati in precedenza. Quindi scorre attraverso la maschera fornita da `ImageSegmenter`, che indica quali pixel sono in primo piano. Mentre itera attraverso la maschera, copia selettivamente i pixel che contengono il feed della fotocamera dell'utente nei dati dei pixel di sfondo corrispondenti. Fatto ciò, converte i dati finali dei pixel con il primo piano copiato sullo sfondo e li disegna su un'area di lavoro.

#### JavaScript
<a name="background-replacement-web-logic-create-replacebackground-code"></a>

```
function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
  // Only copy pixels on to the background image if the mask indicates they are in the foreground
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }

 // Convert the pixel data to a format suitable to be drawn to a canvas
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}
```

Per riferimento, ecco il file `app.js` completo contenente tutta la logica di cui sopra:

#### JavaScript
<a name="background-replacement-web-logic-app-js-code"></a>

```
/*! Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */

// All helpers are expose on 'media-devices.js' and 'dom.js'
const { setupParticipant } = window;

const { Stage, LocalStageStream, SubscribeType, StageEvents, ConnectionState, StreamType } = IVSBroadcastClient;
const canvasElement = document.getElementById("canvas");
const background = document.getElementById("background");
const canvasCtx = canvasElement.getContext("2d");
const backgroundCtx = background.getContext("2d");
const video = document.getElementById("webcam");

import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision";

let cameraButton = document.getElementById("camera-control");
let micButton = document.getElementById("mic-control");
let joinButton = document.getElementById("join-button");
let leaveButton = document.getElementById("leave-button");

let controls = document.getElementById("local-controls");
let audioDevicesList = document.getElementById("audio-devices");
let videoDevicesList = document.getElementById("video-devices");

// Stage management
let stage;
let joining = false;
let connected = false;
let localCamera;
let localMic;
let cameraStageStream;
let micStageStream;
let imageSegmenter;
let lastWebcamTime = -1;

const init = async () => {
  await initializeDeviceSelect();

  cameraButton.addEventListener("click", () => {
    const isMuted = !cameraStageStream.isMuted;
    cameraStageStream.setMuted(isMuted);
    cameraButton.innerText = isMuted ? "Show Camera" : "Hide Camera";
  });

  micButton.addEventListener("click", () => {
    const isMuted = !micStageStream.isMuted;
    micStageStream.setMuted(isMuted);
    micButton.innerText = isMuted ? "Unmute Mic" : "Mute Mic";
  });

  localCamera = await getCamera(videoDevicesList.value);
  const segmentationStream = canvasElement.captureStream();

  joinButton.addEventListener("click", () => {
    joinStage(segmentationStream);
  });

  leaveButton.addEventListener("click", () => {
    leaveStage();
  });

  initBackgroundCanvas();

  video.srcObject = localCamera;
  video.addEventListener("loadeddata", renderVideoToCanvas);
};

const joinStage = async (segmentationStream) => {
  if (connected || joining) {
    return;
  }
  joining = true;

  const token = document.getElementById("token").value;

  if (!token) {
    window.alert("Please enter a participant token");
    joining = false;
    return;
  }

  // Retrieve the User Media currently set on the page
  localMic = await getMic(audioDevicesList.value);

  cameraStageStream = new LocalStageStream(segmentationStream.getVideoTracks()[0]);
  micStageStream = new LocalStageStream(localMic.getAudioTracks()[0]);

  const strategy = {
    stageStreamsToPublish() {
      return [cameraStageStream, micStageStream];
    },
    shouldPublishParticipant() {
      return true;
    },
    shouldSubscribeToParticipant() {
      return SubscribeType.AUDIO_VIDEO;
    },
  };

  stage = new Stage(token, strategy);

  // Other available events:
  // https://aws.github.io/amazon-ivs-web-broadcast/docs/sdk-guides/stages#events
  stage.on(StageEvents.STAGE_CONNECTION_STATE_CHANGED, (state) => {
    connected = state === ConnectionState.CONNECTED;

    if (connected) {
      joining = false;
      controls.classList.remove("hidden");
    } else {
      controls.classList.add("hidden");
    }
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_JOINED, (participant) => {
    console.log("Participant Joined:", participant);
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_STREAMS_ADDED, (participant, streams) => {
    console.log("Participant Media Added: ", participant, streams);

    let streamsToDisplay = streams;

    if (participant.isLocal) {
      // Ensure to exclude local audio streams, otherwise echo will occur
      streamsToDisplay = streams.filter((stream) => stream.streamType === StreamType.VIDEO);
    }

    const videoEl = setupParticipant(participant);
    streamsToDisplay.forEach((stream) => videoEl.srcObject.addTrack(stream.mediaStreamTrack));
  });

  stage.on(StageEvents.STAGE_PARTICIPANT_LEFT, (participant) => {
    console.log("Participant Left: ", participant);
    teardownParticipant(participant);
  });

  try {
    await stage.join();
  } catch (err) {
    joining = false;
    connected = false;
    console.error(err.message);
  }
};

const leaveStage = async () => {
  stage.leave();

  joining = false;
  connected = false;

  cameraButton.innerText = "Hide Camera";
  micButton.innerText = "Mute Mic";
  controls.classList.add("hidden");
};

function replaceBackground(result) {
  let imageData = canvasCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  let backgroundData = backgroundCtx.getImageData(0, 0, video.videoWidth, video.videoHeight).data;
  const mask = result.categoryMask.getAsFloat32Array();
  let j = 0;

  for (let i = 0; i < mask.length; ++i) {
    const maskVal = Math.round(mask[i] * 255.0);

    j += 4;
    if (maskVal < 255) {
      backgroundData[j] = imageData[j];
      backgroundData[j + 1] = imageData[j + 1];
      backgroundData[j + 2] = imageData[j + 2];
      backgroundData[j + 3] = imageData[j + 3];
    }
  }
  const uint8Array = new Uint8ClampedArray(backgroundData.buffer);
  const dataNew = new ImageData(uint8Array, video.videoWidth, video.videoHeight);
  canvasCtx.putImageData(dataNew, 0, 0);
  window.requestAnimationFrame(renderVideoToCanvas);
}

const createImageSegmenter = async () => {
  const audio = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.2/wasm");

  imageSegmenter = await ImageSegmenter.createFromOptions(audio, {
    baseOptions: {
      modelAssetPath: "https://storage.googleapis.com/mediapipe-models/image_segmenter/selfie_segmenter/float16/latest/selfie_segmenter.tflite",
      delegate: "GPU",
    },
    runningMode: "VIDEO",
    outputCategoryMask: true,
  });
};

const renderVideoToCanvas = async () => {
  if (video.currentTime === lastWebcamTime) {
    window.requestAnimationFrame(renderVideoToCanvas);
    return;
  }
  lastWebcamTime = video.currentTime;
  canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);

  if (imageSegmenter === undefined) {
    return;
  }

  let startTimeMs = performance.now();

  imageSegmenter.segmentForVideo(video, startTimeMs, replaceBackground);
};

const initBackgroundCanvas = () => {
  let img = new Image();
  img.src = "beach.jpg";

  img.onload = () => {
    backgroundCtx.clearRect(0, 0, canvas.width, canvas.height);
    backgroundCtx.drawImage(img, 0, 0);
  };
};

createImageSegmenter();
init();
```

### Creazione di un file di configurazione di Webpack.
<a name="background-replacement-web-webpack-config"></a>

Aggiungi questa configurazione al file di configurazione di Webpack per raggruppare `app.js`, in modo che le chiamate di importazione funzionino:

#### JavaScript
<a name="background-replacement-web-webpack-config-code"></a>

```
const path = require("path");
module.exports = {
  entry: ["./app.js"],
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist"),
  },
};
```

### Raggruppamento dei tuoi file JavaScript
<a name="background-replacement-web-bundle-javascript"></a>

```
npm run build
```

Avvia un semplice server HTTP dalla directory contenente `index.html` e apri `localhost:8000` per vedere il risultato:

```
python3 -m http.server -d ./
```

## Android
<a name="background-replacement-android"></a>

Per sostituire lo sfondo del tuo streaming live, puoi utilizzare l'API di segmentazione dei selfie di [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation). L'API di segmentazione dei selfie accetta l'immagine di una fotocamera come input e restituisce una maschera che fornisce un punteggio di affidabilità per ogni pixel dell'immagine, indicando se era in primo piano o sullo sfondo. In base al punteggio di affidabilità, puoi quindi recuperare il colore dei pixel corrispondente dall'immagine di sfondo o dall'immagine in primo piano. Questo processo continua fino a quando non sono stati esaminati tutti i punteggi di affidabilità nella maschera. Il risultato è una nuova gamma di colori dei pixel contenenti pixel in primo piano combinati con i pixel dell'immagine di sfondo.

Per integrare la sostituzione dello sfondo con l'SDK di trasmissione per lo streaming in tempo reale IVS, è necessario:

1. Installare le librerie CameraX e Google ML Kit.

1. Inizializzare le variabili boilerplate.

1. Creare una sorgente di immagini personalizzate.

1. Gestire i frame della fotocamera.

1. Passre i frame della fotocamera a Google ML Kit.

1. Sovrapprre i frame della fotocamera in primo piano allo sfondo personalizzato.

1. Inserire la nuova immagine in una sorgente di immagini personalizzate.

### Installazione delle librerie CameraX e di Google ML Kit
<a name="background-replacement-android-install-camerax-googleml"></a>

Per estrarre immagini dal feed live della fotocamera, utilizza la libreria CameraX di Android. Per installare l'SDK Camera Kit e Google ML Kit, aggiungi quanto segue al file `build.gradle` del modulo. Sostituisci `${camerax_version}` e `${google_ml_kit_version}` con la versione più recente delle librerie [CameraX](https://developer.android.com/jetpack/androidx/releases/camera) e [Google ML Kit](https://developers.google.com/ml-kit/vision/selfie-segmentation/android), rispettivamente. 

#### Java
<a name="background-replacement-android-install-camerax-googleml-code"></a>

```
implementation "com.google.mlkit:segmentation-selfie:${google_ml_kit_version}"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
```

Importa le seguenti librerie:

#### Java
<a name="background-replacement-android-import-libraries-code"></a>

```
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.lifecycle.ProcessCameraProvider
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
```

### Inizializzazione delle variabili boilerplate.
<a name="background-replacement-android-initialize-variables"></a>

Inizializza un'istanza di `ImageAnalysis` e un'istanza di un `ExecutorService`:

#### Java
<a name="background-replacement-android-initialize-imageanalysis-executorservice-code"></a>

```
private lateinit var binding: ActivityMainBinding
private lateinit var cameraExecutor: ExecutorService
private var analysisUseCase: ImageAnalysis? = null
```

Inizializza un'istanza Segmenter in [STREAM\_MODE:](https://developers.google.com/ml-kit/vision/selfie-segmentation/android#detector_mode)

#### Java
<a name="background-replacement-android-initialize-segmenter-code"></a>

```
private val options =
        SelfieSegmenterOptions.Builder()
            .setDetectorMode(SelfieSegmenterOptions.STREAM_MODE)
            .build()

private val segmenter = Segmentation.getClient(options)
```

### Creazione di una sorgente di immagini personalizzate
<a name="background-replacement-android-create-image-source"></a>

Nel metodo `onCreate` della tua attività, crea un'istanza di un oggetto `DeviceDiscovery` e crea una sorgente di immagini personalizzate. Il `Surface` fornito dalla sorgente di immagini personalizzate riceverà l'immagine finale, con il primo piano sovrapposto a un'immagine di sfondo personalizzata. Creerai quindi un'istanza di un `ImageLocalStageStream` utilizzando la sorgente di immagini personalizzate. L'istanza di un `ImageLocalStageStream` (denominata `filterStream` in questo esempio) può quindi essere pubblicata in una fase. Consulta la [Guida all'SDK di trasmissione Android IVS](broadcast-android.md) per le istruzioni di configurazione di una fase. Infine, crea anche un thread che verrà utilizzato per gestire la fotocamera.

#### Java
<a name="background-replacement-android-create-image-source-code"></a>

```
var deviceDiscovery = DeviceDiscovery(applicationContext)
var customSource = deviceDiscovery.createImageInputSource( BroadcastConfiguration.Vec2(
720F, 1280F
))
var surface: Surface = customSource.inputSurface
var filterStream = ImageLocalStageStream(customSource)

cameraExecutor = Executors.newSingleThreadExecutor()
```

### Gestione di frame della fotocamera
<a name="background-replacement-android-camera-frames"></a>

Quindi, crea una funzione per inizializzare la fotocamera. Questa funzione utilizza la libreria CameraX per estrarre immagini dal feed live della fotocamera. Innanzitutto, crea un'istanza di un `ProcessCameraProvider` chiamata `cameraProviderFuture`. Questo oggetto rappresenta un risultato futuro dell'ottenimento di un fornitore di fotocamere. Quindi carica un'immagine dal tuo progetto come bitmap. Questo esempio utilizza l'immagine di una spiaggia come sfondo, ma è possibile utilizzare qualsiasi immagine desiderata.

Quindi aggiungi un listener a `cameraProviderFuture`. Questo listener riceve una notifica quando la fotocamera diventa disponibile o se si verifica un errore durante il processo di ricerca di un fornitore di fotocamere.

#### Java
<a name="background-replacement-android-initialize-camera-code"></a>

```
private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.beach
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;


        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            
                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```

All'interno del listener, crea `ImageAnalysis.Builder` per accedere a ogni singolo fotogramma dal feed live della fotocamera. Imposta la strategia di contropressione su `STRATEGY_KEEP_ONLY_LATEST`. Ciò garantisce che per l'elaborazione venga fornito un solo fotogramma alla volta. Converte ogni singolo fotogramma della fotocamera in una bitmap in modo da poterne estrarre i pixel per combinarli successivamente con l'immagine di sfondo personalizzata.

#### Java
<a name="background-replacement-android-create-imageanalysisbuilder-code"></a>

```
val imageAnalyzer = ImageAnalysis.Builder()
analysisUseCase = imageAnalyzer
    .setTargetResolution(Size(360, 640))
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

analysisUseCase?.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
    val mediaImage = imageProxy.image
    val tempBitmap = imageProxy.toBitmap();
    val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())
```

### Passaggio dei frame della fotocamera a Google ML Kit
<a name="background-replacement-android-frames-to-mlkit"></a>

Quindi, crea un file `InputImage` e invialo all'istanza di Segmenter per l'elaborazione. Un `InputImage` può essere creato da un `ImageProxy` fornito dall'istanza di `ImageAnalysis`. Una volta fornito `InputImage` a Segmenter, viene restituita una maschera con punteggi di affidabilità che indicano la probabilità che un pixel sia in primo piano o sullo sfondo. Questa maschera fornisce anche proprietà di larghezza e altezza, che verranno utilizzate per creare un nuovo array contenente i pixel di sfondo dell'immagine di sfondo personalizzata caricata in precedenza.

#### Java
<a name="background-replacement-android-frames-to-mlkit-code"></a>

```
if (mediaImage != null) {
        val inputImage =
            InputImage.fromMediaImag


segmenter.process(inputImage)
    .addOnSuccessListener { segmentationMask ->
        val mask = segmentationMask.buffer
        val maskWidth = segmentationMask.width
        val maskHeight = segmentationMask.height
        val backgroundPixels = IntArray(maskWidth * maskHeight)
        bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)
```

### Sovrapposizione dei frame della fotocamera in primo piano allo sfondo personalizzato
<a name="background-replacement-android-overlay-frame-foreground"></a>

Con la maschera contenente i punteggi di affidabilità, il frame della fotocamera come bitmap e i pixel a colori dell'immagine di sfondo personalizzata, hai tutto ciò di cui hai bisogno per sovrapporre il primo piano allo sfondo personalizzato. La funzione `overlayForeground` viene quindi chiamata con i parametri seguenti:

#### Java
<a name="background-replacement-android-call-overlayforeground-code"></a>

```
resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
```

Questa funzione scorre attraverso la maschera e verifica i valori di affidabilità per determinare se ottenere il colore dei pixel corrispondente dall'immagine di sfondo o dal frame della fotocamera. Se il valore di affidabilità indica che un pixel nella maschera è molto probabilmente sullo sfondo, otterrà il colore dei pixel corrispondente dall'immagine di sfondo; in caso contrario, otterrà il colore dei pixel corrispondente dal frame della fotocamera per costruire il primo piano. Una volta che la funzione termina l'iterazione nella maschera, viene creata e restituita una nuova bitmap utilizzando la nuova matrice di pixel colorati. Questa nuova bitmap contiene il primo piano sovrapposto allo sfondo personalizzato.

#### Java
<a name="background-replacement-android-run-overlayforeground-code"></a>

```
private fun overlayForeground(
        byteBuffer: ByteBuffer,
        maskWidth: Int,
        maskHeight: Int,
        cameraBitmap: Bitmap,
        backgroundPixels: IntArray
    ): Bitmap {
        @ColorInt val colors = IntArray(maskWidth * maskHeight)
        val cameraPixels = IntArray(maskWidth * maskHeight)

        cameraBitmap.getPixels(cameraPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

        for (i in 0 until maskWidth * maskHeight) {
            val backgroundLikelihood: Float = 1 - byteBuffer.getFloat()

            // Apply the virtual background to the color if it's not part of the foreground
            if (backgroundLikelihood > 0.9) {
                // Get the corresponding pixel color from the background image
                // Set the color in the mask based on the background image pixel color
                colors[i] = backgroundPixels.get(i)
            } else {
                // Get the corresponding pixel color from the camera frame
                // Set the color in the mask based on the camera image pixel color
                colors[i] = cameraPixels.get(i)
            }
        }

        return Bitmap.createBitmap(
            colors, maskWidth, maskHeight, Bitmap.Config.ARGB_8888
        )
    }
```

### Inserimento della nuova immagine in una sorgente di immagini personalizzata
<a name="background-replacement-android-custom-image-source"></a>

È quindi possibile scrivere la nuova bitmap nella `Surface` fornita dalla sorgente di immagini personalizzata. Questa operazione la trasmetterà alla fase.

#### Java
<a name="background-replacement-android-custom-image-source-code"></a>

```
resultBitmap = overlayForeground(mask, inputBitmap, mutableBitmap, bgBitmap)
canvas = surface.lockCanvas(null);
canvas.drawBitmap(resultBitmap, 0f, 0f, null)
```

Ecco la funzione completa per ottenere i fotogrammi della fotocamera, passarli a Segmenter e sovrapporli allo sfondo:

#### Java
<a name="background-replacement-android-custom-image-source-startcamera-code"></a>

```
@androidx.annotation.OptIn(androidx.camera.core.ExperimentalGetImage::class)
    private fun startCamera(surface: Surface) {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        val imageResource = R.drawable.clouds
        val bgBitmap: Bitmap = BitmapFactory.decodeResource(resources, imageResource)
        var resultBitmap: Bitmap;

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val imageAnalyzer = ImageAnalysis.Builder()
            analysisUseCase = imageAnalyzer
                .setTargetResolution(Size(720, 1280))
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()

            analysisUseCase!!.setAnalyzer(cameraExecutor) { imageProxy: ImageProxy ->
                val mediaImage = imageProxy.image
                val tempBitmap = imageProxy.toBitmap();
                val inputBitmap = tempBitmap.rotate(imageProxy.imageInfo.rotationDegrees.toFloat())

                if (mediaImage != null) {
                    val inputImage =
                        InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)

                    segmenter.process(inputImage)
                        .addOnSuccessListener { segmentationMask ->
                            val mask = segmentationMask.buffer
                            val maskWidth = segmentationMask.width
                            val maskHeight = segmentationMask.height
                            val backgroundPixels = IntArray(maskWidth * maskHeight)
                            bgBitmap.getPixels(backgroundPixels, 0, maskWidth, 0, 0, maskWidth, maskHeight)

                            resultBitmap = overlayForeground(mask, maskWidth, maskHeight, inputBitmap, backgroundPixels)
                            canvas = surface.lockCanvas(null);
                            canvas.drawBitmap(resultBitmap, 0f, 0f, null)

                            surface.unlockCanvasAndPost(canvas);

                        }
                        .addOnFailureListener { exception ->
                            Log.d("App", exception.message!!)
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }

                }
            };

            val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, analysisUseCase)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }
```