Generare immagini con Imagen

Imagen è un modello di generazione di immagini. Può essere utilizzato per generare avatar personalizzati per i profili utente o per integrare asset visivi personalizzati nei flussi di schermata esistenti per aumentare il coinvolgimento degli utenti.

Puoi accedere ai modelli Imagen dalla tua app per Android utilizzando l'SDK Firebase AI Logic. I modelli Imagen sono disponibili utilizzando entrambi i provider di API Firebase AI Logic: l'API Gemini Developer (consigliata per la maggior parte degli sviluppatori) e Vertex AI.

Un diagramma che illustra un'architettura di integrazione di Firebase AI Logic
       per accedere all'API Gemini Developer. Un'app per Android utilizza l'SDK
       Firebase per Android per connettersi a Firebase. Firebase interagisce quindi con l'API Gemini Developer, che accede a Gemini Pro e Flash nel cloud.
Figura 1. Accedi ai modelli Imagen utilizzando Firebase AI Logic.

Sperimenta con i prompt

La creazione dei prompt ideali spesso richiede più tentativi. Puoi sperimentare con i prompt di immagini in Google AI Studio, un IDE per la progettazione e la prototipazione dei prompt. Per suggerimenti su come migliorare i prompt, consulta la guida agli attributi di prompt e immagine.

Screenshot dell'interfaccia di Google AI Studio,
      che mostra quattro immagini generate di un T-Rex con uno zaino blu in una
      foresta preistorica.
Figura 2. Google AI Studio può aiutarti a perfezionare i prompt di generazione delle immagini.

Configura un progetto Firebase e connetti la tua app

Segui i passaggi descritti nella documentazione di Firebase per aggiungere Firebase al tuo progetto Android.

Aggiungi la dipendenza Gradle

Aggiungi le seguenti dipendenze al file build.gradle:

dependencies {
  // Import the BoM for the Firebase platform
  implementation(platform("com.google.firebase:firebase-bom:34.4.0"))

  // Add the dependency for the Firebase AI Logic library. When using the BoM,
  // you don't specify versions in Firebase library dependencies
  implementation("com.google.firebase:firebase-ai")
}

Genera un'immagine

Per generare un'immagine nella tua app per Android, inizia istanziando un ImagenModel con una configurazione facoltativa.

Puoi utilizzare il parametro generationConfig per definire un prompt negativo, il numero di immagini, le proporzioni dell'immagine di output, il formato dell'immagine e aggiungere una filigrana. Puoi utilizzare il parametro safetySettings per configurare i filtri sicurezza e persone.

Kotlin

val config = ImagenGenerationConfig {
    numberOfImages = 2,
    aspectRatio = ImagenAspectRatio.LANDSCAPE_16x9,
    imageFormat = ImagenImageFormat.jpeg(compressionQuality = 100),
    addWatermark = false
}

// Initialize the Gemini Developer API backend service
// For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI())
val model = Firebase.ai(backend = GenerativeBackend.googleAI()).imagenModel(
    modelName = "imagen-4.0-generate-001",
    generationConfig = config,
    safetySettings = ImagenSafetySettings(
       safetyFilterLevel = ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,
       personFilterLevel = ImagenPersonFilterLevel.BLOCK_ALL
    )
)

Java

ImagenGenerationConfig config = new ImagenGenerationConfig.Builder()
    .setNumberOfImages(2)
    .setAspectRatio(ImagenAspectRatio.LANDSCAPE_16x9)
    .setImageFormat(ImagenImageFormat.jpeg(100))
    .setAddWatermark(false)
    .build();

// For Vertex AI use Firebase.ai(backend = GenerativeBackend.vertexAI())
ImagenModelFutures model = ImagenModelFutures.from(
    FirebaseAI.ai(backend = GenerativeBackend.googleAI()).imagenModel(
       "imagen-4.0-generate-001",
       config,
       ImagenSafetySettings.builder()
          .setSafetyFilterLevel(ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE)
          .setPersonFilterLevel(ImagenPersonFilterLevel.BLOCK_ALL)
          .build())
);

Una volta creata un'istanza di ImagenModel, puoi generare immagini chiamando generateImages:

Kotlin

val imageResponse = model.generateImages(
  prompt = "A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest",
)
val image = imageResponse.images.first
val bitmapImage = image.asBitmap()

Java

CompletableFuture<GenerateContentResponse> futureResponse =
    model.generateContent(
        Content.newBuilder()
            .addParts(
                Part.newBuilder()
                    .setText("A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest")
                    .build())
            .build());

try {
  GenerateContentResponse imageResponse = futureResponse.get();
  List<GeneratedImage> images =
      imageResponse
          .getCandidates(0)
          .getContent()
          .getParts(0)
          .getInlineData()
          .getImagesList();

  if (!images.isEmpty()) {
    GeneratedImage image = images.get(0);
    Bitmap bitmapImage = image.asBitmap();
    // Use bitmapImage
  }
} catch (ExecutionException | InterruptedException e) {
  e.printStackTrace();
}

