YOLOv8 crop detected objects workflows let you extract each detected object from an image or video and save it as a separate image file. Instead of saving only a full annotated image, you can use each detection’s bounding box to isolate the corresponding region. Current Ultralytics Predict mode supports this directly with save_crop=True, and the Results.save_crop() method can also save crops into class-specific subdirectories.
Object cropping is useful for dataset creation, OCR pipelines, product extraction, face or vehicle analysis, visual search, secondary classification, and other workflows where the detected object must be processed independently from the full scene.
Introduction to Cropping Detected Objects in YOLOv8
YOLOv8 normally predicts where objects are located by returning bounding boxes. Those boxes can also be used as crop coordinates.
For example:
Input Image
↓
YOLOv8 Detection
↓
Bounding Boxes
↓
Crop Each Box Region
↓
person_1.jpg
car_1.jpg
car_2.jpg
Suppose YOLOv8 detects a car with:
x1 = 120
y1 = 180
x2 = 620
y2 = 520
The corresponding image region can be extracted using those pixel coordinates.
Ultralytics provides two main approaches. You can enable automatic crop saving with save_crop=True, or manually access result.boxes.xyxy and slice the original image yourself. The manual method gives more control over filenames, filtering, padding, crop quality, and duplicate handling.
What Does Cropping Detected Objects Mean in YOLOv8?
Cropping means extracting only the rectangular image region covered by a detected object’s bounding box.
If the original image contains:
person
car
dog
background
and YOLOv8 detects all three objects, cropping can produce:
person crop
car crop
dog crop
as separate images.
The crop normally includes the original pixels inside the detection box. It does not automatically remove the background inside that rectangle. For true object isolation, instance segmentation masks are more appropriate.
How Bounding Boxes Define Crop Areas
YOLOv8 detection results expose bounding boxes through:
result.boxes.xyxy
The format is:
[x1, y1, x2, y2]
where:
x1 = left edge
y1 = top edge
x2 = right edge
y2 = bottom edge
A NumPy or OpenCV image can then be sliced as:
crop = image[y1:y2, x1:x2]
Ultralytics’ own segmentation-object guide demonstrates the same basic technique by reading boxes.xyxy, converting coordinates to integers, and slicing the image region.
Why Object Cropping Is Useful
Cropping can simplify downstream processing because the next system sees only the relevant detected region.
Common uses include:
vehicle crop → license plate OCR
person crop → clothing analysis
product crop → product classification
animal crop → species classifier
document crop → text recognition
It can also help create focused datasets from larger scenes.
For example, if a detector processes 5,000 warehouse images and finds 30,000 boxes, those detections can be converted into individual object images for review or secondary model training.
How to Crop Detected Objects with YOLOv8
The manual cropping workflow consists of three main stages:
Run Detection
↓
Read Bounding Boxes
↓
Crop Original Image
This approach gives you full control over which objects are saved.
Run Object Detection on an Image
Load a model:
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
Run prediction:
results = model("image.jpg")
Select the result:
result = results[0]
You can inspect:
print(result.boxes)
Each box contains coordinates, confidence, and class information.
Extract Bounding Box Coordinates
Loop through detections:
for box in result.boxes:
x1, y1, x2, y2 = (
box.xyxy[0]
.cpu()
.tolist()
)
print(x1, y1, x2, y2)
For image slicing, convert them to integers:
x1, y1, x2, y2 = map(
int,
box.xyxy[0].cpu().tolist()
)
Integer conversion is important because NumPy image indexes must be integer positions.
Crop Each Detected Object
The original input image is available from:
result.orig_img
A manual crop is:
image = result.orig_img
for box in result.boxes:
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = image[
y1:y2,
x1:x2
]
You can then save the crop using OpenCV.
Crop YOLOv8 Detections Using Python
Python gives the most flexibility when you need custom naming, filtering, metadata, or processing.
A complete basic workflow is:
from ultralytics import YOLO
import cv2
model = YOLO("yolov8n.pt")
results = model("image.jpg")
result = results[0]
image = result.orig_img
for i, box in enumerate(result.boxes):
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = image[y1:y2, x1:x2]
cv2.imwrite(
f"crop_{i}.jpg",
crop
)
This saves each detection separately.
Access results.boxes
The bounding boxes are stored in:
result.boxes
Useful properties include:
result.boxes.xyxy
result.boxes.conf
result.boxes.cls
For example:
for box in result.boxes:
class_id = int(box.cls[0])
confidence = float(box.conf[0])
coordinates = (
box.xyxy[0]
.cpu()
.tolist()
)
print(
class_id,
confidence,
coordinates
)
This information can be used to decide whether a particular detection should be cropped.
Convert Box Coordinates to Image Regions
Bounding boxes must stay within valid image boundaries.
A safer implementation is:
image = result.orig_img
height, width = image.shape[:2]
for box in result.boxes:
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(width, x2)
y2 = min(height, y2)
crop = image[y1:y2, x1:x2]
This prevents invalid coordinates from extending outside the image.
Save Cropped Objects to Files
Use the class name when saving:
import os
import cv2
os.makedirs(
"crops",
exist_ok=True
)
for i, box in enumerate(result.boxes):
class_id = int(
box.cls[0]
)
class_name = result.names[
class_id
]
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = result.orig_img[
y1:y2,
x1:x2
]
filename = (
f"crops/"
f"{class_name}_{i}.jpg"
)
cv2.imwrite(
filename,
crop
)
This creates files such as:
person_0.jpg
car_1.jpg
car_2.jpg
dog_3.jpg
Save Cropped Objects Automatically
Manual cropping is not always necessary.
Ultralytics Predict mode currently provides:
save_crop=True
which automatically saves detection crops. The Predict documentation lists save_crop as a built-in visualization/output option specifically for saving cropped detection images.
Use the save_crop Option
Python:
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
model.predict(
source="image.jpg",
save_crop=True
)
CLI:
yolo detect predict \
model=yolov8n.pt \
source=image.jpg \
save_crop=True
Ultralytics also exposes the result-level method:
result.save_crop(
save_dir="crops",
file_name="detection"
)
The method saves one crop per detected object.
Organize Crops by Class Name
Current Results.save_crop() automatically creates a subdirectory using the detected class name.
The documented pattern is:
save_dir/
└── class_name/
└── file_name.jpg
For example:
crops/
├── person/
│ ├── detection.jpg
│ └── detection2.jpg
├── car/
│ ├── detection.jpg
│ └── detection2.jpg
└── dog/
└── detection.jpg
The current Ultralytics implementation builds the path using the class name associated with each detection.
Choose the Output Directory
For direct result saving:
for result in results:
result.save_crop(
save_dir="my_crops",
file_name="object"
)
The method creates required directories automatically.
With normal Predict mode and save_crop=True, crops are placed under the prediction run’s output structure.
If you require a completely custom path layout, manual cropping or result.save_crop() gives more control than relying only on the automatic Predict run directory.
Crop Multiple Detected Objects
One image can contain dozens or hundreds of detections.
You should therefore generate unique filenames for each crop.
Loop Through All Predictions
Example:
for i, box in enumerate(
result.boxes
):
print(
"Detection:",
i
)
Then extract each region independently:
for i, box in enumerate(
result.boxes
):
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = result.orig_img[
y1:y2,
x1:x2
]
Save Each Detection Separately
Use the index:
filename = (
f"object_{i}.jpg"
)
Or combine it with the class:
filename = (
f"{class_name}_{i}.jpg"
)
A better production naming scheme might include:
source image
class
confidence
detection index
timestamp
For example:
street01_car_003_0.91.jpg
Handle Multiple Objects of the Same Class
Suppose the image contains five persons.
Saving every one as:
person.jpg
would overwrite previous files.
Instead:
person_index = 0
for box in result.boxes:
class_id = int(
box.cls[0]
)
class_name = result.names[
class_id
]
if class_name == "person":
filename = (
f"person_"
f"{person_index}.jpg"
)
person_index += 1
Unique naming is particularly important in video processing because the same class may appear thousands of times.
Crop Objects from Video with YOLOv8
A video contains a sequence of frames, so crops can be extracted from every processed frame.
Using stream=True is appropriate for long videos because results are yielded incrementally rather than retained in one large list.
Process Video Frames
Example:
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model.predict(
source="video.mp4",
stream=True
)
for frame_id, result in enumerate(
results
):
print(
frame_id,
len(result.boxes)
)
Each result.orig_img contains the current frame.
Crop Detections Frame by Frame
Example:
import cv2
import os
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
os.makedirs(
"video_crops",
exist_ok=True
)
for frame_id, result in enumerate(
model.predict(
source="video.mp4",
stream=True
)
):
for detection_id, box in enumerate(
result.boxes
):
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = result.orig_img[
y1:y2,
x1:x2
]
filename = (
f"video_crops/"
f"frame_{frame_id}_"
f"object_{detection_id}.jpg"
)
cv2.imwrite(
filename,
crop
)
This gives every detection a frame-specific filename.
Avoid Saving Duplicate Crops
Video creates a major problem: the same object may be detected in many consecutive frames.
For example:
Frame 100 → Car A
Frame 101 → Car A
Frame 102 → Car A
Frame 103 → Car A
Saving every detection creates four nearly identical crops.
A better approach is to use object tracking:
results = model.track(
source="video.mp4",
stream=True,
persist=True
)
Then use:
box.id
to identify a tracked object.
You can save only:
first appearance of each track ID
or:
one crop every N frames per track
or:
best-confidence crop per track
This substantially reduces duplicate images.
Filter Objects Before Cropping
Not every detection should necessarily be saved.
Filtering improves crop quality and reduces unnecessary files.
Crop Only Selected Classes
Suppose only vehicles are needed.
Example:
allowed_classes = {
2,
5,
7
}
for box in result.boxes:
class_id = int(
box.cls[0]
)
if class_id not in allowed_classes:
continue
Alternatively, filter by class name:
allowed_names = {
"car",
"bus",
"truck"
}
Then:
class_name = result.names[
class_id
]
if class_name not in allowed_names:
continue
Use a Confidence Threshold
You can filter before inference output:
results = model.predict(
source="image.jpg",
conf=0.60
)
Or after prediction:
confidence = float(
box.conf[0]
)
if confidence < 0.80:
continue
For crop datasets, a higher threshold may reduce low-quality false-positive crops.
However, setting the threshold too high can remove difficult but valid examples.
Ignore Small or Low-Quality Detections
Small boxes often produce low-resolution crops.
You can calculate:
width = x2 - x1
height = y2 - y1
and reject tiny boxes:
if width < 80 or height < 80:
continue
You can also use area:
area = width * height
if area < 5000:
continue
The correct threshold depends on the source resolution and downstream application.
For OCR or fine-grained classification, a very small crop may contain too little detail to be useful.
Crop Objects with a Custom YOLOv8 Model
Custom models work with the same cropping workflow as pretrained YOLOv8 models.
The main difference is that class names come from your own dataset.
Load Custom-Trained Weights
Example:
from ultralytics import YOLO
model = YOLO(
"runs/detect/train/"
"weights/best.pt"
)
Run:
results = model(
"custom_image.jpg"
)
Then use:
results[0].boxes
normally.
Crop Custom Object Classes
Suppose your custom model contains:
0 = defect
1 = crack
2 = corrosion
You can crop only cracks:
for box in result.boxes:
class_id = int(
box.cls[0]
)
if class_id != 1:
continue
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = result.orig_img[
y1:y2,
x1:x2
]
Nothing else about the crop logic changes.
Save Results with Custom Class Names
Custom names are available through:
result.names
Example:
class_name = result.names[
class_id
]
Then:
filename = (
f"{class_name}_{i}.jpg"
)
Using:
result.save_crop(
save_dir="custom_crops",
file_name="sample"
)
also organizes saved detections into class-name subdirectories automatically.
Common Problems When Cropping YOLOv8 Detections
Most crop problems come from coordinate handling, file naming, video duplication, or insufficient source resolution.
Incorrect Crop Coordinates
Make sure you use:
box.xyxy
when performing:
image[y1:y2, x1:x2]
Do not accidentally use:
box.xywh
as if it were XYXY.
xywh means:
x_center
y_center
width
height
not left, top, right, bottom.
Also convert coordinates to integers before slicing.
Empty or Partial Cropped Images
An empty crop can occur if:
x2 <= x1
y2 <= y1
coordinates exceed image bounds
coordinates were interpreted incorrectly
Check:
if (
x2 <= x1
or y2 <= y1
):
continue
and clamp values:
x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(width, x2)
y2 = min(height, y2)
This protects against invalid slicing.
Duplicate Crops from Video
Video naturally repeats the same object across many frames.
Use tracking IDs if you want one image per physical track.
For example:
saved_ids = set()
for result in results:
for box in result.boxes:
if box.id is None:
continue
track_id = int(
box.id[0]
)
if track_id in saved_ids:
continue
saved_ids.add(
track_id
)
# save crop here
For better quality, you may instead keep the highest-confidence crop for each tracking ID.
Low-Resolution Cropped Objects
The crop cannot contain more native detail than the source image.
For example, if an object occupies only:
25 × 30 pixels
inside the source frame, cropping it produces a tiny image.
Upscaling:
25×30 → 250×300
makes the file larger but does not restore missing visual detail.
Possible improvements include:
use higher-resolution source
increase camera zoom
use higher-resolution stream
use larger inference size when appropriate
move camera closer
FAQs About Cropping Detected Objects in YOLOv8
How do I crop detected objects in YOLOv8?
The easiest automatic method is:
model.predict(
source="image.jpg",
save_crop=True
)
Current Ultralytics Predict mode supports save_crop specifically for saving detection crops.
You can also manually crop:
x1, y1, x2, y2 = map(
int,
box.xyxy[0].cpu().tolist()
)
crop = result.orig_img[
y1:y2,
x1:x2
]
Does YOLOv8 have a built-in save_crop option?
Yes.
Use:
save_crop=True
during prediction.
You can also call:
result.save_crop(
save_dir="crops",
file_name="detection"
)
The current save_crop() method automatically creates class-specific directories.
How do I crop only one class in YOLOv8?
Filter the class before saving:
for box in result.boxes:
class_id = int(
box.cls[0]
)
if class_id != 0:
continue
# crop detection
You can also identify the desired class using:
result.names[class_id]
instead of relying only on a numeric ID.
Can YOLOv8 crop multiple detected objects automatically?
Yes.
save_crop=True saves detected objects individually.
The result-level save_crop() method loops through the current Boxes collection and saves one bounding-box crop for each detection.
Can I crop objects from a video stream?
Yes.
Use streaming prediction:
for result in model.predict(
source="video.mp4",
stream=True
):
for box in result.boxes:
# crop current frame
pass
For live streams, the same approach applies because each result gives access to the current orig_img and detection boxes.
Use tracking IDs if you need to avoid repeatedly saving the same object.
Where does YOLOv8 save cropped detections?
With Results.save_crop(), the documented structure is:
save_dir/
└── class_name/
└── file_name.jpg
and the required class directories are created automatically.
With Predict mode save_crop=True, the crops are saved under the active prediction run’s output directory structure.
Can I use custom-trained weights for object cropping?
Yes.
Load custom weights:
model = YOLO(
"runs/detect/train/"
"weights/best.pt"
)
Then use:
model.predict(
source="image.jpg",
save_crop=True
)
or manually access:
result.boxes
Custom class names are preserved through:
result.names
so crops can be organized according to your own object categories.
Conclusion
YOLOv8 crop detected objects functionality makes it easy to turn ordinary object detections into individual image files.
The fastest built-in method is:
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
model.predict(
source="image.jpg",
save_crop=True
)
Current Ultralytics documents save_crop=True as a Predict option for saving cropped images of detected objects.
For more control, use the Results API:
from ultralytics import YOLO
import cv2
model = YOLO("yolov8n.pt")
result = model(
"image.jpg"
)[0]
for i, box in enumerate(
result.boxes
):
class_id = int(
box.cls[0]
)
class_name = result.names[
class_id
]
x1, y1, x2, y2 = map(
int,
box.xyxy[0]
.cpu()
.tolist()
)
crop = result.orig_img[
y1:y2,
x1:x2
]
cv2.imwrite(
f"{class_name}_{i}.jpg",
crop
)
Ultralytics also provides:
result.save_crop(
save_dir="crops",
file_name="detection"
)
which saves crops into class-specific subdirectories and creates those directories automatically. The current implementation uses each detection’s xyxy bounding box and the original image when creating the saved crop.
A practical cropping workflow is:
Run Detection
↓
Read Bounding Boxes
↓
Filter Classes
↓
Filter Confidence
↓
Check Box Size
↓
Crop Image Regions
↓
Assign Unique Filenames
↓
Save Crops
For video, add tracking IDs so the same object is not saved repeatedly on every frame. For low-quality crops, improve the source resolution rather than relying only on upscaling after the crop has already been created.
Automatic save_crop=True is best when you simply need all detected objects saved. Manual cropping is better when you need custom filenames, selected classes, confidence filtering, minimum-size rules, padding, duplicate prevention, or additional processing before each crop is written.
I’m Jane Austen, a skilled content writer with the ability to simplify any complex topic. I focus on delivering valuable tips and strategies throughout my articles.