콘텐츠로 이동

YOLO 온디바이스 모바일(iOS/Android) 구현 타당성 심층 보고서

작성일: 2026-05-16
조사 기반: Ultralytics 공식 문서, 학술 논문(2024-2025), GitHub, Roboflow, ONNX Runtime 문서
조사 범위: YOLO11/YOLOv8, iOS Core ML, Android LiteRT/TFLite, 식물 병 진단 특화


결론: YOLO 온디바이스 모바일 구현은 충분히 가능하다. 단, 올바른 버전·크기 선택이 핵심이다.

  • YOLO11n / YOLO11s — 식물 앱 온디바이스에 현실적으로 권장되는 크기
  • iOS: Core ML 변환 시 21FPS(PyTorch) → 85FPS(Core ML + Neural Engine)로 4배 이상 가속. 60+ FPS 달성 가능
  • Android: Snapdragon 8 Gen 2 NPU + TFLite INT8에서 YOLOv8n 기준 ~18-20ms/frame
  • 공식 Flutter SDK 존재(ultralytics_yolo 패키지), iOS(Core ML)·Android(TFLite) 동시 지원
  • 식물 병 진단 목적: 단순 잎 전체 분류 → YOLO-Classify 태스크 사용. 병변 위치 검출 → YOLO Detection 태스크 사용

2. YOLO 버전별 온디바이스 가능성 비교표

섹션 제목: “2. YOLO 버전별 온디바이스 가능성 비교표”

2.1 YOLO11 Detection 모델 스펙 (COCO 기준)

섹션 제목: “2.1 YOLO11 Detection 모델 스펙 (COCO 기준)”
모델파라미터FLOPsmAP50-95CPU ONNX(ms)권장 모바일 사용
YOLO11n2.6M6.5B39.556.1✅ 강력 권장 (최경량)
YOLO11s9.4M21.5B47.090.0✅ 권장 (균형형)
YOLO11m20.1M68.0B51.5183.2⚠️ 플래그십 기기만
YOLO11l25.3M86.9B53.4238.6❌ 모바일 부적합
YOLO11x56.9M194.9B54.7462.8❌ 모바일 불가

CPU ONNX 속도는 서버 CPU 기준. 모바일 NPU 가속 시 수십 배 빠름 (아래 벤치마크 참조).

2.2 YOLO11 Classification 모델 스펙 (ImageNet 기준)

섹션 제목: “2.2 YOLO11 Classification 모델 스펙 (ImageNet 기준)”
모델크기Top-1 정확도모바일 적합성
yolo11n-cls6MB70.0%✅ 최적
yolo11s-cls13MB75.4%✅ 권장
yolo11m-cls22MB77.3%⚠️ 가능
yolo11l-cls27MB78.3%⚠️ 제한적
yolo11x-cls57MB79.5%❌ 부적합
버전모바일 최적화Core ML 지원TFLite 지원Flutter SDK권장 여부
YOLOv5보통❌ 비공식
YOLOv8좋음✅ (제한적 GPU)❌ 비공식⚠️
YOLO11최고✅ (권장)✅ 공식✅ 강력 권장
YOLO26최신✅ NMS-free✅ 참고

YOLO11 권장 이유: YOLOv8m 대비 22% 파라미터 감소 + 1.3% mAP 향상. Core ML NMS 내장 지원. 공식 Flutter SDK.


Terminal window
pip install ultralytics coremltools
# macOS 또는 x86 Linux에서 실행 (Apple Silicon Mac 권장)
# Python 3.8+, coremltools 7.x+

기본 FP32 내보내기:

from ultralytics import YOLO
# Detection 모델
model = YOLO("yolo11n.pt")
model.export(format="coreml", nms=True)
# 출력: yolo11n.mlpackage
# Classification 모델
model = YOLO("yolo11n-cls.pt")
model.export(format="coreml")
# 출력: yolo11n-cls.mlpackage

FP16 양자화 (권장 — 크기 50% 감소, 속도 향상):

model = YOLO("yolo11n.pt")
model.export(format="coreml", nms=True, half=True)

INT8 양자화 (최대 압축 — 크기 75% 감소):

model = YOLO("yolo11n.pt")
model.export(
format="coreml",
nms=True,
int8=True,
data="coco8.yaml" # 양자화 캘리브레이션용 데이터셋 필요
)

CLI 방식:

Terminal window
# FP16 + NMS 포함
yolo export model=yolo11n.pt format=coreml nms=True half=True
# 파인튜닝된 식물 병 모델
yolo export model=plant_disease_yolo11n.pt format=coreml nms=True half=True

