Skip to content

Commit 3677e25

Browse files
committed
Add dataset to training
1 parent f47cb21 commit 3677e25

8 files changed

Lines changed: 177 additions & 72 deletions

File tree

abraia/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dotenv import load_dotenv
33
load_dotenv()
44

5-
__version__ = '0.24.1'
5+
__version__ = '0.24.2'
66

77
from . import config
88
from .client import Abraia, APIError

abraia/training/__init__.py

Lines changed: 3 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11

22
from ..client import Abraia
3-
from ..utils import get_color, url_path, make_dirs, load_image, load_url
3+
from ..utils import url_path, make_dirs
44
from . import classify, detect
55

66
import os
77
import shutil
88
import itertools
9-
from tqdm import tqdm
109
from PIL import Image
11-
from transformers import pipeline
1210
from tqdm.contrib.concurrent import process_map
1311
from sklearn.model_selection import train_test_split
1412

1513

1614
abraia = Abraia()
1715

1816

19-
def load_projects():
17+
def list_datasets():
2018
folders = abraia.list_files()[1]
2119
return [folder['name'] for folder in folders if abraia.check_file(f"{folder['name']}/annotations.json")]
2220

2321

24-
def load_dataset(project):
22+
def list_images(project):
2523
files = abraia.list_files(f"{project}/")[0]
2624
dataset = [f for f in files if f['type'].startswith('image/')]
2725
for data in dataset:
@@ -165,42 +163,3 @@ def create_dataset(dataset, task, classes):
165163
for x in ['train', 'val', 'test']:
166164
save_data(data_annotations[x], f"{dataset}/{x}", classes, task)
167165
save_config(dataset, classes)
168-
169-
170-
# As the Grounding DINO model was trained with a "." after each text, we'll do the same here.
171-
def preprocess_caption(caption: str) -> str:
172-
result = caption.lower().strip()
173-
return result if result.endswith('.') else result + '.'
174-
175-
176-
def format_results(results, threshold=0.6):
177-
r = []
178-
for result in results:
179-
score = result["score"]
180-
if score > threshold:
181-
label = result["label"].rpartition('.')[0]
182-
xmin, ymin, xmax, ymax = result['box'].values()
183-
r.append({"label": label, "score": score, "box": [xmin, ymin, xmax - xmin, ymax - ymin]})
184-
return r
185-
186-
187-
def annotate_image(pipe, img, classes, threshold=0.3):
188-
im = Image.fromarray(img)
189-
labels = [preprocess_caption(txt) for txt in classes]
190-
objects = format_results(pipe(im, candidate_labels=labels, threshold=threshold))
191-
return objects
192-
193-
194-
def annotate_images(project, classes):
195-
dataset = load_dataset(project)
196-
pipe = pipeline(task="zero-shot-object-detection", model="IDEA-Research/grounding-dino-tiny")
197-
#pipe = pipeline(task="zero-shot-object-detection", model="google/owlv2-base-patch16-ensemble")
198-
annotations = []
199-
for row in tqdm(dataset):
200-
url, filename = row['url'], row['name']
201-
img = load_image(load_url(url))
202-
objects = annotate_image(pipe, img, classes)
203-
if objects:
204-
annotation = {'url': url, 'filename': filename, 'objects': objects}
205-
annotations.append(annotation)
206-
save_annotations(project, annotations)
Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
import itertools
88
import imagehash
99

10+
from tqdm import tqdm
1011
from PIL import Image
11-
from . import HEADERS
12+
from transformers import pipeline
13+
from ..utils import HEADERS, load_image, load_url
14+
from . import list_datasets, list_images, load_annotations, save_annotations
1215

1316
GOOGLE_BASE_URL = 'https://www.google.com/search?q='
1417
GOOGLE_PICTURE_ID = '''&biw=1536&bih=674&tbm=isch&sxsrf=ACYBGNSXXpS6YmAKUiLKKBs6xWb4uUY5gA:1581168823770&source=lnms&sa=X&ved=0ahUKEwioj8jwiMLnAhW9AhAIHbXTBMMQ_AUI3QUoAQ'''
@@ -22,7 +25,7 @@ def download_page(url):
2225
return resp.text
2326

2427

25-
def save_image(link, file_path, timeout=10, max_size=1920):
28+
def save_image(link, output_dir, timeout=10, max_size=1920):
2629
resp = requests.get(link, headers=HEADERS, allow_redirects=True, timeout=timeout)
2730
kind = filetype.guess(resp.content)
2831
if kind and kind.mime.startswith('image'):
@@ -31,7 +34,7 @@ def save_image(link, file_path, timeout=10, max_size=1920):
3134
im = Image.open(d).convert('RGB')
3235
im.thumbnail([max_size, max_size])
3336
phash = str(imagehash.phash(im))
34-
im.save(os.path.join(os.path.dirname(file_path), phash + '.jpg'))
37+
im.save(os.path.join(output_dir, phash + '.jpg'))
3538
else:
3639
raise ValueError(f'Invalid image, not saving')
3740

