Nano Banana多模态 RAG

Nano Banana 现在在社交媒体上火了,而且绝对有它的理由!你可能已经看过它生成的图像,甚至亲自尝试过。这是一款最新的图像生成模型,能够以惊人的精度和速度将纯文本转换为收藏级的手办照片。

输入类似“交换 Elon 的帽子和裙子”之类的内容,大约 16 秒后,你就能得到照片级逼真的效果:衬衫塞好,颜色混合,配饰到位——无需手动编辑,毫无延迟。

我也忍不住尝试了一下。我的提示是:

“使用 Nano Banana 模型,以逼真的风格和环境,创建一个 1/7 比例的插画人物商业化手办。将手办放在电脑桌上,使用圆形透明亚克力底座,底座上不带任何文字。在电脑屏幕上,显示手办的 ZBrush 建模过程。在屏幕旁边,放置一个印有原图的万代风格玩具包装盒。”

最终效果让我惊叹不已——它看起来就像刚从展台上拿下来的量产原型。

毫不奇怪,各大团队已经找到了它的重要用例。我们的一位客户,一个以扭蛋和装扮游戏为特色的移动娱乐平台,正在开发一项功能,允许玩家上传照片,并立即用游戏内的配饰装扮他们的虚拟形象。一些电商品牌正在尝试“一次拍摄,永久重复使用”:拍摄一张基础模型图像,然后用人工智能生成无数种服装和发型变化,而不是在工作室里重复拍摄20次。

但问题在于——单靠图像生成并不能解决所有问题。这些系统还需要智能检索:能够从海量非结构化媒体库中即时找到合适的服装、道具和视觉元素。没有智能检索,生成模型就如同在黑暗中摸索。公司真正需要的是一个多模态RAG(检索增强生成)系统——其中Nano Banana负责创意,强大的矢量数据库负责处理上下文。

这就是 Milvus 的用武之地。作为一个开源矢量数据库,Milvus 可以索引和搜索数十亿个嵌入向量——图像、文本、音频等等。与 Nano Banana 搭配使用,它将成为企业级多模态 RAG 流程的骨干:搜索、匹配和生成。

在本博客的其余部分,我们将介绍如何结合 Nano Banana 和 Milvus 构建企业级多模态 RAG 系统,以及这种组合为何能够开启下一波 AI 应用浪潮。

1、构建文本转图像检索引擎

对于快速发展的消费品品牌、游戏工作室和媒体公司来说,AI 图像生成的瓶颈不在于模型,而在于数据本身的混乱。

他们的档案库充斥着非结构化数据,包括产品照片、角色素材、宣传视频和服装渲染图。当你需要查找“上一季春夏新品中的红色披风”时,祝你好运——传统的基于关键词的搜索根本无法解决这个问题。

解决方案?构建一个文本转图像检索系统。

具体方法如下:使用 CLIP 将文本和图像数据嵌入到向量中。将这些向量存储在 Milvus 中,这是一个专为相似性搜索而构建的开源向量数据库。然后,当用户输入描述(“带金色边饰的红色丝绸披风”)时,你就会访问数据库,并返回语义上最相似的前 3 张图片。

它速度快,可扩展,还能将你杂乱的媒体库变成一个结构化、可查询的素材库。

构建方法如下:

安装依赖项

# Install necessary packages
%pip install --upgrade pymilvus pillow matplotlib
%pip install git+https://github.com/openai/CLIP.git

导入必要的库:

import os
import clip
import torch
from PIL import Image
import matplotlib.pyplot as plt
from pymilvus import MilvusClient
from glob import glob
import math

print("All libraries imported successfully!")

初始化 Milvus 客户端:

milvus_client = MilvusClient(uri="http://localhost:19530",token="root:Miluvs")
print("Milvus client initialized successfully!")

加载 CLIP 模型:

# Load CLIP model
model_name = "ViT-B/32"
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load(model_name, device=device)
model.eval()

print(f"CLIP model '{model_name}' loaded successfully, running on device: {device}")
print(f"Model input resolution: {model.visual.input_resolution}")
print(f"Context length: {model.context_length}")
print(f"Vocabulary size: {model.vocab_size}")

输出结果:

CLIP model `ViT-B/32` loaded successfully, running on: cpu
 Model input resolution: 224
 Context length: 77
 Vocabulary size: 49,408

定义特征提取函数:

def encode_image(image_path):
    """Encode image into normalized feature vector"""
    try:
        image = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
        
        with torch.no_grad():
            image_features = model.encode_image(image)
            image_features /= image_features.norm(dim=-1, keepdim=True)  # Normalize
        
        return image_features.squeeze().cpu().tolist()
    except Exception as e:
        print(f"Error processing image {image_path}: {e}")
        return None
def encode_text(text):
    """Encode text into normalized feature vector"""
    text_tokens = clip.tokenize([text]).to(device)
    
    with torch.no_grad():
        text_features = model.encode_text(text_tokens)
        text_features /= text_features.norm(dim=-1, keepdim=True)  # Normalize
    
    return text_features.squeeze().cpu().tolist()

