【Swift/iOS】Core MLの実装方法!機械学習で画像解析
この記事からわかること
- Swift/XcodeでCore MLの使い方
- 機械学習で画像解析する方法
- イラストへの変換やオブジェクトや文字識別
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:15.4
- iOS:17.0
- Swift:5.9
- macOS:Sonoma 14.1
Core MLとは?
「Core ML」はAppleが提供する機械学習を実装できるフレームワークです。Core MLを使用することでさまざまな機械学習モデル(画像分類、テキスト分類、音声認識など)をiOSアプリに統合することができるようになります。
できること
- 画像分類(動物や食べ物の種類に認識など)
- 画像セグメンテーション(背景や物体などの切り分け)
- 画像をイラストへ変換
- 奥行き推定
- 線画分類(テキストなど)
- etc...
機械学習モデル:.mlmodel
実装するには機械学習モデルをXcodeプロジェクトに導入します。プロジェクト内に機械学習モデルを持つのでクラウドを介すことなくローカルで処理を完結できるのでセキュリティ的にも安全になっています。
機械学習モデルはCore MLフォーマット(拡張子:.mlmodel
)として変換されたものをCore MLでは扱うことが可能です。
Appleからいくつか機械学習モデルが提供されています。今回は「線画分類:MNIST」を使用して0〜9までの手書きの数字を識別するモデルを例にCore MLの実装方法を見ていきます。
実装方法
手書き文字を識別できるモデルですが、Core MLの実装にフォーカスを当てたいため、今回は手書き部分の実装などはなく数字の画像で試します。
- .mlmodelファイルの導入
- VNCoreMLRequestの作成と実行
1..mlmodelファイルの導入
まずはApple公式のCore MLモデルから「線画分類:MNIST(MNISTClassifier.mlmodel)」をダウンロードします。ダウンロードできたらXcodeにドラッグ&ドロップで入れ込みます。
入れ込んだ後に「Model Class」の下に「Automatically generated Swift model class」と出ていれば読み込み完了です。別の言葉が出ていれば「⌘ + B」でビルドすれば読み込みが完了すると思います。それでもダメならClean Buildしてみてください。
ちなみに「Preview」タブでは実際に画像の解析結果を試すことができます。今回は線画分類なので以下のように8と書かれた画像を渡すと「8」が「100% confidence」になっていることを確認できます。
2.VNCoreMLRequestの作成と実行
読み込みが完了したらモデルをインスタンス化してVNCoreMLRequest
に渡します。そこまでのコードを1行ずつみていきます。
VNCoreMLModel
の引数に読み込んだモデルクラスを指定します。先ほどの手順で読み込みが完了していれば該当クラスが自動で生成されているはずです。
guard let model = try? VNCoreMLModel(for: MNISTClassifier(configuration: MLModelConfiguration()).model) else { return }
モデルがインスタンス化できたらVNCoreMLRequest
の引数に渡します。completionHandler
から実行した際の結果を参照することができVNClassificationObservation.identifier
に今回で言うと解析結果の文字が格納されています。
// VNCoreMLRequestの作成
let request = VNCoreMLRequest(model: model) { request, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
// 結果を参照
guard let results = request.results as? [VNClassificationObservation],
let firstResult = results.first else { return }
// 解析した結果を取得
self.number = firstResult.identifier
}
最後に作成したリクエストをVNImageRequestHandler
を使用して実行します。引数には解析対象の画像をCGImage
型で渡します。
// VNImageRequestHandlerの作成とリクエストの実行
let handler = VNImageRequestHandler(cgImage: cgImage)
do {
try handler.perform([request])
} catch {
print("Failed to perform request: \(error.localizedDescription)")
}
Swift UIで画像のピッカーと解析機能を実装する全体サンプルコードを貼っておきます。実際には手書きで数字を書き込める機能などを実装した方が良いかもですね!
import SwiftUI
import CoreML
import Vision
import Combine
import PhotosUI
struct ContentView: View {
@State var show = false
@State var number = ""
@State var image: UIImage?
var body: some View {
VStack {
Text("Number:\(number)")
if let image = image {
Image(uiImage: image)
.resizable()
.frame(width: 200, height: 200)
}
Button {
show = true
} label: {
Text("PICKER")
}
Button {
guard let cgImage = image?.cgImage else { return }
// Core MLモデルの設定
guard let model = try? VNCoreMLModel(for: MNISTClassifier(configuration: MLModelConfiguration()).model) else { return }
// VNCoreMLRequestの作成
let request = VNCoreMLRequest(model: model) { request, error in
if let error = error {
print("Error: \(error.localizedDescription)")
return
}
guard let results = request.results as? [VNClassificationObservation],
let firstResult = results.first else { return }
// 解析した結果を取得
self.number = firstResult.identifier
}
// VNImageRequestHandlerの作成とリクエストの実行
let handler = VNImageRequestHandler(cgImage: cgImage)
do {
try handler.perform([request])
} catch {
print("Failed to perform request: \(error.localizedDescription)")
}
} label: {
Text("解析")
}
}.sheet(isPresented: $show, content: {
ImagePickerDialog(image: $image)
})
}
}
#Preview {
ContentView()
}
struct ImagePickerDialog: UIViewControllerRepresentable {
@Binding var image: UIImage?
@Environment(\.presentationMode) var presentationMode
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePickerDialog>) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 1
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_: PHPickerViewController, context _: UIViewControllerRepresentableContext<ImagePickerDialog>) {}
class Coordinator: NSObject, UINavigationControllerDelegate, PHPickerViewControllerDelegate {
var parent: ImagePickerDialog
init(_ parent: ImagePickerDialog) {
self.parent = parent
}
func picker(_: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.presentationMode.wrappedValue.dismiss()
if let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, _ in
guard let self = self else { return }
guard let image = image as? UIImage else { return }
DispatchQueue.main.sync {
self.parent.image = image
}
}
}
}
}
}
ちなみにこの「MNIST」が対応しているのは1桁の手書き数字のみなので2桁になると期待通りには取得できないので注意してください。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。