はじめに
こんにちは。watnowで主にiOSアプリをダラダラ作っているKUROです。
今回はFirebaseが提供しているMLKitという文字認識のAPIを使って文字を認識し、認識した文字を翻訳できるアプリを作りたいと思います。
今回作るもの
今回作るものは下記のようにカメラに映った文字認識して翻訳するアプリです。
使用した技術
MLKit
CropViewController
UIKit
ひらがなAPI
実際に作る
下準備
ライブラリの導入
MLKitとCropViewControllerはCocoaPodsを使って導入しました。
CocoaPodsでのライブラリ導入方法はこちらの記事を参考にしました。
UIを作る
今回はカメラを起動し、その場で撮影した画像から翻訳範囲を切り取り、翻訳をするアプリを作成するので、シンプルなUIを目指しました。
パーツとしては撮影した画像を表示するUIImageViewと翻訳を実施するUIButton、カメラを起動するUIButton、翻訳結果を表示するUILabelの4つのパーツを配置します。
ライブラリのインポート
UIKitはもちろん、今回使用するMLKit
、日本語と文字を認識するためのMLKitTextRecognitionJapanese
とMLKitTextRecognitionCommon
、カメラを使用するためのAVFoundation
そして撮影した画像を切り取るためのCropViewController
import UIKit
import MLKit
import MLKitTextRecognitionJapanese
import MLKitTextRecognitionCommon
import AVFoundation
import CropViewController
ついに本題へ
下準備も終えたので、本題のコードを書いていきましょう。
コードを書いていく!
まずは変数を宣言。
//カメラやCropViewControllerのDelegateを継承しておくのを忘れずに
class ViewController: UIViewController,UIImagePickerControllerDelegate,UINavigationControllerDelegate,CropViewControllerDelegate {
//パーツの宣言
@IBOutlet var imageView: UIImageView!
@IBOutlet var result: UILabel!
//翻訳結果を一時的に入れる変数
var kanziResult: String = ""
//日本語を認識するためのクラス
let japaneseOptions = JapaneseTextRecognizerOptions()
let japaneseTextRecognizer = TextRecognizer.textRecognizer(options:JapaneseTextRecognizerOptions())
次にカメラの起動処理の部分を書いていきます。
takePhoto
関数でカメラを起動し、imagePuckerController
関数でカメラを閉じた際にCropViewController
を起動し、画像を切り取りできるようにしています。
//カメラを起動するボタンのIBAction
@IBAction func takePhoto() {
let picker = UIImagePickerController()
picker.sourceType = .camera
picker.delegate = self
present(picker, animated: true, completion: nil)
}
//カメラを閉じた後の処理を記述
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true)
guard let image = info[.originalImage] as? UIImage else{
print("Image not found.")
return
}
let cropController = CropViewController(croppingStyle: .default, image: image)
cropController.delegate = self
cropController.customAspectRatio = CGSize(width: 100, height: 100)
//今回は使わないボタン等を非表示にする。
cropController.aspectRatioPickerButtonHidden = true
cropController.resetAspectRatioEnabled = true
cropController.rotateButtonsHidden = true
//cropBoxのサイズを固定する。
cropController.cropView.cropBoxResizeEnabled = true
//pickerを閉じたら、cropControllerを表示する。
picker.dismiss(animated: true) {
self.present(cropController, animated: true, completion: nil)
}
}
//撮影した画像を切り取ったものに置き換える
func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
updateImageViewWithImage(image, fromCropViewController: cropViewController)
}
//ImageViewに切り取った画像を表示させる
func updateImageViewWithImage(_ image: UIImage, fromCropViewController cropViewController: CropViewController) {
imageView.image = image
cropViewController.dismiss(animated: true, completion: nil)
}
カメラを起動して画像を撮影できたらいよいよ翻訳していきましょう。
今回は画像内の文字をひらがなに変えてくれる、ひらがなAPIを使用しました。基本的にはこちらの記事を参考にし、ひらがなに変える元の文字をMLKit
で抽出しました。
@IBAction func honyaku() {
let image = VisionImage(image: imageView.image!)
//ここで、画像からテキストを認識しています。
japaneseTextRecognizer.process(image) { result, error in
guard error == nil, let result = result else { return }
self.kanziResult = result.text
print("resultText: \(result.text)")
}
//ここからAPIにHTTPリクエストを送っていきます。
var request = URLRequest(url: URL(string: "https://labs.goo.ne.jp/api/hiragana")!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
//POSTするデータをURLRequestに持たせる
let postData = PostData(app_id: "6937c927439be919b9c5add398fcd7a18307788779ce23b4181eb3a9c5435b6f", request_id: "record003", sentence: kanziResult, output_type: "hiragana")
guard let uploadData = try? JSONEncoder().encode(postData) else {
print("json生成に失敗しました")
return
}
request.httpBody = uploadData
//APIへPOSTしてresponseを受け取る
let task = URLSession.shared.uploadTask(with: request, from: uploadData) {
data, response, error in
if let error = error {
print ("error: \(error)")
return
}
guard let response = response as? HTTPURLResponse,
(200...299).contains(response.statusCode) else {
print ("server error")
return
}
if response.statusCode == 200 {
guard let data = data, let jsonData = try? JSONDecoder().decode(Hiragana.self, from: data) else {
print("json変換に失敗しました")
return
}
print(jsonData.converted)
DispatchQueue.main.async {
self.result.text = jsonData.converted
}
} else {
print("サーバエラー ステータスコード: \(response.statusCode)\n")
}
}
task.resume()
}
}
//APIから受け取る結果の構造体
struct Hiragana:Codable {
var request_id: String
var output_type: String
var converted: String
}
//APIにPOSTする際の構造体
struct PostData: Codable {
var app_id:String
var request_id: String
var sentence: String
var output_type: String
}
ここまでできたら、一度実行してみてください。おそらく、もうすでに翻訳ができていると思います。
ただ、一つ気づくと思います。結果のLabelに収まらなくね?と。そうなった場合は以下のコードを追加してください。自動的に結果をLabelに収めてくれます。
override func viewDidLoad() {
result.adjustsFontSizeToFitWidth = true
super.viewDidLoad()
}
完成!
終わりに
今回はMLKitを使った翻訳アプリを作ってみました。
僕が所属するwatnowではこのように簡単なモバイルアプリから、1万ユーザーを超えるバスアプリまで色々なアプリやサービスを作っています。
もし、少しでも興味がありましたら是非Twitter覗いてみてください。
ここまで読んでいただきありがとうございました。