AI图片分类:探索zero-shot模型在分类领域的实现

vit模型

Vision Transformer(ViT)是一种基于Transformer架构的计算机视觉模型,通过将图像分割成小块(patches)并利用Transformer编码器处理这些局部特征,实现了对全局信息的有效提取。

Zero-shot学习旨在使模型能够识别、分类或理解在训练过程中未见过的类别或概念。对于解决现实世界中常见的长尾分布问题至关重要,即对于一些罕见或未知类别的样本,传统的监督学习方法可能难以处理。

Zero-shot学习典型算法主要集中在建立从已知类别到未知类别的知识迁移机制上。这些算法通常利用类别之间共享的属性或者语义关系来桥接已知类别和未知类别之间的差异。简单来说就是将未知类别通过语义相似度等方式与用训练中的已知类别进行关联,达到对未知类别的检测。

CLIP-ViT

CLIP-ViT模型是OpenAI开发的基于Vision Transformer(ViT)架构的多模态预训练模型,通过对比学习实现图像与文本的跨模态对齐,广泛应用于零样本分类、图像检索等任务。(2021年)

CLIP模型由图像编码器和文本编码器两部分组成。图像编码器负责将图像转换为特征向量,可以是卷积神经网络(如ResNet)或Transformer模型(如ViT);文本编码器则负责将文本转换为特征向量,通常是一个Transformer模型,

CLIP的工作原理可以概括为"对比学习"。模型会接收一批图像-文本对作为输入,并尝试将匹配的图像和文本向量在共同的语义空间中拉近,而将不匹配的向量推远,这种学习方式使得CLIP能够捕捉到图像和文本之间的深层语义联系,实现跨模态理解。不同于以的分类网络的类别数量是固定的,CLIP给了我们很高的自由度去设置"多项选择题"提供给网络的分类标签不仅数量不固定,内容也是自由的,摆脱了事先定好的分类标签。

如果没有在原图片标记实体的需求,那么与OWL-ViT相比较CLIP-ViT可能是更好的选择,它的鲁棒性更好一些。

它的使用比较简单,如下:

python 复制代码
from PIL import Image
from transformers import CLIPProcessor, CLIPModel

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

text_queries = ["a photo of a dog", "a photo of a human"]
image = Image.open("1.jpeg")
inputs = processor(text=text_queries, images=image, return_tensors="pt", padding=True)
outputs = model(**inputs)
logits = outputs.logits_per_image  # 图像-文本相似度
scores = logits.softmax(dim=1)    # 归一化得分

但是运行的是会自动取huggingface下载模型,国内需要梯子才行。

本地运行

如果没有梯子或者太慢,可以手动下载模型本地运行,地址是: huggingface.co/openai/clip...

需要下载文件有:

  • config.json
  • merges.txt
  • pytorch_model.bin或者其他模型文件
  • preprocessor_config.json
  • special_tokens_map.json
  • tokenizer_config.json
  • vocab.json

下载后将这些文件放在一个目录中,比如clip-vit-base-patch32

然后代码如下:

python 复制代码
import datetime
from io import BytesIO

import cv2
import numpy as np
import requests
import torch
from PIL import Image
from transformers import CLIPProcessor, CLIPModel

model_path = "models/clip-vit-base-patch32"

model = CLIPModel.from_pretrained(model_path)
processor = CLIPProcessor.from_pretrained(model_path)

start = datetime.datetime.now().timestamp()

text_queries = ["a piece of homework", "a photo of a child", "a screenshot of an app", "a photo of a cat"]

image = Image.open("1.jpg")
# url = "https://knowboxqiniu.knowbox.cn/mars/db161c24f422755065b37873ddf15644.jpeg?imageMogr2/thumbnail/!480x480r"
# response = requests.get(url)
# image = Image.open(BytesIO(response.content)).convert('RGB')

time = datetime.datetime.now().timestamp() - start
print(time)

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

outputs = model(**inputs)
logits = outputs.logits_per_image  # 图像-文本相似度
scores = logits.softmax(dim=1)    # 归一化得分

for query, score in zip(text_queries, scores[0]):
    print(f"query:{query}, score:{score}")

time = datetime.datetime.now().timestamp() - start
print(time)

得到的结果如下:

makefile 复制代码
query:a piece of homework, score:0.007369962986558676
query:a photo of a child, score:0.8415855765342712
query:a screenshot of an app, score:0.1493387222290039
query:a photo of a cat, score:0.0017056920332834125
0.07288002967834473

可以通过最高得分来判断图片的类型。

可以看到一张图片的处理时间大概是80毫秒左右。

预处理

