问题:传统图像搜索为何失败
如果你曾尝试构建图像搜索系统,就会知道其中的痛苦。传统方法将整个图像压缩成一个密集向量——本质上是将复杂的视觉场景压缩成高维空间中的一个点。
会丢失什么?
- 空间布局和位置信息
- 杂乱场景中的多个对象
- 图表、示意图或文本区域等细粒度细节
- 对精确匹配至关重要的局部语义上下文
想象一下在技术手册中搜索"显示数据库架构的图表"。全局向量无法确定该图表在页面中的位置,也无法将其与其他视觉元素区分开来。你只能得到模糊、不精确的匹配。
ColPali登场:补丁级多向量索引
ColPali(Contextual Late-interaction over Patches)从根本上重新思考视觉搜索。它不是每张图像一个向量,而是生成数百或数千个补丁级嵌入——保留空间结构和语义丰富性。
工作原理
- 图像分解:每张图像被分割成网格(例如:32×32补丁 = 每页1024个补丁)
- 补丁嵌入:每个补丁使用视觉语言模型获得自己的上下文嵌入
- Late Interaction:查询时,文本查询token与所有补丁嵌入进行匹配
- MaxSim评分:对于每个查询token,只保留所有补丁中的最大相似度,然后将这些分数相加
这受到ColBERT的late interaction范式启发——但适应了多模态视觉搜索。
为什么重要
🎯 细粒度搜索:匹配特定区域,而不仅仅是全局语义
🏗️ 保留结构:空间关系和布局信息保持完整
📊 更好的召回率:密集视觉场景不会"忘记"小但重要的区域
⚡ 高效检索:Late interaction避免了索引时昂贵的交叉注意力
🚫 无需OCR:无需容易出错的文本提取即可原生处理图像
使用CocoIndex + Qdrant构建
我们正在构建的架构:
图像 → ColPali嵌入 → 多向量存储(Qdrant) → Late Interaction搜索
步骤1:摄取图像
@cocoindex.flow_def(name="ImageObjectEmbeddingColpali")
def image_object_embedding_flow(flow_builder, data_scope):
data_scope["images"] = flow_builder.add_source(
cocoindex.sources.LocalFile(
path="img",
included_patterns=["*.jpg", "*.jpeg", "*.png"],
binary=True
),
refresh_interval=datetime.timedelta(minutes=1),
)
这会监视本地目录,并在新图像到达时每分钟自动刷新。
步骤2:使用ColPali嵌入
img_embeddings = data_scope.add_collector()
with data_scope["images"].row() as img:
img["embedding"] = img["content"].transform(
cocoindex.functions.ColPaliEmbedImage(
model="vidore/colpali-v1.2"
)
)
每张图像现在变成多向量表示:Vector[Vector[Float32, N]]
其中:
- 外层维度 = 补丁数量(例如:1024)
- 内层维度 = 模型隐藏大小(例如:128)
步骤3:存储到Qdrant
collect_fields = {
"id": cocoindex.GeneratedField.UUID,
"filename": img["filename"],
"embedding": img["embedding"],
}
img_embeddings.collect(**collect_fields)
img_embeddings.export(
"img_embeddings",
cocoindex.targets.Qdrant(collection_name="ImageSearchColpali"),
primary_key_fields=["id"],
)
Qdrant原生支持多向量字段,非常适合ColPali的基于补丁的方法。
步骤4:实时索引
@asynccontextmanager
async def lifespan(app: FastAPI):
load_dotenv()
cocoindex.init()
image_object_embedding_flow.setup(report_to_stdout=True)
app.state.live_updater = cocoindex.FlowLiveUpdater(
image_object_embedding_flow
)
app.state.live_updater.start()
yield
当图像被添加、修改或删除时,索引会实时保持同步。
查询索引
@app.get("/search")
def search(
q: str = Query(..., description="搜索查询"),
limit: int = Query(5, description="结果数量"),
) -> Any:
# 查询的多向量嵌入
query_embedding = text_to_colpali_embedding.eval(q)
# Qdrant中的Late Interaction搜索
results = qdrant_client.search(
collection_name="ImageSearchColpali",
query_vector=query_embedding,
limit=limit
)
return results
性能差异
与单向量方法(如CLIP)相比,ColPali实现了:
✅ 更丰富的检索:捕获细微的视觉细节
✅ 更好的定位:识别复杂场景中的特定区域
✅ 更高的召回率:不会遗漏小但重要的元素
✅ 可解释性:MaxSim分数显示哪些补丁匹配了哪些查询token
超越本地文件:连接任何数据源
CocoIndex支持生产就绪的源连接器:
- Google Drive:自动同步文档和图像
- Amazon S3/SQS:大规模事件驱动索引
- Azure Blob Storage:企业云集成
更改会自动检测并实时反映在索引中——无需手动重建。
用例
🔍 Visual RAG:构建理解文档布局的AI代理
📚 文档搜索:在手册中查找特定图表、表格或图解
🏥 医学影像:按解剖特征搜索放射学报告
🛍️ 电子商务:细粒度产品图像搜索
🎨 数字资产管理:按视觉构图搜索设计文件
重要的技术细节
存储格式
Vector[Vector[Float32, embedding_dim]]
- 每张图像 = 补丁向量数组
- 启用late interaction策略
- 兼容量化和压缩(HPC-ColPali)
Late Interaction评分
score = Σ max(sim(query_token_i, patch_j)) 对所有补丁j
- 避免昂贵的联合编码
- 在规模上实现高效检索
- 保持可解释性
扩展策略
- 量化:在最小精度损失的情况下压缩嵌入
- 分层补丁压缩:进一步减少存储需求
- 分布式索引:扩展到数十亿张图像
自己试试
完整的工作代码:github.com/cocoindex-i…
pip install cocoindex
# 运行示例
python examples/image_search/colpali_main.py
为什么这对生产很重要
传统图像搜索在需要以下情况时会失败:
- 复杂场景中的精确定位
- 多对象理解
- 布局感知检索
- 与变化的数据源实时同步
ColPali + CocoIndex提供了处理所有这些的生产就绪基础——只需几行声明式Python。
如果你正在构建多模态AI系统,请在GitHub上给CocoIndex加星:github.com/cocoindex-i…
正在构建下一代多模态搜索的AI基础设施?CocoIndex是你一直在寻找的缺失部分。