Skip to content

Commit

Permalink
add example for object detection
Browse files Browse the repository at this point in the history
  • Loading branch information
tianweidut committed Dec 3, 2023
1 parent 2f714e6 commit be7b020
Show file tree
Hide file tree
Showing 13 changed files with 604 additions and 0 deletions.
49 changes: 49 additions & 0 deletions example/object-detection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Object Detection
======

Object detection is a computer vision technique for locating instances of objects in images or videos. Object detection algorithms typically leverage machine learning or deep learning to produce meaningful results.

In these examples, we will use Starwhale to evaluate a set of object detection models on COCO datasets.

Thanks to [ultralytics](https://github.com/ultralytics/ultralytics), it makes Starwhale Model Evaluation on YOLO easily.

Links
------

- Github Example Code: <https://github.com/star-whale/starwhale/tree/main/example/object-detection>
- Starwhale Cloud Demo: <https://cloud.starwhale.cn/projects/397/overview>

What we learn
------

- build Starwhale Dataset by Starwhale Python SDK and use Starwhale Dataset Web Viewer.

Models
------

- [YOLO](https://docs.ultralytics.com/): We will compare YOLOv8-{n,s,m,l,x} and YOLOv6-{n,s,m,l,l6} model evaluations.

Datasets
------

- [COCO128](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/cfg/datasets/coco128.yaml)

- Introduction: Ultralytics COCO8 is a small, but versatile object detection dataset composed of the first 128 images of the COCO train 2017 set. This dataset is ideal for testing and debugging object detection models.
- Size: Validation images 128.
- Dataset build command:

```bash
swcli runtime activate object-detection
python3 datasets/coco128.py
```

- [COCO_val2017](https://cocodataset.org/#download)

- Introduction: The COCO (Common Objects in Context) dataset is a large-scale object detection, segmentation, and captioning dataset. It is designed to encourage research on a wide variety of object categories and is commonly used for benchmarking computer vision models. The dataset comprises 80 object categories.
- Size: Validation images 5,000.
- Dataset build command:

```bash
swcli runtime activate object-detection
python3 datasets/coco_val2017.py
```
1 change: 1 addition & 0 deletions example/object-detection/datasets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data/*
76 changes: 76 additions & 0 deletions example/object-detection/datasets/coco128.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

from pathlib import Path

from utils import download, extract_zip, get_name_by_coco_category_id

from starwhale import Image, dataset, BoundingBox, init_logger
from starwhale.utils import console

init_logger(3)

ROOT = Path(__file__).parent
DATA_DIR = ROOT / "data" / "coco128"

# Copy from https://www.kaggle.com/datasets/ultralytics/coco128.


def build() -> None:
_zip_path = DATA_DIR / "coco128.zip"
download("https://ultralytics.com/assets/coco128.zip", _zip_path)
extract_zip(
_zip_path, DATA_DIR, DATA_DIR / "coco129/images/train2017/000000000650.jpg"
)

with dataset("coco128") as ds:
for img_path in (DATA_DIR / "coco128/images/train2017").glob("*.jpg"):
name = img_path.name.split(".jpg")[0]

# YOLO Darknet format: https://docs.plainsight.ai/labels/exporting-labels/yolo
# Format: <object-class> <x_center> <y_center> <width> <height>
# Meaning: object-class> - zero-based index representing the class in obj.names from 0 to (classes-1).
# <x_center> <y_center> <width> <height> - float values relative to width and height of image, it can be equal from (0.0 to 1.0].
# <x_center> = <absolute_x> / <image_width>
# <height> = <absolute_height> / <image_height>

annotations = []
image = Image(img_path)
i_width, i_height = image.to_pil().size

label_path = DATA_DIR / "coco128/labels/train2017" / f"{name}.txt"
if not label_path.exists():
continue

for line in label_path.read_text().splitlines():
class_id, x, y, w, h = line.split()
class_id, x, y, w, h = (
int(class_id),
float(x),
float(y),
float(w),
float(h),
)
annotations.append(
{
"class_id": class_id,
"class_name": get_name_by_coco_category_id(class_id),
"darknet_bbox": [x, y, w, h],
"bbox": BoundingBox(
x=(x - w / 2) * i_width,
y=(y - h / 2) * i_height,
width=w * i_width,
height=h * i_height,
),
}
)

ds[name] = {"image": image, "annotations": annotations}

console.print("commit dataset...")
ds.commit()

console.print(f"{ds} has been built successfully!")


if __name__ == "__main__":
build()
76 changes: 76 additions & 0 deletions example/object-detection/datasets/coco_val2017.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

import json
from pathlib import Path
from collections import defaultdict

from tqdm import tqdm
from utils import download, extract_zip, get_name_by_coco_category_id
from ultralytics.data.converter import coco91_to_coco80_class

from starwhale import Image, dataset, init_logger
from starwhale.utils import console
from starwhale.base.data_type import BoundingBox

init_logger(3)

ROOT = Path(__file__).parent
DATA_DIR = ROOT / "data" / "coco2017"

# The coco2017 val set is from https://cocodataset.org/#download.


def build() -> None:
_zip_path = DATA_DIR / "val2017.zip"
download(
"https://starwhale-examples.oss-cn-beijing.aliyuncs.com/dataset/coco2017/val2017.zip",
_zip_path,
)
extract_zip(_zip_path, DATA_DIR, DATA_DIR / "val2017/000000000139.jpg")

_zip_path = DATA_DIR / "annotations_trainval2017.zip"
download(
"https://starwhale-examples.oss-cn-beijing.aliyuncs.com/dataset/coco2017/annotations_trainval2017.zip",
_zip_path,
)
json_path = DATA_DIR / "annotations/instances_val2017.json"
extract_zip(_zip_path, DATA_DIR, json_path)

coco_classes = coco91_to_coco80_class()

with json_path.open() as f:
content = json.load(f)
annotations = defaultdict(list)
for ann in content["annotations"]:
class_id = coco_classes[ann["category_id"] - 1]
annotations[ann["image_id"]].append(
{
"bbox": BoundingBox(*ann["bbox"]),
"class_id": class_id,
"class_name": get_name_by_coco_category_id(class_id),
}
)

with dataset("coco_val2017") as ds:
for image in tqdm(content["images"]):
name = image["file_name"].split(".jpg")[0]
for ann in annotations[image["id"]]:
bbox = ann["bbox"]
ann["darknet_bbox"] = [
(bbox.x + bbox.width / 2) / image["width"],
(bbox.y + bbox.height / 2) / image["height"],
bbox.width / image["width"],
bbox.height / image["height"],
]
ds[name] = {
"image": Image(DATA_DIR / "val2017" / image["file_name"]),
"annotations": annotations[image["id"]],
}
console.print("commit dataset...")
ds.commit()

console.print(f"{ds} has been built successfully!")


if __name__ == "__main__":
build()
135 changes: 135 additions & 0 deletions example/object-detection/datasets/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
from __future__ import annotations

import zipfile
from pathlib import Path

import requests
from tqdm import tqdm

from starwhale.utils import console

_COCO_CLASSES_MAP = {
0: "person",
1: "bicycle",
2: "car",
3: "motorcycle",
4: "airplane",
5: "bus",
6: "train",
7: "truck",
8: "boat",
9: "traffic light",
10: "fire hydrant",
11: "stop sign",
12: "parking meter",
13: "bench",
14: "bird",
15: "cat",
16: "dog",
17: "horse",
18: "sheep",
19: "cow",
20: "elephant",
21: "bear",
22: "zebra",
23: "giraffe",
24: "backpack",
25: "umbrella",
26: "handbag",
27: "tie",
28: "suitcase",
29: "frisbee",
30: "skis",
31: "snowboard",
32: "sports ball",
33: "kite",
34: "baseball bat",
35: "baseball glove",
36: "skateboard",
37: "surfboard",
38: "tennis racket",
39: "bottle",
40: "wine glass",
41: "cup",
42: "fork",
43: "knife",
44: "spoon",
45: "bowl",
46: "banana",
47: "apple",
48: "sandwich",
49: "orange",
50: "broccoli",
51: "carrot",
52: "hot dog",
53: "pizza",
54: "donut",
55: "cake",
56: "chair",
57: "couch",
58: "potted plant",
59: "bed",
60: "dining table",
61: "toilet",
62: "tv",
63: "laptop",
64: "mouse",
65: "remote",
66: "keyboard",
67: "cell phone",
68: "microwave",
69: "oven",
70: "toaster",
71: "sink",
72: "refrigerator",
73: "book",
74: "clock",
75: "vase",
76: "scissors",
77: "teddy bear",
78: "hair drier",
79: "toothbrush",
}


def get_name_by_coco_category_id(category_id: int | None) -> str:
return (
_COCO_CLASSES_MAP[category_id] if category_id is not None else "uncategorized"
)


def extract_zip(from_path: Path, to_path: Path, chk_path: Path) -> None:
if chk_path.exists():
console.log(f"skip extract {from_path}, dir {chk_path} already exists")
return

with zipfile.ZipFile(from_path, "r", zipfile.ZIP_STORED) as z:
for file in tqdm(
iterable=z.namelist(),
total=len(z.namelist()),
desc=f"extract {from_path.name}",
):
z.extract(member=file, path=to_path)


def download(url: str, to_path: Path) -> None:
if to_path.exists():
console.log(f"skip download {url}, file {to_path} already exists")
return

to_path.parent.mkdir(parents=True, exist_ok=True)

with requests.get(url, timeout=60, stream=True) as r:
r.raise_for_status()
size = int(r.headers.get("content-length", 0))
with tqdm(
iterable=r.iter_content(chunk_size=1024),
total=size,
unit="B",
unit_scale=True,
desc=f"download {url}",
) as pbar:
with open(to_path, "wb") as f:
for chunk in pbar:
f.write(chunk)
pbar.update(len(chunk))
3 changes: 3 additions & 0 deletions example/object-detection/models/yolo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
checkpoints/*
runs/*
flagged/*
2 changes: 2 additions & 0 deletions example/object-detection/models/yolo/.swignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
runs/*
checkpoints/cache/*
Empty file.
Loading

0 comments on commit be7b020

Please sign in to comment.