You need to enable JavaScript to run this app.
导航
基于云搜索服务+Doubao-1.5-pro视觉理解模型构建图文检索应用
最近更新时间:2025.02.26 19:03:51首次发布时间:2025.02.26 19:03:51

在云搜索服务的 ML 服务中,支持创建 Image Embedding 模型,该模型是将图像转换为低维的向量表示,以便于在各种机器学习任务中进行处理、分析和比较。豆包的视觉理解模型通过分析和理解图像内容,将视觉信息转化为可识别、可处理的语义信息,从而实现对图像的智能化理解和应用。本文介绍基于云搜索服务 Image Embedding 模型和豆包视觉理解模型,快速搭建一套支持以图搜图、以文搜图、图文混合搜图的图文检索应用。

背景信息

图文检索在电商、广告、设计、搜索引擎等热门领域被广泛应用。常见的图文检索包括以图搜图和以文搜图,用户通过文字描述或上传图片就可以在海量的图片库中快速找到同款或者相似图片。
图片的文本描述和图片作为检索对象,分别对 image 和 text 进行特征提取,并在模型中对文本和图片建立相关联系,然后在海量图片数据库进行特征向量检索,返回与检索对象最相关的记录集合。其中文本描述由豆包视觉理解模型 Doubao-1.5-vision-pro-32k 生成,特征提取部分采用 CLIP 模型,向量检索采用火山引擎云搜索服务在海量图片特征中进行快速搜索。
Image

注意事项
  1. 目前仅 OpenSearch 2.9.0 版本实例支持 ML 服务。如何创建 OpenSearch 实例,具体操作,请参见创建实例
  2. 创建豆包视觉理解模型 Doubao-1.5-vision-pro-32k 的在线推理接入点来生成图片描述。如何创建在线推理接入点,具体操作,请参见创建在线推理接入点

准备工作

一、创建 TOS Bucket

在构建图文检索应用前,您需要提前创建 TOS Bucket,后续图片文件将被上传到该 TOS Bucket 中。

  1. 登录对象存储控制台
  2. 创建一个存储桶。
    在左侧导航栏单击桶列表,在页面左上方单击创建桶。具体操作,请参见创建存储桶
  3. 进入 Bucket,在 Bucket 的文件列表页面单击创建文件夹,设置文件夹名称,比如picture
    Image
  4. 整理信息。
    • 存储桶名称:doc-test
    • Endpoint 域名节点:tos-cn-beijing.ivolces.com
    • 图片存放路径:picture/image_temp

二、安装opensearch-remote-inference插件

本文通过在 Ingest Pipeline 中指定使用的 Image Embedding 模型,实现将指定字段(图片信息)转换为向量后嵌入回去。
在创建 Ingest Pipeline 前,需要提前在实例中安装opensearch-remote-inference插件,否则会报错。安装插件会触发重启集群,建议在业务低峰期操作。如果已经安装该插件,则无需执行以下步骤。

  1. 登录云搜索服务控制台
  2. 在顶部导航栏,选择目标项目和地域。
  3. 实例列表 v2页面,单击目标实例名称。
  4. 在实例详情的左侧导航栏选择实例管理 > 插件管理,然后单击系统内置插件
  5. 单击批量安装/升级/卸载,然后在opensearch-remote-inference插件的操作列中勾选安装并选择需要安装的插件版本,然后单击批量提交
    Image
  6. 批量提交对话框,查看提示信息,然后单击确定

    说明

    安装插件会触发重启集群,建议在业务低峰期操作。

    Image
  7. 查看插件安装进度。
    执行插件安装操作后,目标插件状态显示为安装中,当状态变为已安装,则表示插件安装成功。

部署图文检索应用

一、创建 ML 服务

OpenSearch 实例创建后,您可以在实例中创建 ML 服务,用于后续的模型创建和部署。
如何创建 ML 服务,请参见创建 ML 服务

说明

  • 目前支持 CPU 和 GPU 两种资源类型,本文选择使用 CPU 类型资源。如需了解各参数含义,请参见创建 ML 服务
  • 使用 CPU 资源类型时,建议选择1:4资源比例,能够保障模型服务应用的性能更佳。

二、创建并启动模型

您可以在 ML 服务中创建 Image Embedding 模型,也可以选择使用豆包的图文向量化模型。ML 服务中的 Image Embedding 模型类型支持 CLIP 和 ChineseCLIP 及其衍生模型,本文以 CLIP 模型为例。
云搜索服务为您提供了模型示例文件,您可以自行下载上传。