两种方式中都会先对图片进行预处理,既

python 复制代码
inputs = processor(text=text_queries, images=image, return_tensors="pt", padding=True)

在预处理的时候,会将图片尺寸压缩到224x224,这个过程是两部分:

  • resize:先压缩图片,使其短边至224,比例不变
  • clip:然后裁剪图片,居中裁剪出224x224区域

所以注意,图片边缘的实体就无法检测到。

当然预处理除了压缩裁剪,还有其他一些步骤。

可以使用下面的代码来显示处理后的图片:

python 复制代码
import numpy as np
import torch
import cv2
from transformers.utils.constants import OPENAI_CLIP_STD, OPENAI_CLIP_MEAN
#展示预处理后的图像
def show_processed_image(inputs):
    pixel_values = inputs['pixel_values']
    pixel_values = pixel_values.squeeze().numpy()
    unnormalized_image = (pixel_values * np.array(OPENAI_CLIP_STD)[:, None, None]) + np.array(OPENAI_CLIP_MEAN)[:, None, None]
    unnormalized_image = (unnormalized_image * 255).astype(np.uint8)
    unnormalized_image = np.moveaxis(unnormalized_image, 0, -1)
    unnormalized_image = Image.fromarray(unnormalized_image)
    mat = np.array(unnormalized_image, dtype=np.uint8)
    width, height = mat.shape[:2]
    print(f"image size: {width}x{height}")
    cv2.imshow("test",mat )
    cv2.waitKey(0)
    cv2.destroyAllWindows()

OWL-ViT

传统的对象检测模型大多是封闭词汇类型,只能识别有限的固定类别。增加新的类别需要大量的注释数据。然而,现实世界中的物体类别几乎无穷无尽,这就需要能够检测未知类别的开放式词汇类型。

OWL-ViT(Vision Transformer for Open-World Localization)是由 Google Research 开发的一种用于开放词汇检测的方法。可以实现零样本实体检测。(2022年)

OWL-ViT是以CLIP-ViT为其多模态主干网络的(multi-modal backbone),并且对CLIP-ViT从零开始训练和微调。

使用起来比较简单,如下:

python 复制代码
from transformers import pipeline

detector = pipeline("zero-shot-object-detection", model="google/owlvit-base-patch32")

image = "1.jpg"

results = detector(image, candidate_labels=["a photo of a dog", "a photo of a cat"])

print(results)

但是运行的是会自动取huggingface下载模型,国内需要梯子才行。

本地运行

如果没有梯子或者太慢,可以手动下载模型本地运行,地址是: huggingface.co/google/owlv...

需要下载文件有:

  • config.json
  • merges.txt
  • model.safetensors或者其他模型文件
  • preprocessor_config.json
  • vocab.json

下载后将这些文件放在一个目录中,比如owlvit-base-patch32。

然后编写代码如下:

python 复制代码
import datetime

import cv2
import numpy as np
from transformers import OwlViTProcessor, OwlViTForObjectDetection
from transformers.utils.constants import OPENAI_CLIP_STD, OPENAI_CLIP_MEAN
from PIL import Image
import torch

def get_preprocessed_image(pixel_values):
    pixel_values = pixel_values.squeeze().numpy()
    unnormalized_image = (pixel_values * np.array(OPENAI_CLIP_STD)[:, None, None]) + np.array(OPENAI_CLIP_MEAN)[:, None, None]
    unnormalized_image = (unnormalized_image * 255).astype(np.uint8)
    unnormalized_image = np.moveaxis(unnormalized_image, 0, -1)
    unnormalized_image = Image.fromarray(unnormalized_image)
    return unnormalized_image

model_path = "models/owlvit-base-patch32"

processor = OwlViTProcessor.from_pretrained(model_path)
model = OwlViTForObjectDetection.from_pretrained(model_path)

start = datetime.datetime.now().timestamp()
text_queries = ["a photo of a dog", "a photo of a child"]
image = Image.open("images/people/1.jpeg")

inputs = processor(text=text_queries, images=image, return_tensors="pt")

with torch.no_grad():
    outputs = model(**inputs)

# 解析输出(目标检测结果)
unnormalized_image = get_preprocessed_image(inputs.pixel_values)

target_sizes = torch.tensor([unnormalized_image.size[::-1]])  # [height, width]
results = processor.post_process_object_detection(
    outputs, threshold=0.001, target_sizes=target_sizes
)[0]


# 提取检测到的物体信息
scores = results["scores"].tolist()
labels = results["labels"].tolist()
boxes = results["boxes"].tolist()

