iOS 15ではOCR機能がOSに標準搭載されます(Live Text)。これはオフラインの機械学習モデルを使っているので、ネットワークに画像データが乗ったり、プライバシーの心配なく使えるのが魅力です。しかし、リリース時点では日本語に対応していません。
そこで使ってみたのがGoogleのML Kitになります。まだベータ版なのですが、バージョン2から日本語にも対応しています。また、ML Kitはオフライン動作も可能で、Google Cloudへの登録なども不要です。これを使えばLive Text的なことはできそうです。
今回はこのML Kitを使って、以前作成したNCMBのSwift SDKを用いたカメラメモアプリのメモを自動入力する機能を追加してみます。写真の中にあるテキスト情報をメモに使えば、後で検索したりコピーしたりするのに便利そうです。
コード
今回のデモアプリのコードはNCMBMania/Swift_CameraMemo_MLKit: Swift SDKによるカメラメモアプリにML Kitを追加した版ですにアップロードしてあります。
利用したライブラリ・SDK
今回は以下のライブラリ・SDKを利用しています。
言語はSwiftです。
カメラメモアプリの機能について
カメラメモアプリは、写真を撮影して、その写真データをファイルストアにアップロードします。そして、写真の中にあるテキスト情報をML Kitを使って抽出し、メモとして写真に紐付けて保存します。
利用する機能について
カメラメモアプリで利用するNCMBの機能は次の通りです。以前あった認証機能は省いています。
- データストア
- ファイルストアと紐付けたメモを保存
- 保存しているメモを取得
- ファイルストア
- ファイルアップロード
- ファイルダウンロード
画面について
カメラメモアプリでは次の画面(View)を用意しています。
- ContentView
タブバーで2つの画面を読み込んでいます。1つは写真撮影とアップロードする画面(InputView)、もう1つは写真一覧画面(ImageView)です。 - Imagepicker
写真の撮影またはフォトライブラリから写真を選択します。 - InputView
タブバーから読み込まれます。写真の撮影とテキスト抽出、アップロードを行います。 - ImageView
NCMBへアップロードした写真を一覧表示します。 - ModalView
写真一覧をタップした際にモーダル表示します。 - GridImageView
写真一覧のグリッド表示用ビューです。
ContentView
タブバーで2つの画面を読み込んでいます。
struct ContentView: View {
var body: some View {
TabView {
InputView()
.tabItem {
VStack {
Image(systemName: "photo")
Text("Photo")
}
}.tag(1)
ImageView()
.tabItem {
VStack {
Image(systemName: "rectangle.grid.2x2")
Text("Photos")
}
}.tag(2)
}
}
}
Imagepicker
写真の撮影またはフォトライブラリから写真を選択します。コードは【SwiftUI】カメラ機能の実装方法【撮影画像とライブラリー画像の利用】から拝借しています。ありがとうございます。
InputView
写真の選択やML Kitによる日本語抽出、NCMBへの保存処理を行います。
struct InputView: View {
// イメージピッカーの表示用フラグ
@State var showingPicker = false
// 選択した写真が入る
@State var image: UIImage?
// 認識したテキストが入る
@State var recognizeText: String?
// ML Kitのテキスト認識用オブジェクト
@State var textRecognizer = TextRecognizer.textRecognizer(options: JapaneseTextRecognizerOptions())
// アラートのタイトル
@State var title: String?
// アラートのメッセージ
@State var message: String?
// アラートの表示用フラグ
@State private var showingAlert = false
var body: some View {
VStack {
HStack {
Button("写真を撮る", action: {
showingPicker = true
})
Spacer()
Button("保存する", action: {
savePhoto()
})
.disabled(image == nil)
}
if let image = image {
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
Text(recognizeText ?? "")
}
}
.sheet(isPresented: $showingPicker) {
ImagePickerView(image: $image, sourceType: .library)
}
.onChange(of: image, perform: { value in
getText()
})
.alert(isPresented: $showingAlert) {
Alert(title: Text(title!), message: Text(message!))
}
}
// テキスト抽出を行います
func getText() -> Void {
// 後述
}
// NCMBへ写真とテキストを保存します
func savePhoto() -> Void {
// 後述
}
}
ImageView
ファイルストアから画像を取得します。
struct ImageView: View {
@State var memos: [NCMBObject] = []
var columns: [GridItem] = Array(repeating: .init(.fixed(200)), count: 2)
var body: some View {
ScrollView(.vertical) {
LazyVGrid(columns: columns, alignment: .center, spacing: 200) {
ForEach (memos, id: \.objectId) { memo in
GridImageView(memo: memo)
}
}
.onAppear() {
getAllPhotos()
}
}
}
func getAllPhotos() {
// 写真をファイルストアから取得(後述)
}
}
ModalView
写真をタップした際にモーダル表示します。
struct ModalView: View {
@Binding var isActive: Bool
@State var memo: NCMBObject
@State var imageData: Data
var body: some View {
HStack {
Spacer()
VStack {
Spacer()
GeometryReader { geometry in
Image(uiImage: UIImage(data: imageData)!)
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.height)
.clipped()
}
if let text: String = memo["text"] {
Text(text).padding()
}
Button("閉じる") {
isActive = false
}
Spacer()
}
Spacer()
}
.padding()
.background(Color(.black))
}
}
GridImageView
写真一覧のグリッド表示用です。
struct GridImageView: View {
@State private var fileName: String = ""
@State private var imageData: Data? = .init(capacity:0)
@State var memo: NCMBObject? = nil
@State private var isShowing = false
var body: some View {
GeometryReader { geometry in
if imageData?.count ?? 0 > 0 {
Image(uiImage: UIImage(data: imageData!)!)
.resizable()
.scaledToFill()
.frame(width: geometry.size.width, height: geometry.size.width)
.clipped()
} else {
Rectangle().fill(Color.clear)
}
}.onAppear() {
loadImage()
}.onTapGesture {
isShowing = true
}.fullScreenCover(isPresented: $isShowing) {
ModalView(isActive: $isShowing, memo: memo!, imageData: imageData!)
}
}
func loadImage() {
// 写真データを読み込みます(後述)
}
}
SDKの導入法
NCMBのSwift SDKはCocoaPods向けに提供しています。Swift Packagesでもインストールできますが、ML KitがCocoaPods向けだったので、一緒にインストールします。Xcodeで新しいiOSプロジェクトを作ったら、Podfileを用意します。
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'TextRecognition' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for TextRecognition
pod 'GoogleMLKit/TextRecognitionJapanese', '2.3.0'
pod 'NCMB', :git => 'https://github.com/NIFCLOUD-mbaas/ncmb_swift.git'
end
そして pod install
でSwift SDKとML Kitをインストールします。
Swift SDKの初期化について
現在、新規でiOSアプリを作成すると、InterfaceがSwiftUI、Life CycleがSwiftUI Appとなっています。この状態で作ると AppDelegate.swift
はなく、 (アプリ名)App.swift
というファイルが作られます。
この場合、まずSwift SDKを読み込みます。
import SwiftUI
import NCMB // 追加
そして var body
の上に @Environment
を追加します。
@Environment(\.scenePhase) private var scenePhase // 追加
var body: some Scene {
最後に WindowGroup
に onChange
を追加します。このonChangeの中で初期化処理を行います。
WindowGroup {
ContentView()
}
.onChange(of: scenePhase) { scene in
switch scene {
case .active:
NCMB.initialize(applicationKey: "YOUR_APPLICATION_KEY", clientKey: "YOUR_CLIENT_KEY")
case .background: break;
case .inactive: break;
@unknown default: break;
}
}
これで利用可能になります。
まとめ
今回は必要な画面の内容と、NCMBのSwift SDK導入までを紹介しました。次回はML Kitによるテキスト抽出と写真アップロードを解説します。