주의: nms=True는 Detection 모델에만 적용. 모델 크기: yolo11n FP32 기준 ~12MB → FP16 시 ~6MB → INT8 시 ~3MB (mlpackage 기준)

방법 A: Ultralytics Swift Package (가장 빠름)

  1. Xcode → File → Add Package Dependencies
  2. URL 입력: https://github.com/ultralytics/yolo-ios-app
  3. Package 추가 후 사용:
import YOLO
// 1. 카메라 실시간 추론 (가장 간단)
struct PlantDiagnosisView: View {
var body: some View {
YOLOCamera(
// 직접 mlpackage URL 또는 로컬 에셋 경로
url: URL(string: "https://example.com/plant_yolo11n.mlpackage.zip")!,
task: .detect, // 또는 .classify
cameraPosition: .back
)
.ignoresSafeArea()
}
}
// 2. 단일 이미지 추론
let model = YOLO("plant_yolo11n", task: .detect)
let result = model(uiImage)
for detection in result.boxes ?? [] {
print("\(detection.cls): \(detection.conf)")
}

방법 B: Core ML + Vision 프레임워크 직접 통합

import CoreML
import Vision
import UIKit
class PlantDiseaseDetector {
private var request: VNCoreMLRequest?
init() {
setupModel()
}
private func setupModel() {
guard let modelURL = Bundle.main.url(
forResource: "plant_yolo11n",
withExtension: "mlpackage"
) else { return }
do {
let mlModel = try MLModel(contentsOf: modelURL)
let vnModel = try VNCoreMLModel(for: mlModel)
request = VNCoreMLRequest(model: vnModel) { [weak self] request, error in
self?.handleResults(request.results)
}
// 이미지 비율 유지 (중요)
request?.imageCropAndScaleOption = .scaleFit
} catch {
print("Model load error: \(error)")
}
}
func analyze(image: UIImage, completion: @escaping ([VNRecognizedObjectObservation]) -> Void) {
guard let cgImage = image.cgImage,
let request = request else { return }
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
DispatchQueue.global(qos: .userInitiated).async {
try? handler.perform([request])
}
}
private func handleResults(_ results: [VNObservation]?) {
guard let observations = results as? [VNRecognizedObjectObservation] else { return }
DispatchQueue.main.async {
for obs in observations where obs.confidence > 0.5 {
let label = obs.labels.first?.identifier ?? "unknown"
let box = obs.boundingBox // 정규화된 좌표 (0~1)
print("병명: \(label), 신뢰도: \(obs.confidence), 위치: \(box)")
}
}
}
}
// 카메라 라이브 피드 통합 (AVFoundation)
extension PlantDiseaseDetector: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
let request = request else { return }
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
try? handler.perform([request])
}
}
기기칩셋Neural EngineYOLO11n Core ML 속도
iPhone 12A14 Bionic11.8 TOPS~20-25ms (~40-50 FPS)
iPhone 14A15 Bionic15.8 TOPS~15-18ms (~55-65 FPS)
iPhone 15A16 Bionic17 TOPS~12-15ms (~67-83 FPS)
iPhone 15 ProA17 Pro35 TOPS~8-12ms (85+ FPS)
iPhone 16A1838 TOPS~6-10ms (100+ FPS)

PyTorch on-device → Core ML 변환 시 4배 가속 확인된 사례(YOLO11, 21FPS → 85FPS).
Neural Engine이 FP16/INT8 연산을 CPU 대비 최대 120배 빠르게 처리.


4. Android 구현 방법 (단계별 + 코드)

섹션 제목: “4. Android 구현 방법 (단계별 + 코드)”

TFLite 경로 (권장):

from ultralytics import YOLO
model = YOLO("yolo11n.pt")
# FP32 기본
model.export(format="tflite")
# 출력: yolo11n_float32.tflite
# FP16 양자화
model.export(format="tflite", half=True)
# 출력: yolo11n_float16.tflite
# INT8 양자화 (NPU 최대 활용)
model.export(format="tflite", int8=True, data="coco8.yaml")
# 출력: yolo11n_int8.tflite

ONNX → TFLite 변환 경로 (대안):

Terminal window
# 1단계: ONNX 내보내기
yolo export model=yolo11n.pt format=onnx
# 2단계: ONNX → TFLite 변환 (onnx2tf 사용)
pip install onnx2tf
onnx2tf -i yolo11n.onnx -o yolo11n_tf -oiqt
# 출력: yolo11n_float32.tflite, yolo11n_float16.tflite, yolo11n_integer_quant.tflite

