포스트

SeeFood

CoreML

CleanShot 2024-04-17 at 09 24 32@2x

  • 머신러닝 모델을 iOS 프로젝트에 사용가능하게 해준다.
    • 머신러닝 모델을 로드할 수 있다.
    • 훈련한 모델들이 예측할 수 있도록 만들 수 있다.

애플 개발자 사이트 참고!

우리가 사용할 수 있는 ML모델들이 있다.

아래는 Flow에 대한 간단한 사진 from Udemy.

CleanShot 2024-04-17 at 09 38 13@2x

CleanShot 2024-04-17 at 09 38 30@2x

프로젝트 내부에 ML Model 추가.

개발자사이트에 가보면 Apple이 올려둔 여러 Model들이 존재하는데 다운 받아 사용 하면 된다.

그리고 다운받은 모델을 프로젝트로 드래그 하면 끝.

참고로 Model들은 용량이 꽤 많이 나가는 편이다.

VC 작성

우선 import를 해주는데 CoreML만 하는것이아닌, Vision도 해준다.

Vision의 경우, 이미지를 더 쉽게 처리하고 많은 코드를 작성하지 않아도 CoreML과 작업할 수 있도록 해준다.

1
2
3
import CoreML
import Vision

1. ImagePicker 사용.

1
2
3
4
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    
}

해당 기능을 사용하기위해 extension으로 뺐다.

그리고 다음과 같이 코드를 ViewDidLoad에 적는다.

1
2
3
imagePicker.delegate = self
imagePicker.sourceType = .camera
imagePicker.allowsEditing = false

의미는 picker 프로토콜에 대한 위임자 설정과, picker를 어떤 타입으로 쓸지 (camera, photolibrary, savedPhotosAlbum), 그리고 이미지를 사용 후 수정을 허용할지에 대한 내용이다.

참고사항

imagePicker.sourceType에서 camera를 제외한 나머지 기능 (photolibrary, savedPhotosAlbum)은 Deprecate 예정

해당 기능을 사용하려면 imagepicker 대신 phpicker를 사용하자.

2. 선택한 이미지를 ML Model에 전달.

didFinishPickingMediaWithInfo 메서드를 사용한다.

이미지 선택이 끝나고 수행하기 위한 메서드.

1
2
3
4
5
 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        let userPickedImage = info[UIImagePickerController.InfoKey.originalImage]
        
    }

유져가 선택한 이미지 원본을 가져오기 위해 다음과 같이 적었다.

기존에는 UIImagePickerControllerOriginalImage로 작성을 했으나,

현재는 UIImagePickerController.InfoKey.originalImage로 바뀌었다는 점 참고하도록 하자.

imageView.image = userPickedImage를 사용하여 적용하니 Type에 대한 Error가 발생한다.

1
2
3
if let userPickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            imageView.image = userPickedImage
        }

DownCasting을 해주는데 이때 optional Binding을 하여 nil을 방지해주자.

1
2
3
4
5
6
7
8
9
10
11
12
13
extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
        if let userPickedImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            imageView.image = userPickedImage
        }
        
        imagePicker.dismiss(animated: true)
    }
    
  
}

실행해보자.

카메라를 눌러보니 보이진 않고 다음과 같은 Warning이 발생한다.

CleanShot 2024-04-17 at 11 13 00@2x

2. info.Plist 수정

우리가 카메라를 사용할때 알람이 뜨면서 카메라를 허용하는 그 내용을 만들것이다.

값을 추가해주고.

CleanShot 2024-04-17 at 11 24 43@2x

다음과 같이 적어주었다.

value는 자유롭게!

시뮬레이터에서는 카메라가 없어서 실제 iPhone에 빌드하여 테스트를 진행했다.

IMG_0017

IMG_0019

IMG_0020

오늘의 키보드를 찍은건데. 너무 대충 찍긴 했다.

무튼 작동하면 다음과 같이 뜬다.

현재 시뮬레이터에서 작동을 하지않는건 시뮬레이터에 카메라가 없어서 그런것같다.

찾아보니 카메라 기능은 실제 iphone으로 해야하는것같다.

Model 사용해보기.

1. Image → CIImage로 변환하기

CIImage란?

코어 이미지 필터에 의해 처리되거나 생성되는 이미지의 표현 (from Docs)

이렇게 사전적정의로 하니 뭔가 크게 받아들여지지는 않는다.

특별한 유형의 이미지로 Vision & CoreML Framework를 사용할 수 있도록 한다.

let ciImage = CIImage(image: userPickedImage) 이렇게 ciImage에 대한 Instance는 아주 간단히 설정이 가능.

2. Model을 사용하여 이미지를 감지하는 함수 구현

1
2
3
4
5
6
 func detect(image: CIImage) {
        
        guard let model = try? VNCoreMLModel(for: Inceptionv3().model) else {
            fatalError("Loading CoreML Model Failed.")
        }
    }

우선 다음과 같이 model을 객체화 하였다.

이제는 예외에 대한 부분도 생각해야하기에 if let / guard let을 사용하여 옵셔널 바인딩을 확실하게 해주자.

VNCoreMLModel은 Vision에서 온 메서드이고 이미지 분석 요청을 수행할 수 있게 해준다.

이후 API 사용하듯이 request로 요청하여 작성을 하게 된다. (API에서 task작성하는 느낌과 비슷.)

