使用CLIP构建视频搜索引擎

news2024/9/25 11:14:41

CLIP(Contrastive Language-Image Pre-training)是一种机器学习技术,它可以准确理解和分类图像和自然语言文本,这对图像和语言处理具有深远的影响,并且已经被用作流行的扩散模型DALL-E的底层机制。在这篇文章中,我们将介绍如何调整CLIP来辅助视频搜索。

这篇文章将不深入研究CLIP模型的技术细节,而是展示CLIP的另外一个实际应用(除了扩散模型外)。

首先我们要知道:CLIP使用图像解码器和文本编码器来预测数据集中哪些图像与哪些文本是匹配的。

使用CLIP进行搜索

通过使用来自hugging face的预训练CLIP模型,我们可以构建一个简单而强大的视频搜索引擎,并且具有自然语言能力,而且不需要进行特征工程的处理。

我们需要用到以下的软件

Python≥= 3.8,ffmpeg,opencv

通过文本搜索视频的技术有很多。我们可以将搜索引擎将由两部分组成,索引和搜索。

索引

视频索引通常涉及人工和机器过程的结合。人类通过在标题、标签和描述中添加相关关键字来预处理视频,而自动化过程则是提取视觉和听觉特征,例如物体检测和音频转录。用户交互指标等等,这样可以记录视频的哪些部分是最相关的,以及它们保持相关性的时间。所有这些步骤都有助于创建视频内容的可搜索索引。

索引过程的概述如下

  • 将视频分割成多个场景
  • 为框架取样场景
  • 帧处理后进行像素嵌入
  • 索引建立存储

将视频分成多个场景

为什么场景检测很重要?视频由场景组成,而场景由相似的帧组成。如果我们只对视频中的任意场景进行采样,可能会错过整个视频中的关键帧。

所以我们就需要准确地识别和定位视频中的特定事件或动作。例如,如果我搜索“公园里的狗”,而我正在搜索的视频包含多个场景,例如一个男人骑自行车的场景和一个公园里的狗的场景,场景检测可以让我识别出与搜索查询最接近的场景。

可以使用“scene detect”python包来进行这个操作。

 mport scenedetect as sd
 
 video_path = '' # path to video on machine
 
 video = sd.open_video(video_path)
 sm = sd.SceneManager()
         
 sm.add_detector(sd.ContentDetector(threshold=27.0))
 sm.detect_scenes(video)
 
 scenes = sm.get_scene_list()

对场景的帧进行采样

然后就需要使用cv2对视频进行帧采样。

 import cv2
 
 cap = cv2.VideoCapture(video_path)
 
 every_n = 2 # number of samples per scene
 
 scenes_frame_samples = []    
 for scene_idx in range(len(scenes)):
     scene_length = abs(scenes[scene_idx][0].frame_num - scenes[scene_idx][1].frame_num)
     every_n = round(scene_length/no_of_samples)
     local_samples = [(every_n * n) + scenes[scene_idx][0].frame_num for n in range(3)]
             
     scenes_frame_samples.append(local_samples)

将帧转换为像素嵌入

在收集样本之后,我们需要将它们计算成CLIP模型可用的东西。

首先需要将每个样本转换为图像张量嵌入。

 from transformers import CLIPProcessor
 from PIL import Image
 
 clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
 
 def clip_embeddings(image):
     inputs = clip_processor(images=image, return_tensors="pt", padding=True)
     input_tokens = {
         k: v for k, v in inputs.items()
     }
 
     return input_tokens['pixel_values']
 
 # ... 
 scene_clip_embeddings = [] # to hold the scene embeddings in the next step
 
 for scene_idx in range(len(scenes_frame_samples)):
     scene_samples = scenes_frame_samples[scene_idx]
 
     pixel_tensors = [] # holds all of the clip embeddings for each of the samples
     for frame_sample in scene_samples:
         cap.set(1, frame_sample)
         ret, frame = cap.read()
         if not ret:
             print('failed to read', ret, frame_sample, scene_idx, frame)
             break
 
          pil_image = Image.fromarray(frame)
                 
          clip_pixel_values = clip_embeddings(pil_image)
          pixel_tensors.append(clip_pixel_values)

下一步就是平均同一场景中的所有样本,这样可以降低样本的维数,而且还可以解决单个样本中存在噪声的问题。

 import torch
 import uuid
 
 def save_tensor(t):
     path = f'/tmp/{uuid.uuid4()}'
     torch.save(t, path)
 
     return path
 
 # ..
 avg_tensor = torch.mean(torch.stack(pixel_tensors), dim=0)
 scene_clip_embeddings.append(save_tensor(avg_tensor))

