200 lines
5.7 KiB
Python
200 lines
5.7 KiB
Python
"""
|
||
Qdrant 图片向量数据库工具
|
||
功能:上传 vectors.jsonl 中已分析好的向量,用文本查询返回相似度+图片名
|
||
依赖:pip install qdrant-client openai
|
||
"""
|
||
|
||
import json
|
||
import re
|
||
import uuid
|
||
from qdrant_client import QdrantClient, models
|
||
from openai import OpenAI
|
||
|
||
# ========== 配置区 ==========
|
||
QDRANT_HOST = "10.0.0.66"
|
||
QDRANT_PORT = 6333
|
||
COLLECTION_NAME = "my_collection1"
|
||
VECTOR_DIM = 2048 # vectors.jsonl 中向量维度
|
||
|
||
# 嵌入模型配置(查询时用)
|
||
EMBED_API_KEY = "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"
|
||
EMBED_BASE_URL = "https://integrate.api.nvidia.com/v1"
|
||
EMBED_MODEL = "nvidia/llama-nemotron-embed-vl-1b-v2"
|
||
|
||
|
||
def get_qdrant_client() -> QdrantClient:
|
||
return QdrantClient(host=QDRANT_HOST, port=QDRANT_PORT)
|
||
|
||
|
||
def get_text_vector(text: str) -> list[float]:
|
||
"""获取文本向量(用于查询)"""
|
||
client = OpenAI(api_key=EMBED_API_KEY, base_url=EMBED_BASE_URL)
|
||
resp = client.embeddings.create(
|
||
input=[text],
|
||
model=EMBED_MODEL,
|
||
encoding_format="float",
|
||
extra_body={"modality": ["text"], "input_type": "query", "truncate": "NONE"}
|
||
)
|
||
return resp.data[0].embedding
|
||
|
||
|
||
def parse_timestamp(filename: str) -> str:
|
||
"""
|
||
从文件名解析时间戳
|
||
文件名格式: xxx_00m05s_213.jpg → "00:05"
|
||
"""
|
||
match = re.search(r'(\d{2})m(\d{2})s', filename)
|
||
if match:
|
||
return f"{match.group(1)}:{match.group(2)}"
|
||
return ""
|
||
|
||
|
||
# ========== 创建集合 ==========
|
||
|
||
def create_collection(
|
||
collection_name: str = COLLECTION_NAME,
|
||
vector_dim: int = VECTOR_DIM
|
||
):
|
||
"""创建 Qdrant 集合"""
|
||
client = get_qdrant_client()
|
||
collections = [c.name for c in client.get_collections().collections]
|
||
if collection_name in collections:
|
||
print(f"集合 '{collection_name}' 已存在,跳过创建")
|
||
return
|
||
|
||
client.create_collection(
|
||
collection_name=collection_name,
|
||
vectors_config=models.VectorParams(
|
||
size=vector_dim,
|
||
distance=models.Distance.COSINE
|
||
)
|
||
)
|
||
print(f"集合 '{collection_name}' 创建成功,维度={vector_dim}")
|
||
|
||
|
||
# ========== 上传向量 ==========
|
||
|
||
def upload_vectors_jsonl(
|
||
jsonl_path: str = "vectors.jsonl",
|
||
collection_name: str = COLLECTION_NAME
|
||
):
|
||
"""
|
||
读取 vectors.jsonl 并上传到 Qdrant
|
||
jsonl 格式: {"image_path": "/path/to/image.jpg","image_name":"[猫和....01s_042.jpg", "vector": [...]}
|
||
"""
|
||
client = get_qdrant_client()
|
||
|
||
points = []
|
||
with open(jsonl_path, "r", encoding="utf-8") as f:
|
||
for line in f:
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
item = json.loads(line)
|
||
path = item["path"]
|
||
vector = item["vector"]
|
||
image_name = path.split("/")[-1].split("\\")[-1]
|
||
timestamp = parse_timestamp(image_name)
|
||
|
||
points.append(
|
||
models.PointStruct(
|
||
id=str(uuid.uuid4()),
|
||
vector=vector,
|
||
payload={
|
||
"image_name": image_name,
|
||
"image_path": path,
|
||
"timestamp": timestamp,
|
||
}
|
||
)
|
||
)
|
||
|
||
sp = serch_payload(image_name)
|
||
# print(sp)
|
||
if sp[0]:
|
||
print(f"向量已经上传过,不再处理{path}")
|
||
points = []
|
||
continue
|
||
# 每 500 条批量上传
|
||
if len(points) >= 100:
|
||
client.upsert(collection_name=collection_name, points=points)
|
||
print(f"已上传 {len(points)} 条...")
|
||
points = []
|
||
|
||
if points:
|
||
client.upsert(collection_name=collection_name, points=points)
|
||
|
||
total = client.count(collection_name=collection_name).count
|
||
print(f"上传完成,集合 '{collection_name}' 共 {total} 条向量")
|
||
|
||
|
||
# ========== 查询 ==========
|
||
|
||
def search(
|
||
query_text: str,
|
||
top_k: int = 5,
|
||
collection_name: str = COLLECTION_NAME
|
||
):
|
||
"""
|
||
用文本搜索相似图片
|
||
返回: [(相似度, 图片名, 时间戳), ...]
|
||
"""
|
||
client = get_qdrant_client()
|
||
vector = get_text_vector(query_text)
|
||
|
||
results = client.query_points(
|
||
collection_name=collection_name,
|
||
query=vector,
|
||
limit=top_k
|
||
)
|
||
|
||
output = []
|
||
print(f"查询: '{query_text}'\n")
|
||
for i, point in enumerate(results.points):
|
||
name = point.payload.get("image_name", "")
|
||
ts = point.payload.get("timestamp", "")
|
||
score = point.score
|
||
output.append((score, name, ts))
|
||
print(f" [{i + 1}] 相似度={score:.4f} 时间={ts} 文件={name}")
|
||
|
||
return output
|
||
|
||
def serch_payload(exact_value):
|
||
# exact_value = '[猫和老鼠(五十周年纪念版-国粤英三语)].Tom.And.Jerry.E01.2001.DVDrip.x264.AC3-CMCT_102m13s_210.jpg'
|
||
filter_condition = models.Filter(
|
||
must=[
|
||
models.FieldCondition(
|
||
key="image_name",
|
||
match=models.MatchValue(value=exact_value)
|
||
)
|
||
]
|
||
)
|
||
client = get_qdrant_client()
|
||
result = client.scroll(
|
||
collection_name=COLLECTION_NAME,
|
||
scroll_filter=filter_condition,
|
||
limit=10,
|
||
with_payload=True
|
||
)
|
||
return result
|
||
|
||
|
||
# ========== 运行设置(改这里)==========
|
||
# 设为 True 的才会执行
|
||
DO_CREATE = False # 创建集合
|
||
DO_UPLOAD = 1 # 上传 vectors.jsonl
|
||
DO_SEARCH = False # 文本搜图片
|
||
|
||
SEARCH_TEXT = "猫和老鼠" # 搜索内容
|
||
SEARCH_TOP_K = 5 # 返回数量
|
||
# ======================================
|
||
|
||
|
||
if __name__ == "__main__":
|
||
if DO_CREATE:
|
||
create_collection()
|
||
|
||
if DO_UPLOAD:
|
||
upload_vectors_jsonl("vectors1.jsonl")
|
||
|
||
if DO_SEARCH:
|
||
search(SEARCH_TEXT, top_k=SEARCH_TOP_K) |