YOLOv8 Save Predictions to JSON: Complete Guide

Saving YOLOv8 predictions to JSON is useful when detection results need to be stored, transferred, analyzed, indexed, or consumed by another application. Current Ultralytics Results objects provide a built-in to_json() method that exports prediction results as a JSON-formatted string. The method supports options such as normalized values and configurable decimal precision.

A simple YOLOv8 JSON workflow looks like:

from ultralytics import YOLO

model = YOLO("yolov8n.pt")

results = model("image.jpg")

json_data = results[0].to_json()

with open("predictions.json", "w") as f:
    f.write(json_data)

For standard detection, the exported information can represent object coordinates, class information, confidence scores, and task-specific prediction data. For segmentation, pose, and OBB models, the structure changes to include relevant polygons, keypoints, or oriented bounding-box information.

Table of Contents

Introduction to Saving YOLOv8 Predictions as JSON

YOLOv8 normally returns structured prediction objects rather than plain text. In Python, every processed image or frame receives its own Results object. That object can contain bounding boxes, masks, keypoints, oriented boxes, class mappings, paths, and other task-dependent information.

JSON provides a convenient way to transform those predictions into a portable text format.

A typical workflow is:

Input Image
    ↓
YOLOv8 Prediction
    ↓
Results Object
    ↓
Convert to JSON
    ↓
Save to File / API / Database

JSON is especially useful for applications such as:

REST APIs
web dashboards
analytics pipelines
event storage
database ingestion
dataset review tools
automation systems

Instead of trying to serialize raw PyTorch tensors manually, you can either use Ultralytics’ built-in to_json() method or construct your own Python dictionaries after converting tensor values to standard Python types.

What Does YOLOv8 Prediction Output Contain?

The exact prediction output depends on the model task.

For ordinary object detection, each prediction usually contains:

bounding-box coordinates
class ID
class name
confidence score

Current Ultralytics detection results store raw boxes with the structure:

x1, y1, x2, y2, confidence, class

inside the result’s box data.

The Results object also provides the class-name mapping and information about the source image.

Bounding Box Coordinates

Bounding boxes describe where detected objects appear.

For example:

x1 = 120
y1 = 80
x2 = 430
y2 = 610

In Python:

result = results[0]

print(result.boxes.xyxy)

You can also use:

result.boxes.xywh

for center-based coordinates.

Normalized variants include:

result.boxes.xyxyn
result.boxes.xywhn

This makes it possible to choose the coordinate representation that best fits your JSON schema.

Class IDs and Class Names

Class IDs are available through:

result.boxes.cls

For example:

for box in result.boxes:
    class_id = int(box.cls[0])
    print(class_id)

Human-readable class names are available through:

result.names

Example:

for box in result.boxes:
    class_id = int(box.cls[0])
    class_name = result.names[class_id]

    print(class_name)

The Results object exposes names as a mapping between class indices and model class names.

Confidence Scores

Confidence scores are stored in:

result.boxes.conf

Example:

for box in result.boxes:
    confidence = float(box.conf[0])
    print(confidence)

A detection may conceptually look like:

{
  "class_id": 2,
  "class_name": "car",
  "confidence": 0.91
}

Confidence can also be used to filter predictions before they are written to JSON.

How to Save YOLOv8 Predictions to JSON

Current Ultralytics makes JSON export straightforward through the Results.to_json() method.

The method returns a JSON-formatted string rather than automatically choosing your output filename.

This gives you control over where and how the JSON data is stored.

Run Inference with the Python API

Start by loading the model:

from ultralytics import YOLO

model = YOLO("yolov8n.pt")

Run prediction:

results = model("image.jpg")

Then select the result:

result = results[0]

At this point, you can access:

result.boxes
result.names
result.orig_shape
result.path

depending on what information your JSON output should contain.

Convert Prediction Results to JSON

The simplest method is:

json_output = result.to_json()

Current Ultralytics documents:

to_json(normalize=False, decimals=5)

where:

normalize=False
→ keeps non-normalized numeric values

normalize=True
→ normalizes supported numeric values

decimals=5
→ controls decimal precision

The method returns a JSON string.

For example:

json_output = result.to_json(
    normalize=False,
    decimals=4
)

Save JSON Output to a File

Write the returned string directly:

