์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ“ฑ Core ML๊ณผ Vision Framework๋กœ ์‹œ์ž‘ํ•˜๋Š” ์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹ ์—ฌํ–‰

๐Ÿ“ฑ Core ML๊ณผ Vision Framework๋กœ ์‹œ์ž‘ํ•˜๋Š” ์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹ ์—ฌํ–‰

๋‚ด ์•„์ดํฐ์ด ๋˜‘๋˜‘ํ•ด์ง€๋Š” ๋งˆ๋ฒ• ๊ฐ™์€ ์ด์•ผ๊ธฐ โœจ

์•ˆ๋…•! ํ˜น์‹œ ์•„์ดํฐ์œผ๋กœ ์‚ฌ์ง„ ์ฐ์„ ๋•Œ ์ž๋™์œผ๋กœ ์–ผ๊ตด์„ ์ธ์‹ํ•˜๊ฑฐ๋‚˜, ์‚ฌ์ง„ ์•ฑ์—์„œ "๊ณ ์–‘์ด" ๊ฒ€์ƒ‰ํ•˜๋ฉด ๊ณ ์–‘์ด ์‚ฌ์ง„๋งŒ ์™์™ ๋‚˜์˜ค๋Š” ๊ฑฐ ์‹ ๊ธฐํ•˜์ง€ ์•Š์•˜์–ด? ๐Ÿฑ ์ด๊ฒŒ ๋ฐ”๋กœ ์˜ค๋Š˜ ์šฐ๋ฆฌ๊ฐ€ ์ด์•ผ๊ธฐํ•  ์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹์˜ ๋งˆ๋ฒ•์ด์•ผ!



์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ง€ ์•Š๊ณ ๋„ ๋‚ด ๊ธฐ๊ธฐ์—์„œ ์ง์ ‘ AI๊ฐ€ ๋Œ์•„๊ฐ€๋Š” ๊ฑฐ์ง€. ๊ฐœ์ธ์ •๋ณด๋„ ์•ˆ์ „ํ•˜๊ณ , ์ธํ„ฐ๋„ท ์—†์–ด๋„ ์ž‘๋™ํ•˜๊ณ , ์†๋„๋„ ์—„์ฒญ ๋น ๋ฅด๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์–ด. ์• ํ”Œ์ด ์ œ๊ณตํ•˜๋Š” Core ML๊ณผ Vision Framework๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฐ ๋ฉ‹์ง„ ๊ธฐ๋Šฅ๋“ค์„ ์šฐ๋ฆฌ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

iPhone Core ML Vision ์ด๋ฏธ์ง€ ์ธ์‹ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ ๋น ๋ฅธ ์†๋„ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ์„œ๋ฒ„ ์ „์†ก ๋ถˆํ•„์š” ์˜คํ”„๋ผ์ธ ์ž‘๋™ ๐Ÿš€ ์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹
๐ŸŽฏ Core ML์ด ๋ญ๊ธธ๋ž˜?

Core ML์€ ์• ํ”Œ์ด 2017๋…„์— ์„ ๋ณด์ธ ๋จธ์‹ ๋Ÿฌ๋‹ ํ”„๋ ˆ์ž„์›Œํฌ์•ผ. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ํ›ˆ๋ จ๋œ AI ๋ชจ๋ธ์„ iOS ์•ฑ์— ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋„๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋ผ! ๐Ÿ› ๏ธ



์˜ˆ์ „์—๋Š” ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ๋ชจ๋ฐ”์ผ์—์„œ ๋Œ๋ฆฌ๋ ค๋ฉด ์—„์ฒญ ๋ณต์žกํ–ˆ์–ด. ๋ชจ๋ธ ํฌ๊ธฐ๋„ ํฌ๊ณ , ์ตœ์ ํ™”๋„ ์–ด๋ ต๊ณ , ๋ฐฐํ„ฐ๋ฆฌ๋„ ๊ธˆ๋ฐฉ ๋‹ณ๊ณ ... ํ•˜์ง€๋งŒ Core ML์€ ์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์„ ์• ํ”Œ์ด ๋‹ค ํ•ด๊ฒฐํ•ด์คฌ์–ด. ๊ฐœ๋ฐœ์ž๋Š” ๊ทธ๋ƒฅ ๋ชจ๋ธ ํŒŒ์ผ๋งŒ ํ”„๋กœ์ ํŠธ์— ๋„ฃ์œผ๋ฉด ๋! ๐Ÿ˜Ž

โœจ Core ML์˜ ํ•ต์‹ฌ ํŠน์ง•

1. ์ตœ์ ํ™”๋œ ์„ฑ๋Šฅ ๐Ÿƒโ€โ™‚๏ธ
์• ํ”Œ ์‹ค๋ฆฌ์ฝ˜(A์‹œ๋ฆฌ์ฆˆ, M์‹œ๋ฆฌ์ฆˆ ์นฉ)์— ์ตœ์ ํ™”๋˜์–ด ์žˆ์–ด์„œ CPU, GPU, Neural Engine์„ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•ด. ๊ฐ™์€ ๋ชจ๋ธ์ด๋ผ๋„ ๋‹ค๋ฅธ ํ”Œ๋žซํผ๋ณด๋‹ค ํ›จ์”ฌ ๋น ๋ฅด๊ฒŒ ๋Œ์•„๊ฐ€์ง€!


2. ๋‹ค์–‘ํ•œ ๋ชจ๋ธ ์ง€์› ๐ŸŽจ
์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜, ๊ฐ์ฒด ํƒ์ง€, ์ž์—ฐ์–ด ์ฒ˜๋ฆฌ, ์Œ์„ฑ ์ธ์‹ ๋“ฑ ๋‹ค์–‘ํ•œ ์ข…๋ฅ˜์˜ ๋ชจ๋ธ์„ ์ง€์›ํ•ด. TensorFlow, PyTorch, Keras ๊ฐ™์€ ์œ ๋ช…ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋งŒ๋“  ๋ชจ๋ธ๋„ ๋ณ€ํ™˜ํ•ด์„œ ์“ธ ์ˆ˜ ์žˆ์–ด!


3. ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ ๐Ÿ”’
๋ชจ๋“  ์ฒ˜๋ฆฌ๊ฐ€ ๊ธฐ๊ธฐ ๋‚ด์—์„œ ์ด๋ฃจ์–ด์ง€๋‹ˆ๊นŒ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๊ฐ€ ์™ธ๋ถ€๋กœ ๋‚˜๊ฐ€์ง€ ์•Š์•„. ์š”์ฆ˜ ๊ฐ™์€ ์‹œ๋Œ€์— ์ •๋ง ์ค‘์š”ํ•œ ์žฅ์ ์ด์ง€!


4. ๋ฐฐํ„ฐ๋ฆฌ ํšจ์œจ ๐Ÿ”‹
Neural Engine์„ ํ™œ์šฉํ•ด์„œ ์ „๋ ฅ ์†Œ๋น„๋ฅผ ์ตœ์†Œํ™”ํ•ด. ํ•˜๋ฃจ ์ข…์ผ AI ๊ธฐ๋Šฅ์„ ์จ๋„ ๋ฐฐํ„ฐ๋ฆฌ ๊ฑฑ์ •์ด ๋œํ•˜๋‹ค๋Š” ๊ฑฐ!

๐Ÿ‘๏ธ Vision Framework๋Š” ๋˜ ๋ญ์•ผ?

Vision Framework๋Š” Core ML ์œ„์—์„œ ๋™์ž‘ํ•˜๋Š” ์ปดํ“จํ„ฐ ๋น„์ „ ์ „์šฉ ํ”„๋ ˆ์ž„์›Œํฌ์•ผ. ์ด๋ฏธ์ง€๋‚˜ ๋น„๋””์˜ค์—์„œ ๋ญ”๊ฐ€๋ฅผ ์ธ์‹ํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š” ์ž‘์—…์— ํŠนํ™”๋˜์–ด ์žˆ์ง€! ๐Ÿ“ธ



Core ML์ด ๋ฒ”์šฉ ๋จธ์‹ ๋Ÿฌ๋‹ ํ”„๋ ˆ์ž„์›Œํฌ๋ผ๋ฉด, Vision์€ "์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ „๋ฌธ๊ฐ€"๋ผ๊ณ  ๋ณด๋ฉด ๋ผ. ์–ผ๊ตด ์ธ์‹, ๋ฐ”์ฝ”๋“œ ์Šค์บ”, ํ…์ŠคํŠธ ์ธ์‹, ๊ฐ์ฒด ์ถ”์  ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์ด ์ด๋ฏธ ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด์„œ ๋ฐ”๋กœ๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ์–ด! ๐ŸŽฏ

๐Ÿ”ฅ Vision Framework๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ฃผ์š” ๊ธฐ๋Šฅ๋“ค

โ€ข ์–ผ๊ตด ๊ฐ์ง€ ๋ฐ ๋žœ๋“œ๋งˆํฌ ์ธ์‹
์‚ฌ์ง„ ์† ์–ผ๊ตด์„ ์ฐพ๊ณ , ๋ˆˆยท์ฝ”ยท์ž… ์œ„์น˜๊นŒ์ง€ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•ด!


โ€ข ๋ฐ”์ฝ”๋“œ ๋ฐ QR์ฝ”๋“œ ์ธ์‹
์นด๋ฉ”๋ผ๋กœ ๋น„์ถ”๊ธฐ๋งŒ ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ธ์‹. ๊ฒฐ์ œ ์•ฑ์ด๋‚˜ ์žฌ๊ณ  ๊ด€๋ฆฌ ์•ฑ์— ๋”ฑ์ด์ง€!


