基于Lindorm多模能力快速实现图片检索

更新时间:2025-03-07 09:03:46

本文介绍如何基于Lindorm标量、全文、向量融合检索以及在线推理等多模能力,快速搭建图片检索服务,实现以文搜图和以图搜图功能。

背景介绍

在自动驾驶、AIGC、多模态大模型等领域的快速发展下,图片检索的需求日益增长。无论是从海量图库中筛选高质量数据集用于模型训练,还是通过互联网图片搜索提升生成内容的质量,智能图片检索都成为关键环节。

本文将为您介绍一种基于Lindorm多模态数据库的智能搜索方案,能够有效解决以下核心问题:

  • 高质量召回:结合标量、全文、向量检索以及AI多模态模型的能力,实现图片、文本和特征的精准匹配,大幅提升检索效果。

  • 高效成本控制:针对海量图片的存储与检索需求,提供高性价比的解决方案,显著降低检索成本。

  • 弹性扩展能力:支持秒级弹性扩展,轻松应对数据量快速增长的场景。

通过本方案,您可以快速构建高性能、低成本的图片检索服务,存储计算分离架构可以做到秒级弹性,满足业务对高质量图片检索的需求。

方案架构

本文将通过Python代码演示如何基于Lindorm标量、全文、向量融合检索以及在线推理能力,帮您快速构建图片检索服务。

image

步骤一:开通Lindorm多模能力

  1. 登录Lindorm管理控制台

  2. 单击页面左上角的创建

  3. Lindorm售卖页面,设置以下配置项:

    配置项

    说明

    配置项

    说明

    商品类型

    选择Lindorm新版售卖

    形态选择

    选择生产型

    实例配置

    勾选搜索引擎向量引擎AI引擎

    说明
    • 提交工单开通AI引擎:

      • 在创建实例前,提交工单开通白名单权限,之后在创建过程中即可勾选AI引擎

      • 若已创建实例,请您提交工单单独开通AI引擎功能。

    • Lindorm向量引擎的功能实现依赖搜索引擎,因此需同时开通。

    • 各引擎具体信息请参见引擎类型

    • 您可以根据实际业务需求,变更实例的规格与节点,具体操作请参见变更实例规格

  4. 单击立即购买,并根据售卖页的指引,完成支付。

步骤二:配置白名单

将客户端IP添加至Lindorm白名单。如何添加,请参见设置白名单

步骤三:下载代码

请下载完整代码示例multimodalRetrieval,以便在后续步骤中配置和构建图片检索业务。

步骤四:环境配置

运行环境

已安装Python环境,要求安装Python 3.10及以上版本。

安装依赖

 pip3 install -r requirements.txt 

配置Lindorm连接地址

在已下载代码中的env脚本里配置Lindorm各引擎的连接地址。连接地址获取方法请参见查看连接地址

大模型推理依赖百炼,需要配置百炼的API KeyAPI Key获取方法请参见获取API Key

# AI host(配置AI引擎连接地址)
AI_HOST="ld-bp17j28j2y7pm****-proxy-ai-pub.lindorm.aliyuncs.com"
AI_PORT="9002"

# Search host(配置搜索引擎连接地址)
SEARCH_HOST="ld-bp17j28j2y7pm****-proxy-search-pub.lindorm.aliyuncs.com"
SEARCH_PORT="30070"

# Lindorm user password(配置Lindorm用户密码)
LD_USER="root"
LD_PASSWORD="test****"

# 返回结果的最大数量
SEARCH_TOP_K="9"

# 配置百炼的API Key
DASHSCOPE_API_KEY="sk-****"

# 模型名称固定值  
LD_VL_MODEL="bge_visual0"
LD_TEXT_MODEL="bge_m3_model"