model.zip
未知大小

  1. 上传 Image Embedding 模型文件到 TOS Bucket。

    1. 下载模型文件。
    2. 登录对象存储控制台,将文件上传到存储桶。
    3. 假设模型文件完整的存放路径为tos://doc-test/ImageEmbedding/。具体操作,请参见创建存储桶上传文件
  2. 创建模型。

    1. 登录云搜索服务控制台,在左侧导航栏选择 ML 服务,查找并单击目标服务名称。
    2. 在左侧导航栏选择模型管理,然后选择我的模型页签,再单击创建模型
    3. 选择创建 Image Embedding 模型,然后设置模型相关参数,再单击确定
      Image

    参数

    说明

    模型名称

    自定义设置模型名称,比如ImageSearch-Demo。说明Image Embedding 模型具备以下两种能力:支持直接对图片和文字进行向量化,返回向量化结果。如何获取向量化,请参见调用 Embedding 模型。代理 OpenSearch,使用类似 OpenSearch 接口同时进行向量化和写入操作。

    模型类型

    此处需要选择创建 Image Embedding 模型。

    选择存储桶

    从下拉列表中选择存放模型文件的对象存储桶。

    模型文件

    选择对象存储文件,表示从选择的对象存储桶中选择模型文件,此时还需要设置对象存储文件夹,即从该文件夹中读取已经存在的模型文件。

    描述

    设置模型的描述语句。

  3. 创建并启动推理服务。
    您可以创建一个推理服务,关联新建的 Image Embedding 模型,并根据业务需求配置网络、资源和自定义参数等信息。

    1. 在左侧导航栏选择推理服务,然后单击创建推理服务
    2. 创建推理服务面板,配置推理服务相关参数,然后单击确定
      Image

    配置

    说明

    服务名称

    自定义设置推理服务的名称,比如“image-search”。首字符仅支持字母或下划线()。可包含字母、数字、特殊字符仅支持英文句号(.)、下划线()、短横线(-)、反斜杠(/),长度为 1~128 个字符。最多只能包含一个反斜杠(/)字符。

    选择模型

    从拉列表框选择新建的 Image Embedding 模型。

    可用区

    选择需要部署推理服务的可用区。如果 OpenSearch 实例是单可用区,那么您的推理服务也只有一个可用区。如果需要多可用区部署推理服务,则需要确保 OpenSearch 实例有多个可用区。如何为实例添加可用区,请参见添加可用区

    是否开启高可用区

    单可用区部署推理服务时,支持为推理服务开启高可用。
    开启高可用的推理服务,节点数量至少为 2。

    资源类型

    从下拉列表中选择推理服务使用的资源类型,您可以选择 CPU 类型和 GPU 两种资源类型。
    如果需要使用的资源类型还未启用,或资源规格太低,您可以选择先变配 ML 服务。具体操作,请参见变配 ML 服务

    规格

    从下拉列表中选择规格。

    节点数量

    设置推理服务的节点数量。单可用区部署,未开启高可用:节点数量可设范围为 1~512。单可用区部署,开启高可用:节点数量为 2 的整数倍,最大值为 512。多可用区部署:节点数量为可用区个数的整数倍,最大值为 512。比如可用区个数为 2,节点数量就为 2 的整数倍。

    网络配置

    是否开通私网访问。
    开通后,系统将为推理服务关联的模型分配一个私网访问地址,允许与 ML 服务在相同 VPC 环境内的客户端访问关联的模型。

    描述

    自定义设置推理服务的描述信息。

    高级选项

    为推理服务配置自定义参数,默认提供UserConfig、MaxReplicasPerNode两个参数。
    本文关联 Image Embedding 模型,无需填写。

  4. 启动推理服务。

    1. 推理服务创建后,单击操作列中的启动
    2. 在弹出的对话框,单击确定
  5. 获取 Image Embedding 模型调用信息。
    推理服务启动后,在推理服务详情页面单击查看调用信息,然后获取模型的调用信息。
    Image

三、创建 ingest pipeline