โ€ข ํ…์ŠคํŠธ ์ธ์‹ (OCR)
์ด๋ฏธ์ง€ ์† ํ…์ŠคํŠธ๋ฅผ ์ถ”์ถœํ•ด์„œ ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•ด์ค˜. ๋ช…ํ•จ ์Šค์บ”, ๋ฌธ์„œ ๋””์ง€ํ„ธํ™”์— ์œ ์šฉํ•ด!


โ€ข ๊ฐ์ฒด ์ถ”์ 
๋น„๋””์˜ค์—์„œ ํŠน์ • ๊ฐ์ฒด๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์–ด. AR ์•ฑ์ด๋‚˜ ์Šคํฌ์ธ  ๋ถ„์„ ์•ฑ์— ํ™œ์šฉ ๊ฐ€๋Šฅ!


โ€ข ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋ฐ ๊ฐ์ฒด ํƒ์ง€
Core ML ๋ชจ๋ธ๊ณผ ๊ฒฐํ•ฉํ•ด์„œ ์‚ฌ์ง„ ์† ์‚ฌ๋ฌผ์ด ๋ญ”์ง€ ์•Œ์•„๋‚ด๊ฑฐ๋‚˜, ์—ฌ๋Ÿฌ ๊ฐ์ฒด์˜ ์œ„์น˜๋ฅผ ๋™์‹œ์— ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์–ด!

๐Ÿš€ ์‹ค์ „! Core ML ๋ชจ๋ธ ์‚ฌ์šฉํ•˜๊ธฐ

์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณผ๊นŒ? ๊ฑฑ์ • ๋งˆ! ์ƒ๊ฐ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ•ด. ๋งˆ์น˜ ๋ ˆ๊ณ  ๋ธ”๋ก ์กฐ๋ฆฝํ•˜๋“ฏ์ด ๋‹จ๊ณ„๋ณ„๋กœ ๋”ฐ๋ผ์˜ค๋ฉด ๋ผ! ๐Ÿงฑ

STEP 1

๋ชจ๋ธ ์ค€๋น„ํ•˜๊ธฐ

๋จผ์ € Core ML ๋ชจ๋ธ์ด ํ•„์š”ํ•ด. ์• ํ”Œ์ด ์ œ๊ณตํ•˜๋Š” ์ƒ˜ํ”Œ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜, ์ง์ ‘ ๋งŒ๋“  ๋ชจ๋ธ์„ ๋ณ€ํ™˜ํ•ด์„œ ์“ธ ์ˆ˜ ์žˆ์–ด. ์• ํ”Œ ๊ฐœ๋ฐœ์ž ์‚ฌ์ดํŠธ์—์„œ MobileNet, ResNet, SqueezeNet ๊ฐ™์€ ์œ ๋ช…ํ•œ ๋ชจ๋ธ๋“ค์„ ๋ฌด๋ฃŒ๋กœ ๋‹ค์šด๋ฐ›์„ ์ˆ˜ ์žˆ์–ด! ๐Ÿ“ฆ


๋ชจ๋ธ ํŒŒ์ผ ํ™•์žฅ์ž๋Š” .mlmodel์ด์•ผ. ์ด ํŒŒ์ผ์„ Xcode ํ”„๋กœ์ ํŠธ์— ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญํ•˜๋ฉด ์ž๋™์œผ๋กœ Swift ํด๋ž˜์Šค๊ฐ€ ์ƒ์„ฑ๋ผ. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜Š

STEP 2

๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์ฝ”๋“œ

import UIKit
import CoreML
import Vision

class ImageClassifierVC: UIViewController {
    
    // MobileNetV2 ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
    let model = try? VNCoreMLModel(for: MobileNetV2().model)
    
    func classifyImage(_ image: UIImage) {
        guard let model = model else {
            print("๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ!")
            return
        }
        
        // Vision ์š”์ฒญ ์ƒ์„ฑ
        let request = VNCoreMLRequest(model: model) { request, error in
            guard let results = request.results as? [VNClassificationObservation],
                  let topResult = results.first else {
                print("๋ถ„๋ฅ˜ ์‹คํŒจ!")
                return
            }
            
            // ๊ฒฐ๊ณผ ์ถœ๋ ฅ
            print("์ธ์‹ ๊ฒฐ๊ณผ: \(topResult.identifier)")
            print("์‹ ๋ขฐ๋„: \(topResult.confidence * 100)%")
        }
        
        // ์ด๋ฏธ์ง€๋ฅผ CIImage๋กœ ๋ณ€ํ™˜
        guard let ciImage = CIImage(image: image) else {
            print("์ด๋ฏธ์ง€ ๋ณ€ํ™˜ ์‹คํŒจ!")
            return
        }
        
        // ์š”์ฒญ ์‹คํ–‰
        let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
        try? handler.perform([request])
    }
}
๐Ÿ’ก ์ฝ”๋“œ ์„ค๋ช… ์ด ์ฝ”๋“œ๋Š” ์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ํ•ต์‹ฌ์€ ์„ธ ๋‹จ๊ณ„์•ผ:

1) Core ML ๋ชจ๋ธ์„ Vision ๋ชจ๋ธ๋กœ ๋ž˜ํ•‘ (VNCoreMLModel)
2) Vision ์š”์ฒญ ๊ฐ์ฒด ์ƒ์„ฑ (VNCoreMLRequest)
3) ์ด๋ฏธ์ง€ ํ•ธ๋“ค๋Ÿฌ๋กœ ์š”์ฒญ ์‹คํ–‰ (VNImageRequestHandler)

๊ฒฐ๊ณผ๋Š” VNClassificationObservation ๋ฐฐ์—ด๋กœ ๋ฐ˜ํ™˜๋˜๋Š”๋ฐ, ์‹ ๋ขฐ๋„ ์ˆœ์œผ๋กœ ์ •๋ ฌ๋˜์–ด ์žˆ์–ด!
STEP 3

์‹ค์‹œ๊ฐ„ ์นด๋ฉ”๋ผ ๋ถ„๋ฅ˜

์ •์ง€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜๋Š” ์ด์ œ ํ–ˆ์œผ๋‹ˆ, ์‹ค์‹œ๊ฐ„ ์นด๋ฉ”๋ผ ํ”ผ๋“œ์—์„œ ๋ถ„๋ฅ˜ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์•Œ์•„๋ณด์ž! ๐Ÿ“น AVFoundation๊ณผ ๊ฒฐํ•ฉํ•˜๋ฉด ์นด๋ฉ”๋ผ๋กœ ๋ณด์ด๋Š” ๊ฑธ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ธ์‹ํ•  ์ˆ˜ ์žˆ์–ด.

import AVFoundation

class RealtimeClassifierVC: UIViewController {
    
    var captureSession: AVCaptureSession!
    var previewLayer: AVCaptureVideoPreviewLayer!
    let model = try? VNCoreMLModel(for: MobileNetV2().model)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCamera()
    }
    
    func setupCamera() {
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .photo
        
        guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, 
                                                    for: .video, 
                                                    position: .back),
              let input = try? AVCaptureDeviceInput(device: camera) else {
            return
        }
        
        captureSession.addInput(input)
        
        let output = AVCaptureVideoDataOutput()
        output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
        captureSession.addOutput(output)
        
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.frame = view.bounds
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)
        
        captureSession.startRunning()
    }
}

extension RealtimeClassifierVC: AVCaptureVideoDataOutputSampleBufferDelegate {
    
    func captureOutput(_ output: AVCaptureOutput, 
                      didOutput sampleBuffer: CMSampleBuffer, 
                      from connection: AVCaptureConnection) {
        
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
              let model = model else {
            return
        }
        
        let request = VNCoreMLRequest(model: model) { request, error in
            guard let results = request.results as? [VNClassificationObservation],
                  let topResult = results.first else {
                return
            }
            
            DispatchQueue.main.async {
                // UI ์—…๋ฐ์ดํŠธ (๋ผ๋ฒจ์— ๊ฒฐ๊ณผ ํ‘œ์‹œ ๋“ฑ)
                print("์‹ค์‹œ๊ฐ„ ์ธ์‹: \(topResult.identifier) - \(topResult.confidence)")
            }
        }
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
        try? handler.perform([request])
    }
}
โš ๏ธ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ ์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ๋Š” ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•ด! ๋ช‡ ๊ฐ€์ง€ ํŒ์„ ์•Œ๋ ค์ค„๊ฒŒ:

โ€ข ํ”„๋ ˆ์ž„ ์Šคํ‚ต: ๋ชจ๋“  ํ”„๋ ˆ์ž„์„ ์ฒ˜๋ฆฌํ•˜์ง€ ๋ง๊ณ  2~3ํ”„๋ ˆ์ž„๋งˆ๋‹ค ํ•œ ๋ฒˆ์”ฉ ์ฒ˜๋ฆฌํ•ด
โ€ข ํ•ด์ƒ๋„ ์กฐ์ ˆ: captureSession.sessionPreset์„ .medium์ด๋‚˜ .low๋กœ ๋‚ฎ์ถฐ๋ด
โ€ข ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ๋ธ”๋กํ•˜์ง€ ์•Š๋„๋ก ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ ์‚ฌ์šฉ
โ€ข ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ: ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€๋ฅผ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋ฉด ๋” ํšจ์œจ์ ์ด์•ผ
์นด๋ฉ”๋ผ ์ž…๋ ฅ ๐Ÿ“ท Core ML ์ฒ˜๋ฆฌ Neural Engine ์ดˆ๊ณ ์† ์ถ”๋ก  ๊ฒฐ๊ณผ ์ถœ๋ ฅ ๊ฐ•์•„์ง€ ์‹ ๋ขฐ๋„: 95.3% ๊ณ ์–‘์ด ์‹ ๋ขฐ๋„: 3.2% ํ† ๋ผ ์‹ ๋ขฐ๋„: 1.5%
๐ŸŽจ Vision Framework ์‹ฌํ™” ํ™œ์šฉ