@@ -104,8 +107,7 @@ def download(query, limit=100, output_dir='dataset', verbose=True):
104107
seen.add(link)
105108
if download_count < limit:
106109
try:
107-
filename = f"{'-'.join(query.split(' '))}_{download_count}.jpg"
108-
save_image(link, os.path.join(output_dir, filename))
110+
save_image(link, output_dir)
109111
download_count += 1
110112
if verbose:
111113
print(f"[%] Downloaded Image #{download_count} from {link}")
@@ -117,3 +119,48 @@ def download(query, limit=100, output_dir='dataset', verbose=True):
117119
ends[id] = True
118120
if set(ends) == {True}:
119121
break
122+
123+
def search_images(query, limit=100, output_dir='dataset', verbose=True):
124+
"""Search and download images from Google and Bing."""
125+
download(query, limit=limit, output_dir=output_dir, verbose=verbose)
126+
files = [os.path.join(output_dir, f) for f in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, f))]
127+
return files
128+
129+
130+
# As the Grounding DINO model was trained with a "." after each text, we'll do the same here.
131+
def preprocess_caption(caption: str) -> str:
132+
result = caption.lower().strip()
133+
return result if result.endswith('.') else result + '.'
134+
135+
136+
def format_results(results, threshold=0.6):
137+
r = []
138+
for result in results:
139+
score = result["score"]
140+
if score > threshold:
141+
label = result["label"].rpartition('.')[0]
142+
xmin, ymin, xmax, ymax = result['box'].values()
143+
r.append({"label": label, "score": score, "box": [xmin, ymin, xmax - xmin, ymax - ymin]})
144+
return r
145+
146+
147+
def annotate_image(pipe, img, classes, threshold=0.3):
148+
im = Image.fromarray(img)
149+
labels = [preprocess_caption(txt) for txt in classes]
150+
objects = format_results(pipe(im, candidate_labels=labels, threshold=threshold))
151+
return objects
152+
153+
154+
def annotate_images(images, classes):
155+
"""Annotate a dataset using Grounding Dino."""
156+
pipe = pipeline(task="zero-shot-object-detection", model="IDEA-Research/grounding-dino-tiny")
157+
#pipe = pipeline(task="zero-shot-object-detection", model="google/owlv2-base-patch16-ensemble")
158+
annotations = []
159+
for row in tqdm(images):
160+
url, filename = row['url'], row['name']
161+
img = load_image(load_url(url))
162+
objects = annotate_image(pipe, img, classes)
163+
if objects:
164+
annotation = {'url': url, 'filename': filename, 'objects': objects}
165+
annotations.append(annotation)
166+
return annotations

abraia/training/detect.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from ..client import Abraia
22

33
import os
4+
import io
5+
import contextlib
46
import numpy as np
57
#os.environ['YOLO_VERBOSE'] = 'False'
68

@@ -29,18 +31,31 @@ def __init__(self, task, model_type='yolov8n'):
2931
model_name = build_model_name(model_type, task)
3032
self.model = YOLO(f"{model_name}.pt", verbose=False)
3133
self.model_name = model_name
34+
self.metrics = {}
3235
self.task = task
3336

3437
def train(self, dataset, epochs=100, batch=32, imgsz=640):
3538
data = f"{dataset}" if self.task == 'classify' else f"{dataset}/data.yaml"
3639
results = self.model.train(data=data, batch=batch, epochs=epochs, imgsz=imgsz)
40+
# TODO: Merge with test and add parse metrics
3741
metrics = self.model.val(data=data)
42+
# self.metrics = self.test(split='val')
3843
return metrics
3944

45+
def test(self, split='val'):
46+
out = io.StringIO()
47+
with contextlib.redirect_stderr(out):
48+
metrics = self.model.val(split=split)
49+
return {'mAP': metrics.box.map50, 'P': metrics.box.p, 'R': metrics.box.r,
50+
'confusionMatrix': metrics.confusion_matrix.matrix}
51+
4052
def save(self, dataset, classes, imgsz=640, device="cpu"):
53+
# TODO: Add versioning
4154
model_src = self.model.export(format="onnx", device=device)
4255
abraia.upload_file(model_src, f"{dataset}/{self.model_name}.onnx")
43-
abraia.save_json(f"{dataset}/{self.model_name}.json", {'task': self.task, 'inputShape': [1, 3, imgsz, imgsz], 'classes': classes})
56+
abraia.save_json(f"{dataset}/{self.model_name}.json",
57+
{'task': self.task, 'inputShape': [1, 3, imgsz, imgsz],
58+
'classes': classes, 'metrics': self.metrics})
4459

4560
def run(self, img):
4661
objects = []

abraia/utils/draw.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,20 @@ def draw_rectangle(img, rect, color, thickness = 2):
4545
return img
4646

4747