with open(
    "predictions.json",
    "w"
) as f:
    f.write(
        result.to_json()
    )

You can also specify UTF-8 explicitly:

with open(
    "predictions.json",
    "w",
    encoding="utf-8"
) as f:
    f.write(
        result.to_json()
    )

This is preferable to trying:

json.dump(result.boxes)

because the raw Boxes object contains tensor-backed data that standard Python JSON serialization does not understand directly.

Understanding YOLOv8 JSON Output Structure

The exact JSON structure produced by current Ultralytics is generated from the Results summary through its data-export utilities. The to_json() method converts the result summary into a structured tabular representation and serializes it as JSON.

The exact fields depend on task type.

For detection, expect object-level records representing the detected instances.

Image Information

If you are designing your own combined JSON structure, it is useful to include image-level metadata.

For example:

{
  "image": "image.jpg",
  "width": 1920,
  "height": 1080,
  "detections": []
}

The image path is available through:

result.path

and image dimensions through:

result.orig_shape

Current Ultralytics stores orig_shape as:

(height, width)

inside the Results object.

Detection Objects

A useful custom detection structure might look like:

{
  "class_id": 0,
  "class_name": "person",
  "confidence": 0.9421,
  "bbox": {
    "x1": 120.5,
    "y1": 82.1,
    "x2": 410.9,
    "y2": 702.0
  }
}

If several objects are detected:

{
  "image": "image.jpg",
  "detections": [
    {
      "class_id": 0,
      "class_name": "person"
    },
    {
      "class_id": 2,
      "class_name": "car"
    }
  ]
}

This kind of schema is often easier for web applications than storing raw tensor arrays.

Bounding Boxes, Classes, and Confidence Values

A custom Python conversion can be written as:

detections = []

for box in result.boxes:

    x1, y1, x2, y2 = (
        box.xyxy[0]
        .cpu()
        .tolist()
    )

    class_id = int(
        box.cls[0]
    )

    confidence = float(
        box.conf[0]
    )

    detections.append({
        "class_id": class_id,
        "class_name": result.names[class_id],
        "confidence": confidence,
        "bbox": {
            "x1": x1,
            "y1": y1,
            "x2": x2,
            "y2": y2
        }
    })

Then:

import json

with open(
    "predictions.json",
    "w"
) as f:
    json.dump(
        detections,
        f,
        indent=2
    )

Save Multiple Image Predictions to JSON

When processing several images, YOLOv8 returns one Results object per input image.

You therefore need to decide whether to create:

one JSON file per image

or:

one combined JSON file

for the complete collection.

Process a Folder of Images

Run:

from ultralytics import YOLO

model = YOLO("yolov8n.pt")

results = model.predict(
    source="images/"
)

You can iterate through each image:

for result in results:
    print(result.path)
    print(result.boxes)

Current Ultralytics Predict mode returns one Results object for each image or video frame.

Store Results for Each Image

A simple custom structure is:

all_results = []

for result in results:

    detections = []

    for box in result.boxes:

        class_id = int(
            box.cls[0]
        )

        detections.append({
            "class_id": class_id,
            "class_name": result.names[class_id],
            "confidence": float(box.conf[0]),
            "xyxy": box.xyxy[0]
                .cpu()
                .tolist()
        })

    all_results.append({
        "image": result.path,
        "detections": detections
    })

Now each image retains its own prediction group.

Create a Single Combined JSON File

Save:

import json

with open(
    "all_predictions.json",
    "w",
    encoding="utf-8"
) as f:
    json.dump(
        all_results,
        f,
        indent=2
    )

The resulting file could look like:

[
  {
    "image": "images/image1.jpg",
    "detections": [
      {
        "class_id": 0,
        "class_name": "person",
        "confidence": 0.91,
        "xyxy": [100, 80, 350, 640]
      }
    ]
  },
  {
    "image": "images/image2.jpg",
    "detections": []
  }
]

This format is useful when one JSON document must represent an entire inference job.

Save Video Predictions to JSON

Videos contain many frames, so prediction results should normally be processed incrementally.

Using:

stream=True

prevents every frame’s Results object from being retained in memory at once.

For long videos, this is usually more efficient.

Process Video Frames

Example:

from ultralytics import YOLO

