dk - edit
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# Normal Object Detection
|
# Normal Object Detection
|
||||||
|
|
||||||
YOLO 기반 일반 객체 탐지 스크립트입니다. 원격 이미지 URL을 받아 추론하고, 결과를 JSON으로 출력하며 ClearML에 기록합니다.
|
YOLO 기반 일반 객체 탐지 스크립트입니다. 원격 이미지 URL을 받아 추론하고, 결과를 JSON으로 출력합니다.
|
||||||
|
|
||||||
## 요구 사항
|
## 요구 사항
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ uv sync
|
|||||||
## 사용법
|
## 사용법
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run main.py --image_url "https://example.com/image.jpg"
|
uv run start.py --image_url "https://example.com/image.jpg"
|
||||||
```
|
```
|
||||||
|
|
||||||
- `--image_url`: 추론할 이미지 URL (필수)
|
- `--image_url`: 추론할 이미지 URL (필수)
|
||||||
@@ -36,12 +36,34 @@ uv run main.py --image_url "https://example.com/image.jpg"
|
|||||||
| `detections` | 탐지 객체 목록 (클래스, 신뢰도, `xywh` 박스) |
|
| `detections` | 탐지 객체 목록 (클래스, 신뢰도, `xywh` 박스) |
|
||||||
| `image_url` | 이미지 경로 |
|
| `image_url` | 이미지 경로 |
|
||||||
|
|
||||||
## ClearML
|
---
|
||||||
|
|
||||||
스크립트 실행 시 ClearML Task가 초기화되고, 추론 완료 후 결과가 아티팩트로 업로드됩니다.
|
## 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`
|
- **Project**: `Normal_Object_Detection`
|
||||||
- **Task**: `model-yolo26-human`
|
- **Task**: `model-yolo26-human`
|
||||||
- **Artifact**: `final_result` (`output`, `status: PASS`)
|
- **Artifact**: `final_result` (`output`, `status: PASS`)
|
||||||
|
|
||||||
ClearML 서버 연결 설정이 필요합니다 (`clearml-init`).
|
Agent 머신에는 ClearML 서버 연결 설정이 필요합니다 (`clearml-init`).
|
||||||
|
|
||||||
|
### 워크플로우
|
||||||
|
|
||||||
|
Git 업로드, UI 입력, `main.py` 생성, ClearML Task/Agent 연동까지의 순차 절차는 [WORKFLOW.md](./WORKFLOW.md)를 참고하세요.
|
||||||
|
|||||||
@@ -1,86 +1,28 @@
|
|||||||
import json
|
# 시스템 생성 — 이 파일은 커스텀 UI 입력에 따라 자동 생성됩니다. 직접 수정하지 마세요.
|
||||||
import math
|
|
||||||
from pathlib import Path
|
|
||||||
from ultralytics import YOLO
|
|
||||||
import requests
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from clearml import Task # 1. ClearML 임포트
|
from clearml import Task
|
||||||
|
|
||||||
# 방법 A) 가장 최신 트렌드인 NMS-Free 임베디드 특화 모델 로드
|
from start import main
|
||||||
model = YOLO("./weights/yolo26m.pt") # Medium 크기 가중치 자동 다운로드
|
|
||||||
# 방법 B) 복잡한 구도나 정밀 식별에 강한 어텐션 기반 모델 로드
|
|
||||||
# model = YOLO("./weights/yolo12m.pt")
|
|
||||||
|
|
||||||
|
|
||||||
|
def init_acai_task(task: Task, output: dict) -> None:
|
||||||
def xyxy_to_xywh(box: dict) -> dict:
|
"""UI output 정의에 맞춰 ClearML 아티팩트를 업로드한다."""
|
||||||
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 main(image_path: str):
|
|
||||||
# uv run main.py --image_url "https://acai.ketidev.kr:20443/detect/image/202606/20260619_145116_image.jpg"
|
|
||||||
# 이미지 원격 추론 테스트 (COCO 80종 기본 탐지 가능)
|
|
||||||
# results = model("https://acai.ketidev.kr:20443/detect/image/202606/20260619_145116_image.jpg")
|
|
||||||
results = model(image_path)
|
|
||||||
|
|
||||||
result = results[0]
|
|
||||||
|
|
||||||
image_path = Path(result.path)
|
|
||||||
marked_path = image_path.with_name(f"{image_path.stem}_marked{image_path.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": image_path,
|
|
||||||
}
|
|
||||||
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
task = Task.init(
|
|
||||||
project_name="Normal_Object_Detection",
|
|
||||||
task_name="model-yolo26-human"
|
|
||||||
)
|
|
||||||
def init_acai_task(output):
|
|
||||||
result_data = {"output": output, "status": "PASS"}
|
result_data = {"output": output, "status": "PASS"}
|
||||||
task.upload_artifact(name="final_result", artifact_object=result_data)
|
task.upload_artifact(name="final_result", artifact_object=result_data)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("--image_url", type=str)
|
parser.add_argument("--image_url", type=str, required=True)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
image_url = args.image_url
|
task = Task.init(
|
||||||
|
project_name="Normal_Object_Detection",
|
||||||
|
task_name="model-yolo26-human",
|
||||||
|
)
|
||||||
|
task.connect(parser)
|
||||||
|
|
||||||
if image_url is None:
|
output = main(args.image_url)
|
||||||
print("Image path is required")
|
init_acai_task(task, output)
|
||||||
exit(1)
|
|
||||||
|
|
||||||
response = requests.get(image_url)
|
|
||||||
response.raise_for_status()
|
|
||||||
image_data = response.content
|
|
||||||
with open("image.jpg", "wb") as f:
|
|
||||||
f.write(image_data)
|
|
||||||
image_path = "image.jpg"
|
|
||||||
|
|
||||||
output = main(image_path)
|
|
||||||
|
|
||||||
init_acai_task(output)
|
|
||||||
|
|||||||
@@ -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 run(image_path: str) -> dict:
|
||||||
|
"""이미지 경로를 받아 추론하고 결과 dict를 반환한다."""
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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 main(image_url: str) -> dict:
|
||||||
|
image_path = download_image(image_url)
|
||||||
|
return run(image_path)
|
||||||
|
|
||||||
|
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 = main(args.image_url)
|
||||||
|
|
||||||
|
print(json.dumps(output, indent=2, ensure_ascii=False))
|
||||||
Reference in New Issue
Block a user