Создавайте изображения с помощью Imagen

Imagen — это модель генерации изображений. Её можно использовать для создания индивидуальных аватаров для профилей пользователей или для интеграции персонализированных визуальных ресурсов в существующие экранные потоки для повышения вовлечённости пользователей.

Доступ к моделям Imagen из вашего Android-приложения осуществляется с помощью Firebase AI Logic SDK. Модели Imagen доступны через API обоих поставщиков Firebase AI Logic: Gemini Developer API (рекомендуется для большинства разработчиков) и Vertex AI.

Диаграмма, иллюстрирующая архитектуру интеграции Firebase AI Logic для доступа к Gemini Developer API. Приложение Android использует Firebase Android SDK для подключения к Firebase. Затем Firebase взаимодействует с Gemini Developer API, который обеспечивает доступ к Gemini Pro и Flash в облаке.
Рисунок 1. Доступ к моделям Imagen с помощью Firebase AI Logic.

Экспериментируйте с подсказками

Создание идеальных подсказок часто требует нескольких попыток. Вы можете поэкспериментировать с изображениями в подсказках в Google AI Studio — IDE для дизайна и прототипирования подсказок. Советы по улучшению подсказок см. в руководстве по атрибутам подсказок и изображений .

Скриншот интерфейса Google AI Studio, на котором показаны четыре сгенерированных изображения тираннозавра рекса с синим рюкзаком в доисторическом лесу.
Рисунок 2. Google AI Studio поможет вам усовершенствовать подсказки по генерации изображений.

Настройте проект Firebase и подключите свое приложение

Следуйте инструкциям в документации Firebase, чтобы добавить Firebase в свой проект Android .

Добавьте зависимость Gradle

Добавьте следующие зависимости в файл 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")
}

Создать изображение

Чтобы создать изображение в приложении Android, начните с создания экземпляра ImagenModel с дополнительной конфигурацией.

Параметр generationConfig позволяет задать отрицательное приглашение, количество изображений, соотношение сторон выходного изображения, формат изображения и добавить водяной знак. Параметр safetySettings позволяет настроить фильтры безопасности и по лицам.

Котлин

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
    )
)

Ява

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())
);

После создания экземпляра ImagenModel вы можете генерировать изображения, вызывая generateImages :

Котлин

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()

Ява

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();
}

Редактируйте изображения с помощью Imagen

Пакеты Firebase AI Logic SDK предлагают расширенные возможности редактирования изображений с помощью модели Imagen, позволяя вам:

  • Редактирование изображений на основе масок , которое включает такие действия, как вставка или удаление объектов, расширение содержимого изображения за пределы исходных границ и изменение фона.
  • Настраивайте изображения , применяя определенные стили (узоры, текстуры или стили художника), фокусируясь на различных объектах (например, продуктах, людях или животных) или придерживаясь различных элементов управления (например, нарисованного от руки эскиза, изображения с аккуратными краями или сетки лица).

Инициализация модели

Чтобы использовать функции редактирования Imagen, укажите модель Imagen, которая поддерживает редактирование изображений, например imgen-3.0-capability-001 . Версия модели:

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

Редактирование на основе масок

Редактирование на основе масок в Imagen позволяет изменять изображения, определяя области, которыми модель может манипулировать. Эта возможность позволяет выполнять ряд действий, включая создание и применение масок, вставку или удаление объектов, а также расширение содержимого изображения за пределы исходных границ.

Создать маску

Для выполнения редактирования на основе маски, например вставки или удаления объектов, необходимо определить область, которую необходимо отредактировать с помощью модели, — маску .

Чтобы создать маску, вы можете заставить модель автоматически сгенерировать ее с помощью ImagenBackgroundMask() или ImagenSemanticMask() , передав идентификатор класса .