这样就获得了一个CLIP嵌入的表示视频内容的的张量列表。

存储索引

对于底层索引存储,我们使用LevelDB(LevelDB是由谷歌维护的键/值库)。我们搜索引擎的架构将包括 3 个独立的索引:

视频场景索引:哪些场景属于特定视频

场景嵌入索引:保存特定的场景数据

视频元数据索引:保存视频的元数据。

我们将首先将视频中所有计算出的元数据以及视频的唯一标识符,插入到元数据索引中,这一步都是现成的,非常简单。

 import leveldb
 import uuid
 
 def insert_video_metadata(videoID, data):
     b = json.dumps(data)
 
     level_instance = leveldb.LevelDB('./dbs/videometadata_index')
     level_instance.Put(videoID.encode('utf-8'), b.encode('utf-8'))
 
 # ...
 video_id = str(uuid.uuid4())
 insert_video_metadata(video_id, {
     'VideoURI': video_path,
 })

然后在场景嵌入索引中创建一个新条目保存视频中的每个像素嵌入,还需要一个唯一的标识符来识别每个场景。

 import leveldb
 import uuid
 
 def insert_scene_embeddings(sceneID, data):
     level_instance = leveldb.LevelDB('./dbs/scene_embedding_index')
     level_instance.Put(sceneID.encode('utf-8'), data)
 
 # ...
 for f in scene_clip_embeddings:
     scene_id = str(uuid.uuid4())
     
     with open(f, mode='rb') as file:
         content = file.read()
             
         insert_scene_embeddings(scene_id, content)

最后,我们需要保存哪些场景属于哪个视频。

 import leveldb
 import uuid
 
 def insert_video_scene(videoID, sceneIds):
     b = ",".join(sceneIds)
     
     level_instance = leveldb.LevelDB('./dbs/scene_index')
     level_instance.Put(videoID.encode('utf-8'), b.encode('utf-8'))
 
 # ...
 scene_ids = []
 for f in scene_clip_embeddings:
     # .. as shown in previous step
     scene_ids.append(scene_id)
     scene_embedding_index.insert(scene_id, content)
 
 scene_index.insert(video_id, scene_ids)

搜索

现在我们有了一种将视频的索引,下面就可以根据模型输出对它们进行搜索和排序。

第一步需要遍历场景索引中的所有记录。然后,创建一个视频中所有视频和匹配场景id的列表。

 records = []
 
 level_instance = leveldb.LevelDB('./dbs/scene_index')
 
 for k, v in level_instance.RangeIter():    
     record = (k.decode('utf-8'), str(v.decode('utf-8')).split(','))
     records.append(record)

下一步需要收集每个视频中存在的所有场景嵌入张量。

import leveldb

def get_tensor_by_scene_id(id):
    level_instance = leveldb.LevelDB('./dbs/scene_embedding_index')
    b = level_instance.Get(bytes(id,'utf-8'))

    return BytesIO(b)

for r in records:
    tensors = [get_tensor_by_scene_id(id) for id in r[1]]

在我们有了组成视频的所有张量之后,我们可以把它传递到模型中。该模型的输入是“pixel_values”,表示视频场景的张量。

import torch
from transformers import CLIPProcessor, CLIPModel

processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")

inputs = processor(text=text, return_tensors="pt", padding=True)

for tensor in tensors:
    image_tensor = torch.load(tensor)
    inputs['pixel_values'] = image_tensor   
    outputs = model(**inputs)

然后访问模型输出中的“logits_per_image”获得模型的输出。

Logits本质上是对网络的原始非标准化预测。由于我们只提供一个文本字符串和一个表示视频中的场景的张量,所以logit的结构将是一个单值预测。

logits_per_image = outputs.logits_per_image    
probs = logits_per_image.squeeze()

prob_for_tensor = probs.item()

将每次迭代的概率相加,并在运算结束时将其除以张量的总数来获得视频的平均概率。

def clip_scenes_avg(tensors, text):
    avg_sum = 0.0

    for tensor in tensors:
        # ... previous code snippets
        probs = probs.item()
        avg_sum += probs.item()

    return avg_sum / len(tensors)

最后在得到每个视频的概率并对概率进行排序后,返回请求的搜索结果数目。

import leveldb
import json

top_n = 1 # number of search results we want back

def video_metadata_by_id(id):
    level_instance = leveldb.LevelDB('./dbs/videometadata_index')
    b = level_instance.Get(bytes(id,'utf-8'))
    return json.loads(b.decode('utf-8'))