CLI 방식:

Terminal window
yolo export model=yolo11n.pt format=tflite half=True

4.2 Android 프로젝트 통합 (Kotlin + TFLite)

섹션 제목: “4.2 Android 프로젝트 통합 (Kotlin + TFLite)”

build.gradle 의존성:

dependencies {
// TFLite (LiteRT)
implementation("org.tensorflow:tensorflow-lite:2.14.0")
implementation("org.tensorflow:tensorflow-lite-gpu:2.14.0") // GPU delegate
implementation("org.tensorflow:tensorflow-lite-support:0.4.4")
// NNAPI는 TFLite에 내장됨 (별도 의존성 불필요)
}

Kotlin 추론 코드:

import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.gpu.GpuDelegate
import android.graphics.Bitmap
class PlantDiseaseDetector(context: Context) {
private lateinit var interpreter: Interpreter
private val inputSize = 640
init {
loadModel(context)
}
private fun loadModel(context: Context) {
val modelFile = context.assets.open("yolo11n_float16.tflite")
.readBytes()
.let { java.nio.ByteBuffer.wrap(it) }
// GPU Delegate 설정 (지원 기기에서 자동 활성화)
val gpuDelegate = GpuDelegate()
val options = Interpreter.Options().apply {
addDelegate(gpuDelegate)
setNumThreads(4)
// NNAPI (Android 8.1+, NPU 활용)
setUseNNAPI(true)
}
interpreter = Interpreter(modelFile, options)
}
fun analyze(bitmap: Bitmap): List<Detection> {
// 1. 전처리: 640x640으로 리사이즈, 정규화
val resized = Bitmap.createScaledBitmap(bitmap, inputSize, inputSize, true)
val inputBuffer = preprocessBitmap(resized)
// 2. 출력 버퍼 준비 (YOLO11n: 1 x 84 x 8400)
// 84 = 4(bbox) + 80(class scores for COCO)
// 식물 병 파인튜닝 시 클래스 수에 맞게 조정
val outputBuffer = Array(1) { Array(84) { FloatArray(8400) } }
// 3. 추론 실행
interpreter.run(inputBuffer, outputBuffer)
// 4. 후처리: NMS 적용
return postprocess(outputBuffer[0], bitmap.width, bitmap.height)
}
private fun preprocessBitmap(bitmap: Bitmap): java.nio.ByteBuffer {
// RGB float 변환, 0-255 → 0-1 정규화
val buffer = java.nio.ByteBuffer.allocateDirect(4 * 3 * inputSize * inputSize)
buffer.order(java.nio.ByteOrder.nativeOrder())
val pixels = IntArray(inputSize * inputSize)
bitmap.getPixels(pixels, 0, inputSize, 0, 0, inputSize, inputSize)
for (pixel in pixels) {
buffer.putFloat(((pixel shr 16) and 0xFF) / 255.0f) // R
buffer.putFloat(((pixel shr 8) and 0xFF) / 255.0f) // G
buffer.putFloat((pixel and 0xFF) / 255.0f) // B
}
return buffer
}
data class Detection(val label: String, val confidence: Float, val box: RectF)
private fun postprocess(output: Array<FloatArray>, imgW: Int, imgH: Int): List<Detection> {
// YOLO 출력 파싱 + NMS (생략 — 실제 구현 시 ultralytics 후처리 코드 참조)
return emptyList() // placeholder
}
}

주의: TFLite GPU delegate는 YOLOv8/YOLO11의 일부 연산(예: SILU 활성화)을 지원하지 않을 수 있음.
이 경우 setUseNNAPI(true)로 NPU를 직접 활용하거나 CPU fallback 사용.
최선: INT8 + NNAPI delegate 조합 → Snapdragon Hexagon NPU에서 최대 298x CPU 대비 가속.

implementation("com.microsoft.onnxruntime:onnxruntime-android:1.18.0")
import ai.onnxruntime.*
class YOLOOnnxDetector(context: Context) {
private lateinit var session: OrtSession
private val env = OrtEnvironment.getEnvironment()
init {
val modelBytes = context.assets.open("yolo11n.onnx").readBytes()
val options = OrtSession.SessionOptions().apply {
// NNAPI 가속기 추가
addNnapi()
// 또는 GPU 가속기
// addConfigEntry("backend_type", "gpu")
}
session = env.createSession(modelBytes, options)
}
fun detect(bitmap: Bitmap): OrtSession.Result {
val inputTensor = OnnxTensor.createTensor(
env,
preprocessToFloatArray(bitmap),
longArrayOf(1, 3, 640, 640)
)
return session.run(mapOf("images" to inputTensor))
}
}
SoC기기 예시프레임워크양자화YOLOv8n 추론속도
Snapdragon 8 Gen 2Galaxy S23TFLite + NPUINT8~5-8ms (125-200 FPS)
Snapdragon 8 Gen 2Galaxy S23TFLite + CPUFP32~120ms (8 FPS)
Snapdragon 888Galaxy S21TFLite + NPUINT8~12-18ms (55-83 FPS)
Snapdragon 778GGalaxy A52sTFLite + CPUFP16~60-80ms (12-17 FPS)
Dimensity 9000OPPO Find X5TFLite + APUINT8~15-25ms (40-67 FPS)

