Ver3.0 ๐ฑ Core ML๊ณผ Vision Framework๋ก ์์ํ๋ ์จ๋๋ฐ์ด์ค ๋จธ์ ๋ฌ๋ ์ฌํ

๐ฑ Core ML๊ณผ Vision Framework๋ก ์์ํ๋ ์จ๋๋ฐ์ด์ค ๋จธ์ ๋ฌ๋ ์ฌํ
๋ด ์์ดํฐ์ด ๋๋ํด์ง๋ ๋ง๋ฒ ๊ฐ์ ์ด์ผ๊ธฐ โจ
์๋ ! ํน์ ์์ดํฐ์ผ๋ก ์ฌ์ง ์ฐ์ ๋ ์๋์ผ๋ก ์ผ๊ตด์ ์ธ์ํ๊ฑฐ๋, ์ฌ์ง ์ฑ์์ "๊ณ ์์ด" ๊ฒ์ํ๋ฉด ๊ณ ์์ด ์ฌ์ง๋ง ์์ ๋์ค๋ ๊ฑฐ ์ ๊ธฐํ์ง ์์์ด? ๐ฑ ์ด๊ฒ ๋ฐ๋ก ์ค๋ ์ฐ๋ฆฌ๊ฐ ์ด์ผ๊ธฐํ ์จ๋๋ฐ์ด์ค ๋จธ์ ๋ฌ๋์ ๋ง๋ฒ์ด์ผ!
์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด์ง ์๊ณ ๋ ๋ด ๊ธฐ๊ธฐ์์ ์ง์ AI๊ฐ ๋์๊ฐ๋ ๊ฑฐ์ง. ๊ฐ์ธ์ ๋ณด๋ ์์ ํ๊ณ , ์ธํฐ๋ท ์์ด๋ ์๋ํ๊ณ , ์๋๋ ์์ฒญ ๋น ๋ฅด๋ค๋ ์ฅ์ ์ด ์์ด. ์ ํ์ด ์ ๊ณตํ๋ Core ML๊ณผ Vision Framework๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ฐ ๋ฉ์ง ๊ธฐ๋ฅ๋ค์ ์ฐ๋ฆฌ๋ ๋ง๋ค ์ ์์ด! ๐
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๋ Core ML ์์์ ๋์ํ๋ ์ปดํจํฐ ๋น์ ์ ์ฉ ํ๋ ์์ํฌ์ผ. ์ด๋ฏธ์ง๋ ๋น๋์ค์์ ๋ญ๊ฐ๋ฅผ ์ธ์ํ๊ณ ๋ถ์ํ๋ ์์ ์ ํนํ๋์ด ์์ง! ๐ธ
Core ML์ด ๋ฒ์ฉ ๋จธ์ ๋ฌ๋ ํ๋ ์์ํฌ๋ผ๋ฉด, Vision์ "์ด๋ฏธ์ง ์ฒ๋ฆฌ ์ ๋ฌธ๊ฐ"๋ผ๊ณ ๋ณด๋ฉด ๋ผ. ์ผ๊ตด ์ธ์, ๋ฐ์ฝ๋ ์ค์บ, ํ ์คํธ ์ธ์, ๊ฐ์ฒด ์ถ์ ๊ฐ์ ๊ธฐ๋ฅ๋ค์ด ์ด๋ฏธ ๋ด์ฅ๋์ด ์์ด์ ๋ฐ๋ก๋ฐ๋ก ์ธ ์ ์์ด! ๐ฏ
๐ฅ Vision Framework๊ฐ ์ ๊ณตํ๋ ์ฃผ์ ๊ธฐ๋ฅ๋ค
โข ์ผ๊ตด ๊ฐ์ง ๋ฐ ๋๋๋งํฌ ์ธ์
์ฌ์ง ์ ์ผ๊ตด์ ์ฐพ๊ณ , ๋ยท์ฝยท์
์์น๊น์ง ์ ํํ๊ฒ ํ์
ํด!
โข ๋ฐ์ฝ๋ ๋ฐ QR์ฝ๋ ์ธ์
์นด๋ฉ๋ผ๋ก ๋น์ถ๊ธฐ๋ง ํ๋ฉด ์๋์ผ๋ก ์ธ์. ๊ฒฐ์ ์ฑ์ด๋ ์ฌ๊ณ ๊ด๋ฆฌ ์ฑ์ ๋ฑ์ด์ง!
โข ํ
์คํธ ์ธ์ (OCR)
์ด๋ฏธ์ง ์ ํ
์คํธ๋ฅผ ์ถ์ถํด์ ํธ์ง ๊ฐ๋ฅํ ๋ฌธ์๋ก ๋ณํํด์ค. ๋ช
ํจ ์ค์บ, ๋ฌธ์ ๋์งํธํ์ ์ ์ฉํด!
โข ๊ฐ์ฒด ์ถ์
๋น๋์ค์์ ํน์ ๊ฐ์ฒด๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ฐ๋ผ๊ฐ๋ฉฐ ์ถ์ ํ ์ ์์ด. AR ์ฑ์ด๋ ์คํฌ์ธ ๋ถ์ ์ฑ์ ํ์ฉ ๊ฐ๋ฅ!
โข ์ด๋ฏธ์ง ๋ถ๋ฅ ๋ฐ ๊ฐ์ฒด ํ์ง
Core ML ๋ชจ๋ธ๊ณผ ๊ฒฐํฉํด์ ์ฌ์ง ์ ์ฌ๋ฌผ์ด ๋ญ์ง ์์๋ด๊ฑฐ๋, ์ฌ๋ฌ ๊ฐ์ฒด์ ์์น๋ฅผ ๋์์ ์ฐพ์๋ผ ์ ์์ด!
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํด๋ณผ๊น? ๊ฑฑ์ ๋ง! ์๊ฐ๋ณด๋ค ํจ์ฌ ๊ฐ๋จํด. ๋ง์น ๋ ๊ณ ๋ธ๋ก ์กฐ๋ฆฝํ๋ฏ์ด ๋จ๊ณ๋ณ๋ก ๋ฐ๋ผ์ค๋ฉด ๋ผ! ๐งฑ
๋ชจ๋ธ ์ค๋นํ๊ธฐ
๋จผ์ Core ML ๋ชจ๋ธ์ด ํ์ํด. ์ ํ์ด ์ ๊ณตํ๋ ์ํ ๋ชจ๋ธ์ ์ฌ์ฉํ๊ฑฐ๋, ์ง์ ๋ง๋ ๋ชจ๋ธ์ ๋ณํํด์ ์ธ ์ ์์ด. ์ ํ ๊ฐ๋ฐ์ ์ฌ์ดํธ์์ MobileNet, ResNet, SqueezeNet ๊ฐ์ ์ ๋ช ํ ๋ชจ๋ธ๋ค์ ๋ฌด๋ฃ๋ก ๋ค์ด๋ฐ์ ์ ์์ด! ๐ฆ
๋ชจ๋ธ ํ์ผ ํ์ฅ์๋ .mlmodel์ด์ผ. ์ด ํ์ผ์ Xcode ํ๋ก์ ํธ์ ๋๋๊ทธ ์ค ๋๋กญํ๋ฉด ์๋์ผ๋ก Swift ํด๋์ค๊ฐ ์์ฑ๋ผ. ์ ๋ง ํธ๋ฆฌํ์ง? ๐
๊ธฐ๋ณธ ์ด๋ฏธ์ง ๋ถ๋ฅ ์ฝ๋
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 ๋ฐฐ์ด๋ก ๋ฐํ๋๋๋ฐ, ์ ๋ขฐ๋ ์์ผ๋ก ์ ๋ ฌ๋์ด ์์ด!
์ค์๊ฐ ์นด๋ฉ๋ผ ๋ถ๋ฅ
์ ์ง ์ด๋ฏธ์ง ๋ถ๋ฅ๋ ์ด์ ํ์ผ๋, ์ค์๊ฐ ์นด๋ฉ๋ผ ํผ๋์์ ๋ถ๋ฅํ๋ ๋ฐฉ๋ฒ๋ ์์๋ณด์! ๐น 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๋ก ๋ฎ์ถฐ๋ด
โข ๋น๋๊ธฐ ์ฒ๋ฆฌ: ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๋ธ๋กํ์ง ์๋๋ก ๋ฐฑ๊ทธ๋ผ์ด๋ ํ ์ฌ์ฉ
โข ๋ฐฐ์น ์ฒ๋ฆฌ: ์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ฉด ๋ ํจ์จ์ ์ด์ผ
์ด์ 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])
}
โข ๊ฐ์ ๋ฉ์ดํฌ์ ์ฑ: ์ ์ ์์น์ ๋ฆฝ์คํฑ ์์ ์ ํ๊ธฐ
โข ์๊ฒฝ ๊ฐ์ ์ฐฉ์ฉ: ๋ ์์น์ ๋ง์ถฐ ์๊ฒฝ ๋ ๋๋ง
โข ํ์ ์ธ์: ์ ๋ชจ์์ผ๋ก ์๋์ง ์ฐก๊ทธ๋ฆฌ๋์ง ํ๋จ
โข ์ผ๊ตด ํํฐ: ์ค๋ ์ฑ ๊ฐ์ ์ฌ๋ฏธ์๋ ํํฐ ํจ๊ณผ
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๋ก ๋ณํ. ํ์๋ค์ด ๊ต๊ณผ์๋ ๋
ธํธ ์ ๋ฆฌํ ๋ ์ ์ฉํด! ๐
๋ฒ์ญ ์ฑ
์ธ๊ตญ์ด ๊ฐํ์ด๋ ๋ฉ๋ดํ์ ์นด๋ฉ๋ผ๋ก ๋น์ถ๋ฉด ์ค์๊ฐ ๋ฒ์ญ. ์ฌํ ๊ฐ ๋ ์ง์ง ํธํด! โ๏ธ
์์์ฆ ๊ด๋ฆฌ
์์์ฆ ์ฌ์ง์์ ๊ธ์ก, ๋ ์ง, ์์ ๋ช
์ ์๋ ์ถ์ถํด์ ๊ฐ๊ณ๋ถ ์์ฑ. ์ฌ๋ฌด ๊ด๋ฆฌ๊ฐ ์ฌ์์ ธ! ๐ฐ
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์ด๋ผ๋ ์ ํ์ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๋ฉ ์์ด๋ ๋ชจ๋ธ์ ํ๋ จ์ํฌ ์ ์์ด. ์ ๋ง ์ฌ์! ๐
๐ ์ค๋น๋ฌผ
1. ํ์ต ๋ฐ์ดํฐ
๊ฐ ์นดํ
๊ณ ๋ฆฌ๋ณ๋ก ์ต์ 10์ฅ ์ด์์ ์ด๋ฏธ์ง๊ฐ ํ์ํด. ๋ง์์๋ก ์ข์ง๋ง, 100~200์ฅ ์ ๋๋ฉด ์ถฉ๋ถํด!
2. ํด๋ ๊ตฌ์กฐ
์ด๋ฏธ์ง๋ฅผ ์นดํ
๊ณ ๋ฆฌ๋ณ๋ก ํด๋์ ์ ๋ฆฌํด์ผ ํด:
Training Data/
โโโ ๊ฐ์์ง/
โ โโโ dog1.jpg
โ โโโ dog2.jpg
โ โโโ ...
โโโ ๊ณ ์์ด/
โ โโโ cat1.jpg
โ โโโ cat2.jpg
โ โโโ ...
โโโ ํ ๋ผ/
โโโ rabbit1.jpg
โโโ rabbit2.jpg
โโโ ...3. ๊ฒ์ฆ ๋ฐ์ดํฐ (์ ํ์ฌํญ)
ํ์ต์ ์ฌ์ฉํ์ง ์์ ๋ณ๋์ ์ด๋ฏธ์ง๋ก ๋ชจ๋ธ ์ฑ๋ฅ์ ํ
์คํธํด!
๐ 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')
โข ์ ๋ ฅ ํ์ ๋ง์ถ๊ธฐ: ์ด๋ฏธ์ง ํฌ๊ธฐ์ ์ ๊ทํ ๋ฐฉ์์ ์ ํํ ์ง์ ํด์ผ ํด
โข ์์ํ ๊ณ ๋ ค: ๋ชจ๋ธ ํฌ๊ธฐ๋ฅผ ์ค์ด๋ ค๋ฉด ์์ํ(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')
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 ํ์ฉ)
โ
์บ์ฑ
๋ฐ๋ณต์ ์ธ ์์ธก ๊ฒฐ๊ณผ๋ ์บ์ฑํด์ ์ฌ์ฌ์ฉ
โ
๋ฐฐํฐ๋ฆฌ ๋ชจ๋ํฐ๋ง
์ ์ ๋ ฅ ๋ชจ๋์์๋ ์ฒ๋ฆฌ ๋น๋ ์ค์ด๊ธฐ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ์ฑ์ ํ๋ ๋ง๋ค์ด๋ณด์! ์นด๋ฉ๋ผ๋ก ์๋ฌผ์ ๋น์ถ๋ฉด ์ข ๋ฅ๋ฅผ ์๋ ค์ฃผ๋ ์ฑ์ด์ผ. ๐ฟ ์ฌ๋ฅ๋ท์์ ์์ ๊ด๋ จ ์ฌ๋ฅ์ ๊ณต์ ํ๋ ๋ถ๋ค์๊ฒ ์ ์ฉํ ๊ฑฐ์ผ!
๐ ํ์ผ ๊ตฌ์ฑ
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. ์ฌ์ฉ์ ๋์
๋ฐ์ดํฐ ์์ง์ด๋ ๋ถ์์ ๋ํด ์ฌ์ฉ์์๊ฒ ๋ช
ํํ ์๋ฆฌ๊ณ ๋์๋ฅผ ๋ฐ์์ผ ํด.
โข ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ชฉ์ ๋ช ์
โข ์ฌ์ฉ์ ๋์ ํ๋
โข ๋ฐ์ดํฐ ์ญ์ ๊ถ๋ฆฌ ๋ณด์ฅ
โข ๋ฐ์ดํฐ ์ด๋๊ถ ์ ๊ณต
โข ๋ณด์ ์ฌ๊ณ ์ ์ ๊ณ ์๋ฌด
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ ๋ง๋ จ์ด์ง! ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์๋ณด์. ๐ง
๐ ์์ฃผ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค
๋ฌธ์ 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! ๐ป๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.