์ด์ œ Vision Framework์˜ ๋‹ค๋ฅธ ๋ฉ‹์ง„ ๊ธฐ๋Šฅ๋“ค๋„ ์‚ดํŽด๋ณด์ž! Core ML๊ณผ ๊ฒฐํ•ฉํ•˜์ง€ ์•Š์•„๋„ Vision๋งŒ์œผ๋กœ๋„ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒŒ ์—„์ฒญ ๋งŽ์•„. ๐ŸŒŸ

์–ผ๊ตด ๊ฐ์ง€ ๋ฐ ๋žœ๋“œ๋งˆํฌ ์ธ์‹
import Vision

func detectFaces(in image: UIImage) {
    guard let ciImage = CIImage(image: image) else { return }
    
    // ์–ผ๊ตด ๊ฐ์ง€ ์š”์ฒญ ์ƒ์„ฑ
    let faceRequest = VNDetectFaceRectanglesRequest { request, error in
        guard let observations = request.results as? [VNFaceObservation] else {
            return
        }
        
        print("๊ฐ์ง€๋œ ์–ผ๊ตด ์ˆ˜: \(observations.count)")
        
        for face in observations {
            let boundingBox = face.boundingBox
            print("์–ผ๊ตด ์œ„์น˜: \(boundingBox)")
            print("์‹ ๋ขฐ๋„: \(face.confidence)")
        }
    }
    
    // ์–ผ๊ตด ๋žœ๋“œ๋งˆํฌ ๊ฐ์ง€ (๋ˆˆ, ์ฝ”, ์ž… ๋“ฑ)
    let landmarkRequest = VNDetectFaceLandmarksRequest { request, error in
        guard let observations = request.results as? [VNFaceObservation] else {
            return
        }
        
        for face in observations {
            if let landmarks = face.landmarks {
                // ์™ผ์ชฝ ๋ˆˆ
                if let leftEye = landmarks.leftEye {
                    print("์™ผ์ชฝ ๋ˆˆ ํฌ์ธํŠธ ์ˆ˜: \(leftEye.pointCount)")
                }
                
                // ์˜ค๋ฅธ์ชฝ ๋ˆˆ
                if let rightEye = landmarks.rightEye {
                    print("์˜ค๋ฅธ์ชฝ ๋ˆˆ ํฌ์ธํŠธ ์ˆ˜: \(rightEye.pointCount)")
                }
                
                // ์ฝ”
                if let nose = landmarks.nose {
                    print("์ฝ” ํฌ์ธํŠธ ์ˆ˜: \(nose.pointCount)")
                }
                
                // ์ž…
                if let mouth = landmarks.outerLips {
                    print("์ž…์ˆ  ํฌ์ธํŠธ ์ˆ˜: \(mouth.pointCount)")
                }
            }
        }
    }
    
    let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    try? handler.perform([faceRequest, landmarkRequest])
}
๐Ÿ’ก ํ™œ์šฉ ์•„์ด๋””์–ด ์–ผ๊ตด ๋žœ๋“œ๋งˆํฌ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์ด๋Ÿฐ ์•ฑ๋“ค์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด:

โ€ข ๊ฐ€์ƒ ๋ฉ”์ดํฌ์—… ์•ฑ: ์ž…์ˆ  ์œ„์น˜์— ๋ฆฝ์Šคํ‹ฑ ์ƒ‰์ƒ ์ž…ํžˆ๊ธฐ
โ€ข ์•ˆ๊ฒฝ ๊ฐ€์ƒ ์ฐฉ์šฉ: ๋ˆˆ ์œ„์น˜์— ๋งž์ถฐ ์•ˆ๊ฒฝ ๋ Œ๋”๋ง
โ€ข ํ‘œ์ • ์ธ์‹: ์ž… ๋ชจ์–‘์œผ๋กœ ์›ƒ๋Š”์ง€ ์ฐก๊ทธ๋ฆฌ๋Š”์ง€ ํŒ๋‹จ
โ€ข ์–ผ๊ตด ํ•„ํ„ฐ: ์Šค๋ƒ…์ฑ— ๊ฐ™์€ ์žฌ๋ฏธ์žˆ๋Š” ํ•„ํ„ฐ ํšจ๊ณผ
ํ…์ŠคํŠธ ์ธ์‹ (OCR)
func recognizeText(in image: UIImage) {
    guard let ciImage = CIImage(image: image) else { return }
    
    let request = VNRecognizeTextRequest { request, error in
        guard let observations = request.results as? [VNRecognizedTextObservation] else {
            return
        }
        
        let recognizedStrings = observations.compactMap { observation in
            observation.topCandidates(1).first?.string
        }
        
        print("์ธ์‹๋œ ํ…์ŠคํŠธ:")
        recognizedStrings.forEach { print($0) }
    }
    
    // ์ธ์‹ ์ •ํ™•๋„ ์„ค์ • (accurate๊ฐ€ ๋” ์ •ํ™•ํ•˜์ง€๋งŒ ๋А๋ฆผ)
    request.recognitionLevel = .accurate
    
    // ์ธ์‹ํ•  ์–ธ์–ด ์„ค์ •
    request.recognitionLanguages = ["ko-KR", "en-US"]
    
    // ์ž๋™ ์–ธ์–ด ๊ฐ์ง€
    request.usesLanguageCorrection = true
    
    let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    try? handler.perform([request])
}

๐Ÿ“ OCR ํ™œ์šฉ ์‚ฌ๋ก€

๋ช…ํ•จ ์Šค์บ” ์•ฑ
๋ช…ํ•จ์„ ์ฐ์œผ๋ฉด ์ž๋™์œผ๋กœ ์ด๋ฆ„, ์ „ํ™”๋ฒˆํ˜ธ, ์ด๋ฉ”์ผ์„ ์ถ”์ถœํ•ด์„œ ์—ฐ๋ฝ์ฒ˜์— ์ €์žฅ! ๋น„์ฆˆ๋‹ˆ์Šค ๋„คํŠธ์›Œํ‚น์— ํ•„์ˆ˜์ง€. ๐Ÿค


๋ฌธ์„œ ๋””์ง€ํ„ธํ™”
์ข…์ด ๋ฌธ์„œ๋ฅผ ์Šค์บ”ํ•ด์„œ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅํ•œ PDF๋กœ ๋ณ€ํ™˜. ํ•™์ƒ๋“ค์ด ๊ต๊ณผ์„œ๋‚˜ ๋…ธํŠธ ์ •๋ฆฌํ•  ๋•Œ ์œ ์šฉํ•ด! ๐Ÿ“š


๋ฒˆ์—ญ ์•ฑ
์™ธ๊ตญ์–ด ๊ฐ„ํŒ์ด๋‚˜ ๋ฉ”๋‰ดํŒ์„ ์นด๋ฉ”๋ผ๋กœ ๋น„์ถ”๋ฉด ์‹ค์‹œ๊ฐ„ ๋ฒˆ์—ญ. ์—ฌํ–‰ ๊ฐˆ ๋•Œ ์ง„์งœ ํŽธํ•ด! โœˆ๏ธ


์˜์ˆ˜์ฆ ๊ด€๋ฆฌ
์˜์ˆ˜์ฆ ์‚ฌ์ง„์—์„œ ๊ธˆ์•ก, ๋‚ ์งœ, ์ƒ์ ๋ช…์„ ์ž๋™ ์ถ”์ถœํ•ด์„œ ๊ฐ€๊ณ„๋ถ€ ์ž‘์„ฑ. ์žฌ๋ฌด ๊ด€๋ฆฌ๊ฐ€ ์‰ฌ์›Œ์ ธ! ๐Ÿ’ฐ

๋ฐ”์ฝ”๋“œ ๋ฐ QR์ฝ”๋“œ ์Šค์บ”
func scanBarcode(in image: UIImage) {
    guard let ciImage = CIImage(image: image) else { return }
    
    let request = VNDetectBarcodesRequest { request, error in
        guard let observations = request.results as? [VNBarcodeObservation] else {
            return
        }
        
        for barcode in observations {
            if let payload = barcode.payloadStringValue {
                print("๋ฐ”์ฝ”๋“œ ํƒ€์ž…: \(barcode.symbology.rawValue)")
                print("๋ฐ”์ฝ”๋“œ ๋ฐ์ดํ„ฐ: \(payload)")
                
                // QR์ฝ”๋“œ์ธ ๊ฒฝ์šฐ URL ์ฒ˜๋ฆฌ
                if barcode.symbology == .QR,
                   let url = URL(string: payload) {
                    print("QR์ฝ”๋“œ URL: \(url)")
                    // UIApplication.shared.open(url) ๋กœ ์—ด๊ธฐ ๊ฐ€๋Šฅ
                }
            }
        }
    }
    
    // ํŠน์ • ๋ฐ”์ฝ”๋“œ ํƒ€์ž…๋งŒ ์ธ์‹ํ•˜๋„๋ก ์„ค์ • ๊ฐ€๋Šฅ
    // request.symbologies = [.QR, .EAN13, .Code128]
    
    let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    try? handler.perform([request])
}
๊ฐ์ฒด ์ถ”์ 
class ObjectTrackerVC: UIViewController {
    
    var trackingRequest: VNTrackObjectRequest?
    var lastObservation: VNDetectedObjectObservation?
    