创建 Ingest Pipeline,需要指定使用的 Image Embedding 模型,可以将指定字段(图片信息)转换为向量后嵌入回去。

  1. 登录可视化工具,进入命令行。
    1. 登录可视化工具。使用公网访问地址登录可视化工具,请参见公网访问 Kibana/Dashboards
    2. 在左侧导航栏选择Management > Dev Tools
  2. 执行以下命令,创建 ID 为image_remote_embedding的 Ingest Pipeline。
    创建 Ingest Pipeline 时,请注意填写以下信息:
    • url:设置为从模型服务的调用信息中获取模型的url
    • model:设置从模型服务的调用信息中获取模型的model,比如ImageSearch-Demo
    • "vector_source_image": "vector":索引中的vector_source_image字段含义为图片的 TOS Bucket 存放路径,该配置表示将vector_source_image字段转为向量存储到vector中。
    PUT _ingest/pipeline/image_remote_embedding
    {
        "description": "image embedding pipeline for remote inference",
        "processors": [
            {
                "remote_text_embedding": {
                    "remote_config": {
                        "method": "POST",
                        "url": "http://d-182800***-serve-svc.r-00g**:8000/v1/embeddings",
                        "params": {
    
                        },
                        "headers": {
                            "Content-Type": "application/json"
                        },
                        "advance_request_body": {
                            "model": "ImageSearch-Demo"
                        }
                    },
                    "field_map": {
                        "vector_source_image": "vector"
                    }
                }
            }
        ]
    }
    

四、创建 search pipeline

创建 Search Pipeline,需要指定使用的 Image Embedding 模型。

  1. 登录可视化工具,进入命令行。
    1. 登录可视化工具。使用公网访问地址登录可视化工具,请参见公网访问 Kibana/Dashboards
    2. 在左侧导航栏选择 Management > Dev Tools
  2. 执行以下命令,创建 ID 为image_remote_embedding_search_pipeline的 Search Pipeline。
    创建 Search Pipeline 时,请注意填写以下信息:
    • url:设置为从模型服务的调用信息中获取模型的url
    • model:设置从模型服务的调用信息中获取模型的model,比如ImageSearch-Demo
    PUT _search/pipeline/image_remote_embedding_search_pipeline
    {
      "description": "image embedding pipeline for remote inference",
      "request_processors": [
        {
          "remote_embedding": {
             "remote_config":
                    {
                        "method": "POST",
                        "url": "http://d-1828002***-serve-svc.r-00g***:8000/v1/embeddings",
                        "params":
                        {},
                        "headers":
                        {
                            "Content-Type": "application/json"
                        },
                        "advance_request_body":
                        {
                            "model": "ImageSearch-Demo"
                        }
                    }
          }
        }
      ]
    }
    

五、创建索引

OpenSearch 实例创建后,您可以在实例中创建一个索引,用于存储图片的向量信息。

  1. 登录可视化工具,进入命令行。
    1. 登录可视化工具。使用公网访问地址登录可视化工具,请参见公网访问 Kibana/Dashboards
    2. 在左侧导航栏选择Management > Dev Tools
  2. 执行以下命令,创建一个名称为image_search的索引。
    • 创建索引时,指明使用的默认管道default_pipeline为新建的 Ingest Pipeline,ID 为image_remote_embedding
    • id:图片的 ID 字段,可理解为图片名称或 ID,起标识作用。
    • description:图片的描述信息,由豆包视觉理解模型生成。
    • vector_source_image:表示图片在 TOS Bucket 上的存储路径,通过该路径可以获取到具体图片。
    • vector:表示图片生成的 embedding 向量字段,将vector设置为knn_vectordimension需要和使用的模型保持相同维度,比如768。
    PUT imagesearch
    {
      "settings": {
        "default_pipeline": "image_remote_embedding",
        "index": {
          "refresh_interval": "60s",
          "number_of_shards": "3",
          "knn.space_type": "cosinesimil",
          "knn": "true",
          "number_of_replicas": "1"
        }
      },
        "mappings": {
          "properties": {
            "id": {
              "type": "text"
            },
            "description": {
               "type": "text"
            },
             "vector_source_image": {
               "type": "text"
            },
            "vector": {
             "type": "knn_vector",
             "dimension": 768,
             "method": {
              "name": "hnsw",
              "engine": "nmslib",
              "space_type": "cosinesimil"
            }
            }
          }
        }
    }
    

六、创建图文搜索应用

