สร้างรูปภาพด้วย 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
      แสดงรูปภาพที่สร้างขึ้น 4 รูปของทีเร็กซ์สะพายเป้สีน้ำเงินใน
      ป่าดึกดำบรรพ์
รูปที่ 2 Google AI Studio ช่วยปรับแต่งพรอมต์การสร้างรูปภาพได้

ตั้งค่าโปรเจ็กต์ Firebase และเชื่อมต่อแอป

ทําตามขั้นตอนในเอกสารประกอบของ Firebase เพื่อเพิ่ม Firebase ลงในโปรเจ็กต์ Android

เพิ่มการอ้างอิง Gradle

เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในไฟล์ 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 เพื่อกำหนดค่า ตัวกรองความปลอดภัยและตัวกรองบุคคลได้

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

เมื่อสร้างอินสแตนซ์ของ ImagenModel แล้ว คุณจะสร้างรูปภาพได้โดยเรียกใช้ 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();
}

แก้ไขรูปภาพด้วย Imagen

SDK ตรรกะ AI ของ Firebase มีความสามารถในการแต่งภาพขั้นสูงผ่านโมเดล Imagen ซึ่งช่วยให้คุณทำสิ่งต่อไปนี้ได้

  • แก้ไขรูปภาพตามมาสก์ ซึ่งรวมถึงการดำเนินการต่างๆ เช่น การแทรก หรือนำวัตถุออก การขยายเนื้อหารูปภาพให้เกินขอบเขตเดิม และการเปลี่ยนพื้นหลัง
  • ปรับแต่งรูปภาพโดยใช้สไตล์ที่เฉพาะเจาะจง (ลวดลาย พื้นผิว หรือสไตล์ของศิลปิน) โดยมุ่งเน้นที่วัตถุต่างๆ (เช่น ผลิตภัณฑ์ ผู้คน หรือสัตว์) หรือโดยใช้การควบคุมที่แตกต่างกัน (เช่น ภาพร่างที่วาดด้วยมือ รูปภาพขอบที่คมชัด หรือตาข่ายใบหน้า)

การเริ่มต้นโมเดล

หากต้องการใช้ฟีเจอร์การแก้ไข Imagen ให้ระบุโมเดล Imagen ที่รองรับการแก้ไขรูปภาพ เช่น imgen-3.0-capability-001 เวอร์ชันของโมเดล

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

การแก้ไขโดยใช้มาสก์

การแก้ไขตามมาสก์ของ Imagen ช่วยให้แก้ไขรูปภาพได้โดยการกำหนด พื้นที่ที่เฉพาะเจาะจงเพื่อให้โมเดลจัดการ ความสามารถนี้ช่วยให้ทำสิ่งต่างๆ ได้มากมาย รวมถึงการสร้างและใช้มาสก์ การแทรกหรือนำวัตถุออก และการขยายเนื้อหาของรูปภาพให้เกินขอบเขตเดิม

สร้างมาสก์

หากต้องการทำการแก้ไขโดยใช้มาสก์ เช่น การแทรกหรือนำออบเจ็กต์ออก คุณจะต้อง กำหนดพื้นที่ที่โมเดลต้องแก้ไข ซึ่งก็คือมาสก์

หากต้องการสร้างมาสก์ คุณสามารถให้โมเดลสร้างมาสก์โดยอัตโนมัติได้โดยใช้ ImagenBackgroundMask() หรือ ImagenSemanticMask() โดยส่ง class ID

นอกจากนี้ คุณยังวาดมาสก์บนหน้าจอด้วยตนเองได้โดยการสร้างบิตแมปมาสก์และ แปลงเป็น 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")
        }
    }
}

จากนั้นคุณจะสร้างบิตแมปมาสก์ได้โดยวาดเส้นทางบน 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
}

ตรวจสอบว่ามาสก์มีขนาดเท่ากับรูปภาพต้นฉบับ ดูรายละเอียดเพิ่มเติมได้ที่ตัวอย่างแคตตาล็อกของ 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
}

นำวัตถุออก

การลบสิ่งที่ไม่ต้องการออกจากภาพช่วยให้คุณนำวัตถุที่ไม่ต้องการออกจากรูปภาพได้ โดยใช้ฟังก์ชัน 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
}

ขยายเนื้อหารูปภาพ

คุณขยายรูปภาพให้เกินขอบเขตเดิมได้ ซึ่งเรียกว่าการวาดภาพนอกกรอบ โดยใช้ฟังก์ชัน 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 เพื่อสร้างหรือแก้ไข รูปภาพตามรูปภาพอ้างอิงที่ระบุวัตถุ การควบคุม หรือสไตล์ได้ ซึ่งทำได้โดยการระบุพรอมต์ข้อความพร้อมกับรูปภาพอ้างอิงอย่างน้อย 1 รูปเพื่อเป็นแนวทางให้โมเดล

ปรับแต่งตามเรื่อง

คุณสร้างรูปภาพใหม่ของวัตถุที่เฉพาะเจาะจงจากรูปภาพอ้างอิงได้ (เช่น ผลิตภัณฑ์ บุคคล หรือสัตว์) เพียงระบุพรอมต์ข้อความและรูปภาพอ้างอิงของวัตถุอย่างน้อย 1 รูป เช่น คุณสามารถอัปโหลด รูปภาพสัตว์เลี้ยงและสร้างรูปภาพใหม่ของสัตว์เลี้ยงใน สภาพแวดล้อมที่แตกต่างไปจากเดิมโดยสิ้นเชิง

โดยกำหนดการอ้างอิงเรื่องโดยใช้ 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
}

ปรับแต่งตามการควบคุม

เทคนิคนี้จะสร้างรูปภาพใหม่โดยอิงตามรูปภาพอ้างอิงควบคุม เช่น ภาพร่างที่วาดด้วยมือ ("ขีดเขียน") รูปภาพขอบ Canny หรือตาข่ายใบหน้า โมเดลใช้รูปภาพควบคุมเป็นแนวทางโครงสร้างสำหรับเลย์เอาต์และองค์ประกอบของรูปภาพใหม่ ขณะที่พรอมต์ข้อความจะให้รายละเอียดต่างๆ เช่น สีและพื้นผิว

กำหนดการอ้างอิงการควบคุมด้วย 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 
}

ขั้นตอนถัดไป