    // ์ถ”์  ์‹œ์ž‘
    func startTracking(object: VNDetectedObjectObservation, in pixelBuffer: CVPixelBuffer) {
        // ์ƒˆ๋กœ์šด ์ถ”์  ์š”์ฒญ ์ƒ์„ฑ
        trackingRequest = VNTrackObjectRequest(detectedObjectObservation: object) { request, error in
            guard let observation = request.results?.first as? VNDetectedObjectObservation else {
                return
            }
            
            // ์ถ”์  ์„ฑ๊ณต
            self.lastObservation = observation
            print("๊ฐ์ฒด ์œ„์น˜: \(observation.boundingBox)")
            print("์‹ ๋ขฐ๋„: \(observation.confidence)")
            
            // UI ์—…๋ฐ์ดํŠธ (๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ ๋“ฑ)
            DispatchQueue.main.async {
                self.updateTrackingBox(observation.boundingBox)
            }
        }
        
        trackingRequest?.trackingLevel = .accurate
    }
    
    // ๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ ์ถ”์  ๊ณ„์†
    func continueTracking(in pixelBuffer: CVPixelBuffer) {
        guard let request = trackingRequest else { return }
        
        let handler = VNSequenceRequestHandler()
        try? handler.perform([request], on: pixelBuffer)
        
        // ์‹ ๋ขฐ๋„๊ฐ€ ๋‚ฎ์œผ๋ฉด ์žฌํƒ์ง€ ํ•„์š”
        if let observation = lastObservation, observation.confidence < 0.5 {
            print("์ถ”์  ์‹ ๋ขฐ๋„ ๋‚ฎ์Œ - ์žฌํƒ์ง€ ํ•„์š”")
            // ์—ฌ๊ธฐ์„œ ๊ฐ์ฒด ์žฌํƒ์ง€ ๋กœ์ง ์‹คํ–‰
        }
    }
    
    func updateTrackingBox(_ boundingBox: CGRect) {
        // ํ™”๋ฉด์— ๋ฐ”์šด๋”ฉ ๋ฐ•์Šค ๊ทธ๋ฆฌ๊ธฐ
        // boundingBox๋Š” ์ •๊ทœํ™”๋œ ์ขŒํ‘œ (0~1)์ด๋ฏ€๋กœ ์‹ค์ œ ํ™”๋ฉด ํฌ๊ธฐ๋กœ ๋ณ€ํ™˜ ํ•„์š”
    }
}

๐ŸŽฏ ๊ฐ์ฒด ์ถ”์  ํ™œ์šฉ ์‚ฌ๋ก€

์Šคํฌ์ธ  ๋ถ„์„ ์•ฑ โšฝ
์ถ•๊ตฌ๊ณต์ด๋‚˜ ์„ ์ˆ˜๋ฅผ ์ถ”์ ํ•ด์„œ ์ด๋™ ๊ฒฝ๋กœ ๋ถ„์„. ์ฝ”์น˜๋“ค์ด ์ „์ˆ  ๋ถ„์„ํ•  ๋•Œ ์‚ฌ์šฉ!


AR ๊ฒŒ์ž„ ๐ŸŽฎ
์‹ค์ œ ๋ฌผ์ฒด๋ฅผ ์ถ”์ ํ•ด์„œ ๊ฐ€์ƒ ๊ฐ์ฒด๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ๋ฐฐ์น˜. ํฌ์ผ“๋ชฌ ๊ณ  ๊ฐ™์€ ๊ฒŒ์ž„์˜ ํ•ต์‹ฌ ๊ธฐ์ˆ !


๋ณด์•ˆ ์‹œ์Šคํ…œ ๐Ÿ”
์˜์‹ฌ์Šค๋Ÿฌ์šด ๋ฌผ์ฒด๋‚˜ ์‚ฌ๋žŒ์„ ์ž๋™์œผ๋กœ ์ถ”์ . CCTV ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์— ํ™œ์šฉ!


์ž์œจ์ฃผํ–‰ ๋ณด์กฐ ๐Ÿš—
์•ž์ฐจ๋‚˜ ๋ณดํ–‰์ž๋ฅผ ์ถ”์ ํ•ด์„œ ์ถฉ๋Œ ๋ฐฉ์ง€. ์ž๋™์ฐจ ์•ˆ์ „ ์‹œ์Šคํ…œ์˜ ๊ธฐ์ดˆ!

๐Ÿ› ๏ธ ์ปค์Šคํ…€ ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ

์• ํ”Œ์ด ์ œ๊ณตํ•˜๋Š” ๋ชจ๋ธ๋„ ์ข‹์ง€๋งŒ, ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ํŠน์ • ์ž‘์—…์„ ์œ„ํ•œ ์ปค์Šคํ…€ ๋ชจ๋ธ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์„ ๋•Œ๊ฐ€ ์žˆ์ง€? ์˜ˆ๋ฅผ ๋“ค์–ด ์šฐ๋ฆฌ ์ง‘ ๊ฐ•์•„์ง€ ํ’ˆ์ข…์„ ๊ตฌ๋ณ„ํ•˜๊ฑฐ๋‚˜, ํšŒ์‚ฌ ์ œํ’ˆ์„ ์ธ์‹ํ•˜๋Š” ๋ชจ๋ธ ๊ฐ™์€ ๊ฑฐ! ๐Ÿ•



๊ฑฑ์ • ๋งˆ! Create ML์ด๋ผ๋Š” ์• ํ”Œ์˜ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋”ฉ ์—†์ด๋„ ๋ชจ๋ธ์„ ํ›ˆ๋ จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด. ์ •๋ง ์‰ฌ์›Œ! ๐Ÿ˜Š

Create ML๋กœ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ

๐Ÿ“‹ ์ค€๋น„๋ฌผ

1. ํ•™์Šต ๋ฐ์ดํ„ฐ
๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ์ตœ์†Œ 10์žฅ ์ด์ƒ์˜ ์ด๋ฏธ์ง€๊ฐ€ ํ•„์š”ํ•ด. ๋งŽ์„์ˆ˜๋ก ์ข‹์ง€๋งŒ, 100~200์žฅ ์ •๋„๋ฉด ์ถฉ๋ถ„ํ•ด!


2. ํด๋” ๊ตฌ์กฐ
์ด๋ฏธ์ง€๋ฅผ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ํด๋”์— ์ •๋ฆฌํ•ด์•ผ ํ•ด:

Training Data/
  โ”œโ”€โ”€ ๊ฐ•์•„์ง€/
  โ”‚   โ”œโ”€โ”€ dog1.jpg
  โ”‚   โ”œโ”€โ”€ dog2.jpg
  โ”‚   โ””โ”€โ”€ ...
  โ”œโ”€โ”€ ๊ณ ์–‘์ด/
  โ”‚   โ”œโ”€โ”€ cat1.jpg
  โ”‚   โ”œโ”€โ”€ cat2.jpg
  โ”‚   โ””โ”€โ”€ ...
  โ””โ”€โ”€ ํ† ๋ผ/
      โ”œโ”€โ”€ rabbit1.jpg
      โ”œโ”€โ”€ rabbit2.jpg
      โ””โ”€โ”€ ...

3. ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ (์„ ํƒ์‚ฌํ•ญ)
ํ•™์Šต์— ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ๋ณ„๋„์˜ ์ด๋ฏธ์ง€๋กœ ๋ชจ๋ธ ์„ฑ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•ด!

Create ML ์‚ฌ์šฉ ๋‹จ๊ณ„

๐ŸŽ“ Step-by-Step ๊ฐ€์ด๋“œ

Step 1: Create ML ์•ฑ ์‹คํ–‰
Xcode ๋ฉ”๋‰ด์—์„œ Xcode โ†’ Open Developer Tool โ†’ Create ML ์„ ํƒ!


Step 2: ์ƒˆ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ
File โ†’ New Project โ†’ Image Classifier ์„ ํƒ. ํ”„๋กœ์ ํŠธ ์ด๋ฆ„๊ณผ ์ €์žฅ ์œ„์น˜ ์ง€์ •!


Step 3: ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
Training Data ์„น์…˜์— ์ค€๋น„ํ•œ ํด๋”๋ฅผ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ! ์ž๋™์œผ๋กœ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ์ธ์‹ํ•ด์ค˜.


Step 4: ํ•™์Šต ์‹œ์ž‘
Train ๋ฒ„ํŠผ ํด๋ฆญ! ๋ฐ์ดํ„ฐ ์–‘์— ๋”ฐ๋ผ ๋ช‡ ๋ถ„์—์„œ ๋ช‡ ์‹œ๊ฐ„ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์–ด. ์ปคํ”ผ ํ•œ ์ž” ํ•˜๊ณ  ์™€! โ˜•


Step 5: ๋ชจ๋ธ ํ‰๊ฐ€
ํ•™์Šต์ด ๋๋‚˜๋ฉด Validation ํƒญ์—์„œ ์ •ํ™•๋„๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด. ๋ณดํ†ต 80% ์ด์ƒ์ด๋ฉด ๊ดœ์ฐฎ์•„!


Step 6: ๋ชจ๋ธ ๋‚ด๋ณด๋‚ด๊ธฐ
Output ํƒญ์—์„œ Get ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ .mlmodel ํŒŒ์ผ์„ ์ €์žฅ! ์ด์ œ Xcode ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽ‰

