157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
"""Create tiny deterministic datasets for all YOLO tasks supported by the WebUI."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import math
|
|
import shutil
|
|
from pathlib import Path
|
|
|
|
from PIL import Image, ImageDraw
|
|
|
|
|
|
IMAGE_SIZE = 128
|
|
TRAIN_IMAGES = 6
|
|
VAL_IMAGES = 2
|
|
|
|
|
|
def image_geometry(index: int) -> tuple[int, tuple[int, int, int, int]]:
|
|
class_id = index % 2
|
|
offset = (index % 3) * 5
|
|
box = (26 + offset, 29, 93 + offset, 98)
|
|
return class_id, box
|
|
|
|
|
|
def make_image(path: Path, index: int, *, rotated: bool = False) -> None:
|
|
class_id, box = image_geometry(index)
|
|
colors = ((225, 72, 72), (55, 145, 225))
|
|
image = Image.new("RGB", (IMAGE_SIZE, IMAGE_SIZE), (238, 241, 245))
|
|
draw = ImageDraw.Draw(image)
|
|
if rotated:
|
|
cx, cy = 64 + (index % 3) * 3, 64
|
|
half_w, half_h = 37, 23
|
|
angle = math.radians(15 if class_id == 0 else -15)
|
|
points = []
|
|
for x, y in ((-half_w, -half_h), (half_w, -half_h), (half_w, half_h), (-half_w, half_h)):
|
|
points.append(
|
|
(
|
|
cx + x * math.cos(angle) - y * math.sin(angle),
|
|
cy + x * math.sin(angle) + y * math.cos(angle),
|
|
)
|
|
)
|
|
draw.polygon(points, fill=colors[class_id], outline=(25, 25, 25), width=2)
|
|
else:
|
|
draw.rectangle(box, fill=colors[class_id], outline=(25, 25, 25), width=2)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
image.save(path)
|
|
|
|
|
|
def normalized_box(box: tuple[int, int, int, int]) -> tuple[float, float, float, float]:
|
|
left, top, right, bottom = box
|
|
return (
|
|
(left + right) / 2 / IMAGE_SIZE,
|
|
(top + bottom) / 2 / IMAGE_SIZE,
|
|
(right - left) / IMAGE_SIZE,
|
|
(bottom - top) / IMAGE_SIZE,
|
|
)
|
|
|
|
|
|
def write_yaml(root: Path, task: str, extra: str = "") -> None:
|
|
yaml_text = (
|
|
f"path: {root.resolve()}\n"
|
|
"train: images/train\n"
|
|
"val: images/val\n"
|
|
"names:\n"
|
|
" 0: red_shape\n"
|
|
" 1: blue_shape\n"
|
|
f"{extra}"
|
|
)
|
|
(root / f"{task}.yaml").write_text(yaml_text, encoding="utf-8")
|
|
|
|
|
|
def create_detection_style(base: Path, task: str) -> None:
|
|
root = base / task
|
|
for split, count in (("train", TRAIN_IMAGES), ("val", VAL_IMAGES)):
|
|
for index in range(count):
|
|
sample = index if split == "train" else index + TRAIN_IMAGES
|
|
image_path = root / "images" / split / f"sample_{sample:02d}.png"
|
|
label_path = root / "labels" / split / f"sample_{sample:02d}.txt"
|
|
make_image(image_path, sample, rotated=task == "obb")
|
|
class_id, box = image_geometry(sample)
|
|
cx, cy, width, height = normalized_box(box)
|
|
|
|
if task == "detect":
|
|
label = f"{class_id} {cx:.6f} {cy:.6f} {width:.6f} {height:.6f}\n"
|
|
elif task == "segment":
|
|
left, top, right, bottom = (value / IMAGE_SIZE for value in box)
|
|
label = (
|
|
f"{class_id} {left:.6f} {top:.6f} {right:.6f} {top:.6f} "
|
|
f"{right:.6f} {bottom:.6f} {left:.6f} {bottom:.6f}\n"
|
|
)
|
|
elif task == "pose":
|
|
class_id = 0
|
|
points = (
|
|
(cx, cy - height * 0.25),
|
|
(cx - width * 0.25, cy),
|
|
(cx + width * 0.25, cy),
|
|
(cx, cy + height * 0.25),
|
|
)
|
|
keypoints = " ".join(f"{x:.6f} {y:.6f} 2" for x, y in points)
|
|
label = f"{class_id} {cx:.6f} {cy:.6f} {width:.6f} {height:.6f} {keypoints}\n"
|
|
elif task == "obb":
|
|
angle = math.radians(15 if class_id == 0 else -15)
|
|
center_x, center_y = 64 + (sample % 3) * 3, 64
|
|
half_w, half_h = 37, 23
|
|
points = []
|
|
for x, y in ((-half_w, -half_h), (half_w, -half_h), (half_w, half_h), (-half_w, half_h)):
|
|
px = center_x + x * math.cos(angle) - y * math.sin(angle)
|
|
py = center_y + x * math.sin(angle) + y * math.cos(angle)
|
|
points.extend((px / IMAGE_SIZE, py / IMAGE_SIZE))
|
|
label = f"{class_id} " + " ".join(f"{value:.6f}" for value in points) + "\n"
|
|
else:
|
|
raise ValueError(f"Unsupported task: {task}")
|
|
|
|
label_path.parent.mkdir(parents=True, exist_ok=True)
|
|
label_path.write_text(label, encoding="utf-8")
|
|
|
|
if task == "pose":
|
|
write_yaml(
|
|
root,
|
|
task,
|
|
extra=(
|
|
"kpt_shape: [4, 3]\n"
|
|
"flip_idx: [0, 2, 1, 3]\n"
|
|
),
|
|
)
|
|
else:
|
|
write_yaml(root, task)
|
|
|
|
|
|
def create_classification(base: Path) -> None:
|
|
root = base / "classify"
|
|
for split, count in (("train", TRAIN_IMAGES), ("val", 4)):
|
|
for index in range(count):
|
|
sample = index if split == "train" else index + TRAIN_IMAGES
|
|
class_id = sample % 2
|
|
make_image(root / split / ("red_shape" if class_id == 0 else "blue_shape") / f"sample_{sample:02d}.png", sample)
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--output", type=Path, default=Path("datasets/yolo26_smoke"))
|
|
parser.add_argument("--force", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
if args.output.exists():
|
|
if not args.force:
|
|
raise SystemExit(f"Dataset already exists: {args.output}; use --force to recreate it")
|
|
shutil.rmtree(args.output)
|
|
|
|
for task in ("detect", "segment", "pose", "obb"):
|
|
create_detection_style(args.output, task)
|
|
create_classification(args.output)
|
|
print(args.output.resolve())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|