Samsung Galaxy Tab S9 (Snapdragon 8 Gen 2) 실측: YOLOv8x INT8 NPU = CPU 대비 298x 가속.
ResNet18 기준: NPU FP32 = 1.20ms, INT8 = 0.61ms.


5. 식물 병 진단 특화 전략 — 분류 vs 검출 선택 가이드

섹션 제목: “5. 식물 병 진단 특화 전략 — 분류 vs 검출 선택 가이드”
당신의 앱이 "이 잎이 어떤 병인가?"를 묻는다면 → Classification
당신의 앱이 "이 잎의 어느 부분에 병이 있나?"를 묻는다면 → Detection
항목YOLO Classify (yolo11n-cls)YOLO Detect (yolo11n)
목적잎 전체 병명 분류병변 위치 + 병명 검출
입력이미지 1장이미지 1장
출력클래스별 확률 (예: 흰가루병 94%)바운딩박스 + 클래스 + 신뢰도
학습 데이터PlantVillage (이미지+레이블)PlantDoc (이미지+바운딩박스)
추론 속도더 빠름 (simpler head)약간 느림
모델 크기더 작음 (yolo11n-cls: 6MB)더 큼 (yolo11n detect: ~12MB)
UX”병명: 흰가루병 94%“병변 부위 하이라이트 시각화
어노테이션간단 (이미지-클래스)복잡 (바운딩박스 필요)

초기 MVP → YOLO11n-cls (Classification)

  • PlantVillage 54,000장 이미지 바로 활용 가능
  • 바운딩박스 어노테이션 없어도 됨
  • 학습 난이도 낮음, 빠른 개발
  • 관엽식물 흰가루병, 점무늬병, 역병 등 분류 충분

고도화 단계 → YOLO11n Detection

  • 병변 위치를 앱 UI에 표시 (AR-like 시각화)
  • 복수의 병 동시 감지 (한 잎에 여러 병)
  • 플랜트독(PlantDoc) 데이터셋 활용 또는 커스텀 어노테이션
  • 사용자 경험 크게 차별화 가능

5.4 PlantVillage vs PlantDoc 데이터셋 비교

섹션 제목: “5.4 PlantVillage vs PlantDoc 데이터셋 비교”
항목PlantVillagePlantDoc
이미지 수54,000+2,569
식물 종14종13종
클래스 수38 (병+건강)30
환경실험실 (배경 제거)현실 환경 (야외/실내)
어노테이션이미지 분류 레이블바운딩박스 (YOLO 변환 가능)
라이선스CC BY-SA 4.0CC BY 4.0
목적분류(Classification)검출(Detection)
Roboflow이용 가능YOLO 형식 다운로드 가능

상황권장 모델이유
MVP, 빠른 검증YOLO11n-cls6MB, 70% ImageNet 정확도, 단순 파인튜닝
정확도-속도 균형YOLO11s-cls13MB, 75.4% 정확도, 여전히 실시간 가능
병변 위치 표시 필요YOLO11n (detect)12MB, 39.5 mAP, 실시간 가능
최고 정확도YOLO11s (detect)21.5MB, 47.0 mAP, 플래그십 기기에서 실시간

단계 1: PlantVillage로 분류 모델 파인튜닝

from ultralytics import YOLO
# 사전 학습 모델 로드
model = YOLO("yolo11n-cls.pt")
# PlantVillage 데이터셋으로 파인튜닝
model.train(
data="plantvillage/", # train/, val/ 서브디렉토리
epochs=50,
imgsz=224, # 분류는 224x224로 충분
batch=32,
lr0=0.001,
freeze=10, # 처음 10레이어 동결 (전이학습)
device="mps" # Apple Silicon Mac
)
# 검증
results = model.val()
print(f"Top-1 Accuracy: {results.top1}")

단계 2: 모바일 내보내기