โš ๏ธ ๋ชจ๋ธ ํ•™์Šต ํŒ โ€ข ๋ฐ์ดํ„ฐ ํ’ˆ์งˆ์ด ์ค‘์š”ํ•ด: ํ๋ฆฟํ•˜๊ฑฐ๋‚˜ ์ด์ƒํ•œ ๊ฐ๋„์˜ ์ด๋ฏธ์ง€๋Š” ์ œ์™ธํ•˜์ž
โ€ข ๊ท ํ˜• ์žกํžŒ ๋ฐ์ดํ„ฐ์…‹: ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ์˜ ์ด๋ฏธ์ง€ ์ˆ˜๋ฅผ ๋น„์Šทํ•˜๊ฒŒ ๋งž์ถฐ์ค˜
โ€ข ๋‹ค์–‘์„ฑ ํ™•๋ณด: ๋‹ค์–‘ํ•œ ์กฐ๋ช…, ๋ฐฐ๊ฒฝ, ๊ฐ๋„์˜ ์ด๋ฏธ์ง€๋ฅผ ํฌํ•จ์‹œ์ผœ
โ€ข ์ฆ๊ฐ• ๊ธฐ๋ฒ• ํ™œ์šฉ: Create ML์€ ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ํšŒ์ „/๋ฐ˜์ „์‹œ์ผœ ๋ฐ์ดํ„ฐ๋ฅผ ๋Š˜๋ ค์ค˜
โ€ข ๊ณผ์ ํ•ฉ ์ฃผ์˜: ํ•™์Šต ์ •ํ™•๋„๋Š” ๋†’์€๋ฐ ์‹ค์ œ ์‚ฌ์šฉ ์‹œ ์ •ํ™•๋„๊ฐ€ ๋‚ฎ๋‹ค๋ฉด ๊ณผ์ ํ•ฉ ์˜์‹ฌ!
๐Ÿ”„ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ ๋ชจ๋ธ ๋ณ€ํ™˜ํ•˜๊ธฐ

TensorFlow๋‚˜ PyTorch๋กœ ๋งŒ๋“  ๋ชจ๋ธ์„ Core ML๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? coremltools๋ผ๋Š” ํŒŒ์ด์ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ! ๐Ÿ

TensorFlow ๋ชจ๋ธ ๋ณ€ํ™˜ ์˜ˆ์ œ

# Python ์ฝ”๋“œ
import coremltools as ct
import tensorflow as tf

# TensorFlow ๋ชจ๋ธ ๋กœ๋“œ
model = tf.keras.models.load_model('my_model.h5')

# Core ML๋กœ ๋ณ€ํ™˜
coreml_model = ct.convert(
    model,
    inputs=[ct.ImageType(shape=(1, 224, 224, 3))],
    classifier_config=ct.ClassifierConfig(['cat', 'dog', 'rabbit'])
)

# ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
coreml_model.author = '๋‚ด ์ด๋ฆ„'
coreml_model.license = 'MIT'
coreml_model.short_description = '๋™๋ฌผ ๋ถ„๋ฅ˜ ๋ชจ๋ธ'

# ์ €์žฅ
coreml_model.save('AnimalClassifier.mlmodel')

PyTorch ๋ชจ๋ธ ๋ณ€ํ™˜ ์˜ˆ์ œ

# Python ์ฝ”๋“œ
import torch
import coremltools as ct

# PyTorch ๋ชจ๋ธ ๋กœ๋“œ
model = torch.load('my_pytorch_model.pth')
model.eval()

# ์˜ˆ์ œ ์ž…๋ ฅ ์ƒ์„ฑ
example_input = torch.rand(1, 3, 224, 224)

# TorchScript๋กœ ๋ณ€ํ™˜
traced_model = torch.jit.trace(model, example_input)

# Core ML๋กœ ๋ณ€ํ™˜
coreml_model = ct.convert(
    traced_model,
    inputs=[ct.ImageType(shape=example_input.shape)]
)

coreml_model.save('MyPyTorchModel.mlmodel')
๐Ÿ’ก ๋ณ€ํ™˜ ์‹œ ์ฃผ์˜์‚ฌํ•ญ โ€ข ์ง€์›๋˜๋Š” ๋ ˆ์ด์–ด ํ™•์ธ: Core ML์ด ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ ˆ์ด์–ด๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์–ด
โ€ข ์ž…๋ ฅ ํ˜•์‹ ๋งž์ถ”๊ธฐ: ์ด๋ฏธ์ง€ ํฌ๊ธฐ์™€ ์ •๊ทœํ™” ๋ฐฉ์‹์„ ์ •ํ™•ํžˆ ์ง€์ •ํ•ด์•ผ ํ•ด
โ€ข ์–‘์žํ™” ๊ณ ๋ ค: ๋ชจ๋ธ ํฌ๊ธฐ๋ฅผ ์ค„์ด๋ ค๋ฉด ์–‘์žํ™”(quantization)๋ฅผ ์ ์šฉํ•ด๋ด
โ€ข ํ…Œ์ŠคํŠธ ํ•„์ˆ˜: ๋ณ€ํ™˜ ํ›„ ๋ฐ˜๋“œ์‹œ ์ •ํ™•๋„๋ฅผ ๋‹ค์‹œ ํ™•์ธํ•ด์•ผ ํ•ด!
โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต

๋ชจ๋ธ์ด ์ž˜ ์ž‘๋™ํ•˜๋Š” ๊ฑด ์ข‹์€๋ฐ, ์•ฑ์ด ๋А๋ฆฌ๊ฑฐ๋‚˜ ๋ฐฐํ„ฐ๋ฆฌ๋ฅผ ๋งŽ์ด ๋จน์œผ๋ฉด ๋ฌธ์ œ์ง€? ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ์ •๋ง ์ค‘์š”ํ•ด! ๐Ÿš€

์ตœ์ ํ™” ๊ธฐ๋ฒ• ํšจ๊ณผ ๋‚œ์ด๋„
๋ชจ๋ธ ์–‘์žํ™” ๋ชจ๋ธ ํฌ๊ธฐ 75% ๊ฐ์†Œ, ์†๋„ 2~4๋ฐฐ ํ–ฅ์ƒ ์‰ฌ์›€
์ž…๋ ฅ ํ•ด์ƒ๋„ ์กฐ์ ˆ ์ฒ˜๋ฆฌ ์†๋„ ๋Œ€ํญ ํ–ฅ์ƒ, ์ •ํ™•๋„ ์•ฝ๊ฐ„ ๊ฐ์†Œ ์‰ฌ์›€
๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€ ๋™์‹œ ์ฒ˜๋ฆฌ๋กœ ํšจ์œจ ์ฆ๊ฐ€ ๋ณดํ†ต
Neural Engine ํ™œ์šฉ ์ „๋ ฅ ์†Œ๋น„ ์ตœ์†Œํ™”, ์†๋„ ํ–ฅ์ƒ ์ž๋™
๋ชจ๋ธ ํ”„๋ฃจ๋‹ ๋ถˆํ•„์š”ํ•œ ๊ฐ€์ค‘์น˜ ์ œ๊ฑฐ, ํฌ๊ธฐ ๊ฐ์†Œ ์–ด๋ ค์›€
๋ชจ๋ธ ์–‘์žํ™” ์ ์šฉํ•˜๊ธฐ
# Python ์ฝ”๋“œ - coremltools ์‚ฌ์šฉ
import coremltools as ct

# ๊ธฐ์กด ๋ชจ๋ธ ๋กœ๋“œ
model = ct.models.MLModel('MyModel.mlmodel')

# ์–‘์žํ™” ์ ์šฉ (16๋น„ํŠธ)
quantized_model = ct.models.neural_network.quantization_utils.quantize_weights(
    model, 
    nbits=16
)

# 8๋น„ํŠธ ์–‘์žํ™” (๋” ์ž‘์ง€๋งŒ ์ •ํ™•๋„ ๊ฐ์†Œ ๊ฐ€๋Šฅ)
# quantized_model = ct.models.neural_network.quantization_utils.quantize_weights(
#     model, 
#     nbits=8
# )

quantized_model.save('MyModel_Quantized.mlmodel')
Swift์—์„œ ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง
import CoreML

class PerformanceMonitor {
    
    func measureInferenceTime(model: MLModel, input: MLFeatureProvider) {
        let startTime = CFAbsoluteTimeGetCurrent()
        
        do {
            let prediction = try model.prediction(from: input)
            
            let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
            print("์ถ”๋ก  ์‹œ๊ฐ„: \(timeElapsed * 1000)ms")
            
            // ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ
            let memoryUsage = reportMemory()
            print("๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ: \(memoryUsage)MB")
            
        } catch {
            print("์˜ˆ์ธก ์‹คํŒจ: \(error)")
        }
    }
    
    func reportMemory() -> Double {
        var info = mach_task_basic_info()
        var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4
        
        let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {
            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {
                task_info(mach_task_self_,
                         task_flavor_t(MACH_TASK_BASIC_INFO),
                         $0,
                         &count)
            }
        }
        
        if kerr == KERN_SUCCESS {
            return Double(info.resident_size) / 1024.0 / 1024.0
        }
        return 0
    }
}

๐ŸŽฏ ์‹ค์ „ ์ตœ์ ํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ๋ชจ๋ธ ์„ ํƒ
์ž‘์—…์— ๋งž๋Š” ๊ฐ€์žฅ ์ž‘์€ ๋ชจ๋ธ ์„ ํƒ (MobileNet > ResNet > VGG)


โœ… ์ž…๋ ฅ ํฌ๊ธฐ
ํ•„์š” ์ด์ƒ์œผ๋กœ ํฐ ์ด๋ฏธ์ง€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ (224x224๋ฉด ์ถฉ๋ถ„ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„)


โœ… ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ
์‹ค์‹œ๊ฐ„ ์ฒ˜๋ฆฌ ์‹œ ๋ชจ๋“  ํ”„๋ ˆ์ž„ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ธฐ (30fps โ†’ 10fps๋กœ ์ถฉ๋ถ„)


โœ… ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ฒ˜๋ฆฌ
๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋ก ๋ฐฉ์ง€ (DispatchQueue ํ™œ์šฉ)


โœ… ์บ์‹ฑ
๋ฐ˜๋ณต์ ์ธ ์˜ˆ์ธก ๊ฒฐ๊ณผ๋Š” ์บ์‹ฑํ•ด์„œ ์žฌ์‚ฌ์šฉ


โœ… ๋ฐฐํ„ฐ๋ฆฌ ๋ชจ๋‹ˆํ„ฐ๋ง
์ €์ „๋ ฅ ๋ชจ๋“œ์—์„œ๋Š” ์ฒ˜๋ฆฌ ๋นˆ๋„ ์ค„์ด๊ธฐ