results = []
for r in records:
    # .. collect scene tensors

    # r[0]: video id
    return (clip_scenes_avg, r[0]) 

sorted = list(results)
sorted.sort(key=lambda x: x[0], reverse=True)

results = []
for s in sorted[:top_n]:
    data = video_metadata_by_id(s[1])

    results.append({
        'video_id': s[1],
        'score': s[0],
        'video_uri': data['VideoURI']
     })

就是这样!现在就可以输入一些视频并测试搜索结果。

总结

通过CLIP可以轻松地创建一个频搜索引擎。使用预训练的CLIP模型和谷歌的LevelDB,我们可以对视频进行索引和处理,并使用自然语言输入进行搜索。通过这个搜索引擎使用户可以轻松地找到相关的视频,最主要的是我们并不需要大量的预处理或特征工程。

那么我们还能有什么改进呢?

  • 使用场景的时间戳来确定最佳场景。
  • 修改预测让他在计算集群上运行。
  • 使用向量搜索引擎,例如Milvus 替代LevelDB
  • 在索引的基础上建立推荐系统
  • 等等

最后:

可以在这里找到本文的代码:
https://avoid.overfit.cn/post/a190cdd81cf74c5dadd651a87697d14c

作者:Guy Ross

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

再获权威认可 百分点科技入选Forrester AI/ML平台主流供应商

近日,全球领先的研究和咨询公司Forrester发布了2022年第四季度中国AI/ML(人工智能/机器学习)平台报告《The AI/ML Platform Landscape In China, Q4 2022》,系统分析了AI/ML平台市场的业务价值、市场成熟度及市场动态,…

python基础语法24-多线程实操

上一节说了多线程的理论知识,今天来实际操作一下。 1.创建线程 python中有2中方法创建线程,分别为函数和类继承 (1).使用函数来创建线程 调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下: _thread.start_new_thread ( function, args[, kwargs] ) 参数说…

保护小程序,防止反编译:打造不怕反编译的小程序

保护小程序,防止反编译 打造不怕反编译的小程序 这几年,小程序、小游戏,非常火。 业内人都知道,小程序或小游戏,就是H5应用,就是htmlJS。这类应用,反编译很容易,网上就有很多方法教程。 对小…

我坦白→低代码功能我有,SQL练习题、数据可视化、数据填充助你高效

简介 今天勇哥看了一下群里的聊天信息,大家都在说低代码平台,见大家对于低代码这么热衷的情况下,勇哥也不藏着掖着了,先放几个低代码功能出来,给大家玩一玩,更多的功能敬请期待。 帮勇哥投个票&#xff1…

Docker进阶 — 一文掌握Docker基础

Docker进阶 — 一文掌握Docker基础 文章目录Docker进阶 --- 一文掌握Docker基础一、初识 Docker1. 什么是Docker2. Docker架构3. DockerHub4. Docker运行模式5. Docker和虚拟机的区别二、Docker 的安装1. Linux安装Docker2. Window 安装Docker环境配置1. 开启 Hyper-V服务2. 安…

五款炫酷精美动态登录页面,彩虹气泡动态云层深海灯光水母炫酷星空蛛网HTMLCSS源码

完整源码详见 微信公众号&#xff1a;创享日记 对话框发送&#xff1a;登录页面 获取HTMLCSSjs等源码文件 一、彩虹气泡登录页面 效果图&#xff1a; HTML源码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-…

linux 下coredump 生成及调试分析

Windows环境崩溃问题&#xff08;dump&#xff09;可根据vs调试或windbg工具查看.linux环境崩溃文件为core文件&#xff0c;可以使用gdb进行调试分析。 前提&#xff1a;都是都是用了root权限的用户操作。 1.生成core文件的前提 产生coredump的条件&#xff0c;首先需要确认…

优化改进YOLOv5算法之添加RepVGG模块(超详细)

在前面的文章中已经详细介绍了在本机上安装YOLOv5的教程&#xff0c;安装YOLOv5可参考前面的文章YOLOv5训练自己的数据集(超详细)https://blog.csdn.net/qq_40716944/article/details/118188085 目录 1、RepVGG原理 1.1 模型定义 1.2 为什么要用VGG式模型 1.3 结构重参数化…

基于ssm+mysql+jsp实现水果蔬菜商城系统

基于ssmmysqljsp实现水果商城系统一、系统介绍1、系统主要功能&#xff1a;2、环境配置二、功能展示1.主页(客户)2.登陆&#xff08;客户&#xff09;3.我的购物车(客户)4.我的订单&#xff08;客户&#xff09;5.主页&#xff08;管理员&#xff09;6.订单管理&#xff08;管理…

