Tạo hình ảnh bằng Imagen

Imagen là một mô hình tạo hình ảnh. Bạn có thể dùng tính năng này để tạo hình đại diện tuỳ chỉnh cho hồ sơ người dùng hoặc để tích hợp các thành phần hình ảnh được cá nhân hoá vào luồng màn hình hiện có nhằm tăng mức độ tương tác của người dùng.

Bạn có thể truy cập vào các mô hình Imagen từ ứng dụng Android của mình bằng SDK Firebase AI Logic. Bạn có thể sử dụng các mô hình Imagen bằng cả nhà cung cấp API Firebase AI Logic: Gemini Developer API (được đề xuất cho hầu hết các nhà phát triển) và Vertex AI.

Sơ đồ minh hoạ cấu trúc tích hợp Firebase AI Logic để truy cập vào Gemini Developer API. Ứng dụng Android sử dụng Firebase Android SDK để kết nối với Firebase. Sau đó, Firebase tương tác với Gemini Developer API, API này truy cập vào Gemini Pro và Flash trong đám mây.
Hình 1. Truy cập vào các mô hình Imagen bằng Firebase AI Logic.

Thử nghiệm với câu lệnh

Việc tạo ra câu lệnh lý tưởng thường mất nhiều lần thử. Bạn có thể thử nghiệm với câu lệnh tạo hình ảnh trong AI Studio của Google, một IDE để thiết kế và tạo nguyên mẫu câu lệnh. Để biết các mẹo về cách cải thiện câu lệnh, hãy xem hướng dẫn về câu lệnh và thuộc tính hình ảnh.

Ảnh chụp màn hình giao diện Google AI Studio, cho thấy 4 hình ảnh được tạo về một con khủng long bạo chúa có ba lô màu xanh dương trong một khu rừng thời tiền sử.
Hình 2. Google AI Studio có thể giúp bạn tinh chỉnh câu lệnh tạo hình ảnh.

Thiết lập dự án Firebase và kết nối ứng dụng của bạn

Làm theo các bước trong tài liệu của Firebase để thêm Firebase vào dự án Android.

Thêm phần phụ thuộc vào Gradle

Thêm các phần phụ thuộc sau vào tệp 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")
}

Tạo hình ảnh

Để tạo hình ảnh trong ứng dụng Android, hãy bắt đầu bằng cách khởi tạo một ImagenModel bằng cấu hình không bắt buộc.

Bạn có thể sử dụng tham số generationConfig để xác định câu lệnh phủ định, số lượng hình ảnh, tỷ lệ khung hình của hình ảnh đầu ra, định dạng hình ảnh và thêm hình mờ. Bạn có thể sử dụng tham số safetySettings để định cấu hình bộ lọc an toàn và bộ lọc người.

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

Sau khi tạo thực thể ImagenModel, bạn có thể tạo hình ảnh bằng cách gọi 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();
}

Chỉnh sửa hình ảnh bằng Imagen

SDK Firebase AI Logic cung cấp các chức năng chỉnh sửa ảnh nâng cao thông qua mô hình Imagen, cho phép bạn:

  • Chỉnh sửa hình ảnh dựa trên mặt nạ, bao gồm các thao tác như chèn hoặc xoá đối tượng, mở rộng nội dung hình ảnh ra ngoài ranh giới ban đầu và thay đổi nền.
  • Tuỳ chỉnh hình ảnh thông qua việc áp dụng các kiểu cụ thể (mẫu, kết cấu hoặc kiểu của nghệ sĩ), bằng cách tập trung vào nhiều đối tượng (chẳng hạn như sản phẩm, người hoặc động vật) hoặc bằng cách tuân thủ các chế độ kiểm soát khác nhau (chẳng hạn như bản phác thảo vẽ tay, hình ảnh có đường viền rõ ràng hoặc lưới khuôn mặt).

Khởi chạy mô hình

Để sử dụng các tính năng chỉnh sửa của Imagen, hãy chỉ định một mô hình Imagen hỗ trợ chỉnh sửa hình ảnh, chẳng hạn như imgen-3.0-capability-001. phiên bản của mô hình:

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

Chỉnh sửa dựa trên mặt nạ

Tính năng chỉnh sửa dựa trên mặt nạ của Imagen cho phép sửa đổi hình ảnh bằng cách xác định các khu vực cụ thể để mô hình thao tác. Tính năng này cho phép thực hiện nhiều thao tác, bao gồm tạo và áp dụng mặt nạ, chèn hoặc xoá đối tượng và mở rộng nội dung hình ảnh vượt ra ngoài ranh giới ban đầu.

Tạo mặt nạ

Để thực hiện thao tác chỉnh sửa dựa trên mặt nạ (chẳng hạn như chèn hoặc xoá các đối tượng), bạn cần xác định vùng mà mô hình cần chỉnh sửa, đó là mặt nạ.

Để tạo một mặt nạ, bạn có thể để mô hình tự động tạo mặt nạ đó bằng cách sử dụng ImagenBackgroundMask() hoặc ImagenSemanticMask(), truyền một mã lớp.

Bạn cũng có thể vẽ mặt nạ theo cách thủ công trên màn hình bằng cách tạo một bitmap mặt nạ và chuyển đổi bitmap đó thành một ImagenRawMask. Bằng cách sử dụng detectDragGesturesCanvas, bạn có thể triển khai giao diện người dùng vẽ mặt nạ bằng Jetpack Compose trong ứng dụng của mình như sau:

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

Sau đó, bạn có thể tạo bitmap mặt nạ bằng cách vẽ các đường dẫn trên 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
}