Modificare le immagini con Imagen

Gli SDK Firebase AI Logic offrono funzionalità avanzate di editing delle immagini tramite il modello Imagen, che ti consente di:

  • Modifica le immagini in base alle maschere, che include azioni come l'inserimento o la rimozione di oggetti, l'estensione dei contenuti dell'immagine oltre i suoi limiti originali e la modifica degli sfondi.
  • Personalizza le immagini applicando stili specifici (pattern, texture o stili di artisti), concentrandoti su vari soggetti (ad esempio prodotti, persone o animali) o rispettando diversi controlli (ad esempio uno schizzo disegnato a mano, un'immagine con rilevamento dei contorni o una mesh del volto).

Inizializzazione del modello

Per utilizzare le funzionalità di editing di Imagen, specifica un modello Imagen che supporti l'editing delle immagini, come imgen-3.0-capability-001. versione del modello:

val imagenModel = Firebase.ai(backend = GenerativeBackend.vertexAI())
.imagenModel("imagen-3.0-capability-001")

Modifica basata su maschera

La modifica basata su maschera di Imagen consente di modificare le immagini definendo aree specifiche da manipolare per il modello. Questa funzionalità consente una serie di azioni, tra cui la creazione e l'applicazione di maschere, l'inserimento o la rimozione di oggetti e l'espansione dei contenuti delle immagini oltre i confini originali.

Creare una maschera

Per eseguire modifiche basate su maschera, ad esempio inserire o rimuovere oggetti, devi definire l'area che deve essere modificata dal modello, ovvero la maschera.

Per creare una maschera, puoi farla generare automaticamente al modello utilizzando ImagenBackgroundMask() o ImagenSemanticMask(), passando un ID classe.

Puoi anche disegnare manualmente la maschera sullo schermo generando una bitmap della maschera e convertendola in un ImagenRawMask. Utilizzando detectDragGestures e Canvas, puoi implementare un'interfaccia utente per il disegno di maschere con Jetpack Compose nella tua app nel seguente modo:

import androidx.compose.ui.graphics.Color as ComposeColor
[...]

@Composable
fun ImagenEditingMaskEditor(
    sourceBitmap: Bitmap,
    onMaskFinalized: (Bitmap) -> Unit,
) {

    val paths = remember { mutableStateListOf<Path>() }
    var currentPath by remember { mutableStateOf<Path?>(null) }
    var scale by remember { mutableFloatStateOf(1f) }
    var offsetX by remember { mutableFloatStateOf(0f) }
    var offsetY by remember { mutableFloatStateOf(0f) }

    Column(
        modifier = Modifier.fillMaxSize(),
    ) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .pointerInput(Unit) {
                    detectDragGestures(
                        onDragStart = { startOffset ->
                            val transformedStart = Offset(
                                (startOffset.x - offsetX) / scale,
                                (startOffset.y - offsetY) / scale,
                            )
                            currentPath = Path().apply { moveTo(transformedStart.x, transformedStart.y) }
                        },
                        onDrag = { change, _ ->
                            currentPath?.let {
                                val transformedChange = Offset(
                                    (change.position.x - offsetX) / scale,
                                    (change.position.y - offsetY) / scale,
                                )
                                it.lineTo(transformedChange.x, transformedChange.y)
                                currentPath = Path().apply { addPath(it) }
                            }
                            change.consume()
                        },
                        onDragEnd = {
                            currentPath?.let { paths.add(it) }
                            currentPath = null
                        },
                    )
                },
        ) {
            Image(
                bitmap = sourceBitmap.asImageBitmap(),
                contentDescription = null,
                modifier = Modifier.fillMaxSize(),
                contentScale = ContentScale.Fit,
            )
            Canvas(modifier = Modifier.fillMaxSize()) {
                val canvasWidth = size.width
                val canvasHeight = size.height
                val bitmapWidth = sourceBitmap.width.toFloat()
                val bitmapHeight = sourceBitmap.height.toFloat()
                scale = min(canvasWidth / bitmapWidth, canvasHeight / bitmapHeight)
                offsetX = (canvasWidth - bitmapWidth * scale) / 2
                offsetY = (canvasHeight - bitmapHeight * scale) / 2
                withTransform(
                    {
                        translate(left = offsetX, top = offsetY)
                        scale(scale, scale, pivot = Offset.Zero)
                    },
                ) {
                    val strokeWidth = 70f / scale
                    val stroke = Stroke(width = strokeWidth, cap = StrokeCap.Round, join = StrokeJoin.Round)
                    val pathColor = ComposeColor.White.copy(alpha = 0.5f)
                    paths.forEach { path ->
                        drawPath(path = path, color = pathColor, style = stroke)
                    }
                    currentPath?.let { path ->
                        drawPath(path = path, color = pathColor, style = stroke)
                    }
                }
            }
        }
        Button(
            onClick = {
                val maskBitmap = createMask(sourceBitmap, paths)
                onMaskFinalized(maskBitmap)
            },
        ) {
            Text("Save mask")
        }
    }
}

