|
|
|
|
|
import shutil |
|
import subprocess |
|
import sys |
|
from pathlib import Path |
|
from types import SimpleNamespace |
|
from typing import Dict, List, Union |
|
|
|
import cv2 |
|
|
|
from ultralytics.utils import ( |
|
ASSETS, |
|
DEFAULT_CFG, |
|
DEFAULT_CFG_DICT, |
|
DEFAULT_CFG_PATH, |
|
DEFAULT_SOL_DICT, |
|
IS_VSCODE, |
|
LOGGER, |
|
RANK, |
|
ROOT, |
|
RUNS_DIR, |
|
SETTINGS, |
|
SETTINGS_FILE, |
|
TESTS_RUNNING, |
|
IterableSimpleNamespace, |
|
__version__, |
|
checks, |
|
colorstr, |
|
deprecation_warn, |
|
vscode_msg, |
|
yaml_load, |
|
yaml_print, |
|
) |
|
|
|
|
|
SOLUTION_MAP = { |
|
"count": ("ObjectCounter", "count"), |
|
"heatmap": ("Heatmap", "generate_heatmap"), |
|
"queue": ("QueueManager", "process_queue"), |
|
"speed": ("SpeedEstimator", "estimate_speed"), |
|
"workout": ("AIGym", "monitor"), |
|
"analytics": ("Analytics", "process_data"), |
|
"trackzone": ("TrackZone", "trackzone"), |
|
"inference": ("Inference", "inference"), |
|
"help": None, |
|
} |
|
|
|
|
|
MODES = {"train", "val", "predict", "export", "track", "benchmark"} |
|
TASKS = {"detect", "segment", "classify", "pose", "obb"} |
|
TASK2DATA = { |
|
"detect": "coco8.yaml", |
|
"segment": "coco8-seg.yaml", |
|
"classify": "imagenet10", |
|
"pose": "coco8-pose.yaml", |
|
"obb": "dota8.yaml", |
|
} |
|
TASK2MODEL = { |
|
"detect": "yolo11n.pt", |
|
"segment": "yolo11n-seg.pt", |
|
"classify": "yolo11n-cls.pt", |
|
"pose": "yolo11n-pose.pt", |
|
"obb": "yolo11n-obb.pt", |
|
} |
|
TASK2METRIC = { |
|
"detect": "metrics/mAP50-95(B)", |
|
"segment": "metrics/mAP50-95(M)", |
|
"classify": "metrics/accuracy_top1", |
|
"pose": "metrics/mAP50-95(P)", |
|
"obb": "metrics/mAP50-95(B)", |
|
} |
|
MODELS = {TASK2MODEL[task] for task in TASKS} |
|
|
|
ARGV = sys.argv or ["", ""] |
|
SOLUTIONS_HELP_MSG = f""" |
|
Arguments received: {str(["yolo"] + ARGV[1:])}. Ultralytics 'yolo solutions' usage overview: |
|
|
|
yolo solutions SOLUTION ARGS |
|
|
|
Where SOLUTION (optional) is one of {list(SOLUTION_MAP.keys())[:-1]} |
|
ARGS (optional) are any number of custom 'arg=value' pairs like 'show_in=True' that override defaults |
|
at https://docs.ultralytics.com/usage/cfg |
|
|
|
1. Call object counting solution |
|
yolo solutions count source="path/to/video/file.mp4" region=[(20, 400), (1080, 400), (1080, 360), (20, 360)] |
|
|
|
2. Call heatmaps solution |
|
yolo solutions heatmap colormap=cv2.COLORMAP_PARULA model=yolo11n.pt |
|
|
|
3. Call queue management solution |
|
yolo solutions queue region=[(20, 400), (1080, 400), (1080, 360), (20, 360)] model=yolo11n.pt |
|
|
|
4. Call workouts monitoring solution for push-ups |
|
yolo solutions workout model=yolo11n-pose.pt kpts=[6, 8, 10] |
|
|
|
5. Generate analytical graphs |
|
yolo solutions analytics analytics_type="pie" |
|
|
|
6. Track objects within specific zones |
|
yolo solutions trackzone source="path/to/video/file.mp4" region=[(150, 150), (1130, 150), (1130, 570), (150, 570)] |
|
|
|
7. Streamlit real-time webcam inference GUI |
|
yolo streamlit-predict |
|
""" |
|
CLI_HELP_MSG = f""" |
|
Arguments received: {str(["yolo"] + ARGV[1:])}. Ultralytics 'yolo' commands use the following syntax: |
|
|
|
yolo TASK MODE ARGS |
|
|
|
Where TASK (optional) is one of {TASKS} |
|
MODE (required) is one of {MODES} |
|
ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults. |
|
See all ARGS at https://docs.ultralytics.com/usage/cfg or with 'yolo cfg' |
|
|
|
1. Train a detection model for 10 epochs with an initial learning_rate of 0.01 |
|
yolo train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01 |
|
|
|
2. Predict a YouTube video using a pretrained segmentation model at image size 320: |
|
yolo predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320 |
|
|
|
3. Val a pretrained detection model at batch-size 1 and image size 640: |
|
yolo val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640 |
|
|
|
4. Export a YOLO11n classification model to ONNX format at image size 224 by 128 (no TASK required) |
|
yolo export model=yolo11n-cls.pt format=onnx imgsz=224,128 |
|
|
|
5. Ultralytics solutions usage |
|
yolo solutions count or in {list(SOLUTION_MAP.keys())[1:-1]} source="path/to/video/file.mp4" |
|
|
|
6. Run special commands: |
|
yolo help |
|
yolo checks |
|
yolo version |
|
yolo settings |
|
yolo copy-cfg |
|
yolo cfg |
|
yolo solutions help |
|
|
|
Docs: https://docs.ultralytics.com |
|
Solutions: https://docs.ultralytics.com/solutions/ |
|
Community: https://community.ultralytics.com |
|
GitHub: https://github.com/ultralytics/ultralytics |
|
""" |
|
|
|
|
|
CFG_FLOAT_KEYS = { |
|
"warmup_epochs", |
|
"box", |
|
"cls", |
|
"dfl", |
|
"degrees", |
|
"shear", |
|
"time", |
|
"workspace", |
|
"batch", |
|
} |
|
CFG_FRACTION_KEYS = { |
|
"dropout", |
|
"lr0", |
|
"lrf", |
|
"momentum", |
|
"weight_decay", |
|
"warmup_momentum", |
|
"warmup_bias_lr", |
|
"hsv_h", |
|
"hsv_s", |
|
"hsv_v", |
|
"translate", |
|
"scale", |
|
"perspective", |
|
"flipud", |
|
"fliplr", |
|
"bgr", |
|
"mosaic", |
|
"mixup", |
|
"copy_paste", |
|
"conf", |
|
"iou", |
|
"fraction", |
|
} |
|
CFG_INT_KEYS = { |
|
"epochs", |
|
"patience", |
|
"workers", |
|
"seed", |
|
"close_mosaic", |
|
"mask_ratio", |
|
"max_det", |
|
"vid_stride", |
|
"line_width", |
|
"nbs", |
|
"save_period", |
|
} |
|
CFG_BOOL_KEYS = { |
|
"save", |
|
"exist_ok", |
|
"verbose", |
|
"deterministic", |
|
"single_cls", |
|
"rect", |
|
"cos_lr", |
|
"overlap_mask", |
|
"val", |
|
"save_json", |
|
"save_hybrid", |
|
"half", |
|
"dnn", |
|
"plots", |
|
"show", |
|
"save_txt", |
|
"save_conf", |
|
"save_crop", |
|
"save_frames", |
|
"show_labels", |
|
"show_conf", |
|
"visualize", |
|
"augment", |
|
"agnostic_nms", |
|
"retina_masks", |
|
"show_boxes", |
|
"keras", |
|
"optimize", |
|
"int8", |
|
"dynamic", |
|
"simplify", |
|
"nms", |
|
"profile", |
|
"multi_scale", |
|
} |
|
|
|
|
|
def cfg2dict(cfg): |
|
""" |
|
Converts a configuration object to a dictionary. |
|
|
|
Args: |
|
cfg (str | Path | Dict | SimpleNamespace): Configuration object to be converted. Can be a file path, |
|
a string, a dictionary, or a SimpleNamespace object. |
|
|
|
Returns: |
|
(Dict): Configuration object in dictionary format. |
|
|
|
Examples: |
|
Convert a YAML file path to a dictionary: |
|
>>> config_dict = cfg2dict("config.yaml") |
|
|
|
Convert a SimpleNamespace to a dictionary: |
|
>>> from types import SimpleNamespace |
|
>>> config_sn = SimpleNamespace(param1="value1", param2="value2") |
|
>>> config_dict = cfg2dict(config_sn) |
|
|
|
Pass through an already existing dictionary: |
|
>>> config_dict = cfg2dict({"param1": "value1", "param2": "value2"}) |
|
|
|
Notes: |
|
- If cfg is a path or string, it's loaded as YAML and converted to a dictionary. |
|
- If cfg is a SimpleNamespace object, it's converted to a dictionary using vars(). |
|
- If cfg is already a dictionary, it's returned unchanged. |
|
""" |
|
if isinstance(cfg, (str, Path)): |
|
cfg = yaml_load(cfg) |
|
elif isinstance(cfg, SimpleNamespace): |
|
cfg = vars(cfg) |
|
return cfg |
|
|
|
|
|
def get_cfg(cfg: Union[str, Path, Dict, SimpleNamespace] = DEFAULT_CFG_DICT, overrides: Dict = None): |
|
""" |
|
Load and merge configuration data from a file or dictionary, with optional overrides. |
|
|
|
Args: |
|
cfg (str | Path | Dict | SimpleNamespace): Configuration data source. Can be a file path, dictionary, or |
|
SimpleNamespace object. |
|
overrides (Dict | None): Dictionary containing key-value pairs to override the base configuration. |
|
|
|
Returns: |
|
(SimpleNamespace): Namespace containing the merged configuration arguments. |
|
|
|
Examples: |
|
>>> from ultralytics.cfg import get_cfg |
|
>>> config = get_cfg() # Load default configuration |
|
>>> config_with_overrides = get_cfg("path/to/config.yaml", overrides={"epochs": 50, "batch_size": 16}) |
|
|
|
Notes: |
|
- If both `cfg` and `overrides` are provided, the values in `overrides` will take precedence. |
|
- Special handling ensures alignment and correctness of the configuration, such as converting numeric |
|
`project` and `name` to strings and validating configuration keys and values. |
|
- The function performs type and value checks on the configuration data. |
|
""" |
|
cfg = cfg2dict(cfg) |
|
|
|
|
|
if overrides: |
|
overrides = cfg2dict(overrides) |
|
if "save_dir" not in cfg: |
|
overrides.pop("save_dir", None) |
|
check_dict_alignment(cfg, overrides) |
|
cfg = {**cfg, **overrides} |
|
|
|
|
|
for k in "project", "name": |
|
if k in cfg and isinstance(cfg[k], (int, float)): |
|
cfg[k] = str(cfg[k]) |
|
if cfg.get("name") == "model": |
|
cfg["name"] = str(cfg.get("model", "")).split(".")[0] |
|
LOGGER.warning(f"WARNING ⚠️ 'name=model' automatically updated to 'name={cfg['name']}'.") |
|
|
|
|
|
check_cfg(cfg) |
|
|
|
|
|
return IterableSimpleNamespace(**cfg) |
|
|
|
|
|
def check_cfg(cfg, hard=True): |
|
""" |
|
Checks configuration argument types and values for the Ultralytics library. |
|
|
|
This function validates the types and values of configuration arguments, ensuring correctness and converting |
|
them if necessary. It checks for specific key types defined in global variables such as CFG_FLOAT_KEYS, |
|
CFG_FRACTION_KEYS, CFG_INT_KEYS, and CFG_BOOL_KEYS. |
|
|
|
Args: |
|
cfg (Dict): Configuration dictionary to validate. |
|
hard (bool): If True, raises exceptions for invalid types and values; if False, attempts to convert them. |
|
|
|
Examples: |
|
>>> config = { |
|
... "epochs": 50, # valid integer |
|
... "lr0": 0.01, # valid float |
|
... "momentum": 1.2, # invalid float (out of 0.0-1.0 range) |
|
... "save": "true", # invalid bool |
|
... } |
|
>>> check_cfg(config, hard=False) |
|
>>> print(config) |
|
{'epochs': 50, 'lr0': 0.01, 'momentum': 1.2, 'save': False} # corrected 'save' key |
|
|
|
Notes: |
|
- The function modifies the input dictionary in-place. |
|
- None values are ignored as they may be from optional arguments. |
|
- Fraction keys are checked to be within the range [0.0, 1.0]. |
|
""" |
|
for k, v in cfg.items(): |
|
if v is not None: |
|
if k in CFG_FLOAT_KEYS and not isinstance(v, (int, float)): |
|
if hard: |
|
raise TypeError( |
|
f"'{k}={v}' is of invalid type {type(v).__name__}. " |
|
f"Valid '{k}' types are int (i.e. '{k}=0') or float (i.e. '{k}=0.5')" |
|
) |
|
cfg[k] = float(v) |
|
elif k in CFG_FRACTION_KEYS: |
|
if not isinstance(v, (int, float)): |
|
if hard: |
|
raise TypeError( |
|
f"'{k}={v}' is of invalid type {type(v).__name__}. " |
|
f"Valid '{k}' types are int (i.e. '{k}=0') or float (i.e. '{k}=0.5')" |
|
) |
|
cfg[k] = v = float(v) |
|
if not (0.0 <= v <= 1.0): |
|
raise ValueError(f"'{k}={v}' is an invalid value. Valid '{k}' values are between 0.0 and 1.0.") |
|
elif k in CFG_INT_KEYS and not isinstance(v, int): |
|
if hard: |
|
raise TypeError( |
|
f"'{k}={v}' is of invalid type {type(v).__name__}. '{k}' must be an int (i.e. '{k}=8')" |
|
) |
|
cfg[k] = int(v) |
|
elif k in CFG_BOOL_KEYS and not isinstance(v, bool): |
|
if hard: |
|
raise TypeError( |
|
f"'{k}={v}' is of invalid type {type(v).__name__}. " |
|
f"'{k}' must be a bool (i.e. '{k}=True' or '{k}=False')" |
|
) |
|
cfg[k] = bool(v) |
|
|
|
|
|
def get_save_dir(args, name=None): |
|
""" |
|
Returns the directory path for saving outputs, derived from arguments or default settings. |
|
|
|
Args: |
|
args (SimpleNamespace): Namespace object containing configurations such as 'project', 'name', 'task', |
|
'mode', and 'save_dir'. |
|
name (str | None): Optional name for the output directory. If not provided, it defaults to 'args.name' |
|
or the 'args.mode'. |
|
|
|
Returns: |
|
(Path): Directory path where outputs should be saved. |
|
|
|
Examples: |
|
>>> from types import SimpleNamespace |
|
>>> args = SimpleNamespace(project="my_project", task="detect", mode="train", exist_ok=True) |
|
>>> save_dir = get_save_dir(args) |
|
>>> print(save_dir) |
|
my_project/detect/train |
|
""" |
|
if getattr(args, "save_dir", None): |
|
save_dir = args.save_dir |
|
else: |
|
from ultralytics.utils.files import increment_path |
|
|
|
project = args.project or (ROOT.parent / "tests/tmp/runs" if TESTS_RUNNING else RUNS_DIR) / args.task |
|
name = name or args.name or f"{args.mode}" |
|
save_dir = increment_path(Path(project) / name, exist_ok=args.exist_ok if RANK in {-1, 0} else True) |
|
|
|
return Path(save_dir) |
|
|
|
|
|
def _handle_deprecation(custom): |
|
""" |
|
Handles deprecated configuration keys by mapping them to current equivalents with deprecation warnings. |
|
|
|
Args: |
|
custom (Dict): Configuration dictionary potentially containing deprecated keys. |
|
|
|
Examples: |
|
>>> custom_config = {"boxes": True, "hide_labels": "False", "line_thickness": 2} |
|
>>> _handle_deprecation(custom_config) |
|
>>> print(custom_config) |
|
{'show_boxes': True, 'show_labels': True, 'line_width': 2} |
|
|
|
Notes: |
|
This function modifies the input dictionary in-place, replacing deprecated keys with their current |
|
equivalents. It also handles value conversions where necessary, such as inverting boolean values for |
|
'hide_labels' and 'hide_conf'. |
|
""" |
|
for key in custom.copy().keys(): |
|
if key == "boxes": |
|
deprecation_warn(key, "show_boxes") |
|
custom["show_boxes"] = custom.pop("boxes") |
|
if key == "hide_labels": |
|
deprecation_warn(key, "show_labels") |
|
custom["show_labels"] = custom.pop("hide_labels") == "False" |
|
if key == "hide_conf": |
|
deprecation_warn(key, "show_conf") |
|
custom["show_conf"] = custom.pop("hide_conf") == "False" |
|
if key == "line_thickness": |
|
deprecation_warn(key, "line_width") |
|
custom["line_width"] = custom.pop("line_thickness") |
|
if key == "label_smoothing": |
|
deprecation_warn(key) |
|
custom.pop("label_smoothing") |
|
|
|
return custom |
|
|
|
|
|
def check_dict_alignment(base: Dict, custom: Dict, e=None): |
|
""" |
|
Checks alignment between custom and base configuration dictionaries, handling deprecated keys and providing error |
|
messages for mismatched keys. |
|
|
|
Args: |
|
base (Dict): The base configuration dictionary containing valid keys. |
|
custom (Dict): The custom configuration dictionary to be checked for alignment. |
|
e (Exception | None): Optional error instance passed by the calling function. |
|
|
|
Raises: |
|
SystemExit: If mismatched keys are found between the custom and base dictionaries. |
|
|
|
Examples: |
|
>>> base_cfg = {"epochs": 50, "lr0": 0.01, "batch_size": 16} |
|
>>> custom_cfg = {"epoch": 100, "lr": 0.02, "batch_size": 32} |
|
>>> try: |
|
... check_dict_alignment(base_cfg, custom_cfg) |
|
... except SystemExit: |
|
... print("Mismatched keys found") |
|
|
|
Notes: |
|
- Suggests corrections for mismatched keys based on similarity to valid keys. |
|
- Automatically replaces deprecated keys in the custom configuration with updated equivalents. |
|
- Prints detailed error messages for each mismatched key to help users correct their configurations. |
|
""" |
|
custom = _handle_deprecation(custom) |
|
base_keys, custom_keys = (set(x.keys()) for x in (base, custom)) |
|
if mismatched := [k for k in custom_keys if k not in base_keys]: |
|
from difflib import get_close_matches |
|
|
|
string = "" |
|
for x in mismatched: |
|
matches = get_close_matches(x, base_keys) |
|
matches = [f"{k}={base[k]}" if base.get(k) is not None else k for k in matches] |
|
match_str = f"Similar arguments are i.e. {matches}." if matches else "" |
|
string += f"'{colorstr('red', 'bold', x)}' is not a valid YOLO argument. {match_str}\n" |
|
raise SyntaxError(string + CLI_HELP_MSG) from e |
|
|
|
|
|
def merge_equals_args(args: List[str]) -> List[str]: |
|
""" |
|
Merges arguments around isolated '=' in a list of strings and joins fragments with brackets. |
|
|
|
This function handles the following cases: |
|
1. ['arg', '=', 'val'] becomes ['arg=val'] |
|
2. ['arg=', 'val'] becomes ['arg=val'] |
|
3. ['arg', '=val'] becomes ['arg=val'] |
|
4. Joins fragments with brackets, e.g., ['imgsz=[3,', '640,', '640]'] becomes ['imgsz=[3,640,640]'] |
|
|
|
Args: |
|
args (List[str]): A list of strings where each element represents an argument or fragment. |
|
|
|
Returns: |
|
List[str]: A list of strings where the arguments around isolated '=' are merged and fragments with brackets are joined. |
|
|
|
Examples: |
|
>>> args = ["arg1", "=", "value", "arg2=", "value2", "arg3", "=value3", "imgsz=[3,", "640,", "640]"] |
|
>>> merge_and_join_args(args) |
|
['arg1=value', 'arg2=value2', 'arg3=value3', 'imgsz=[3,640,640]'] |
|
""" |
|
new_args = [] |
|
current = "" |
|
depth = 0 |
|
|
|
i = 0 |
|
while i < len(args): |
|
arg = args[i] |
|
|
|
|
|
if arg == "=" and 0 < i < len(args) - 1: |
|
new_args[-1] += f"={args[i + 1]}" |
|
i += 2 |
|
continue |
|
elif arg.endswith("=") and i < len(args) - 1 and "=" not in args[i + 1]: |
|
new_args.append(f"{arg}{args[i + 1]}") |
|
i += 2 |
|
continue |
|
elif arg.startswith("=") and i > 0: |
|
new_args[-1] += arg |
|
i += 1 |
|
continue |
|
|
|
|
|
depth += arg.count("[") - arg.count("]") |
|
current += arg |
|
if depth == 0: |
|
new_args.append(current) |
|
current = "" |
|
|
|
i += 1 |
|
|
|
|
|
if current: |
|
new_args.append(current) |
|
|
|
return new_args |
|
|
|
|
|
def handle_yolo_hub(args: List[str]) -> None: |
|
""" |
|
Handles Ultralytics HUB command-line interface (CLI) commands for authentication. |
|
|
|
This function processes Ultralytics HUB CLI commands such as login and logout. It should be called when executing a |
|
script with arguments related to HUB authentication. |
|
|
|
Args: |
|
args (List[str]): A list of command line arguments. The first argument should be either 'login' |
|
or 'logout'. For 'login', an optional second argument can be the API key. |
|
|
|
Examples: |
|
```bash |
|
yolo login YOUR_API_KEY |
|
``` |
|
|
|
Notes: |
|
- The function imports the 'hub' module from ultralytics to perform login and logout operations. |
|
- For the 'login' command, if no API key is provided, an empty string is passed to the login function. |
|
- The 'logout' command does not require any additional arguments. |
|
""" |
|
from ultralytics import hub |
|
|
|
if args[0] == "login": |
|
key = args[1] if len(args) > 1 else "" |
|
|
|
hub.login(key) |
|
elif args[0] == "logout": |
|
|
|
hub.logout() |
|
|
|
|
|
def handle_yolo_settings(args: List[str]) -> None: |
|
""" |
|
Handles YOLO settings command-line interface (CLI) commands. |
|
|
|
This function processes YOLO settings CLI commands such as reset and updating individual settings. It should be |
|
called when executing a script with arguments related to YOLO settings management. |
|
|
|
Args: |
|
args (List[str]): A list of command line arguments for YOLO settings management. |
|
|
|
Examples: |
|
>>> handle_yolo_settings(["reset"]) # Reset YOLO settings |
|
>>> handle_yolo_settings(["default_cfg_path=yolo11n.yaml"]) # Update a specific setting |
|
|
|
Notes: |
|
- If no arguments are provided, the function will display the current settings. |
|
- The 'reset' command will delete the existing settings file and create new default settings. |
|
- Other arguments are treated as key-value pairs to update specific settings. |
|
- The function will check for alignment between the provided settings and the existing ones. |
|
- After processing, the updated settings will be displayed. |
|
- For more information on handling YOLO settings, visit: |
|
https://docs.ultralytics.com/quickstart/#ultralytics-settings |
|
""" |
|
url = "https://docs.ultralytics.com/quickstart/#ultralytics-settings" |
|
try: |
|
if any(args): |
|
if args[0] == "reset": |
|
SETTINGS_FILE.unlink() |
|
SETTINGS.reset() |
|
LOGGER.info("Settings reset successfully") |
|
else: |
|
new = dict(parse_key_value_pair(a) for a in args) |
|
check_dict_alignment(SETTINGS, new) |
|
SETTINGS.update(new) |
|
|
|
print(SETTINGS) |
|
LOGGER.info(f"💡 Learn more about Ultralytics Settings at {url}") |
|
except Exception as e: |
|
LOGGER.warning(f"WARNING ⚠️ settings error: '{e}'. Please see {url} for help.") |
|
|
|
|
|
def handle_yolo_solutions(args: List[str]) -> None: |
|
""" |
|
Processes YOLO solutions arguments and runs the specified computer vision solutions pipeline. |
|
|
|
Args: |
|
args (List[str]): Command-line arguments for configuring and running the Ultralytics YOLO |
|
solutions: https://docs.ultralytics.com/solutions/, It can include solution name, source, |
|
and other configuration parameters. |
|
|
|
Returns: |
|
None: The function processes video frames and saves the output but doesn't return any value. |
|
|
|
Examples: |
|
Run people counting solution with default settings: |
|
>>> handle_yolo_solutions(["count"]) |
|
|
|
Run analytics with custom configuration: |
|
>>> handle_yolo_solutions(["analytics", "conf=0.25", "source=path/to/video/file.mp4"]) |
|
|
|
Run inference with custom configuration, requires Streamlit version 1.29.0 or higher. |
|
>>> handle_yolo_solutions(["inference", "model=yolo11n.pt"]) |
|
|
|
Notes: |
|
- Default configurations are merged from DEFAULT_SOL_DICT and DEFAULT_CFG_DICT |
|
- Arguments can be provided in the format 'key=value' or as boolean flags |
|
- Available solutions are defined in SOLUTION_MAP with their respective classes and methods |
|
- If an invalid solution is provided, defaults to 'count' solution |
|
- Output videos are saved in 'runs/solution/{solution_name}' directory |
|
- For 'analytics' solution, frame numbers are tracked for generating analytical graphs |
|
- Video processing can be interrupted by pressing 'q' |
|
- Processes video frames sequentially and saves output in .avi format |
|
- If no source is specified, downloads and uses a default sample video\ |
|
- The inference solution will be launched using the 'streamlit run' command. |
|
- The Streamlit app file is located in the Ultralytics package directory. |
|
""" |
|
full_args_dict = {**DEFAULT_SOL_DICT, **DEFAULT_CFG_DICT} |
|
overrides = {} |
|
|
|
|
|
for arg in merge_equals_args(args): |
|
arg = arg.lstrip("-").rstrip(",") |
|
if "=" in arg: |
|
try: |
|
k, v = parse_key_value_pair(arg) |
|
overrides[k] = v |
|
except (NameError, SyntaxError, ValueError, AssertionError) as e: |
|
check_dict_alignment(full_args_dict, {arg: ""}, e) |
|
elif arg in full_args_dict and isinstance(full_args_dict.get(arg), bool): |
|
overrides[arg] = True |
|
check_dict_alignment(full_args_dict, overrides) |
|
|
|
|
|
if args and args[0] in SOLUTION_MAP: |
|
if args[0] != "help": |
|
s_n = args.pop(0) |
|
else: |
|
LOGGER.info(SOLUTIONS_HELP_MSG) |
|
else: |
|
LOGGER.warning( |
|
f"⚠️ No valid solution provided. Using default 'count'. Available: {', '.join(SOLUTION_MAP.keys())}" |
|
) |
|
s_n = "count" |
|
|
|
if args and args[0] == "help": |
|
return |
|
|
|
if s_n == "inference": |
|
checks.check_requirements("streamlit>=1.29.0") |
|
LOGGER.info("💡 Loading Ultralytics live inference app...") |
|
subprocess.run( |
|
[ |
|
"streamlit", |
|
"run", |
|
str(ROOT / "solutions/streamlit_inference.py"), |
|
"--server.headless", |
|
"true", |
|
overrides.pop("model", "yolo11n.pt"), |
|
] |
|
) |
|
else: |
|
cls, method = SOLUTION_MAP[s_n] |
|
|
|
from ultralytics import solutions |
|
|
|
solution = getattr(solutions, cls)(IS_CLI=True, **overrides) |
|
process = getattr( |
|
solution, method |
|
) |
|
|
|
cap = cv2.VideoCapture(solution.CFG["source"]) |
|
|
|
|
|
import os |
|
from pathlib import Path |
|
|
|
from ultralytics.utils.files import increment_path |
|
|
|
w, h, fps = (int(cap.get(x)) for x in (cv2.CAP_PROP_FRAME_WIDTH, cv2.CAP_PROP_FRAME_HEIGHT, cv2.CAP_PROP_FPS)) |
|
if s_n == "analytics": |
|
w, h = 1920, 1080 |
|
save_dir = increment_path(Path("runs") / "solutions" / "exp", exist_ok=False) |
|
save_dir.mkdir(parents=True, exist_ok=True) |
|
vw = cv2.VideoWriter(os.path.join(save_dir, "solution.avi"), cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h)) |
|
|
|
try: |
|
f_n = 0 |
|
while cap.isOpened(): |
|
success, frame = cap.read() |
|
if not success: |
|
break |
|
frame = process(frame, f_n := f_n + 1) if s_n == "analytics" else process(frame) |
|
vw.write(frame) |
|
if cv2.waitKey(1) & 0xFF == ord("q"): |
|
break |
|
finally: |
|
cap.release() |
|
|
|
|
|
def parse_key_value_pair(pair: str = "key=value"): |
|
""" |
|
Parses a key-value pair string into separate key and value components. |
|
|
|
Args: |
|
pair (str): A string containing a key-value pair in the format "key=value". |
|
|
|
Returns: |
|
key (str): The parsed key. |
|
value (str): The parsed value. |
|
|
|
Raises: |
|
AssertionError: If the value is missing or empty. |
|
|
|
Examples: |
|
>>> key, value = parse_key_value_pair("model=yolo11n.pt") |
|
>>> print(f"Key: {key}, Value: {value}") |
|
Key: model, Value: yolo11n.pt |
|
|
|
>>> key, value = parse_key_value_pair("epochs=100") |
|
>>> print(f"Key: {key}, Value: {value}") |
|
Key: epochs, Value: 100 |
|
|
|
Notes: |
|
- The function splits the input string on the first '=' character. |
|
- Leading and trailing whitespace is removed from both key and value. |
|
- An assertion error is raised if the value is empty after stripping. |
|
""" |
|
k, v = pair.split("=", 1) |
|
k, v = k.strip(), v.strip() |
|
assert v, f"missing '{k}' value" |
|
return k, smart_value(v) |
|
|
|
|
|
def smart_value(v): |
|
""" |
|
Converts a string representation of a value to its appropriate Python type. |
|
|
|
This function attempts to convert a given string into a Python object of the most appropriate type. It handles |
|
conversions to None, bool, int, float, and other types that can be evaluated safely. |
|
|
|
Args: |
|
v (str): The string representation of the value to be converted. |
|
|
|
Returns: |
|
(Any): The converted value. The type can be None, bool, int, float, or the original string if no conversion |
|
is applicable. |
|
|
|
Examples: |
|
>>> smart_value("42") |
|
42 |
|
>>> smart_value("3.14") |
|
3.14 |
|
>>> smart_value("True") |
|
True |
|
>>> smart_value("None") |
|
None |
|
>>> smart_value("some_string") |
|
'some_string' |
|
|
|
Notes: |
|
- The function uses a case-insensitive comparison for boolean and None values. |
|
- For other types, it attempts to use Python's eval() function, which can be unsafe if used on untrusted input. |
|
- If no conversion is possible, the original string is returned. |
|
""" |
|
v_lower = v.lower() |
|
if v_lower == "none": |
|
return None |
|
elif v_lower == "true": |
|
return True |
|
elif v_lower == "false": |
|
return False |
|
else: |
|
try: |
|
return eval(v) |
|
except Exception: |
|
return v |
|
|
|
|
|
def entrypoint(debug=""): |
|
""" |
|
Ultralytics entrypoint function for parsing and executing command-line arguments. |
|
|
|
This function serves as the main entry point for the Ultralytics CLI, parsing command-line arguments and |
|
executing the corresponding tasks such as training, validation, prediction, exporting models, and more. |
|
|
|
Args: |
|
debug (str): Space-separated string of command-line arguments for debugging purposes. |
|
|
|
Examples: |
|
Train a detection model for 10 epochs with an initial learning_rate of 0.01: |
|
>>> entrypoint("train data=coco8.yaml model=yolo11n.pt epochs=10 lr0=0.01") |
|
|
|
Predict a YouTube video using a pretrained segmentation model at image size 320: |
|
>>> entrypoint("predict model=yolo11n-seg.pt source='https://youtu.be/LNwODJXcvt4' imgsz=320") |
|
|
|
Validate a pretrained detection model at batch-size 1 and image size 640: |
|
>>> entrypoint("val model=yolo11n.pt data=coco8.yaml batch=1 imgsz=640") |
|
|
|
Notes: |
|
- If no arguments are passed, the function will display the usage help message. |
|
- For a list of all available commands and their arguments, see the provided help messages and the |
|
Ultralytics documentation at https://docs.ultralytics.com. |
|
""" |
|
args = (debug.split(" ") if debug else ARGV)[1:] |
|
if not args: |
|
LOGGER.info(CLI_HELP_MSG) |
|
return |
|
|
|
special = { |
|
"help": lambda: LOGGER.info(CLI_HELP_MSG), |
|
"checks": checks.collect_system_info, |
|
"version": lambda: LOGGER.info(__version__), |
|
"settings": lambda: handle_yolo_settings(args[1:]), |
|
"cfg": lambda: yaml_print(DEFAULT_CFG_PATH), |
|
"hub": lambda: handle_yolo_hub(args[1:]), |
|
"login": lambda: handle_yolo_hub(args), |
|
"logout": lambda: handle_yolo_hub(args), |
|
"copy-cfg": copy_default_cfg, |
|
"solutions": lambda: handle_yolo_solutions(args[1:]), |
|
} |
|
full_args_dict = {**DEFAULT_CFG_DICT, **{k: None for k in TASKS}, **{k: None for k in MODES}, **special} |
|
|
|
|
|
special.update({k[0]: v for k, v in special.items()}) |
|
special.update({k[:-1]: v for k, v in special.items() if len(k) > 1 and k.endswith("s")}) |
|
special = {**special, **{f"-{k}": v for k, v in special.items()}, **{f"--{k}": v for k, v in special.items()}} |
|
|
|
overrides = {} |
|
for a in merge_equals_args(args): |
|
if a.startswith("--"): |
|
LOGGER.warning(f"WARNING ⚠️ argument '{a}' does not require leading dashes '--', updating to '{a[2:]}'.") |
|
a = a[2:] |
|
if a.endswith(","): |
|
LOGGER.warning(f"WARNING ⚠️ argument '{a}' does not require trailing comma ',', updating to '{a[:-1]}'.") |
|
a = a[:-1] |
|
if "=" in a: |
|
try: |
|
k, v = parse_key_value_pair(a) |
|
if k == "cfg" and v is not None: |
|
LOGGER.info(f"Overriding {DEFAULT_CFG_PATH} with {v}") |
|
overrides = {k: val for k, val in yaml_load(checks.check_yaml(v)).items() if k != "cfg"} |
|
else: |
|
overrides[k] = v |
|
except (NameError, SyntaxError, ValueError, AssertionError) as e: |
|
check_dict_alignment(full_args_dict, {a: ""}, e) |
|
|
|
elif a in TASKS: |
|
overrides["task"] = a |
|
elif a in MODES: |
|
overrides["mode"] = a |
|
elif a.lower() in special: |
|
special[a.lower()]() |
|
return |
|
elif a in DEFAULT_CFG_DICT and isinstance(DEFAULT_CFG_DICT[a], bool): |
|
overrides[a] = True |
|
elif a in DEFAULT_CFG_DICT: |
|
raise SyntaxError( |
|
f"'{colorstr('red', 'bold', a)}' is a valid YOLO argument but is missing an '=' sign " |
|
f"to set its value, i.e. try '{a}={DEFAULT_CFG_DICT[a]}'\n{CLI_HELP_MSG}" |
|
) |
|
else: |
|
check_dict_alignment(full_args_dict, {a: ""}) |
|
|
|
|
|
check_dict_alignment(full_args_dict, overrides) |
|
|
|
|
|
mode = overrides.get("mode") |
|
if mode is None: |
|
mode = DEFAULT_CFG.mode or "predict" |
|
LOGGER.warning(f"WARNING ⚠️ 'mode' argument is missing. Valid modes are {MODES}. Using default 'mode={mode}'.") |
|
elif mode not in MODES: |
|
raise ValueError(f"Invalid 'mode={mode}'. Valid modes are {MODES}.\n{CLI_HELP_MSG}") |
|
|
|
|
|
task = overrides.pop("task", None) |
|
if task: |
|
if task == "classify" and mode == "track": |
|
raise ValueError( |
|
f"❌ Classification doesn't support 'mode=track'. Valid modes for classification are" |
|
f" {MODES - {'track'}}.\n{CLI_HELP_MSG}" |
|
) |
|
elif task not in TASKS: |
|
if task == "track": |
|
LOGGER.warning( |
|
"WARNING ⚠️ invalid 'task=track', setting 'task=detect' and 'mode=track'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}." |
|
) |
|
task, mode = "detect", "track" |
|
else: |
|
raise ValueError(f"Invalid 'task={task}'. Valid tasks are {TASKS}.\n{CLI_HELP_MSG}") |
|
if "model" not in overrides: |
|
overrides["model"] = TASK2MODEL[task] |
|
|
|
|
|
model = overrides.pop("model", DEFAULT_CFG.model) |
|
if model is None: |
|
model = "yolo11n.pt" |
|
LOGGER.warning(f"WARNING ⚠️ 'model' argument is missing. Using default 'model={model}'.") |
|
overrides["model"] = model |
|
stem = Path(model).stem.lower() |
|
if "rtdetr" in stem: |
|
from ultralytics import RTDETR |
|
|
|
model = RTDETR(model) |
|
elif "fastsam" in stem: |
|
from ultralytics import FastSAM |
|
|
|
model = FastSAM(model) |
|
elif "sam_" in stem or "sam2_" in stem or "sam2.1_" in stem: |
|
from ultralytics import SAM |
|
|
|
model = SAM(model) |
|
else: |
|
from ultralytics import YOLO |
|
|
|
model = YOLO(model, task=task) |
|
if isinstance(overrides.get("pretrained"), str): |
|
model.load(overrides["pretrained"]) |
|
|
|
|
|
if task != model.task: |
|
if task: |
|
LOGGER.warning( |
|
f"WARNING ⚠️ conflicting 'task={task}' passed with 'task={model.task}' model. " |
|
f"Ignoring 'task={task}' and updating to 'task={model.task}' to match model." |
|
) |
|
task = model.task |
|
|
|
|
|
if mode in {"predict", "track"} and "source" not in overrides: |
|
overrides["source"] = ( |
|
"https://ultralytics.com/images/boats.jpg" if task == "obb" else DEFAULT_CFG.source or ASSETS |
|
) |
|
LOGGER.warning(f"WARNING ⚠️ 'source' argument is missing. Using default 'source={overrides['source']}'.") |
|
elif mode in {"train", "val"}: |
|
if "data" not in overrides and "resume" not in overrides: |
|
overrides["data"] = DEFAULT_CFG.data or TASK2DATA.get(task or DEFAULT_CFG.task, DEFAULT_CFG.data) |
|
LOGGER.warning(f"WARNING ⚠️ 'data' argument is missing. Using default 'data={overrides['data']}'.") |
|
elif mode == "export": |
|
if "format" not in overrides: |
|
overrides["format"] = DEFAULT_CFG.format or "torchscript" |
|
LOGGER.warning(f"WARNING ⚠️ 'format' argument is missing. Using default 'format={overrides['format']}'.") |
|
|
|
|
|
getattr(model, mode)(**overrides) |
|
|
|
|
|
LOGGER.info(f"💡 Learn more at https://docs.ultralytics.com/modes/{mode}") |
|
|
|
|
|
if IS_VSCODE and SETTINGS.get("vscode_msg", True): |
|
LOGGER.info(vscode_msg()) |
|
|
|
|
|
|
|
def copy_default_cfg(): |
|
""" |
|
Copies the default configuration file and creates a new one with '_copy' appended to its name. |
|
|
|
This function duplicates the existing default configuration file (DEFAULT_CFG_PATH) and saves it |
|
with '_copy' appended to its name in the current working directory. It provides a convenient way |
|
to create a custom configuration file based on the default settings. |
|
|
|
Examples: |
|
>>> copy_default_cfg() |
|
# Output: default.yaml copied to /path/to/current/directory/default_copy.yaml |
|
# Example YOLO command with this new custom cfg: |
|
# yolo cfg='/path/to/current/directory/default_copy.yaml' imgsz=320 batch=8 |
|
|
|
Notes: |
|
- The new configuration file is created in the current working directory. |
|
- After copying, the function prints a message with the new file's location and an example |
|
YOLO command demonstrating how to use the new configuration file. |
|
- This function is useful for users who want to modify the default configuration without |
|
altering the original file. |
|
""" |
|
new_file = Path.cwd() / DEFAULT_CFG_PATH.name.replace(".yaml", "_copy.yaml") |
|
shutil.copy2(DEFAULT_CFG_PATH, new_file) |
|
LOGGER.info( |
|
f"{DEFAULT_CFG_PATH} copied to {new_file}\n" |
|
f"Example YOLO command with this new custom cfg:\n yolo cfg='{new_file}' imgsz=320 batch=8" |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
entrypoint(debug="") |
|
|