์„ฑ๋Šฅ ์ตœ์ ํ™” ํšจ๊ณผ ๋น„๊ต ์ตœ์ ํ™” ์ „ ๋ชจ๋ธ ํฌ๊ธฐ: 50MB ์ถ”๋ก  ์‹œ๊ฐ„: 200ms ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ: ๋†’์Œ ์ตœ์ ํ™” ์ตœ์ ํ™” ํ›„ ๋ชจ๋ธ ํฌ๊ธฐ: 12MB โ†“ 76% ๊ฐ์†Œ ์ถ”๋ก  ์‹œ๊ฐ„: 50ms โ†“ 75% ๊ฐ์†Œ ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ: ๋‚ฎ์Œ โ†“ 60% ๊ฐ์†Œ ์ตœ์ ํ™” ๊ธฐ๋ฒ• ๋ชจ๋ธ ์–‘์žํ™” (16bit) ์ž…๋ ฅ ํ•ด์ƒ๋„ ์กฐ์ ˆ Neural Engine ํ™œ์šฉ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ํ”„๋ ˆ์ž„ ์Šคํ‚ต
๐ŸŽจ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์‹๋ฌผ ์ธ์‹ ์•ฑ ๋งŒ๋“ค๊ธฐ

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ๋กœ ์•ฑ์„ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด๋ณด์ž! ์นด๋ฉ”๋ผ๋กœ ์‹๋ฌผ์„ ๋น„์ถ”๋ฉด ์ข…๋ฅ˜๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ์•ฑ์ด์•ผ. ๐ŸŒฟ ์žฌ๋Šฅ๋„ท์—์„œ ์›์˜ˆ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๋Š” ๋ถ„๋“ค์—๊ฒŒ ์œ ์šฉํ•  ๊ฑฐ์•ผ!

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

๐Ÿ“ ํŒŒ์ผ ๊ตฌ์„ฑ

PlantIdentifier/
โ”œโ”€โ”€ Models/
โ”‚   โ””โ”€โ”€ PlantClassifier.mlmodel
โ”œโ”€โ”€ ViewControllers/
โ”‚   โ”œโ”€โ”€ CameraViewController.swift
โ”‚   โ””โ”€โ”€ ResultViewController.swift
โ”œโ”€โ”€ Views/
โ”‚   โ”œโ”€โ”€ CameraPreviewView.swift
โ”‚   โ””โ”€โ”€ ResultView.swift
โ”œโ”€โ”€ Utilities/
โ”‚   โ”œโ”€โ”€ ImageProcessor.swift
โ”‚   โ””โ”€โ”€ PlantDatabase.swift
โ””โ”€โ”€ Resources/
    โ””โ”€โ”€ plant_info.json
๋ฉ”์ธ ์นด๋ฉ”๋ผ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ
import UIKit
import AVFoundation
import Vision
import CoreML

class CameraViewController: UIViewController {
    
    // MARK: - Properties
    private var captureSession: AVCaptureSession!
    private var previewLayer: AVCaptureVideoPreviewLayer!
    private var model: VNCoreMLModel!
    private var lastPredictionTime: Date = Date()
    private let predictionInterval: TimeInterval = 0.5 // 0.5์ดˆ๋งˆ๋‹ค ์˜ˆ์ธก
    
    // UI Elements
    private let resultLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 24, weight: .bold)
        label.textColor = .white
        label.backgroundColor = UIColor.black.withAlphaComponent(0.7)
        label.layer.cornerRadius = 10
        label.clipsToBounds = true
        label.numberOfLines = 0
        return label
    }()
    
    private let confidenceLabel: UILabel = {
        let label = UILabel()
        label.textAlignment = .center
        label.font = .systemFont(ofSize: 16)
        label.textColor = .white
        return label
    }()
    
    private let captureButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("๐Ÿ“ธ ์ดฌ์˜", for: .normal)
        button.titleLabel?.font = .systemFont(ofSize: 20, weight: .bold)
        button.backgroundColor = .systemGreen
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 30
        return button
    }()
    
    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setupModel()
        setupCamera()
        setupUI()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        previewLayer?.frame = view.bounds
    }
    
    // MARK: - Setup
    private func setupModel() {
        do {
            let config = MLModelConfiguration()
            config.computeUnits = .all // CPU, GPU, Neural Engine ๋ชจ๋‘ ์‚ฌ์šฉ
            
            let mlModel = try PlantClassifier(configuration: config).model
            model = try VNCoreMLModel(for: mlModel)
        } catch {
            print("๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ: \(error)")
            showAlert(message: "AI ๋ชจ๋ธ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
        }
    }
    
    private func setupCamera() {
        captureSession = AVCaptureSession()
        captureSession.sessionPreset = .photo
        
        guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, 
                                                    for: .video, 
                                                    position: .back),
              let input = try? AVCaptureDeviceInput(device: camera) else {
            showAlert(message: "์นด๋ฉ”๋ผ์— ์ ‘๊ทผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
            return
        }
        
        captureSession.addInput(input)
        
        let output = AVCaptureVideoDataOutput()
        output.setSampleBufferDelegate(self, queue: DispatchQueue(label: "videoQueue"))
        output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
        captureSession.addOutput(output)
        
        previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        previewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(previewLayer)
        
        DispatchQueue.global(qos: .userInitiated).async { [weak self] in
            self?.captureSession.startRunning()
        }
    }
    
    private func setupUI() {
        view.addSubview(resultLabel)
        view.addSubview(confidenceLabel)
        view.addSubview(captureButton)
        
        resultLabel.translatesAutoresizingMaskIntoConstraints = false
        confidenceLabel.translatesAutoresizingMaskIntoConstraints = false
        captureButton.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            resultLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            resultLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            resultLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            resultLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 60),
            
            confidenceLabel.topAnchor.constraint(equalTo: resultLabel.bottomAnchor, constant: 10),
            confidenceLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            
            captureButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
            captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            captureButton.widthAnchor.constraint(equalToConstant: 120),
            captureButton.heightAnchor.constraint(equalToConstant: 60)
        ])
        
        captureButton.addTarget(self, action: #selector(captureButtonTapped), for: .touchUpInside)
    }
    
    // MARK: - Actions
    @objc private func captureButtonTapped() {
        // ํ˜„์žฌ ํ”„๋ ˆ์ž„ ์บก์ฒ˜ ๋ฐ ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ
        print("์‚ฌ์ง„ ์ดฌ์˜!")
        // ResultViewController๋กœ ์ด๋™ํ•˜๋Š” ๋กœ์ง ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
    }
    
    private func showAlert(message: String) {
        let alert = UIAlertController(title: "์•Œ๋ฆผ", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "ํ™•์ธ", style: .default))
        present(alert, animated: true)
    }
}

// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    
    func captureOutput(_ output: AVCaptureOutput, 
                      didOutput sampleBuffer: CMSampleBuffer, 
                      from connection: AVCaptureConnection) {
        
        // ์˜ˆ์ธก ๊ฐ„๊ฒฉ ์ฒดํฌ (์„ฑ๋Šฅ ์ตœ์ ํ™”)
        let now = Date()
        guard now.timeIntervalSince(lastPredictionTime) >= predictionInterval else {
            return
        }
        lastPredictionTime = now
        
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            return
        }
        
        classifyPlant(pixelBuffer: pixelBuffer)
    }
    
    private func classifyPlant(pixelBuffer: CVPixelBuffer) {
        guard let model = model else { return }
        
        let request = VNCoreMLRequest(model: model) { [weak self] request, error in
            guard let results = request.results as? [VNClassificationObservation],
                  let topResult = results.first else {
                return
            }
            
            // ์‹ ๋ขฐ๋„๊ฐ€ ์ผ์ • ์ˆ˜์ค€ ์ด์ƒ์ผ ๋•Œ๋งŒ ํ‘œ์‹œ
            if topResult.confidence > 0.6 {
                DispatchQueue.main.async {
                    self?.updateResult(plantName: topResult.identifier, 
                                     confidence: topResult.confidence)
                }
            }
        }
        
        request.imageCropAndScaleOption = .centerCrop
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
        try? handler.perform([request])
    }
    
    private func updateResult(plantName: String, confidence: Float) {
        // ์‹๋ฌผ ์ด๋ฆ„์„ ํ•œ๊ธ€๋กœ ๋ณ€ํ™˜ (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ™œ์šฉ)
        let koreanName = PlantDatabase.shared.getKoreanName(for: plantName)
        
        resultLabel.text = "๐ŸŒฟ \(koreanName)"
        confidenceLabel.text = String(format: "์‹ ๋ขฐ๋„: %.1f%%", confidence * 100)
        
        // ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ
        UIView.animate(withDuration: 0.3) {
            self.resultLabel.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)
        } completion: { _ in
            UIView.animate(withDuration: 0.3) {
                self.resultLabel.transform = .identity
            }
        }
    }
}
์‹๋ฌผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์œ ํ‹ธ๋ฆฌํ‹ฐ
import Foundation

class PlantDatabase {
    
    static let shared = PlantDatabase()
    private var plantInfo: [String: PlantInfo] = [:]
    
    struct PlantInfo {
        let koreanName: String
        let scientificName: String
        let description: String
        let careInstructions: String
        let wateringFrequency: String
        let sunlightRequirement: String
    }
    
    private init() {
        loadPlantData()
    }
    
    private func loadPlantData() {
        // JSON ํŒŒ์ผ์—์„œ ์‹๋ฌผ ์ •๋ณด ๋กœ๋“œ
        guard let url = Bundle.main.url(forResource: "plant_info", withExtension: "json"),
              let data = try? Data(contentsOf: url),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: [String: String]] else {
            return
        }
        