print("Feature extraction functions defined successfully!")

创建 Milvus 集合:

collection_name = "production_image_collection"
# If collection already exists, delete it
if milvus_client.has_collection(collection_name):
    milvus_client.drop_collection(collection_name)
    print(f"Existing collection deleted: {collection_name}")

# Create new collection
milvus_client.create_collection(
    collection_name=collection_name,
    dimension=512,  # CLIP ViT-B/32 embedding dimension
    auto_id=True,  # Auto-generate ID
    enable_dynamic_field=True,  # Enable dynamic fields
    metric_type="COSINE"  # Use cosine similarity
)

print(f"Collection '{collection_name}' created successfully!")
print(f"Collection info: {milvus_client.describe_collection(collection_name)}")

集合创建成功输出:

Existing collection deleted: production_image_collection
Collection 'production_image_collection' created successfully!
Collection info: {'collection_name': 'production_image_collection', 'auto_id': True, 'num_shards': 1, 'description': '', 'fields': [{'field_id': 100, 'name': 'id', 'description': '', 'type': <DataType.INT64: 5>, 'params': {}, 'auto_id': True, 'is_primary': True}, {'field_id': 101, 'name': 'vector', 'description': '', 'type': <DataType.FLOAT_VECTOR: 101>, 'params': {'dim': 512}}, {'field_id': 102, 'name': 'function': [], 'aliases': [], 'collection_id': 460508990706033544, 'consistency_level': 2, 'properties': {}, 'num_partitions': 1, 'enable_dynamic_field': True, 'created_timestamp': 460511723827494913, 'updated_timestamp': 460511723827494913}

处理并插入图片:

# Set image directory path
image_dir = "./production_image"
raw_data = []

# Get all supported image formats
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.JPEG', '*.JPG', '*.PNG']
image_paths = []

for ext in image_extensions:
    image_paths.extend(glob(os.path.join(image_dir, ext)))

print(f"Found {len(image_paths)} images in {image_dir}")

# Process images and generate embeddings
successful_count = 0
for i, image_path in enumerate(image_paths):
    print(f"Processing progress: {i+1}/{len(image_paths)} - {os.path.basename(image_path)}")
    
    image_embedding = encode_image(image_path)
    if image_embedding is not None:
        image_dict = {
            "vector": image_embedding,
            "filepath": image_path,
            "filename": os.path.basename(image_path)
        }
        raw_data.append(image_dict)
        successful_count += 1

print(f"Successfully processed {successful_count} images")

图片处理进度输出:

Found 50 images in ./production_image
Processing progress: 1/50 - download (5).jpeg
Processing progress: 2/50 - images (2).jpeg
Processing progress: 3/50 - download (23).jpeg
Processing progress: 4/50 - download.jpeg
Processing progress: 5/50 - images (14).jpeg
Processing progress: 6/50 - images (16).jpeg
…
Processing progress: 44/50 - download (10).jpeg
Processing progress: 45/50 - images (18).jpeg
Processing progress: 46/50 - download (9).jpeg
Processing progress: 47/50 - download (12).jpeg
Processing progress: 48/50 - images (1).jpeg
Processing progress: 49/50 - download.png
Processing progress: 50/50 - images.png
Successfully processed 50 images

将数据插入 Milvus:

# Insert data into Milvus
if raw_data:
    print("Inserting data into Milvus...")
    insert_result = milvus_client.insert(collection_name=collection_name, data=raw_data)
    
    print(f"Successfully inserted {insert_result['insert_count']} images into Milvus")
    print(f"Sample inserted IDs: {insert_result['ids'][:5]}...")  # Show first 5 IDs
else:
    print("No successfully processed image data to insert")

定义搜索和可视化函数:

def search_images_by_text(query_text, top_k=3):
    """Search images based on text query"""
    print(f"Search query: '{query_text}'")
    
    # Encode query text
    query_embedding = encode_text(query_text)
    
    # Search in Milvus
    search_results = milvus_client.search(
        collection_name=collection_name,
        data=[query_embedding],
        limit=top_k,
        output_fields=["filepath", "filename"]
    )
    
    return search_results[0]


def visualize_search_results(query_text, results):
    """Visualize search results"""
    num_images = len(results)
    
    if num_images == 0:
        print("No matching images found")
        return
    
    # Create subplots
    fig, axes = plt.subplots(1, num_images, figsize=(5*num_images, 5))
    fig.suptitle(f'Search Results: "{query_text}" (Top {num_images})', fontsize=16, fontweight='bold')
    
    # Handle single image case
    if num_images == 1:
        axes = [axes]
    
    # Display images
    for i, result in enumerate(results):
        try:
            img_path = result['entity']['filepath']
            filename = result['entity']['filename']
            score = result['distance']
            
            # Load and display image
            img = Image.open(img_path)
            axes[i].imshow(img)
            axes[i].set_title(f"{filename}\nSimilarity: {score:.3f}", fontsize=10)
            axes[i].axis('off')
            
            print(f"{i+1}. File: {filename}, Similarity score: {score:.4f}")
            
        except Exception as e:
            axes[i].text(0.5, 0.5, f'Error loading image\n{str(e)}',
                        ha='center', va='center', transform=axes[i].transAxes)
            axes[i].axis('off')
    
    plt.tight_layout()
    plt.show()

print("Search and visualization functions defined successfully!")

执行文本转图像搜索:

# Example search 1
query1 = "a golden watch"
results1 = search_images_by_text(query1, top_k=3)
visualize_search_results(query1, results1)

搜索查询执行输出:

Search query: 'a golden watch'
1. File: images (19).jpeg, Similarity score: 0.2934
2. File: download (26).jpeg, Similarity score: 0.3073
3. File: images (17).jpeg, Similarity score: 0.2717

2、使用 Nano-banana 创建品牌宣传图片

现在,我们的文本转图片搜索系统已经与 Milvus 兼容,接下来让我们集成 Nano-banana,根据检索到的素材生成新的宣传内容。

安装 Google SDK:

%pip install google-generativeai
%pip install requests
print("Google Generative AI SDK installation complete!")

配置 Gemini API:

import google.generativeai as genai
from PIL import Image
from io import BytesIO
genai.configure(api_key="<your_api_key>")

生成新图片:

prompt = (
    "An European male model wearing a suit, carrying a gold watch."
)

image = Image.open("/path/to/image/watch.jpg")

model = genai.GenerativeModel('gemini-2.5-flash-image-preview')
response = model.generate_content([prompt, image])

for part in response.candidates[0].content.parts:
    if part.text is not None:
        print(part.text)
    elif part.inline_data is not None:
        image = Image.open(BytesIO(part.inline_data.data))
        image.save("generated_image.png")
        image.show()

3、这对您的开发工作流程意味着什么

作为开发者,Milvus 与 Nano-banana 的集成从根本上改变了您处理内容生成项目的方式。您无需管理静态素材库或依赖昂贵的创意团队,而是拥有一个动态系统,可以实时检索和生成应用程序所需的内容。

考虑以下最近的客户场景:一个品牌推出了几款新产品,但选择完全跳过传统的照片拍摄流程。使用我们的集成系统,他们可以将现有的产品数据库与 Nano-banana 的生成功能相结合,立即生成宣传图片。

提示:一位模特在海滩上穿着这些产品

当您需要创建复杂、多变的内容时,真正的强大功能就显现出来了,而这通常需要摄影师、模特和布景设计师之间进行大量的协调。 Milvus 负责资源检索,Nano-banana 负责生成,您可以以编程方式创建符合您特定需求的复杂场景:

提示:一位模特正倚靠在一辆蓝色敞篷跑车上摆姿势。她身着露背连衣裙及配饰,佩戴着钻石项链和蓝色手表,脚踩高跟鞋,手捧拉布布吊坠。

对于游戏或收藏品领域的开发者来说,该系统为快速原型设计和概念验证开辟了全新的可能性。您无需花费数周时间进行 3D 建模才能确定一个概念是否可行,现在您可以生成包含包装、环境背景甚至制造流程的逼真产品可视化效果:

提示:使用 nano-banana 模型,以逼真的风格和环境创建插图中角色的 1/7 比例商业化模型。将模型放置在电脑桌上,使用圆形透明亚克力底座,底座上不带任何文字。在电脑屏幕上显示模型的 ZBrush 建模过程。在电脑屏幕旁边放一个印有原版图案的万代风格玩具包装盒。

4、结束语

从技术角度来看,Nano Banana 不仅仅是一个新奇事物——它已经具备了生产环境的可操作性,这对开发者来说至关重要。它最大的优势在于一致性和可控性,这意味着更少的边缘情况会渗透到你的应用程序逻辑中。同样重要的是,它能够处理微妙的这些细节往往会阻碍自动化流程:保持品牌色彩的一致性、生成符合物理规律的光照和反射,以及确保多种输出格式的视觉一致性。

真正的魔力在于将其与 Milvus 矢量数据库相结合。矢量数据库不仅仅是存储嵌入,它还能成为一个智能的资产管理器,能够呈现最相关的历史内容,指导新一代模型的生成。其结果是:更快的生成时间(因为模型拥有更佳的上下文)、更高的应用程序一致性,以及自动执行品牌或风格指南的能力。

简而言之,Milvus 将 Nano Banana 从一个创意玩具转变为一个可扩展的企业系统。

当然,没有哪个系统是完美无缺的。复杂的多步骤指令仍然可能出现问题,而光照物理特性有时会超出您的预期。我们见过的最可靠的解决方案是使用存储在 Milvus 中的参考图像来补充文本提示,这为模型提供了更丰富的基础、更可预测的结果和更短的迭代周期。通过此设置,您不仅可以试验多模式 RAG,还可以满怀信心地在生产中运行它。


原文链接:Nano Banana + Milvus: Turning Hype into Enterprise-Ready Multimodal RAG

汇智网翻译整理,转载请标明出处