model = YOLO("yolov8n.pt")

results = model.predict(
    source="video.mp4",
    stream=True
)

for result in results:
    print(result.boxes)

Each yielded result corresponds to a processed frame.

Store Frame Numbers and Detections

Example:

import json
from ultralytics import YOLO

model = YOLO("yolov8n.pt")

video_results = []

for frame_id, result in enumerate(
    model.predict(
        source="video.mp4",
        stream=True
    )
):

    detections = []

    if result.boxes is not None:

        for box in result.boxes:

            class_id = int(
                box.cls[0]
            )

            detections.append({
                "class_id": class_id,
                "class_name": result.names[class_id],
                "confidence": float(box.conf[0]),
                "xyxy": box.xyxy[0]
                    .cpu()
                    .tolist()
            })

    video_results.append({
        "frame": frame_id,
        "detections": detections
    })

with open(
    "video_predictions.json",
    "w"
) as f:
    json.dump(
        video_results,
        f,
        indent=2
    )

For very long videos or continuous cameras, it is better to write results incrementally instead of accumulating the entire video_results list.

Save Tracking IDs with Predictions

If Track mode is used:

results = model.track(
    source="video.mp4",
    stream=True,
    persist=True
)

tracking IDs can be available through:

result.boxes.id

Current Ultralytics tracked bounding boxes can include a track ID in addition to coordinates, confidence, and class information.

Example:

for box in result.boxes:

    track_id = None

    if box.id is not None:
        track_id = int(
            box.id[0]
        )

    record = {
        "track_id": track_id,
        "class_id": int(box.cls[0]),
        "confidence": float(box.conf[0]),
        "bbox": box.xyxy[0]
            .cpu()
            .tolist()
    }

This is useful for trajectory and object-history analysis.

JSON Output for Different YOLOv8 Tasks

JSON export is not limited to object detection.

Current Ultralytics Results supports task-dependent structures for detection, segmentation, pose, and oriented bounding boxes. The built-in data export system is designed to export structured results for multiple supported prediction tasks.

Object Detection JSON

Detection JSON typically represents:

class
class name
confidence
bounding box

A simple custom example is:

{
  "class_id": 2,
  "class_name": "car",
  "confidence": 0.91,
  "bbox": [
    110,
    170,
    620,
    540
  ]
}

The standard detection Results object uses result.boxes.

Segmentation JSON

Segmentation models add:

result.masks

Current Ultralytics exposes:

result.masks.data
result.masks.xy
result.masks.xyn

where xy contains polygon points in pixel coordinates and xyn contains normalized polygons.

For JSON, polygons are usually more compact than storing every binary mask pixel.

Example:

segments = []

for polygon in result.masks.xy:
    segments.append(
        polygon.tolist()
    )

Then:

{
  "class_name": "person",
  "polygon": [
    [102.1, 80.2],
    [110.5, 91.3],
    [120.8, 108.5]
  ]
}

Pose Keypoint JSON

Pose results use:

result.keypoints

Current Ultralytics exposes:

result.keypoints.xy
result.keypoints.xyn
result.keypoints.data

for pose coordinates and optional visibility/confidence-related values.

Example:

keypoints = (
    result.keypoints.xy
    .cpu()
    .tolist()
)

A custom JSON structure can be:

{
  "person": 0,
  "keypoints": [
    [220.4, 115.2],
    [214.1, 109.8],
    [229.6, 110.1]
  ]
}

OBB JSON

OBB models use:

result.obb

Current Ultralytics provides:

result.obb.xywhr
result.obb.xyxyxyxy
result.obb.conf
result.obb.cls

The xywhr structure contains:

center x
center y
width
height
rotation

while xyxyxyxy contains four corner points.

Example:

corners = (
    result.obb.xyxyxyxy
    .cpu()
    .tolist()
)

This can be stored directly as JSON-compatible polygon geometry.

Customize YOLOv8 JSON Predictions

The built-in to_json() method is convenient, but custom JSON generation gives you complete control over:

which classes are stored
which confidence levels are retained
which coordinate format is used
what metadata is included

This is useful when an external application expects a particular JSON schema.

Save Only Selected Classes

Suppose only class ID 0 is needed.

Use:

selected = []