安装Jupyter Notebook

  1. 安装Jupyter。

    pip3 install jupyter
  2. 生成配置文件~/.jupyter/jupyter_notebook_config.py

    jupyter notebook --generate-config
  3. 获取要设置访问Jupyter的密码 。

    from passlib.hash import argon2
    print(argon2.hash('Vector123'))

    输出密码哈希值,用于Jupyter Notebook的密码配置。输出示例如下:

    $argon2id$v=19$m=65536,t=3,p=4$4TyndM75H8N4b+291xqjdA$n0QSxlv/uCLjGR0TX/jbD/XFlEu9BzQGI1b2Mcu6gxg
  4. 使用 vim ~/.jupyter/jupyter_notebook_config.py 命令打开并编辑配置文件。

    #文件最后几行加上如下配置
    c.NotebookApp.ip = '*'
    # 笔记本的默认打开目录, 自行设置
    # 笔记本启动后是否打开浏览器, 设为 False即可
    c.NotebookApp.open_browser = False
    
    # 默认访问端口, 可自行修改
    c.NotebookApp.port = 9000
    
    # 下方代码中argon2后面的内容替换成上一步骤已获取到的Jupyter的密码
    c.NotebookApp.password = 'argon2:$argon2id$v=19$m=65536,t=3,p=4$4TyndM75H8N4b+291xqjdA$n0QSxlv/uCLjGR0TX/jbD/XFlEu9BzQGI1b2Mcu6gxg'
    
    # 这个主目录非常重要,后续您的访问文件需要放在该目录
    c.NotebookApp.notebook_dir = u'/data/lindorm/LindormDemo'  #设置你打开jupyter notebook的时候想显示的位置,可以设置成经常使用的路径
  5. 启动Jupyter服务。前端启动,可以查看是否有启动错误。若需停止服务,可使用Ctrl+C来终止进程。

    jupyter notebook
    说明

    实际业务使用过程中建议使用后端启动Jupyter服务,可以避免因终端关闭导致的服务中断,同时便于后台管理。后端启动请执行:nohup jupyter notebook --allow-root >/tmp/jupyter.log 2>&1 &

步骤五:运行ipynb脚本

创建Lindorm多模态检索模型

示例代码依赖了两种Lindorm多模态检索模型:

  • 文本向量化模型:BGE_M3_MODEL。

  • 图片向量化模型:BGE_VISUAL0。

说明

AI引擎部署模型的具体操作及参数详情请参见模型管理通过curl命令使用AI引擎RESTful API示例

部署BGE_M3_MODEL模型

curl -i -k --location --header 'x-ld-ak:<username>' --header 'x-ld-sk:<password>' -X POST http://ld-bp17j28j2y7pm****-proxy-ai-pub.lindorm.aliyuncs.com:9002/v1/ai/models/create \
     -H "Content-Type: application/json" \
     -d '{
          "model_name": "bge_m3_model",
          "model_path": "huggingface://BAAI/bge-m3",
          "task": "FEATURE_EXTRACTION",
          "algorithm": "BGE_M3",
          "settings": {"instance_count": "2"}
     }'

部署BGE_VISUAL0模型

curl -X POST "http://ld-bp17j28j2y7pm****-proxy-ai-pub.lindorm.aliyuncs.com:9002/v1/ai/models/create" \
-H "x-ld-ak: <username>" \
-H "x-ld-sk: <password>" \
-H "Content-Type: application/json" \
-d '{
    "model_name": "bge_visual0",
    "model_path": "huggingface://BAAI/bge-visualized",
    "task": "FEATURE_EXTRACTION",
    "algorithm": "BGE_VISUALIZED_M3",    
    "settings": {
        "model_type": "ensemble",
        "instance_count": "4"
    }}'

查看所有模型

curl -i -k --location --header 'x-ld-ak:<username>' --header 'x-ld-sk:<password>' --request GET 'http://ld-bp17j28j2y7pm****-proxy-ai-pub.lindorm.aliyuncs.com:9002/v1/ai/models/list'

部署环节说明

Jupyter中执行代码LindormQwenVlMultiModalSearch.ipynb,利用通义千问图片识别模型和Lindorm多模态模型构建高质量图片检索库。

环节

说明

涉及引擎

环节

说明

涉及引擎

创建向量表

创建一个包含图片向量列的搜索索引表。

def create_search_index(self):
    body = {
        "settings": {
            "index": {
                "number_of_shards": 2,
                "knn": True
            }
        },
        "mappings": {
            "_source": {
                "excludes": ["image_embedding"]
            },
            "properties": {
                "image_embedding": {
                    "type": "knn_vector",
                    "dimension": self.dimension,
                    "data_type": "float",
                    "method": {
                        "engine": "lvector",
                        "name": "hnsw",
                        "space_type": "cosinesimil",
                        "parameters": {
                            "m": 24,
                            "ef_construction": 500
                        }
                    }
                },
                "description": {
                    "type": "text"
                }
            }
        }
    }
    print(self.index_name, self.client.indices.create(self.index_name, body=body, timeout=60))

搜索引擎

向量引擎

导入图片

