mingyang91's picture
add crack split.py
684e6f5 verified
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()