Đảm bảo mặt nạ có cùng kích thước với hình ảnh nguồn. Hãy xem Mẫu danh mục AI của Imagen để biết thêm thông tin chi tiết.

Chèn đối tượng

Bạn có thể chèn một đối tượng hoặc nội dung mới vào hình ảnh hiện có, còn được gọi là tô màu nội dung bị thiếu. Mô hình sẽ tạo và chèn nội dung mới vào vùng bị che được chỉ định.

Để thực hiện việc này, hãy sử dụng hàm editImage(). Bạn cần cung cấp hình ảnh gốc, mặt nạ và câu lệnh dạng văn bản mô tả nội dung bạn muốn chèn. Ngoài ra, hãy truyền một đối tượng ImagenEditingConfig, đảm bảo thuộc tính editMode của đối tượng đó được đặt thành 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
}

Xoá đối tượng

Tính năng chỉnh sửa ảnh giúp bạn loại bỏ các đối tượng không mong muốn khỏi hình ảnh. Để thực hiện việc này, hãy sử dụng hàm editImage. Bạn sẽ cần cung cấp hình ảnh gốc và một mặt nạ làm nổi bật đối tượng mà bạn muốn xoá. Nếu muốn, bạn có thể thêm một câu lệnh dạng văn bản để mô tả đối tượng. Câu lệnh này có thể hỗ trợ mô hình xác định chính xác đối tượng. Ngoài ra, bạn phải đặt editMode trong ImagenEditingConfig thành 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
}

Mở rộng nội dung hình ảnh

Bạn có thể mở rộng hình ảnh vượt ra ngoài ranh giới ban đầu (còn gọi là vẽ mở rộng) bằng cách sử dụng hàm outpaintImage(). Hàm này yêu cầu hình ảnh gốc và Dimensions cần thiết của hình ảnh mở rộng. Nếu muốn, bạn có thể thêm một câu lệnh mô tả cho phần mở rộng và chỉ định ImagenImagePlacement của hình ảnh gốc trong hình ảnh mới được tạo:

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
}

Thay thế nền

Bạn có thể thay thế nền của một hình ảnh trong khi vẫn giữ nguyên đối tượng ở tiền cảnh. Để thực hiện việc này, hãy sử dụng hàm editImage. Truyền hình ảnh gốc, một đối tượng ImagenBackgroundMask (chứa câu lệnh dạng văn bản cho nền mới) và một ImagenEditingConfig có thuộc tính editMode được đặt thành 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
}

Tuỳ chỉnh

Bạn có thể sử dụng khả năng tuỳ chỉnh của Imagen để tạo hoặc chỉnh sửa hình ảnh dựa trên hình ảnh tham khảo chỉ định một chủ thể, chế độ kiểm soát hoặc kiểu. Việc này được thực hiện bằng cách cung cấp một câu lệnh dạng văn bản cùng với một hoặc nhiều hình ảnh tham khảo để hướng dẫn mô hình.

Tuỳ chỉnh dựa trên một chủ đề

Bạn có thể tạo hình ảnh mới về một chủ thể cụ thể từ một hình ảnh tham khảo (ví dụ: sản phẩm, người hoặc động vật). Bạn chỉ cần cung cấp một câu lệnh dạng văn bản và ít nhất một hình ảnh tham khảo về chủ đề. Ví dụ: bạn có thể tải ảnh thú cưng của mình lên và tạo một hình ảnh mới cho thú cưng đó trong một môi trường hoàn toàn khác.

Để làm vậy, hãy xác định tham chiếu chủ đề bằng ImagenSubjectReference rồi truyền tham chiếu đó đến editImage cùng với câu lệnh của bạn. Ngoài ra, hãy thêm một ImagenEditingConfig chỉ định số lượng editSteps; giá trị editSteps càng cao thì kết quả thường có chất lượng càng tốt:

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
}

Tuỳ chỉnh dựa trên một chế độ kiểm soát

Kỹ thuật này tạo ra một hình ảnh mới dựa trên hình ảnh tham chiếu kiểm soát, chẳng hạn như bản phác thảo vẽ tay ("vẽ nguệch ngoạc"), hình ảnh cạnh Canny hoặc lưới khuôn mặt. Mô hình này sử dụng hình ảnh kiểm soát làm hướng dẫn về cấu trúc cho bố cục và thành phần của hình ảnh mới, trong khi câu lệnh văn bản cung cấp các thông tin chi tiết như màu sắc và hoạ tiết.

Xác định một tham chiếu kiểm soát bằng ImagenControlReference và cung cấp tham chiếu đó cho editImage cùng với một câu lệnh và ImagenEditingConfig với số lượng editSteps (giá trị càng cao thì chất lượng càng có thể cải thiện):

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
}

Tuỳ chỉnh dựa trên một kiểu

Bạn có thể tạo hoặc chỉnh sửa hình ảnh để có phong cách cụ thể từ một hình ảnh tham khảo, chẳng hạn như mẫu, kết cấu hoặc thiết kế của hình ảnh đó. Mô hình này sử dụng hình ảnh tham khảo để hiểu được tính thẩm mỹ cần thiết và áp dụng tính thẩm mỹ đó cho hình ảnh mới được mô tả trong câu lệnh bằng văn bản. Ví dụ: bạn có thể tạo hình ảnh một chú mèo theo phong cách của một bức tranh nổi tiếng bằng cách cung cấp hình ảnh của bức tranh đó.

Xác định một tham chiếu kiểu bằng ImagenStyleReference và cung cấp tham chiếu đó cho editImage cùng với một câu lệnh và ImagenEditingConfig với số lượng editSteps (giá trị càng cao thì chất lượng càng có thể cải thiện):

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 
}

Các bước tiếp theo