# 存储每个类别的最高得分检测
max_score_per_label = {}
for score, label, box in zip(scores, labels, boxes):
    text = text_queries[label]
    if text not in max_score_per_label or score > max_score_per_label[text]:
        max_score_per_label[text] = score

for label, score in max_score_per_label.items():
    print(f"Label: {label}, Score: {score}")

time = datetime.datetime.now().timestamp() - start
print(time)

结果如下:

yaml 复制代码
Label: a photo of a child, Score: 0.23997317254543304
Label: a photo of a dog, Score: 0.00792715698480606
0.23093581199645996

如果提高threshold就可以过滤掉那些不可信的部分。可以通过得分来进行图片分类。

可以看到一张图片的耗时大概在200毫秒左右

预处理

在上面的代码中已经获取到了预处理后的图片,可以直接展示出来:

scss 复制代码
# 显示处理后的图像
mat = np.array(unnormalized_image, dtype=np.uint8)
width, height = mat.shape[:2]
print(f"image size: {width}x{height}")
cv2.imshow("test",mat )
cv2.waitKey(0)
cv2.destroyAllWindows()

展示出来,可以看到经过预处理后的图片变成了768x768,而且没有对图片进行裁剪,而是将图片进行了拉伸。这样带来的问题是如果原图宽高比差很多的情况下,图片就会严重拉伸,所以可以看到这个模型的鲁棒性差一些。如果使用这个模型,那么就需要提前对图片进行一下处理,比如裁剪或填空,防止拉伸严重。

OWL-V2

OWL-V2是google在2023推出的,与OWL-ViT类似。同样是以CLIP-ViT为其多模态主干网络的(multi-modal backbone),并且对CLIP-ViT从零开始训练和微调。

官方代码如下:

python 复制代码
import requests
from PIL import Image
import numpy as np
import torch
from transformers import AutoProcessor, Owlv2ForObjectDetection
from transformers.utils.constants import OPENAI_CLIP_MEAN, OPENAI_CLIP_STD

processor = AutoProcessor.from_pretrained("google/owlv2-base-patch16")
model = Owlv2ForObjectDetection.from_pretrained("google/owlv2-base-patch16")

url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
texts = [["a photo of a cat", "a photo of a dog"]]
inputs = processor(text=texts, images=image, return_tensors="pt")

# forward pass
with torch.no_grad():
    outputs = model(**inputs)

# Note: boxes need to be visualized on the padded, unnormalized image
# hence we'll set the target image sizes (height, width) based on that

def get_preprocessed_image(pixel_values):
    pixel_values = pixel_values.squeeze().numpy()
    unnormalized_image = (pixel_values * np.array(OPENAI_CLIP_STD)[:, None, None]) + np.array(OPENAI_CLIP_MEAN)[:, None, None]
    unnormalized_image = (unnormalized_image * 255).astype(np.uint8)
    unnormalized_image = np.moveaxis(unnormalized_image, 0, -1)
    unnormalized_image = Image.fromarray(unnormalized_image)
    return unnormalized_image

unnormalized_image = get_preprocessed_image(inputs.pixel_values)

target_sizes = torch.Tensor([unnormalized_image.size[::-1]])
# Convert outputs (bounding boxes and class logits) to final bounding boxes and scores
results = processor.post_process_object_detection(
    outputs=outputs, threshold=0.2, target_sizes=target_sizes
)

i = 0  # Retrieve predictions for the first image for the corresponding text queries
text = texts[i]
boxes, scores, labels = results[i]["boxes"], results[i]["scores"], results[i]["labels"]

for box, score, label in zip(boxes, scores, labels):
    box = [round(i, 2) for i in box.tolist()]
    print(f"Detected {text[label]} with confidence {round(score.item(), 3)} at location {box}")

同样需要梯子

本地运行

地址是: huggingface.co/google/owlv...

需要下载文件有:

  • config.json
  • merges.txt
  • model.safetensors或者其他模型文件
  • preprocessor_config.json
  • vocab.json

下载后将这些文件放在一个目录中,比如owlv2-base-patch16。

然后编写代码如下:

ini 复制代码
import datetime

import cv2
import numpy as np
from transformers import AutoProcessor, Owlv2ForObjectDetection
from PIL import Image
import torch
from transformers.utils.constants import OPENAI_CLIP_STD, OPENAI_CLIP_MEAN


def get_preprocessed_image(pixel_values):
    pixel_values = pixel_values.squeeze().numpy()
    unnormalized_image = (pixel_values * np.array(OPENAI_CLIP_STD)[:, None, None]) + np.array(OPENAI_CLIP_MEAN)[:, None, None]
    unnormalized_image = (unnormalized_image * 255).astype(np.uint8)
    unnormalized_image = np.moveaxis(unnormalized_image, 0, -1)
    unnormalized_image = Image.fromarray(unnormalized_image)
    return unnormalized_image