Вы также можете вручную нарисовать маску на экране, создав растровое изображение маски и преобразовав его в ImagenRawMask . Используя detectDragGestures и Canvas , вы можете реализовать пользовательский интерфейс для рисования маски с помощью Jetpack Compose в своём приложении следующим образом:

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")
        }
    }
}

Затем вы можете создать растровое изображение маски, нарисовав пути на холсте:

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
}

Убедитесь, что маска имеет тот же размер, что и исходное изображение. Подробнее см. в примерах каталога Imagen AI.

Вставка объектов

Вы можете вставить новый объект или контент в существующее изображение, что также называется «вставкой» . Модель сгенерирует и вставит новый контент в указанную замаскированную область.

Для этого используйте функцию editImage() . Вам потребуется предоставить исходное изображение и маску. и текстовое приглашение с описанием содержимого, которое вы хотите вставить. Кроме того, передайте объект ImagenEditingConfig , убедившись, что его свойство editMode имеет значение 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
}

Удалить объекты

Функция Inpainting позволяет удалить нежелательные объекты с изображения. Для этого используйте функцию editImage . Вам потребуется предоставить исходное изображение и маску. который выделяет объект, который нужно удалить. При желании можно включить текстовую подсказку с описанием объекта, что поможет модели точно идентифицировать его. Кроме того, необходимо установить для editMode в ImagenEditingConfig значение 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
}

Развернуть содержимое изображения

Вы можете расширить изображение за пределы его исходных границ (это называется «outpainting» ), используя функцию outpaintImage() . Эта функция принимает исходное изображение и необходимые Dimensions расширенного изображения. При желании вы можете включить подсказку с описанием расширения и указать расположение ImagenImagePlacement исходного изображения в новом сгенерированном изображении:

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
}

Заменить фон

Вы можете заменить фон изображения, сохранив объект переднего плана. Для этого используйте функцию editImage . Передайте исходное изображение, объект ImagenBackgroundMask (содержащий текстовую подсказку для нового фона) и ImagenEditingConfig со свойством editMode , установленным в значение 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
}

Настройка

Вы можете использовать возможности настройки Imagen для создания или редактирования изображений на основе референсных изображений, определяющих тему, элемент управления или стиль. Это достигается путем предоставления текстовой подсказки вместе с одним или несколькими референсными изображениями для управления моделью.

Настроить на основе темы

Вы можете создавать новые изображения конкретного объекта на основе исходного изображения (например, продукта, человека или животного). Просто введите текстовую подсказку и добавьте хотя бы одно исходное изображение объекта. Например, вы можете загрузить фотографию своего питомца и создать его новое изображение в совершенно иной обстановке.

Для этого определите ссылку на объект с помощью ImagenSubjectReference и передайте её в editImage вместе с подсказкой. Кроме того, включите ImagenEditingConfig , который определяет количество editSteps ; чем больше значение editSteps , тем выше качество результатов:

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
}

Настроить на основе элемента управления

Этот метод генерирует новое изображение на основе контрольного изображения , например, наброска от руки («каракули»), изображения контура Кэнни или сетки лица. Модель использует контрольное изображение в качестве структурного ориентира для компоновки и композиции нового изображения, а текстовая подсказка предоставляет такие детали, как цвет и текстура.

Определите ссылку на элемент управления с помощью ImagenControlReference и предоставьте ее editImage вместе с подсказкой и ImagenEditingConfig с числом editSteps (более высокое значение может улучшить качество):

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
}

Настроить на основе стиля

Вы можете создать или отредактировать изображение, подобрав его к определённому стилю , например, к узору, текстуре или дизайну. Модель использует изображение-образец для определения необходимой эстетики и применяет её к новому изображению, описанному в текстовой подсказке. Например, вы можете создать изображение кошки в стиле известной картины, предоставив изображение этой картины.

Определите ссылку на стиль с помощью ImagenStyleReference и предоставьте ее editImage вместе с подсказкой и ImagenEditingConfig с количеством editSteps (более высокое значение может улучшить качество):

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 
}

Следующие шаги