Scene Text OBB Dataset
The Scene Text OBB dataset combines COCO-Text and HierText into a single-class word-detection dataset for Oriented Bounding Box (OBB) training. It contains natural-scene and document images with rotated, curved, densely packed, handwritten, and printed text.
Key Features#
- 65,325 images and 1,288,991 legible word annotations after conversion.
- One
textclass with normalized polygon labels compatible with Ultralytics YOLO OBB models. - COCO-Text contributes varied natural scenes; HierText contributes dense text from natural scenes and documents.
- Automatic source downloads, annotation conversion, and cross-platform image linking through the dataset YAML.
- Source polygons with more than four points are preserved and reduced to minimum-area rectangles by the OBB data pipeline.
Dataset Structure#
The converted dataset has the following splits. Counts include only legible words with a contour area of at least one pixel; images without their source files are excluded.
| Split | COCO-Text images / words | HierText images / words | Combined images / words |
|---|---|---|---|
| Train | 43,686 / 65,919 | 8,281 / 862,577 | 51,967 / 928,496 |
| Val | 4,927 / 7,211 | 1,724 / 178,382 | 6,651 / 185,593 |
| Test | 5,073 / 7,714 | 1,634 / 167,188 | 6,707 / 174,902 |
| Total | 53,686 / 80,844 | 11,639 / 1,208,147 | 65,325 / 1,288,991 |
COCO-Text uses its official training split. Its official validation images are partitioned deterministically into approximately balanced validation and test subsets using the image ID. HierText retains its official train, validation, and test splits, with validation renamed to val.
Images with no retained words remain in the dataset with empty label files and serve as background images. The converted directories use this structure:
datasets/
└── scene-text-yolo/
├── coco-text/
│ ├── train/
│ │ ├── images/
│ │ └── labels/
│ ├── val/
│ └── test/
└── hiertext/
├── train/
├── val/
└── test/Each label row contains class 0 followed by normalized polygon coordinates. Images are hard-linked when the filesystem supports it and copied otherwise.
Dataset YAML#
The scene-text-obb.yaml configuration references both converted datasets. On first use, it downloads COCO 2017 images, COCO-Text annotations, HierText annotations, and HierText images before converting them.
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
# Scene Text OBB dataset combining COCO-Text and HierText legible word annotations
# COCO-Text: https://bgshih.github.io/cocotext/ (annotations: CC BY 4.0; COCO image terms apply)
# HierText: https://github.com/google-research-datasets/hiertext (CC BY-SA 4.0; Open Images terms apply)
# Documentation: https://docs.ultralytics.com/datasets/obb/scene-text
# Example usage: yolo obb train data=scene-text-obb.yaml model=yolo26n-obb.pt imgsz=640
# parent
# ├── ultralytics
# └── datasets
# ├── coco
# ├── coco-text
# ├── hiertext
# └── scene-text-yolo
# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: scene-text-yolo # dataset root dir
train:
- coco-text/train/images # 43686 images, 65919 words
- hiertext/train/images # 8281 images, 862577 words
val:
- coco-text/val/images # 4927 images, 7211 words
- hiertext/val/images # 1724 images, 178382 words
test:
- coco-text/test/images # 5073 images, 7714 words
- hiertext/test/images # 1634 images, 167188 words
names:
0: text
# Download COCO 2017 images and both annotation sources, then convert legible words to YOLO OBB format (~24 GB)
download: |
import gzip
import hashlib
import json
import os
import shutil
from pathlib import Path
from tempfile import TemporaryDirectory
import cv2
import numpy as np
from ultralytics.data.utils import check_det_dataset
from ultralytics.utils import TQDM
from ultralytics.utils.downloads import download
def polygon_label(vertices, width, height):
polygon = np.asarray(vertices, dtype=np.float32).reshape(-1, 2)
if len(polygon) < 3 or cv2.contourArea(polygon) < 1:
return ""
polygon[:, 0] = polygon[:, 0].clip(0, width - 1)
polygon[:, 1] = polygon[:, 1].clip(0, height - 1)
polygon /= np.array([width, height], dtype=np.float32)
return "0 " + " ".join(f"{x:.7f} {y:.7f}" for x, y in polygon)
def link_image(source, destination):
destination.parent.mkdir(parents=True, exist_ok=True)
try:
os.link(source, destination)
except OSError:
shutil.copy2(source, destination)
def write_labels(label_dir, stem, labels):
label_dir.mkdir(parents=True, exist_ok=True)
(label_dir / f"{stem}.txt").write_text("\n".join(labels) + ("\n" if labels else ""), encoding="utf-8")
def convert_coco_text(parent, destination):
source_dir = parent / "coco-text"
annotations = source_dir / "cocotext.v2.json"
if not annotations.is_file():
download(
"https://github.com/bgshih/cocotext/releases/download/dl/cocotext.v2.zip",
dir=source_dir,
delete=True,
)
coco = check_det_dataset("coco.yaml")
images = Path(coco["path"]) / "images"
data = json.loads(annotations.read_text(encoding="utf-8"))
for image_id, image in TQDM(data["imgs"].items(), desc="Converting COCO-Text"):
split = (
"train"
if image["set"] == "train"
else ("val" if hashlib.sha256(str(image["id"]).encode()).digest()[0] < 128 else "test")
)
filename = image["file_name"].replace("COCO_train2014_", "", 1)
source = images / "train2017" / filename
if not source.exists():
source = images / "val2017" / filename
if not source.exists():
continue
image_dir = destination / split / "images"
link_image(source, image_dir / filename)
im = cv2.imread(str(source))
if im is None:
raise ValueError(f"Unreadable image: {source}")
height, width = im.shape[:2]
labels = []
for ann_id in data["imgToAnns"].get(image_id, []):
ann = data["anns"][str(ann_id)]
if ann["legibility"] == "legible" and (label := polygon_label(ann["mask"], width, height)):
labels.append(label)
write_labels(destination / split / "labels", Path(filename).stem, labels)
def convert_hiertext(parent, destination):
source = parent / "hiertext" / "source"
splits = {"train": "train", "validation": "val", "test": "test"}
base = "https://raw.githubusercontent.com/google-research-datasets/hiertext/main"
if not all((source / "gt" / f"{split}.jsonl.gz").is_file() for split in splits):
download([f"{base}/gt/{split}.jsonl.gz" for split in splits], dir=source / "gt", unzip=False, threads=3)
if not all((source / split).is_dir() for split in splits):
download(
[f"https://open-images-dataset.s3.amazonaws.com/ocr/{split}.tgz" for split in splits],
dir=source,
delete=True,
threads=3,
)
for source_split, split in splits.items():
with gzip.open(source / "gt" / f"{source_split}.jsonl.gz", "rt", encoding="utf-8") as f:
rows = json.load(f)["annotations"]
for row in TQDM(rows, desc=f"Converting HierText {source_split}"):
image_id = row["image_id"]
source_image = source / source_split / f"{image_id}.jpg"
if not source_image.exists():
continue
image = destination / split / "images" / f"{image_id}.jpg"
link_image(source_image, image)
im = cv2.imread(str(image))
if im is None:
raise ValueError(f"Unreadable image: {source_image}")
height, width = im.shape[:2]
labels = []
for paragraph in row["paragraphs"]:
for line in paragraph["lines"]:
for word in line["words"]:
if word["legible"] and (label := polygon_label(word["vertices"], width, height)):
labels.append(label)
write_labels(destination / split / "labels", image_id, labels)
final = Path(yaml["path"])
parent = final.parent
parent.mkdir(parents=True, exist_ok=True)
with TemporaryDirectory(dir=parent, prefix=".scene-text-") as tmp:
destination = Path(tmp)
convert_coco_text(parent, destination / "coco-text")
convert_hiertext(parent, destination / "hiertext")
destination.rename(final)The sources require approximately 24 GB of downloads. Allow at least 50 GB of free storage during the initial download and extraction, including temporary COCO archives. If hard links are unavailable, copied images require additional storage. Review the licensing terms before using or redistributing the converted data.
Usage#
Train a YOLO26 OBB model at 640-pixel image size with the following commands. Existing COCO and HierText source files under the Ultralytics datasets_dir are reused.
from ultralytics import YOLO
model = YOLO("yolo26n-obb.pt")
results = model.train(data="scene-text-obb.yaml", epochs=100, imgsz=640)Applications#
Use this dataset to train word-level text detectors for document processing, receipt understanding, sign translation, industrial inspection, and OCR pipelines. Detection is only one stage of OCR: pair the detector with a text recognizer when transcription is required.
Licensing#
The combined dataset uses source-specific terms rather than a single unconditional license:
- COCO-Text annotations: CC BY 4.0. The underlying COCO image terms remain applicable.
- HierText annotations: CC BY-SA 4.0. Redistributed adaptations require attribution, a change notice, and licensing under the same or a compatible license.
- HierText images: Sourced from Open Images. The Open Images terms list images as CC BY 2.0 and instruct users to verify individual image licenses.
These annotation licenses permit commercial use subject to their conditions, but the dataset is not public domain. Preserve source attribution and review the applicable image terms when redistributing data.
Citations and Acknowledgments#
If you use this dataset in your work, cite both source datasets:
@article{veit2016cocotext,
title={COCO-Text: Dataset and Benchmark for Text Detection and Recognition in Natural Images},
author={Veit, Andreas and Matera, Tomas and Neumann, Lukas and Matas, Jiri and Belongie, Serge},
journal={arXiv preprint arXiv:1601.07140},
year={2016}
}
@inproceedings{long2022towards,
title={Towards End-to-End Unified Scene Text Detection and Layout Analysis},
author={Long, Shangbang and Qin, Siyang and Panteleev, Dmitry and Bissacco, Alessandro and Fujii, Yasuhisa and Raptis, Michalis},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
year={2022}
}We thank the COCO-Text and HierText teams for creating and maintaining these datasets.
FAQ#
No. The conversion produces single-class word-detection polygons for OBB training. Use the original COCO-Text and HierText annotations if your pipeline also requires transcripts.
COCO-Text provides train and validation annotations. This configuration retains the official train split and deterministically divides the official validation images into validation and test subsets. HierText keeps its official splits.
Yes. Place it under the Ultralytics
datasets_dirin the standardcoco/layout, includingimages/train2017,images/val2017, and the COCO split text files. The download script reuses it instead of downloading COCO again.
Comments
Ask a question, share your results, or help someone else.