for box in result.boxes:

    class_id = int(
        box.cls[0]
    )

    if class_id != 0:
        continue

    selected.append({
        "class_id": class_id,
        "confidence": float(box.conf[0]),
        "bbox": box.xyxy[0]
            .cpu()
            .tolist()
    })

You can also filter multiple classes:

allowed_classes = {
    0,
    2,
    5
}

then:

if class_id not in allowed_classes:
    continue

Filter Predictions by Confidence

Filter during prediction:

results = model.predict(
    source="image.jpg",
    conf=0.50
)

or after prediction:

for box in result.boxes:

    confidence = float(
        box.conf[0]
    )

    if confidence < 0.70:
        continue

Using post-result filtering is useful when the JSON threshold differs from the threshold used for the original prediction workflow.

Change Bounding Box Coordinate Format

Pixel XYXY:

box.xyxy

Pixel XYWH:

box.xywh

Normalized XYXY:

box.xyxyn

Normalized XYWH:

box.xywhn

Example JSON using normalized center coordinates:

bbox = (
    box.xywhn[0]
    .cpu()
    .tolist()
)

You could then store:

{
  "bbox_format": "xywhn",
  "bbox": [
    0.51,
    0.43,
    0.22,
    0.35
  ]
}

The built-in to_json() method also supports a normalize=True option.

YOLOv8 JSON vs TXT and CSV Output

JSON, TXT, and CSV are all useful, but they serve different purposes.

JSON is hierarchical and flexible. CSV is tabular. YOLO TXT labels are compact and useful for annotation-style workflows.

Current Ultralytics exports prediction and metrics data through structured export utilities including JSON and CSV.

Advantages of JSON Format

JSON is particularly useful when each detection contains nested information.

For example:

{
  "image": "frame001.jpg",
  "detections": [
    {
      "class": "person",
      "confidence": 0.94,
      "bbox": {
        "x1": 100,
        "y1": 50,
        "x2": 300,
        "y2": 600
      }
    }
  ]
}

JSON is a strong choice for:

APIs
web applications
NoSQL storage
nested segmentation data
pose keypoints
tracking events
multi-image metadata

It is also easy to read in JavaScript, Python, PHP, Java, and many other languages.

When to Use TXT or CSV Instead

TXT is useful when you need compact YOLO-style annotations.

A detection label may look like:

0 0.50 0.45 0.20 0.35

CSV is better when the data is flat and table-oriented:

image,class,confidence,x1,y1,x2,y2

For example:

img1.jpg,person,0.92,100,80,300,600
img1.jpg,car,0.85,400,200,800,550

CSV is convenient for:

Excel
spreadsheets
simple analytics
SQL imports

while JSON is more appropriate when each image contains a variable number of nested detections.

Common Problems When Saving Predictions to JSON

JSON serialization errors usually occur when raw PyTorch or NumPy values are passed directly to Python’s standard json module.

Built-in Ultralytics to_json() avoids much of this problem by converting prediction results through its export layer first.

Tensor Is Not JSON Serializable

This will fail:

import json

data = {
    "box": result.boxes.xyxy[0]
}

json.dumps(data)

because:

torch.Tensor

is not a standard JSON type.

Convert it first:

data = {
    "box": result.boxes.xyxy[0]
        .cpu()
        .tolist()
}

Likewise:

confidence = float(
    box.conf[0]
)

and:

class_id = int(
    box.cls[0]
)

produce standard Python types.

NumPy Values Cause Serialization Errors

A NumPy scalar such as:

np.float32

may also fail with standard json.dumps().

Convert:

value = float(
    numpy_value
)

For arrays:

array.tolist()

is the normal conversion.

Alternatively, use Ultralytics:

result.to_json()

which handles result export for you.

Empty Detection Results

An image can produce a valid Results object with no detections.

For example:

if result.boxes is None or len(result.boxes) == 0:
    detections = []

Your JSON can still represent the image:

{
  "image": "empty_scene.jpg",
  "detections": []
}

An empty detection list is not necessarily an error. It simply means no predictions survived filtering.

Incorrect Bounding Box Format

A common mistake is mixing:

xyxy

with:

xywh

For example:

[100, 80, 400, 600]

in XYXY means:

left = 100
top = 80
right = 400
bottom = 600

while in XYWH it means:

center x = 100
center y = 80
width = 400
height = 600