将原始图片数据处理为系统可用的向量形式,并写入索引表。

  1. 处理图片函数。

    def handle_picture(file_path: str):
        # 读取本地图片内容
        with open(file_path, 'rb') as f:
            # print(f"图片描述 {file_path}")
            code, description = lindorm.qwen_vl_picture_withdraw(file_path)
            if code != 0:
                print(f"图片描述失败 {file_path}, code {code}, error {description}")
                return None
            # print(f"图片描述成功 {file_path}, description {description}")
            content = f.read()
            code, embedding = lindorm.image_text_embedding(content, description)
            if code != 0:
                print(f"图文向量化失败 {file_path}, error {embedding}")
                raise Exception(f"图文向量化失败 {file_path}, error {embedding}")
            return lindorm.write_doc_with_description(file_path, description, embedding)
  2. 调用通义千问图片识别模型提取图片文字描述。

    def qwen_vl_picture_withdraw(self, file_path):
        """Simple single round multimodal conversation call.
        """
        image_path = f"file://{file_path}"
    
        # 构建消息
        messages = [
            {"role": "system", "content": [{"text": "You are a helpful assistant."}]},
            {"role": "user", "content": [{"image": image_path}, {"text": "图中描绘的是什么景象?"}]}
        ]
    
        # 调用模型
        response = dashscope.MultiModalConversation.call(
            api_key=self.api_key,  # 如果没有配置环境变量,请直接使用API Key
            model='qwen-vl-max-latest',
            messages=messages
        )
    
        # The response status_code is HTTPStatus.OK indicate success,
        # otherwise indicate request is failed, you can get error code
        # and message from code and message.
        if response.status_code == HTTPStatus.OK:
            result = response.output.get('choices')[0].get('message').get('content')[0].get('text')
            return 0, result
        else:
            print(response)
            print(messages, response.code, response.message)  # The error code.
            # raise Exception("http request failed, status code: {}".format(response.message))
            return response.status_code, response.message
  3. 将文字和图片一起通过多模态模型转换成文字-图片融合向量。

    def image_text_embedding(self, image, text):
        encoded_str = base64.b64encode(image).decode('utf-8')
        data = {"input": [{"image": encoded_str, "text": text}]}
        return self.post_model_request(Config.LD_VL_MODEL, data)
    def post_model_request(self, model: str, data: dict):
        data = json.dumps(data)
        url = 'http://{}:{}/v1/ai/models/{}/infer'.format(Config.AI_HOST, Config.AI_PORT, model)
        try:
            result = requests.post(url, data=data, headers=self.headers, verify=False)
            # 确保请求成功
            result.raise_for_status()
            # print(f'请求成功: {url}, 返回: {result.json()}')
            return 0, result.json()['data'][0]
        except requests.exceptions.Timeout:
            return -1, "请求超时"
        except requests.exceptions.HTTPError as http_err:
            return -1, f"HTTP 错误: {http_err}"
        except requests.exceptions.RequestException as err:
            return -1, f"请求发生错误: {err}"  
  4. 将文字和向量写入索引表。

    def write_doc_with_description(self, url: str, description: str, embedding: list):
        element = {
            "image_embedding": embedding,
            "description": description
        }
        return self.client.index(self.index_name, element, id=url)

AI引擎

搜索引擎

向量引擎

检索方式说明

  • 检索方式:以文搜图/以图搜图。

  • 检索选项:向量检索/全文和向量混合检索。

  • 图片去重:通过以图搜图功能,自动识别并过滤相似度达到或超过阈值的图片,避免重复导入,同时返回相似图片供参考。

检索方式

具体描述

涉及引擎

检索方式

具体描述

涉及引擎

以文搜图

通过文字搜索图片,支持向量检索,也支持全文和向量混合检索。

  1. 输入描述文字。

    def on_button_clicked(b):
        with output:
            clear_output()  # 清除上次输出
            if text_input.value:
                code, embedding = lindorm.text_embedding(text_input.value)
            else:
                print("请先输入描述文字")
                return
            
            if b.description == "纯向量检索":
                hits = lindorm.knn_search(embedding)
            elif b.description == "RRF融合检索":
                if text_input.value == '':
                    print("请先输入关键字")
                    return
                hits = lindorm.rrf_search(text_input.value, embedding)
            print("文本搜图")
            show_hits(hits)
  2. 文字转向量。

    if text_input.value:
                code, embedding = lindorm.text_embedding(text_input.value)
    def text_embedding(self, text: str):
            data = {"input": [text]}
            return self.post_model_request(Config.LD_TEXT_MODEL, data)
         
  3. 检索图片。

    • 向量检索。

      if b.description == "纯向量检索":
          hits = lindorm.knn_search(embedding)
      def knn_search(self, picture_embedding):
              body = {
                  "query": {
                      "knn": {
                          "image_embedding": {
                              "vector": picture_embedding,
                              "k": self.top_k
                          }
                      }
                  },
                  "size": self.top_k
              }
              response = self.client.search(index=self.index_name, body=body)
              return response.get('hits').get('hits')
      
    • 全文和向量混合检索。

      elif b.description == "RRF融合检索":
          if text_input.value == '':
              print("请先输入关键字")
              return
          hits = lindorm.rrf_search(text_input.value, embedding)
      def rrf_search(self, text, embedding):
              query = {
                  "_source": ["description"],
                  "size": self.top_k,
                  "query": {
                      "knn": {
                          "image_embedding": {
                              "vector": embedding,
                              "filter": {
                                  "match": {
                                      "description": text
                                  }
                              },
                              "k": self.top_k
                          }
                      }
                  },
                  "ext": {"lvector": {
                      "hybrid_search_type": "filter_rrf",
                      "rrf_rank_constant": "60",
                      "rrf_knn_weight_factor": "0.4"
                  }}
              }
              response = self.client.search(index=self.index_name, body=query)
              return response.get('hits').get('hits')
              