Puoi quindi creare la bitmap della maschera disegnando i tracciati sul canvas:

import android.graphics.Color as AndroidColor
import android.graphics.Paint
[...]

private fun createMaskBitmap(
    sourceBitmap: Bitmap,
    paths: SnapshotStateList<Path>,
): Bitmap {
    val maskBitmap = createBitmap(sourceBitmap.width, sourceBitmap.height)
    val canvas = android.graphics.Canvas(maskBitmap)
    val paint = Paint().apply {
        color = AndroidColor.RED
        strokeWidth = 70f
        style = Paint.Style.STROKE
        strokeCap = Paint.Cap.ROUND
        strokeJoin = Paint.Join.ROUND
        isAntiAlias = true
    }
    paths.forEach { path -> canvas.drawPath(path.asAndroidPath(), paint) }

    return maskBitmap
}

Assicurati che la maschera abbia le stesse dimensioni dell'immagine di origine. Per saperne di più, consulta i campioni del catalogo Imagen AI.

Inserire oggetti

Puoi inserire un nuovo oggetto o contenuto in un'immagine esistente, operazione chiamata anche inpainting. Il modello genererà e inserirà i nuovi contenuti nell'area mascherata specificata.

Per farlo, utilizza la funzione editImage(). Dovrai fornire l'immagine originale, una maschera e un prompt di testo che descriva i contenuti che vuoi inserire. Inoltre, passa un oggetto ImagenEditingConfig, assicurandoti che la proprietà editMode sia impostata su ImagenEditMode.INPAINT_INSERTION.

suspend fun insertFlowersIntoImage(
  model: ImagenModel,
  originalImage: Bitmap,
  mask: ImagenMaskReference): ImagenGenerationResponse<ImagenInlineImage> {
    val prompt = "a vase of flowers"

    // Pass the original image, a mask, the prompt, and an editing configuration.
    val editedImage = model.editImage(
        sources = listOf(
            ImagenRawImage(originalImage),
            mask),
        prompt = prompt,
        // Define the editing configuration for inpainting and insertion.
        config = ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)
    )

    return editedImage
}

Rimuovere oggetti

L'inpainting ti consente di rimuovere gli oggetti indesiderati da un'immagine. Per farlo, utilizza la funzione editImage. Dovrai fornire l'immagine originale e una maschera che evidenzi l'oggetto che vuoi rimuovere. Se vuoi, puoi includere un prompt di testo per descrivere l'oggetto, il che può aiutare il modello a identificarlo con precisione. Inoltre, devi impostare editMode all'interno di ImagenEditingConfig su ImagenEditMode.INPAINT_REMOVAL.

suspend fun removeBallFromImage(model: ImagenModel, originalImage: Bitmap, mask: ImagenMaskReference): ImagenGenerationResponse<ImagenInlineImage> {

    // Optional: provide the prompt describing the content to be removed.
    val prompt = "a ball"

    // Pass the original image, a mask, the prompt, and an editing configuration.
    val editedImage = model.editImage(
        sources = listOf(
            ImagenRawImage(originalImage),
            mask
        ),
        prompt = prompt,
        // Define the editing configuration for inpainting and removal.
        config = ImagenEditingConfig(ImagenEditMode.INPAINT_REMOVAL)
    )

    return editedImage
}

Espandi i contenuti dell'immagine

Puoi espandere un'immagine oltre i suoi limiti originali, una tecnica nota come outpainting, utilizzando la funzione outpaintImage(). Questa funzione richiede l'immagine originale e il Dimensions necessario dell'immagine espansa. Se vuoi, puoi includere un prompt descrittivo per l'espansione e specificare il ImagenImagePlacement dell'immagine originale all'interno della nuova immagine generata:

suspend fun expandImage(originalImage: Bitmap, imagenModel: ImagenModel): ImagenGenerationResponse<ImagenInlineImage> {

    // Optionally describe what should appear in the expanded area.
    val prompt = "a sprawling sandy beach next to the ocean"

    val editedImage = model.outpaintImage(
        ImagenRawImage(originalImage),
        Dimension(width, height),
        prompt = prompt,
        newPosition = ImagenImagePlacement.LEFT_CENTER
    )


    return editedImage
}

Sostituire lo sfondo