Always include the coordinate format in a custom API or JSON schema if another application will consume the output.

FAQs About Saving YOLOv8 Predictions to JSON

Can YOLOv8 save predictions directly to JSON?

Yes.

Current Ultralytics Results objects provide:

result.to_json()

which returns a JSON-formatted string representing the prediction result.

You can write it directly:

with open(
    "results.json",
    "w"
) as f:
    f.write(
        result.to_json()
    )

How do I convert YOLOv8 results to JSON?

Use:

json_data = result.to_json()

Optional settings include:

result.to_json(
    normalize=True,
    decimals=4
)

Current Ultralytics documents both normalize and decimals as arguments for JSON export.

What information is included in YOLOv8 JSON output?

The information depends on the task.

Detection can contain:

class
class name
confidence
bounding box

Segmentation can include polygon information, pose can include keypoint data, and OBB output can include rotated-box geometry.

Can I save segmentation masks in JSON?

Yes, although storing complete binary masks as JSON is usually inefficient.

A better option is often to store segmentation polygons:

result.masks.xy

or normalized polygons:

result.masks.xyn

Current Ultralytics exposes both polygon forms as part of segmentation Results.

Convert polygons with:

polygon.tolist()

before using Python’s standard JSON serializer.

Can YOLOv8 tracking IDs be saved to JSON?

Yes.

When track IDs are present:

result.boxes.id

contains them.

Current Ultralytics tracked boxes include tracking IDs as an additional field in the bounding-box data structure.

Example:

track_id = None

if box.id is not None:
    track_id = int(
        box.id[0]
    )

Then store it normally in JSON.

How do I save predictions from multiple images in one JSON file?

Process every Results object and append its predictions to one Python list:

combined = []

for result in results:
    combined.append({
        "image": result.path,
        "detections": result.summary()
    })

Then:

import json

with open(
    "combined.json",
    "w"
) as f:
    json.dump(
        combined,
        f,
        indent=2
    )

Current Results provides summary() as a structured representation that can also be used by its JSON, CSV, and DataFrame export methods.

Why are YOLOv8 tensors not JSON serializable?

PyTorch tensors are Python objects containing numerical data and device information, but standard JSON supports only basic types such as:

string
number
boolean
null
list
object

Therefore:

box.xyxy

must be converted using something such as:

box.xyxy.cpu().tolist()

before json.dumps() can serialize it.

Using:

result.to_json()

is usually simpler when the built-in Ultralytics output format is acceptable.

Conclusion

Saving YOLOv8 predictions to JSON is straightforward with the current Ultralytics Results API.

The simplest workflow is:

from ultralytics import YOLO

model = YOLO("yolov8n.pt")

results = model("image.jpg")

result = results[0]

with open(
    "predictions.json",
    "w"
) as f:
    f.write(
        result.to_json()
    )

Current Ultralytics exposes:

result.to_json(
    normalize=False,
    decimals=5
)

as a built-in JSON export method. It returns a JSON-formatted string generated from the structured prediction summary.

For custom schemas, a practical workflow is:

Run Prediction
      ↓
Read Results Object
      ↓
Extract Boxes / Masks / Keypoints / OBB
      ↓
Convert Tensor Values
      ↓
Add Image or Frame Metadata
      ↓
Filter Classes and Confidence
      ↓
Create Python Dictionaries
      ↓
Save with json.dump()

For normal detection:

for box in result.boxes:

    class_id = int(
        box.cls[0]
    )

    record = {
        "class_id": class_id,
        "class_name": result.names[class_id],
        "confidence": float(box.conf[0]),
        "xyxy": box.xyxy[0]
            .cpu()
            .tolist()
    }

For segmentation, polygons are available through result.masks.xy and result.masks.xyn. For pose, keypoints are available through result.keypoints.xy, xyn, and data. For OBB models, rotated boxes are accessible through result.obb.xywhr and result.obb.xyxyxyxy.

For video and tracking workloads, include:

frame number
tracking ID
class
confidence
coordinates

so detections remain associated with both time and object identity.

JSON is usually the best choice when YOLOv8 predictions need to be consumed by APIs, dashboards, databases, tracking systems, or other applications because it can represent image-level metadata and multiple nested detections in a single structured format.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top