# 학습 완료 후 모바일 배포
model = YOLO("runs/classify/train/weights/best.pt")
# iOS
model.export(format="coreml", half=True)
# Android
model.export(format="tflite", half=True)

단계 3: PlantDoc으로 검출 모델 파인튜닝 (고도화)

model = YOLO("yolo11n.pt")
model.train(
data="plantdoc.yaml", # Roboflow에서 YOLO 형식 다운로드
epochs=100,
imgsz=640,
batch=16,
patience=20, # Early stopping
device="mps"
)

PlantDoc YOLO 형식 다운로드 (Roboflow):

from roboflow import Roboflow
rf = Roboflow(api_key="YOUR_API_KEY")
project = rf.workspace("joseph-nelson").project("plantdoc")
dataset = project.version(1).download("yolov8") # YOLO 형식

6.3 Flutter 크로스플랫폼 통합 (가장 빠른 방법)

섹션 제목: “6.3 Flutter 크로스플랫폼 통합 (가장 빠른 방법)”
pubspec.yaml
dependencies:
ultralytics_yolo: ^0.3.4
// iOS: Core ML (.mlpackage) 자동 사용
// Android: TFLite (.tflite) 자동 사용
// 코드는 하나로 통일
import 'package:ultralytics_yolo/ultralytics_yolo.dart';
class PlantDiagnosisScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: YOLOView(
// 로컬 에셋 모델 (iOS: .mlpackage, Android: .tflite)
modelPath: 'assets/models/plant_yolo11n',
task: YOLOTask.detect, // 또는 YOLOTask.classify
onResult: (List<YOLOResult> results) {
for (final result in results) {
print('병명: ${result.className}');
print('신뢰도: ${result.confidence}');
if (result.boundingBox != null) {
print('위치: ${result.boundingBox}');
}
}
},
confidenceThreshold: 0.5,
iouThreshold: 0.45,
),
);
}
}

Flutter 플러그인 자동 처리: iOS = .mlpackage 파일, Android = .tflite 파일
두 파일을 assets/models/ 에 함께 배치하면 플랫폼별 자동 선택

iOS:

  • 최소: iPhone 12 (A14 Bionic) — YOLO11n-cls에서 40+ FPS
  • 권장: iPhone 14 (A15 Bionic) — YOLO11n detect에서 실시간 가능
  • 최적: iPhone 15 Pro 이상 (A17 Pro) — 모든 모델 실시간

Android:

  • 최소: Snapdragon 778G (2021년 이후 중급) — YOLO11n FP16 CPU 약 15-20 FPS
  • 권장: Snapdragon 888 이상 — NPU로 60+ FPS
  • 최적: Snapdragon 8 Gen 2 이상 — NPU INT8으로 100+ FPS

항목MobileNetV3 (분류)YOLO11n-cls (분류)YOLO11n (검출)
목적분류만분류만분류 + 위치
모델 크기3-5MB6MB~12MB
추론 속도매우 빠름빠름보통
정확도PlantVillage ~85% 가능유사 또는 우수훨씬 많은 정보
병변 위치❌ 불가❌ 불가✅ 가능
학습 용이성쉬움쉬움어노테이션 필요
공식 SDK✅ Flutter 공식✅ Flutter 공식

선택 기준:

  • 분류만 필요 + 최소 모델 크기 → MobileNetV3
  • 분류 + Ultralytics 생태계 활용 + 향후 검출 확장 고려 → YOLO11n-cls (권장)
  • 병변 위치 시각화 필요 → YOLO11n detect (강력 권장)

  • INT8 CoreML 내보내기 시 캘리브레이션 데이터셋 필수 (data 파라미터)
  • .mlmodel.mlpackage 형식 변경됨 (coremltools 7.x 이상)
  • NMS 옵션: Detection 모델만 nms=True 지원
  • TFLite GPU delegate: SILU 등 일부 YOLO 연산 미지원 → CPU fallback 자동 처리
  • YOLOv8 INT8 양자화 시 mAP ~6.5p 하락 (동적 양자화는 0.1p 하락으로 최소화)
  • NNAPI: Android 8.1+ (API 27+) 필요
  • 보급형 기기(Snapdragon 4xx): CPU FP32로 실시간 어려울 수 있음
  • 모델 크기: yolo11n.pt ≈ 5.5MB, CoreML 내보내기 시 2배 팽창 가능 (FP16으로 상쇄)
  • 라이선스: Ultralytics YOLO는 기본 AGPL-3.0. 상업적 앱 배포 시 Enterprise 라이선스 구매 필요