やること
前回記事(【Tensorflow・VGG16】転移学習による画像分類)で作成したモデルを使ってiPhone上で写真を撮影し、それを推定してみる。
手順概要
- Kerasで作成したモデル(.h5)を変換
- Xcodeでプロジェクトを作成
- 画面のパーツ作成 & コードの関連付け
- カメラへのアクセス許可 & カメラを起動するコードを追加
- ボタン(写真撮影)押下時の挙動
- モデルを読み込んで推定
動作環境
- macOS Catalina 10.15 beta
- Python 3.6.8
- keras 2.2.4
- coremltools 2.1.0
- Xcode 10.2.1
1. Kerasで作成したモデル(.h5)を変換
- 前回記事で作成したモデル(vgg16_transfer.h5)を使い、iOSで利用可能なモデルに変換する
モデルの変換
import coremltools
coreml_model = coremltools.converters.keras.convert(
'vgg16_transfer.h5',
input_names='image',
image_input_names='image',
output_names='Prediction',
class_labels=['apple', 'tomato', 'strawberry'],
)
coreml_model.save('./vgg16_transfer.mlmodel')
2. Xcodeでプロジェクトを作成
- Xcodeを起動し、「Create a new Xcode project」を選択
3. 画面のパーツ作成 & コードの関連付け
-
Xcode上で下記3つの画面のパーツを追加する
-
Image View(写真を表示する領域)
-
Text View(推定結果を表示する領域)
-
Button(カメラを起動するボタン)
-
こんな感じのレイアウトにしておく
- 各パーツの関連付けを実施する
- Image ViewとText Viewは「class ViewController」の下に追加(パーツを選択し、controlキーを押下しながら追加)
- Buttonは「override func viewDidLoad()」の下に追加
- Name(とConnection)はそれぞれ下記で設定
- Image View : imageDisplay(Connection : Outlet)
- Text View : predictionDisplay(Connection : Outlet)
- Button : takePhoto(Connection : Action)
各パーツの関連付け
# Image View
@IBOutlet weak var imageDisplay: UIImageView!
# Text View
@IBOutlet weak var predictionDisplay: UITextView!
# Button
@IBAction func takePhoto(_ sender: Any) {
}
4. カメラへのアクセス許可 & カメラを起動するコードを追加
- info.plistに「Privacy - Camera Usage Description」を追加し、カメラへのアクセスを許可する
- 下記2つの「delegate」(イベントを検知・処理)を追加する。(カメラを表示する画面、写真を保存した時に元の画面に戻る操作を行うことが可能)
- UIImagePickerControllerDelegate
- UINavigationControllerDelegate
- カメラの撮影画面を表示するために「imagePicker」という変数を追加
- アプリが起動した直後に「imagePicker」を初期化するコードを「viewDidLoad()」内に追加
カメラを起動(before)
class ViewController: UIViewController {
@IBOutlet weak var imageDisplay: UIImageView!
@IBOutlet weak var predictionDisplay: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
# ~~~ 省略 ~~~
}
カメラを起動(after)
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
@IBOutlet weak var imageDisplay: UIImageView!
@IBOutlet weak var predictionDisplay: UITextView!
var imagePicker: UIImagePickerController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
# 初期化
imagePicker = UIImagePickerController()
# delegate:イベントを受け渡しする変数
imagePicker.delegate = self
# sourceType : カメラからデータを撮るか、アルバムから読込か -> 今回はカメラ
imagePicker.sourceType = .camera
}
# ~~~ 省略 ~~~
}
5. ボタン(写真撮影)押下時の挙動
- 4.で作成しておいたimagePickerを表示するため、present関数を「takePhoto」で呼び出す
before
@IBAction func takePhoto(_ sender: Any) {
}
after
@IBAction func takePhoto(_ sender: Any) {
present(imagePicker, animated: true, completion: nil)
}
- 写真を撮り終わった後の処理を記載(imagePickerController)
- imageDisplayのimage属性を更新する
- infoに撮影した画像が入っているため、それを取り出してimageDisplayのimage属性にセットする
- infoのUIImagePickerController.InfoKeyに各種属性が入っているため、「originalImage」というプロパティを設定する('as? UIImage'でタイプを指定する)
- 画像を設定したら、先程のImagePickerを閉じる(dismiss)
after
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
imagePicker.dismiss(animated: true, completion: nil)
}
6. モデルを読み込んで推定
- mlmodelを読み込みして、そのモデルに画像ファイルを渡して推定する
- 1.で変換したモデル(vgg16_transfer.mlmodel)をXcodeのプロジェクトにドラッグ・アンド・ドロップする
- 下記ライブラリを追加する
- CoreML : 機械学習のライブラリ
- Vision : 画像ファイルを扱うライブラリ
ライブラリの追加
import CoreML
import Vision
- 推定処理を加える
- imagePickerControllerの後ろに推定処理(imagePrediction)を加える
- 引数に画像ファイルを指定(画面上に表示した内容と同じ(imageDisplay.image))
before
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
imagePicker.dismiss(animated: true, completion: nil)
}
after
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
imagePicker.dismiss(animated: true, completion: nil)
imagePrediction(image: (info[UIImagePickerController.InfoKey.originalImage] as? UIImage)!)
}
- 推定処理(imagePrediction)の関数を作成
- モデルの読み込み
モデルの読み込み
func imagePrediction(image: UIImage) {
guard let model = try? VNCoreMLModel(for: vgg16_transfer().model) else {
fatalError("Model not found")
}
- VNCoreMLRequestのインスタンスを生成
- 推定結果をresultsに格納する(推定結果は「VNClassificationObservation」という変数に入って返ってくる)
- results.firstにスコアの高いデータが格納されている
- predictionDiaplayに推定結果を表示する
- firstResult.confidence : 確率(少数で入っているので、100倍して%表示)
- firstResult.identifier : ラベル(推定結果)
モデルによる推定処理
let request = VNCoreMLRequest(model: model) {
[weak self] request, error in
guard let results = request.results as? [VNClassificationObservation],
let firstResult = results.first else {
fatalError("No results found")
}
DispatchQueue.main.async {
self?.predictionDisplay.text = "Accuracy: = \(Int(firstResult.confidence * 100))% \n\nラベル: \((firstResult.identifier))"
}
}
- requestに対する処理を記載
- ciImageというデータ型に変換できないと推定処理が出来ないため、取得出来ない場合はエラー
- Visionフレームワークで画像を使うためのimageHandlerという変数を宣言し、VNImageRequestHandlerを生成
- imageHandlerにrequestを実行させる
guard let ciImage = CIImage(image: image) else {
fatalError("Can't convert image.")
}
let imageHandler = VNImageRequestHandler(ciImage: ciImage)
DispatchQueue.global(qos: .userInteractive).async {
do {
try imageHandler.perform([request])
} catch {
print("Error")
}
}
- コードの全体は以下の通り
ViewController.swift
import UIKit
import CoreML
import Vision
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
@IBOutlet weak var imageDisplay: UIImageView!
@IBOutlet weak var predictionDisplay: UITextView!
var imagePicker: UIImagePickerController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .camera
}
@IBAction func takePhoto(_ sender: Any) {
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imageDisplay.image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage
imagePicker.dismiss(animated: true, completion: nil)
imagePrediction(image: (info[UIImagePickerController.InfoKey.originalImage] as? UIImage)!)
}
func imagePrediction(image: UIImage) {
guard let model = try? VNCoreMLModel(for: vgg16_transfer().model) else {
fatalError("Model not found")
}
let request = VNCoreMLRequest(model: model) {
[weak self] request, error in
guard let results = request.results as? [VNClassificationObservation],
let firstResult = results.first else {
fatalError("No results found")
}
DispatchQueue.main.async {
self?.predictionDisplay.text = "Accuracy: = \(Int(firstResult.confidence * 100))% \n\nラベル: \((firstResult.identifier))"
}
}
guard let ciImage = CIImage(image: image) else {
fatalError("Can't convert image.")
}
let imageHandler = VNImageRequestHandler(ciImage: ciImage)
DispatchQueue.global(qos: .userInteractive).async {
do {
try imageHandler.perform([request])
} catch {
print("Error")
}
}
}
}
ソースコード
https://github.com/hiraku00/ios_camera
('vgg16_transfer.h5'と'vgg16_transfer.mlmodel'は除外)