Spaces:
Runtime error
Runtime error
import json | |
import copy | |
from typing import Optional | |
from PIL import Image | |
bbox = [float, float, float, float] | |
annotation = { | |
"id": int, | |
"image_id": int, | |
"category_id": int, | |
"bbox": bbox, | |
"ignore": int, | |
"iscrowd": int, | |
"area": float, | |
} | |
small_image = { | |
"image": Image, | |
"area": bbox | |
} | |
def split_image(image: Image, | |
hint_size_min: tuple[int, int], | |
hint_size_max: tuple[int, int], | |
overlap: float = 0.1) -> list[small_image]: | |
""" | |
Given an image and a hint size, split the image into a list of images. | |
New images are overlapped with other images by the overlap ratio. | |
:param image: The image to split. typically a large image. 1kx1k ~ 10kx10k | |
:param hint_size_min: The minimum size of the output image. | |
:param hint_size_max: The maximum size of the output image. | |
:param overlap: The overlap ratio of the output image. | |
:return: A list of images. | |
""" | |
Wi, Hi = image.size | |
Wmin, Hmin = hint_size_min | |
Wmax, Hmax = hint_size_max | |
assert Wmin <= Wmax <= Wi | |
assert Hmin <= Hmax <= Hi | |
w_search = search(Wi, Wmin, Wmax, overlap) | |
h_search = search(Hi, Hmin, Hmax, overlap) | |
if w_search is None or h_search is None: | |
raise ValueError('The image is too small to split.') | |
w_count, output_width, last_output_width, width_overlap = w_search | |
h_count, output_height, last_output_height, height_overlap = h_search | |
images = [] | |
for h_index in range(h_count): | |
h = h_index * (output_height - height_overlap) | |
for w_index in range(w_count): | |
w = w_index * (output_width - width_overlap) | |
small = { | |
"image": image.crop((w, h, w + output_width, h + output_height)), | |
"area": (w, h, output_width, output_height) | |
} | |
images.append(small) | |
if last_output_width > 0: | |
w = Wi - output_width | |
small = { | |
"image": image.crop((w, h, w + output_width, h + output_height)), | |
"area": (w, h, output_width, output_height) | |
} | |
images.append(small) | |
return images | |
def search(input: int, | |
output_min: int, | |
output_max: int, | |
overlap: float) -> Optional[tuple[int, int, int, int]]: | |
""" | |
example 1: | |
input: 8000, output: 1000, overlap: 0.1 | |
8000 // (1000 - 100) = 8 | |
8000 % (1000 - 100) = 800 | |
count = 8, output = 1000, last_output = 800, overlap_pixels = 100 | |
example 2: | |
input: 7200, output: 800, overlap: 0.1 | |
7200 // (800 - 80) = 10 | |
7200 % (800 - 80) = 0 | |
count = 10, output = 800, last_output = 0, overlap_pixels = 80 | |
:param input: The length of the input image. | |
:param output_min: The minimum length of the output image. | |
:param output_max: The maximum length of the output image. | |
:param overlap: The overlap ratio of the output image. | |
:return: A tuple of (count, output, last_output, overlap_pixels). | |
""" | |
for output in range(output_max, output_min - 1, -1): | |
overlap_pixels = int(output * overlap) | |
last_output = input % (output - overlap_pixels) | |
if last_output == 0 or output_min <= last_output <= output_max: | |
count = input // (output - overlap_pixels) | |
return count, output, last_output, overlap_pixels | |
return None | |
def box_intersected(box1: bbox, box2: bbox) -> bool: | |
""" | |
Check if two boxes are intersected. | |
:param box1: The first box. | |
:param box2: The second box. | |
:return: True if the two boxes are intersected. | |
""" | |
x1, y1, w1, h1 = box1 | |
x2, y2, w2, h2 = box2 | |
return x1 < x2 + w2 and x2 < x1 + w1 and y1 < y2 + h2 and y2 < y1 + h1 | |
def fit_in_area(annotations: list[annotation], in_area: bbox) -> list[annotation]: | |
result = [] | |
for old in annotations: | |
ann = copy.deepcopy(old) | |
result.append(ann) | |
x, y, w, h = ann["bbox"] | |
if x < in_area[0]: | |
ann["bbox"][0] = 0 | |
else: | |
ann["bbox"][0] -= in_area[0] | |
if y < in_area[1]: | |
ann["bbox"][1] = 0 | |
else: | |
ann["bbox"][1] -= in_area[1] | |
if x + w > in_area[0] + in_area[2]: | |
ann["bbox"][2] = in_area[2] - ann["bbox"][0] | |
if y + h > in_area[1] + in_area[3]: | |
ann["bbox"][3] = in_area[3] - ann["bbox"][1] | |
return result | |
small_image_with_labels = { | |
"image": Image, | |
"area": bbox, | |
"labels": list[annotation] | |
} | |
def split_image_with_labels(image: Image, | |
labels: list[annotation], | |
hint_size_min: tuple[int, int], | |
hint_size_max: tuple[int, int], | |
overlap: float = 0.1) -> list[small_image]: | |
small_imgs = split_image(image, hint_size_min, hint_size_max, overlap) | |
result = [] | |
for small_img in small_imgs: | |
small_labels = [ann for ann in labels if box_intersected(ann["bbox"], small_img["area"])] | |
small_labels = fit_in_area(small_labels, small_img["area"]) | |
result.append({ | |
"image": small_img["image"], | |
"area": small_img["area"], | |
"labels": small_labels | |
}) | |
return result | |
def main(): | |
image = Image.open('../datasets/Das3300161.jpg') | |
small_imgs = split_image(image, (800, 800), (1000, 1000), 0.1) | |
labels = json.load(open('../datasets/result.json')) | |
annotations = list(filter(lambda ann: ann["image_id"] == 28, labels["annotations"])) | |
for small_img in small_imgs: | |
small_labels = [ann for ann in annotations if box_intersected(ann["bbox"], small_img["area"])] | |
small_labels = fit_in_area(small_labels, small_img["area"]) | |
# save small_labels to json | |
json.dump(small_labels, open('datasets/' + str(small_img["area"]) + '.json', 'w')) | |
# save small_image["image"] to file | |
small_img["image"].save('datasets/' + str(small_img["area"]) + '.jpg') | |
if __name__ == '__main__': | |
main() | |