model_path = "models/owlv2-base-patch16"

processor = AutoProcessor.from_pretrained(model_path)
model = Owlv2ForObjectDetection.from_pretrained(model_path)

start = datetime.datetime.now().timestamp()
text_queries = [["a photo of a dog", "a photo of a child"]]
image = Image.open("images/people/1.jpeg")

inputs = processor(text=text_queries, images=image, return_tensors="pt")

# show_processed_image(inputs)

with torch.no_grad():
    outputs = model(**inputs)

# 解析输出(目标检测结果)
unnormalized_image = get_preprocessed_image(inputs.pixel_values)

target_sizes = torch.Tensor([unnormalized_image.size[::-1]])

results = processor.post_process_object_detection(
    outputs, threshold=0.001, target_sizes=target_sizes
)[0]


# 提取检测到的物体信息
scores = results["scores"].tolist()
labels = results["labels"].tolist()
boxes = results["boxes"].tolist()

# 存储每个类别的最高得分检测
max_score_per_label = {}
for score, label, box in zip(scores, labels, boxes):
    text = text_queries[0][label]
    if text not in max_score_per_label or score > max_score_per_label[text]:
        max_score_per_label[text] = score

for label, score in max_score_per_label.items():
    print(f"Label: {label}, Score: {score}")

time = datetime.datetime.now().timestamp() - start
print(time)

注意:如果不加载本地模型,还是去下载模型,可能是模型的本地路径写错了,好好检查一下

得到的结果如下:

yaml 复制代码
Label: a photo of a child, Score: 0.5044812560081482
3.5331411361694336

可以看到OWL-V2的鲁棒性要比OWL-ViT好一些,但是一张图片耗时达到了3秒多。

预处理

在上面的代码中已经获取到了预处理后的图片,可以直接展示出来:

scss 复制代码
# 显示处理后的图像
mat = np.array(unnormalized_image, dtype=np.uint8)
width, height = mat.shape[:2]
print(f"image size: {width}x{height}")
cv2.imshow("test",mat )
cv2.waitKey(0)
cv2.destroyAllWindows()

展示出来,可以看到经过预处理后的图片变成了960x960,而且没有对图片进行裁剪,保证图片比例不变的情况下作了空白填充。

这样的好处是图片更完整,但是处理后的图片太大,会增加处理时间。

总结

最后总结一下这三个模型:

  • OWL-ViT:预处理成768x768,拉伸图片。鲁棒性差一些,平均一张图片200、300毫秒左右。严重拉伸可能导致识别不准确。

  • OWL-V2:预处理成960x960,填充空白。鲁棒性好一些,但是速度很慢,平均一张图片3秒左右。优点是不会丢失部分实体。

  • CLIP-ViT:预处理成224x224,居中裁剪。鲁棒性好,速度快,平均一张图片80毫秒左右。但是因为裁剪可能会丢失部分实体。

模型 预处理 鲁棒性 效率(每张图耗时) 其他
CLIP-ViT 224x224,居中裁剪 80毫秒 裁剪丢失边缘实体
OWL-ViT 768x768,拉伸图片 一般 200+毫秒 严重拉伸识别错误
OWL-V2 960x960,填充空白 可以 3秒 patch16
相关推荐
qq_314009833 分钟前
Dify版本升级实操
人工智能·aigc·开源软件
钮钴禄·爱因斯晨1 小时前
赛博算命之八字测算事业运势的Java实现(四柱、五行、十神、流年、格局详细测算)
java·开发语言·aigc
墨风如雪11 小时前
代码生成提速5.4倍!字节跳动这把剑,斩向GPT的“慢”时代
aigc
算家计算14 小时前
告别“AI味”图像!最新开源AI模型FLUX.1-Krea实现真实光影生成
人工智能·开源·aigc
安思派Anspire15 小时前
GraphRAG 工作原理分步解析(二)
aigc·openai·agent
数据智能老司机16 小时前
让流浪汉都能学会的大模型教程——用大语言模型设计解决方案
架构·llm·aigc
数据智能老司机17 小时前
让流浪汉都能学会的大模型教程——关于大模型的误解、局限和惊人本事
架构·llm·aigc
慧星云18 小时前
魔多 AI 支持 Flux.1 Krea 在线训练:感受超自然细节
人工智能·云计算·aigc
算家计算20 小时前
全新升级!Nexus-Gen V2本地部署教程:图像理解能力媲美GPT-4o
人工智能·开源·aigc