使用 Python 语言利用 Gradio 库搭建交互式的图文检索应用。

  1. 安装依赖

    pip install gradio opensearch-py tos openai
    
  2. 生成图片描述

    1. 配置环境变量,将<ARK_API_KEY>替换为您的方舟 API Key。方舟 API Key 获取方式,请参见API Key 管理

      export ARK_API_KEY="ARK_API_KEY"
      
    2. 利用豆包视觉模型推理接入点生成图片描述,注意填写以下信息:
      ark_model:设置为豆包视觉模型推理接入点 ID。

      import os
      import base64
      from openai import OpenAI
      from PIL import Image
      
      def gen_image_description(image, prompt="提取图中商品的关键词,直接输出结果", image_path=None):
          ark_model = 'ep-****'
          llm = OpenAI(api_key=os.environ.get("ARK_API_KEY"), base_url="https://ark.cn-beijing.volces.com/api/v3")
      
          if image_path:
              image = Image.open(image_path)
          if image is None:
              return ""
          response = llm.chat.completions.create(
              model=ark_model,
              temperature=0.1,
              max_tokens=64,
              messages=[
                  {
                      "role": "user",
                      "content": [
                          {"type": "text", "text": prompt},
                          {
                              "type": "image_url",
                              "image_url": {
                                  "url": "data:image/jpeg;base64," + image_to_base64(image)}
                          }
                      ]
                  }
              ])
          return response.choices[0].message.content
      
      def image_to_base64(pil_image):
          buffered = BytesIO()
          pil_image.save(buffered, format="JPEG")
          img_str = base64.b64encode(buffered.getvalue())
          return img_str.decode()
      
  3. 配置 OpenSearch 和 TOS 连接
    连接 OpenSearch 实例,请注意填写以下信息:

    • host:设置为 OpenSearch 实例的公网访问地址,具体配置方式,请参见配置实例公网访问
    • port:设置为 OpenSearch 实例的访问端口。
    • username 和 password: 设置为 OpenSearch 实例的访问用户名和访问密码。
      from opensearchpy import OpenSearch
      
      host = 'opensearch-o-***.escloud.volces.com'
      port = '9200'
      auth = ('username', 'password')
      index = 'imagesearch'
      client = OpenSearch(
          hosts=[{'host': host, 'port': port}],
          http_compress=True,
          http_auth=auth,
          use_ssl=True,
          verify_certs=False,
          ssl_assert_hostname=False,
          ssl_show_warn=False,
      )
      

    建立 TOS 连接,请注意填写以下信息:

    • accessKey 和 secretKey:配置方式,请参见配置访问凭证
    • tos_path:设置为期望存储图片文件的 TOS Bucket 路径。
    • tos_region:设置为 TOS Bucket 所在的区域,如 cn-beijing。
      import tos
      
      accessKey = 'AK**'
      secretKey = '****'
      tos_path = 'tos://doc-test/picture/image_temp'
      tos_region = 'cn-beijing'
      tos_endpoint = 'https://tos-' + tos_region + '.volces.com'
      tos_client = tos.TosClientV2(accessKey, secretKey, tos_endpoint, tos_region)
      
  4. 上传图片
    将图片上传到 TOS Bucket,并生成图片向量和图片描述存储到 OpenSearch。

    import gradio as gr
    
    def upload_images(file_objs, ark_prompt: str = None, progress=gr.Progress()):
        result = []
        for file_obj in progress.tqdm(file_objs, desc="Uploading files"):
            file_name = file_obj.name
            base_file_name = os.path.basename(file_name)
            tos_file_path = upload_to_tos(file_obj)
            upload_to_opensearch(index, tos_file_path, file_name, ark_prompt)
            result.append([base_file_name, "Done"])
        return result
    
    def upload_to_tos(file):
        from urllib.parse import urlparse
        bucket_name = urlparse(tos_path).netloc
        prefix = urlparse(tos_path).path[1:]
        object_key = os.path.join(prefix,os.path.basename(file))
        tos_client.put_object_from_file(bucket_name, object_key, file)
        return os.path.join(tos_path, os.path.basename(file))
    
    def upload_to_opensearch(index, tos_file_path, local_file_path, ark_prompt):
        base_name = os.path.basename(tos_file_path)
        document = {
            "id": base_name,
            "vector_source_image": tos_file_path,
            "description": gen_image_description(None, prompt=ark_prompt, image_path=local_file_path)
        }
        print(document['description'])
        pipeline_id = 'image_remote_embedding'
        client.index(index=index, body=document, id=base_name, params={"pipeline": pipeline_id})
    
  5. 搜索图片

    from concurrent.futures import ThreadPoolExecutor
    
    def search_images(img: Image.Image, description=None, percent=None, size=None):
        result = query_opensearch(img, description, percent, size)
        image_list = []
        data_list = []
        for item in result:
            image_list.append(item["_source"]["vector_source_image"])
            data_list.append(
                [item["_id"], item["_score"], item["_source"]["vector_source_image"], item["_source"]["description"]])
        return data_list, download_from_tos(image_list)
    
    def query_opensearch(img: Image.Image, description=None, percent=None, size=None):
        if not size:
            size = 20
        bool_queries = {"must": [], "should": []}
        bool_queries["must"].append({
            "remote_neural": {
                "vector": {
                    "query_text": "data:image," + image_to_base64(img),
                    "k": size * 10,
                    "boost": 100
                }
            }
        })
        if description and percent and percent > 0:
            bool_queries["must"].append({
                "remote_neural": {
                    "vector": {
                        "query_text": description,
                        "k": size * 10,
                        "boost": percent
                    }
                }
            })
            bool_queries["should"].append({
                "dis_max": {
                    "queries": [
                        {
                            "match": {
                                "description": {
                                    "query": description,
                                    "boost": percent/10
                                }
                            }
                        }
                    ]
                }
            })
        search_query = {
            "_source": {
                "exclude": ["vector"]
            },
            "size": size,
            "query": {
                "function_score": {
                    "query": {
                        "bool": bool_queries
                    }
                }
            }
        }
    
        search_pipeline_id = 'image_remote_embedding_search_pipeline'
        result = client.search(index=index, body=search_query, params={"search_pipeline": search_pipeline_id})
        return result["hits"]["hits"]
    
    def download_from_tos(keys):
        with ThreadPoolExecutor() as executor:
            images = list(executor.map(get_image_from_tos, keys))
        return images
    
    def get_image_from_tos(url):
        from urllib.parse import urlparse
        bucket_name = urlparse(url).netloc
        prefix = urlparse(url).path[1:]
        object_stream = tos_client.get_object(bucket_name, prefix)
        return Image.open(object_stream)
    
  6. 创建搜索应用界面

    import gradio as gr
    
    with gr.Blocks(analytics_enabled=False) as demo:
        local_storage = gr.State([])
        gr.Markdown("# CloudSearch Image Search")
        with gr.Tab("Upload Files"):
            with gr.Column():
                with gr.Row():
                    with gr.Column():
                        gr.Markdown("# 上传图像文件")
                        file_input = gr.File(file_count="multiple", label="支持 png,jpeg,jpg,webp 类型 ", file_types=['image'])
                        radio_input = gr.Radio([False, True], label="是否开启图像增强,开启后会调用大模型生成图像描述", value=True)
                        prompt = gr.Textbox(lines=1, label="Prompt", value='提取图中商品的关键词,直接输出结果')
                        upload_button = gr.Button("Uploads")
                result = gr.Dataframe(
                    headers=["File", "Status"],
                    datatype=["str", "str"],
                    label="Upload Result",
                )
            upload_button.click(upload_images, inputs=[file_input, prompt], outputs=[result])
    
        with gr.Tab("Search Image"):
            with gr.Column():
                with gr.Row():
                    image_input = gr.Image(type="pil", sources=["upload"], height=450, width=450)
                    with gr.Column():
                        gr.Markdown("图片描述内容用来提供混合搜索。可以调用大模型生成图片描述,也可以手动填写内容,如果描述内容为空,则只进行图片搜索")
                        prompt = gr.Textbox(lines=1, label="Prompt", value='提取图中商品的关键词,直接输出结果')
                        generate_des_btn = gr.Button(value="生成图片描述")
                        description_input = gr.Textbox(lines=1, label="描述", value='')
                        temp_slider = gr.Slider(0, 100, value=1, step=0.1, interactive=True, label="文字描述搜索占比")
                        generate_des_btn.click(gen_image_description,inputs=[image_input, prompt],outputs=[description_input])
                size = gr.Number(minimum=10, maximum=1000, step=10, value=10)
                image_button = gr.Button("Search Image")
                data_output = gr.Dataframe(
                    headers=["Id",  "Score", "ImageUrl","Description"],
                    datatype=["str", "number", "str", "str"],
                    label="Image List",
                )
                image_output = gr.Gallery(show_label=False, elem_id="gallery", columns=[4], object_fit="contain", height="auto")
    
            image_button.click(search_images, inputs=[image_input, description_input, temp_slider, size], outputs=[data_output, image_output])
    
    demo.launch(server_name="0.0.0.0", server_port=7860)
    

效果验证

上传图片数据

启动图文搜索应用后,在浏览器中打开 http://0.0.0.0:7860/, 从页面上传图片。
Image

图文混合搜图

启动图文搜索应用后,在浏览器中打开 http://0.0.0.0:7860/,从页面进行图片检索。
Image