AI引擎

搜索引擎

向量引擎

以图搜图

通过图片搜索图片,全文和向量混合检索。

  1. 显示上传的图片。

    def show_uploaded_image(change):
        with output:
            clear_output()  # 清除上次输出
            if change['new']:
                # 获取上传的文件内容
                uploaded_file = change['new'][0]
                content = uploaded_file['content']
                # 使用 PIL 打开图片
                image = Image.open(io.BytesIO(content))
                # 使用 matplotlib 显示图片
                plt.imshow(image)
                plt.axis('off')  # 不显示坐标轴
                plt.show()
                code, embedding = lindorm.picture_embedding(content)
                hits = lindorm.knn_search(embedding)
                print("图片上传检索")
                show_hits(hits)
  2. 图片转向量。

    code , embedding = lindorm.picture_embedding(content)
    def picture_embedding(self, image):
        encoded_str = base64.b64encode(image).decode('utf-8')
        data = {"input": {"images": [encoded_str]}}
        return self.post_model_request(Config.LD_VL_MODEL, data)
  3. 向量检索图片。

    hits = lindorm.knn_search(embedding)
    def rrf_search(self, text, embedding):
        query = {
            "_source": ["description"],
            "size": self.top_k,
            "query": {
                "knn": {
                    "image_embedding": {
                        "vector": embedding,
                        "filter": {
                            "match": {
                                "description": text
                            }
                        },
                        "k": self.top_k
                    }
                }
            },
            "ext": {"lvector": {
                "hybrid_search_type": "filter_rrf",
                "rrf_rank_constant": "60",
                "rrf_knn_weight_factor": "0.4"
            }}
        }
        response = self.client.search(index=self.index_name, body=query)
        return response.get('hits').get('hits')
        

AI引擎

搜索引擎

向量引擎

以图搜图-图片去重

运行LindormMultiModalSearch.ipynb,对图片去重。

  1. 创建索引并导入图片。

    if __name__ == '__main__':
        print("start")
        lindorm = Lindorm("multimodal_search_index2")
        
        if lindorm.get_index() is None:
            # lindorm.drop_index()
            lindorm.create_search_index()
        print("索引创建完成")
        import_all_data(dir_input.value)
  2. 计算图片相似度。

    # 计算图片向量
    code, picture_embedding = lindorm.picture_embedding(image_data)
    hits = lindorm.knn_search(picture_embedding)
    
    # 显示 float_input
    print('图片相似度阈值:', float_input.value)
    print('图片类型:', image_type)
  3. 若存在达到或超过阈值的图片,则不导入;否则,完成图片入库。

    if hits[0].get('_score') >= float_input.value:
        print(f"库中存在图片相似度 >= {float_input.value},不导入")
    else:
        print(f"库中不存在图片相似度 >= {float_input.value},导入这张图片")
        dir = dir_input.value
        if not os.path.exists(dir):
            os.makedirs(dir)
        pic_name = str(uuid.uuid4()) + "." + image_type
        pic_path = os.path.join(dir, f"{os.path.basename(pic_name)}")
        import_new_image(image_data, pic_path, picture_embedding)

AI引擎

搜索引擎

向量引擎

  • 本页导读 (1)
  • 背景介绍
  • 方案架构
  • 步骤一:开通Lindorm多模能力
  • 步骤二:配置白名单
  • 步骤三:下载代码
  • 步骤四:环境配置
  • 运行环境
  • 安装依赖
  • 配置Lindorm连接地址
  • 安装Jupyter Notebook
  • 步骤五:运行ipynb脚本
  • 创建Lindorm多模态检索模型
  • 部署环节说明
  • 检索方式说明