CleanShot 2024-04-17 at 11 54 02@2x

그리고 해당 요청을 다룰 handler를 만들어 준다.

그리고 실패할수있기에 do~catch문을 사용한다.

perform에 갑자기 배열? 이라고 생각 할 수있는데. perform에 대해 읽어보면 parameter가 저렇다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func detect(image: CIImage) {
        
        guard let model = try? VNCoreMLModel(for: Inceptionv3().model) else {
            fatalError("Loading CoreML Model Failed.")
        }
        
        let request = VNCoreMLRequest(model: model) { (request, error) in
            guard let results = request.results as? [VNClassificationObservation] else {
                fatalError("Model Failed to process Image")
            }
            print(results)
        }
        
        let handler = VNImageRequestHandler(ciImage: image)
        
        do {
            try handler.perform([request])
        } catch {
            print(error)
        }
    }

실행을 해서 사진을 찍어 테스트를 해보자.

사진을 사용하면

1
[<VNClassificationObservation: 0x302271800> 544438A7-B5CC-43EF-A821-AE7A9DFFE458 VNCoreMLRequestRevision1 confidence=0.754823 "computer keyboard, keypad", <VNClassificationObservation: 0x302210980> 47C13DE4-854B-415F-AD3F-C529CA97409A VNCoreMLRequestRevision1 confidence=0.168424 "space bar", <VNClassificationObservation: 0x302274500> 9DB696BD-738D-412B-BDD5-6ED4AFBBB745 VNCoreMLRequestRevision1 confidence=0.045331 "notebook, notebook computer", <VNClassificationObservation: 0x302204200> A538D26B-D16D-4F0C-B947-0FBD36AB1C0F VNCoreMLRequestRevision1 confidence=0.013825 "typewriter keyboard", <VNClassificationObservation: 0x302270a80> 8E6F5208-D937-42CF-9129-1F032EEDFDA4 VNCoreMLRequestRevision1 confidence=0.006530 "laptop, laptop computer",

엄청 많은 내용이 나오는데 신뢰도 순으로 나온다.

confidence=0.754823 "computer keyboard, keypad" 여기를 참고하여 보면.

현재 가장 신뢰도가 높은 값은 computer keyboard, keypad라고 나온다.

신뢰도는 약 75%로 나오고있다.

지금까지의 Sequence 정리

여태까지의 흐름을 한번 정리를 해본다면.

전체 흐름.

  1. 우선 사진을 찍고 사진데이터를 userPickedImage에 저장 (Type: UIImage)
  2. userPickedImage 데이터를 CIimage Type으로 변환 (Type: CIImage)
  3. detect 메서드로 변환된 이미지 데이터를 전달.

Detect 메서드 흐름

  1. VNCoreMLModel 메서드를 사용하여 우리가 사용하려는 Model을 객체화.
    • VNCoreMLModel(for: 사용하고자 하는 모델의 class를 가져와서 그안의 model값을 사용.)
  2. VNCoreMLRequest 메서드를 사용하여 request 호출 내용 구현
    • model / completionHandler 두개의 파라미터가 사용이 되며, completionHandler 부분은 API 사용하듯, Closure형태로 전환하여 구현.
    • completionHandler는 (request, error) 두가지 형태로 나뉜다 (parameter는 본인 마음)
      • 성공: request의 결과를 강의에선 다운 캐스팅을 해서 as? [VNClassificationObservation] 로 특정.
        • 하지만 현재는 requests.results의 타입을 확인하면 [Any]? → [VNObservation]? 로 바뀌었음.
        • 그래도 다운캐스팅을 해줘야하는게, 아래 사진을 보면알겠지만 여기에 identifier가 있다.
        • CleanShot 2024-04-18 at 09 56 42@2x
      • 실패: 지금은 print(error) 로 간단하게 리턴
  3. VNImageRequestHandler 메서드를 사용하여 handler를 구현 (API 관점에서보면 task)
    • 이떄 Parameter로 ciImage를 사용.
  4. do~catch 를 사용하여 handler에게 request를 수행하라고 지시.
    • 이떄 Parameter는 배열이다. ([VNRequest])

Hotdog / Not Hotdog 구현

테스트를 통해 우리가 가져온 모델이 나름 괜찮게 결과값을 보여주는 것을 확인했다.

이젠 result를 콘솔에 출력하는것이 아닌, 핫도그인지 아닌지를 구분하게 만들어보자.

result의 closure 부분으로 돌아가서

1
2
3
4
5
6
7
8
9
10
11
// before
print(results)

// after
if let firstResult = results.first {
                if firstResult.identifier.contains("hotdog") {
                    self.navigationItem.title = "Hotdog!!"
                } else {
                    self.navigationItem.title = "Not Hotdog!!"
                }
            }

결과값의 첫번째, 즉 신뢰도가 가장 높은 값을 가져와서, identifier에 핫도그가 있는지 없는지를 구분하게 하면된다.

그리고 해당 결과를 navigationItem의 Title로 표시할것이다.

그러면 이제 핫도그 이미지를 하나 창에 띄워서 테스트를 해보면 결과를 알 수 있다.

ezgif-1-caef140d0b

이럴줄 알았음 그냥 영상을 올릴걸 그랬다.

IMG_0024

위에 핫도그라고 잘 나온다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.