48-
def draw_filled_rectangle(img, rect, color):
49-
return draw_rectangle(img, rect, color, -1)
48+
# def draw_filled_rectangle(img, rect, color):
49+
# return draw_rectangle(img, rect, color, -1)
50+
51+
52+
def draw_filled_rectangle(img, rect, color, opacity = 1):
53+
x, y, w, h = np.round(rect).astype(np.int32)
54+
pt1, pt2 = (x, y), (x + w, y + h)
55+
if opacity == 1:
56+
cv2.rectangle(img, pt1, pt2, color, -1)
57+
else:
58+
img_copy = img.copy()
59+
cv2.rectangle(img_copy, pt1, pt2, color, -1)
60+
cv2.addWeighted(img_copy, opacity, img, 1 - opacity, 0, img)
61+
return img
5062

5163

5264
def draw_polygon(img, polygon, color, thickness = 2):
@@ -55,12 +67,39 @@ def draw_polygon(img, polygon, color, thickness = 2):
5567
return img
5668

5769

70+
# def draw_filled_polygon(img, polygon, color, opacity = 1):
71+
# points = np.round(polygon).astype(np.int32)
72+
# cv2.fillPoly(img, [points], color)
73+
# return img
74+
75+
5876
def draw_filled_polygon(img, polygon, color, opacity = 1):
5977
points = np.round(polygon).astype(np.int32)
60-
cv2.fillPoly(img, [points], color)
78+
if opacity == 1:
79+
cv2.fillPoly(img, [points], color)
80+
else:
81+
img_copy = img.copy()
82+
cv2.fillPoly(img_copy, [points], color)
83+
cv2.addWeighted(img_copy, opacity, img, 1 - opacity, 0, img)
6184
return img
6285

6386

87+
# def draw_blurred_mask(img, mask):
88+
# w_k = int(0.1 * max(img.shape[:2]))
89+
# w_k = w_k + 1 if w_k % 2 == 0 else w_k
90+
# blurred_img = cv2.GaussianBlur(img, (w_k, w_k), 0)
91+
# mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
92+
# img = np.where(mask==0, img, blurred_img)
93+
# return img
94+
95+
96+
# def draw_blurred_polygon(img, polygon):
97+
# points = np.round(polygon).astype(np.int32)
98+
# mask = np.zeros(img.shape, dtype=np.uint8)
99+
# mask = cv2.fillPoly(mask, [points], 255)
100+
# return draw_blurred_mask(img, mask)
101+
102+
64103
def draw_blurred_mask(img, mask):
65104
w_k = int(0.1 * max(img.shape[:2]))
66105
w_k = w_k + 1 if w_k % 2 == 0 else w_k
@@ -82,16 +121,24 @@ def draw_overlay_mask(img, mask, color = (255, 0, 0), opacity = 1):
82121
return img_copy
83122

84123

124+
def calculate_contrast_text_color(background_color):
125+
r, g, b = background_color
126+
brightness = (r * 299 + g * 587 + b * 114) / 1000
127+
return (0, 0, 0) if brightness > 150 else (255, 255, 255)
128+
129+
85130
def draw_text(img, text, point, background_color = None, text_color = (255, 255, 255),
86131
text_scale = 0.8, padding = 6):
87-
text_font, text_thickness = cv2.FONT_HERSHEY_DUPLEX, 1
88-
w, h = cv2.getTextSize(text, text_font, text_scale, text_thickness)[0]
89-
width, height = w + 2 * padding, h + 2 * padding
90-
x, y = point[0], max(point[1] - height, 0)
91-
org = (x + padding, y + padding + h)
92-
if background_color is not None:
93-
img = draw_filled_rectangle(img, [x, y, width, height], background_color)
94-
cv2.putText(img, text, org, text_font, text_scale, text_color, text_thickness, cv2.LINE_AA)
132+
if text:
133+
text_font, text_thickness = cv2.FONT_HERSHEY_DUPLEX, 1
134+
w, h = cv2.getTextSize(text, text_font, text_scale, text_thickness)[0]
135+
width, height = w + 2 * padding, h + 2 * padding
136+
x, y = point[0], max(point[1] - height, 0)
137+
org = (x + padding, y + padding + h)
138+
if background_color is not None:
139+
img = draw_filled_rectangle(img, [x, y, width, height], background_color)
140+
text_color = calculate_contrast_text_color(background_color)
141+
cv2.putText(img, text, org, text_font, text_scale, text_color, text_thickness, cv2.LINE_AA)
95142
return img
96143

97144

@@ -147,10 +194,11 @@ def render_counter(img, line, text='', color=(0, 0, 255)):
147194
return img
148195

149196

150-
def render_region(img, region, text='', color=(0, 0, 0)):
197+
def render_region(img, region, text='', color=(255, 0, 0)):
151198
point = np.min(region, axis=0).astype(np.int32)
152199
thickness = calculate_optimal_thickness(img.shape[:2])
153200
text_scale = calculate_optimal_text_scale(img.shape[:2])
154201
draw_polygon(img, region, color=color, thickness=thickness)
202+
draw_filled_polygon(img, region, color=color, opacity=0.2)
155203
draw_text(img, text, point, background_color=color, text_scale=text_scale)
156204
return img

images/blur.mp4

-13.7 MB
Binary file not shown.

images/people-detected.mp4

-31.9 MB
Binary file not shown.

0 commit comments

Comments
 (0)