إنشاء صور باستخدام Imagen

‫Imagen هو نموذج لإنشاء الصور. يمكن استخدامه لإنشاء أفاتارات مخصّصة لملفات تعريف المستخدمين أو لدمج مواد عرض مرئية مخصّصة في مسارات الشاشة الحالية لزيادة تفاعل المستخدمين.

يمكنك الوصول إلى نماذج Imagen من تطبيق Android باستخدام الـ Firebase AI Logic SDK. تتوفّر نماذج Imagen باستخدام أحد موفِّرَي واجهات برمجة التطبيقات من Google: Gemini Developer API (ننصح بها معظم المطوّرين) وAgent Platform Gemini API (المعروفة سابقًا باسم Vertex AI).

مخطّط يوضّح بنية تكامل Firebase AI Logic للوصول إلى Gemini Developer API يستخدم تطبيق Android حزمة تطوير البرامج (SDK) لنظام Android من Firebase للربط بمنصة Firebase. يتفاعل Firebase بعد ذلك مع
       Gemini Developer API، الذي يصل إلى Gemini Pro & Flash ضمن
       السحابة الإلكترونية.
الشكل 1. الوصول إلى نماذج Imagen باستخدام Firebase AI Logic

تجربة الطلبات

غالبًا ما يتطلب إنشاء الطلبات المثالية عدة محاولات. يمكنك تجربة طلبات الصور في Google AI Studio، وهو بيئة تطوير متكاملة لتصميم النماذج الأولية والطلبات. للحصول على نصائح حول كيفية تحسين طلباتك، يُرجى مراجعة الـ دليل الطلبات وسمات الصور.

لقطة شاشة لواجهة Google AI Studio تعرض أربع صور من إنشاء الذكاء الاصطناعي لدينا لديناصور T-Rex يحمل حقيبة ظهر زرقاء في غابة تعود إلى عصور ما قبل التاريخ.
الشكل 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.18.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.getInstance(GenerativeBackend.googleAI()).imagenModel(
                "imagen-4.0-generate-001",
                config,
                new ImagenSafetySettings(
                        ImagenSafetyFilterLevel.BLOCK_LOW_AND_ABOVE,
                        ImagenPersonFilterLevel.BLOCK_ALL))
);

بعد إنشاء مثيل من 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

ListenableFuture<ImagenGenerationResponse<ImagenInlineImage>> futureResponse =
        model.generateImages(
                "A hyper realistic picture of a t-rex with a blue bagpack in a prehistoric forest");

try {
    ImagenGenerationResponse<ImagenInlineImage> imageResponse = futureResponse.get();
    List<ImagenInlineImage> images = null;
    if (imageResponse != null) {
        images = imageResponse.getImages();
    }
    if (images != null && !images.isEmpty()) {
        ImagenInlineImage image = images.get(0);
        Bitmap bitmapImage = image.asBitmap();
        // Use bitmapImage
    }
} catch (ExecutionException | InterruptedException e) {
    e.printStackTrace();
}

تعديل الصور باستخدام Imagen

توفّر حزم Firebase AI Logic SDK إمكانات متقدّمة لتعديل الصور من خلال نموذج Imagen، ما يتيح لك ما يلي:

  • تعديل الصور استنادًا إلى الأقنعة، ويشمل ذلك إجراءات مثل إدراج العناصر أو إزالتها وتوسيع محتوى الصورة خارج حدودها الأصلية وتغيير الخلفيات.
  • تخصيص الصور من خلال تطبيق أنماط معيّنة (الأنماط أو الأنسجة أو أنماط الفنانين)، أو من خلال التركيز على مواضيع مختلفة (مثل المنتجات أو الأشخاص أو الحيوانات)، أو من خلال الالتزام بعناصر تحكّم مختلفة (مثل رسم تخطيطي مرسوم باليد أو صورة ذات حواف واضحة أو شبكة الوجه).

إعداد النموذج

لاستخدام ميزات تعديل Imagen، حدِّد نموذج Imagen يتيح تعديل الصور، مثل imagen-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 = createMaskBitmap(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 = Bitmap.createBitmap(sourceBitmap.width, sourceBitmap.height, Bitmap.Config.ARGB_8888)
    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(
        referenceImages = listOf(
            ImagenRawImage(originalImage.toImagenInlineImage()),
            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(
        referenceImages = listOf(
            ImagenRawImage(originalImage.toImagenInlineImage()),
            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 = imagenModel.outpaintImage(
        originalImage.toImagenInlineImage(),
        Dimensions(1024, 1024),
        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(
        referenceImages = listOf(
            ImagenRawImage(originalImage.toImagenInlineImage()),
            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.toImagenInlineImage(),
        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(
        referenceImages = 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, referenceImage: Bitmap): ImagenGenerationResponse<ImagenInlineImage> {

    // Define the subject reference using the reference image.
    val controlReference = ImagenControlReference(
        image = referenceImage.toImagenInlineImage(),
        referenceId = 1,
        type = ImagenControlType.SCRIBBLE,
    )

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

    val editedImage = model.editImage(
        referenceImages = 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.toImagenInlineImage(),
        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(
        referenceImages = listOf(styleReference),
        prompt = prompt,
        config = ImagenEditingConfig(
            editSteps = 50 // Number of editing steps, a higher value can improve quality
        ),
    )

    return editedImage
}

الخطوات التالية