        for (key, value) in json {
            plantInfo[key] = PlantInfo(
                koreanName: value["korean_name"] ?? key,
                scientificName: value["scientific_name"] ?? "",
                description: value["description"] ?? "",
                careInstructions: value["care_instructions"] ?? "",
                wateringFrequency: value["watering_frequency"] ?? "",
                sunlightRequirement: value["sunlight_requirement"] ?? ""
            )
        }
    }
    
    func getKoreanName(for englishName: String) -> String {
        return plantInfo[englishName]?.koreanName ?? englishName
    }
    
    func getPlantInfo(for name: String) -> PlantInfo? {
        return plantInfo[name]
    }
    
    func getAllPlants() -> [String] {
        return Array(plantInfo.keys)
    }
}
๐Ÿ’ก ํ”„๋กœ์ ํŠธ ํ™•์žฅ ์•„์ด๋””์–ด โ€ข ์‹๋ฌผ ๊ด€๋ฆฌ ์ผ์ง€: ์ธ์‹ํ•œ ์‹๋ฌผ์˜ ๋ฌผ์ฃผ๊ธฐ, ๋น„๋ฃŒ ์ฃผ๊ธฐ ์•Œ๋ฆผ ๊ธฐ๋Šฅ
โ€ข ์ปค๋ฎค๋‹ˆํ‹ฐ ๊ธฐ๋Šฅ: ์‚ฌ์šฉ์ž๋“ค์ด ์‹๋ฌผ ์‚ฌ์ง„๊ณผ ๊ด€๋ฆฌ ํŒ ๊ณต์œ 
โ€ข AR ๊ธฐ๋Šฅ: ์นด๋ฉ”๋ผ๋กœ ๋น„์ถ˜ ๊ณต๊ฐ„์— ์‹๋ฌผ์„ ๊ฐ€์ƒ์œผ๋กœ ๋ฐฐ์น˜ํ•ด๋ณด๊ธฐ
โ€ข ๋ณ‘ํ•ด์ถฉ ์ง„๋‹จ: ์‹๋ฌผ์˜ ์žŽ ์ƒํƒœ๋กœ ์งˆ๋ณ‘ ์ง„๋‹จ
โ€ข ์„ฑ์žฅ ๊ธฐ๋ก: ์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ์‹๋ฌผ ์„ฑ์žฅ ๊ณผ์ • ํƒ€์ž„๋žฉ์Šค

์žฌ๋Šฅ๋„ท์—์„œ ์›์˜ˆ ์ „๋ฌธ๊ฐ€์™€ ํ˜‘์—…ํ•˜๋ฉด ๋” ์ •ํ™•ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŒฑ
๐Ÿ” ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ์™€ ๋ณด์•ˆ

์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹์˜ ๊ฐ€์žฅ ํฐ ์žฅ์  ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ์•ผ! ํ•˜์ง€๋งŒ ๊ทธ๋ž˜๋„ ์ฃผ์˜ํ•ด์•ผ ํ•  ์ ๋“ค์ด ์žˆ์–ด. ๐Ÿ”’

๐Ÿ›ก๏ธ ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

1. ์นด๋ฉ”๋ผ ๊ถŒํ•œ ๊ด€๋ฆฌ
Info.plist์— ์นด๋ฉ”๋ผ ์‚ฌ์šฉ ์ด์œ ๋ฅผ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•ด์•ผ ํ•ด:

<key>NSCameraUsageDescription</key>
<string>์‹๋ฌผ์„ ์ธ์‹ํ•˜๊ธฐ ์œ„ํ•ด ์นด๋ฉ”๋ผ ์ ‘๊ทผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</string>

2. ๋ฐ์ดํ„ฐ ์ €์žฅ ์ตœ์†Œํ™”
ํ•„์š”ํ•˜์ง€ ์•Š์€ ์ด๋ฏธ์ง€๋Š” ์ €์žฅํ•˜์ง€ ๋ง๊ณ , ์ €์žฅํ•˜๋”๋ผ๋„ ์•”ํ˜ธํ™”ํ•ด์„œ ๋ณด๊ด€ํ•ด!


3. ๋ชจ๋ธ ๋ณด์•ˆ
๋ชจ๋ธ ํŒŒ์ผ ์ž์ฒด๋„ ์ง€์  ์žฌ์‚ฐ์ด๋‹ˆ๊นŒ, ๋‚œ๋…ํ™”๋‚˜ ์•”ํ˜ธํ™”๋ฅผ ๊ณ ๋ คํ•ด๋ด.


4. ๋„คํŠธ์›Œํฌ ํ†ต์‹ 
์„œ๋ฒ„์™€ ํ†ต์‹ ์ด ํ•„์š”ํ•˜๋‹ค๋ฉด HTTPS๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋Š” ์•”ํ˜ธํ™”ํ•ด!


5. ์‚ฌ์šฉ์ž ๋™์˜
๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์ด๋‚˜ ๋ถ„์„์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ช…ํ™•ํžˆ ์•Œ๋ฆฌ๊ณ  ๋™์˜๋ฅผ ๋ฐ›์•„์•ผ ํ•ด.

โš ๏ธ GDPR ๋ฐ ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์ค€์ˆ˜ ์œ ๋Ÿฝ์ด๋‚˜ ํ•œ๊ตญ์—์„œ ์„œ๋น„์Šคํ•œ๋‹ค๋ฉด ๊ด€๋ จ ๋ฒ•๊ทœ๋ฅผ ๊ผญ ํ™•์ธํ•ด์•ผ ํ•ด:

โ€ข ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ชฉ์  ๋ช…์‹œ
โ€ข ์‚ฌ์šฉ์ž ๋™์˜ ํš๋“
โ€ข ๋ฐ์ดํ„ฐ ์‚ญ์ œ ๊ถŒ๋ฆฌ ๋ณด์žฅ
โ€ข ๋ฐ์ดํ„ฐ ์ด๋™๊ถŒ ์ œ๊ณต
โ€ข ๋ณด์•ˆ ์‚ฌ๊ณ  ์‹œ ์‹ ๊ณ  ์˜๋ฌด
๐Ÿ› ๋””๋ฒ„๊น…๊ณผ ๋ฌธ์ œ ํ•ด๊ฒฐ

๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๊ธฐ ๋งˆ๋ จ์ด์ง€! ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž. ๐Ÿ”ง

๐Ÿ” ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค

๋ฌธ์ œ 1: ๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ โŒ
์ฆ์ƒ: "Failed to load ML model" ์—๋Ÿฌ
์›์ธ: ๋ชจ๋ธ ํŒŒ์ผ์ด ํ”„๋กœ์ ํŠธ์— ์ œ๋Œ€๋กœ ์ถ”๊ฐ€๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ํƒ€๊ฒŸ ๋ฉค๋ฒ„์‹ญ ์„ค์ •์ด ์•ˆ ๋จ
ํ•ด๊ฒฐ: Xcode์—์„œ ๋ชจ๋ธ ํŒŒ์ผ ์„ ํƒ โ†’ Target Membership ์ฒดํฌ


๋ฌธ์ œ 2: ์˜ˆ์ธก ๊ฒฐ๊ณผ๊ฐ€ ์ด์ƒํ•จ ๐Ÿค”
์ฆ์ƒ: ๋ถ„๋ช… ๊ฐ•์•„์ง€์ธ๋ฐ ๊ณ ์–‘์ด๋กœ ์ธ์‹
์›์ธ: ์ž…๋ ฅ ์ด๋ฏธ์ง€ ์ „์ฒ˜๋ฆฌ๊ฐ€ ์ž˜๋ชป๋จ (์ •๊ทœํ™”, ํฌ๊ธฐ ์กฐ์ ˆ ๋“ฑ)
ํ•ด๊ฒฐ: ๋ชจ๋ธ ํ•™์Šต ์‹œ ์‚ฌ์šฉํ•œ ์ „์ฒ˜๋ฆฌ ๋ฐฉ์‹๊ณผ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ


๋ฌธ์ œ 3: ์•ฑ์ด ๋А๋ ค์ง€๊ฑฐ๋‚˜ ๋ฉˆ์ถค ๐ŸŒ
์ฆ์ƒ: UI๊ฐ€ ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๊ฑฐ๋‚˜ ์‘๋‹ต ์—†์Œ
์›์ธ: ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๋ฌด๊ฑฐ์šด ์ž‘์—… ์ˆ˜ํ–‰
ํ•ด๊ฒฐ: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ์—์„œ ์˜ˆ์ธก ์ˆ˜ํ–‰, ๊ฒฐ๊ณผ๋งŒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ


๋ฌธ์ œ 4: ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ๊ฐ€ ์‹ฌํ•จ ๐Ÿ”‹
์ฆ์ƒ: ์•ฑ ์‚ฌ์šฉ ์‹œ ๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๊ธ‰๊ฒฉํžˆ ์ค„์–ด๋“ฆ
์›์ธ: ๋„ˆ๋ฌด ๋†’์€ ํ”„๋ ˆ์ž„ ๋ ˆ์ดํŠธ๋กœ ์ฒ˜๋ฆฌ
ํ•ด๊ฒฐ: ์˜ˆ์ธก ๊ฐ„๊ฒฉ ๋Š˜๋ฆฌ๊ธฐ, ํ•ด์ƒ๋„ ๋‚ฎ์ถ”๊ธฐ, Neural Engine ํ™œ์šฉ


๋ฌธ์ œ 5: ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ๐Ÿ’พ
์ฆ์ƒ: ์•ฑ์ด ๊ฐ‘์ž๊ธฐ ์ข…๋ฃŒ๋จ
์›์ธ: ํฐ ๋ชจ๋ธ์ด๋‚˜ ์ด๋ฏธ์ง€๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๊ณ„์† ์œ ์ง€
ํ•ด๊ฒฐ: ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฐ์ฒด ํ•ด์ œ, ์ด๋ฏธ์ง€ ์••์ถ•, ๋ชจ๋ธ ์–‘์žํ™”

