Add ClearML main.py wrapper for ACAI

This commit is contained in:
2026-07-16 15:56:30 +09:00
commit e472b80387
12 changed files with 2016 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg-info/
.eggs/
dist/
build/
*.egg
.venv/
venv/
env/
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
desktop.ini
# Model weights & exports
# weights/
# *.pt
# *.pth
# *.onnx
# *.engine
# *.weights
# Images (inference input/output)
*_marked.jpg
*_marked.jpeg
*_marked.png
image.jpg
*.jpg
*.jpeg
*.png
*.bmp
*.webp
!image1.jpg
!image2.jpg
!image3.jpg
# Ultralytics outputs
runs/
output/
outputs/
results/
# ClearML
clearml.conf
*.log
# Jupyter
.ipynb_checkpoints/
# Environment & secrets
.env
.env.*
*.pem
+1
View File
@@ -0,0 +1 @@
3.11
+69
View File
@@ -0,0 +1,69 @@
# Normal Object Detection
YOLO 기반 일반 객체 탐지 스크립트입니다. 원격 이미지 URL을 받아 추론하고, 결과를 JSON으로 출력합니다.
## 요구 사항
- Python 3.11+
- [uv](https://docs.astral.sh/uv/) (권장)
## 설치
```bash
uv sync
```
## 사용법
```bash
uv run start.py --image_url "https://example.com/image.jpg"
```
- `--image_url`: 추론할 이미지 URL (필수)
- 모델: `weights/yolo26m.pt` (Ultralytics YOLO, COCO 80종 탐지)
- 이미지는 `image.jpg`로 저장되고, 탐지 결과 이미지는 `*_marked.jpg`로 저장됩니다.
## 출력
표준 출력으로 JSON이 출력됩니다.
| 필드 | 설명 |
|------|------|
| `path` | 입력 이미지 경로 |
| `marked_path` | 바운딩 박스가 그려진 이미지 경로 |
| `shape` | 이미지 크기 (width, height) |
| `speed_ms` | 추론 속도 (ms) |
| `detections` | 탐지 객체 목록 (클래스, 신뢰도, `xywh` 박스) |
| `image_url` | 이미지 경로 |
---
## ClearML 자동 연동 (시스템 생성)
> 아래 내용은 커스텀 UI·ClearML Agent 연동 시 **시스템이 자동으로 처리**하는 부분입니다.
> 개발자는 `start.py`만 유지하면 되며, `main.py`는 UI 입력에 따라 자동 생성됩니다.
### 파일 역할
| 파일 | 역할 | 관리 |
|------|------|------|
| `start.py` | 추론·결과 처리 (`main(image_url)`) | 개발자가 Git에 커밋 |
| `main.py` | ClearML Task 초기화, input/output 연동 | 시스템이 자동 생성 |
### Agent 실행
```bash
uv run main.py --image_url "https://example.com/image.jpg"
```
실행 시 ClearML Task가 초기화되고, `start.main()` 결과가 아티팩트로 업로드됩니다.
- **Project**: `Normal_Object_Detection`
- **Task**: `model-yolo26-human`
- **Artifact**: `final_result` (`output`, `status: PASS`)
Agent 머신에는 ClearML 서버 연결 설정이 필요합니다 (`clearml-init`).
### 워크플로우
Git 업로드, UI 입력, `main.py` 생성, ClearML Task/Agent 연동까지의 순차 절차는 [WORKFLOW.md](./WORKFLOW.md)를 참고하세요.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

+26
View File
@@ -0,0 +1,26 @@
import argparse
from clearml import Task
from start import run
def upload_acai_task(task: Task, output: dict) -> None:
"""UI output 정의에 맞춰 업로드"""
result_data = {"output": output, "status": "PASS"}
task.upload_artifact(name="final_result", artifact_object=result_data)
if __name__ == "__main__":
task = Task.init(
project_name="Normal_Object_Detection",
task_name="model-yolo26-human",
)
parser = argparse.ArgumentParser()
parser.add_argument("--image_url", type=str, required=True)
args = parser.parse_args()
task.connect(parser)
output = run(args.image_url)
upload_acai_task(task, output)
+11
View File
@@ -0,0 +1,11 @@
[project]
name = "normal-object-detection"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"clearml>=2.1.9",
"requests==2.32.5",
"ultralytics==8.4.71",
]
+2
View File
@@ -0,0 +1,2 @@
ultralytics==8.4.71
requests==2.32.5
+75
View File
@@ -0,0 +1,75 @@
import argparse
import json
import math
from pathlib import Path
import requests
from ultralytics import YOLO
# 방법 A) 가장 최신 트렌드인 NMS-Free 임베디드 특화 모델 로드
model = YOLO("./weights/yolo26m.pt")
# 방법 B) 복잡한 구도나 정밀 식별에 강한 어텐션 기반 모델 로드
# model = YOLO("./weights/yolo12m.pt")
def xyxy_to_xywh(box: dict) -> dict:
x1 = math.floor(box["x1"])
y1 = math.floor(box["y1"])
x2 = math.ceil(box["x2"])
y2 = math.ceil(box["y2"])
return {"x": x1, "y": y1, "w": x2 - x1, "h": y2 - y1}
def download_image(image_url: str, save_path: str = "image.jpg") -> str:
"""원격 이미지 URL을 로컬 파일로 저장하고 경로를 반환한다."""
# 이미지 URL 체크
if not image_url.startswith("http"):
raise ValueError("Invalid image URL")
response = requests.get(image_url)
response.raise_for_status()
with open(save_path, "wb") as f:
f.write(response.content)
return save_path
def run(image_url: str) -> dict:
"""이미지 경로를 받아 추론하고 결과 dict를 반환한다."""
image_path = download_image(image_url)
results = model(image_path)
result = results[0]
image_path_obj = Path(result.path)
marked_path = image_path_obj.with_name(
f"{image_path_obj.stem}_marked{image_path_obj.suffix}"
)
result.save(filename=str(marked_path))
detections = []
for det in result.summary():
detections.append({**det, "box": xyxy_to_xywh(det["box"])})
output = {
"path": result.path,
"marked_path": str(marked_path),
"shape": {"height": result.orig_shape[0], "width": result.orig_shape[1]},
"speed_ms": result.speed,
"detections": detections,
"image_url": str(image_path_obj),
}
return output
if __name__ == "__main__":
# 추론 결과를 출력하기 위한 테스트 코드
# uv run start.py --image_url "https://acai.ketidev.kr:20443/detect/image/202606/20260619_145116_image.jpg"
parser = argparse.ArgumentParser()
parser.add_argument("--image_url", type=str, required=True)
args = parser.parse_args()
output = run(args.image_url)
print(json.dumps(output, indent=2, ensure_ascii=False))
Generated
+1767
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.