python寻找男厕所小便池的最佳站位

题目描述 相信我&#xff0c;每一个人内在都是有精神洁癖的。尤其是在题目所在的场景中。 下面我们就用一个算法来衡量一下&#xff01;&#xff01;&#xff01; 在考场里&#xff0c;一排有 N 个座位&#xff0c;分别编号为 0, 1, 2, …, N-1 。 当学生进入考场后&#xff0…

Python利用pandas读取Excel某列为键某几列为列表类型的值

在日常的办公中&#xff0c;我们经常要处理表格之间的数据匹配&#xff0c;会经常用到VLOOKUP函数&#xff0c;那么在Python中可以把VLOOKUP函数转换为字典&#xff0c;在转换成本地json文件&#xff0c;这样不仅可以匹配大批量数据&#xff0c;而且速度也会变快。 今天我们要…

第二十六讲:神州路由器PPP PAP认证的配置

实验拓扑图如下所示 操作步骤&#xff1a; 步骤1&#xff1a;连接网络拓扑图。 步骤2&#xff1a;RouterA基本配置。 router>enable &#xff01;进入特权模式 router#config &#xff01;进入全局配置模式 router_config#hostname RouterA …

移植RT-thread Nano完成一个 modbus接口的温湿度Slave设备,让上位机PC通过modbus协议获取温湿度

文章目录前言一、移植RT-thread Nano1、STM32CubeMX 安装 RT-Thread2、Keil安装RT-Thread二、STM32CubeMX 创建工程1.添加RT-Thread组件2、配置项目三、keil配置1、ANT20配置2、移植freeModebusRTU3、代码配置四、总结五、参考资料前言 硬件&#xff1a;stm32f103c8t6 核心板软…

springcloud+nacos+gateway+oauth2+jwt再相聚

在springcloud微服务架构下&#xff0c;如何进行统一的认证、鉴权&#xff0c;一直是大家非常关心的问题&#xff0c;下面对微服务架构下的认证和鉴权继续聊聊&#xff0c;一是自己的再次思考总结&#xff0c;二是希望对小伙伴有所帮助。 1、方案思路 在springcloud微服务中&am…

React学习08-React Redux

Redux Redux理解 redux是一个专门用于做状态管理的JS库(不是react插件库)。可以用在React, Angular, Vue等项目中, 但基本与React配合使用。作用: 集中式管理React应用中多个组件共享的状态。Redux只负责管理状态 文档 英文文档 中文文档 Github 需要使用Redux的情况…

2023跨年烟花(浪漫烟花+美妙音乐+雪花飘飘)含前端源码直接下载---系列最终篇

2023年快要到来啦&#xff0c;很高兴这次我们又能一起度过~ 特辑最终篇&#xff01;&#xff01;&#xff01; 视觉中国 目录 一、前言 二、跨年烟花 三、效果展示 四、详细介绍 五、编码实现 index.html js 六、获取代码 需要源码&#xff0c;可以私信我(⊙o⊙)&…

【html实现书籍网(未完待续)】

html实现书籍网(未完待续) 前言1.直接下载文件2.简单分析3.后续工作总结前言 最近花了一些时间写了大多只有前端的书籍网,后端仅由flask进行了一下链接的跳转,主要目录有以下: static bootstrapcssfrontimgjslayuitemplates 个人页面.html主界面.html找回密码.html注册页…

Redis客户端框架Redisson

介绍 Redisson是架设在Redis基础上的一个Java驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。 Redisson在基于NIO的Netty框架上&#xff0c;充分的利用了Redis键值数据库提供的一系列优势&#xff0c;在Java实用工具包中常用接口的基础上&#xff0c;为使用者提…

使用Stable Diffusion进行Ai+艺术设计(以智慧灯杆为例)

目录一. 安装环境二. 配置模型2.1 stable diffusion v12.2 运行并测试生成效果Stable Diffusion 是一种以 CLIP ViT-L/14 文本编码器的&#xff08;非池化&#xff09;文本嵌入为条件的潜在扩散模型。一. 安装环境 创建并激活一个合适的名为conda的环境&#xff1a;ldm conda…

来自2022的年终总结,迎接新的2023

来自2022的年终总结&#xff0c;迎接新的2023&#x1f389;2022&#x1f389;&#x1f339;CSDN博客数据&#x1f339;2022年度也在持续原创博文&#xff0c;累计超过100篇&#xff0c;也收获了很多同学支持付费专栏订阅不断上升&#xff0c;帮助越来越多的同学学习&#x1f33…