Spaces:
Runtime error
Runtime error
Commit
·
ac1c6ae
0
Parent(s):
Duplicate from s-l-s/cbir-image-similarity
Browse files- .gitattributes +34 -0
- README.md +14 -0
- requirements.txt +6 -0
- src/.gitattributes +4 -0
- src/CLIP.py +13 -0
- src/FaRL.py +15 -0
- src/__pycache__/colordescriptor.cpython-39.pyc +0 -0
- src/__pycache__/searcher.cpython-39.pyc +0 -0
- src/app.py +81 -0
- src/colordescriptor.py +60 -0
- src/helper.py +9 -0
- src/index.py +32 -0
- src/search.py +31 -0
- src/searcher.py +47 -0
.gitattributes
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Cbir Image Similarity
|
3 |
+
emoji: 🏢
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: purple
|
6 |
+
sdk: gradio
|
7 |
+
sdk_version: 3.19.1
|
8 |
+
app_file: src/app.py
|
9 |
+
pinned: false
|
10 |
+
license: openrail
|
11 |
+
duplicated_from: s-l-s/cbir-image-similarity
|
12 |
+
---
|
13 |
+
|
14 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
numpy==1.21.6
|
3 |
+
opencv-python==4.5.4.60
|
4 |
+
faiss-cpu
|
5 |
+
transformers
|
6 |
+
torch
|
src/.gitattributes
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.jpg filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.gif filter=lfs diff=lfs merge=lfs -text
|
src/CLIP.py
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from transformers import AutoProcessor, CLIPModel
|
2 |
+
|
3 |
+
|
4 |
+
class CLIPImageEncoder:
|
5 |
+
def __init__(self, device="cpu"):
|
6 |
+
self.device = device
|
7 |
+
self.model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
|
8 |
+
self.processor = AutoProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
9 |
+
|
10 |
+
def encode_image(self, image_pil):
|
11 |
+
input = self.processor(images=image_pil, return_tensors="pt")
|
12 |
+
image_features = self.model.get_image_features(**input)
|
13 |
+
return image_features.cpu().detach().numpy()[0]
|
src/FaRL.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#import torch
|
2 |
+
#import clip
|
3 |
+
|
4 |
+
|
5 |
+
#class CLIPImageEncoder:
|
6 |
+
# def __init__(self, device="cpu"):
|
7 |
+
# self.device = device
|
8 |
+
# self.model, self.preprocess = clip.load("ViT-B/16", device=device)
|
9 |
+
#
|
10 |
+
# def encode_image(self, image_pil):
|
11 |
+
# print("Encoding image with CLIP")
|
12 |
+
# with torch.no_grad():
|
13 |
+
# image_preprocessed = self.preprocess(image_pil).unsqueeze(0).to(self.device)
|
14 |
+
# image_features = self.model.encode_image(image_preprocessed)
|
15 |
+
# return image_features.cpu().numpy()[0]
|
src/__pycache__/colordescriptor.cpython-39.pyc
ADDED
Binary file (1.52 kB). View file
|
|
src/__pycache__/searcher.cpython-39.pyc
ADDED
Binary file (1.55 kB). View file
|
|
src/app.py
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from colordescriptor import ColorDescriptor
|
2 |
+
from CLIP import CLIPImageEncoder
|
3 |
+
import gradio as gr
|
4 |
+
import cv2
|
5 |
+
import numpy as np
|
6 |
+
from datasets import *
|
7 |
+
|
8 |
+
dataset = load_dataset("huggan/CelebA-faces")
|
9 |
+
candidate_subset = dataset["train"].select(range(1000)) # This is a small CBIR app! :D
|
10 |
+
|
11 |
+
def emb_dataset(dataset):
|
12 |
+
# This function might need to be split up, to reduce start-up time of app
|
13 |
+
# It could also use batches to increase speed
|
14 |
+
# If indexes are saved in files, this is all not really necessary
|
15 |
+
|
16 |
+
## Color Embeddings
|
17 |
+
cd = ColorDescriptor((8, 12, 3))
|
18 |
+
dataset_with_embeddings = dataset.map(lambda row: {'color_embeddings': cd.describe(row["image"])}) # we assume that dataset has a column 'image'
|
19 |
+
dataset_with_embeddings.add_faiss_index(column='color_embeddings')
|
20 |
+
|
21 |
+
## CLIP Embeddings
|
22 |
+
clip_model = CLIPImageEncoder()
|
23 |
+
dataset_with_embeddings = dataset.map(lambda row: {'clip_embeddings': clip_model.encode_image(row["image"])})
|
24 |
+
dataset_with_embeddings.add_faiss_index(column='clip_embeddings')
|
25 |
+
dataset_with_embeddings # Just to check, if okay
|
26 |
+
|
27 |
+
return dataset_with_embeddings
|
28 |
+
|
29 |
+
dataset_with_embeddings = emb_dataset(candidate_subset)
|
30 |
+
|
31 |
+
# Main function, to find similar images
|
32 |
+
# TODO: allow different descriptor/embedding functions
|
33 |
+
# TODO: implement different distance measures
|
34 |
+
|
35 |
+
def get_neighbors(query_image, selected_descriptor, top_k=5):
|
36 |
+
"""Returns the top k nearest examples to the query image.
|
37 |
+
|
38 |
+
Args:
|
39 |
+
query_image: A PIL object representing the query image.
|
40 |
+
top_k: An integer representing the number of nearest examples to return.
|
41 |
+
|
42 |
+
Returns:
|
43 |
+
A list of the top_k most similar images as PIL objects.
|
44 |
+
"""
|
45 |
+
if "Color Descriptor" in selected_descriptor:
|
46 |
+
cd = ColorDescriptor((8, 12, 3))
|
47 |
+
qi_embedding = cd.describe(query_image)
|
48 |
+
qi_np = np.array(qi_embedding)
|
49 |
+
scores, retrieved_examples = dataset_with_embeddings.get_nearest_examples(
|
50 |
+
'color_embeddings', qi_np, k=top_k)
|
51 |
+
images = retrieved_examples['image'] #retrieved images is a dict, with images and embeddings
|
52 |
+
return images
|
53 |
+
if "CLIP" in selected_descriptor:
|
54 |
+
clip_model = CLIPImageEncoder()
|
55 |
+
qi_embedding = clip_model.encode_image(query_image)
|
56 |
+
scores, retrieved_examples = dataset_with_embeddings.get_nearest_examples(
|
57 |
+
'clip_embeddings', qi_embedding, k=top_k)
|
58 |
+
images = retrieved_examples['image']
|
59 |
+
return images
|
60 |
+
else:
|
61 |
+
print("This descriptor is not yet supported :(")
|
62 |
+
return []
|
63 |
+
|
64 |
+
|
65 |
+
# Define the Gradio Interface
|
66 |
+
|
67 |
+
|
68 |
+
iface = gr.Interface(
|
69 |
+
fn=get_neighbors,
|
70 |
+
inputs=[
|
71 |
+
gr.Image(type="pil", label="Your Image"),
|
72 |
+
gr.CheckboxGroup(["Color Descriptor", "LBP", "CLIP"], label="Descriptor method?"),
|
73 |
+
],
|
74 |
+
outputs=gr.Gallery(),
|
75 |
+
title="Image Similarity Gallery",
|
76 |
+
description="Upload an image and get similar images",
|
77 |
+
allow_flagging="never"
|
78 |
+
)
|
79 |
+
|
80 |
+
# Launch the Gradio interface
|
81 |
+
iface.launch()
|
src/colordescriptor.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import cv2
|
3 |
+
from helper import pil_cv2_image_converter
|
4 |
+
|
5 |
+
class ColorDescriptor:
|
6 |
+
def __init__(self, bins):
|
7 |
+
# store the number of bins for the 3D histogram
|
8 |
+
self.bins = bins
|
9 |
+
|
10 |
+
def histogram(self, image, mask):
|
11 |
+
# extract a 3D color histogram from the masked region of the
|
12 |
+
# image, using the supplied number of bins per channel
|
13 |
+
hist = cv2.calcHist([image], [0, 1, 2], mask, self.bins,
|
14 |
+
[0, 180, 0, 256, 0, 256])
|
15 |
+
|
16 |
+
hist = cv2.normalize(hist, hist).flatten()
|
17 |
+
# return the histogram
|
18 |
+
return hist
|
19 |
+
|
20 |
+
def describe(self, image):
|
21 |
+
# first, convert image to cv2 from pil
|
22 |
+
# TODO: Add check, if already cv2 image
|
23 |
+
image = pil_cv2_image_converter(image)
|
24 |
+
# convert the image to the HSV color space and initialize
|
25 |
+
# the features used to quantify the image
|
26 |
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
|
27 |
+
features = []
|
28 |
+
# grab the dimensions and compute the center of the image
|
29 |
+
(h, w) = image.shape[:2]
|
30 |
+
(cX, cY) = (int(w * 0.5), int(h * 0.5))
|
31 |
+
|
32 |
+
# divide the image into four rectangles/segments (top-left,
|
33 |
+
# top-right, bottom-right, bottom-left)
|
34 |
+
segments = [(0, cX, 0, cY), (cX, w, 0, cY), (cX, w, cY, h),
|
35 |
+
(0, cX, cY, h)]
|
36 |
+
# construct an elliptical mask representing the center of the
|
37 |
+
# image
|
38 |
+
(axesX, axesY) = (int(w * 0.75) // 2, int(h * 0.75) // 2)
|
39 |
+
ellipMask = np.zeros(image.shape[:2], dtype = "uint8")
|
40 |
+
cv2.ellipse(ellipMask, (cX, cY), (axesX, axesY), 0, 0, 360, 255, -1)
|
41 |
+
|
42 |
+
# loop over the segments
|
43 |
+
for (startX, endX, startY, endY) in segments:
|
44 |
+
# construct a mask for each corner of the image, subtracting
|
45 |
+
# the elliptical center from it
|
46 |
+
cornerMask = np.zeros(image.shape[:2], dtype = "uint8")
|
47 |
+
cv2.rectangle(cornerMask, (startX, startY), (endX, endY), 255, -1)
|
48 |
+
cornerMask = cv2.subtract(cornerMask, ellipMask)
|
49 |
+
# extract a color histogram from the image, then update the
|
50 |
+
# feature vector
|
51 |
+
hist = self.histogram(image, cornerMask)
|
52 |
+
features.extend(hist)
|
53 |
+
|
54 |
+
# extract a color histogram from the elliptical region and
|
55 |
+
# update the feature vector
|
56 |
+
hist = self.histogram(image, ellipMask)
|
57 |
+
features.extend(hist)
|
58 |
+
|
59 |
+
# return the feature vector
|
60 |
+
return features
|
src/helper.py
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import cv2
|
3 |
+
|
4 |
+
def pil_cv2_image_converter(image):
|
5 |
+
numpy_image = np.array(image)
|
6 |
+
# Convert to an OpenCV image; notice the COLOR_RGB2BGR flag, which means
|
7 |
+
# that the color is converted from RGB to BGR format.
|
8 |
+
opencv_image = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR)
|
9 |
+
return opencv_image
|
src/index.py
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# # import the necessary packages
|
2 |
+
# from colordescriptor import ColorDescriptor
|
3 |
+
# import glob
|
4 |
+
# import cv2
|
5 |
+
|
6 |
+
|
7 |
+
# class Indexer:
|
8 |
+
# def __init__(self, indexPath):
|
9 |
+
# # store our index path
|
10 |
+
# self.indexPath = indexPath
|
11 |
+
|
12 |
+
# def index(self):
|
13 |
+
# # initialize the color descriptor
|
14 |
+
# cd = ColorDescriptor((8, 12, 3))
|
15 |
+
|
16 |
+
# # open the output index file for writing
|
17 |
+
# output = open(self.indexPath, "w")
|
18 |
+
|
19 |
+
# # use glob to grab the image paths and loop over them
|
20 |
+
# for imagePath in glob.glob("../static/images/" + "/*.png"):
|
21 |
+
# # extract the image ID (i.e. the unique filename) from the image
|
22 |
+
# # path and load the image itself
|
23 |
+
# imageID = imagePath[imagePath.rfind("/") + 1:]
|
24 |
+
# image = cv2.imread(imagePath)
|
25 |
+
# # describe the image
|
26 |
+
# features = cd.describe(image)
|
27 |
+
# # write the features to file
|
28 |
+
# features = [str(f) for f in features]
|
29 |
+
# output.write("%s,%s\n" % (imageID, ",".join(features)))
|
30 |
+
|
31 |
+
# # close the index file
|
32 |
+
# output.close()
|
src/search.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# from colordescriptor import ColorDescriptor
|
2 |
+
# from searcher import Searcher
|
3 |
+
# import argparse
|
4 |
+
# import cv2
|
5 |
+
|
6 |
+
# # construct the argument parser and parse the arguments
|
7 |
+
# ap = argparse.ArgumentParser()
|
8 |
+
# ap.add_argument("-i", "--index", required = True,
|
9 |
+
# help = "Path to where the computed index will be stored")
|
10 |
+
# ap.add_argument("-q", "--query", required = True,
|
11 |
+
# help = "Path to the query image")
|
12 |
+
# ap.add_argument("-r", "--result-path", required = True,
|
13 |
+
# help = "Path to the result path")
|
14 |
+
# args = vars(ap.parse_args())
|
15 |
+
|
16 |
+
# # initialize the image descriptor
|
17 |
+
# cd = ColorDescriptor((8, 12, 3))
|
18 |
+
# # load the query image and describe it
|
19 |
+
# query = cv2.imread(args["query"])
|
20 |
+
# features = cd.describe(query)
|
21 |
+
# # perform the search
|
22 |
+
# searcher = Searcher(args["index"])
|
23 |
+
# results = searcher.search(features)
|
24 |
+
# # display the query
|
25 |
+
# cv2.imshow("Query", query)
|
26 |
+
# # loop over the results
|
27 |
+
# for (score, resultID) in results:
|
28 |
+
# # load the result image and display it
|
29 |
+
# result = cv2.imread(args["result_path"] + "/" + resultID)
|
30 |
+
# cv2.imshow("Result", result)
|
31 |
+
# cv2.waitKey(0)
|
src/searcher.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import numpy as np
|
2 |
+
# import csv
|
3 |
+
|
4 |
+
# class Searcher:
|
5 |
+
# def __init__(self, indexPath):
|
6 |
+
# # store our index path
|
7 |
+
# self.indexPath = indexPath
|
8 |
+
|
9 |
+
|
10 |
+
# def chi2_distance(self, histA, histB, eps = 1e-10):
|
11 |
+
# # compute the chi-squared distance
|
12 |
+
# d = 0.5 * np.sum([((a - b) ** 2) / (a + b + eps)
|
13 |
+
# for (a, b) in zip(histA, histB)])
|
14 |
+
# # return the chi-squared distance
|
15 |
+
# return d
|
16 |
+
|
17 |
+
# def search(self, queryFeatures, limit = 3):
|
18 |
+
# # initialize our dictionary of results
|
19 |
+
# results = {}
|
20 |
+
# # open the index file for reading
|
21 |
+
# with open(self.indexPath) as f:
|
22 |
+
# # initialize the CSV reader
|
23 |
+
# reader = csv.reader(f)
|
24 |
+
# # loop over the rows in the index
|
25 |
+
# for row in reader:
|
26 |
+
# # parse out the image ID and features, then compute the
|
27 |
+
# # chi-squared distance between the features in our index
|
28 |
+
# # and our query features
|
29 |
+
# features = [float(x) for x in row[1:]]
|
30 |
+
# d = self.chi2_distance(features, queryFeatures)
|
31 |
+
# # now that we have the distance between the two feature
|
32 |
+
# # vectors, we can udpate the results dictionary -- the
|
33 |
+
# # key is the current image ID in the index and the
|
34 |
+
# # value is the distance we just computed, representing
|
35 |
+
# # how 'similar' the image in the index is to our query
|
36 |
+
# results[row[0]] = d
|
37 |
+
|
38 |
+
# # close the reader
|
39 |
+
# f.close()
|
40 |
+
|
41 |
+
# # sort our results, so that the smaller distances (i.e. the
|
42 |
+
# # more relevant images are at the front of the list)
|
43 |
+
# path = "home/user/app/static/images/"
|
44 |
+
# results = sorted([(v, f"{path}{k}") for (k, v) in results.items()])
|
45 |
+
|
46 |
+
# # return our (limited) results
|
47 |
+
# return results[:limit]
|