๋””๋ฒ„๊น… ํ—ฌํผ ํด๋ž˜์Šค

import Foundation
import CoreML

class MLDebugger {
    
    static func printModelInfo(_ model: MLModel) {
        print("=== ๋ชจ๋ธ ์ •๋ณด ===")
        print("๋ชจ๋ธ ์„ค๋ช…: \(model.modelDescription)")
        
        // ์ž…๋ ฅ ์ •๋ณด
        print("\n์ž…๋ ฅ:")
        for input in model.modelDescription.inputDescriptionsByName {
            print("- \(input.key): \(input.value)")
        }
        
        // ์ถœ๋ ฅ ์ •๋ณด
        print("\n์ถœ๋ ฅ:")
        for output in model.modelDescription.outputDescriptionsByName {
            print("- \(output.key): \(output.value)")
        }
        
        // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
        if let metadata = model.modelDescription.metadata {
            print("\n๋ฉ”ํƒ€๋ฐ์ดํ„ฐ:")
            print("์ž‘์„ฑ์ž: \(metadata[.author] ?? "Unknown")")
            print("์„ค๋ช…: \(metadata[.description] ?? "No description")")
            print("๋ผ์ด์„ ์Šค: \(metadata[.license] ?? "No license")")
        }
    }
    
    static func validateInput(_ input: MLFeatureProvider, for model: MLModel) -> Bool {
        let inputDescription = model.modelDescription.inputDescriptionsByName
        
        for (name, description) in inputDescription {
            guard let feature = input.featureValue(for: name) else {
                print("โŒ ์ž…๋ ฅ '\(name)'์ด ์—†์Šต๋‹ˆ๋‹ค.")
                return false
            }
            
            // ํƒ€์ž… ์ฒดํฌ
            if feature.type != description.type {
                print("โŒ ์ž…๋ ฅ '\(name)'์˜ ํƒ€์ž…์ด ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
                print("   ์˜ˆ์ƒ: \(description.type), ์‹ค์ œ: \(feature.type)")
                return false
            }
            
            print("โœ… ์ž…๋ ฅ '\(name)' ๊ฒ€์ฆ ํ†ต๊ณผ")
        }
        
        return true
    }
    
    static func measurePerformance(_ block: () -> Void) {
        let start = CFAbsoluteTimeGetCurrent()
        block()
        let elapsed = CFAbsoluteTimeGetCurrent() - start
        print("โฑ๏ธ ์‹คํ–‰ ์‹œ๊ฐ„: \(elapsed * 1000)ms")
    }
}
๐Ÿ“š ํ•™์Šต ๋ฆฌ์†Œ์Šค์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ

Core ML๊ณผ Vision Framework๋ฅผ ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? ์œ ์šฉํ•œ ๋ฆฌ์†Œ์Šค๋“ค์„ ์†Œ๊ฐœํ• ๊ฒŒ! ๐Ÿ“–

๋ฆฌ์†Œ์Šค ์„ค๋ช… ๋งํฌ
Apple Developer ๊ณต์‹ ๋ฌธ์„œ์™€ ์ƒ˜ํ”Œ ์ฝ”๋“œ developer.apple.com
WWDC ์„ธ์…˜ ์• ํ”Œ ๊ฐœ๋ฐœ์ž ์ปจํผ๋Ÿฐ์Šค ์˜์ƒ developer.apple.com/videos
Core ML Community ๊ฐœ๋ฐœ์ž ํฌ๋Ÿผ๊ณผ Q&A forums.developer.apple.com
GitHub ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์™€ ์˜ˆ์ œ github.com/topics/coreml
Medium ๊ฐœ๋ฐœ์ž ๋ธ”๋กœ๊ทธ์™€ ํŠœํ† ๋ฆฌ์–ผ medium.com/tag/core-ml

๐ŸŽ“ ์ถ”์ฒœ ํ•™์Šต ๊ฒฝ๋กœ

์ดˆ๊ธ‰ (1-2์ฃผ)
โ€ข Apple ๊ณต์‹ ํŠœํ† ๋ฆฌ์–ผ ๋”ฐ๋ผํ•˜๊ธฐ
โ€ข ๊ธฐ๋ณธ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜ ์•ฑ ๋งŒ๋“ค๊ธฐ
โ€ข Vision Framework ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ์‹ค์Šต


์ค‘๊ธ‰ (1-2๊ฐœ์›”)
โ€ข Create ML๋กœ ์ปค์Šคํ…€ ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ
โ€ข ์‹ค์‹œ๊ฐ„ ๊ฐ์ฒด ํƒ์ง€ ๊ตฌํ˜„
โ€ข ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ธฐ๋ฒ• ์ ์šฉ


๊ณ ๊ธ‰ (3๊ฐœ์›” ์ด์ƒ)
โ€ข TensorFlow/PyTorch ๋ชจ๋ธ ๋ณ€ํ™˜
โ€ข ๋ณต์žกํ•œ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•
โ€ข ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ ์•ฑ ๊ฐœ๋ฐœ

๐ŸŽ‰ ๐Ÿš€ ๐Ÿ’ป ๐Ÿค– ๐Ÿ“ฑ
๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์—ˆ๋‹ค๋ฉด ์ •๋ง ๋Œ€๋‹จํ•ด! ๐Ÿ‘ Core ML๊ณผ Vision Framework์— ๋Œ€ํ•ด ๊ฝค ๋งŽ์€ ๊ฑธ ๋ฐฐ์› ์ง€?



์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹์€ ์ •๋ง ๊ฐ•๋ ฅํ•œ ๊ธฐ์ˆ ์ด์•ผ. ์‚ฌ์šฉ์ž์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๋ณดํ˜ธํ•˜๋ฉด์„œ๋„ ๋น ๋ฅด๊ณ  ํšจ์œจ์ ์ธ AI ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์• ํ”Œ์ด ์ด ๋ถ„์•ผ์— ์—„์ฒญ ํˆฌ์žํ•˜๊ณ  ์žˆ์–ด์„œ, ์•ž์œผ๋กœ ๋” ๋งŽ์€ ๊ฐ€๋Šฅ์„ฑ์ด ์—ด๋ฆด ๊ฑฐ์•ผ! ๐ŸŒŸ



์ฒ˜์Œ์—๋Š” ์–ด๋ ค์›Œ ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผํ•˜๋‹ค ๋ณด๋ฉด ๊ธˆ๋ฐฉ ์ต์ˆ™ํ•ด์งˆ ๊ฑฐ์•ผ. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž! ๐Ÿ’ช



์žฌ๋Šฅ๋„ท์—์„œ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ์ง€์‹์„ ๊ณต์œ ํ•˜๊ณ , ๋ฉ˜ํ† ๋ง์„ ๋ฐ›๊ฑฐ๋‚˜ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฒŒ ์ œ์ผ ๋น ๋ฅธ ๊ธธ์ด๊ฑฐ๋“ ! ๐Ÿค

โœจ ํ•ต์‹ฌ ์š”์•ฝ

โ€ข Core ML: iOS์—์„œ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ํ”„๋ ˆ์ž„์›Œํฌ


โ€ข Vision Framework: ์ด๋ฏธ์ง€/๋น„๋””์˜ค ์ฒ˜๋ฆฌ์— ํŠนํ™”๋œ ์ปดํ“จํ„ฐ ๋น„์ „ ๋„๊ตฌ


โ€ข ์˜จ๋””๋ฐ”์ด์Šค ์ฒ˜๋ฆฌ: ์„œ๋ฒ„ ์—†์ด ๊ธฐ๊ธฐ์—์„œ ์ง์ ‘ AI ์‹คํ–‰ โ†’ ๋น ๋ฅด๊ณ  ์•ˆ์ „


โ€ข Create ML: ์ฝ”๋”ฉ ์—†์ด ์ปค์Šคํ…€ ๋ชจ๋ธ ๋งŒ๋“ค๊ธฐ


โ€ข ์„ฑ๋Šฅ ์ตœ์ ํ™”: ์–‘์žํ™”, ํ•ด์ƒ๋„ ์กฐ์ ˆ, Neural Engine ํ™œ์šฉ


โ€ข ์‹ค์ „ ํ™œ์šฉ: ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜, ๊ฐ์ฒด ํƒ์ง€, ์–ผ๊ตด ์ธ์‹, OCR ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ

์ด์ œ ๋‹น์‹ ๋„ AI ์•ฑ ๊ฐœ๋ฐœ์ž! ๋ฉ‹์ง„ ์•ฑ์„ ๋งŒ๋“ค์–ด์„œ ์„ธ์ƒ์„ ๋†€๋ผ๊ฒŒ ํ•ด๋ด! ๐Ÿš€โœจ



๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ์งˆ๋ฌธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค์˜ ๊ฒฝํ—˜์„ ์ฐธ๊ณ ํ•ด๋ด. ํ•จ๊ป˜ ๋ฐฐ์šฐ๊ณ  ์„ฑ์žฅํ•˜๋Š” ๊ฒŒ ๊ฐœ๋ฐœ์˜ ์ฆ๊ฑฐ์›€์ด๋‹ˆ๊นŒ! ๐Ÿ˜Š



ํ–‰์šด์„ ๋นŒ์–ด! Happy Coding! ๐Ÿ’ป๐ŸŽ‰

๐Ÿ“ฑ Core ML ๐Ÿ‘๏ธ Vision ๐Ÿš€ Your App ์˜จ๋””๋ฐ”์ด์Šค ๋จธ์‹ ๋Ÿฌ๋‹์˜ ๋ฏธ๋ž˜๋ฅผ ๋งŒ๋“ค์–ด๊ฐ€์„ธ์š”!
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.