Puoi sostituire lo sfondo di un'immagine mantenendo il soggetto in primo piano. Per farlo, utilizza la funzione editImage. Passa l'immagine originale, un oggetto ImagenBackgroundMask (contenente un prompt di testo per il nuovo sfondo) e un ImagenEditingConfig con la proprietà editMode impostata su ImagenEditMode.INPAINT_INSERTION.

suspend fun replaceBackground(model: ImagenModel, originalImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {
    // Provide the prompt describing the new background.
    val prompt = "space background"

    // Pass the original image, a mask, the prompt, and an editing configuration.
    val editedImage = model.editImage(
        sources = listOf(
            ImagenRawImage(originalImage),
            ImagenBackgroundMask(),
        ),
        prompt = prompt,
        config = ImagenEditingConfig(ImagenEditMode.INPAINT_INSERTION)
    )

    return editedImage
}

Personalizzazione

Puoi utilizzare la funzionalità di personalizzazione di Imagen per generare o modificare immagini basate su immagini di riferimento che specificano un soggetto, un controllo o uno stile. Ciò si ottiene fornendo un prompt di testo insieme a una o più immagini di riferimento per guidare il modello.

Personalizzare in base a un argomento

Puoi generare nuove immagini di un soggetto specifico a partire da un'immagine di riferimento (ad esempio, un prodotto, una persona o un animale). È sufficiente fornire un prompt di testo e almeno un'immagine di riferimento del soggetto. Ad esempio, potresti caricare una foto del tuo animale domestico e generare una nuova immagine in un ambiente completamente diverso.

Per farlo, definisci il riferimento dell'oggetto utilizzando ImagenSubjectReference e poi trasferiscilo a editImage insieme al prompt. Inoltre, includi un ImagenEditingConfig che specifica il numero di editSteps; un valore di editSteps più alto generalmente porta a risultati di qualità migliore:

suspend fun customizeCatImage(model: ImagenModel, referenceCatImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {

    // Define the subject reference using the reference image.
    val subjectReference = ImagenSubjectReference(
        image = referenceCatImage,
        referenceID = 1,
        description = "cat",
        subjectType = ImagenSubjectReferenceType.ANIMAL
    )

    // Provide a prompt that describes the final image.
    // The "[1]" links the prompt to the subject reference with ID 1.
    val prompt = "A cat[1] flying through outer space"

    // Use the editImage API to perform the subject customization.
    val editedImage = model.editImage(
        references = listOf(subjectReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50 // Number of editing steps, a higher value can improve quality
        )
    )

    return editedImage
}

Personalizzare in base a un controllo

Questa tecnica genera una nuova immagine basata su un'immagine di riferimento di controllo, come uno schizzo disegnato a mano ("scarabocchio"), un'immagine con rilevamento dei contorni di Canny o una mesh del volto. Il modello utilizza l'immagine di controllo come guida strutturale per il layout e la composizione della nuova immagine, mentre il prompt di testo fornisce dettagli come colore e texture.

Definisci un riferimento di controllo con ImagenControlReference e fornisci a editImage insieme a un prompt e ImagenEditingConfig con il numero di editSteps (un valore più alto può migliorare la qualità):

suspend fun customizeCatImageByControl(model: ImagenModel, referenceCatImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {
 
   // Define the subject reference using the reference image.
    val controlReference = ImagenControlReference(
        image = referenceImage,
        referenceID = 1,
        controlType = CONTROL_TYPE_SCRIBBLE
    )

    val prompt = "A cat flying through outer space arranged like the scribble map[1]"

    val editedImage = model.editImage(
        references = listOf(controlReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50
        )
    )

    return editedImage
}

Personalizzare in base a uno stile

Puoi generare o modificare un'immagine in modo che corrisponda a uno stile specifico di un'immagine di riferimento, ad esempio il suo pattern, la sua texture o il suo design. Il modello utilizza l'immagine di riferimento per comprendere l'estetica richiesta e la applica alla nuova immagine descritta nel prompt di testo. Ad esempio, potresti generare l'immagine di un gatto nello stile di un famoso dipinto fornendo un'immagine di quel dipinto.

Definisci un riferimento di stile con ImagenStyleReference e fornisci editImage insieme a un prompt e ImagenEditingConfig con il numero di editSteps (un valore più alto può migliorare la qualità):

suspend fun customizeImageByStyle(model: ImagenModel, referenceVanGoghImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {

    // Define the style reference using the reference image.
    val styleReference = ImagenStyleReference(
        image = referenceVanGoghImage,
        referenceID = 1,
        description = "Van Gogh style"
    )

    // Provide a prompt that describes the final image.
    // The "1" links the prompt to the style reference with ID 1.
    val prompt = "A cat flying through outer space, in the Van Gogh style[1]"

    // Use the editImage API to perform the style customization.
    val editedImage = model.editImage(
        references = listOf(styleReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50 // Number of editing steps, a higher value can improve quality
        )
    )

    return editedImage 
}

Passaggi successivi